Hello,大家好,這裡是從不拖更的糖葫蘆喵喵~!
ようこそ実力至上主義のCS231n教室へ天気がいいから、いっしょに散歩しましょう。(劃掉煩人的聽力!)
考N1什麼的才不會耽誤更新!大家一定要保佑喵喵一次通過啊~
那麼,最後一篇CS231n 2017 Spring Assignment3 就要開始了~!
喵喵的代碼實作:Observerspy/CS231n,歡迎watch和star!
視訊講解CS231n Assignment3:
Assignment 3-AI慕課學院www.mooc.ai
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5yYiJWO0UzN5IzMkljMmJGMmFjZiRDMmZ2N5MmN5MmMw8CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
Part 1 Image Captioning with Vanilla RNNs
下面讓我們來和普通RNN一起玩耍,并完成一個簡單的看圖說話(圖像語義分析)的任務吧!
1.1 Vanilla RNN首先我們來看對于一個RNN單元:
前向:向右的箭頭:也就是
從左到右的傳播過程:
向上的箭頭:輸出層,是一個softmax:
反向我們需要計算dWx,dWh,db,dx和dh,不管計算哪一個,都要先把
這層先去掉,其導數是
。是以上遊傳下來的dout和導數先相乘,我們把這個值先記為dtheta。
剩下的就是一個線性的式子,分别對我們要的5個參數求導:
db = sum(dtheta)
dWx = dtheta
dotx (是
矩陣乘哦,然後還有
注意形狀!這裡喵喵給出的不是實際式子,應該誰和誰相乘請按照形狀自己想一想~)
dWh = dtheta
doth
dx = dtheta
dotWx
dh = dtheta
dotWh
1.1.2 完整RNN每個單元都是一樣的,那麼,對于完整的RNN:
前向:也就是要對RNN單元循環T次,T是序列的長度。
傳回來看RNN單元前向公式,每次輸入5個參數,輸出一個
,
。是以在循環内部我們要不斷更新
,并記錄它。這裡就不贅述了。
反向:RNN反向和之前我們計算的反向都不太一樣,因為之前我們計算反向時上遊傳回來的值隻有dout一個。在RNN示例圖中,反向時所有箭頭都要翻轉,也就是說,不僅有從右邊來的dprev_h,還有從上面來的dh。
既然有兩個那麼加起來就好了。
首先我們逆序循環:
for t in range(T-1, -1, -1):
對于每一個時間片t,上面來的導數是dh[:, t, :](形狀是(N, T, H)),右邊來的暫時記作dprev_h。
對于最後一個單元,它的右邊沒有傳來導數,是以初始化dprev_h是0。于是對于每一個RNN單元,需要傳進去的導數就是
dh[:, t, :]+dprev_h。顯然dprev_h是需要更新的。
注意到我們在實作RNN單元的反向傳播時還需要有一個參數cache,是以還需要構造cache,這時候需要注意其中有些元素的
下标。
參數計算完畢,利用剛才實作的反向傳播就可以計算出5個導數了。
但是還沒有完!
對于dx,我們可以計算它每一個時刻的導數,那麼剩下dWx,dWh,db和dh呢?注意到
其實RNN中,
這四個參數在不同時刻是共享的,是以需要在
每一個時刻把它們都加起來做更新。
RNN我們做完啦!下面讓我們來看看這個看圖說話(圖像語義分析)任務是什麼樣!
1.2 Image Captioning簡單說就是看圖說話,給你一幅圖,給出關于這幅圖的說明文字。網絡結構如下圖所示:
輸入一幅圖檔,從訓練好的vgg16的fc7層中取出
特征,當做
h0輸入到RNN中。這樣剩下的任務就是訓練網絡,使之能夠輸出圖檔說明句子(單詞序列)。然後h還要經過一次線性變換。
如何表示單詞呢?做過自然語言處理想必一定知道必不可少的操作就是Word embedding。其實就是把onehot編碼的
單詞映射為一個向量,這個
映射你可以自己從頭訓練,也可以用現成的比如word2vec,glove等繼續訓練,這裡不再贅述。那麼我們來看Word embedding的具體操作:
1.2.1 Word embedding 前向:就是從
映射矩陣裡取出你要的單詞對應的vec,直接取就好了。
反向:
反向隻是選出dout中對應x的值就行了,注意各部分的形狀,np.add.at()可以實作這一操作:
dW = np.zeros(W.shape) #dW.shape: (V, D)
np.add.at(dW, x, dout) #x,shape(N, T) dout.shape: (N, T, D)
這樣,對于RNN而言通過上面的兩個步驟,兩個輸入(隐藏層的
h0和
embedding的單詞)都準備好了,就可以進行訓練了!
1.2.2 網絡結構:圖檔特征 -> FC -> h0
單詞 -> embedding -> input
(h0, input) -> RNN -> Output
Output ->
時序FC->
時序softmax-> Loss
注意最後的loss是時序FC和時序softmax,具體可以看給出的實作(其實沒有太大的差别,就是形狀不一樣:原來是輸入是(N, D)現在是(N, T, D),要來回reshape(N*T, D))。
前向和反向過程按照上述結構組合子產品就可以了!
注意訓練時候輸入是captions[:, :-1],label是captions[:, 1:],也就是用
下一個詞當做目前詞的label。
captions的第一個詞是start标記,最後一個詞是end标記。
測試的時候稍有不同,我們需要構造一個輸入,它的第一個單詞是start标記。和訓練時不同的是,
測試時我們不知道T的大小,是以
限定了最大輸出長度,需要在循環裡用RNN前向單元來進行預測:
for t in range(1,max_length):
word_embedding_forward()
rnn_step_forward()
affine_forward()
captions[:,t] = np.argmax()
這樣所有的結構都完成了,快去進行愉快的訓練和測試吧!看看結果是不是很有趣?
Part 2 Image Captioning with LSTMs
RNN對長一點的序列有難以訓練的缺點,那麼我們就用LSTM替換它!
整個Image Captioning結構就不在重複說了,這裡着重說明一下LSTM。
2.1 LSTM單元解釋一下這個圖
從左向右傳遞的兩個:
上面是從下向上計算的分别是:
、
、
和
,計算公式如下:(空心圈是元素乘)
注意到我們有三個輸入c、h和x,同時Wx、Wh和b都分别有四個,實際上代碼中将四個Wx、Wh和b連結在了一起,是以
f、i、g、o的線性部分可以通過以一次計算完成:
z = x.dot(Wx) + prev_h.dot(Wh) + b
z就變成了(N, 4H)的形狀,取出對應的部分計算c、h即可。
反向:反向要求dWx,dWh,db,dx,dprev_h和dprev_c。
和RNN不同,
這次反向傳回來的值有兩個,分别是dnext_h和dnext_c。
回到圖中分析,将
所有箭頭反過來發現
o隻受到dnext_h影響,而i,f,g都是收到
dnext_c與dnext_h通過tanh傳回上面的值(記作dh2c)的和影響。
式子比較多,我們先不管那四個一樣的線性部分(把它們記作i,f,o,g)
針對
首先求do(o是sigmoid求出來的,複習一下sigmoid的導數:sigmoid * (1 - sigmoid))
do = dnext_h * tanh(c) * o * (1 - o)
然後求
dnext_h通過tanh傳回上面的值(dh2c):dh2c = dnext_h * o * (1 - tanh(c)**2)
這個值和
dnext_c在
的計算中彙合(就是把兩個值加起來),共同影響了i,f,g
針對
:(傳回這個式子的導數就是
dnext_h + dh2c)
di = (dnext_h + dh2c) * g * i * (1 - i)
df = (dnext_h + dh2c) * prev_c * f * (1 - f)
dg = (dnext_h + dh2c) * i * (1 - g**2)
dprev_c = (dnext_h + dh2c) * f
然後把di,df,do和dg用np.hstack連結在一起(記作d)就得到了線性部分的導數。
剩下的線性部分導數就很簡單了
dx = d
dotWx
dWx = d
dotx
dWh = d
dotprev_h
dprev_h = d
dotWh
db = sum(d)
注意對應形狀,大功告成!
2.2 完整LSTMLSTM單元完成了,剩下的LSTM子產品其實和RNN差不多啦
前向:比RNN多了一個c,初始為0,記得循環裡要更新它。
反向:也是多了一個c,初始為0,循環裡要更新它。
注意代碼中cache的構造方式和RNN不同。
好了,現在你可以把LSTM加入到Image Captioning中的網絡結構裡去了!看看效果吧!
Part 3 Network Visualization: Saliency maps, Class Visualization, and Fooling Images
複雜的公式和求導再也沒有了,從這裡開始都是有趣的例子,讓我們一起看看吧!
3.1 Saliency maps顯著圖,也就是要看看我們訓練好的網絡在進行圖檔分類時
關注的是圖檔的那些地方(像素點)。
和之前不同的是,以前我們的梯度都是針對參數進行計算的,現在我們要利用
正确分類的score去計算
輸入圖檔的梯度。然後取
梯度的絕對值,在
3個通道上選擇最大的那個值。
這裡注意求梯度的函數tf.gradients()傳回的是一個
list,我們隻要取
tf.gradients()[0]的值就行了。
然後運作可視化出來,是不是很有趣的結果?
調整我們的圖檔,使它
騙過我們訓練好的網絡,也就是用
目标分類對
輸入圖檔的
梯度來疊代
更新輸入圖檔。首先計算目标分類對于目前輸入圖檔的梯度,注意這裡label要用tf.one_hot轉換成onehot編碼形式:
loss = tf.nn.softmax_cross_entropy_with_logits(labels=tf.one_hot(target_y, 1000), logits=model.classifier)
g = tf.gradients(loss, model.image)[0]
然後按照要求計算normalize的梯度,并梯度上升更新輸入:
X_fooling = model.image - dX
一般在100輪疊代内即可完成,是以要寫一個循環,循環裡面計算:
X = sess.run(X_fooling, feed_dict={model.image: X})
注意這行代碼實際上是在不斷更新X(也就是一開始我們的輸入)。
當然你可以不用等100輪疊代完成,用下面的子產品做一個判斷然後break就行啦。
scores = sess.run(model.classifier, {model.image: X})
if scores[0].argmax() == target_y:
print(i," ",class_names[scores[0].argmax()])
break
然後跑起來吧,得到的結果如下圖,看起來一樣,但是你的網絡已經認為它不一樣了!
類别可視化,和顯著圖類似,不過這次我們要看的是訓練好的網絡
關注的每個類别圖檔是什麼樣子。基本和上一個任務一樣,隻不過這次我們在
随機噪聲圖檔上更新目标分類的梯度。
注意根據要求這次loss要減去一個l2正則項。
循環裡依舊更新X,最終跑出來的結果:
竟然是蜘蛛!(逃...
Part 4 Style Transfer
風格遷移,顧名思義,就是把一幅圖檔的風格轉移到另一幅圖檔上,效果如下:
網絡結構是這樣子的:
簡單說就是定義一個新的loss,然後對随機噪聲圖檔進行梯度更新。注意三個圖檔是經過
同樣的訓練好的網絡的。
4.1 loss為了達到這個效果,首先我們要考慮loss。新的loss由三項構成:content loss、style loss和total variation loss。
4.1.1 content loss内容loss反映了
生成的圖檔内容和
源内容圖檔的差異,很簡單,用feature map度量:
其中
是
生成圖檔在網絡中第的feature map,
是
源内容圖檔在網絡中第的feature map,
是權重。可以用2 * tf.nn.l2_loss()實作(l2_loss是有系數0.5的)。
4.1.2 style loss風格loss目的就是為了衡量
生成的圖檔風格和
源風格圖檔的差異。而風
格是用feature map的協方差矩陣度量的,是以新定義了一個Gram matrix:
實際上協方差矩陣反應了feature map值之間的關聯性,通過相乘,使得
原來大的值更大,
原來小的值更小,也就是要突出自己的feature map的特點。
和内容loss一樣,風格差異定義如下:
其中
是
生成圖檔在網絡中第的feature map,
是
源風格圖檔在網絡中第的feature map,
是權重。實際計算的時候往往用
多層的feature map來計算風格loss,是以最後要對不同層的風格loss
進行求和。
4.1.3 total variation loss這一項正則可以使生成圖檔更平滑:
其中
是生成圖像,
是權重。這個式子乍一看很複雜,其實這個式子就是在H和W次元上計算
相鄰像素的內插補點的平方和,隻要計算好
相鄰像素的內插補點:pixel_dif1 = img[:, 1:, :W-1, :] - img[:, :H-1, :W-1, :]
pixel_dif2 = img[:, :H-1, 1:, :] - img[:, :H-1, :W-1, :]
實際上tf裡提供了絕對值版本的
total variation loss:tf.image.total_variation()。也就是不算平方,隻用絕對值。
至此,三個loss計算完畢,加在一起就好了。
代碼跑起來就可以看到上面的效果了,是不是很有趣?
這裡有很多可以調的參數,loss的三個權重,内容loss和風格loss的層選擇等等,這些參數對于結果有很大影響。
特别的,當風格loss權重為0時,是對源内容圖像的重建,當然你也可以試試内容loss權重為0的情況。
另外,和這個需要不斷疊代的風格遷移不同,有更快的fast style transfer方法去進行遷移,詳見部落格:談談圖像的Style Transfer(一),談談圖像的style transfer(二)。
Part 5 Generative Adversarial Networks(GAN)
終于到了我們作業的最後階段啦!
生成對抗網絡可以說是近年最火的一個研究方向,它生成的圖檔品質之高讓人驚歎。那麼就讓我們來看看它的結構吧!
GAN其實就是
生成器和
判别器之間的
博弈,一方面
判别器從真實資料和生成資料中不斷學習
提高自己的判别能力,另一方面
生成器不斷疊代以
提高自己的欺騙能力。這樣,我們就可以用loss來表達我們的需求了:
這個公式有兩層意思:
- 更新生成器 使得判别器做出 正确判斷 的機率 下降
- 更新判别器 使得判别器做出 正确判斷 的機率 上升
所謂正确判斷是指判别器把
真實資料判别為真,生成樣本資料判别為假。這個過程也就是我們說的博弈的過程。但是一邊上升一邊下降是很難計算的,是以我們把1.反過來說:
- 更新生成器 使得判别器做出 錯誤判斷 的機率 上升
1.對應公式:
2.對應公式:
接下來,我們分别要實作vanilla gan、ls-gan、dc-gan和wgan-gp。至于哪一種gan好,谷歌說,都差不多2333。詳見那麼多GAN哪個好?谷歌大腦潑了盆冷水:都沒能超越原版|論文。不過從效果上看,我覺得dc-gan在手寫數字生成上更好一些。
下面就是實作不同的gan了,都是調用tf的函數,建議使用
tf.layers裡面的函數,省的自己定義參數了,友善快捷,效果也好。
5.1 vanilla gan 判别器:Fully connected layer from size 784 to 256
LeakyReLU with alpha 0.01
Fully connected layer from 256 to 256
LeakyReLU with alpha 0.01
Fully connected layer from 256 to 1
生成器:Fully connected layer from tf.shape(z)[1] (the number of noise dimensions) to 1024
ReLU
Fully connected layer from 1024 to 1024
ReLU
Fully connected layer from 1024 to 784
TanH (To restrict the output to be [-1,1])
loss:有三個部分,
一定注意label應該對應誰。D_loss1 = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits = logits_real, labels = D_label))
D_loss2 = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits = logits_fake, labels = G_fake_label))
G_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits = logits_fake, labels = G_real_label))
5.2 ls-gan loss有所變動,不在計算交叉熵了:
32 Filters, 5x5, Stride 1, Leaky ReLU(alpha=0.01)
Max Pool 2x2, Stride 2
64 Filters, 5x5, Stride 1, Leaky ReLU(alpha=0.01)
Max Pool 2x2, Stride 2
Flatten
Fully Connected size 4 x 4 x 64, Leaky ReLU(alpha=0.01)
Fully Connected size 1
生成器:注意conv2d^T (transpose)操作是反卷積tf.nn.conv2d_transposeFully connected of size 1024, ReLU
BatchNorm
Fully connected of size 7 x 7 x 128, ReLU
BatchNorm
Resize into Image Tensor
64 conv2d^T (transpose) filters of 4x4, stride 2, ReLU
BatchNorm
1 conv2d^T (transpose) filter of 4x4, stride 2, TanH
5.4 wgan-gp 判别器:64 Filters of 4x4, stride 2, LeakyReLU
128 Filters of 4x4, stride 2, LeakyReLU
BatchNorm
Flatten
Fully connected 1024, LeakyReLU
Fully connected size 1
生成器同上一個網絡。loss:這裡多了
gradient penalty的操作。優點詳見鄭華濱:生成式對抗網絡GAN有哪些最新的發展,可以實際應用到哪些場景中?
由于涉及内容較多,具體的
gradient penalty實作請看代碼。
5.5 效果最後的最後,讓我們比較一下四種gan的輸出結果吧!
左上vanilla gan右上ls-gan左下dc-gan右下wgan-gp。看起來還是dc-gan好呢!
恭喜看到這裡的大家,你們已經完成了所有的Assignment!相信你們一定學到了很多東西,請在接下來的旅程中繼續努力吧!
じゃ、おやすみ~!