深度學習 之七 【卷積神經網路 CNN】
1.CNN的應用
-
- 如果你能訓練人工智慧機器人唱歌,幹嘛還訓練它聊天?在 2017 年 4 月,研究人員使用 WaveNet 模型的變體生成了歌曲。原始論文和演示可以在 此處 找到。
瞭解 Facebook 的 創新 CNN 方法(Facebook) ,該方法專門用於解決語言翻譯任務,準確率達到了前沿性水平,並且速度是 RNN 模型的 9 倍。
利用 CNN 和強化學習玩 Atari 遊戲。你可以下載 此論文附帶的程式碼。
- 如果你想研究一些(深度強化學習)初學者程式碼,建議你參閱 Andrej Karpathy 的帖子。
-
- 閱讀 這篇文章,其中提出了一個問題:如果掌控 Go“需要人類直覺”,那麼人性受到挑戰是什麼感覺?
觀看這些非常酷的視訊,其中的無人機都受到 CNN 的支援。
如果你對無人駕駛汽車使用的 CNN 感興趣,請參閱:
- 我們的機器學習工程師納米學位課程,我們在此專案中對街景門牌號資料集中的門牌號進行分類。
- 這些系列部落格,其中詳細講述瞭如何訓練用 Python 編寫的 CNN,以便生成能夠玩“俠盜獵車手”的無人駕駛 AI。
參閱視訊中沒有提到的其他應用情形。
2.MLP和CNN的區別
MLP(多層感知器)
- 1.將輸入的矩陣轉換成向量,然後送到隱藏層,這樣會丟失一些二維資訊
- 2.對於一些小的圖片,比如:28px * 28px,想要達到好的預測效果,引數都超過60萬了
如圖:4 x 4的矩陣轉換成16維向量後,當做輸入傳遞給MLP,這是具有一個隱藏層(四個節點)的MLP,輸出層有10個節點,輸出時有一個softmax函式,返回一個十維的向量,包含圖片描述的0到9的數字的可能概率。
CNN(卷積神經網路)
- 1.接受矩陣作為輸入,不需要將矩陣轉換成向量
- 2.每一個輸入,並不需要與所有的節點相關聯
- 3.通過指定過濾器
數量
和大小
,控制卷積層
的行為
卷積神經網路
特徵對映要求,輸入的圖片格式一樣大小的。
區域性連線層
- 1.包含更少的權重,
- 2.區域性相連,節點僅與上一層中的小部分節點相連
- 3.空間內共享引數
全連線層 Dense(密集層)
- 1.每個節點與前一層中的每個節點相連
卷積窗(過濾器),stride為1的移動動畫
Keras的卷積層使用方法
Conv2D(filters, kernel_size, strides, padding, activation='relu', input_shape)
引數
必須傳遞以下引數:
- filters
- 過濾器數量。
- kernel_size
- 指定(方形)卷積視窗的高和寬的數字。
你可能還需要調整其他可選引數:
- strides
- 卷積 stride。如果不指定任何值,則 strides 設為 1。
- padding
- 選項包括 ‘valid’ 和 ‘same’。如果不指定任何值,則 padding 設為 ‘valid’。
- activation
- 通常為 ‘relu’。如果未指定任何值,則不應用任何啟用函式。強烈建議你向網路中的每個卷積層新增一個 ReLU 啟用函式。
- input_shape
- 指定輸入的高度、寬度和深度(按此順序)的元組。
注意:可以將 kernel_size
和 strides
表示為數字或元組。
注意:如果卷積層不是網路的第一個層級,請勿包含 input_shape
引數。
更多詳細引數使用案例,查閱官方文件
公式:卷積層中的引數數量
卷積層中的引數數量取決於 filters
、kernel_size
和 input_shape
的值。
K
- 卷積層中的過濾器數量F
- 卷積過濾器的高度和寬度D_in
- 上一層級的深度
注意:K = filters
,F = kernel_size
。類似地,D_in
是 input_shape
元祖中的最後一個值。
因為每個過濾器有 F*F*D_in
個權重,卷積層由 K 個過濾器組成,因此卷積層中的權重總數是 K*F*F*D_in
。
因為每個過濾器有 1
個偏差項,卷積層有 K
個偏差。因此,卷積層中的引數數量是 K*F*F*D_in + K
。
公式:卷積層的形狀
卷積層的形狀取決於 kernel_size
、input_shape
、padding
和 stride
的值。我們定義幾個變數:
K
- 卷積層中的過濾器數量F
- 卷積過濾器的高度和寬度H_in
- 上一層級的高度W_in
- 上一層級的寬度
注意:K = filters
、F = kernel_size
,以及S = stride
。類似地,H_in
和 W_in
分別是 input_shape
元祖的第一個和第二個值。
卷積層的深度始終為過濾器
數量 K
。
如果 padding
= ‘same’,那麼卷積層的空間維度如下:
- height = ceil(float(
H_in
) / float(S
)) - width = ceil(float(
W_in
) / float(S
))
如果 padding
= ‘valid’,那麼卷積層的空間維度如下:
- height = ceil(float(
H_in
- F + 1) / float(S
)) - width = ceil(float(
W_in
- F + 1) / float(S
))
池化層
池化層總是將卷積層作為輸入。
卷積層是指特徵對映堆疊
,每個過濾器對應一個特徵對映,且負責從圖片中查詢一種規律
過濾器越多,堆疊越大,維度就越高,引數也會更多,這就很可能導致過擬合,因此我們需要降低維度,這就是池化層在卷積神經網路中扮演的角色。
最大池化層 Max Pooling Layer
就是在卷積窗(過濾器)在移動過程中,每次取最大的那個數,這樣就一次一次的卷積運算,都會將維度,降低矩陣大小,如下圖
每個特徵對映的寬和高都減小了,如下圖
程式碼例項:
from keras.models import Sequential
from keras.layers import MaxPooling2D
model = Sequential()
model.add(MaxPooling2D(pool_size=2, strides=2, input_shape=(100, 100, 15)))
model.summary()
引數
你必須包含以下引數:
- pool_size
- 指定池化視窗高度和寬度的數字。
你可能還需要調整其他可選引數:
- strides
- 垂直和水平 stride
。如果不指定任何值,則 strides
預設為 pool_size
。
- padding
- 選項包括 'valid'
和 'same'
。如果不指定任何值,則 padding
設為 'valid'
。
注意:可以將 pool_size
和 strides
表示為數字或元組。
此外,建議閱讀 官方文件。
舉栗子:
假設我要構建一個 CNN
,並且我想通過在卷積層後面新增最大池化層
,降低卷積層的維度。假設卷積層的大小是 (100, 100, 15)
,我希望最大池化層的大小為 (50, 50, 15)
。要實現這一點,我可以在最大池化層中使用 2x2
視窗,stride
設為 2
,程式碼如下:
MaxPooling2D(pool_size=2, strides=2)
當然了,stride
也可以為 1
。
全域性平均池化 Global Average Pooling Layer
全域性平均池化,既不指定卷積窗(kernel_size
)大小,也不指定 stride
,這是一種更極端的降低維度的池化型別,過程是:
- 1.它獲得了一堆的特徵對映
- 2.並計算每個對映的節點均值(均值,就是先對所有的節點值求和,然後除以總節點數)
- 3.這樣的話,每個特徵對映都縮減成了一個值
最後全域性平均池化就將一個三維的陣列轉變成了一個向量
瞭解不同型別的池化層,請參閱該 Keras 文件
第一個CNN的架構介紹
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense
model = Sequential()
model.add(Conv2D(filters=16, kernel_size=2, padding='same', activation='relu', input_shape=(32, 32, 3)))
model.add(MaxPooling2D(pool_size=2))
model.add(Conv2D(filters=32, kernel_size=2, padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Conv2D(filters=64, kernel_size=2, padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Flatten())
model.add(Dense(500, activation='relu'))
model.add(Dense(10, activation='softmax'))
注意事項
- 始終向 CNN 中的
Conv2D
層新增ReLU
啟用函式。但是網路的最後層級除外,密集層也應該具有ReLU
啟用函式。 - 在構建分類網路時,網路中的最後層級應該是具有
softmax
啟用函式的 密集層。最後層級的節點數量
應該等於資料集中的類別總數
。 - 建議參閱 Andrej Karpathy 的 tumblr(來自外網,可能打不開),其中包含了使用者提交的損失函式,對應的是本身有問題的模型。損失函式在訓練期間應該是減小的,但是這些圖表顯示的卻是非常不同的行為 :)。
增強圖片的訓練
為什麼要增強圖片的訓練?這是因為圖片資料集中的預測目標物件不可能都是在正中間,有的圖片傾斜著,有的圖片歪倒著,這就需要我們來生成一些位置不一樣的同類圖片,加入到訓練集中。
- Rotation Invariance 旋轉不變性
- Translation Invariance 平移不變性
這就需要用到 ImageDataGenerator
類
from keras.preprocessing.image import ImageDataGenerator
# create and configure augmented image generator
datagen_train = ImageDataGenerator(
width_shift_range=0.1, # randomly shift images horizontally (10% of total width)
height_shift_range=0.1, # randomly shift images vertically (10% of total height)
horizontal_flip=True) # randomly flip images horizontally
# fit augmented image generator on data
datagen_train.fit(x_train)
訓練時,將 fit()
函式換成了 fit_generator()
from keras.callbacks import ModelCheckpoint
batch_size = 32
epochs = 100
# train the model
checkpointer = ModelCheckpoint(filepath='aug_model.weights.best.hdf5', verbose=1,
save_best_only=True)
model.fit_generator(datagen_train.flow(x_train, y_train, batch_size=batch_size),
steps_per_epoch=x_train.shape[0] // batch_size,
epochs=epochs, verbose=2, callbacks=[checkpointer],
validation_data=(x_valid, y_valid),
validation_steps=x_valid.shape[0] // batch_size)
在 Keras 中,封裝了一些著名的 CNN 預訓練模型
- Xception
- VGG16
- VGG19
- ResNet50
- InceptionV3
- InceptionResNetV2
- MobileNet
- DenseNet
- NASNet
視覺化CNN
這是另一個 CNN 視覺化工具的 演示。如果你想詳細瞭解這些視覺化圖表是如何製作的,請觀看此視訊。
這是另一個可與 Keras 和 Tensorflow 中的 CNN 無縫合作的視覺化工具。
閱讀這篇視覺化 CNN 如何看待這個世界的 Keras 博文。在此博文中,你會找到 Deep Dreams 的簡單介紹,以及在 Keras 中自己編寫 Deep Dreams 的程式碼。閱讀了這篇博文後:
再觀看這個利用 Deep Dreams 的 音樂視訊(注意 3:15-3:40 部分)!
使用這個網站建立自己的 Deep Dreams(不用編寫任何程式碼!)。
如果你想詳細瞭解 CNN 的解釋
這篇文章詳細講解了在現實生活中使用深度學習模型(暫時無法解釋)的一些危險性。
這一領域有很多熱點研究。這些作者最近朝著正確的方向邁出了一步。
假如有一個三層的Conv2d的網路模型,那麼它發現規律的流程是
- 第一層級將檢測圖片中的邊緣
- 第二層級將檢測形狀
- 第三個卷積層將檢測更高階的特徵
著名卷積網路
- AlexNet
TensorFlow 的CNN
上述程式碼用了 tf.nn.conv2d()
函式來計算卷積,weights
作為濾波器,[1, 2, 2, 1]
作為 strides
。TensorFlow 對每一個 input 維度使用一個單獨的 stride 引數,[batch
, input_height
, input_width
, input_channels
]。我們通常把 batch
和 input_channels
(strides 序列中的第一個第四個)的 stride
設為 1
。
你可以專注於修改 input_height
和 input_width
, batch
和 input_channels
都設定成 1
。input_height
和 input_width
strides
表示濾波器在input 上移動的步長
。上述例子中,在 input 之後,設定了一個 5x5
,stride
為 2
的濾波器。
# Output depth
k_output = 64
# Image Properties
image_width = 10
image_height = 10
color_channels = 3
# Convolution filter
filter_size_width = 5
filter_size_height = 5
# Input/Image
input = tf.placeholder(
tf.float32,
shape=[None, image_height, image_width, color_channels])
# Weight and bias
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)
ksize
和 strides
引數也被構建為四個元素的列表,每個元素對應 input tensor
的一個維度 ([batch
, height
, width
, channels
]),對 ksize
和 strides
來說,batch
和 channel
通常都設定成 1
。
conv_layer = tf.nn.max_pool(
conv_layer,
ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1],
padding='SAME')
池化層總的來說,目的是:減小輸入大小,降低過擬合,相應的引數也減少了很多。
但是,近期,池化層並不是很受青睞,部分原因是:
- 現在的資料集又大又複雜,我們更關心欠擬合問題
- dropout是一種更好的正則化方法
- 池化導致資訊丟失,想象最大池化的例子,n個數字當中,我們只保留最大的,把剩餘的n-1完全捨棄了。
小練習
最大池化層練習 Max Pooling Layer
假如輸入資料是
[[[0, 1, 0.5, 10],
[2, 2.5, 1, -8],
[4, 0, 5, 6],
[15, 1, 2, 3]]]
filter_size=(2, 2), strides=(2, 2), 輸出維度: 2x2x1,那麼經過最大池化層後的結果是:
[2.5,10,15,6]
計算方式
max(0, 1, 2, 2.5) = 2.5
max(0.5, 10, 1, -8) = 10
max(4, 0, 15, 1) = 15
max(5, 6, 2, 3) = 6
全域性平均池化 Global Average Layer
假如輸入資料是:
[[[0, 1, 0.5, 10],
[2, 2.5, 1, -8],
[4, 0, 5, 6],
[15, 1, 2, 3]]]
filter_size=(2, 2), strides=(2, 2), 輸出維度: 2x2x1,那麼經過全域性平均池化層後的結果是:
1.375,0.875,5.0,4.0
計算方式是:
mean(0, 1, 2, 2.5) = 1.375
mean(0.5, 10, 1, -8) = 0.875
mean(4, 0, 15, 1) = 5
mean(5, 6, 2, 3) = 4
1x1卷積
- 1.
1x1
卷積會增加引數數量 - 2.它關注的是一個畫素,而不是一塊影象
- 3.相較於池化,它更高效
Inception 模組
通過1x1的卷積輸出給 1x1、3x3、5x5的卷積後,再將他們組合起來輸出,看起來有點複雜,但是要比簡單的卷積好的多
類似如下圖
測試專案 【使用TensorFlow的CNN來訓練資料】
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
import math
# 1.匯入MNIST資料集
mnist = input_data.read_data_sets(".", one_hot=True, reshape=False)
# 2.設定一些引數
learning_rate = 0.00001
epochs = 10
batch_size = 128
test_valid_size = 256
n_classes = 10
dropout = 0.75
# weights = [filter_size_height, filter_size_width, color_channels, k_output]
weights = {
'wc1': tf.Variable(tf.random_normal([28, 28, 1, 32])),
'wc2': tf.Variable(tf.random_normal([14, 14, 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]))
}
# 3.建立卷積函式
def conv2d(x, W, b, strides=1):
# strides = [batch, input_height, input_width, input_channels]
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*32
conv1 = conv2d(x, weights['wc1'], biases['bc1']) # 28*28*1
conv1 = maxpool2d(conv1, k=2) # 14*14*32
# Layer 2 - 14*14*32 to 7*7*64
conv2 = conv2d(conv1, weights['wc2'], biases['bc2']) # 14*14*32
conv2 = maxpool2d(conv2, k=2) # 7*7*64
fc1 = 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)
out = tf.add(tf.matmul(fc1, weights['out']), biases['out'])
return out
# inputs = [bach_size, image_height, image_width, color_channels]
x = tf.placeholder(tf.float32, [None, 28, 28, 1])
y = tf.placeholder(tf.float32, [None, n_classes])
# 留存率 訓練時=0.5,驗證和測試時=1.0
keep_prob = tf.placeholder(tf.float32)
# 計算輸出層的線性啟用函式 logit score
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))
# 4.訓練網路
with tf.Session() as session:
# 初始化tensorflow的所有變數
session.run(tf.global_variables_initializer())
# 迭代 epoch 次
for epoch in range(epochs):
# 計算需要多少個 batch
batches = int(math.ceil(mnist.train.num_examples // batch_size))
# 每次 epoch,需要訓練分類器 batches 次
for batch in range(batches):
# 獲取下一個批次的訓練資料
batch_x, batch_y = mnist.train.next_batch(batch_size)
# 訓練資料
session.run(optimizer, feed_dict={x: batch_x, y: batch_y, keep_prob: dropout})
# 計算訓練誤差值
loss = session.run(cost, feed_dict={x: batch_x, y: batch_y, keep_prob: 1.})
# 計算訓練精準度
vali_acc = session.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={:.4f} Validation Accuracy={:.6f}"
.format(epoch+1, batch+1, loss, vali_acc))
# 最後訓練完後,根據測試資料集得出測試精準度
test_acc = session.run(accuracy, feed_dict={x: mnist.test.images[:test_valid_size], y: mnist.test.labels[:test_valid_size], keep_prob: 1.})
print("Test Accuracy: {}".format(test_acc))
test accuracy是:
Test Accuracy: 0.9609375
課外閱讀
Andrej Karpathy’s CS231n Stanford course on Convolutional Neural Networks.
Michael Nielsen’s free book on Deep Learning.
Goodfellow, Bengio, and Courville’s more advanced free book on Deep Learning.