MXNet/Gluon第二課:過擬合,多層感知機,GPU和卷積神經網路筆記
關於GPU的知識,在這裡就不講了,到時候,我會專門寫一個有關什麼是GPU,為什麼GPU目前在深度學習流行的時代這麼的火,它的原理是什麼。以及什麼是CPU,CPU和GPU的區別和聯繫是什麼。
過擬合和欠擬合:
首先先來講講什麼是過擬合和欠擬合。
在前面模式識別專欄中,我們就學習到了什麼是overfitting問題,什麼是underfitting問題。
這裡,沐神通過我們日常生活中經常見到的一個例子,來說明了這個問題。
就是在我們考試前,通過刷大量的模擬題,甚至對於有些題目,我們只要看一眼,就能夠把他們背下來,但是,在考試的時候,依舊容易考掛,那麼,這個就是overfitting的問題。
如果,我們能夠從模擬題中總結出一套比較通用的解題模式,那麼,在接下來的考試中,我們就能運用這套解題模式輕鬆的通過考試,並且取得非常不錯的分數。
其實,機器的學習過程和考試的這個過程很類似。
機器的學習模型是說也可能由於自身不同的訓練量和不同的學習能力而產生不同的測試效果。
訓練誤差(模考成績)和泛化誤差(高考成績)
在我們通常構建一個ml問題的時候,需要我們在訓練集上不斷的update我們的parameters。然後,將這個在training set上train出來的model,放進一個與training set沒有任何交集的testing set中去測試,並且根據model在testing set上的表現來衡量它的好壞。那麼,model在training set上表現出來的誤差就叫做訓練誤差,而在testing set上表現出來的誤差就叫泛化誤差。
其實關於誤差的計算,我們前面已經有所引入,
- 在線性回歸中,我們可以通過平方誤差損失函數來計算。
- 在多類邏輯回歸中,我們可以通過crossentropy函數來計算。
之所以要了解訓練誤差和泛化誤差,是因為在ML的很多問題中,我們都可以通過這兩個基本的知識得到解釋,比如這節要講的overfitting和underfitting問題。
在理論的研究中,必須要基於一定的假設,不然理論的研究有時候會看起來不成立。
重要假設:訓練數據集和測試數據集里的每一個數據都是從同一個概率分布中獨立的生成的
這也就是我們在概率論中所學到的,獨立同分布假設。
基於以上獨立同分布假設,我們可以給定任意一個機器學習模型和參數,它的訓練誤差的期望和泛化誤差的期望都是一樣的。
但是,我們知道,模型的參數並不是一開始就定義好的,而是通過不斷的在訓練數據集上通過學習而update到一個看似最optim的狀態,使得整個的training loss在訓練的過程中不斷降低。所以說,如果模型參數是通過訓練數據一步一步update出來的,那麼其訓練誤差一定不會超過泛化誤差。那麼,這句話的等價狀態就是,通過在訓練數據集上學到的參數,會使模型在訓練集上的表現不亞於測試集上的表現
那麼,聰明的讀者一定就能夠看出,訓練集上誤差的降低並不一定意味著測試集上誤差的降低。而我們在構建ML系統的過程中,就是希望我們能夠得到一個訓練集上誤差足夠低,測試集上的泛化誤差足夠低的model。
欠擬合和過擬合
有了前面這些知識的鋪墊後,我們在回過頭來看看什麼是overfitting,什麼是underfitting問題。
- 過擬合:機器學習模型無法得到較低的訓練誤差
- 過擬合:機器學習模型在訓練集上的誤差要比測試集上的誤差低很多
那麼,出現了這兩種問題,我們該怎麼解決呢?這裡就沐神的教程來看,我們主要討論兩個因素:訓練數據集的大小和模型的選擇
模型選擇
在開頭的時候,通過學生考試的例子,表達了每個學生具有不同的學習能力。其實對於model來說,不同的model也具有不同的擬合能力,就拿多項式擬合問題來說,高階的多項式往往要比低階的多項式在相同訓練數據集上具有更好的擬合能力,也既具有較低的訓練誤差。
所以,在給定訓練集的情況下,較低擬合能力的模型往往更容易發生欠擬合問題。
較高擬合能力的模型往往更容易發生過擬合問題
模型的擬合能力(複雜度)和誤差之間的關係圖如下所示:
數據訓練集的大小
如果訓練數據集過小,特別是比模型參數數量小很多的時候,過擬合很容易發生。除此之外,泛化誤差不會隨著訓練集樣本個數的增加而增大
通過上面的圖,我們可以看出來,隨著訓練集的增多,深度模型的表現能力是要遠遠強於簡單模型的,並且最終的loss是會收斂的,不會隨著數據集的增多而變化。
接下來,讓我們就動手寫寫代碼,來看看訓練集的大小和模型複雜的對於擬合的影響情況。
多項式擬合
我們通過一個多項式擬合的例子,來動手寫代碼分析分析。
給定一個標量集合點x和對應的標量的目標值y,多項式擬合的目的就是找到一個K階多項式。
其向量由w和偏置b組成。其可以用來更好的近似每一個樣本x和y。
當我們使用平方誤差損失函數的時候,且k=1,那麼這個時候就是前面講過的線性回歸問題。
創建數據集
這裡我們使用人工數據集來簡化問題本身。
具體來說,我們使用如下的二階多項式來生成每一個數據樣本
其中的noise是均值為0,標準差為0.1的正太分布。
需要注意的是,我們通過上面的這個表達式來一次性生成訓練集合測試集,每個數據集的大小都是100.
代碼:
from mxnet import ndarray as ndfrom mxnet import autogradfrom mxnet import gluonnum_train = 100num_test = 100true_w = [1.2, -3.4, 5.6]true_b = 5.0
下面生成數據:
x = nd.random.normal(shape=(num_train + num_test, 1))X = nd.concat(x, nd.power(x, 2), nd.power(x, 3))y = true_w[0] * X[:, 0] + true_w[1] * X[:, 1] + true_w[2] * X[:, 2] + true_by += .1 * nd.random.normal(shape=y.shape)(x:, x[:5], X:, X[:5], y:, y[:5])
通過output,我們可以看出,x表示的是一次項,X表示的是一次項,二次項,三次項。
y表示的是這些項的組合所得到的最終數值
這時候,我們定義一個train函數,只要在後面進行不同實驗的時候,我們只需要每次調用train函數,然後給與不同的參數就行了。
%matplotlib inline
表示的是通過matplotlib畫出來的圖標,將其內嵌到jupyter notebook中來。
%matplotlib inlineimport matplotlib as mplmpl.rcParams[figure.dpi]= 120import matplotlib.pyplot as plt
這段代碼是說,我們通過調用python中的matplotlib來畫圖,其中的dpi指的是dots per inch,也就是我們經常所說的解析度。將畫出來的圖片的解析度設置成為120*120
def train(X_train, X_test, y_train, y_test): # 線性回歸模型 net = gluon.nn.Sequential() with net.name_scope(): net.add(gluon.nn.Dense(1)) net.initialize() # 設一些默認參數 learning_rate = 0.01 epochs = 100 batch_size = min(10, y_train.shape[0]) dataset_train = gluon.data.ArrayDataset(X_train, y_train) data_iter_train = gluon.data.DataLoader( dataset_train, batch_size, shuffle=True) # 默認SGD和均方誤差 trainer = gluon.Trainer(net.collect_params(), sgd, { learning_rate: learning_rate}) square_loss = gluon.loss.L2Loss() # 保存訓練和測試損失 train_loss = [] test_loss = [] for e in range(epochs): for data, label in data_iter_train: with autograd.record(): output = net(data) loss = square_loss(output, label) loss.backward() trainer.step(batch_size) train_loss.append(square_loss( net(X_train), y_train).mean().asscalar()) test_loss.append(square_loss( net(X_test), y_test).mean().asscalar()) # 列印結果 plt.plot(train_loss) plt.plot(test_loss) plt.legend([train,test]) plt.show() return (learned weight, net[0].weight.data(), learned bias, net[0].bias.data())
上面這段代碼就是通過gluon來進行train的函數,函數接受四個參數,分別為
- X_train
- X_test
- y_train
- y_test
- 分別表示用來訓練的樣本(X_train,y_train)和用來評估model性能的測試樣本(X_test,y_test)。
然後,我們來一行一行的解讀下這段代碼吧。
net = gluon.nn.Sequential() with net.name_scope(): net.add(gluon.nn.Dense(1)) net.initialize()
上面這段代碼是說,我們首先建立一個只有一層的神經網路,這個一層的神經網路在前面的學習中,我們已經知道了,他就相當於一個linear regression。
learning_rate = 0.01 epochs = 100 batch_size = min(10, y_train.shape[0]) dataset_train = gluon.data.ArrayDataset(X_train, y_train) data_iter_train = gluon.data.DataLoader( dataset_train, batch_size, shuffle=True)
上面這段代碼表明,
- 此時model的學習率為0.01,並且需要對所有的數據反覆迭代100次。
- 每次的batch_size取10
- 關於訓練集的構造,通過gluon.data.ArrayDataset來完成
- 每個batch的迭代訓練通過gluon.data.DataLoader來完成,其中的參數表示,dataset_train為訓練集,batch_size表示每個batch所含有的數據的多少,shuffle表示通過亂序來學習,會比通過順序的學習得到更強的capacity
關於trainer,我們默認為使用sgd的方法,和使用MSELoss,代碼如下:
trainer = gluon.Trainer(net.collect_params(),sgd,{learning_rate:learning_rate})square_loss = gluon.loss.L2Loss()
常規操作,不多解釋了。
接下里,就是有關訓練和測試損失
train_loss = []test_loss = []for e in range(epochs): for data,label in data_iter_train: with autograd.record(): output = net(data) loss = square_loss(output,label) loss.backward() trainer.step(batch_size) train_loss.append(square_loss(net(X_train),y_train).mean().asscalar()) test_loss.append(square_loss(net(X_test),y_test).mean().asscalar())
這裡定義了兩個loss的list
- 一個是train_loss
- 一個是test_loss
- 通過觀察這兩個loss之間的關係,我們就可以得到訓練數據集對於過擬合和欠擬合的影響情況。
然後,我們將結果的曲線畫出來,橫坐標表示的是epoch的次數,縱坐標表示的是loss的數值
plt.plot(train_loss) plt.plot(test_loss) plt.legend([train,test]) plt.show() return (learned weight, net[0].weight.data(), learned bias, net[0].bias.data())
接下來,我們就把上面定義好的模型用在我們的分析實驗中。
三階多項式擬合(正常,介於overfitting和underfitting之間)
我們先使用與數據生成函數同階的三階多項式擬合,實驗表明,這個模型的訓練誤差和測試誤差都很低,最終得到的parameter參數,也和我們的真實參數一致。
通過在控制台使用如下代碼:
train(X[:num_train,:],X[num_train:,:],y[:num_test],y[num_test:])
線性回歸(欠擬合)
我們再試試線性擬合,很明顯,我們知道,線性模型是沒辦法很好的擬合非線性模型的,這個時候,模型的誤差就會相當大。
train(x[:num_train,:],x[num_train:,:],y[:num_test],y[num_test:])
注意到這裡是使用的x,而不是上面三次擬合中的X,所以,僅僅是一次項的數值拿出來做擬合。而沒有二次項,三次項。
訓練量不足(過擬合)
通過我們的分析,我們能夠知道,即使我們使用了與目標函數相同階數的數據來做擬合,那麼當我們的訓練量遠遠跟不上的時候,也是沒辦法得到一個近似完美的fit的,這個時候,也會出現overfitting的問題。
在這裡,沐神僅僅使用了2個訓練樣本來訓練,很顯然,這裡的訓練數據太少了,甚至少於模型參數的數量,那麼肯定會出現overfitting問題。
train(X[0:2,:],X[num_train:,:],y[0:2],y[num_train:])
結論:
- 訓練誤差的降低並不一定表示測試誤差的降低
- 過擬合和欠擬合都是需要盡量避免的。我們要十分注意模型的選擇問題和訓練樣本的個數問題,這在我們處理ml問題中是非常重要的兩個話題。
正則化---從0開始
通過前面學習,我們已經知道了在ML相關問題的研究中,自然而然不可避免的就是overfitting的問題。所以,接下來,我們就來通過gluon,看看正則化項對於我們training的影響是什麼。
和上一部分一樣,我們同樣引入一個高維的線性回歸模型,訓練樣本的個數非常少(20個),並且模型的維度特別大(200維度)
from mxnet import ndarray as ndfrom mxnet import autogradfrom mxnet import gluonimport mxnet as mxnum_train = 20num_test = 100num_inputs = 200true_w = nd.ones((num_inputs, 1)) * 0.01true_b = 0.05X = nd.random.normal(shape=(num_train + num_test, num_inputs))y = nd.dot(X, true_w) + true_by += .01 * nd.random.normal(shape=y.shape)X_train, X_test = X[:num_train, :], X[num_train:, :]y_train, y_test = y[:num_train], y[num_train:]
首先通過調入相關的功能,然後創建我們的數據集,其中訓練集的個數為20個,測試集的個數為100個,模型的維度是200維的。
import matplotlib.pyplot as pltimport numpy as npbatch_size = 1dataset_train = gluon.data.ArrayDataset(X_train, y_train)data_iter_train = gluon.data.DataLoader(dataset_train, batch_size, shuffle=True)square_loss = gluon.loss.L2Loss()def test(net, X, y): return square_loss(net(X), y).mean().asscalar()def train(weight_decay): epochs = 10 learning_rate = 0.005 net = gluon.nn.Sequential() with net.name_scope(): net.add(gluon.nn.Dense(1)) net.collect_params().initialize(mx.init.Normal(sigma=1)) # 注意到這裡 wd trainer = gluon.Trainer(net.collect_params(), sgd, { learning_rate: learning_rate, wd: weight_decay}) train_loss = [] test_loss = [] for e in range(epochs): for data, label in data_iter_train: with autograd.record(): output = net(data) loss = square_loss(output, label) loss.backward() trainer.step(batch_size) train_loss.append(test(net, X_train, y_train)) test_loss.append(test(net, X_test, y_test)) plt.plot(train_loss) plt.plot(test_loss) plt.legend([train,test]) plt.show() return (learned w[:10]:, net[0].weight.data()[:,:10], learned b:, net[0].bias.data())
這裡,有個區別就是,在trainer裡面,又包含了一個叫做wd的參數,wd就是我們在深度學習中的weight decay,這是說,它的作用其實和一般做ML問題中的, 是一樣的,都是用來懲罰我們的模型參數,調節過擬合問題。
我們在控制台,通過使用不同的weight decay,就可以得到不同weight decay下,train_loss和test_loss的變化情況,具體問題,具體分析。
小夥伴們可以自己動手試試。。。
使用GPU來計算
這一部分的內容就先不講了,由於我目前只有一台mac電腦,沒有多餘的卡來用,等到了公司,有了GPU,我在來更新有關GPU的相關知識的學習。
卷積神經網路 --- 從0開始
卷積神經網路可以說是deep learning,尤其是計算機視覺中最重要的部分,如果讀者能夠搞懂CNN的幾乎所有知識點,那麼,我們就可以搞懂目前CV領域80%的工作,知道他們是在幹什麼,還有什麼是沒做的,需要我們去做。
在之前沐神的教程中,我們知道,在將一張圖片輸入到神經網路之前,會將其轉換成相應的向量形式,這樣做有兩個不足的地方:
- 在原先圖片中,兩個位置很近的像素點,在經過flatten後,就變得相當遠了,從而破壞了他們之間的位置關係
- 對於輸入的圖片大小,假如我們輸入的是256*256*3的圖像,輸出層是1000,那麼,我們最終模型的參數量非常大(256*256*3*1000)。而實際很多照片比256*256還要大
針對上面兩個問題,我們就引出了CNN。
CNN的作用就是:
- 第一點,首先要保證輸入圖像之間各個像素的位置空間關係
- 第二點,減少參數的數量,使得訓練能夠正常
那什麼是卷積神經網路呢?
卷積神經網路,就是主要通過卷積層構建的網路。
為了對於CNN這個概念有一個更深層的理解和認識,我們還是從頭開始自己搭建一個CNN,並不藉助gluon。稍後,可以在寫一個有關gluon版本的CNN
from mxnet import ndarray as ndfrom mxnet import autograd# from mxnet import gluonimport mxnet as mx# batch * channel * height * width# 假設這裡的batch和channel的大小都為1# 權重格式: output_channels * in_channels * height * width, 這裡的input_filter和output_filter都為1w = nd.arange(4).reshape((1,1,2,2))b = nd.array([1])data = nd.arange(9).reshape((1,1,3,3))out = nd.Convolution(data,w,b,kernel = w.shape[2:],num_filter = w.shape[0])print (input:,data,
weight:,w,
bias:,b,
output:,out)
上述代碼中
- 首先定義了一個w表示我們的權重,其中的分配為1*1*2*2,表示1個output,1個input(用來表示filter的channel個數,其必須與輸入的channel個數一致,不然在後面的卷積運算中會出錯),並且大小為2*2 的格式
- num_filter表示的需要幾個濾波器,有多少個濾波器,那麼feature map的個數就是多少,所以說,要求的out_channels個數等於num_filter
- 然後我們的偏置b默認為1
- 接下來,就是我們的輸入圖片的格式,是一個1*1*3*3的,那麼,表示的就是一個一張,單通道,大小為3*3的圖片。
- 通過print,我們再來分析分析
其中,每個元素位置的值,必須會算。
- 拿output中的20來說:0*0+1*1+3*2+4*3+1 = 20
- 其餘數值的計算同理可得。。。
上面的這種形式是,padding默認為0,stride默認為1的情況,其實,我們還可以自由調節padding的個數,stride的大小。
out = nd.Convolution(data,w,b,kernel = w.shape[2:],num_filter = w.shape[1],stride = (2,2),pad = (1,1))print(input:,data,
weight:,w,
bias:,b,
output:,out)
結果如上所示。
當輸入數據有多個通道的時候,每個通道會有對應的權重,然後會對每個通道做卷積之後在通道之間求和
相應的代碼:
w = nd.arange(8).reshape((1,2,2,2))data = nd.arange(18).reshape((1,2,3,3))out = nd.Convolution(data,w,b,kernel = w.shape[2:],num_filter = w.shape[0])print (input:,data,
weight:,w,
bias:,b,
output:,out)
通過print的信息
我們可以看出:
- 因為data是(1 * 2 * 3 * 3)的,所以說,它是一個雙通道,大小為3*3的矩陣
- w是(1 * 2 * 2 * 2)的,表明in_channels必須和data的雙通道保持一致,所以為2。1表示的是out_channel,也就是說,只輸出一個feature map,需要的num_filter的個數也就是1,那最終的結果就是1。2*2表示的是濾波器的大小。
同樣,你想怎麼調整這些參數,就好好的學習下data和w這兩個矩陣中,各自對應的4個參數的意義。
池化層(pooling)
因為,我們每次在做卷積的過程中,都是一個窗口一個窗口的在整張圖像中滑動,對於位置信息相當敏感。這時候,就需要引入池化層來解決這個問題。也就是說,池化層理論上具有一定的旋轉不變性和平移不變性。
data = nd.arange(18).reshape((1,2,3,3))max_pool = nd.Pooling(data = data,pool_type = max,kernel = (2,2))avg_pool = nd.Pooling(data = data,pool_type = avg,kernel = (2,2))print (input:,data,
max_pooling:,max_pool,
avg_pooling:,avg_pooling)
通過print,我們可以看到
接下里,我們來通過gluon看看怎麼構建一個CNN。
我們還是以LeNet為例,LeNet的結構是一個具有2層卷積層,2層全連接層的組合。
定義模型:
我們知道在gluon中構建model的時候,不需要去定義model的輸入,因為gluon底層會幫我們計算出來input是什麼,這就很智能了,我們要做的就是明確model的output就行了。
from mxnet import ndarray as ndfrom mxnet import autogradfrom mxnet import gluonfrom mxnet.gluon import nnnet = gluon.nn.Sequential()with net.name_scope(): net.add( nn.Conv2D(channels = 20,kernel_size = 5,activation = relu), nn.MaxPool2D(pool_size = 2,strides = 2), nn.Conv2D(channels = 50,kernel_size = 3,activation = relu), nn.MaxPool2D(pool_size = 2,strides = 2), nn.Flatten(), nn.Dense(128,activation = relu), nn.Dense(10) )
上述代碼就是通過gluon來定義的CNN,比起用mxnet從0開始寫,是不是感覺非常簡單呢?
代碼解讀:
- 首先還是通過nn.Sequential()搭建一個大體的網路
- 然後第一個卷積層用nn.Conv2D(channels = 20, kernel_size = 5,activation = relu)表示輸出的feature map個數是20個,kernel_size的大小為5*5,激活函數採用relu來進行
- 池化層採用最大池化,nn.MaxPool2D(pool_size = 2, strides = 2)表示的是池化層的窗口是2*2的,每次跨越的步長為2
- 最後,通過nn.Flatten()將其拉成一個向量,方便最後一層全連接層處理。
- 全連接層的輸出個數是10
好了, 發現gluon的好處了吧,簡直和pytorch一樣簡介。關於具體的gluon和pytorch,TensorFlow,caffe等框架的比較,我們在後續的學習中慢慢補充和跟進。
第二節課的內容就到這裡結束了,沐神留了一些練習,希望小夥伴們能夠自己動手獨立完成,learning by doing。
推薦閱讀:
※InsightFace - 使用篇, 如何一鍵刷分LFW 99.80%, MegaFace 98%.
※MXNet入坑(二)- Engine 「依賴引擎」與它的核心演算法
※為什麼選擇 MXNet?
※國內哪些公司在用caffe、torch、TensorFlow、paddle等框架,哪些在用自研框架?
※Bring TensorBoard to MXNet
TAG:深度學習DeepLearning | MXNet |