跟我一起學(xué)PyTorch-05:深度神經(jīng)網(wǎng)絡(luò)DNN

前面我們介紹了神經(jīng)網(wǎng)絡(luò),包括神經(jīng)元函數(shù),激活函數(shù),前向算法,反向傳播算法,梯度下降等,這些內(nèi)容基本上是傳統(tǒng)神經(jīng)網(wǎng)絡(luò)的范疇,這個浪潮大致在1980~1995年之間,主要標(biāo)志是1986年David Rumelhart和Geoffrey Hinton等人使用反向傳播算法訓(xùn)練具有一兩個隱含層的神經(jīng)網(wǎng)絡(luò)。這種模擬人腦系統(tǒng)的神經(jīng)網(wǎng)絡(luò)初步成功,在一些諸如異或(XOR)問題上能夠完美解決,人們熱切地盼望著人工智能時代的到來。不少基于神經(jīng)網(wǎng)絡(luò)技術(shù)和其他AI技術(shù)的公司紛紛建立起來,但是在很多圖像識別的實際問題上,神經(jīng)網(wǎng)絡(luò)很難進(jìn)行訓(xùn)練,神經(jīng)網(wǎng)絡(luò)的參數(shù)調(diào)試需要很多技巧;同時,其他機器學(xué)習(xí)方法如SVM(Support Vector Machine,支持向量機)、圖模型取得了長足的進(jìn)步。這兩者導(dǎo)致神經(jīng)網(wǎng)絡(luò)研究熱潮的衰退,這種現(xiàn)象持續(xù)到2006年。

Geoffrey Hinton提出了一種名為“深度信念網(wǎng)絡(luò)”的神經(jīng)網(wǎng)絡(luò),可以使用“貪婪逐層預(yù)訓(xùn)練”的策略有效地進(jìn)行神經(jīng)網(wǎng)絡(luò)的訓(xùn)練。緊接著,這種方法在其他神經(jīng)網(wǎng)絡(luò)的訓(xùn)練上也取得了成功。在諸如圖像識別、語音識別等領(lǐng)域,這些新型的神經(jīng)網(wǎng)絡(luò)取得了令人矚目的成績,標(biāo)志著機器學(xué)習(xí)一個全新時代的到來。這些新型的神經(jīng)網(wǎng)絡(luò)統(tǒng)稱為深度學(xué)習(xí),因為這些神經(jīng)網(wǎng)絡(luò)的模型可以有多個隱含層。深度學(xué)習(xí)主要包括深度神經(jīng)網(wǎng)絡(luò)DNN、卷積神經(jīng)網(wǎng)絡(luò)CNN、循環(huán)神經(jīng)網(wǎng)絡(luò)RNN、LSTM以及強化學(xué)習(xí)等。

深度學(xué)習(xí)之所以能夠成功,是因為解決了神經(jīng)網(wǎng)絡(luò)的訓(xùn)練問題,使得包含多個隱含層的神經(jīng)網(wǎng)絡(luò)模型變得可能。神經(jīng)網(wǎng)絡(luò)訓(xùn)練問題的解決,包括了四個方面的因素:
(1)硬件設(shè)備特別是高性能GPU的進(jìn)步,極大地提高了數(shù)值運算和矩陣運算的速度,神經(jīng)網(wǎng)絡(luò)的訓(xùn)練時間明顯減少。
(2)大規(guī)模得到標(biāo)注的數(shù)據(jù)集(如CIFAR10和ImageNet等)可以避免神經(jīng)網(wǎng)絡(luò)因為參數(shù)過多而得不到充分訓(xùn)練的問題。
(3)新型神經(jīng)網(wǎng)絡(luò)的提出,包括深度信念網(wǎng)絡(luò)、受限玻爾茲曼機、卷積神經(jīng)網(wǎng)絡(luò)CNN、循環(huán)神經(jīng)網(wǎng)絡(luò)RNN、LSTM等。
(4)優(yōu)化算法上的進(jìn)步,包括ReLU激活函數(shù)、Mini-Batch梯度下降算法、新型優(yōu)化器、正則化、Batch Normalization以及Dropout等。

本章主要介紹深度神經(jīng)網(wǎng)絡(luò)、梯度下降算法、優(yōu)化器及正則化等優(yōu)化訓(xùn)練技巧。

1.深度神經(jīng)網(wǎng)絡(luò)

如果神經(jīng)網(wǎng)絡(luò)中前后層的所有結(jié)點都是相連的,那么這種網(wǎng)絡(luò)結(jié)構(gòu)稱為全連接層網(wǎng)絡(luò)結(jié)構(gòu)。深度神經(jīng)網(wǎng)絡(luò)是最基礎(chǔ)的神經(jīng)網(wǎng)絡(luò)之一,最顯著的特征是其隱含層由全連接層構(gòu)成。全連接層是一個經(jīng)典的神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)層。如下圖所示,該深度神經(jīng)網(wǎng)絡(luò)主要包括1個輸入層,3個隱含層和1個輸出層。前后層的所有結(jié)點都是兩兩相連的。

image.png

深度神經(jīng)網(wǎng)絡(luò)是傳統(tǒng)神經(jīng)網(wǎng)絡(luò)的擴展,看起來就是深度神經(jīng)網(wǎng)絡(luò)包含多個隱含層。不過,這個看似小小的飛躍的背后,經(jīng)歷了長達(dá)20年的艱辛探索。1986年基于后向傳播的神經(jīng)網(wǎng)絡(luò)取得成功,人們期待神經(jīng)網(wǎng)絡(luò)一飛沖天,結(jié)果很快發(fā)現(xiàn)神經(jīng)網(wǎng)絡(luò)只能在有限的領(lǐng)域有效,同時還有嚴(yán)苛的訓(xùn)練技巧。直到2006年,Hilton提出“貪婪逐層訓(xùn)練”的策略進(jìn)行神經(jīng)網(wǎng)絡(luò)訓(xùn)練,在圖像識別和語音識別領(lǐng)域率先突破,才取得了令人矚目的成績。后續(xù)研究發(fā)現(xiàn),這種逐層訓(xùn)練的技巧不是完全必要的,在訓(xùn)練數(shù)據(jù)和計算資源充足的情況下,使用ReLU激活函數(shù)、Mini-Batch梯度下降算法、新型優(yōu)化器、正則化、Batch Normalization及Dropout等算法,就能訓(xùn)練得到比較滿意的深度學(xué)習(xí)模型。那么傳統(tǒng)的神經(jīng)網(wǎng)絡(luò)為什么難以訓(xùn)練呢?

1.神經(jīng)網(wǎng)絡(luò)為何難以訓(xùn)練

神經(jīng)網(wǎng)絡(luò)在層數(shù)較多的網(wǎng)絡(luò)模型訓(xùn)練的時候很容易出問題。除了計算資源不足和帶標(biāo)注的訓(xùn)練數(shù)據(jù)因素引起的問題外,還表現(xiàn)出兩個重大的問題:梯度消失問題和梯度爆炸問題。這兩個問題在模型的層數(shù)增加時會變得更加明顯。例如在上圖所示的深度神經(jīng)網(wǎng)絡(luò)中,如果存在梯度消失問題,根據(jù)反向傳播算法原理,接近輸出的隱含層3的權(quán)值更新相對正常;在反方向上,權(quán)值更新越來越不明顯,以此類推,接近輸入層的隱含層1的權(quán)值更新幾乎消失,導(dǎo)致經(jīng)過很多次的訓(xùn)練后,仍然接近初始化的權(quán)值,這樣導(dǎo)致隱含層1相當(dāng)于只對輸入層做了一個同一映射,那么整個神經(jīng)網(wǎng)絡(luò)相當(dāng)于不包括隱含層1的神經(jīng)網(wǎng)絡(luò)。

這個問題是如何產(chǎn)生的呢?在神經(jīng)網(wǎng)絡(luò)的訓(xùn)練中,以反向傳播算法為例(假設(shè)神經(jīng)網(wǎng)絡(luò)中一個隱含層,且對每個神經(jīng)元都有:y_i=\sigma(z_i)=\sigma(w_ix_i+b_i),\sigma 表示Sigmoid激活函數(shù)),如下圖所示:

image.png

根據(jù)鏈?zhǔn)椒▌t可以推導(dǎo)如下:

\begin{equation} \begin{split} \frac{\partial C}{\partial b_1}&=\frac{\partial C}{\partial y_4}\frac{\partial y_4}{\partial z_4}\frac{\partial z_4}{\partial x_4}\frac{\partial x_4}{\partial z_3}\frac{\partial z_3}{\partial x_3}\frac{\partial x_3}{\partial z_2}\frac{\partial z_2}{\partial x_2}\frac{\partial x_2}{\partial z_1}\frac{\partial z_1}{\partial b_1}\\ &=\frac{\partial C}{\partial y_4}\sigma'(z_4)w_4\sigma'(z_3)w_3\sigma'(z_2)w_2\sigma'(z_1)w_1 \end{split} \end{equation}

而Sigmoid函數(shù)的公式為:

\sigma(x)=\frac{1}{1+e^{-x}}

其導(dǎo)數(shù)公式為:

\sigma'(x)=\sigma(x)(1-\sigma(x))

Sigmoid函數(shù)的導(dǎo)數(shù)\sigma'(x)的圖像如下所示:

image.png

正如上圖所示,導(dǎo)數(shù)\sigma'(x)的最大值為0.25,而初始化的權(quán)值w的絕對值通常都小于1,因此|\sigma'(x)|<0.25,對于上面的鏈?zhǔn)角髮?dǎo),神經(jīng)網(wǎng)絡(luò)的層數(shù)越多,求導(dǎo)結(jié)果\frac{\partial C}{\partial b_1}越小,因而在反向傳播中導(dǎo)致梯度消失的情況出現(xiàn)。

同樣地,梯度爆炸問題的出現(xiàn)原因類似,即求導(dǎo)結(jié)果大于1也是比較常見的情況,對于上面的鏈?zhǔn)角髮?dǎo),神經(jīng)網(wǎng)絡(luò)的層數(shù)越多,求導(dǎo)結(jié)果越多,因而在反向傳播過程中導(dǎo)致梯度爆炸的情況出現(xiàn)。但是對于Sigmoid函數(shù)來說,這種情況比較少,因為\sigma'(x)的大小也與w有關(guān)(z=wx+b),除非該層的輸入值一直在一個比較小的范圍內(nèi)。

其實,梯度消失和梯度爆炸都是因為網(wǎng)絡(luò)層數(shù)太深、權(quán)值更新不穩(wěn)定造成的,本質(zhì)上是因為梯度反向傳播中的連乘效應(yīng)。

2. 改進(jìn)策略

上面分析了神經(jīng)網(wǎng)絡(luò)訓(xùn)練中出現(xiàn)的兩大問題:梯度消失和梯度爆炸。分析神經(jīng)網(wǎng)絡(luò)出現(xiàn)的問題,可以從分析損失函數(shù)錯誤平面開始。前面章節(jié)已經(jīng)詳細(xì)討論了損失函數(shù)。從對損失函數(shù)錯誤平面的討論引申出優(yōu)化思路——梯度下降。同時,神經(jīng)網(wǎng)絡(luò)也出現(xiàn)泛化問題(欠擬合),深度學(xué)習(xí)模型在訓(xùn)練集上表現(xiàn)好,而在測試集上表現(xiàn)差。這時需要考慮新的思路,提高模型泛化的能力,需要正則化了。接下來就詳細(xì)介紹梯度下降算法及其改進(jìn),還有模型正則化方法,它們是深度學(xué)習(xí)模型訓(xùn)練不可或缺的。

2.梯度下降

深度學(xué)習(xí)算法的訓(xùn)練都是以梯度下降算法及其改進(jìn)算法為核心的。在深度學(xué)習(xí)中,訓(xùn)練的最終目的是使損失函數(shù)最小。如何使損失函數(shù)最小呢?從數(shù)學(xué)知識知道,對于連續(xù)可導(dǎo)函數(shù),函數(shù)的最小值就是它導(dǎo)數(shù)為0的極值點,可以通過求導(dǎo)并令導(dǎo)數(shù)為0來找到極值點,或者可以采用逐步逼近的方法把極值點找出來。梯度,在數(shù)學(xué)上說是一個向量,指向函數(shù)值上升最快的方向。那么梯度的反方向就是函數(shù)值下降最快的方向。每次沿著梯度下降方向更新變量,就能找到函數(shù)最小值。對于深度學(xué)習(xí)的訓(xùn)練來說,同樣采用梯度下降算法求解。

1.批量梯度下降

使用整個訓(xùn)練集的優(yōu)化算法稱為批量算法,因為它們會在一個大批量中同時處理所有樣本。批量梯度下降算法每次學(xué)習(xí)都使用整個訓(xùn)練集,其優(yōu)點在于每次更新都會朝著正確的方向進(jìn)行,最終能保證收斂到全局最小值,這樣收斂速度快,迭代次數(shù)少。但其缺點也很明顯,就是每次梯度更新都要遍歷整個數(shù)據(jù)集,需要大量的計算,內(nèi)存消耗極多,特別是在數(shù)據(jù)集規(guī)模較大的時候,同時它還不利于分布式訓(xùn)練。

2.隨機梯度下降

每次只使用單個樣本的優(yōu)化算法稱為隨機梯度下降。隨機梯度下降算法每次只隨機選擇一個樣本來更新模型參數(shù),因此每次的學(xué)習(xí)是非常快速的。隨機梯度下降算法最大的缺點在于有時不會按照梯度下降最快的方向進(jìn)行,因此可能帶來擾動。對于局部極小值點,擾動使得梯度下降方向從當(dāng)前的局部極小值點跳到另一個局部極小值點,最后難以收斂。由于擾動,收斂速度會變慢,往往需要更多的迭代次數(shù)才能收斂。

3.Mini-Batch梯度下降

大多數(shù)用于深度學(xué)習(xí)的梯度下降算法介于批量梯度下降和隨機梯度下降之間,使用一個以上但又不是全部的訓(xùn)練樣本,稱為小批量梯度下降算法(Mini-Batch Gradient Descent)。

小批量梯度下降算法需要樣本隨機抽取。計算梯度需要樣本滿足相互獨立的條件,而現(xiàn)實中數(shù)據(jù)自然排列,前后樣本之間具有一定的關(guān)聯(lián)性。因此需要把樣本順序隨機打亂,以便滿足樣本獨立性的要求。小批量梯度下降綜合了批量梯度下降和隨機梯度下降,在更新速度和迭代次數(shù)中間取得一個平衡,每次更新從訓(xùn)練集中隨機選擇m個樣本(m<n)進(jìn)行學(xué)習(xí)。

相對于批量梯度下降,Mini-Batch梯度下降降低了收斂擾動性,即降低了參數(shù)更新的方差,使得更新更加穩(wěn)定。相對于批量梯度下降,其提高了每次學(xué)習(xí)的速度,并且不用擔(dān)心內(nèi)存瓶頸,可以利用矩陣運算提高計算效率。一般而言,每次更新隨機選擇50~256個樣本進(jìn)行學(xué)習(xí),但是也要根據(jù)具體問題而選擇,實踐中可以進(jìn)行多次試驗,選擇一個更新速度和迭代次數(shù)都較合適的樣本數(shù)。Mini-Batch梯度下降可以保證收斂性,又可以保證更新速度快,常用于神經(jīng)網(wǎng)絡(luò)的訓(xùn)練中。

目前,Mini-Batch梯度下降是深度學(xué)習(xí)中的主流方法。在深度學(xué)習(xí)實踐中,批量梯度下降和隨機梯度下降可以看做Mini-Batch梯度下降的特例,批量梯度下降看作是Mini-Batch的size大小是整個數(shù)據(jù)集,隨機梯度下降可以看做是Mini-Batch的size為1的情況。因此只有一種MIni-Batch的方法就夠了。在PyTorch中同樣如此。Mini-Batch方法是作為數(shù)據(jù)加載函數(shù)torch.utils.data.DataLoader的一個參數(shù)batch_size出現(xiàn)的,如果值為1就是隨機梯度下降,如果值是數(shù)據(jù)集大小就是批量梯度下降,如果值在二者之間就是Mini-Batch梯度下降。特別指出,DataLoader只涉及數(shù)據(jù)集的劃分,并不涉及梯度下降算法本身。

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)

用法示例:

train_loader = torch.utils.data.DataLoader(dataset=train_dataset,batch_size=batch_size,shuffle=True)

在函數(shù)torch.utils.data.DataLoader中,實現(xiàn)數(shù)據(jù)加載功能,根據(jù)Mini-Batch方法和采樣機制,對數(shù)據(jù)集進(jìn)行劃分,并在數(shù)據(jù)集上提供單進(jìn)程或多進(jìn)程迭代器,各個參數(shù)的意義如下:

  • dataset:加載的數(shù)據(jù)集
  • batch_size:Mini-Batch的尺寸,每個批次加載多少個樣本(默認(rèn)為1,即隨機梯度下降)
  • shuffle:True表示每次迭代時打亂數(shù)據(jù),在訓(xùn)練時必須設(shè)置為True
  • sampler:采樣策略,如果指定該參數(shù),則忽略shuffle參數(shù)
  • batch_sampler:批量采樣策略,與batch_size,shuffle,sampler和drop_out互斥
  • num_workers:用多少個進(jìn)程加載數(shù)據(jù)。默認(rèn)為0,表示只在主進(jìn)程中加載數(shù)據(jù)
  • collate_fn:合并樣本列表形成Mini-Batch
  • pin_memory:True表示使用固定的內(nèi)存緩沖區(qū),主機到GPU的復(fù)制速度要快很多
  • drop_last:是否刪除最后一個不完整的batch,默認(rèn)為False

3.優(yōu)化器

對梯度下降算法可以進(jìn)行多方面的優(yōu)化,可以加速梯度下降,可以改進(jìn)學(xué)習(xí)率。在PyTorch中,有一個優(yōu)化器Optimizer的概念,具體的包名叫做torch.optim。其中包含的具體的優(yōu)化算法有SGD、Momentum、RMSProp、AdaGrad和Adam。其中,Momentum是加速梯度下降,其他三種方法是改進(jìn)學(xué)習(xí)率。下面將逐一介紹這些優(yōu)化算法的原理和使用。

1.SGD

在深度學(xué)習(xí)和PyTorch中,SGD就是Mini-Batch梯度下降算法,隨機梯度下降方法及其變種是深度學(xué)習(xí)中應(yīng)用最多的優(yōu)化方法。SGD方法流程如下:

Require:學(xué)習(xí)率 \epsilon
Require:初始參數(shù)\theta
while 停止準(zhǔn)則未滿足 do
\qquad從訓(xùn)練集中采樣,包含m個樣本{x^{(1)},...,x^{(m)}}的小批量,其中x^{(i)}對應(yīng)目標(biāo)為y^{(i)}
\qquad計算梯度估計:g←+\frac{1}{m}\nabla_{\theta}\sum_iL(f(x^{(i)};\theta),y^{(i)})
\qquad應(yīng)用更新:\theta←\theta - \epsilon g
end while

2.Momentum

SGD方法是常用的優(yōu)化方法,但其收斂過程會很慢,Momentum方法可以加速收斂。Momentum方法顧名思義,類似物理上的動量。設(shè)想一下,從山頂滾下一個鐵球,鐵球在滾下山的過程中,速度越來越快,動量不斷增加,加速沖向終點?;趧恿康奶荻认陆邓惴ㄊ侨绾伪憩F(xiàn)的呢?算法在更新模型參數(shù)時,對于那些當(dāng)前的梯度方向與上一次梯度方向相同的參數(shù)進(jìn)行加強,即這些方向上更快了;對于那些當(dāng)前的梯度方向與上一次梯度方向不同的參數(shù)進(jìn)行削減,即這些方向上減緩了。因此Momentum方法可以獲得更快的收斂速度和減少擾動。使用了動量的SGD算法流程如下:

Require:學(xué)習(xí)率 \epsilon,動量參數(shù)\alpha
Require:初始參數(shù)\theta,初始速度v
while 停止準(zhǔn)則未滿足 do
\qquad從訓(xùn)練集中采樣,包含m個樣本{x^{(1)},...,x^{(m)}}的小批量,其中x^{(i)}對應(yīng)目標(biāo)為y^{(i)}
\qquad計算梯度估計:g←+\frac{1}{m}\nabla_{\theta}\sum_iL(f(x^{(i)};\theta),y^{(i)})
\qquad計算速度更新:v←\alpha v - \epsilon g
\qquad應(yīng)用更新:\theta←\theta + v
end while

在PyTorch中,Momentum方法調(diào)用函數(shù)是torch.optim.SGD,注意SGD和Momentum方法都是調(diào)用同一個函數(shù),靠設(shè)置參數(shù)momentum進(jìn)行區(qū)分:

class torch.optim.SGD(params,lr=<objectobject>,momentum=0,dempening=0,weight_decay=0,nesterov=False)

參數(shù)含義:

  • params:用于優(yōu)化的迭代參數(shù)
  • lr:學(xué)習(xí)率,默認(rèn)為1e-3
  • momentum:動量因子,用于動量梯度下降算法,默認(rèn)為0
  • dampening:抑制因子,用于動量算法,默認(rèn)為0
  • weight_decay:權(quán)值衰減系數(shù),L2參數(shù),默認(rèn)為0
  • nesterov:動量方法使用
3.AdaGrad

學(xué)習(xí)率是SGD的一個關(guān)鍵參數(shù),但是它是比較難以設(shè)置的參數(shù)之一,因為它對神經(jīng)網(wǎng)絡(luò)模型有很大的影響。如何自適應(yīng)地設(shè)置模型參數(shù)的學(xué)習(xí)率是深度學(xué)習(xí)的研究方向之一。AdaGrad算法,根據(jù)每個參數(shù)所有梯度歷史平方和的平方根,成比例的縮放參數(shù),能獨立地適應(yīng)調(diào)整所有模型參數(shù)的學(xué)習(xí)率。損失最大的參數(shù)相應(yīng)地有一個快速下降的學(xué)習(xí)率,損失較小偏導(dǎo)的參數(shù)在學(xué)習(xí)率上的下降幅度相對較小。在參數(shù)空間中更為平緩的傾斜方向會取得更大的進(jìn)步。AdaGrad算法具有一些令人滿意的理論性質(zhì)。然而,實踐中發(fā)現(xiàn),在訓(xùn)練神經(jīng)網(wǎng)絡(luò)時,從訓(xùn)練開始時積累的梯度平方會導(dǎo)致有效學(xué)習(xí)率過早和過量減小。AdaGrad只在某些深度學(xué)習(xí)模型上效果不錯。AdaGrad算法流程如下:

Require:全局學(xué)習(xí)率 \epsilon
Require:初始參數(shù)\theta
Require:小常數(shù)\delta,為了數(shù)值穩(wěn)定大約設(shè)為10^{-7}
初始化梯度積累變量 r=0
while 停止準(zhǔn)則未滿足 do
\qquad從訓(xùn)練集中采樣,包含m個樣本{x^{(1)},...,x^{(m)}}的小批量,其中x^{(i)}對應(yīng)目標(biāo)為y^{(i)}
\qquad計算梯度:g←+\frac{1}{m}\nabla_{\theta}\sum_iL(f(x^{(i)};\theta),y^{(i)})
\qquad積累平方梯度:r←r + g \odot g
\qquad計算更新:\Delta \theta = \frac{-\epsilon}{\sqrt{\delta+r}} \odot g (逐元素地應(yīng)用除和求平方根)
\qquad應(yīng)用更新:\theta←\theta + \Delta \theta
end while

在PyTorch中,AdaGrad方法調(diào)用函數(shù)torch.optim.Adagrad:

class torch.optim.Adagrad(params,lr=0.001,lr_decay=0,weight_decay=0)

參數(shù)含義:

  • params:用于優(yōu)化的迭代參數(shù)
  • lr:學(xué)習(xí)率,默認(rèn)為1e-3
  • lr_decay:學(xué)習(xí)率衰減因子,默認(rèn)為0
  • weight_decay:權(quán)值衰減系數(shù),L2參數(shù),默認(rèn)為0
4.RMSProp

AdaGrad在凸函數(shù)中能夠快速收斂,但實際神經(jīng)網(wǎng)絡(luò)的損失函數(shù)難以滿足這個條件。Hilton修改AdaGrad的計算梯度平方累加為對應(yīng)的指數(shù)衰減平均,這就是RMSProp方法。AdaGrad根據(jù)平方梯度的整個歷史收縮學(xué)習(xí)率,使得學(xué)習(xí)率過早和過快的衰減。RMSProp使用指數(shù)衰減平均以丟棄遙遠(yuǎn)過去的歷史,可以避免學(xué)習(xí)率下降過快的問題。在實踐中,RMSProp已被證明是一種有效且實用的深度神經(jīng)網(wǎng)絡(luò)優(yōu)化算法。目前它是深度學(xué)習(xí)從業(yè)者經(jīng)常采用的優(yōu)化方法之一。RMSProp算法流程如下:

Require:全局學(xué)習(xí)率 \epsilon,衰減速率\rho
Require:初始參數(shù)\theta
Require:小常數(shù)\delta,為了數(shù)值穩(wěn)定大約設(shè)為10^{-6}(用于被小數(shù)除時的數(shù)值穩(wěn)定)
初始化梯度積累變量 r=0
while 停止準(zhǔn)則未滿足 do
\qquad從訓(xùn)練集中采樣,包含m個樣本{x^{(1)},...,x^{(m)}}的小批量,其中x^{(i)}對應(yīng)目標(biāo)為y^{(i)}
\qquad計算梯度:g←+\frac{1}{m}\nabla_{\theta}\sum_iL(f(x^{(i)};\theta),y^{(i)})
\qquad積累平方梯度:r←\rho r + (1-\rho) g \odot g
\qquad計算參數(shù)更新:\Delta \theta = \frac{-\epsilon}{\sqrt{\delta+r}} \odot g (逐元素應(yīng)用\frac{1}{\sqrt{\delta+r}}
\qquad應(yīng)用更新:\theta←\theta + \Delta \theta
end while

在PyTorch中,RMSProp方法調(diào)用函數(shù)torch.optim.RMSProp:

class torch.optim.RMSProp(params,lr=0.001,alpha=0.99,eps=1e-8,weight_decay=0,momentum=0,centered=False)

參數(shù)含義:

  • params:用于優(yōu)化的迭代參數(shù)
  • lr:學(xué)習(xí)率,默認(rèn)為1e-3
  • momentum:動量因子,默認(rèn)為0
  • alpha:平滑常量,默認(rèn)為0.99
  • eps:添加到分母的因子,用于改善分子穩(wěn)定性,默認(rèn)為1e-8
  • centered:如果為真,計算中心化的RMSProp,梯度根據(jù)它的方差進(jìn)行歸一化
  • weight_decay:權(quán)值衰減系數(shù),L2參數(shù),默認(rèn)為0
5.Adam

Adam是另一種學(xué)習(xí)率自適應(yīng)的優(yōu)化算法,被看作RMSProp方法和動量方法的結(jié)合。首先,在Adam中,動量直接并入了梯度一階矩的估計。將動量加入RMSProp最直觀的方法是將動量應(yīng)用于收縮后的梯度。其次,Adam包括偏置修正,修正從原點初始化的一階矩和二階矩的估計。Adam方法的優(yōu)點在于經(jīng)過偏置校正后,每一次迭代學(xué)習(xí)率都有一個確定的范圍,從而使得參數(shù)比較平穩(wěn)。Adam方法通常被認(rèn)為是優(yōu)秀的優(yōu)化方法。Adam算法流程如下:

Require:全局學(xué)習(xí)率 \epsilon(建議默認(rèn)為0.001)
Require:矩估計的指數(shù)衰減速率\rho_1\rho_2在區(qū)間[0,1]內(nèi)(建議\rho_1\rho_2默認(rèn)為0.9和0.99)
Require:用于數(shù)值穩(wěn)定的小常數(shù)\delta(建議默認(rèn)值為1e-8)
Require:初始參數(shù)\theta
初始化一階矩和二階矩變量 s=0,r=0
初始化同步時間t=0
while 停止準(zhǔn)則未滿足 do
\qquad從訓(xùn)練集中采樣,包含m個樣本{x^{(1)},...,x^{(m)}}的小批量,其中x^{(i)}對應(yīng)目標(biāo)為y^{(i)}
\qquad計算梯度:g←+\frac{1}{m}\nabla_{\theta}\sum_iL(f(x^{(i)};\theta),y^{(i)})
\qquad t←t+1
\qquad更新有偏一階矩估計:s←\rho_1 s + (1-\rho_1) g
\qquad更新有偏二階矩估計:r←\rho_2 r + (1-\rho_2) g \odot g
\qquad修正一階矩的偏差:\hat s = \frac{s}{1-\rho_1^t}
\qquad修正二階矩的偏差:\hat r = \frac{r}{1-\rho_2^t}
\qquad計算更新:\Delta \theta = -\epsilon \frac{\hat s}{\sqrt{\hat r + \delta}} (逐元素應(yīng)用操作)
\qquad應(yīng)用更新:\theta←\theta + \Delta \theta
end while

在PyTorch中,Adam方法調(diào)用函數(shù)torch.optim.Adam:

class torch.optim.Adam(params,lr=0.001,betas=(0.9,0.99),eps=1e-8,weight_decay=0)

參數(shù)含義:

  • params:用于優(yōu)化的迭代參數(shù)
  • lr:學(xué)習(xí)率,默認(rèn)為1e-3
  • betas:用于計算梯度平均和平方的參數(shù),默認(rèn)為(0.9,0.99)
  • eps:添加到分母的因子,用于改善分子穩(wěn)定性,默認(rèn)為1e-8
  • weight_decay:權(quán)值衰減系數(shù),L2參數(shù),默認(rèn)為0
6.選擇正確的優(yōu)化算法

前面討論了一系列算法,通過自適應(yīng)每個模型參數(shù)的學(xué)習(xí)率以解決優(yōu)化深度模型中的難題。此時,一個自然的問題是:應(yīng)該選擇哪種算法呢?遺憾的是,目前在這一點上沒有達(dá)成共識。chaul et al. (2014)展示了許多優(yōu)化算法在大量學(xué)習(xí)任務(wù)上極具價值的比較。結(jié)果表明,具有自適應(yīng)學(xué)習(xí)率(以RMSProp和AdaDelta為代表)的算法族表現(xiàn)得相當(dāng)健壯,性能差不多,但是沒有哪個算法脫穎而出。

目前,最流行并且使用很高的優(yōu)化算法包括SGD、具有動量的SGD、RMSProp、AdaDelta和Adam。如果你的數(shù)據(jù)是稀疏的,那么最好使用自適應(yīng)學(xué)習(xí)率SGD優(yōu)化方法(AdaGrad、AdaDelta、RMSProp和Adam),因為不需要在迭代過程中對學(xué)習(xí)率進(jìn)行人工調(diào)整。RMSProp是AdaGrad的一種擴展,與AdaDelta類似,但是改進(jìn)版的AdaDelta使用RMS取自動更新學(xué)習(xí)率,并且不需要設(shè)置初始學(xué)習(xí)率。Adam是在RMSProp基礎(chǔ)上使用動量與偏差修正。RMSProp、AdaDelta與Adam在類似的情形下表現(xiàn)的差不多。得益于偏差修正,Adam略優(yōu)于RMSProp,因為其在接近收斂時梯度變得更加稀疏。因此,Adam可能是目前最好的SGD優(yōu)化方法。

有趣的是,最近很多論文都是使用原始的SGD梯度下降算法,并且使用簡單的學(xué)習(xí)速率退火調(diào)整(無動量項)?,F(xiàn)有的實驗已經(jīng)表明:SGD能夠收斂于最小值點,但是相對于其他的SGD,它可能花費的時間更長,并且依賴于健壯的初始值及學(xué)習(xí)速率退火調(diào)整策略,并且很容易陷入局部極小值點,甚至鞍點。因此如果你在意收斂速度或者訓(xùn)練一個更深或者更復(fù)雜的網(wǎng)絡(luò),應(yīng)該選擇一個自適應(yīng)學(xué)習(xí)速率的SGD。

為了使得學(xué)習(xí)過程無偏,應(yīng)該在每次迭代中隨機打亂訓(xùn)練集中的樣本。在驗證集上如果連續(xù)的多次迭代過程中損失函數(shù)不再顯著地降低,那么應(yīng)該提前結(jié)束訓(xùn)練。對梯度增加隨機噪聲會增加模型的健壯性,即使初始參數(shù)值選擇的不好,并適合對特別深層次的網(wǎng)絡(luò)進(jìn)行訓(xùn)練。其原因在于增加隨機噪聲有更多的可能性跳過局部極值點并去尋找一個更好的局部極值點,這種可能性在深層次的網(wǎng)絡(luò)中更常見。

7.優(yōu)化器的使用示例

(1)加載數(shù)據(jù)

import torch
import torch.utils.data as Data
import torch.nn.functional as F
from torch.autograd import Variable
import matplotlib.pyplot as plt
import numpy as np

torch.manual_seed(1)  # 確定隨機種子,保證結(jié)果可重復(fù)

LR = 0.01
BATCH_SIZE = 20
EPOCH = 10

# 生成數(shù)據(jù)
x = torch.unsqueeze(torch.linspace(-1,1,1500),dim=1)
y = x.pow(3) + 0.1 * torch.normal(torch.zeros(*x.size()))

# 數(shù)據(jù)畫圖
plt.scatter(x.numpy(),y.numpy())
plt.show()

# 把數(shù)據(jù)轉(zhuǎn)換為torch需要的類型
torch_dataset = Data.TensorDataset(x,y)
loader = Data.DataLoader(dataset=torch_dataset,batch_size=BATCH_SIZE,shuffle=True,num_workers=2)

數(shù)據(jù)如圖所示:

image.png

(2)配置模型和優(yōu)化器

# 定義模型
class Net(torch.nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.hidden = torch.nn.Linear(1,20)
        self.predict = torch.nn.Linear(20,1)
        
    def forward(self,x):
        x = F.relu(self.hidden(x))
        x = self.predict(x)
        return x
    
# 不同的模型
net_SGD = Net()
net_Momentum = Net()
net_RMSprop = Net()
net_AdaGrad = Net()
net_Adam = Net()

nets = [net_SGD,net_Momentum,net_AdaGrad,net_RMSprop,net_Adam]

# 不同的優(yōu)化器
opt_SGD = torch.optim.SGD(net_SGD.parameters(),lr=LR)
opt_Momentum = torch.optim.SGD(net_Momentum.parameters(),lr=LR,momentum=0.8)
opt_AdaGrad = torch.optim.Adagrad(net_AdaGrad.parameters(),lr=LR)
opt_RMSprop = torch.optim.RMSprop(net_RMSprop.parameters(),lr=LR,alpha=0.9)
opt_Adam = torch.optim.Adam(net_Adam.parameters(),lr=LR,betas=(0.9,0.99))

optimizers = [opt_SGD,opt_Momentum,opt_AdaGrad,opt_RMSprop,opt_Adam]

loss_func = torch.nn.MSELoss()
losses_his = [[],[],[],[],[]]

(3)使用各個優(yōu)化器訓(xùn)練模型,對比優(yōu)化器的結(jié)果

# 訓(xùn)練模型
for epoch in range(EPOCH):
    print('Epoch: ',epoch)
    for step,(batch_x,batch_y) in enumerate(loader):
        b_x = Variable(batch_x)
        b_y = Variable(batch_y)
        
        for net,opt,l_his in zip(nets,optimizers,losses_his):
            output = net(b_x)
            loss = loss_func(output,b_y)
            opt.zero_grad()
            loss.backward()
            opt.step()
            l_his.append(loss.item())

labels = ['SGD','Momentum','AdaGrad','RMSprop','Adam']
for i,l_his in enumerate(losses_his):
    plt.plot(l_his,label=labels[i])

plt.legend(loc='best')
plt.xlabel('Steps')
plt.ylabel('Loss')
plt.ylim((0,0.2))
plt.show()

結(jié)果如下所示:

image.png

4.正則化

前面介紹的是深度學(xué)習(xí)的優(yōu)化方法,是為了讓訓(xùn)練過程更加高效。此外,我們要求模型不僅在訓(xùn)練集上表現(xiàn)良好,而且也要在測試集上表現(xiàn)良好。同時滿足這兩個條件的能力稱為模型的泛化能力。如果一個模型在訓(xùn)練集表現(xiàn)良好,但是在測試集表現(xiàn)很差,則稱為模型過擬合。如果一個模型在訓(xùn)練集和測試集都表現(xiàn)很差,則稱為模型欠擬合。如下圖所示:

image.png

要在欠擬合和過擬合中間取得平衡,一個常用的方法是正則化(Regularization)。正則化的思想就是在目標(biāo)函數(shù)中引入額外的信息來懲罰過大的權(quán)重參數(shù)。假設(shè)神經(jīng)網(wǎng)絡(luò)模型在訓(xùn)練過程中使用的目標(biāo)函數(shù)是J(\theta),那么在優(yōu)化時不是直接優(yōu)化J(\theta),而是優(yōu)化J(\theta)+\lambda R(W)。其中\lambda稱為正則項系數(shù),\lambda R(W)稱為正則項,\lambda \in [0, \infty],\lambda等于0表示不使用正則化,\lambda越大表示正則化懲罰越大。需要說明的是,在深度學(xué)習(xí)中,參數(shù)包括每一層神經(jīng)網(wǎng)絡(luò)的權(quán)重W和偏置b,通常只對權(quán)重做正則化懲罰而不對偏置做正則化懲罰。

1.參數(shù)規(guī)范懲罰

參數(shù)規(guī)范懲罰包括L2參數(shù)正則化和L1參數(shù)正則化。

(1)L2參數(shù)正則化

在深度學(xué)習(xí)中,L2正則化又稱為權(quán)值衰減。L2正則化通常的做法是只針對權(quán)重W,而不針對偏置b。對模型參數(shù)W的L2正則化被定義為:

R(W)=\frac{1}{2}||W||_2=\frac{1}{2}\sum_{j=1}^{m}w_j^2

L2正則化能讓權(quán)重W變小,這也是權(quán)值衰減的由來。過擬合的時候,在某些小區(qū)間內(nèi),函數(shù)值的變化比較劇烈,由于函數(shù)在某些小區(qū)間里的導(dǎo)數(shù)值比較大,而自變量可大可小,要使得導(dǎo)數(shù)比較大,這意味著權(quán)值W的值比較大。正則化約束參數(shù)的范數(shù)使其不能太大,可以在一定程度上減少過擬合的情況。

(2)L1參數(shù)正則化

對模型參數(shù)W的L1正則化被定義為:

R(W)=||W||_1=\sum_{j=1}^{m}|w_j|

相比L2正則化,L1正則化會產(chǎn)生更稀疏的解。L1正則化的稀疏性已經(jīng)廣泛應(yīng)用于特征選擇機制。

通常來講,正則化的神經(jīng)網(wǎng)絡(luò)要比未正則化的神經(jīng)網(wǎng)絡(luò)的泛化能力更好。

在PyTorch中,只實現(xiàn)有L2正則化,沒有實現(xiàn)L1正則化。在torch.optim.SGD和其他torch.optim優(yōu)化算法中,weight_decay就是L2正則化。

2.Batch Normalization

在機器學(xué)習(xí)中,如果訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù)都符合相同的狀態(tài)分布,那么訓(xùn)練的模型能夠較好地預(yù)測測試數(shù)據(jù)集上的數(shù)據(jù);反之,訓(xùn)練的模型在測試數(shù)據(jù)集上的表現(xiàn)就會很差。在訓(xùn)練神經(jīng)網(wǎng)絡(luò)模型時,可以事先將特征去相關(guān),并使得它們滿足一個比較好的分布,比如標(biāo)準(zhǔn)正態(tài)分布,這樣模型的第一層網(wǎng)絡(luò)一般都會有一個比較好的輸入特征。但是隨著模型層次的加深,網(wǎng)絡(luò)的非線性變換使得每一層的結(jié)果變得相關(guān)了,并且不再滿足標(biāo)準(zhǔn)正態(tài)分布。更糟糕的是,可能這些隱含層的特征分布已經(jīng)發(fā)生了偏移。為了解決這個問題,研究人員提出在層與層之間加入BN層(Batch Normalization,批量標(biāo)準(zhǔn)化層)。訓(xùn)練時,BN層會利用隱含層輸出結(jié)果的均值與方差標(biāo)準(zhǔn)化每一層特征的分布,并且維護(hù)所有Mini-Batch數(shù)據(jù)的均值和方差,最后把樣本的均值和方差的無偏估計量用于測試時使用。

鑒于在某些情況下非標(biāo)準(zhǔn)化分布的層的特征可能是最優(yōu)的,標(biāo)準(zhǔn)化每一層的輸出特征反而會使得網(wǎng)絡(luò)的表達(dá)能力變得不好,BN層加上了兩個可學(xué)習(xí)的縮放參數(shù)和偏移參數(shù)以便使模型自適應(yīng)地調(diào)整層的特征分布。

Batch Normalization是一種非常簡單而又實用的加速收斂的技術(shù)。其作用有:

  • 使得模型訓(xùn)練收斂速度更快
  • 模型隱含層輸出特征分布更穩(wěn)定,更利于模型的學(xué)習(xí)

在PyTorch中,有封裝好的Batch Normalization層,相應(yīng)的類定義如下,可以直接使用:

class torch.nn.BatchNorm1d(num_features,eps=1e-5,momentum=0.1,affine=True)
class torch.nn.BatchNorm2d(num_features,eps=1e-5,momentum=0.1,affine=True)
class torch.nn.BatchNorm3d(num_features,eps=1e-5,momentum=0.1,affine=True)

對于小批量(Mini-Batch)的2d或3d輸入進(jìn)行批量標(biāo)準(zhǔn)化(Batch Normalization)操作,在每一個小批量數(shù)據(jù)中,計算輸入各個維度的均值和標(biāo)準(zhǔn)差。gamma和beta是可學(xué)習(xí)的、大小為C的參數(shù)向量(C為輸入大?。?。在訓(xùn)練時,該層計算每次輸入的均值和方差,并進(jìn)行移動平均。移動平均默認(rèn)的動量值為0.1。

在測試時,訓(xùn)練求得的均值和方差將用來標(biāo)準(zhǔn)化測試數(shù)據(jù)。

參數(shù)含義:

  • num_features:來自期望輸入的特征數(shù)。
  • eps:為保證數(shù)值穩(wěn)定性(分母不能趨近或等于0),給分母加上的值,默認(rèn)為1e-5。
  • momentum:動態(tài)均值和動態(tài)方差所使用的動量,默認(rèn)為0.1。
  • affine:一個布爾值,默認(rèn)為True,表示給該層加上可學(xué)習(xí)的仿射變換參數(shù)。

使用示例:

# 帶有可學(xué)習(xí)的參數(shù)
m = nn.BatchNorm1d(100)

# 不帶有可學(xué)習(xí)的參數(shù)
m = nn.BatchNorm1d(100,affine=False)
input = autograd.Variable(torch.randn(20,100))
output = m(input)
3.Dropout

Dropout是指在深度神經(jīng)網(wǎng)絡(luò)的訓(xùn)練過程中,對于某些神經(jīng)元,按照一定的概率將其暫時從網(wǎng)絡(luò)中丟棄,這樣可以讓模型更加健壯,因為它不會太依賴某些局部的特征(因為局部特征有可能被丟棄)。注意是暫時,對于隨機梯度下降來說,由于是隨機丟棄,故而每一個小批量都是在訓(xùn)練不同的網(wǎng)絡(luò)。

image.png

左圖是一個標(biāo)準(zhǔn)的全連接的神經(jīng)網(wǎng)絡(luò),右圖是對左圖應(yīng)用了dropout的結(jié)果,會以一定的概率隨機的丟棄一些神經(jīng)元。在實踐中通過把神經(jīng)元的輸出置為0來“關(guān)閉”神經(jīng)元。具體步驟如下:

(1)建立一個維度和本層神經(jīng)元相同的矩陣D
(2)根據(jù)概率(keep_prop)將D中的元素置為0,置為0的神經(jīng)元表示該神經(jīng)元失效,不參與后續(xù)計算
(3)將本層激活函數(shù)的輸出與D相乘作為新的輸出
(4)新的輸出將除以keep_prop,以保證訓(xùn)練和測試滿足同一分布,這樣在測試中Dropout就可以參與計算了。

在PyTorch中,Dropout有專門的Dropout層,包括兩個類:

class torch.nn.Dropout(p=0.5,inplace=False)
class torch.nn.Dropout2d(p=0.5,inplace=False)

Dropout在訓(xùn)練中根據(jù)伯努利分布隨機將輸入張量中的部分元素(概率p)置為0。對于每次前向調(diào)用,被置為0的元素都是隨機的。參數(shù)含義如下:

  • p:將元素置為0的概率,默認(rèn)為0.5
  • inplace:若設(shè)置True,則對input進(jìn)行直接處理。默認(rèn)為False

其中,Dropout2d的輸入來自conv2d模塊。

在訓(xùn)練中,Dropout的輸出需要乘以1/(1-p),這樣訓(xùn)練和測試將滿足同一分布。

示例如下:

import torch
torch.manual_seed(1)
m = torch.nn.Dropout(p=0.5)
input = torch.autograd.Variable(torch.randn(5,5))
output = m(input)
print(input)
print(output)

變量input是:

tensor([[-1.5256, -0.7502, -0.6540, -1.6095, -0.1002],
        [-0.6092, -0.9798, -1.6091, -0.7121,  1.1712],
        [ 1.7674, -0.0954,  0.1394, -1.5785, -0.3206],
        [-0.2993,  1.8793,  0.3357,  0.2753,  1.7163],
        [-0.0561,  0.9107, -1.3924,  2.6891, -0.1110]])

變量output是:

tensor([[-3.0512, -0.0000, -0.0000, -0.0000, -0.0000],
        [-1.2184, -1.9595, -0.0000, -1.4243,  0.0000],
        [ 3.5349, -0.1907,  0.2787, -0.0000, -0.0000],
        [-0.5987,  3.7587,  0.6715,  0.5507,  3.4326],
        [-0.0000,  0.0000, -0.0000,  5.3782, -0.2220]])

除了這里介紹的正則化方法之外,還有一些正則化方法也很常用,比如數(shù)據(jù)集增強、噪聲健壯性、多任務(wù)學(xué)習(xí)和提前終止等。由于沒有在PyTorch中實現(xiàn),這里不再展開。感興趣的話可以參考《深度學(xué)習(xí)》一書了解相關(guān)內(nèi)容。

5.PyTorch示例:深度神經(jīng)網(wǎng)絡(luò)實現(xiàn)

本節(jié)介紹如何使用PyTorch實現(xiàn)一個簡單的深度神經(jīng)網(wǎng)絡(luò)(手寫數(shù)字識別程序),對手寫數(shù)字?jǐn)?shù)據(jù)集MNIST進(jìn)行學(xué)習(xí)和預(yù)測,預(yù)期可以達(dá)到98%左右的準(zhǔn)確率。該神經(jīng)網(wǎng)絡(luò)由1個輸入層、1個全連接層結(jié)構(gòu)的隱含層和1個輸出層構(gòu)成。我們通過這個例子可以掌握設(shè)計深度神經(jīng)網(wǎng)絡(luò)的特征及參數(shù)的配置。

1.配置庫和配置參數(shù)
import torch
import torch.nn as nn
import torchvision.datasets as dsets
import torchvision.transforms as transforms
from torch.autograd import Variable

torch.manual_seed(1)      # 設(shè)置人工種子,保證結(jié)果可重復(fù)
input_size = 784          # 圖片為28*28=784個特征,輸入層大?。簃 * 784 
hidden_size = 500         # 隱含層大小:784 * 500
num_classes = 10          # 輸出層大?。?00 * 10
num_epochs = 5            # 訓(xùn)練5輪
batch_size = 100          # 每個批次100個樣本,60000個訓(xùn)練樣本要分成600個批次進(jìn)行
learning_rate = 0.001     # 學(xué)習(xí)率0.001
2.加載MNIST數(shù)據(jù)集
# 加載訓(xùn)練數(shù)據(jù)(可以手動下載數(shù)據(jù)放到./data目錄)
train_dataset = dsets.MNIST(root='./data',
                           train=True,
                           transform=transforms.ToTensor(),
                           download=True)
# 加載測試數(shù)據(jù)
test_dataset = dsets.MNIST(root='./data',
                           train=False,
                           transform=transforms.ToTensor()
                          )
3.數(shù)據(jù)的批處理
# 訓(xùn)練集的shuffle必須為True,表示每次從60000訓(xùn)練樣本中隨機選擇100個作為一個批次
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                          batch_size=batch_size,
                                          shuffle=True)
# 測試集的shuffle要為False,即要保證10000個測試樣本都只被預(yù)測一遍
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                          batch_size=batch_size,
                                          shuffle=False)
4.創(chuàng)建DNN模型
class Net(nn.Module):
    def __init__(self,input_size,hidden_size,num_classes):
        super(Net,self).__init__()
        self.fc1 = nn.Linear(input_size,hidden_size)        # 線性變換,即:m * 784 --> 784 * 500
        self.relu = nn.ReLU()                               # 激活函數(shù)
        self.fc2 = nn.Linear(hidden_size,num_classes)       # 線性變換,即:784 * 500 --> 500 * 10
        
    def forward(self,x):
        out = self.fc1(x)
        out = self.relu(out)
        out = self.fc2(out)
        return out
    
net = Net(input_size,hidden_size,num_classes)
print(net)

輸出如下:

Net(
  (fc1): Linear(in_features=784, out_features=500, bias=True)
  (relu): ReLU()
  (fc2): Linear(in_features=500, out_features=10, bias=True)
)
5.訓(xùn)練模型
# 使用交叉熵?fù)p失函數(shù):CrossEntropyLoss
criterion = nn.CrossEntropyLoss()
# 使用Adam優(yōu)化器
optimizer = torch.optim.Adam(net.parameters(),lr=learning_rate)
# 訓(xùn)練5輪
for epoch in range(num_epochs):
    # 每次從60000訓(xùn)練樣本中隨機選擇100個作為一個批次,所以共重復(fù)600次
    for i,(images,labels) in enumerate(train_loader):
        images = Variable(images.view(-1,28*28))  # images大?。?00 * 784
        labels = Variable(labels)                 # labels大小:100 * 1
        optimizer.zero_grad()                     # 梯度清零
        outputs = net(images)                     # 輸入網(wǎng)絡(luò),前向傳播
        loss = criterion(outputs,labels)          # 計算損失
        loss.backward()                           # 損失后向傳播
        optimizer.step()                          # 更新梯度
        # 每隔100個批次打印一次信息
        if (i+1)%100 == 0:
            print('Epoch [%d/%d], Step[%d/%d], Loss: %.4f' % (epoch+1,num_epochs,i+1,len(train_dataset)//batch_size,loss.item()))

輸出如下:

Epoch [1/5], Step[100/600], Loss: 0.2454
Epoch [1/5], Step[200/600], Loss: 0.2444
Epoch [1/5], Step[300/600], Loss: 0.2048
Epoch [1/5], Step[400/600], Loss: 0.1400
Epoch [1/5], Step[500/600], Loss: 0.1388
Epoch [1/5], Step[600/600], Loss: 0.1777
Epoch [2/5], Step[100/600], Loss: 0.0496
Epoch [2/5], Step[200/600], Loss: 0.0722
Epoch [2/5], Step[300/600], Loss: 0.1917
Epoch [2/5], Step[400/600], Loss: 0.1537
Epoch [2/5], Step[500/600], Loss: 0.1080
Epoch [2/5], Step[600/600], Loss: 0.1118
Epoch [3/5], Step[100/600], Loss: 0.0559
Epoch [3/5], Step[200/600], Loss: 0.0333
Epoch [3/5], Step[300/600], Loss: 0.1146
Epoch [3/5], Step[400/600], Loss: 0.1371
Epoch [3/5], Step[500/600], Loss: 0.0477
Epoch [3/5], Step[600/600], Loss: 0.0597
Epoch [4/5], Step[100/600], Loss: 0.0746
Epoch [4/5], Step[200/600], Loss: 0.0128
Epoch [4/5], Step[300/600], Loss: 0.0349
Epoch [4/5], Step[400/600], Loss: 0.0418
Epoch [4/5], Step[500/600], Loss: 0.0298
Epoch [4/5], Step[600/600], Loss: 0.0356
Epoch [5/5], Step[100/600], Loss: 0.0456
Epoch [5/5], Step[200/600], Loss: 0.0877
Epoch [5/5], Step[300/600], Loss: 0.0280
Epoch [5/5], Step[400/600], Loss: 0.0525
Epoch [5/5], Step[500/600], Loss: 0.0416
Epoch [5/5], Step[600/600], Loss: 0.0104
6.評估模型

使用測試集進(jìn)行模型評估,計算模型的準(zhǔn)確度:

correct = 0    # 記錄預(yù)測正確的個數(shù)
total = 0        # 記錄預(yù)測的總個數(shù)(一般就是測試集大?。?# 測試集大小10000,每個批次大小100個,共100個批次
for images,labels in test_loader:
    images = Variable(images.view(-1,28*28))   # images大?。?00 * 784
    outputs = net(images)                      # 使用訓(xùn)練好的網(wǎng)絡(luò)進(jìn)行計算
    _,predicted = torch.max(outputs.data,1)    # 數(shù)字識別共10分類,會得到10個概率值,以最大概率的類別為預(yù)測類別
    total += labels.size(0)                    # 累加預(yù)測總個數(shù)
    correct += (predicted==labels).sum()       # 累加預(yù)測正確總個數(shù)
# 打印全部測試集上的正確率
print('Accuracy of the network on the 10000 test images: %d %%' % (100*correct/total))

輸出如下:

Accuracy of the network on the 10000 test images: 98 %
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容