卷积层与池化层(bn层的原理和作用)
構(gòu)建了最簡單的網(wǎng)絡(luò)之后,是時(shí)候再加上卷積和池化了。這篇,雖然我還沒開始構(gòu)思,但我知道,一定是很長的文章。
卷積神經(jīng)網(wǎng)絡(luò)(Convolutional Neural Layer, CNN),除了全連接層以外(有時(shí)候也不含全連接層,因?yàn)槌霈F(xiàn)了Global average pooling),還包含了卷積層和池化層。卷積層用來提取特征,而池化層可以減少參數(shù)數(shù)量。
卷積層
先談一下卷積層的工作原理。
我們是使用卷積核來提取特征的,卷積核可以說是一個矩陣。假如我們設(shè)置一個卷積核為3*3的矩陣,而我們圖片為一個分辨率5*5的圖片。那么卷積核的任務(wù)就如下所示:
來自:https://mlnotebook.github.io/post/CNN1/
從左上角開始,卷積核就對應(yīng)著數(shù)據(jù)的3*3的矩陣范圍,然后相乘再相加得出一個值。按照這種順序,每隔一個像素就操作一次,我們就可以得出9個值。這九個值形成的矩陣被我們稱作激活映射(Activation map)。這就是我們的卷積層工作原理。也可以參考下面一個gif:
其中,卷積核為
101010101 1 0 1 0 1 0 1 0 1
來自:https://blog.csdn.net/yunpiao123456/article/details/52437794
其實(shí)我們平時(shí)舉例的卷積核已經(jīng)被翻轉(zhuǎn)180度一次了,主要是因?yàn)橛?jì)算過程的原因。詳細(xì)不用了解,但原理都一樣。
但其實(shí)我們輸入的圖像一般為三維,即含有R、G、B三個通道。但其實(shí)經(jīng)過一個卷積核之后,三維會變成一維。它在一整個屏幕滑動的時(shí)候,其實(shí)會把三個通道的值都累加起來,最終只是輸出一個一維矩陣。而多個卷積核(一個卷積層的卷積核數(shù)目是自己確定的)滑動之后形成的Activation Map堆疊起來,再經(jīng)過一個激活函數(shù)就是一個卷積層的輸出了。
來自:CS231n,卷積與池化
卷積層還有另外兩個很重要的參數(shù):步長和padding。
所謂的步長就是控制卷積核移動的距離。在上面的例子看到,卷積核都是隔著一個像素進(jìn)行映射的,那么我們也可以讓它隔著兩個、三個,而這個距離被我們稱作步長。
而padding就是我們對數(shù)據(jù)做的操作。一般有兩種,一種是不進(jìn)行操作,一種是補(bǔ)0使得卷積后的激活映射尺寸不變。上面我們可以看到5*5*3的數(shù)據(jù)被3*3的卷積核卷積后的映射圖,形狀為3*3,即形狀與一開始的數(shù)據(jù)不同。有時(shí)候?yàn)榱艘?guī)避這個變化,我們使用“補(bǔ)0”的方法——即在數(shù)據(jù)的外層補(bǔ)上0。
下面是示意圖:
步長為2 (圖片來自于機(jī)器之心)
補(bǔ)0的變化 (圖片來自于機(jī)器之心)
了解卷積發(fā)展史的人都應(yīng)該知道,卷積神經(jīng)網(wǎng)絡(luò)應(yīng)用最開始出現(xiàn)是LeCun(名字真的很像中國人)在識別手寫數(shù)字創(chuàng)建的LeNet-5。
后面爆發(fā)是因?yàn)锳lexNet在ImageNet比賽中拔得頭籌,硬生生把誤差變成去年的一半。從此卷積網(wǎng)絡(luò)就成了AI的大熱點(diǎn),一大堆論文和網(wǎng)絡(luò)不斷地發(fā)揮它的潛能,而它的黑盒性也不斷被人解釋。
能否對卷積神經(jīng)網(wǎng)絡(luò)工作原理做一個直觀的解釋? – Owl of Minerva的回答 – 知乎里面通過我們對圖像進(jìn)行平滑的操作進(jìn)而解釋了卷積核如何讀取特征的。
我們需要先明確一點(diǎn),實(shí)驗(yàn)告訴我們?nèi)祟愐曈X是先對圖像邊緣開始敏感的。在我的理解中,它就是說我們對現(xiàn)有事物的印象是我們先通過提取邊界的特征,然后逐漸的完善再進(jìn)行組裝而成的。而我們的卷積層很好的做到了這一點(diǎn)。
來自https://ujjwalkarn.me/2016/08/11/intuitive-explanation-convnets/
這是兩個不同的卷積核滑動整個圖像后出來的效果,可以看出,經(jīng)過卷積之后圖像的邊界變得更加直觀。我們也可以來看下VGG-16網(wǎng)絡(luò)第一層卷積提取到的特征:
由此來看,我們也知道為什么我們不能只要一個卷積核。在我的理解下,假使我們只有一個卷積核,那我們或許只能提取到一個邊界。但假如我們有許多的卷積核檢測不同的邊界,不同的邊界又構(gòu)成不同的物體,這就是我們怎么從視覺圖像檢測物體的憑據(jù)了。所以,深度學(xué)習(xí)的“深”不僅僅是代表網(wǎng)絡(luò),也代表我們能檢測的物體的深度。即越深,提取的特征也就越多。
Google提出了一個項(xiàng)目叫Deepdream,里面通過梯度上升、反卷積形象的告訴我們一個網(wǎng)絡(luò)究竟想要識別什么。之前權(quán)重更新我們講過梯度下降,而梯度上升便是計(jì)算卷積核對輸入的噪聲的梯度,然后沿著上升的方向調(diào)整我們的輸入。詳細(xì)的以后再講,但得出的圖像能夠使得這個卷積核被激活,也就是說得到一個較好的值。所以這個圖像也就是我們卷積核所認(rèn)為的最規(guī)范的圖像(有點(diǎn)嚇人):
其實(shí)這鵝看著還不錯,有點(diǎn)像孔雀。
池化層 (pooling layer)
前面說到池化層是降低參數(shù),而降低參數(shù)的方法當(dāng)然也只有刪除參數(shù)了。
一般我們有最大池化和平均池化,而最大池化就我認(rèn)識來說是相對多的。需要注意的是,池化層一般放在卷積層后面。所以池化層池化的是卷積層的輸出!
來自:https://blog.csdn.net/yunpiao123456/article/details/52437794
掃描的順序跟卷積一樣,都是從左上角開始然后根據(jù)你設(shè)置的步長逐步掃描全局。有些人會很好奇最大池化的時(shí)候你怎么知道哪個是最大值,emmm,其實(shí)我也考慮過這個問題。CS2131n里面我記得是說會提前記錄最大值保存在一個矩陣中,然后根據(jù)那個矩陣來提取最大值。
至于要深入到計(jì)算過程與否,應(yīng)該是沒有必要的。所以我也沒去查證過程。而且給的都是示例圖,其實(shí)具體的計(jì)算過程應(yīng)該也是不同的,但效果我們可以知道就好了。
至于為什么選擇最大池化,應(yīng)該是為了提取最明顯的特征,所以選用的最大池化。平均池化呢,就是顧及每一個像素,所以選擇將所有的像素值都相加然后再平均。
池化層也有padding的選項(xiàng)。但都是跟卷積層一樣的,在外圍補(bǔ)0,然后再池化。
代碼解析
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
summary_dir = './summary'
#批次
batch_size = 100
n_batch = mnist.train.num_examples // batch_size
x = tf.placeholder(tf.float32, [None, 784], name='input')
y = tf.placeholder(tf.float32, [None, 10], name='label')
def net(input_tensor):
conv_weights = tf.get_variable('weight', [3, 3, 1, 32],
initializer=tf.truncated_normal_initializer(stddev=0.1))
conv_biases = tf.get_variable('biase', [32], initializer=tf.constant_initializer(0.0))
conv = tf.nn.conv2d(input_tensor, conv_weights, strides=[1, 1, 1, 1], padding='SAME')
relu = tf.nn.relu(tf.nn.bias_add(conv, conv_biases))
pool = tf.nn.max_pool(relu, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME')
pool_shape = pool.get_shape().as_list()
nodes = pool_shape[1] * pool_shape[2] * pool_shape[3]
pool_reshaped = tf.reshape(pool, [-1, nodes])
W = tf.Variable(tf.zeros([nodes, 10]), name='weight')
b = tf.Variable(tf.zeros([10]), name='bias')
fc = tf.nn.softmax(tf.matmul(pool_reshaped, W) + b)
return fc
reshaped = tf.reshape(x, (-1, 28, 28, 1))
prediction = net(reshaped)
loss_ = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=tf.argmax(y, 1), logits=prediction, name='loss')
loss = tf.reduce_mean(loss_)
train_step = tf.train.GradientDescentOptimizer(0.2).minimize(loss)
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(prediction, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32), name='accuracy')
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
for epoch in range(31):
for batch in range(n_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys})
acc = sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels})
print('Iter' + str(epoch) + ",Testing Accuracy" + str(acc))
這相對于我第一個只用全連接的網(wǎng)絡(luò)只多了一個net函數(shù),還有因?yàn)榫矸e層的關(guān)系進(jìn)來的數(shù)據(jù)x需要改變形狀。只講這兩部分:
reshaped = tf.reshape(x, (-1, 28, 28, 1))
prediction = net(reshaped)
由于我們feedict上面是,feed_dict={x: mnist.test.images, y: mnist.test.labels},而這樣子調(diào)用tensorflow的句子我們得到的x是固定的形狀。因此我們應(yīng)用tf.reshape(x_need_reshaped,object_shape)來得到需要的形狀。
其中的 ?1 ? 1 表示拉平,不能用None,是固定的。
conv_weights = tf.get_variable('weight', [3, 3, 1, 32],
initializer=tf.truncated_normal_initializer(stddev=0.1))
conv_biases = tf.get_variable('biase', [32], initializer=tf.constant_initializer(0.0))
conv = tf.nn.conv2d(input_tensor, conv_weights, strides=[1, 1, 1, 1], padding='SAME')
relu = tf.nn.relu(tf.nn.bias_add(conv, conv_biases))
pool = tf.nn.max_pool(relu, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME')
大部分都是應(yīng)用內(nèi)置的函數(shù),來初始化weight(就是卷積核)和biases(偏置項(xiàng))。偏置項(xiàng)我們沒有提到,但其實(shí)就是多了一個參數(shù)來調(diào)控,因此我們講卷積層的時(shí)候也沒怎么講。按照代碼就是出來Activation Map之后再分別加上bias。池化也是用到了最大池化。
注意一下relu。它也是一個激活函數(shù),作用可以說跟之前講的softmax一樣,不過它在卷積層用的比較多,而且也是公認(rèn)的比較好的激活函數(shù)。它的變體有很多。有興趣大家可以自己去查閱資料。以后才會寫有關(guān)這方面的文章。
pool_shape = pool.get_shape().as_list()
nodes = pool_shape[1] * pool_shape[2] * pool_shape[3]
pool_reshaped = tf.reshape(pool, [-1, nodes])
W = tf.Variable(tf.zeros([nodes, 10]), name='weight')
池化層的輸出我們并不知道它是如何的形狀(當(dāng)然,你也可以動手算)。因此就算把池化層拉成一維的矩陣,我們也不知道W需要如何的形狀。因此,我們查看pool(即池化層的輸出)的形狀,我暗地里print了一下為[None, 14, 14, 32],因此pool拉平后,就是[None, 14*14*32, 10]。為了接下來進(jìn)行全連接層的計(jì)算,我們的W的形狀也應(yīng)該為[14*14*32, 10]。這段代碼的原理就是如此。
準(zhǔn)確率也一樣取后15次:
emmm, 不用跟之前比了,明顯比以前好很多了。下一章決定總結(jié)一下,優(yōu)化的方法好了。
參考
https://mlnotebook.github.io/post/CNN1/(可惜是全英)
能否對卷積神經(jīng)網(wǎng)絡(luò)工作原理做一個直觀的解釋? – Owl of Minerva的回答 – 知乎
CS231n
總結(jié)
以上是生活随笔為你收集整理的卷积层与池化层(bn层的原理和作用)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: double click items i
- 下一篇: 数据库的数据独立性是什么