本節(jié)從神經網絡的發(fā)展著手,依次介紹激活函數(shù)稀拐、前向算法、損失函數(shù)丹弱、反向傳播算法以及PyTorch中的數(shù)據處理德撬,最后使用PyTorch解決一個iris數(shù)據集上的多分類問題。通過本節(jié)的學習躲胳,我們將對整個神經網絡的流程有一個比較全面的認識砰逻。
1.神經元與神經網絡
神經元最早是生物學上的概念,它是人腦中的最基本單元泛鸟。人腦中含有大量的神經元,米粒大小的腦組織中就包含超過10000個神經元踊东,不同的神經元之間相互連接北滥,每個神經元與其他的神經元平均有6000個連接。一個神經元接收其他神經元傳遞過來的信息闸翅,通過某種方式處理后再傳遞給其他神經元再芋。下圖就是生物神經元的示意圖。
一個神經元由細胞核坚冀、樹突济赎、軸突和軸突末梢等組成。其中樹突有很多條记某,且含有不同的權重司训,主要用來接收從其他神經元傳來的信息;接收到的信息在細胞整合后產生新的信息傳遞給其他神經元液南;而軸突只有一條壳猜,軸突末端有許多神經末梢,可以給其他神經元傳遞信息滑凉。神經末梢跟其他神經元的樹突連接统扳,從而傳遞信號,這個鏈接的位置在生物學上叫做“突觸”畅姊。
在對人腦工作機理研究的基礎上咒钟,1943年McCulloch和Pitts參考了生物神經元的結構,最早提出了人工神經元模型若未,即MP神經元模型朱嘴。MP神經元從外部或者其他神經元接受輸入信息,通過特定的計算得到輸出結果陨瘩。如下圖所示腕够,輸入X1,X2级乍,對應權重w1,w2,偏置b帚湘,通過加權求和代入f(z)函數(shù)中玫荣,得到輸出Y。這個函數(shù)f(z)就是激活函數(shù)大诸。人工神經元是人工神經網絡中最基本的單元捅厂。
MP模型雖然簡單,但卻是構建神經網絡的基礎资柔。神經網絡(Neural Network焙贷,NN)是人工神經網絡(Artificial Neural Network,ANN)的簡稱贿堰,由很多神經元組成辙芍。神經網絡是對人腦工作機制的一種模仿。在MP模型中羹与,權重的值都是預先設置的故硅,因此不能學習。1949年Hebb提出了Hebb學習率纵搁,認為人腦神經細胞的突觸上的強度是可以變化的吃衅。于是研究者開始考慮使用調整權值的方法來讓機器學習。
1958年腾誉,Rosenblatt提出了由兩層(輸入層和輸出層)神經元組成的神經網絡徘层,名叫“感知機”,如下圖所示利职。從結構上趣效,感知機把神經元中的輸入變成了單獨的神經元,成為輸入單元眼耀。與神經元模型不同英支,感知機中的權重是通過訓練得到的。感知機類似一個邏輯回歸模型哮伟,可以做線性分類任務干花,是首個可以學習的人工神經網絡。這為后面的學習算法奠定了基礎楞黄,可以說感知機是神經網絡的基石池凄。但由于它只有一層功能神經單元,因此學習能力非常有限鬼廓。Minsky在1969年出版了一本名為“Perceptron”的書肿仑,使用數(shù)學方法詳細地證明了感知機的弱點,尤其是感知機對XOP(異或)這樣的簡單分類任務都無法解決。
感知機是前饋神經網絡的一種尤慰,前饋神經網絡實最早起也是最簡單的一種人工神經網絡馏锡。前饋神經網絡包含多個神經元,被安排在不同的層伟端,即輸入層杯道、隱含層、輸出層责蝠,其中隱含層的個數(shù)有0個或多個党巾。在前饋神經網絡中,信息在神經元上的傳播方向只有一個——向前霜医,即從輸入層經過隱含層到達 輸出層齿拂,神經元之間沒有循環(huán)結構。感知機就是沒有隱含層的前饋神經網絡肴敛。擁有一個或多個隱含層的前饋神經網絡稱為“多層感知機”(Multi Layer Perceotron署海,MLP),如下圖所示医男。
多層感知機可以很好地解決非線性可分問題叹侄,我們通常將多層感知機這樣的多層結構稱為神經網絡。
所謂神經網絡的訓練或者學習昨登,其主要目的就是通過學習算法得到神經網絡解決指定問題所需的參數(shù)。這里的參數(shù)包括各層神經元之間的連接權重以及偏置等贯底。參數(shù)的確定需要神經網絡通過訓練樣本和學習算法來迭代找到最優(yōu)參數(shù)組合丰辣。說起神經網絡的學習算法,不得不提其中最杰出禽捆、最成功的代表——反向傳播算法笙什。
2.激活函數(shù)
前面提到過在神經元中,輸入信息通過一個非線性函數(shù)y=f(x)產生輸出胚想,這個函數(shù)決定哪些信息保留以傳遞給后面的神經元琐凭,這個函數(shù)就是激活函數(shù)(Activation Function),又被稱為非線性函數(shù)(Nonlinearity Function)浊服,對于給定的輸入统屈,激活函數(shù)執(zhí)行固定的數(shù)學運算得到輸出結果,根據輸出結果控制輸入信息的保留程度牙躺。
激活函數(shù)要具有以下性質:
- 非線性:當激活函數(shù)是線性時愁憔,一個兩層的神經網絡基本上就可以表達所有的函數(shù)了,恒等函數(shù)f(x)=x不滿足這個條件孽拷,如果MLP中使用恒等激活函數(shù)吨掌,那么整個神經網絡跟單層的神經網絡是等價的。為什么需要非線性呢?因為線性的疊加還是線性膜宋,而線性函數(shù)的表達能力有限窿侈,只能做線性可分的任務。對于線性不可分的更復雜的問題秋茫,比如說playground上的一些問題史简,線性不可分,所以需要用到非線性激活函數(shù)学辱。
- 連續(xù)可微性:在訓練神經網絡的過程中乘瓤,使用到了梯度下降,所以連續(xù)可微性是必要的策泣。ReLU雖然不連續(xù)衙傀,但是也同樣適合做激活函數(shù)。
- 值域是有限的:激活函數(shù)的值域是有限的時候萨咕,基于梯度下降的訓練過程才能越來越穩(wěn)定统抬,因為特征表示受有限值的影響更加有效。
- 單調性:激活函數(shù)是單調的時候危队,單層的神經網絡才能保證是凸函數(shù)聪建。
- 具有單調導數(shù)的光滑函數(shù):在某些情況下,這些已經被證明可以更好地概括茫陆。對這些性質的論證表名金麸,這種激活函數(shù)與奧卡姆剃刀原理(簡單有效原理)更加一致。
- 函數(shù)值和輸入近似相等:滿足這個條件的激活函數(shù)簿盅,當權重初始化成很小的隨機數(shù)時挥下,神經網絡的訓練將會很高效,如果不滿足這個條件則需要很小心的初始化神經網絡權重桨醋。
下面介紹幾種常見的激活函數(shù):Sigmoid棚瘟、Tanh、Hard Tanh喜最、ReLU偎蘸、Softmax、LogSoftmax等瞬内。
1.Sigmoid
Sigmoid是一種很常用的非線性函數(shù)迷雪,其公式如下:
因其形狀像S,又稱S函數(shù)虫蝶,將輸入變量映射到(0,1)之間振乏,對于特別大的輸入,其輸出結果是1秉扑;對于特別小的輸入慧邮,其輸出結果是0调限。早期在各類任務中應用廣泛,但是現(xiàn)在只在某些特定的場合使用误澳,因為它有自身的缺點:
- 梯度消失:從圖形上可以看出耻矮,但輸入變量特別大或者特別小的時候,函數(shù)曲線變化趨于平緩忆谓,也就是說函數(shù)的梯度變得越來越小裆装,直到接近于0。這會導致經過神經元的信息很少倡缠。
- 非0均值:輸出值不是0均值的哨免,這樣在后面的神經元上將得到非0均值的輸入。如果進入神經元的數(shù)據時正的昙沦,在反向傳播中權重上的梯度也永遠是正的琢唾。這會導致權重梯度的更新呈現(xiàn)鋸齒形態(tài),這是不可取的盾饮。通過batch的權重和可能最終會得到不同的符號采桃,可以得到緩解。比起梯度消息丘损,這個問題不那么嚴重普办。
- 計算量大:因為其函數(shù)求導涉及除法,在神經網絡的反向傳播求梯度時徘钥,計算量很大衔蹲。
PyTorch中Sigmoid的定義為torch.nn.Sigmoid,對輸入的每個元素執(zhí)行Sigmoid函數(shù)呈础,輸出的維度和輸入的維度相同:
>>> import torch
>>> import torch.nn as nn
>>> import torch.autograd as autograd
>>> input_data = autograd.Variable(torch.randn(2))
>>> print(input_data)
tensor([-0.3765, 1.1710])
>>> m = nn.Sigmoid()
>>> print(m(input_data))
tensor([0.4070, 0.7633])
2.Tanh
Tanh是一個雙曲三角函數(shù)踪危,其公式如下:
從圖像上可以看出,Tanh與Sigmoid不同猪落,它將輸入變量映射到(-1,1)之間,它是Sigmoid函數(shù)經過簡單的變換得到的:
Tanh是0均值的畴博,這一點要比Sigmoid好笨忌,所以實際應用中效果也會比Sigmoid好,但是它仍然沒有解決梯度消失的問題俱病,這點可以從圖像上很清楚地看出來官疲。
PyTorch中Tanh的定義:torch.nn.Tanh。輸出的維度和輸入的維度也是相同的:
>>> input_data = autograd.Variable(torch.randn(2))
>>> print(input_data)
tensor([ 0.5413, -0.8901])
>>> Tanh = nn.Tanh()
>>> print(Tanh(input_data))
tensor([ 0.4940, -0.7114])
注意:由于梯度消失的原因亮隙,不推薦在隱含層使用Sigmoid和Tanh函數(shù)途凫,但是可以在輸出層使用;如果有必要使用它們的時候溢吻,記孜选:Tanh要比Sigmoid好果元,因為Tanh是0均值的。
3.Hard Tanh
和Tanh類似犀盟,Hard Tanh同樣把輸入變量映射到(-1,1)之間而晒,不同的是,映射的時候不再是通過公式計算阅畴,而是通過給定的閾值直接到達最終結果倡怎。標準的Hard Tanh把所有大于1的輸入變成1,所有小于-1的輸入變成-1贱枣,其他的輸入不變:
PyTorch中Hard Tanh支持指定閾值min_val和max_val监署,以改變輸出的最小值和最大值,比如說對于f=Hardtanh(-2,2)的輸出纽哥,所有大于2的輸入的輸出都是2钠乏,所有小于-2的輸入的輸出都是-2,其他原樣輸出昵仅。
>>> input_data = autograd.Variable(torch.randn(2))
>>> print(input_data)
tensor([ 0.2704, -1.3050])
>>> Hardtanh = nn.Hardtanh()
>>> print(Hardtanh(input_data))
tensor([ 0.2704, -1.0000])
4.ReLU
線性整流函數(shù)(Rectified Linear Unit缓熟,ReLU)又稱為修正線性單元。ReLU是一個分段函數(shù)摔笤,其公式如下:
ReLU做的事情很簡單够滑,大于0的數(shù)原樣輸出,小于0的數(shù)輸出0吕世。ReLU在0點處雖然連續(xù)不可導彰触,但是也同樣適合做激勵函數(shù)。
ReLU的優(yōu)點如下:
- 相對于Sigmoid命辖、Tanh而言况毅,ReLU更簡單,只需要設置一個閾值就可以計算結果尔艇,不用復雜的運算尔许。
- ReLU在隨機梯度下降的訓練中收斂會更快,原因是ReLU是非飽和的(non-saturating)终娃。
ReLU在很多任務中都有出色的表現(xiàn)味廊,是目前應用廣泛的激活函數(shù)。但是它也不是十分完美的:ReLU單元很脆弱棠耕,以至于在訓練過程中可能出現(xiàn)死亡現(xiàn)象余佛,即經過一段時間的訓練,一些神經元不再具有有效性窍荧,只會輸出0辉巡,特別是使用較大的學習率的時候。如果發(fā)生這種情況蕊退,神經元的梯度將永遠是0郊楣,不利于訓練憔恳。
一個很大的梯度流過ReLU神經元,權重更新后痢甘,神經元就不會再對任何數(shù)據有效喇嘱,如果這樣,經過這個點的梯度將永遠是0塞栅。也就是說者铜,在訓練過程中,ReLU單元會不可逆的死亡放椰。如果學習率設置得太高作烟,網絡中會有40%可能是死亡的,即整個訓練數(shù)據集中沒有激活的神經元砾医。設置一個合適的學習率可以減少這種情況的發(fā)生拿撩。
PyTorch中的ReLU函數(shù)有一個inplace參數(shù),用于選擇是否進行覆蓋運算如蚜,默認為False压恒。在PyTorch中應用ReLU:
>>> input_data = autograd.Variable(torch.randn(2))
>>> print(input_data)
tensor([ 0.8689, -1.1169])
>>> ReLU = nn.ReLU()
>>> print(ReLU(input_data))
tensor([0.8689, 0.0000])
ReLU的成功應用是在生物學的研究上。生物學研究表明:生物神經不是對所有的外界信息都做出反應错邦,而是部分探赫,即對一部分信息進行忽略,對應于輸入信息小于0的情況撬呢。
5.ReLU的擴展
為了解決ReLU函數(shù)存在的問題伦吠,研究者提出了在ReLU基礎上的優(yōu)化方案。在基于ReLU的擴展中魂拦,主要思路是當輸入是小于0的值時毛仪,不再一味地輸出0,而是通過一個很小的斜率α的直線方程計算結果芯勘,根據α取值的不同可以分為以下幾種方案箱靴。
(1)Leaky ReLU
使用參數(shù)α決定泄露(leak)的程度,就是輸入值小于0時直線的斜率荷愕,α是固定的取值衡怀,而且很小,一般取值為0.01路翻。這樣可以保證在輸入信息小于0的時候也有信息通過神經元,神經元不至于死亡茄靠。
Leaky ReLU函數(shù)公式為:
其中茂契,α是一個很小的常數(shù),比如α=0.01慨绳。
在PyTorch中掉冶,Leaky ReLU有兩個參數(shù):
- negative_slope:控制負斜率的角度真竖,即公式中的alpha,默認值是1e-2厌小。
- inplace:選擇是否進行覆蓋運算恢共,默認值為False。
(2)Parametric ReLU
對于輸入的每一個元素運用函數(shù)PReLU(x)=max(0,x)+α*min(0,x)璧亚,這里的α是自學習的參數(shù)讨韭。當不帶參數(shù)的調用時,nn.PReLU()在所有輸入通道中使用同樣的參數(shù)α癣蟋;如果用nn.PReLU(nChannels)調用透硝,α將應用到每個輸入:
(3)Randomized ReLU
這是Leaky ReLU的random版本,即參數(shù)α是隨機產生的疯搅,RReLU是在Kaggle的NDSB比賽中首次被提出的濒生,其核心思想就是:在訓練過程中,α是從一個高斯分布U(l,u)中隨機生成的幔欧,然后在測試過程中進行修正罪治。
以上三種ReLU擴展的比較:(這里的a_i就是α)
- ReLU,對小于0部分礁蔗,直接置為0觉义;
- Leaky ReLU,對小于0部分瘦麸,進行這樣的轉換:y_i=a_i * x_i 谁撼,它的a_i是固定的;
- PReLU中的a_i 根據數(shù)據變化而變化滋饲;
- RReLU中的a_i是一個在一個給定的范圍內隨機抽取的值不翩,這個值在測試環(huán)節(jié)就會固定下來玖像。
(4)Exponential Linear Unit(ELU)
這是一個新的激活函數(shù),效果比所有的ReLU的變形都要好,訓練用的時間少殊轴,而且測試指標高。
關于ELU中參數(shù)α的選擇脏款,通常設置為1钮科,當然也可以在實際應用中嘗試其他的值,而且整個函數(shù)是平滑的奄喂,在x=0會加速梯度下降铐殃,因為在x=0處不用跳躍。
其缺點是跨新,因為使用了指數(shù)富腊,計算比ReLU系列的計算速度慢,但是訓練時收斂快域帐。
(5)Maxout
Maxout是Ian J.Goodfellow在2013年提出的赘被。Maxout和Dropout結合后是整,在MNIST、CIFAR-10民假、CIFAR-100浮入、SVHN這4個數(shù)據集上都取得了start-of-art的識別率。前面介紹的激活函數(shù)都是作用于輸入信息的一個元素羊异,輸入信息之間是無關的事秀。Maxout單元不是作用于每個元素的函數(shù)g(x),而是將x劃分成具有k個值的組球化,然后輸出其中一組最大的元素秽晚。Maxout有很強的擬合能力,在足夠多隱藏層的情況下可以擬合任意的凸函數(shù)筒愚。
它具有ReLU函數(shù)的優(yōu)點(不會飽和赴蝇,計算簡單),卻沒有ReLU函數(shù)的缺點(容易死亡)巢掺,它的唯一缺點就是每個神經元都有k個權重句伶,導致權重的總數(shù)大大增加。
PyTorch中還沒有Maxout的實現(xiàn)陆淀,如果想嘗試使用考余,可以參考一下PyTorch在GitHub上的一個例子。
6.Softmax
Softmax函數(shù)又稱為歸一化指數(shù)函數(shù)轧苫,是Sigmoid函數(shù)的一種推廣楚堤。它能將一個含有任意實數(shù)的維向量壓縮到另一個維向量中,返回的是每個互斥輸出類別上的概率分布含懊,使得每個元素的范圍都在(0,1)之間身冬,并且所有元素的和為1。公式如下:
跟數(shù)學中的max函數(shù)相比岔乔,max函數(shù)取一組數(shù)中的最大值酥筝,這樣會導致較小的值永遠不會被取到。Softmax很自然地表示具有K個可能值的離散隨機變量的概率分布雏门,所以可以用作一種開關嘿歌,其中越大的數(shù)概率也就越大,Softmax和Maxout一樣不是作用于單個神經網絡中的每個x值茁影,對n維輸入張量運用Softmax函數(shù)宙帝,將張量的每個元素縮放到(0,1)之間,并且各個輸出的和為1募闲。Softmax一般用在網絡的輸出層步脓,比如在多分類的輸出值表示屬于每個種類的概率。
PyTorch中關于Softmax的定義:
class torch.nn.Softmax(dim=None)
它接收一個dim參數(shù),以指定計算Softmax的維度沪编,在給定的維度上各個輸出的和為1。例如:如果輸入的是一個二維張量年扩,dim=0時蚁廓,表示按列計算Softmax值,每一列上的和為1厨幻;dim=1表示按行計算Softmax值相嵌,每一行上的和為1。使用Softmax必須顯式給出dim况脆,否則結果不確定饭宾。
在PyTorch中應用Softmax激活函數(shù):
>>> input_data = autograd.Variable(torch.randn(2,3))
>>> print(input_data)
tensor([[-0.3776, 0.6697, 0.7186],
[-1.0892, 0.1614, 1.2264]])
>>> Softmax = nn.Softmax(dim=1)
>>> print(Softmax(input_data))
tensor([[0.1461, 0.4165, 0.4374],
[0.0684, 0.2388, 0.6928]])
7.LogSoftmax
在應用Softmax函數(shù)前,對輸入應用對數(shù)函數(shù)格了,就是LogSoftmax看铆,公式如下:
PyTorch中的定義同樣接受一個dim參數(shù),含義和用法跟Softmax相同盛末,這里不再贅述弹惦。
PyTorch中更多的預定義激活函數(shù)詳見PyTorch的官方文檔。
3.前向算法
當我們使用前饋神經網絡接收輸入悄但,并產生輸出時棠隐,信息通過網絡向前流動。輸入提供初始信息檐嚣,向輸出的方向傳播到每一層的神經元助泽,并跟相應的權重做運算,最終產生輸出嚎京,這個過程稱為前向傳播(Forward Propagation)嗡贺。
下面結合圖示詳細說明前向傳播過程。
上圖所示是一個只有4層的神經網絡挖藏,其中第1層為輸入層暑刃,第4層為輸出層,第2層和第3層為隱含層膜眠。和是輸入層中的兩個神經元岩臣,表示第個隱含層中的第個神經元,表示第層中第個神經元到第層中第個神經元的權重宵膨,箭頭表示信息傳播方向架谎,即輸入層——>隱含層——>輸出層。
前向傳播過程中的值為所有輸入到該神經元的信息和相應連接上權重的加權求和辟躏。公式為:
其中谷扣,為偏置項。這樣就得到了隱含層中神經元的多元輸入信息和,隨后將多元輸入通過激活函數(shù)得到各個神經元的輸出值和:
為第1個隱含層中神經元的輸出值会涎,隨著信息在網絡中傳播裹匙,它同時也是第2個隱含層的輸入信息,例如此時的輸入信息為和末秃。按照這個過程概页,直到得到最終輸出層神經元的值,這就完成了一次完整的前向傳播過程练慕。
前向傳播是預測的過程惰匙,對每一個訓練集中的實例,通過神經網絡計算每一層的輸出铃将,最終得到整個網絡的輸出项鬼,這個輸出與真實值的差別可以評估當前參數(shù)集的好壞,然后從神經網絡結果中的最后一個隱含層計算劲阎,每一層對整體的誤差“貢獻”了多少绘盟,這個過程就是反向傳播了。前向算法通過網絡中的每個層和激活函數(shù)最終產生輸出悯仙。在訓練過程中奥此,前向傳播可以持續(xù)向前,直到產生一個標量代價函數(shù)雁比。
前向傳播算法描述如下:
Require:網絡深度稚虎,1
Require:,模型權重矩陣
Require:偎捎,模型偏置參數(shù)
Require:蠢终,程序的輸入
Require:,目標輸出
????
????for do
????????
????????
????End for
????
????
4.損失函數(shù)
本小節(jié)我們來介紹損失函數(shù)茴她。首先介紹損失函數(shù)的概念寻拂,然后介紹損失函數(shù)在回歸問題和分類問題上的應用,最后介紹PyTorch中常用的一些損失函數(shù)丈牢。
1.損失函數(shù)的概念
損失函數(shù)(Loss Function)又稱為代價函數(shù)或成本函數(shù)(Cost Function)祭钉,是一個非負的實數(shù)函數(shù),通常用表示己沛。損失函數(shù)用來量化在訓練過程中慌核,網絡輸出和真實目標間的差距。損失函數(shù)是神經網絡中特殊的層申尼。在前向算法中垮卓,每個輸入信息在網絡中流動,最終到達輸出層师幕,產生預測值粟按。然后對數(shù)據集中所有的預測誤差求平均,這個平均誤差反映神經網絡的好壞情況。確定神經網絡最佳狀態(tài)相當于尋找使損失最小的神經網絡參數(shù)灭将。這樣一來疼鸟,損失函數(shù)的出現(xiàn)使網絡的訓練變成一個優(yōu)化問題。神經網絡的參數(shù)很多庙曙,在大多數(shù)情況下很難通過分析來確定愚臀,但是可以利用優(yōu)化算法近似地求解,例如利用梯度下降方法求解最值矾利。
這里需要強調的一點是,損失函數(shù)的值僅跟網絡中權重和偏置有關馋袜。在給定的神經網絡中特定的數(shù)據集下男旗,網絡的損失完全依賴于網絡的狀態(tài),也就是參數(shù)和欣鳖。和的變化會導致網絡的輸出變化察皇,從而影響網絡的損失。所以前向過程中方程可以寫成泽台,即在和條件下對于輸入網絡的輸出是什荣。
在一定程度上可以說損失函數(shù)越小,模型越好怀酷。為什么這么說呢稻爬?因為的均值稱為經驗風險函數(shù),但是并不是經驗風險函數(shù)越小越好蜕依,比如模型復雜度過高會產生過擬合現(xiàn)象桅锄,這個時候經驗風險是很小的,可過擬合的模型并不是我們想要的結果样眠。這里需要引入另外一個概念:正則化友瘤。正則化也叫結構風險,用來表示模型的復雜度檐束。我們把經驗風險和結構風險的和稱為目標函數(shù)辫秧。神經網絡訓練的最終目標就是尋找最佳的參數(shù),使目標函數(shù)最下被丧。后面我們會對正則化展開詳細的討論盟戏。
下面介紹一下常用的損失函數(shù)。損失函數(shù)根據用途通成穑可以分成兩類:一類是用于回歸問題抓半,另一類是用于分類問題。
2.回歸問題
在回歸問題中常用的損失函數(shù)是均方差(Mean Squared Error格嘁,MSE)損失笛求,回歸中的輸出是一個實數(shù)值,這里采用的平方損失函數(shù)類似于線性回歸中的最小二乘法。對于每個輸入實例都只有一個輸出值探入,把所有輸入實例預測值和真實值間的誤差求平方狡孔,然后求平均,公式如下:
其中蜂嗽,是第個輸入的預測值苗膝,是第個輸入的真實值,為輸入集中的個數(shù)植旧。對于多輸入的個數(shù)辱揭,MSE公式為:
其中,表示輸出中的第個值病附,大多時候會在前面乘以1/2问窃,這樣可以與平方項求導得出來的2約掉,方便后續(xù)計算完沪。
MSE在回歸中應用很廣泛域庇,但是它對異常值很敏感,在特定的任務中有時不需要考慮異常值(比如股票的選擇中需要考慮異常值覆积,但是在買房的時候就不需要過多考慮)听皿,這時就需要損失函數(shù)更關注中位數(shù)而不是平均數(shù)。這時可以選擇平均絕對誤差(Mean Absolute Error宽档,MAE)尉姨,公式如下:
MAE為數(shù)據集上所有誤差絕對值的平均數(shù)。
3.分類問題
神經網絡可以解決分類問題吗冤,即判斷輸入屬于哪個類別啊送,例如一張圖片是貓還是狗,收到的郵件是否是垃圾郵件等欣孤。還有一種分類問題馋没,我們的神經網絡模型預測值往往不是特定的類別,而是屬于某一個類別的概率降传,比如前面提到的垃圾郵件問題篷朵,神經網絡的輸出結果為20%可能是垃圾郵件,80%可能不是垃圾郵件婆排。接下來介紹一下分類問題中常用的損失函數(shù)声旺。
對于0-1分類問題,可以使用鉸鏈損失(Hinge Loss)段只,1表示屬于某一類別腮猖,0表示不屬于該類別。大多用-1和1代替0和1赞枕,這時鉸鏈損失的公式如下:
Hinge Loss只可以解決二分類問題澈缺,對于多分類問題坪创,可以把問題轉化為二分類來解決:對于N分類任務,每次只考慮是否屬于某一特定的類別姐赡,把問題轉化成N個二分類問題莱预。
對于前面提到的預測屬于某一類別的概率的問題,可以使用負對數(shù)似然損失函數(shù)(Negative Log Likelihood Loss Function)项滑。接下來舉例說明一下負對數(shù)似然函數(shù)依沮。為了敘述方便,這里同樣使用二分類問題為例枪狂,區(qū)別是這里的二分類問題不是硬分類問題危喉,而是要預測屬于某一類別的概率,用和表示給定輸入的輸出州疾,即屬于某一類別的概率辜限,用和表示網絡權重和偏置,公式為:
上面兩個式子可以整合在一起:
所以損失函數(shù)可以寫成:
為了計算方便孝治,利用對數(shù)函數(shù)把乘積的形式變成求和,因為對數(shù)函數(shù)是單調的审磁,取對數(shù)函數(shù)的最大化和取負對數(shù)函數(shù)的最小化是等價的谈飒,就得出了負對數(shù)似然損失函數(shù)的公式:
我們把問題從二分類擴展到多分類上,用one-hot編碼表示類別的預測結果:屬于該類別的為1态蒂,其他的全為0杭措,公式如下:
從形式上看這個函數(shù)恰好是交叉熵的形式,因此負對數(shù)似然損失函數(shù)又叫做交叉熵損失函數(shù)(Cross Entropy Loss Function)钾恢。交叉熵是不確定性信息的一種度量手素,在這里交叉熵用來衡量我們學習到的和真實目標值的不確定性。對于所有的輸入瘩蚪,如果每個結果越接近目標值泉懦,也就是和的誤差越小,則交叉熵越姓钍荨崩哩;反之,輸出結果距離目標值越遠言沐,則交叉熵越大邓嘹。
4.PyTorch中常用的損失函數(shù)
前面從損失函數(shù)的理論層面做了一些簡單介紹,在PyTorch中已經對常用的損失函數(shù)做好了封裝险胰,在系統(tǒng)中直接調用相應的損失函數(shù)即可汹押。接下來介紹常用的損失函數(shù)在PyTorch中的實現(xiàn)。
(1)MSELoss
PyTorch中MSELoss的定義:
class torch.nn.MSELoss(size_average=True, reduce=True)
參數(shù)含義:
- size_average:True表示此時的損失是每個minibatch的平均起便;False表示此時的損失是對每個minibatch的求和棚贾,默認為True窖维。這個屬性只有當reduce為True時才生效。
- reduce:默認為True鸟悴,此時損失會根據size_average參數(shù)的值計算每個minibatch的平均或者求和陈辱;如果設置為False,則忽略size_average參數(shù)的值细诸,直接返回每個元素的損失沛贪。
使用方法舉例:
>>> loss = nn.MSELoss()
>>> input = autograd.Variable(torch.randn(3,5),requires_grad=True)
>>> target = sutograd.Variable(torch.randn(3,5))
>>> output = loss(input,target)
>>> output.backward()
(2)L1Loss
L1Loss即前面提到的MAE,在PyTorch中的定義為:
class torch.nn.L1Loss(size_average=True, reduce=True)
參數(shù)含義和MSE的一樣震贵。
(3)BCELoss
BCELoss用在二分類問題中利赋,PyTorch中的定義為:
class torch.nn.BCELoss(weight=None, size_average=True)
參數(shù)含義:
-weight:指定batch中每個元素的Loss權重,必須是一個長度和batch相等的Tensor
-size_average:True表示此時的損失是每個minibatch的平均猩系;False表示此時的損失是對每個minibatch的求和媚送,默認為True。
在使用BCELoss時寇甸,需要注意的是塘偎,每個目標值()都要求在(0,1)之間,可以在網絡最后一層使用Sigmoid函數(shù)達到這個要求拿霉。
示例:
>>> m = nn.Sigmoid()
>>> loss = nn.BCELoss()
>>> input = autograd.Variable(torch.randn(3), requires_grad=True)
>>> target = autograd.Variable(torch.FloatTensor(3).random_(2))
>>> output = loss(m(input), target) # 前向輸出時使用Sigmoid函數(shù)
>>> output.backward()
(4)BCEWithLogitsLoss
BCEWithLogitsLoss在PyTorch中的定義為:
class torch.nn.BCEWithLogitsLoss(weight=None,size_average=True)
BCEWithLogitsLoss同樣使用在二分類任務中吟秩,參數(shù)含義和BCELoss相同。與BCELoss不同的是绽淘,它把Sigmoid函數(shù)集成到函數(shù)中涵防,在實際應用中比Sigmoid層加BCELoss層再數(shù)值上更加穩(wěn)定,因為把兩個層合并時可以使用LogSumExp的優(yōu)勢來保證數(shù)值的穩(wěn)定性沪铭。
(5)NLLLoss
NLLLoss使用在多分類任務中的負對數(shù)似然損失函數(shù)壮池,我們用C表示多分類任務中類別的個數(shù),用N表示minibatch杀怠,則NLLLoss的輸入必須是(N,C)的二維Tensor椰憋,即數(shù)據集中每個實例對應每個類別的對數(shù)概率,可以在網絡最后一層應用LogSoftmax層來實現(xiàn)赔退。PyTorch中NLLLoss的定義為:
class torch.nn.NLLLoss(weight=None,size_average=True,ignore_index=-100,reduce=True)
參數(shù)含義:
- size_average和reduce熏矿,和前面介紹的一樣。
- weight:可以指定一個一維的Tensor离钝,用來設置每個類別的權重票编。若用C來表示類別的個數(shù),則Tensor的長度就是C卵渴。當訓練集不平衡時該參數(shù)十分有用慧域。
-ignore_index:可以設置一個被忽略值,使這個值不會影響到輸入的梯度計算浪读。當size_average為True時昔榴,loss的平均值也會忽略該值辛藻。
這里要特別說明一點:輸出根據reduce而定,如果reduce為True則輸出為一個標量互订,如果reduce為False則輸出為一個長度為N的Tensor吱肌。
(6)CrossEntropyLoss
CrossEntropyLoss同樣是多分類任務的交叉熵損失函數(shù),前面提到仰禽,在NLLLoss的輸出層要應用一個LogSoftmax函數(shù)氮墨,CrossEntropyLoss就是LogSoftmax和NLLLoss的組合⊥驴可以使用NLLLoss的任務都可以使用CrossEntropyLoss规揪,而且更簡潔,PyTorch中的定義為:
class torch.nn.CrossEntropyLoss(weight=None,size_average=True,ignore_index=-100,reduce=True)
參數(shù)含義和NLLLoss完全相同温峭。
以上幾種損失函數(shù)是PyTorch中最常用的幾種猛铅,更多損失函數(shù)可以參考PyTorch官網損失函數(shù)模塊。
5.反向傳播算法
前向算法中凤藏,需要W參與運算奸忽,W是網絡中各個連接上的權重,這個值需要在訓練過程中確定揖庄,在傳統(tǒng)的機器學習方法(如邏輯回歸)中栗菜,可以通過梯度下降來確定權重的調整。邏輯回歸可以看做是沒有隱含層的神經網絡抠艾,但是在多元感知機中如何獲取隱含層的權重是很困難的苛萎,我們能做的是計算輸出層的誤差更新參數(shù)桨昙,但在隱含層誤差是不存在的检号。雖然無法直接獲取隱含層的權重,但是我們知道在權重變化后輸出誤差的變化蛙酪,那么能否通過實際輸出值和期望輸出值間的差異來間接調整權重呢齐苛?預測值和真實值之間的差別可以評估輸出層的誤差,然后根據輸出層的誤差桂塞,計算在最后一個隱含層中的每個神經元對輸出層誤差影響了多少凹蜂,最后一層隱含層的誤差又由其前一層隱含層計算得出。如此類推阁危,直到輸入層玛痊。這就是反向傳播(BackPropagation,BP)算法的基本思想狂打。
反向傳播算法是1986年由Rumelhart和McCelland為首的科研小組提出的擂煞。反向傳播基于梯度下降策略,是鏈式求導法則的一個應用趴乡,以目標的負梯度方向對參數(shù)進行調整对省。這是一場以誤差(Error)為主導的反向傳播運動蝗拿,旨在得到最優(yōu)的全局參數(shù)矩陣,進而將多層神經網絡應用到分類或者回歸任務中去蒿涎。
反向傳播算法的原理及詳細推導過程請參考博客《反向傳播算法(過程及公式推導)》哀托。感謝原創(chuàng),作者是個大牛劳秋!
在具體的應用中仓手,成熟的深度學習工具包都完成了對這些操作的封裝。PyTorch封裝了這一系列復雜的計算俗批,前面提到過PyTorch中所有的神經網絡的核心是autograd自動求導包俗或。這里結合反向傳播進行說明。
torch.autograd包的核心是Variable類岁忘,它封裝了Tensor支持的所有操作辛慰,在程序中一旦完成了前向的運算,就可以直接調用.backward()方法干像,這時候所有的梯度計算會自動進行帅腌。如果Variable是標量的形式(只有一個元素),你不必指定任何參數(shù)給backward()函數(shù)麻汰。不過速客,如果它有更多的元素,就需要去指定一個和Variable形狀匹配的grad_variables參數(shù)五鲫,用來保存相關Variable的梯度溺职。
PyTorch中海油一個針對自動求導的實現(xiàn)類:Function。Variable和Function是相互聯(lián)系的位喂,并且它們構建了一個非循環(huán)的圖浪耘,編碼了一個完整的計算歷史信息。每一個Variable都有一個.grad_fn屬性塑崖,引用一個已經創(chuàng)建的Function七冲。
PyTorch中反向傳播的例子:
import torch
from torch.autograd import Variable
x = Variable(torch.ones(2,2),requires_grad=True)
y = x + 2
z = y * y * 3
out = z.mean()
out.backward()
6.數(shù)據處理類
我們要使用的數(shù)據需要轉換成PyTorch能夠處理的格式。PyTorch中提供了torch.utils.data.Dataset類對數(shù)據進行封裝规婆,是所有要加載的數(shù)據集的父類澜躺。在定義Dataset的子類時,需要重載兩個函數(shù):len和getitem抒蚜。其中掘鄙,len返回數(shù)據集的大小嗡髓;getitem實現(xiàn)數(shù)據集的下標索引操漠。
在創(chuàng)建DataLoader時會判斷getitem返回值的數(shù)據類型,然后用不同的分支把數(shù)據轉換成相應的張量器贩。因此颅夺,getitem返回值的數(shù)據類型可選擇范圍很多朋截。比如圖像可以選擇numpy.array類型,標記可以選擇int類型吧黄。
現(xiàn)在有了由數(shù)據文件生成的結構數(shù)據部服,那么怎么在訓練時提供batch數(shù)據呢?PyTorch提供了生成batch數(shù)據的類拗慨。PyTorch用類torch.utils.data.DataLoader加載數(shù)據诈唬,并對數(shù)據進行采樣茂装,生成batch迭代器多望。
class torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, batch_sampler=None, num_workers=0, collate_fn=<function default_collate>, pin_memory=False, drop_last=False)
參數(shù)含義:
- dataset:Dataset類型确徙,指定要加載的數(shù)據
- batch_size:指定每個batch加載多少個樣本,默認為1
- shuffle:指定是否在每個epoch中對數(shù)據進行打亂
- sampler:從數(shù)據集中采樣的策略
-batch_sampler:批量采樣策略烦却,一次返回一批指標
-num_works:加載數(shù)據時使用多少子進程宠叼。默認值為0,表示只在主進程中加載數(shù)據
-collate_fn:定義函數(shù)來合并樣本以形成一個mini-batch- pin_memory:如果為True其爵,此時數(shù)據加載器會將張量復制到CUDA固定內存中冒冬,然后返回它們
-drop_last:如果為True,最后一個不完整的batch將被丟棄
7.示例:單層神經網絡實現(xiàn)
本節(jié)使用神經網絡在iris數(shù)據集的多分類示例來說明神經網絡的整個流程摩渺。一般的神經網絡訓練包括幾個重要的步驟:數(shù)據準備简烤,初始化權重,激活函數(shù)摇幻,前向計算横侦,損失函數(shù),計算損失绰姻,反向傳播枉侧,更新參數(shù),直到收斂或者達到終止條件龙宏。
import torch
from torch import sigmoid
import torch.nn.functional as F
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from torch.autograd import Variable
from torch.optim import SGD
N_FEATURE = 4
N_HIDDEN = 5
N_OUTPUT = 4
N_ITERS = 1000
LR = 0.5
# 判斷GPU是否可用
use_cuda = torch.cuda.is_available()
print("use_cuda: ",use_cuda)
# 加載數(shù)據集
iris = load_iris()
print(iris.keys())
# 數(shù)據預處理
x = iris['data']
y = iris['target']
print('x.shape: ',x.shape)
print('y.shape: ',y.shape)
print(y)
x = torch.FloatTensor(x)
y = torch.LongTensor(y)
x = Variable(x)
y = Variable(y)
# 定義神經網絡類棵逊,繼承自torch.nn.Module
class Net(torch.nn.Module):
# 定義構造函數(shù)
def __init__(self,n_feature, n_hidden, n_output):
super(Net,self).__init__()
# 定義隱含層
self.hidden = torch.nn.Linear(n_feature,n_hidden)
# 定義輸出層
self.predict = torch.nn.Linear(n_hidden,n_output)
# 定義前向傳播
def forward(self,x):
# 計算隱含層伤疙,激活函數(shù)為sigmoid
x = sigmoid(self.hidden(x))
# 計算輸出層银酗,激活函數(shù)為log_softmax(多分類)
out = F.log_softmax(self.predict(x),dim=1)
return out
# 定義神經網絡實例
net = Net(n_feature=N_FEATURE,n_hidden=N_HIDDEN,n_output=N_OUTPUT)
print(net)
# 如果GPU可用,把數(shù)據和模型都轉到GPU上計算徒像;CPU時調用.cpu()即可
if use_cuda:
x = x.cuda()
y = y.cuda()
net = net.cuda()
# 定義神經網絡優(yōu)化器:這里使用隨機梯度下降SGD黍特,學習率lr=0.5
optimizer = SGD(net.parameters(),lr=LR)
# 開始訓練神經網絡
px,py = [],[]
for i in range(N_ITERS):
# 數(shù)據集傳入網絡前向計算預測值
prediction = net(x)
# 計算損失
loss = F.nll_loss(prediction,y)
# 清除網絡狀態(tài)
optimizer.zero_grad()
# 誤差反向傳播
loss.backward()
# 更新參數(shù)
optimizer.step()
# 打印并記錄當前的index和loss
print(i," loss: ",loss.item())
px.append(i)
py.append(loss.item())
plt.figure(figsize=(6,4),dpi=144)
plt.plot(px,py,'r-',lw=1)
plt.yticks([x * 0.1 for x in range(16)])
plt.show()
代碼輸出如下:
use_cuda: True
dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names'])
x.shape: (150, 4)
y.shape: (150,)
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2]
Net(
(hidden): Linear(in_features=4, out_features=5, bias=True)
(predict): Linear(in_features=5, out_features=4, bias=True)
)
0 loss: 1.3780320882797241
1 loss: 1.244009017944336
2 loss: 1.1953575611114502
3 loss: 1.1671373844146729
4 loss: 1.147063136100769
5 loss: 1.1312648057937622
6 loss: 1.1179096698760986
7 loss: 1.10591721534729
8 loss: 1.0945632457733154
9 loss: 1.0833168029785156
10 loss: 1.0717663764953613
……
990 loss: 0.08581560850143433
991 loss: 0.0774485245347023
992 loss: 0.08574181795120239
993 loss: 0.07738661766052246
994 loss: 0.08566807955503464
995 loss: 0.07732491940259933
996 loss: 0.08559456467628479
997 loss: 0.07726352661848068
998 loss: 0.08552153408527374
999 loss: 0.0772024393081665
訓練過程中損失的變化如下圖所示: