Udacity机器人软件工程师课程笔记(二十七) - 卷积神经网络(CNN)
1.卷積神經網絡介紹
**卷積神經網絡(Convolutional Neural Network,CNN)**是一種前饋神經網絡,它的人工神經元可以響應一部分覆蓋范圍內的周圍單元,對于大型圖像處理有出色表現。
雖然上圖中顯示的全連接神經網絡結構和卷積神經網絡的結構直觀上差異比較大,但他們的整體架構是非常相似的。從上圖可以看出,卷積神經網絡也是通過一層一層的節點組織起來的。和全連接神經網絡一樣,卷積神經網絡中的每一個節點都是一個神經元。在全鏈接神經網絡中,每相鄰兩層之間的節點都全部相連,于是一般會將每一層全連接層中的節點組織成一列,這樣方便顯示連接結構。而對于卷積神經網絡,相鄰兩層之間只有部分相連接,為了展示每一層的神經元的維度,一般會將每一層卷積層的節點組織成一個三維矩陣。
除了結構相似,卷積神經網絡的輸入和輸出以及訓練流程與全連接神經網絡也基本一致。以圖象為例,卷積神經網絡的輸入層就是圖像的原始像素,而輸出層的每一個節點代表了不同類別的可信度。這和全鏈接神經網絡的輸入輸出是一致的。卷積神經網絡和全連接神經網絡的的唯一區別就在于神經網絡中相鄰兩層的連接方式。
上圖給出了一個更加具體的卷積神經網絡架構圖。
在神經網絡前幾層中,每一層的節點都被組織成一個三維矩陣,從圖中可以看出卷積神經網絡前幾層中每一個節點之和上一層中部分節點相連。一個卷積神經網絡主要由以下5種結構組成。
- 輸入層。輸入層是整個神經網絡的輸入,在處理圖像的卷積神經網絡中,它一般代表了一張圖片的像素矩陣。
- 卷積層。從名字就可以看出,卷積層是一個卷積神經網絡中最重要的部分。和傳統的全連接層不容,卷積層中每一個介蒂安的輸入只是上一層神經網絡的一小塊。這個小塊常用的大小為33或者5。卷積層試圖將神經網絡中每一個小塊進行更加深入的分析從而得到抽象程度更高的特征。一般來說,通過卷積層處理過的節點矩陣會變得更深。
- 池化層(Pooling)。池化層神經網絡不會改變三維矩陣的深度,但是它可以縮小矩陣的大小。池化操作可以認為使將一張分辨率較高的圖片轉換為一張分辨率較低的圖片。通過池化層,可以進一步縮小最后全連接層結點的個數,從而達到減少整個神經網絡中參數的目的。
- 全連接層。在經過多輪卷積層和池化層的處理之后,在卷積神經網絡的最后一般是由一到兩個全連接層來給出最后的分類結果。經過幾輪卷積層和池化層的處理之后,可以認為圖像中的信息已經被抽象成了信息含量更高的特征。我們可以將卷積層和池化層看成自動圖像特征提取的過程。在特質提取完成之后,仍然需要使用全連接層來完成分類任務。
- Softmax層。Softmax主要用于分類問題。通過Softmax層,可以得到當前樣例屬于不同種類的概率分布情況。
2. 過濾器
(1)分解圖像
CNN的第一步是將圖像分解成較小的碎片。我們通過選擇定義過濾器的寬度和高度來做到這一點。
然后,我們可以簡單地水平或垂直滑動此濾鏡以聚焦在另一幅圖像上。
過濾器滑動的量稱為“步幅”。跨度是我們可以調整的超參數。通過減少每一層觀察到的總面片數量,增加步幅可減小模型的大小。但是,這通常會降低準確性。
讓我們看一個例子。在這張放大的狗的圖像中,我們首先以紅色概述的補丁開始。濾鏡的寬度和高度定義了該正方形的大小。
然后,我們將正方形向右移動給定的步幅(在本例中為2)以獲得另一個圖片。
(2)過濾器深度
通常有多個過濾器。不同的過濾器選擇不同質量的部分。例如,一個過濾器可能查找特定的顏色,而另一個可能查找特定形狀的對象。卷積層中過濾器的數量稱為 過濾器深度 。
過濾器尺寸是指的過濾器的輸入節點矩陣的大小,深度指的hi輸出單位節點矩陣的深度。
假設使用wx,y,ziw_{x,y,z}^iwx,y,zi?來表示對于輸出單位節點矩陣中的第iii個節點,過濾器輸入節點(x,y,z)( x,y,z)(x,y,z) 的權重,使用bib^ibi表示第iii個輸出節點對應的偏置項參數,那么單位矩陣中的第iii個節點的取值g(i)g(i)g(i)為:
g(i)=f(∑x=12∑y=12∑z=13ax,y,z×wx,y,zi+bi)g(i)=f(\sum_{x=1}^{2}\sum_{y=1}^{2}\sum_{z=1}^{3}a_{x,y,z}\times w_{x,y,z}^{i}+b^i)g(i)=f(x=1∑2?y=1∑2?z=1∑3?ax,y,z?×wx,y,zi?+bi)
其中ax,y,za_{x,y,z}ax,y,z?為過濾器中節點(x,y,z)(x,y,z)(x,y,z)的取值, fff為激活函數。下圖展示了在給定a,w0a, w^0a,w0和b0b^0b0的情況下,使用ReLU 作為激活函數時g(0)g(0)g(0)的計算過程。在圖6-9 的左側給出了a,w0a, w^0a,w0的取值,這里通過3 個二維矩陣來表示一個三維矩陣的取值,其中每一個二維矩陣表示三維矩陣在某一個深度上的取值。下圖中?·? 符號表示點積,也就是矩陣中對應元素乘積的和。圖6-9 的右側顯示了g(0)g(0 )g(0)的計算過程。如果給出w1w^1w1到w4w^4w4 和b1b^1b1 到b4b^4b4 , 那么也可以類似地計算出g(1)g(1)g(1)到g(4)g(4)g(4)的取值。如果將aaa 和wiw^iwi組織成兩個向量,那么一個過濾器的計算過程完全可以通過向量乘法來完成。
當過濾器的大小不為l × l 時, 卷積層前向傳播得到的矩陣的尺寸要小于當前層矩陣的尺寸。如下圖所示, 當前層矩陣的大小為3 × 3 (左側矩陣〉,而通過卷積層前向傳播算法之后,得到的矩陣大小為2 × 2 (右側矩陣〉。為了避免尺寸的變化,可以在當前層矩陣的邊界上加入全0填充(zero -padding)。這樣可以使得卷積層前向傳播結果矩陣的大小和當前層矩陣保持一致。
以下公式給出了同時使用全0填充的結果矩陣的大小。
outlength=[inlength/srtidelength]out_{length}=[in_{length}/srtide_{length}]outlength?=[inlength?/srtidelength?]
outwidth=[inwidth/srtidewidth]out_{width}=[in_{width}/srtide_{width}]outwidth?=[inwidth?/srtidewidth?]
如果不使用全0填充,以下公式給出了結果矩陣的大小。
outlength=[(inlength?filterlength+1)/srtidelength]out_{length}=[(in_{length}-filter_{length}+1)/srtide_{length}]outlength?=[(inlength??filterlength?+1)/srtidelength?]
outwidth=[(inwidth?filterwidth+1)/srtidewidth]out_{width}=[(in_{width}-filter_{width}+1)/srtide_{width}]outwidth?=[(inwidth??filterwidth?+1)/srtidewidth?]
TensorFlow 對卷積神經網絡提供了非常好的支持,以下程序實現了一個卷積層的前向傳播過程。從以下代碼可以看出,通過TensorFlow 實現卷積層是非常方便的。
filter_weight = tf.get_variable ('weights',[5, 5, 3, 16], initializer=tf .truncated_normal_initializer(stddev=0.1))biases = tf.get_variable('biases', [16], initializer=tf.constant_initializer ( 0 .1) )conv = tf.nn.conv2d(input , filter_weight, strides=[1, 1, 1, 1], padding ='SAME')bias= tf.nn.bias add(conv, biases)actived_conv = tf.nn.relu(bias)
3.卷積層
TensorFlow提供tf.nn.conv2d()和tf.nn.bias_add()功能來創建卷積層。
# 輸出深度
k_output = 64# 圖像屬性
image_width = 10
image_height = 10
color_channels = 3# 卷積過濾器
filter_size_width = 5
filter_size_height = 5# 輸入圖片
input = tf.placeholder(tf.float32,shape=[None, image_height, image_width, color_channels])# 權重和偏置
weight = tf.Variable(tf.truncated_normal([filter_size_height, filter_size_width, color_channels, k_output]))
bias = tf.Variable(tf.zeros(k_output))# 應用卷積
conv_layer = tf.nn.conv2d(input, weight, strides=[1, 2, 2, 1], padding='SAME')
# 添加偏置
conv_layer = tf.nn.bias_add(conv_layer, bias)
# 應用激活函數
conv_layer = tf.nn.relu(conv_layer)
上面的代碼使用tf.nn.conv2d()函數來計算以權重為過濾器的卷積,并使用[1,2,2,1]來計算步幅。TensorFlow對每個輸入維度使用一個stride, [batch, input_height, input_width, input_channels]。我們通常總是將batch和input_channels(即strides組中的第一個和第四個元素)的步長設置為1。
更改input_height和input_width,同時將batch和input_channels設置為1。input_height和input_widthstrides用于跨越input的過濾器。此示例代碼在輸入上使用了帶有5x5過濾器的2步。
該tf.nn.bias_add()函數在矩陣的最后一個維度上添加一維偏置。
例如,卷積第三層輸出:
4.池化層
在卷積層之間往往會加上一個池化層( pooling layer )。池化層可以非常有效地縮小矩陣的尺寸 ,從而減少最后全連接層中的參數。使用池化層既可以加快計算速度也有防止過擬合問題的作用。
池化層前向傳播的過程也是通過移動一個類似過濾器的結構完成的。不過池化層過濾器中的計算不是節點的加權和,而是采用更加簡單的最大值或者平均值運算。使用最大值操作的池化層被稱之為最大池化層( max pooling ), 這是被使用得最多的池化層結構。使用平均值操作的池化層被稱之為平均池化層(average pooling )。
與卷積層的過濾器類似,池化層的過濾器也需要人工設定過濾器的尺寸、是否使用全0填充以及過濾器移動的步長等設置,而且這些設置的意義也是一樣的。卷積層和池化層中過濾器移動的方式是相似的,唯一的區別在于卷積層使用的過濾器是橫跨整個深度的,而池化層使用的過濾器只影響一個深度上的節點。所以池化層的過濾器除了在長和寬兩個維度移動,它還需要在深度這個維度移動。
不同顏色或者不同線段(虛線或者實線)代表了不同的池化層過濾器。從上圖中可以軒出,池化層的過濾器除了在長和寬的維度上移動,它還需要在深度的維
度上移動。以下TensorFlow 程序實現了最大池化層的前向傳播算法。
# tf.nn.max pool 實現了最大池化層的前向傳播過程,它的參數和tf.nn.conv2d 函數類似。
# ksize 提供了過濾器的尺寸, strides 提供了步長信息, padding 提供了是否使用全0 填充。
pool= tf.nn.max pool(actived_conv, ksize=[1, 3 , 3, 1], strides=[l , 2, 2, 1), padding='SAME')
對比池化層和卷積層前向傳播在TensorFlow 中的實現,可以發現函數的參數形式是相似的。在tf.nn.max_pool函數中, 首先需要傳入當前層的節點矩陣,這個矩陣是一個四維矩陣,格式和tf.nn.conv2d函數中的第一個參數一致。第二個參數為過濾器的尺寸。雖然給出的是一個長度為4 的一維數組,但是這個數組的第一個和最后一個數必須為1 。這意味著池化層的過濾器是不可以跨不同輸入樣例或者節點矩陣深度的。在實際應用中使用得最多的池化層過濾器尺寸為[1,2,2,1][ 1, 2, 2 ,1][1,2,2,1]或者[1,3,3,1][ 1, 3, 3, 1][1,3,3,1]。
tf.nn.max pool 函數的第三個參數為步長,它和tf.nn.conv2d 函數中步長的意義是一樣的,而且第一維和最后一維也只能為1。這意味著在Tensor Flow 中,池化層不能減少節點矩陣的深度或者輸入樣例的個數。tf.nn.max_pool 函數的最后一個參數指定了是否使用全0填充。這個參數也凡有兩種取值一VALID 或者SAME ,其中VALID 表示不使用全0 填充,SAME 表示使用全0 填充。TensorFlow 還提供了tf.nn.avg_pool 來實現平均池化層。tf.nn.avg_pool函數的調用格式和tf.nn.max_pool 函數是一致的。
最近,池層已不再受歡迎。原因如下:
- 最近的數據集如此龐大和復雜,我們更加擔心擬合不足。
- dropout是一個更好的正則化器。
- 合并會導致信息丟失。以最大池化操作為例。我們只保留n個數字中的最大值,從而完全忽略n-1個數字。
5.程序
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets(".", one_hot=True, reshape=False)import tensorflow as tf# gpu參數
gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.333)# 參數
learning_rate = 0.00001
epochs = 10
batch_size = 128# 計算驗證和準確性樣本的數量
# 如果計算精度的內存不足,請減少此值
test_valid_size = 256# 網絡參數
n_classes = 10 # MNIST的全部類別 (0-9 digits)
dropout = 0.75 # Dropout, 保持單位的概率# 存儲層的權重和偏置
weights = {'wc1': tf.Variable(tf.random_normal([5, 5, 1, 32])),'wc2': tf.Variable(tf.random_normal([5, 5, 32, 64])),'wd1': tf.Variable(tf.random_normal([7*7*64, 1024])),'out': tf.Variable(tf.random_normal([1024, n_classes]))}biases = {'bc1': tf.Variable(tf.random_normal([32])),'bc2': tf.Variable(tf.random_normal([64])),'bd1': tf.Variable(tf.random_normal([1024])),'out': tf.Variable(tf.random_normal([n_classes]))}def conv2d(x, W, b, strides=1):x = tf.nn.conv2d(x, W, strides=[1, strides, strides, 1], padding='SAME')x = tf.nn.bias_add(x, b)return tf.nn.relu(x)def maxpool2d(x, k=2):return tf.nn.max_pool(x, ksize=[1, k, k, 1], strides=[1, k, k, 1], padding='SAME')def conv_net(x, weights, biases, dropout):# Layer 1 - 28*28*1 to 14*14*32conv1 = conv2d(x, weights['wc1'], biases['bc1'])conv1 = maxpool2d(conv1, k=2)# Layer 2 - 14*14*32 to 7*7*64conv2 = conv2d(conv1, weights['wc2'], biases['bc2'])conv2 = maxpool2d(conv2, k=2)# Fully connected layer - 7*7*64 to 1024fc1 = tf.reshape(conv2, [-1, weights['wd1'].get_shape().as_list()[0]])fc1 = tf.add(tf.matmul(fc1, weights['wd1']), biases['bd1'])fc1 = tf.nn.relu(fc1)fc1 = tf.nn.dropout(fc1, dropout)# Output Layer - class prediction - 1024 to 10out = tf.add(tf.matmul(fc1, weights['out']), biases['out'])return out# tf 計算圖輸入
x = tf.placeholder(tf.float32, [None, 28, 28, 1])
y = tf.placeholder(tf.float32, [None, n_classes])
keep_prob = tf.placeholder(tf.float32)# 模型
logits = conv_net(x, weights, biases, keep_prob)# 定義損失和優化
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y))
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate).minimize(cost)# 準確率
correct_pred = tf.equal(tf.argmax(logits, 1), tf.argmax(y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))# 初始化變量
init = tf.global_variables_initializer()# 啟動計算圖
with tf.Session(config=tf.ConfigProto(gpu_options=gpu_options)) as sess:sess.run(init)for epoch in range(epochs):for batch in range(mnist.train.num_examples//batch_size):batch_x, batch_y = mnist.train.next_batch(batch_size)sess.run(optimizer, feed_dict={x: batch_x, y: batch_y, keep_prob: dropout})# 計算一個batch的損失和準確性loss = sess.run(cost, feed_dict={x: batch_x, y: batch_y, keep_prob: 1.})valid_acc = sess.run(accuracy, feed_dict={x: mnist.validation.images[:test_valid_size],y: mnist.validation.labels[:test_valid_size],keep_prob: 1.})print('Epoch {:>2}, Batch {:>3} - Loss: {:>10.4f} Validation Accuracy: {:.6f}'.format(epoch + 1,batch + 1,loss,valid_acc))# 計算測試精度test_acc = sess.run(accuracy, feed_dict={x: mnist.test.images[:test_valid_size],y: mnist.test.labels[:test_valid_size],keep_prob: 1.})print('Testing Accuracy: {}'.format(test_acc))
輸出如下:
Epoch 1, Batch 1 - Loss: 42201.4062 Validation Accuracy: 0.113281
Epoch 1, Batch 2 - Loss: 38468.5820 Validation Accuracy: 0.125000
...
Epoch 10, Batch 428 - Loss: 177.6433 Validation Accuracy: 0.835938
Epoch 10, Batch 429 - Loss: 194.3704 Validation Accuracy: 0.835938
Testing Accuracy: 0.84765625
其中:
# gpu參數
gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.333)
...
with tf.Session(config=tf.ConfigProto(gpu_options=gpu_options)) as sess:
...
這兩行是因為我使用了gpu訓練模型才加上的,如果不使用gpu可以不加。
在下個筆記將介紹Fashion-MNIST,將使用卷積神經網絡對十種不同得服飾進行區分。
總結
以上是生活随笔為你收集整理的Udacity机器人软件工程师课程笔记(二十七) - 卷积神经网络(CNN)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Udacity机器人软件工程师课程笔记(
- 下一篇: Udacity机器人软件工程师课程笔记(