歡迎來到實力至上主義的CS231n教室(二)

Hello,大家好,這裡是糖葫蘆喵喵~!

ようこそ実力至上主義のCS231n教室へ

已經進入了糖葫蘆販賣的季節了呢^ ^!北方的同學們已經享受上了暖氣,南方的同學們請抱好你們的1080Ti來取暖(雖然我們在本次作業中用不到GPU

那麼,第二篇CS231n 2017 Spring Assignment2 就要開始了~!

喵喵的代碼實現:Observerspy/CS231n,歡迎watch和star!

Observerspy/CS231ngithub.com圖標

視頻講解CS231n Assignment2:

Assignment 2-AI慕課學院www.mooc.ai圖標

Part 1 Fully-connected Neural Network

上一次的作業中我們實現了一個兩層的神經網路,這裡主要是對代碼進行模塊化,把每一層都抽象出來,分別實現每一層的前向反向部分。

1 Affine layer(線性層)

前向:wx+b,很簡單,np.dot()就行了

反向:db就是上游傳回來的值dout直接sum起來,dw是x和dout相乘(就是線性wx+b對w求導嘛),dx是w和dout相乘,請注意dw和dx的形狀,到底是誰和誰(或是誰的轉置)乘,一定要搞清楚!dw和w形狀一致,dx和x形狀一致,所以算一下形狀就很好確定啦!

2 ReLU

前向:ReLU不過就是和0比較,輸出大的那個,np.maximum()搞定

反向:反向只向後傳遞在前向時輸出大於0的位置(梯度為1)的dout,其他的由於小於0所以梯度為0

dx = (x > 0) * doutn

3 affine_relu

就是把上面兩個連在一起,已經為你實現好了,讀一下吧!

4 loss

loss上次我們也做過了,忘記了的可以回去看一看。

5 Two-layer network

結構:affine - relu - affine - softmax

基本的模塊上面已經做好了,下面組合在一起就好:

參數初始化===>前向 + Loss===>反向

注意w初始化np.random.normal()的三個參數的意義!尤其是形狀一定要把握好。

跑一下你的模型,10epoch可以在validation上得到50%左右的結果。

6 Multilayer network

結構:{affine - [batch norm] - relu - [dropout]} x (L - 1) - affine - softmax

無非就是套層循環,在循環時要想一想哪些是可以循環的,哪些是不行的(最後一層)

循環時一定要注意用的參數的名字,一定要注意i和你的參數名字的關係!

反向小技巧,逆序循環:

for i in range(self.num_layers - 1, 0, -1):n

跑一下模型,你可以調一調參數,20epoch就過擬合了

7 SGD+Momentum

官方文檔給了很多梯度更新的說明,很詳細喵喵就不多說了。

Momentum:mu是超參,多用0.9,交叉驗證可以用[0.5, 0.9, 0.95, 0.99]

# Momentum updatenv = mu * v - learning_rate * dx # integrate velocitynx += v # integrate position n

8 RMSProp and Adam

RMSProp:decay_rate是超參,多用[0.9, 0.99, 0.999],eps是平滑項,範圍為1e-4 ~ 1e-8

# RMSProp updatencache = decay_rate * cache + (1 - decay_rate) * dx**2nx += - learning_rate * dx / (np.sqrt(cache) + eps)n

Adam:推薦超參數eps = 1e-8, beta1 = 0.9,beta2 = 0.999

# Adam updatenm = beta1*m + (1-beta1)*dxnv = beta2*v + (1-beta2)*(dx**2)nx += - learning_rate * m / (np.sqrt(v) + eps)n

最終調一調參數,可以在validation上得到55%左右的結果。

Part 2 Batch Normalization

這是我們接觸到的第一個黑魔法!

這個黑魔法用在卷積層/全連接層後,激勵函數前

這個黑魔法可以有效減緩過擬合減小不好的初始化影響以及你可以用大一點的學習率了!

前向:

(訓練)非常經典的一張圖,其中 gammabeta需要學習的參數, epsilon 是一個常數

簡單說前向就是計算minibatch的均值和方差,然後對minibatch做normalize和scale、shift

(測試)測試的時候沒有minibatch啊,怎麼來計算均值和方差呢?這裡我們的解決方案是通過使用基於momentum的指數衰減,從而估計出均值和方差:

running_mean = momentum * running_mean + (1 - momentum) * sample_meannrunning_var = momentum * running_var + (1 - momentum) * sample_varn

momentum超參,一般是0.9

通過這樣的方式在訓練時不斷更新預測的均值(running_mean)和方差(running_var),測試時直接用這兩個數計算normalize和scale、shift,和訓練時的計算方式一樣。

反向:

我們需要求dx,dgamma和dbeta,前向時我們分了四步計算出了輸出(對照圖中公式,分別為mean,var,xhat和輸出y),這裡我們要反向去計算每個部分。

第四步:線性,因此 dxhat = dout * gammadgamma = sum(dout * dxhat)dbeta = sum(dout)(具體形狀一定要注意,為什麼要sum呢,因為這兩個學習的參數是一維的,類似神經元里的bias,注意axis參數的設置

解決了gamma和beta,我們來分析dx是哪裡來的。

根據公式,x和min,var,xhat都是相關的,因此,dx要由這三部分構成,分別求導

第三步: frac{dxhat}{ sqrt(var + eps)}

第二步: frac{dvar * 2 * (x - mean) }{m}

第一步: frac{dmean * x }{m}

m是什麼,minibatch的大小,x的shape[0]。

那麼我們把求dx轉化為求上面三部分,dxhat已經有了,剩下要求dmean和dvar(同理,var和mean也都是一維的,所以要sum)

var只有第三步有貢獻,直接求導 : dvar = sum(dxhat * (x - mean) * -frac{1}{2} (var + eps)^{-frac{3}{2}})

mean第二步第三步都有貢獻,為兩部分之和: dmean = sum(dxhat * - (var + eps)^{frac{1}{2}} )+sum(dvar * - frac{2}{m} *(x - mean))

和論文里一模一樣

這樣就終於通過公式則把dx求出來了,然後輕鬆愉快實現代碼。其實作業要求的部分是通過鏈式法則直接計算,選作的部分是公式計算,不過公式計算的運算速度快一些,有興趣的可以去試試。

最後把黑魔法放到網路里,看看變化吧!

Part 3 (inverted) Dropout

這又是一個有用的黑魔法!

這個黑魔法用在激勵函數後

這個黑魔法可以有效緩解過擬合

其實原理很簡單,就是以一定的概率選擇使用一部分神經元。

inverted dropout通過多除一個p保證了訓練預測分布的統一。

前向:

(訓練)用mask選擇神經元,有了mask元素乘一下就好了。

mask = (np.random.rand(x.shape[0], x.shape[1]) < p) / pn

(測試)當然是什麼也不做了!

反向:

類似ReLU,不參與更新的部分都用mask去和dout相乘(元素乘)。

在剛才的網路里加上dropout黑魔法吧,你可以對比加與不加的效果。

休息一下!因為後面就是手擼CNN大boss啦!

誰說圖文無關啦!明明珈百璃的墮落(英文:Gabriel DropOut)名字裡面有dropout!!!逃(

Part 4 Convolutional Networks

古人云:學習CNN最好的方式就是手寫一個CNN

是非常有道里的話!

那麼就讓我們開始挑戰吧!(強烈推薦deeplearning.ai第四課CNN,第一周作業詳細講了怎麼手擼CNN,不過那個作業給的循環太多,而本節作業是向量化的)

1 卷積核

(前向)

首先明確輸入x和卷積核的形狀(數量,通道,高,寬):

N, C, H, W = x.shapenF, C, HH, WW = w.shapen

(1) 確定輸出的形狀

pad參數: P,決定了你要在四周pad多少

stride參數:S,決定了卷積核每次移動多少

所以根據公式,可以直接計算輸出out的形狀(N,F,Hnew,Wnew),記得用0初始化

Hnew = (H - HH + 2 * P) / S + 1

Wnew = (W - WW + 2 * P) / S + 1

(2) zero padding

np.pad()就是實現這個功能的,請仔細閱讀函數接受的參數意義。pad_width接受每個維度上前後pad的大小,當某一維度使用(pad,)表示前後都是pad大小(等於(pad, pad))

(3) 卷積操作

這裡的卷積和通信原理里的卷積還是稍有區別的,在這裡其實只是卷積核和相應的區域進行元素乘,然後求和,課程官網給的說明十分形象生動:

也就是每個卷積核分別在每個通道上對應區域進行元素乘,然後求和,對應圖中:

(-3(通道1元素乘後求和) + -1(通道2元素乘後求和) + 0 (通道3元素乘後求和))(三個通道求和) + 1(bias_0) = -3(out的第一個格子里的值)

所以,關鍵問題就是根據步長如何確定x對應區域,這裡需要對Hnew(下標i)和Wnew(下標j)進行雙循環:

x_mask = [:, :, i*stride:i*stride+HH, j*stride:j*stride+WW]n

選好區域直接和每個卷積(下標k)核作元素乘就行了,注意sum的時候我們其實是在(C, H, W)上作的,因此axis=(1, 2, 3)。這時候一個輸出out[:, k , i, j]就計算好了。

所以上述一共套了i, j ,k三層循環,循環完畢後out再加上bias就行了。注意b的形狀(F,),因此要先把b擴展成和out一樣的形狀:b[None, :, None, None](None相當於np.newaxis)

至此,前向計算完畢!

(反向)

首先明確我們要求什麼。dx,dw和db。

(1) db

db最好求啊,直接就是sum(dout),還要和b的形狀(F,)一致,所以axis=(0, 2, 3)

那麼dx,dw到底是什麼呢。其實dx和dw是另外兩個卷積。只不過這次是換在dout的對應區域進行卷積操作了。這篇博客寫的很詳細,這裡借用其幾張圖片:

(2) dx

dx對應區域 = dout對應區域卷積核w進行卷積:

這個圖中的是通過對dout進行padding然後和旋轉180度的卷積核進行卷積計算的,實際上可以不這麼麻煩。用dout中的每一個值原卷積核進行元素乘,然後對dx的對應區域進行疊加就可以了。數學上是等價的:

如圖所示,4*4的矩陣中每一個顏色對應dout中的一個值(共9種),粗體對應原卷積核。每一個藍框對應一個dout值和w的元素乘操作。把重疊區域加起來後和上圖結果一致。

卷積我們已經會了,剩下就是確定對應區域到底是什麼了。

dx的形狀是(N, C, H, W)

w的形狀是(F, C, HH, WW)

dout的形狀是(N,F,Hnew,Wnew)

對於每個樣本(即對N循環,n為下標):

n,i,j將確定一個dout,確定後dout的形狀變為(F,),因此和前向bias同理需要維度擴展為

[:, None, None, None],以保證和w的形狀一致

dx的對應區域由n和x_mask(就是前向中的x對應區域)共同確定:

dx_mask[n, :, i*stride:i*stride+HH, j*stride:j*stride+WW]n

三層循環i,j,n完成卷積後,需要對pad過的dx進行解pad:

dx = dx_pad[:,:,pad:-pad,pad:-pad]n

(3) dw

dw對應區域 = doutx對應區域進行卷積:

對於每個卷積(即對F循環,k為下標):

x對應區域為(向前時已經給出了,就是那個x_mask)

k,i,j將確定一個dout,此時dout的形狀為(N,),為了何x_mask一致同樣需要擴展為

[:, None, None, None]

然後就可以三層循環i,j,k做卷積了,計算結果就是dw[k]。

OK,至此反向計算完畢!大功告成!

剩下一個簡單的操作,pooling。

2 pooling

2.1 max pooling

(前向)

pooling同樣有步長,確定輸出形狀的公式:

Hnew = (H - HH ) / S + 1

Wnew = (W - WW ) / S + 1

其中HH,WW是pooling的大小。

max pooling顧名思義就是取這個pooling大小區域內的max值。注意axis=(2, 3)

(反向)

反向和ReLU、DropOut是類似的,也就是說只有剛才前向通過的才允許繼續傳遞梯度

那麼我們需要記錄max在區域里的位置,用temp_binary_mask表示:

x_padded_mask = x[:, :, i*stride:i*stride+pool_height, j*stride:j*stride+pool_width] #(:, :, HH, WW)nmax_mask = np.max(x_padded_mask, axis=(2,3)) ntemp_binary_mask = (x_padded_mask == (max_mask)[:,:,None,None])n

max_mask形狀是(HH, WW),為了和x_padded_mask形狀對應也要擴展。

然後dout和這個temp_binary_mask元素乘即可。同樣注意dout是由i,j確定的,因此形狀需要擴展

2.2 average pooling

附送一個average pooling,也很簡單。

前向就是在pooling大小區域內求平均值。

反向就是把dout平均在pooling區域內。

3 CNN

結構:conv - relu - 2x2 max pool - affine - relu - affine - softmax

快把剛才完成的結構組合在一起吧!1epoch可以得到40%的準確率。

4 Spatial batch normalization

以前我們做的BN形狀是(N, D),這裡不過是將(N, C, H, W)reshape為(N*H*W, C)。因為spatial batch normalization computes a mean and variance for each of the C feature channels by computing statistics over both the minibatch dimension N and the spatial dimensions H and W.

需要用到np.transpose(),現將(N, C, H, W)變為(N, H, W, C),再reshape。反向也是同理。

至此,手擼CNN全部結束!

完結撒花!(並沒有完結!

Part 5 Tensorflow on CIFAR-10

歡迎來到TensorFlow的世界,從此你可以告別複雜的反向傳播推導了!

tensorflow入門資料回顧,在part D哦。

在這個世界中我們只需要考慮怎麼樣設計網路和loss就行了。

層,激勵函數,Loss:tensorflow.org/api_guid

優化器:tensorflow.org/api_guid

BN:tensorflow.org/api_docs

這裡強調一點,使用BN黑魔法時請務必注意:

A 在你的優化器上套上這兩句:

# batch normalization in tensorflow requires this extra dependencynextra_update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)nwith tf.control_dependencies(extra_update_ops):n train_step = optimizer.minimize(mean_loss)n

B 注意tf.layers.batch_normalization()中的is_training(是一個tf.placeholder)在訓練和測試時的設置!如果你要使用dropout也是類似的。

你可以嘗試各種各樣的網路。這裡喵喵10epoch在test達到了76.2%。相信你可以做得更好。

恭喜你達到了第二章的BOSS,下一次讓我們來做一些有趣的事情吧!

じゃ、おやすみ~!

為什麼這麼模糊!

下期預告:Assignment3作業詳解

推薦閱讀:

(二)計算機視覺四大基本任務(分類、定位、檢測、分割)
初見相關濾波與OTB
[DL-醫療-綜述] 002 綜合指南及實例(中)
何愷明大神的「Focal Loss」,如何更好地理解?
【小林的OpenCV基礎課 番外】Spyder下配置OpenCV

TAG:深度学习DeepLearning | 计算机视觉 | TensorFlow |