深度学习与计算机视觉系列(7)_神经网络数据预处理,正则化与损失函数
作者:寒小陽?&&?龍心塵?
時間:2016年1月。?
出處:?
http://blog.csdn.net/han_xiaoyang/article/details/50451460?
http://blog.csdn.net/longxinchen_ml/article/details/50451493?
聲明:版權所有,轉載請聯系作者并注明出處
1. 引言
上一節我們講完了各種激勵函數的優缺點和選擇,以及網絡的大小以及正則化對神經網絡的影響。這一節我們講一講輸入數據以及損失函數設定的一些事情。
2. 數據與網絡的設定
前一節提到前向計算涉及到的組件(主要是神經元)設定。神經網絡結構和參數設定完畢之后,我們就得到得分函數/score function(忘記的同學們可以翻看一下之前的博文),總體說來,一個完整的神經網絡就是在不斷地進行線性映射(權重和input的內積)和非線性映射(部分激勵函數作用)的過程。這一節我們會展開來講講數據預處理,權重初始化和損失函數的事情。
2.1 數據預處理
在卷積神經網處理圖像問題的時候,圖像數據有3種常見的預處理可能會用到,如下。我們假定數據表示成矩陣為X,其中我們假定X是[N*D]維矩陣(N是樣本數據量,D為單張圖片的數據向量長度)。
- 去均值,這是最常見的圖片數據預處理,簡單說來,它做的事情就是,對待訓練的每一張圖片的特征,都減去全部訓練集圖片的特征均值,這么做的直觀意義就是,我們把輸入數據各個維度的數據都中心化到0了。使用python的numpy工具包,這一步可以用X -= np.mean(X, axis = 0)輕松實現。當然,其實這里也有不同的做法:簡單一點,我們可以直接求出所有像素的均值,然后每個像素點都減掉這個相同的值;稍微優化一下,我們在RGB三個顏色通道分別做這件事。
- 歸一化,歸一化的直觀理解含義是,我們做一些工作去保證所有的維度上數據都在一個變化幅度上。通常我們有兩種方法來實現歸一化。一個是在數據都去均值之后,每個維度上的數據都除以這個維度上數據的標準差(X /= np.std(X, axis = 0))。另外一種方式是我們除以數據絕對值最大值,以保證所有的數據歸一化后都在-1到1之間。多說一句,其實在任何你覺得各維度幅度變化非常大的數據集上,你都可以考慮歸一化處理。不過對于圖像而言,其實這一步反倒可做可不做,因為大家都知道,像素的值變化區間都在[0,255]之間,所以其實圖像輸入數據天生幅度就是一致的。
上述兩個操作對于數據的作用,畫成示意圖,如下:?
- PCA和白化/whitening,這是另外一種形式的數據預處理。在經過去均值操作之后,我們可以計算數據的協方差矩陣,從而可以知道數據各個維度之間的相關性,簡單示例代碼如下:
得到的結果矩陣中元素(i,j)表示原始數據中,第i維和第j維直接愛你的相關性。有意思的是,其實協方差矩陣的對角線包含了每個維度的變化幅度。另外,我們都知道協方差矩陣是對稱的,我們可以在其上做矩陣奇異值分解(SVD factorization):
<code class="language-python hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">U,S,V = np.linalg.svd(cov)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>其中U為特征向量,我們如果相對原始數據(去均值之后)做去相關操作,只需要進行如下運算:
<code class="language-python hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">Xrot = np.dot(X, U)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>這么理解一下可能更好,U是一組正交基向量。所以我們可以看做把原始數據X投射到這組維度保持不變的正交基底上,從而也就完成了對原始數據的去相關。如果去相關之后你再求一下Xrot的協方差矩陣,你會發現這時候的協方差矩陣是一個對角矩陣了。而numpy中的np.linalg.svd更好的一個特性是,它返回的U是對特征值排序過的,這也就意味著,我們可以用它進行降維操作。我們可以只取top的一些特征向量,然后做和原始數據做矩陣乘法,這個時候既降維減少了計算量,同時又保存下了絕大多數的原始數據信息,這就是所謂的主成分分析/PCA:
<code class="language-python hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">Xrot_reduced = np.dot(X, U[:,:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">100</span>])</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>這個操作之后,我們把原始數據集矩陣從[N*D]降維到[N*100],保存了前100個能包含絕大多數數據信息的維度。實際應用中,你在PCA降維之后的數據集上,做各種機器學習的訓練,在節省空間和時間的前提下,依舊能有很好的訓練準確度。
最后我們再提一下whitening操作。所謂whitening,就是把各個特征軸上的數據除以特征向量,從而達到在每個特征軸上都歸一化幅度的結果。whitening變換的幾何意義和理解是,如果輸入的數據是多變量高斯,那whitening之后的 數據是一個均值為0而不同方差的高斯矩陣。這一步簡單代碼實現如下:
<code class="language-python hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">#白化數據</span> Xwhite = Xrot / np.sqrt(S + <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1e-5</span>)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>提個醒:whitening操作會有嚴重化噪聲的可能。注意到我們在上述代碼中,分母的部分加入了一個很小的數1e-5,以防止出現除以0的情況。但是數據中的噪聲部分可能會因whitening操作而變大,因為這個操作的本質是把輸入的每個維度都拉到差不多的幅度,那么本不相關的有微弱幅度變化的噪聲維度,也被拉到了和其他維度同樣的幅度。當然,我們適當提高墳墓中的安全因子(1e-5)可以在一定程度上緩解這個問題。
下圖為原始數據到去相關到白化之后的數據分布示意圖:?
我們來看看真實數據集上的操作與得到的結果,也許能對這些過程有更清晰一些的認識。大家都還記得CIFAR-10圖像數據集吧。訓練集大小為50000*3072,也就是說,每張圖片都被展成一個3072維度的列向量了。然后我們對原始50000*3072數據矩陣做SVD分解,進行上述一些操作,再可視化一下,得到的結果示意圖如下:
我們稍加解釋一下,最左邊是49張原始圖片;左起第2幅圖是最3072個特征向量中最top的144個,這144個特征向量包含了絕大多數數據變量信息,而其實它們代表的是圖片中低頻的信息;左起第3幅圖表示PCA降維操作之后的49張圖片,使用上面求得的144個特征向量。我們可以觀察到圖片好像被蒙上了一層東西一樣,模糊化了,這也就表明了我們的top144個特征向量捕捉到的都是圖像的低頻信息,不過我們發現圖像的絕大多數信息確實被保留下來了;最右圖是whitening的144個數通過乘以U.transpose()[:144,:]還原回圖片的樣子,有趣的是,我們發現,現在低頻信息基本都被濾掉了,剩下一些高頻信息被放大呈現。
實際工程中,因為這個部分講到數據預處理,我們就把基本的幾種數據預處理都講了一遍,但實際卷積神經網中,我們并沒有用到去相關和whitening操作。當然,去均值是非常非常重要的,而每個像素維度的歸一化也是常用的操作。
特別說明,需要特別說明的一點是,上述的預處理操作,一定都是在訓練集上先預算的,然后應用在交叉驗證/測試集上的。舉個例子,有些同學會先把所有的圖片放一起,求均值,然后減掉均值,再把這份數據分作訓練集和測試集,這是不對的親!!!
2.2 權重初始化
我們之前已經看過一個完整的神經網絡,是怎么樣通過神經元和連接搭建起來的,以及如何對數據做預處理。在訓練神經網絡之前,我們還有一個任務要做,那就是初始化參數。
錯誤的想法:全部初始化為0,有些同學說,那既然要訓練和收斂嘛,初始值就隨便設定,簡單一點就全設為0好了。親,這樣是絕對不行的!!!為啥呢?我們在神經網絡訓練完成之前,是不可能預知神經網絡最后的權重具體結果的,但是根據我們歸一化后的數據,我們可以假定,大概有半數左右的權重是正數,而另外的半數是負數。但設定全部初始權重都為0的結果是,網絡中每個神經元都計算出一樣的結果,然后在反向傳播中有一樣的梯度結果,因此迭代之后的變化情況也都一樣,這意味著這個神經網絡的權重沒有辦法差異化,也就沒有辦法學習到東西。
很小的隨機數,其實我們依舊希望初始的權重是較小的數,趨于0,但是就像我們剛剛討論過的一樣,不要真的是0。綜合上述想法,在實際場景中,我們通常會把初始權重設定為非常小的數字,然后正負盡量一半一半。這樣,初始的時候權重都是不一樣的很小隨機數,然后迭代過程中不會再出現迭代一致的情況。舉個例子,我們可能可以這樣初始化一個權重矩陣W=0.0001*np.random.randn(D,H)。這個初始化的過程,使得每個神經元的權重向量初始化為多維高斯中的隨機采樣向量,所以神經元的初始權重值指向空間中的隨機方向。
特別說明:其實不一定更小的初始值會比大值有更好的效果。我們這么想,一個有著非常小的權重的神經網絡在后向傳播過程中,回傳的梯度也是非常小的。這樣回傳的”信號”流會相對也較弱,對于層數非常多的深度神經網絡,這也是一個問題,回傳到最前的迭代梯度已經很小了。
方差歸一化,上面提到的建議有一個小問題,對于隨機初始化的神經元參數下的輸出,其分布的方差隨著輸入的數量,會增長。我們實際上可以通過除以總輸入數目的平方根,歸一化每個神經元的輸出方差到1。也就是說,我們傾向于初始化神經元的權重向量為w = np.random.randn(n) / sqrt(n),其中n為輸入數。
我們從數學的角度,簡單解釋一下,為什么上述操作可以歸一化方差??紤]在激勵函數之前的權重w與輸入x的內積s=∑niwixi部分,我們計算一下s的方差:
Var(s)=Var(∑inwixi)=∑inVar(wixi)=∑in[E(wi)]2Var(xi)+E[(xi)]2Var(wi)+Var(xi)Var(wi)=∑inVar(xi)Var(wi)=(nVar(w))Var(x)
注意,這個推導的前2步用到了方差的性質。第3步我們假定輸入均值為0,因此E[xi]=E[wi]=0。不過這是我們的一個假設,實際情況下并不一定是這樣的,比如ReLU單元的均值就是正的。最后一步我們假定wi,xi是獨立分布。我們想讓s的方差和輸入x的方差一致,因此我們想讓w的方差取值為1/n,又因為我們有公式Var(aX)=a2Var(X),所以a應該取值為a=1/n???√,numpy里的實現為w = np.random.randn(n) / sqrt(n)。
對于初始化權重還有一些類似的研究和建議,比如說Glorot在論文Understanding the difficulty of training deep feedforward neural networks就推薦使用能滿足Var(w)=2/(nin+nout)的權重初始化。其中nin,nout是前一層和后一層的神經元個數。而另外一篇比較新的論文Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification,則指出尤其對于ReLU神經元,我們初始化方差應該為2.0/n,也就是w = np.random.randn(n) * sqrt(2.0/n),目前的神經網絡中使用了很多ReLU單元,因此這個設定其實在實際應用中使用最多。
偏移量/bias初始化:相對而言,bias項初始化就簡單一些。我們很多時候簡單起見,直接就把它們都設為0.在ReLU單元中,有些同學會使用很小的數字(比如0.01)來代替0作為所有bias項的初始值,他們解釋說這樣也能保證ReLU單元一開始就是被激活的,因此反向傳播過程中不會終止掉回傳的梯度。不過似乎實際的實驗過程中,這個優化并不是每次都能起到作用的,因此很多時候我們還是直接把bias項都初始化為0。
2.3 正則化
在前一節里我們說了我們要通過正則化來控制神經網絡,使得它不那么容易過擬合。有幾種正則化的類型供選擇:
-
L2正則化,這個我們之前就提到過,非常常見。實現起來也很簡單,我們在損失函數里,加入對每個參數的懲罰度。也就是說,對于每個權重w,我們在損失函數里加入一項12λw2,其中λ是我們可調整的正則化強度。順便說一句,這里在前面加上1/2的原因是,求導/梯度的時候,剛好變成λw而不是2λw。L2正則化理解起來也很簡單,它對于特別大的權重有很高的懲罰度,以求讓權重的分配均勻一些,而不是集中在某一小部分的維度上。我們再想想,加入L2正則化項,其實意味著,在梯度下降參數更新的時候,每個權重以W += -lambda*W的程度被拉向0。
-
L1正則化,這也是一種很常見的正則化形式。在L1正則化中,我們對于每個權重w的懲罰項為λ|w|。有時候,你甚至可以看到大神們混著L1和L2正則化用,也就是說加入懲罰項λ1∣w∣+λ2w2,L1正則化有其獨特的特性,它會讓模型訓練過程中,權重特征向量逐漸地稀疏化,這意味著到最后,我們只留下了對結果影響最大的一部分權重,而其他不相關的輸入(例如『噪聲』)因為得不到權重被抑制。所以通常L2正則化后的特征向量是一組很分散的小值,而L1正則化只留下影響較大的權重。在實際應用中,如果你不是特別要求只保留部分特征,那么L2正則化通常能得到比L1正則化更好的效果
-
最大范數約束,另外一種正則化叫做最大范數約束,它直接限制了一個上行的權重邊界,然后約束每個神經元上的權重都要滿足這個約束。實際應用中是這樣實現的,我們不添加任何的懲罰項,就按照正常的損失函數計算,只不過在得到每個神經元的權重向量w??之后約束它滿足∥w??∥2<c。有些人提到這種正則化方式幫助他們提高最后的模型效果。另外,這種正則化方式倒是有一點很吸引人:在神經網絡訓練學習率設定很高的時候,它也能很好地約束住權重更新變化,不至于直接掛掉。
-
Dropout,親,這個是我們實際神經網絡訓練中,用的非常多的一種正則化手段,同時也相當有效。Srivastava等人的論文Dropout: A Simple Way to Prevent Neural Networks from Overfitting最早提到用dropout這種方式作為正則化手段。一句話概括它,就是:在訓練過程中,我們對每個神經元,都以概率p保持它是激活狀態,1-p的概率直接關閉它。
下圖是一個3層的神經網絡的dropout示意圖:?
?
可以這么理解,在訓練過程中呢,我們對全體神經元,以概率p做了一個采樣,只有選出的神經元要進行參數更新。所以最后就從左圖的全連接到右圖的Dropout過后神經元連接圖了。需要多說一句的是,在測試階段,我們不用dropout,而是直接從概率的角度,對權重配以一個概率值。
簡單的Dropout代碼如下(這是簡易實現版本,但是不建議使用,我們會分析為啥,并在之后給出優化版):
<code class="language-python hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"> p = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.5</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 設定dropout的概率,也就是保持一個神經元激活狀態的概率</span><span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">train_step</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(X)</span>:</span><span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">""" X contains the data """</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 3層神經網絡前向計算</span>H1 = np.maximum(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, np.dot(W1, X) + b1)U1 = np.random.rand(*H1.shape) < p <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 第一次Dropout</span>H1 *= U1 <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># drop!</span>H2 = np.maximum(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, np.dot(W2, H1) + b2)U2 = np.random.rand(*H2.shape) < p <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 第二次Dropout</span>H2 *= U2 <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># drop!</span>out = np.dot(W3, H2) + b3<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 反向傳播: 計算梯度... (這里省略)</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 參數更新... (這里省略)</span><span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">predict</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(X)</span>:</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 加上Dropout之后的前向計算</span>H1 = np.maximum(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, np.dot(W1, X) + b1) * p H2 = np.maximum(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, np.dot(W2, H1) + b2) * p out = np.dot(W3, H2) + b3</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li></ul>上述代碼中,在train_step函數中,我們做了2次Dropout。我們甚至可以在輸入層做一次dropout。反向傳播過程保持不變,除了我們要考慮一下U1,U2
很重要的一點是,大家仔細看predict函數部分,我們不再dropout了,而是對于每個隱層的輸出,都用概率p做了一個幅度變換??梢詮臄祵W期望的角度去理解這個做法,我們考慮一個神經元的輸出為x(沒有dropout的情況下),它的輸出的數學期望為px+(1?p)0,那我們在測試階段,如果直接把每個輸出x都做變換x→px,其實是可以保持一樣的數學期望的。
上述代碼的寫法有一些缺陷,我們必須在測試階段對每個神經的輸出都以p的概率輸出。考慮到實際應用中,測試階段對于時間的要求非常高,我們可以考慮反著來,代碼實現的時候用inverted dropout,即在訓練階段就做相反的幅度變換/scaling(除以p),這樣在測試階段,我們可以直接把權重拿來使用,而不用附加很多步用p做scaling的過程。inverted dropout的示例代碼如下:
<code class="language-python hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">""" Inverted Dropout的版本,把本該花在測試階段的時間,轉移到訓練階段,從而提高testing部分的速度 """</span>p = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.5</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># dropout的概率,也就是保持一個神經元激活狀態的概率</span><span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">train_step</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(X)</span>:</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># f3層神經網絡前向計算</span>H1 = np.maximum(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, np.dot(W1, X) + b1)U1 = (np.random.rand(*H1.shape) < p) / p <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 注意到這個dropout中我們除以p,做了一個inverted dropout</span>H1 *= U1 <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># drop!</span>H2 = np.maximum(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, np.dot(W2, H1) + b2)U2 = (np.random.rand(*H2.shape) < p) / p <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 這個dropout中我們除以p,做了一個inverted dropout</span>H2 *= U2 <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># drop!</span>out = np.dot(W3, H2) + b3<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 反向傳播: 計算梯度... (這里省略)</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 參數更新... (這里省略)</span><span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">predict</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(X)</span>:</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 直接前向計算,無需再乘以p</span>H1 = np.maximum(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, np.dot(W1, X) + b1) H2 = np.maximum(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, np.dot(W2, H1) + b2)out = np.dot(W3, H2) + b3</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li></ul> 對于dropout這個部分如果你有更深的興趣,歡迎閱讀以下文獻:?
* 2014 Srivastava 的論文Dropout paper?
*?Dropout Training as Adaptive Regularization
- bias項的正則化,其實我們在之前的博客中提到過,我們大部分時候并不對偏移量項做正則化,因為它們也沒有和數據直接有乘法等交互,也就自然不會影響到最后結果中某個數據維度的作用。不過如果你愿意對它做正則化,倒也不會影響最后結果,畢竟總共有那么多權重項,才那么些bias項,所以一般也不會影響結果。
實際應用中:我們最常見到的是,在全部的交叉驗證集上使用L2正則化,同時我們在每一層之后用dropout,很常見的dropout概率為p=0.5,你也可以通過交叉驗證去調整這個值。
2.4 損失函數
剛才討論了數據預處理、權重初始化與正則化相關的問題?,F在我們回到訓練需要的關鍵之一:損失函數。對于這么復雜的神經網絡,我們也得有一個評估準則去評估預測值和真實結果之間的吻合度,也就是損失函數。神經網絡里的損失函數,實際上是計算出了每個樣本上的loss,再求平均之后的一個形式,即L=1N∑iLi,其中N是訓練樣本數。
2.4.1 分類問題
- 分類問題是到目前為止我們一直在討論的。我們假定一個數據集中每個樣本都有唯一一個正確的標簽/類別。我們之前提到過有兩種損失函數可以使用,其一是SVM的hinge loss:
Li=∑j≠yimax(0,fj?fyi+1)
另外一個是Softmax分類器中用到的互熵損失:
Li=?log??efyi∑jefj??
-
問題:特別多的類別數。當類別標簽特別特別多的時候(比如ImageNet包含22000個類別),層次化的Softmax,它將類別標簽建成了一棵樹,這樣任何一個類別,其實就對應tree的一條路徑,然后我們在每個樹的結點上都訓練一個Softmax以區分是左分支還是右分支。
-
屬性分類,上述的兩種損失函數都假定,對于每個樣本,我們只有一個正確的答案yi。但是在有些場景下,yi是一個二值的向量,每個元素都代表有沒有某個屬性,這時候我們怎么辦呢?舉個例子說,Instagram上的圖片可以看作一大堆hashtag里的一個tag子集,所有一張圖片可以有多個tag。對于這種情況,大家可能會想到一個最簡單的處理方法,就是對每個屬性值都建一個二分類的分類器。比如,對應某個類別的二分類器可能有如下形式的損失函數:
Li=∑jmax(0,1?yijfj)
其中的求和是針對有所的類別j,而yij是1或者-1(取決于第i個樣本是否有第j個屬性的標簽),打分向量fj在類別/標簽被預測到的情況下為正,其他情況為負。注意到如果正樣本有比+1小的得分,或者負樣本有比-1大的得分,那么損失/loss就一直在累積。
另外一個也許有效的解決辦法是,我們可以對每個屬性,都單獨訓練一個邏輯回歸分類器,一個二分類的邏輯回歸分類器只有0,1兩個類別,屬于1的概率為:
P(y=1∣x;w,b)=11+e?(wTx+b)=σ(wTx+b)
又因為0,1兩類的概率和為1,所以歸屬于類別0的概率為P(y=0∣x;w,b)=1?P(y=1∣x;w,b)。一個樣本在σ(wTx+b)>0.5的情況下被判定為1,對應sigmoid函數化簡一下,對應的是得分wTx+b>0。這時候的損失函數可以定義為最大化似然概率的形式,也就是:
Li=∑jyijlog(σ(fj))+(1?yij)log(1?σ(fj))
其中標簽yij為1(正樣本)或者0(負樣本),而δ是sigmoid函數。
2.4.2 回歸問題
回歸是另外一類機器學習問題,主要用于預測連續值屬性,比如房子的價格或者圖像中某些東西的長度等。對于回歸問題,我們一般計算預測值和實際值之間的差值,然后再求L2范數或者L1范數用于衡量。其中對一個樣本(一張圖片)計算的L2范數損失為:
Li=∥f?yi∥22
而L1范數損失函數是如下的形式:?
注意:
- 回歸問題中用到的L2范數損失,比分類問題中的Softmax分類器用到的損失函數,更難優化。直觀想一想這個問題,一個神經網絡最后輸出離散的判定類別,比訓練它去輸出一個個和樣本結果對應的連續值,要簡單多了。
- 另外一個,前面的博文中提到過,其實Softmax這種分類器,對于輸出的打分結果具體值是不怎么在乎的,它只在乎各個類別之間的打分幅度有沒有差很多(比如二分類兩個類別的得分是1和9,與0.1和0.9)。
- 再一個,L2范數損失健壯性更差一些,異常點和噪聲都可能改變損失函數的幅度,而帶來大的梯度偏差。
- 一般情況下,對于回歸問題,我們都會首先考慮,這個問題能否轉化成對應的分類問題,比如說我們把輸出值劃分成不同的區域(切成一些桶)。舉個例子,如果我們要預測一個產品的預測打分,我們可以考慮把得分結果分成1-5顆星,而轉化成一個分類問題。
- 如果你覺得問題確實沒辦法轉化成分類問題,那要小心使用L2范數損失:舉個例子,在神經網絡中,在L2損失函數之前使用dropout是不合適的。
如果我們遇到回歸問題,首先要想想,是否完全沒有可能把結果離散化之后,把這個問題轉化成一個分類問題。
3. 總結
總結一下:
- 在很多神經網絡的問題中,我們都建議對數據特征做預處理,去均值,然后歸一化到[-1,1]之間。
- 從一個標準差為2/n???√的高斯分布中初始化權重,其中n為輸入的個數。
- 使用L2正則化(或者最大范數約束)和dropout來減少神經網絡的過擬合。
- 對于分類問題,我們最常見的損失函數依舊是SVM hinge loss和Softmax互熵損失。
總結
以上是生活随笔為你收集整理的深度学习与计算机视觉系列(7)_神经网络数据预处理,正则化与损失函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深度学习与计算机视觉系列(2)_图像分类
- 下一篇: 手把手入门神经网络系列(2)_74行代码