魚書(深度學(xué)習(xí)入門)第四章:神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)
一、從數(shù)據(jù)中學(xué)習(xí)
????神經(jīng)網(wǎng)絡(luò)的特征就是可以從數(shù)據(jù)中學(xué)習(xí)。所謂“從數(shù)據(jù)中學(xué)習(xí)”,是指
可以由數(shù)據(jù)自動決定權(quán)重參數(shù)的值。
????數(shù)據(jù)是機(jī)器學(xué)習(xí)的命根子。從數(shù)據(jù)中尋找答案、從數(shù)據(jù)中發(fā)現(xiàn)模式、根據(jù)數(shù)據(jù)講故事……這些機(jī)器學(xué)習(xí)所做的事情,如果沒有數(shù)據(jù)的話,就無從談起。因此,數(shù)據(jù)是機(jī)器學(xué)習(xí)的核心。這種數(shù)據(jù)驅(qū)動的方法,也可以說是脫離了以人為中心的方法。
????與以往解決方法的思路不同,機(jī)器學(xué)習(xí)的方法極力避免人為介入,嘗試從收集到的數(shù)據(jù)中發(fā)現(xiàn)答案(模式)。神經(jīng)網(wǎng)絡(luò)或深度學(xué)習(xí)則比以往的機(jī)器學(xué)習(xí)方法更能避免人為介入。
????舉一個例子:識別手寫數(shù)字的問題。一種方案是先從圖像中提取特征量,再用機(jī)器學(xué)習(xí)技術(shù)學(xué)習(xí)這些特征量的模式。這里所說的“特征量”是指可以從輸入數(shù)據(jù)(輸入圖像)中準(zhǔn)確地提取本質(zhì)數(shù)據(jù)(重要的數(shù)據(jù))的轉(zhuǎn)換器。圖像的特征量通常表示為向量的形式。但是需要注意的是,將圖像轉(zhuǎn)換為向量時使用的特征量仍是由人設(shè)計(jì)的。也就是說,即使使用特征量和機(jī)器學(xué)習(xí)的方法,也需要針對不同的問題人工考慮合適的特征量。而神經(jīng)網(wǎng)絡(luò)直接學(xué)習(xí)圖像本身。在神經(jīng)網(wǎng)絡(luò)中,連圖像中包含的重要特征量也都是由機(jī)器來學(xué)習(xí)的。其關(guān)系如下圖所示:

????深 度 學(xué) 習(xí) 有 時 也 稱 為 端 到 端 機(jī) 器 學(xué) 習(xí)(end-to-end machine learning)。這里所說的端到端是指從一端到另一端的意思,也就是從原始數(shù)據(jù)(輸入)中獲得目標(biāo)結(jié)果(輸出)的意思。神經(jīng)網(wǎng)絡(luò)的優(yōu)點(diǎn)是對所有的問題都可以用同樣的流程來解決。
????機(jī)器學(xué)習(xí)中,一般將數(shù)據(jù)分為訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù)兩部分來進(jìn)行學(xué)習(xí)和實(shí)驗(yàn)等。首先,使用訓(xùn)練數(shù)據(jù)進(jìn)行學(xué)習(xí),尋找最優(yōu)的參數(shù);然后,使用測試數(shù)據(jù)評價(jià)訓(xùn)練得到的模型的實(shí)際能力。將數(shù)據(jù)分為訓(xùn)練數(shù)據(jù)與測試數(shù)據(jù)的原因是要追求泛化能力。為了正確評價(jià)模型的泛化能力,就必須劃分訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù)。另外,訓(xùn)練數(shù)據(jù)也可以稱為監(jiān)督數(shù)據(jù)。泛化能力是指處理未被觀察過的數(shù)據(jù)(不包含在訓(xùn)練數(shù)據(jù)中的數(shù)據(jù))的能力。獲得泛化能力是機(jī)器學(xué)習(xí)的最終目標(biāo)。因此,僅僅用一個數(shù)據(jù)集去學(xué)習(xí)和評價(jià)參數(shù),是無法進(jìn)行正確評價(jià)的。只對某個數(shù)據(jù)集過度擬合的狀態(tài)稱為過擬合(over fitting)。
二、損失函數(shù)
????神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)通過某個指標(biāo)表示現(xiàn)在的狀態(tài)。然后,以這個指標(biāo)為基準(zhǔn),尋找最優(yōu)權(quán)重參數(shù)。。神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)中所用的指標(biāo)稱為損失函數(shù)(loss function)。這個損失函數(shù)可以使用任意函數(shù),但一般用均方誤差和交叉熵誤差等。
????1.均方誤差
????可以用作損失函數(shù)的函數(shù)有很多,其中最有名的是均方誤差(mean squared error)。均方誤差如下式所示。

????這里,yk是表示神經(jīng)網(wǎng)絡(luò)的輸出,tk表示監(jiān)督數(shù)據(jù),k表示數(shù)據(jù)的維數(shù)。需要注意的是,將正確解標(biāo)簽表示為1,其他標(biāo)簽表示為0的表示方法稱為one-hot表示。
????均方誤差會計(jì)算神經(jīng)網(wǎng)絡(luò)的輸出和正確解監(jiān)督數(shù)據(jù)的各個元素之差的平方,再求總和。其代碼實(shí)現(xiàn)如下:
def mean_squared_error(y,t): #求解均方誤差
? ? return 0.5*np.sum((y-t)**2)
????2.交叉熵誤差
????除了均方誤差之外,交叉熵誤差(cross entropy error)也經(jīng)常被用作損失函數(shù)。交叉熵誤差如下式所示。

????這里,log表示以e為底數(shù)的自然對數(shù),yk是神經(jīng)網(wǎng)絡(luò)的輸出,tk是正確解標(biāo)簽。并且,tk中只有正確解標(biāo)簽的索引為1,其他均為0(one-hot表示)。因此,其實(shí)際上只計(jì)算對應(yīng)正確解標(biāo)簽的輸出的自然對數(shù)。由此可知,交叉熵誤差的值是由正確解標(biāo)簽所對應(yīng)的輸出結(jié)果決定的。正確解標(biāo)簽對應(yīng)的輸出越大,式子的值越接近0;當(dāng)輸出為1時,交叉熵誤差為0。
????其代碼實(shí)現(xiàn)如下:
def cross_entropy_error(y,t): #求解交叉熵誤差
? ? delta=1e-7 #保護(hù)性對策,防止出現(xiàn)負(fù)無限大
? ? return -np.sum(t*np.log(y+delta))
????這里,參數(shù)y和t是NumPy數(shù)組。函數(shù)內(nèi)部在計(jì)算np.log時,加上了一個微小值delta。這是因?yàn)椋?dāng)出現(xiàn)np.log(0)時,np.log(0)會變?yōu)樨?fù)無限大的-inf,這樣一來就會導(dǎo)致后續(xù)計(jì)算無法進(jìn)行。作為保護(hù)性對策,添加一個微小值可以防止負(fù)無限大的發(fā)生。
????3.mini-batch學(xué)習(xí)
????機(jī)器學(xué)習(xí)使用訓(xùn)練數(shù)據(jù)進(jìn)行學(xué)習(xí)。使用訓(xùn)練數(shù)據(jù)進(jìn)行學(xué)習(xí),嚴(yán)格來說,就是針對訓(xùn)練數(shù)據(jù)計(jì)算損失函數(shù)的值,找出使該值盡可能小的參數(shù)。因此,計(jì)算損失函數(shù)時必須將所有的訓(xùn)練數(shù)據(jù)作為對象。要求所有訓(xùn)練數(shù)據(jù)的損失函數(shù)的總和,以交叉熵誤差為例,可以寫成下面的式子。

????這里,假設(shè)數(shù)據(jù)有N個,tnk表示第n個數(shù)據(jù)的第k個元素的值(ynk是神經(jīng)網(wǎng)絡(luò)的輸出,tnk是監(jiān)督數(shù)據(jù))。最后還要除以N進(jìn)行正規(guī)化。通過除以N,可以求單個數(shù)據(jù)的“平均損失函數(shù)”。通過這樣的平均化,可以獲得和訓(xùn)練數(shù)據(jù)的數(shù)量無關(guān)的統(tǒng)一指標(biāo)。
????如果以全部數(shù)據(jù)為對象求損失函數(shù)的和,則計(jì)算過程需要花費(fèi)較長的時間。再者,如果遇到大數(shù)據(jù),數(shù)據(jù)量會有幾百萬、幾千萬之多,這種情況下以全部數(shù)據(jù)為對象計(jì)算損失函數(shù)是不現(xiàn)實(shí)的。因此,我們從全部數(shù)據(jù)中選出一部分,作為全部數(shù)據(jù)的“近似”。神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)也是從訓(xùn)練數(shù)據(jù)中選出一批數(shù)據(jù)(稱為mini-batch,小批量),然后對每個mini-batch進(jìn)行學(xué)習(xí)。比如,從60000個訓(xùn)練數(shù)據(jù)中隨機(jī)選擇100筆,再用這100筆數(shù)據(jù)進(jìn)行學(xué)習(xí)。這種學(xué)習(xí)方式稱為mini-batch學(xué)習(xí)。
????那么,如何從這個訓(xùn)練數(shù)據(jù)中隨機(jī)抽取10筆數(shù)據(jù)呢?我們可以使用 NumPy的np.random.choice(),寫成如下形式。

使用np.random.choice()可以從指定的數(shù)字中隨機(jī)選擇想要的數(shù)字。比如, np.random.choice(60000, 10)會從0到59999之間隨機(jī)選擇10個數(shù)字。之后,我們只需指定這些隨機(jī)選出的索引,取出mini-batch,然后使用這個mini-batch計(jì)算損失函數(shù)即可。
????這里,我們來實(shí)現(xiàn)一個可以同時處理單個數(shù)據(jù)和批量數(shù)據(jù)(數(shù)據(jù)作為batch集中輸入)兩種情況的函數(shù)。代碼如下:
def cross_entropy_error(y,t): #實(shí)現(xiàn)mini-batch交叉熵誤差(one-hot形式)
? ? if y.ndim==1:
? ? ? ? t=t.reshape(1,t.size)
? ? ? ? y=y.reshape(1,y.size)
? ? batch_size=y.shape[0]
? ? return -np.sum(t*np.log(y+1e-7))/batch_size
????這里,y是神經(jīng)網(wǎng)絡(luò)的輸出,t是監(jiān)督數(shù)據(jù)。y的維度為1時,即求單個數(shù)據(jù)的交叉熵誤差時,需要改變數(shù)據(jù)的形狀。并且,當(dāng)輸入為mini-batch時,要用batch的個數(shù)進(jìn)行正規(guī)化,計(jì)算單個數(shù)據(jù)的平均交叉熵誤差。
????此外,當(dāng)監(jiān)督數(shù)據(jù)是標(biāo)簽形式(非one-hot表示,而是像“2”“7”這樣的標(biāo)簽)時,交叉熵誤差可通過如下代碼實(shí)現(xiàn):
def corss_entropy_error(y,t): #數(shù)據(jù)以標(biāo)簽形式的mini-batch交叉熵誤差
? ? if y.ndim==1:
? ? ? ? t=t.reshape(1,t.size)
? ? ? ? y=y.reshape(1,y.size)
? ? batch_size=y.shape[0]
? ? return -np.sum(np.log(y[np.arange(batch_size),t]+1e-7))/batch_size #通過arange取出特定的數(shù)據(jù)
????4.為什么要設(shè)定損失函數(shù)
????既然我們的目標(biāo)是獲得使識
別精度盡可能高的神經(jīng)網(wǎng)絡(luò),那不是應(yīng)該把識別精度作為指標(biāo)嗎?在神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)中,尋找最優(yōu)參數(shù)(權(quán)重和偏置)時,要尋找使損失函數(shù)的值盡可能小的參數(shù)。為了找到使損失函數(shù)的值盡可能小的地方,需要計(jì)算參數(shù)的導(dǎo)數(shù)(確切地講是梯度),然后以這個導(dǎo)數(shù)為指引,逐步更新參數(shù)的值。對該權(quán)重參數(shù)的損失函數(shù)求導(dǎo),表示的是“如果稍微改變這個權(quán)重參數(shù)的值,損失函數(shù)的值會如何變化”。如果導(dǎo)數(shù)的值為負(fù),通過使該權(quán)重參數(shù)向正方向改變,可以減小損失函數(shù)的值,反之亦同。當(dāng)導(dǎo)數(shù)的值為0時,無論權(quán)重參數(shù)向哪個方向變化,損失函數(shù)的值都不會改變,此時該權(quán)重參數(shù)的更新會停在此處。
????之所以不能用識別精度作為指標(biāo),是因?yàn)檫@樣一來絕大多數(shù)地方的導(dǎo)數(shù)都會變?yōu)?,導(dǎo)致參數(shù)無法更新。識別精度對微小的參數(shù)變化基本上沒有什么反應(yīng),即便有反應(yīng),它的值也是不連續(xù)地、突然地變化。作為激活函數(shù)的階躍函數(shù)也有同樣的情況。出于相同的原因,如果使用階躍函數(shù)作為激活函數(shù),神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)將無法進(jìn)行。如果使用了階躍函數(shù),那么即便將損失函數(shù)作為指標(biāo),參數(shù)的微小變化也會被階躍函數(shù)抹殺,導(dǎo)致?lián)p失函數(shù)的值不會產(chǎn)生任何變化。
????但是對于sigmoid函數(shù)。不僅函數(shù)的輸出(豎軸的值)是連續(xù)變化的,曲線的斜率(導(dǎo)數(shù))也是連續(xù)變化的。也就是說,sigmoid函數(shù)的導(dǎo)數(shù)在任何地方都不為0。這對神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)非常重要。得益于這個斜率不會為0的性質(zhì),神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)得以正確進(jìn)行。
三、數(shù)值微分
????1.導(dǎo)數(shù)
????導(dǎo)數(shù)的定義如下:

????導(dǎo)數(shù)的含義是,x的“微小變化”將導(dǎo)致函數(shù)f(x)的值在多大程度上發(fā)生變化。
????在代碼實(shí)現(xiàn)上。有兩個優(yōu)化方法。
????一個是避免舍入誤差(rounding error)。所謂舍入誤差,是指因省略小數(shù)的精細(xì)部分的數(shù)值(比如,小數(shù)點(diǎn)第8位以后的數(shù)值)而造成最終的計(jì)算結(jié)果上的誤差。所以微小值不能取得太小,一般使用10?4 就可以得到正確的結(jié)果。
????第二個是“真的導(dǎo)數(shù)”對應(yīng)函數(shù)在x處的斜率(稱為切線),但上述實(shí)現(xiàn)中計(jì)算的導(dǎo)數(shù)對應(yīng)的是(x + h)和x之間的斜率,如下圖所示。為了減小這個誤差,我們可以計(jì)算函數(shù)f在(x + h)和(x ? h)之間的差分。因?yàn)檫@種計(jì)算方法以x為中心,計(jì)算它左右兩邊的差分,所以也稱為中心差分(而(x + h)和x之間的差分稱為前向差分)。

????則其代碼實(shí)現(xiàn)如下:
def numerical_diff(f,x): #求數(shù)值微分
? ? h=1e-4 #0.0001
? ? return (f(x+h)-f(x-h))/ (2*h)
????如上所示,利用微小的差分求導(dǎo)數(shù)的過程稱為數(shù)值微分(numerical differentiation)。而基于數(shù)學(xué)式的推導(dǎo)求導(dǎo)數(shù)的過程,則用“解析性”(analytic)一詞,稱為“解析性求解”或者“解析性求導(dǎo)”。解析性求導(dǎo)得到的導(dǎo)數(shù)是不含誤差的“真的導(dǎo)數(shù)”。
????2.偏導(dǎo)數(shù)
????對于有兩個變量的函數(shù),如下面的函數(shù)。我們有必要區(qū)分對哪個變量求導(dǎo)數(shù),即對x0和x1兩個變量中的哪一個求導(dǎo)數(shù)。另外,我們把這里討論的有多個變量的函數(shù)的導(dǎo)數(shù)稱為偏導(dǎo)數(shù)。

????

偏導(dǎo)數(shù)和單變量的導(dǎo)數(shù)一樣,都是求某個地方的斜率。不過,
偏導(dǎo)數(shù)需要將多個變量中的某一個變量定為目標(biāo)變量,并將其他變量固定為某個值。
四、梯度
????由全部變量的偏導(dǎo)數(shù)匯總而成的向量稱為梯度(gradient)。其代碼實(shí)現(xiàn)如下:
def numerical_gradient(f,x):
? ? h=1e-4 #0.0001
? ? grad=np.zeros_like(x) #生成和x形狀相同的數(shù)組
? ? for idx in range(x.size):
? ? ? ? tmp_val=x[idx]
? ? ? ? #f(x+h)的計(jì)算
? ? ? ? x[idx]=tmp_val+h
? ? ? ? fxh1=f(x)
? ? ? ? #f(x-h)的計(jì)算
? ? ? ? x[idx]=tmp_val-h
? ? ? ? fxh2=f(x)
? ? ? ? grad[idx]=(fxh1-fxh2)/(2*h)
? ? ? ? x[idx]=tmp_val #還原值
? ? return grad
????函數(shù)numerical_gradient(f, x)的實(shí)現(xiàn)看上去有些復(fù)雜,但它執(zhí)行的處理和求單變量的數(shù)值微分基本沒有區(qū)別。需要注明的是,np.zeros_
like(x)會生成一個形狀和x相同、所有元素都為0的數(shù)組。函數(shù)numerical_gradient(f, x)中,參數(shù)f為函數(shù),x為NumPy數(shù)組,該函數(shù)對NumPy數(shù)組x的各個元素求數(shù)值微分。
????將梯度呈現(xiàn)為有向向量(箭頭)可得下圖。

????雖然圖中的梯度指向了最低處,但并非任何時候都這樣。實(shí)際上,梯度會指向各點(diǎn)處的函數(shù)值降低的方向。更嚴(yán)格地講,梯度指示的方向是各點(diǎn)處的函數(shù)值減小最多的方向,這個性質(zhì)在高等數(shù)學(xué)中易證。
????1.梯度法
????機(jī)器學(xué)習(xí)的主要任務(wù)是在學(xué)習(xí)時尋找最優(yōu)參數(shù)。這里所說的最優(yōu)參數(shù)是指損失函數(shù)取最小值時的參數(shù)。一般而言,損失函數(shù)很復(fù)雜,參數(shù)空間龐大,我們不知道它在何處能取得最小值。而通過巧妙地使用梯度來尋找函數(shù)最小值
(或者盡可能小的值)的方法就是梯度法。
????這里需要注意的是,梯度表示的是各點(diǎn)處的函數(shù)值減小最多的方向。因此,無法保證梯度所指的方向就是函數(shù)的最小值或者真正應(yīng)該前進(jìn)的方向。實(shí)際上,在復(fù)雜的函數(shù)中,梯度指示的方向基本上都不是函數(shù)值最小處。雖然梯度的方向并不一定指向最小值,但沿著它的方向能夠最大限度地減小函數(shù)的值。因此,在尋找函數(shù)的最小值(或者盡可能小的值)的位置的任務(wù)中,要以梯度的信息為線索,決定前進(jìn)的方向。
????在梯度法中,函數(shù)的取值從當(dāng)前位置沿著梯度方向前進(jìn)一定距離,然后在新的地方重新求梯度,再沿著新梯度方向前進(jìn),如此反復(fù),不斷地沿梯度方向前進(jìn)。通過不斷地沿梯度方向前進(jìn), 逐漸減小函數(shù)值的過程就是梯度法(gradient method)。
????使用數(shù)學(xué)式表示梯度法如下圖所示。

????式子中的η表示更新量,在神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)中,稱為學(xué)習(xí)率(learning rate)。學(xué)習(xí)率決定在一次學(xué)習(xí)中,應(yīng)該學(xué)習(xí)多少,以及在多大程度上更新參數(shù)。學(xué)習(xí)率需要事先確定為某個值,比如0.01或0.001。一般而言,這個值過大或過小,都無法抵達(dá)一個“好的位置”。在神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)中,一般會一邊改變學(xué)習(xí)率的值,一邊確認(rèn)學(xué)習(xí)是否正確進(jìn)行了。
????該式表示更新一次的式子,這個步驟會反復(fù)執(zhí)行。每一步都按該式更新變量的值,通過反復(fù)執(zhí)行此步驟,逐漸減小函數(shù)值。
????其代碼實(shí)現(xiàn)如下:
def gradient_descent(f,init_x,lr=0.01,step_num=100): #梯度下降法
? ? x=init_x?
? ? for i in range(step_num):
? ? ? ? grad=numerical_gradient(f,x)
? ? ? ? x-=lr*grad
? ? return x
????參數(shù)f是要進(jìn)行最優(yōu)化的函數(shù),init_x是初始值,lr是學(xué)習(xí)率learning rate,step_num是梯度法的重復(fù)次數(shù)。numerical_gradient(f,x)會求函數(shù)的梯度,用該梯度乘以學(xué)習(xí)率得到的值進(jìn)行更新操作,由step_num指定重復(fù)的次數(shù)。使用這個函數(shù)可以求函數(shù)的極小值,順利的話,還可以求函數(shù)的最小值。
????下面是初始值為(-3.0, 4.0),開始使用梯度法尋找最小值的圖形。

????實(shí)驗(yàn)結(jié)果表明,學(xué)習(xí)率過大的話,會發(fā)散成一個很大的值;反過來,學(xué)習(xí)率過小的話,基本上沒怎么更新就結(jié)束了。設(shè)定合適的學(xué)習(xí)率是一個很重要的問題。
????像學(xué)習(xí)率這樣的參數(shù)稱為超參數(shù)。相對于神經(jīng)網(wǎng)絡(luò)的權(quán)重參數(shù)是通過訓(xùn)練數(shù)據(jù)和學(xué)習(xí)算法自動獲得的,學(xué)習(xí)率這樣的超參數(shù)則是人工設(shè)定的。一般超參數(shù)需要嘗試多個值,以便找到一種可以使學(xué)習(xí)順利進(jìn)行的設(shè)定。
????2.神經(jīng)網(wǎng)絡(luò)的梯度法
????一個只有一個形狀為2 × 3的權(quán)重W的神經(jīng)網(wǎng)絡(luò),損失函數(shù)用L表示。此時,梯度可以用下圖表示。

????這里的重點(diǎn)是,梯度的形狀和W相同。
????我們以一個簡單的神經(jīng)網(wǎng)絡(luò)為例來實(shí)現(xiàn)求梯度的代碼。
????我們先實(shí)現(xiàn)一個名為simpleNet的類。代碼如下:
class simpleNet:
? ? def __init__(self):
? ? ? ? self.W = np.random.randn(2,3) #用高斯分布初始化
? ? def predict(self, x):
? ? ? ? return np.dot(x, self.W)
? ? def loss(self, x, t):
? ? ? ? z = self.predict(x)
? ? ? ? y = softmax(z)
? ? ? ? loss = cross_entropy_error(y, t)
? ? ? ? return loss
????simpleNet類只有一個實(shí)例變量,即形狀為2×3的權(quán)重參數(shù)。它有兩個方法,一個是用于預(yù) 測的predict(x),另一個是用于求損失函數(shù)值的loss(x,t)。這里參數(shù)x接收輸入數(shù)據(jù),t接收正確解標(biāo)簽。
????接下來求梯度。和前面一樣,我們使用numerical_gradient(f, x)求梯度(這里定義的函數(shù)f(W)的參數(shù)W是一個偽參數(shù)。因?yàn)閚umerical_gradient(f, x)會在內(nèi)部執(zhí)行f(x),為了與之兼容而定義了f(W))。
f = lambda w: net.loss(x, t)
dW = numerical_gradient(f, net.W)
????求出神經(jīng)網(wǎng)絡(luò)的梯度后,接下來只需根據(jù)梯度法,更新權(quán)重參數(shù)即可。
五、學(xué)習(xí)算法的實(shí)現(xiàn)
????神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)步驟如下所示。
????前提????神經(jīng)網(wǎng)絡(luò)存在合適的權(quán)重和偏置,調(diào)整權(quán)重和偏置以便擬合訓(xùn)練數(shù)據(jù)的過程稱為“學(xué)習(xí)”。神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)分成下面4個步驟。
????步驟1(mini-batch)從訓(xùn)練數(shù)據(jù)中隨機(jī)選出一部分?jǐn)?shù)據(jù),這部分?jǐn)?shù)據(jù)稱為mini-batch。我們的目標(biāo)是減小mini-batch的損失函數(shù)的值。
????步驟2(計(jì)算梯度)為了減小mini-batch的損失函數(shù)的值,需要求出各個權(quán)重參數(shù)的梯度。梯度表示損失函數(shù)的值減小最多的方向。
????步驟3(更新參數(shù))將權(quán)重參數(shù)沿梯度方向進(jìn)行微小更新。
????步驟4(重復(fù))重復(fù)步驟1、步驟2、步驟3。
????神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)按照上面4個步驟進(jìn)行。因?yàn)檫@里使用的數(shù)據(jù)是隨機(jī)選擇的mini batch數(shù)據(jù),所以又稱為隨機(jī)梯度下降法(stochastic gradient descent)。隨機(jī)梯度下降法是“對隨機(jī)選擇的數(shù)據(jù)進(jìn)行的梯度下降法”。
????下面我們以2層神經(jīng)網(wǎng)絡(luò)為例子。
????1.2層神經(jīng)網(wǎng)絡(luò)的類
????首先,我們將這個2層神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)為一個名為TwoLayerNet的類。其中所含的變量與方法如下圖所示。詳細(xì)代碼在最后。


????TwoLayerNet類有params和grads兩個字典型實(shí)例變量。params變量中保存 了權(quán)重參數(shù)。params變量中保存的權(quán)重參數(shù)會用在推理處理(前向處理)中。此外,與params變量對應(yīng),grads變量中保存了各個參數(shù)的梯度。使用numerical_gradient()方法計(jì)算梯度后,梯度的信息將保存在grads變量中。
????接著是TwoLayerNet的方法的實(shí)現(xiàn)。首先是__init__(self, input_size, hidden_size, output_size)方法,它是類的初始化方法(所謂初始化方法,就是生成TwoLayerNet實(shí)例時被調(diào)用的方法)。從第1個參數(shù)開始,依次表示輸入層的神經(jīng)元數(shù)、隱藏層的神經(jīng)元數(shù)、輸出層的神經(jīng)元數(shù)。
????此外,這個初始化方法會對權(quán)重參數(shù)進(jìn)行初始化。此處權(quán)重使用符合高斯分布的隨機(jī)數(shù)進(jìn)行初始化,偏置使用0進(jìn)行初始化。另外,loss(self, x, t)是計(jì)算損失函數(shù)值的方法。這個方法會基于predict()的結(jié)果和正確解標(biāo)簽,計(jì)算交叉熵誤差。
????剩下的numerical_gradient(self, x, t)方法會計(jì)算各個參數(shù)的梯度。根據(jù)數(shù)值微分,計(jì)算各個參數(shù)相對于損失函數(shù)的梯度。
????2.mini-batch的實(shí)現(xiàn)
????神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)的實(shí)現(xiàn)使用的是前面介紹過的mini-batch學(xué)習(xí)。所謂mini-batch學(xué)習(xí),就是從訓(xùn)練數(shù)據(jù)中隨機(jī)選擇一部分?jǐn)?shù)據(jù)(稱為mini-batch),再以這些mini-batch為對象,使用梯度法更新參數(shù)的過程。
????這里,mini-batch的大小為100,需要每次從60000個訓(xùn)練數(shù)據(jù)中隨機(jī)取出100個數(shù)據(jù)(圖像數(shù)據(jù)和正確解標(biāo)簽數(shù)據(jù))。下圖是每次循環(huán)后損失函數(shù)的變化。

????可以發(fā)現(xiàn)隨著學(xué)習(xí)的進(jìn)行,損失函數(shù)的值在不斷減小。這是學(xué)習(xí)正常進(jìn)行的信號,表示神經(jīng)網(wǎng)絡(luò)的權(quán)重參數(shù)在逐漸擬合數(shù)據(jù)。通過反復(fù)地向它澆灌(輸入)數(shù)據(jù),神經(jīng)網(wǎng)絡(luò)正在逐漸向最優(yōu)參數(shù)靠近。
????3.基于測試數(shù)據(jù)的評價(jià)
????上面的環(huán)節(jié)我們確認(rèn)了通過反復(fù)學(xué)習(xí)可以使損失函數(shù)的值逐漸減小這一事實(shí)。不過這個損失函數(shù)的值,嚴(yán)格地講是“對訓(xùn)練數(shù)據(jù)的某個mini-batch的損失函數(shù)”的值。
????神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)中,必須確認(rèn)是否能夠正確識別訓(xùn)練數(shù)據(jù)以外的其他數(shù)據(jù),即確認(rèn)是否會發(fā)生過擬合。過擬合是指,雖然訓(xùn)練數(shù)據(jù)中的數(shù)字圖像能被正確辨別,但是不在訓(xùn)練數(shù)據(jù)中的數(shù)字圖像卻無法被識別的現(xiàn)象。
????一般在進(jìn)行學(xué)習(xí)的過程中,會定期地對訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù)記錄識別精度。這里,每經(jīng)過一個 epoch,我們都會記錄下訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù)的識別精度。epoch是一個單位。一個 epoch表示學(xué)習(xí)中所有訓(xùn)練數(shù)據(jù)均被使用過一次時的更新次數(shù)。
????將得到的結(jié)果制成圖表如下。

????如圖所示,隨著epoch的前進(jìn)(學(xué)習(xí)的進(jìn)行),我們發(fā)現(xiàn)使用訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù)評價(jià)的識別精度都提高了,并且,這兩個識別精度基本上沒有差異(兩條線基本重疊在一起)。因此,可以說這次的學(xué)習(xí)中沒有發(fā)生過擬合的現(xiàn)象。
詳細(xì)代碼:
import sys, os
sys.path.append(os.pardir) ?# 為了導(dǎo)入父目錄的文件而進(jìn)行的設(shè)定
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
# 讀入數(shù)據(jù)
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
#超參數(shù)
iters_num = 10000 ?# 適當(dāng)設(shè)定循環(huán)的次數(shù)
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
train_loss_list = []
train_acc_list = []
test_acc_list = []
#平均每個epoch的重復(fù)次數(shù) 一個epoch表示學(xué)習(xí)中所有訓(xùn)練數(shù)據(jù)均被使用過一次時的更新次數(shù)
iter_per_epoch = max(train_size / batch_size, 1)
for i in range(iters_num):
? ? #獲取mini-batch
? ? batch_mask = np.random.choice(train_size, batch_size)
? ? x_batch = x_train[batch_mask]
? ? t_batch = t_train[batch_mask]
? ? # 計(jì)算梯度
? ? #grad = network.numerical_gradient(x_batch, t_batch)
? ? grad = network.gradient(x_batch, t_batch) #高速版
? ? # 更新參數(shù)
? ? for key in ('W1', 'b1', 'W2', 'b2'):
? ? ? ? network.params[key] -= learning_rate * grad[key]
? ? loss = network.loss(x_batch, t_batch)
? ? train_loss_list.append(loss)
? ? #每一個epoch的識別精度
? ? if i % iter_per_epoch == 0:
? ? ? ? train_acc = network.accuracy(x_train, t_train)
? ? ? ? test_acc = network.accuracy(x_test, t_test)
? ? ? ? train_acc_list.append(train_acc)
? ? ? ? test_acc_list.append(test_acc)
? ? ? ? print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))
# 繪制圖形
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()