YJango的前饋神經網路--代碼LV3

介紹

前兩節所描述的YJango的前饋神經網路--代碼LV1和YJango的前饋神經網路--代碼LV2是都是為了分析方便而選擇的淺層任務,深層學習對其並不具備什麼優勢,其他機器學習演算法恐怕要比深層學習做的更好。

真正可以發揮深層學習的是具有特定結構的任務。

這一節有兩個目的:

  • 換一個高度非線性和複雜的任務來再次體會一下深層學習的功能
  • 熟悉深層學習訓練的完整流程。

代碼演示LV3用於構建網路的代碼和代碼演示LV2的完全相同相同,但參數等有所改變。全部代碼在github上。所用到的數據輸入輸出數據分別是 X.npy 和 Y.npy。

說明:上圖中打條的內容表示本次網路所用到的(不做說明),帶有數字的內容會被逐一講解。

重要:看下面內容之前,請先回顧上一章深層學習為何要「Deep」(上)中的基本流程圖。

任務描述:

  • 目標:用聲音來預測口腔移動。
  • 維度:兩者都是實數域,輸入是39維,輸出是24維: input in R^{39}label in R^{24}
  • 任務類型:因為預測的數值全部都是連續的實數,所以是回歸(regression)任務。
  • 樣本數:39473
  • 輸出層激活函數:linear(無)
  • 損失函數:預測數值和真實數值的差異可用均方差(mean square error:MSE)來表示。
    • 表達式: L(f(x_i),a_i)= dfrac{1}{24} sum^{24}_{j=1}(f(x_{i})_j-a_{ij})^2
    • 其中, j 表示維度的角標,由於任務是24維,這裡直接用24表示, i 表示不同樣本的角標。 (x_i) 表示經過神經網路計算後的預測值。
  • 初始預覽:下面是未訓練前真實值與預測值在四個不同維度的對比圖。目的就是要給網路很多組(輸入,輸出)數據來更新網路權重,使網路輸出儘可能符合真實值的預測。完美情況就是藍線(真實值)和綠線(預測值)重合。下圖中,橫軸是時間,縱軸是數值。

  • 效果預覽:下圖是經過若干次訓練後的預測值變化圖。

1. 預處理——平緩變體

  • 目的:機器學習的最大困難之一就在於變體。神經網路解決該問題可通過增加隱藏層節點來增強模型擬合變體的能力,然而還有一種相對方式是減弱輸入和輸出空間的變體。預處理就是該種可以通過縮小輸入輸出數據的差異性,使得建模變得相對簡單的方式之一。
  • 效果:下圖是將每個樣本都減去平均值除以標準差後的變化(其中上圖是變化後,下圖是變化前)。可在ploty上查看可交互圖Graph Maker Plotly Online

:點擊original或standardization會只顯示一個軌跡線,可以看到兩條軌跡線的形狀相同,但值域不同(若顯示有問題,請點下面選項欄的autoscale圖標)

  • 代碼

def Standardize(seq): #subtract mean centerized=seq-np.mean(seq, axis = 0) #divide standard deviation normalized=centerized/np.std(centerized, axis = 0) return normalized

  • 解釋:除此之外還有其他的預處理方式。比如之前的激活函數大多用tanh,需要將輸出值y限制到[-1,1]的值域中。但該節注重的預處理是消除各個樣本的差異性,其中去均值和模值的方法最簡單有效。

:該方式的僅僅是縮小了尺度,並不會把值域限制到固定的範圍內。

2. 預處理——窗口化

  • 目的:如果用每個時刻的輸入直接預測對應時刻的輸出,由於判斷依據較少,結果會不理想。就好比你畫我猜,開始的幾筆畫出的信息在觀察者的腦中會產生各種聯想,這些聯想所形成的集合是無比巨大的,觀察者難以確定究竟哪一個才正確。但隨著畫者展開更多內容。聯想範圍會逐漸被縮減。用上下文窗(context window)就有這種功效,它提供了更多的判斷依據。
  • 效果圖:將相鄰的時序向量並接在一起形成一個「大」向量作為輸入,而輸出一般選擇中間那一個向量對應的label,為得是有上文和下文的共同限制。這裡的每個向量也叫作幀(frame)。而沒有上下文的部分可由0來填補(zero padding),填零的部分會失去該維度的信息,但並不會對其他部分的計算產生誤差。下圖中的上半部分是用上下文窗之前的序列。而下半部分是用size為3(3個frames)的window形成的新輸入序列。t的維度是39維的話,下半部分的序列就是3*39維度,總個數不變。

  • 代碼

def Makewindows(indata,window_size=41): outdata=[] mid=int(window_size/2) indata=np.vstack((np.zeros((mid,indata.shape[1])),indata,np.zeros((mid,indata.shape[1])))) # 前後補零 for i in range(indata.shape[0]-window_size+1): outdata.append(np.hstack(indata[i:i+window_size])) return np.array(outdata)

  • 解釋:窗口化並不是一種非常好的處理時序信號的方式,在隨後的遞歸神經網路中會提到它的弊端以及遞歸神經網路的解決方式。

3. 權重初始化

  • 目的:權重初始化決定了網路從什麼位置開始訓練。但請不要認為「網路從任何位置開始都可以訓練到相同結果,僅是耗時差別」。良好的起始位置不僅可以減少訓練耗時,也可以使模型的訓練更加穩定,並且可以避開很多訓練上的問題
  • 效果圖:這裡展示一下初始化對於「dying ReLU」問題的緩解。由於ReLU的梯度簡單,可以避開像sigmoid/tanh這類損失函數所造成的嚴重梯度消失(gradient vanishing),ReLU基本上成為深層學習中激活函數的標配。但在訓練過程中,ReLU也會使很多節點再也無法被激活(「dying ReLU」)。下圖展示了該問題的嚴重。

:梯度消失簡單說就是距離輸出越遠的層的梯度會越接近0,當用鏈式規則計算梯度時,就會使整體的梯度都拉向0,造成更新的權重並不是想要的正確數值。

如果用代碼演示LV2中的隨機權重初始化的話,是無法訓練該任務的。因為隨著訓練,原本隨機初始的預測值全部都變成了0,而後的任何數據都無法再對模型的權重更新起到作用。解決的方式之一就是合理的權重初始化

  • 代碼:只需要在LV2的權重初始化的基礎上將所有值都除以輸出維度的開方,使權重的初始值域落在 [-1/sqrt n,1/sqrt n] )內,其中 n 是該層的輸出維度。

def weight_init(self,shape): # shape : list [in_dim, out_dim] # 在這裡更改初始化方法 # 方式1:下面的權重初始化dropout率40%的網路,可以使用帶有6個隱藏層的神經網路。 # 若過深,則使用dropout會難以擬合。 #initial = tf.truncated_normal(shape, stddev=0.1)/ np.sqrt(shape[1]) # 方式2:下面的權重初始化dropout率40%的網路,可以擴展到12個隱藏層以上(通常不會用那麼多) initial = tf.random_uniform(shape,minval=-np.sqrt(5)*np.sqrt(1.0/shape[0]), maxval=np.sqrt(5)*np.sqrt(1.0/shape[0])) return tf.Variable(initial)

  • 解釋:除此之外的解決「dying ReLU」的方式還有「Leaky ReLU」。

過擬合問題

  • 比喻:學習要達到的效果可以用高考時的做題來比喻:我們希望通過「做題、對照答案」來學會如何解該問題所有類型的題目,最終希望可在高考時解出問題。

「練習題」就是機器學習中的數據(data),同時具有問題(observation)和答案(label)兩種數據。不斷「做題、對照答案」的過程就是學習(training)。而高考的題就是只有問題(observation),需要靠以前的學習的知識(function)來解出答案,從而考察(test)我們是否「真」的學會了該知識(function)。但是不同題有不同的思路(variation),一直做同類練習題會使自己過分糾結於該類題的細節規律(overfitting),再次遇到其他類型問題時卻無法解出。

機器學習也存在相同的現象:模型會過分學習訓練數據(training set)中的規律,而在預測未見過的數據(testing set)時表現不佳。這種現象叫做過擬合(overfitting),而我們真正希望的是模型可以通過有限數據的學習涵蓋所有變體,這種能力叫做普遍化(generalization)。深層學習相比淺層學習的優勢就是在於其普遍化能力。然而普遍化是一個永遠的課題,對人腦的學習來說同樣如此。

  • 舉例:為了感受普遍化是一件多麼難的問題,請考慮這樣一個例子。這裡有一組數據。括弧前一個數表示x,後一個數表示對應的y。(-1,1),(2,0),僅有兩個數據時可以觀察出什麼規律?

單靠觀察,該規律可以是正數就輸出0,負數就輸出1。然而也可以是奇數就輸出1,偶數就輸出0。或者兩者同時滿足。事實上運用各種數學運算,可以有無數種function能夠擬合上面兩個數據,所有機器學習演算法都可以做到,然而機器怎麼知道我們想要哪種function?雖然可以通過更多的數據來確定想要的function。但是數據是無限的,如果我們能夠獲得所有情況的數據也就沒有必要學習了。

  • 說明: 這裡引用Bengio大神的一句話。

If you dont assume much about the world, its actually impossible to learn about it.

如果將所有符合訓練集規律的functions組成一個集合叫 mathcal{F} ,那麼想要用更少的數據來學習具有普遍性的function,就需要加入先驗知識來縮小 mathcal{F} 。不過,縮小 mathcal{F} 雖可排除掉一部分的functions,加快搜索,但代價也是無法學習被排除掉了的functions,這也就是No free lunch theorem所描述的內容。深層學習對於數據的assume是並行與迭代,進而排除掉了很多functions。而卷積神經網路、遞歸神經網路等變體就是優先搜索特定的functions,從而排除掉另一部分的functions。換句話說:

神經網路的變體實際上是對函數優先搜索空間 mathcal{F} 的調整。

防止過擬合也是對的調整。但「過擬合」是對該問題的一個較為偷懶的描述。因為我們想要知道的是造成過擬合的原因,從而加以調整。

4. 防過擬合——L2 regularization

  • 作用:L2對於神經網路有兩個作用。其中一個作用在之前的章節中深層神經網路——方式3:加入隱藏層中已描述。具有強制讓所有節點均攤任務的作用。

而另一個作用舉例來說:當有兩個節點的網路需要輸出3時,可以讓其中一個是300,另一個是-297;也可以讓其中一個是1.5,另一個是1.5,L2會鼓勵網路選擇後者。

:我們是利用L2的特點對網路的權重在訓練中進行調整,僅僅是緩解作用,並不能完全解決問題。其次附加額外loss的做法都會多少對最終結果產生負面影響。比如過高的L2強度會壓制目標loss。

一般做法:我們會選擇稍微過擬合的網路,再增加防止過擬合的方法加以訓練,其目的是:用超過學習訓練集所需的節點來建模,從而涵蓋測試集中的變體。

  • 效果圖:下圖是用playground訓練的效果演示。上半部分是沒用L2,下半部分用了L2。

1.先觀察每層的連線(權重)的深淺,越淺絕對值越低。用L2的模型的權重十分均勻且數值很淺。

2.再看每個節點的方塊,沒用L2的網路就出現了300-297去獲得3的方式,而用了L2的則傾向於用更簡單的1.5+1.5去獲得3。

3.最後再看實際分類圖的差別。

下面這張圖是訓練聲音預測口腔移動任務50個epoch時,loss值隨訓練的變化曲線。x軸是訓練次數(iteration),y軸是loss值。紅線是測試集loss,藍線是訓練集loss。可以明顯看到兩條變化線的差別。測試集的loss最終停在了0.387上,而訓練集的loss近乎為0。

註:一次epoch是指將所有樣本數都訓練完。一次iteration是指更新一次權重。

而下面這張圖則是使用了L2後的效果。這是測試集的loss在50個epoch後,停在了0.368上。(忽略圖中的訓練時間,因為用的是不同的GPU)

5. 防過擬合——dropout

  • 作用:拿語音為例,我們最終獲得的語音信號並不純粹(pure),而是有很多帶有「額外規律」的雜訊(noise)摻雜其中,如錄音設備的噪音,說話人的規律,環境噪音的規律。

dropout論文對其效果的解釋是:dropout實際上是多個模型預測的平均值。但為什麼平均多個模型的預測值會提高表現?

個人理解:dropout可阻礙網路學習僅存在於訓練集中局部的「額外規律」。

    • 現象:目標規律(想學習的規律)基本在所有訓練樣本中都存在,但雜訊規律(由雜訊帶來的額外規律)僅存在於部分的樣本中,由於權重是隨機初始,並且訓練樣本也是隨機打亂順序後逐個送入網路進行訓練。當若干有相同雜訊規律的訓練樣本連續被送入網路訓練後,網路就會記住該規律。
    • 影響:神經網路一大特點在於它可以拆分因素,包括雜訊,並將他們分開建模。但上面的現象會影響神經網路對於因素的拆分。假如目標信號是10,而雜訊是2,獲得的實際語音數據是12,混合信號未必會被拆分成10+2的形式,有可能是9+3。本該被拆分成10的目標信號變成了9,以至於在測試集中表現不佳。
    • 緩解:但是這種現象有一個特點,就是雜訊規律相對目標規律而言較為薄弱,同時也並非在所有訓練樣本中都存在。dropout正是對這一特點展開的攻擊。dropout會隨機扔掉節點,讓已經學到的規律被遺忘掉。這樣即便是網路由於隨機打亂順序所訓練的幾個帶有雜訊規律的樣本,所形成的雜訊規律也會被遺忘掉。只有像目標規律穩固的規律才會得意保留。由於這種現象是隨機性造成的,所以平均各個模型的預測值後就會抵消掉額外規律對目標規律的干擾(比如此次拆分成9+3,下次拆分成11+1的形式。取平均值後9+11=10)。有一種篩子的作用。我想人類的遺忘也有類似作用。

dropout可阻礙網路學習僅存在於訓練集中局部的「額外規律」。

  • 效果圖:dropout的實現方法和特點在代碼演示LV2——訓練前-step 5中已描述。下圖是用了dropout(隨機扔掉30%節點)後的loss圖。dropout的實現非常簡單,但卻異常強大。test的loss最終落在了0.344。

從圖中還可以看出來dropout確實是阻礙網路學習訓練集中的規律,並且不僅僅是額外規律,連目標規律也被殃及(沒用dropout前,50個epoch後的訓練集的loss僅有0.01~0.02左右,用了以後連訓練集loss都變成了0.172)。但其實隨著訓練,訓練集的loss還會進一步下降一些。最終我們想要提升的是測試集的表現。

淺藍色的線是實際的loss。dropout會使其上下波動很大。可以感受到,若規律不是在所有樣本中都存在,則很難在這種dropout的遺忘下存留。

註:還有一種阻礙網路學習僅存在於訓練集中的「額外規律」的方法就是在訓練集中加入其他雜訊,從而屏蔽掉額外規律。 註:如果你的代碼是每一層之後都加入dropoout layer,那麼隨著層數的加深,阻礙網路學習規律的強度也會增加。

6. 隨機打亂

隨機打亂(shuffle)訓練數據送入網路更新的順序十分重要。如dropout中所描述的現象。若更新網路的順序始終不變,就會使網路受雜訊規律的干擾而拆分出錯誤的目標規律。若每個epoch對網路的作用也是幾乎相同的,當該epoch的規律學習完畢後,網路幾乎不再變化(如果看到自己訓練的網路效果不好,一定要記得檢查是否有shuffle)。正確的做法是:

每個epoch之後一定要將訓練數據隨機打亂順序。

7. Batch size

如梯度下降訓練法中基本流程圖-訓練網路所說,Stochastic Gradient Descent (SGD) 有「跳出」局部最小值(鞍點)的作用。純粹的SGD是每次只計算一個樣本的梯度來更新權重。實際應用中,一般會用10-512個樣本(batch)計算出他們的梯度平均值來更新權重。

大的Batch size

  • 優點:有節約訓練時間、收斂方向穩定的優點。
  • 缺點:普遍化的程度降低、容易陷入鞍點、沒有完全利用數據。

小的Batch size

  • 優點:普遍化的程度升高、利於逃脫鞍點、更全面的利用數據。
  • 缺點:訓練時間提升、收斂方向波動。

8. 停訓標準

很多情況下隨著模型的訓練,訓練集的loss會越來越好,然而測試集的loss反而變得糟糕。早停(early stopping)的思路就是在變糟之前停止訓練。比如當測試集的loss停止降低3次以後就可以讓網路自動停止。

在正常分set的時候並不會單一的分成訓練、測試集。而是分成train/test/validation 三部分。原因在於test往往是指從未見過的數據。而我們在調試網路時會從train set中另分一部分validation進行反饋和調試。

完整代碼

完整代碼可到github上查看,為節省篇幅,這裡不再粘貼。網路模型的FNN類和代碼演示LV2的完全一致,除了改變了權重初始化的值域。 值得說明的是數據的處理:

mfc=np.load(X.npy)art=np.load(Y.npy)x=[]y=[]for i in range(len(mfc)): x.append(Makewindows(Standardize(mfc[i])))# 輸入數據標準化並窗口化 y.append(Standardize(art[i]))# 輸出數據標準化vali_size=20 # 分多少百分比的數據作為驗證集totalsamples=len(np.vstack(x))X_train=np.vstack(x)[int(totalsamples/vali_size):].astype("float32")#記得要改變類型。Y_train=np.vstack(y)[int(totalsamples/vali_size):].astype("float32")X_test=np.vstack(x)[:int(totalsamples/vali_size)].astype("float32")Y_test=np.vstack(y)[:int(totalsamples/vali_size)].astype("float32")print(X_train.shape,Y_train.shape,X_test.shape,Y_test.shape)#查看樣本數和維度是否正確。# 實際顯示為:((37500, 1599), (37500, 24), (1973, 1599), (1973, 24))

下面的函數是一個簡單顯示預測效果的plot圖。僅此而已。

def plots(T,P,i, n=21,length=400): m=0 plt.figure(figsize=(20,16)) plt.subplot(411) plt.plot(T[m:m+length,7],--) plt.plot(P[m:m+length,7]) plt.subplot(412) plt.plot(T[m:m+length,8],--) plt.plot(P[m:m+length,8]) plt.subplot(413) plt.plot(T[m:m+length,15],--) plt.plot(P[m:m+length,15]) plt.subplot(414) plt.plot(T[m:m+length,16],--) plt.plot(P[m:m+length,16]) plt.legend([True,Predicted]) plt.savefig(epoch+str(i)+.png) plt.close()

實驗參數

:上述參數不是最優參數。為了節省時間而隨意設定的。

:上述代碼也是為了讓讀者有體會而刻意編寫的。如果熟悉流程,請放心用如Keras、tensorlayer這樣的庫包,可以幫助節省很多編寫和省去你自己測試和找bug的時間。

  • 網路結構圖

  • 訓練/驗證 loss:訓練了200個epoch後的loss圖,最後的驗證loss達到了0.315左右。這次的圖像並沒有那麼波動的原因是選擇了256的batch size。不過feedforward做時序信號預測也只能到達這種程度了。隨後會用相同任務和遞歸神經網路進行比較。


推薦閱讀:

反向傳播之我見
IBM 是如何訓練「沃森」人工智慧平台的?
第8彈:從零開始深度學習(Software篇) | 2017 CS231n
挖掘虎嗅網4萬篇文章,展現中國互聯網江湖
你覺得Alpha Go 對李世石會幾比幾贏?

TAG:深度学习DeepLearning | TensorFlow | 人工智能 |