文章目錄
-
- AlexNet實作
- VGG16-Keras實作
AlexNet實作
在2012年ImageNet競賽中,來自多倫多大學的Hinton教授帶着他的學生Alex Krizhevsky驚豔了世界,他們建構的神經網絡模型以超過第二名10.9個百分點的絕對優勢一舉奪冠。 而這種新型網絡結構以第一作者Alex名字進行命名,被稱為AlexNet。那麼為什麼AlexNet在那個時候會有如此巨大的優勢呢?
AlexNet中包含了幾個比較新的技術點,也首次在CNN中成功應用了ReLU、Dropout和LRN等Trick。同時AlexNet也使用了GPU進行運算加速。
AlexNet将LeNet的思想發揚光大,把CNN的基本原理應用到了很深很寬的網絡中。AlexNet主要使用到的新技術點如下:
(1)成功使用ReLU作為CNN的激活函數,并驗證其效果在較深的網絡超過了Sigmoid,成功解決了Sigmoid在網絡較深時的梯度彌散問題。雖然ReLU激活函數在很久之前就被提出了,但是直到AlexNet的出現才将其發揚光大。
(2)訓練時使用Dropout随機忽略一部分神經元,以避免模型過拟合。Dropout雖有單獨的論文論述,但是AlexNet将其實用化,通過實踐證明了它的效果。在AlexNet中主要是最後幾個全連接配接層使用了Dropout。
(3)在CNN中使用重疊的最大池化。此前CNN中普遍使用平均池化,AlexNet全部使用最大池化,避免平均池化的模糊化效果。并且AlexNet中提出讓步長比池化核的尺寸小,這樣池化層的輸出之間會有重疊和覆寫,提升了特征的豐富性。
(4)提出了LRN層,對局部神經元的活動建立競争機制,使得其中響應比較大的值變得相對更大,并抑制其他回報較小的神經元,增強了模型的泛化能力。
(5)使用CUDA加速深度卷積網絡的訓練,利用GPU強大的并行計算能力,處理神經網絡訓練時大量的矩陣運算。AlexNet使用了兩塊GTX 580 GPU進行訓練,單個GTX 580隻有3GB顯存,這限制了可訓練的網絡的最大規模。是以作者将AlexNet分布在兩個GPU上,在每個GPU的顯存中儲存一半的神經元的參數。因為GPU之間通信友善,可以互相通路顯存,而不需要通過主機記憶體,是以同時使用多塊GPU也是非常高效的。同時,AlexNet的設計讓GPU之間的通信隻在網絡的某些層進行,控制了通信的性能損耗。
(6)資料增強,随機地從256256的原始圖像中截取224224大小的區域(以及水準翻轉的鏡像),相當于增加了2*(256-224)^2=2048倍的資料量。如果沒有資料增強,僅靠原始的資料量,參數衆多的CNN會陷入過拟合中,使用了資料增強後可以大大減輕過拟合,提升泛化能力。進行預測時,則是取圖檔的四個角加中間共5個位置,并進行左右翻轉,一共獲得10張圖檔,對他們進行預測并對10次結果求均值。同時,AlexNet論文中提到了會對圖像的RGB資料進行PCA處理,并對主成分做一個标準差為0.1的高斯擾動,增加一些噪聲,這個Trick可以讓錯誤率再下降1%。
tf實作
# -*- coding=UTF-8 -*-
import tensorflow as tf
# 輸入資料
import input_data
mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)
# 定義網絡超參數
learning_rate = 0.001
training_iters = 200000
batch_size = 64
display_step = 20
# 定義網絡參數
n_input = 784 # 輸入的次元
n_classes = 10 # 标簽的次元
dropout = 0.8 # Dropout 的機率
# 占位符輸入
x = tf.placeholder(tf.types.float32, [None, n_input])
y = tf.placeholder(tf.types.float32, [None, n_classes])
keep_prob = tf.placeholder(tf.types.float32)
# 卷積操作
def conv2d(name, l_input, w, b):
return tf.nn.relu(tf.nn.bias_add( \
tf.nn.conv2d(l_input, w, strides=[1, 1, 1, 1], padding='SAME'),b) \
, name=name)
# 最大下采樣操作
def max_pool(name, l_input, k):
return tf.nn.max_pool(l_input, ksize=[1, k, k, 1], \
strides=[1, k, k, 1], padding='SAME', name=name)
# 歸一化操作
def norm(name, l_input, lsize=4):
return tf.nn.lrn(l_input, lsize, bias=1.0, alpha=0.001 / 9.0, beta=0.75, name=name)
# 定義整個網絡
def alex_net(_X, _weights, _biases, _dropout):
_X = tf.reshape(_X, shape=[-1, 28, 28, 1]) # 向量轉為矩陣
# 卷積層
conv1 = conv2d('conv1', _X, _weights['wc1'], _biases['bc1'])
# 下采樣層
pool1 = max_pool('pool1', conv1, k=2)
# 歸一化層
norm1 = norm('norm1', pool1, lsize=4)
# Dropout
norm1 = tf.nn.dropout(norm1, _dropout)
# 卷積
conv2 = conv2d('conv2', norm1, _weights['wc2'], _biases['bc2'])
# 下采樣
pool2 = max_pool('pool2', conv2, k=2)
# 歸一化
norm2 = norm('norm2', pool2, lsize=4)
# Dropout
norm2 = tf.nn.dropout(norm2, _dropout)
# 卷積
conv3 = conv2d('conv3', norm2, _weights['wc3'], _biases['bc3'])
# 下采樣
pool3 = max_pool('pool3', conv3, k=2)
# 歸一化
norm3 = norm('norm3', pool3, lsize=4)
# Dropout
norm3 = tf.nn.dropout(norm3, _dropout)
# 全連接配接層,先把特征圖轉為向量
dense1 = tf.reshape(norm3, [-1, _weights['wd1'].get_shape().as_list()[0]])
dense1 = tf.nn.relu(tf.matmul(dense1, _weights['wd1']) + _biases['bd1'], name='fc1')
# 全連接配接層
dense2 = tf.nn.relu(tf.matmul(dense1, _weights['wd2']) + _biases['bd2'], name='fc2')
# Relu activation
# 網絡輸出層
out = tf.matmul(dense2, _weights['out']) + _biases['out']
return out
# 存儲所有的網絡參數
weights = {
'wc1': tf.Variable(tf.random_normal([3, 3, 1, 64])),
'wc2': tf.Variable(tf.random_normal([3, 3, 64, 128])),
'wc3': tf.Variable(tf.random_normal([3, 3, 128, 256])),
'wd1': tf.Variable(tf.random_normal([4*4*256, 1024])),
'wd2': tf.Variable(tf.random_normal([1024, 1024])),
'out': tf.Variable(tf.random_normal([1024, 10]))
}
biases = {
'bc1': tf.Variable(tf.random_normal([64])),
'bc2': tf.Variable(tf.random_normal([128])),
'bc3': tf.Variable(tf.random_normal([256])),
'bd1': tf.Variable(tf.random_normal([1024])),
'bd2': tf.Variable(tf.random_normal([1024])),
'out': tf.Variable(tf.random_normal([n_classes]))
}
# 構模組化型
pred = alex_net(x, weights, biases, keep_prob)
# 定義損失函數和學習步驟
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(pred, y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
# 測試網絡
correct_pred = tf.equal(tf.argmax(pred,1), tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
# 初始化所有的共享變量
init = tf.initialize_all_variables()
# 開啟一個訓練
with tf.Session() as sess:
sess.run(init)
step = 1
# Keep training until reach max iterations
while step * batch_size < training_iters:
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
# 擷取批資料
sess.run(optimizer, feed_dict={x: batch_xs, y: batch_ys, keep_prob: dropout})
if step % display_step == 0:
# 計算精度
acc = sess.run(accuracy, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 1.})
# 計算損失值
loss = sess.run(cost, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 1.})
print "Iter " + str(step*batch_size) + ", Minibatch Loss= " + "{:.6f}".format(loss) + ", Training Accuracy= " + "{:.5f}".format(acc)
step += 1
print "Optimization Finished!"
# 計算測試精度
print "Testing Accuracy:", sess.run(accuracy, feed_dict={x: mnist.test.images[:256], y: mnist.test.labels[:256], keep_prob: 1.})
# 以上代碼忽略了部分卷積層,全連接配接層使用了特定的權重。
keras實作
輸入圖檔大小為 227*227*3
1. 卷積池化層
卷積:96個卷積核,11*11*3的卷積核規模 步長為4 卷積後的特征矩陣規模為55*55*96 不填充
池化:最大池化層,3 * 3池化規模 2步長 池化後的特征矩陣規模為27*27*96
2. 卷積池化層
卷積:256個卷積核,5 * 5 * 96的卷積核規模 步長為1 填充為2 卷積後的特征矩陣規模為256*27*27
池化:最大池化層,3 * 3池化規模 2步長 池化後的特征矩陣規模為13*13*256
3. 卷積層
卷積:384個卷積核,3*3*256的卷積核規模 步長為1 填充為1 卷積後的特征矩陣規模為13*13*384
4. 卷積層
卷積:384個卷積核,3*3*384的卷積核規模 步長為1 填充為1 卷積後的特征矩陣規模為13*13*384
5. 卷積池化層
卷積:256個卷積核,3*3*384的卷積核規模 步長為1 填充為2 卷積後的特征矩陣規模為256*13*13
池化:最大池化層,3 * 3池化規模 2步長 池化後的特征矩陣規模為6*6*256
6. 全連接配接層
4096個神經元,6*6*256的卷積核規模 步長為1 不填充 卷積後的特征矩陣規模為1*1*4096
7. 全連接配接層
卷積:4096個神經元,1*1*4096的卷積核規模 步長為1 不填充 卷積後的特征矩陣規模為1*1*4096
8. 全連接配接層
1000個神經元,與fc7層全連接配接,經過softmax輸出1000個值
from keras.models import Sequential
from keras.layers import Dense, Flatten, Dropout,Activation
from keras.layers.convolutional import Conv2D, MaxPooling2D
# AlexNet
model = Sequential()
# 第一層
model.add(Conv2D(filters=96, kernel_size=(11, 11),
strides=(4, 4), padding='valid',
input_shape=(227, 227, 3),
activation='relu'))
model.add(MaxPooling2D(pool_size=(3, 3),
strides=(2, 2),
padding='valid'))
# 第二層
model.add(Conv2D(filters=256, kernel_size=(5, 5),
strides=(1, 1), padding='same',
activation='relu'))
model.add(MaxPooling2D(pool_size=(3, 3),
strides=(2, 2),
padding='valid'))
# 第三層
model.add(Conv2D(filters=384, kernel_size=(3, 3),
strides=(1, 1), padding='same',
activation='relu'))
# 第四層
model.add(Conv2D(filters=384, kernel_size=(3, 3),
strides=(1, 1), padding='same',
activation='relu'))
# 第五層
model.add(Conv2D(filters=256, kernel_size=(3, 3),
strides=(1, 1), padding='same',
activation='relu'))
model.add(MaxPooling2D(pool_size=(3, 3),
strides=(2, 2), padding='valid'))
# 第六段
model.add(Flatten())
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
# 第七層
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
# 第八層
model.add(Dense(1000, activation='relu'))
model.add(Dropout(0.5))
# Output Layer
model.add(Dense(2))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy',
optimizer='sgd',
metrics=['accuracy'])
VGG16-Keras實作
VGG16有13個卷積層,3個全連接配接層,5個池化層,由于卷積層和全連接配接層具有權重系數,
是以也被稱為權重層,一共16個權重層,因而得名
VGG16的突出特點是簡單,展現在:
-
卷積層均采用相同的卷積核參數
卷積層均表示為conv3-XXX,其中conv3說明該卷積層采用的卷積核的尺寸是3,
即寬(width)和高(height)均為3,3*3是很小的卷積核尺寸,結合其它參數
(步幅stride=1,填充方式padding=same),這樣就能夠使得每一個卷積層(張量)與前一層
(張量)保持相同的寬和高。XXX代表卷積層的通道數。
-
池化層均采用相同的池化核參數
池化層的參數均為2×
-
模型是由若幹卷積層和池化層堆疊(stack)的方式構成,
比較容易形成較深的網絡結構(在2014年,16層已經被認為很深了)
VGG16輸入224*224*3的圖檔
VGG16的卷積和池化共分為5塊
1. 兩個卷積一個池化
卷積:64個卷積核 3*3的大小 填充方式為 padding=same 步長為1
池化:2*2的規模 步長為2
2. 兩個卷積一個池化
卷積:128個卷積核 3*3的大小 填充方式為 padding=same 步長為1
池化:2*2的規模 步長為2
3. 三個卷積一個池化
卷積:256個卷積核 3*3的大小 填充方式為 padding=same 步長為1
池化:2*2的規模 步長為2
4. 三個卷積一個池化
卷積:512個卷積核 3*3的大小 填充方式為 padding=same 步長為1
池化:2*2的規模 步長為2
5. 三個卷積一個池化
卷積:512個卷積核 3*3的大小 填充方式為 padding=same 步長為1
池化:2*2的規模 步長為2
全連接配接層
1. 4096個神經元
1. 4096個神經元
1. 1000個神經元 激活函數為softmax
from keras import Sequential
from keras.layers import Dense, Activation, Conv2D, MaxPooling2D, Flatten, Dropout
from keras.layers import Input
from keras.optimizers import SGD
model = Sequential()
# BLOCK 1
model.add(Conv2D(filters = 64, kernel_size = (3, 3), activation = 'relu', padding = 'same', name = 'block1_conv1', input_shape = (224, 224, 3)))
model.add(Conv2D(filters = 64, kernel_size = (3, 3), activation = 'relu', padding = 'same', name = 'block1_conv2'))
model.add(MaxPooling2D(pool_size = (2, 2), strides = (2, 2), name = 'block1_pool'))
# BLOCK2
model.add(Conv2D(filters = 128, kernel_size = (3, 3), activation = 'relu', padding = 'same', name = 'block2_conv1'))
model.add(Conv2D(filters = 128, kernel_size = (3, 3), activation = 'relu', padding = 'same', name = 'block2_conv2'))
model.add(MaxPooling2D(pool_size = (2, 2), strides = (2, 2), name = 'block2_pool'))
# BLOCK3
model.add(Conv2D(filters = 256, kernel_size = (3, 3), activation = 'relu', padding = 'same', name = 'block3_conv1'))
model.add(Conv2D(filters = 256, kernel_size = (3, 3), activation = 'relu', padding = 'same', name = 'block3_conv2'))
model.add(Conv2D(filters = 256, kernel_size = (3, 3), activation = 'relu', padding = 'same', name = 'block3_conv3'))
model.add(MaxPooling2D(pool_size = (2, 2), strides = (2, 2), name = 'block3_pool'))
# BLOCK4
model.add(Conv2D(filters = 512, kernel_size = (3, 3), activation = 'relu', padding = 'same', name = 'block4_conv1'))
model.add(Conv2D(filters = 512, kernel_size = (3, 3), activation = 'relu', padding = 'same', name = 'block4_conv2'))
model.add(Conv2D(filters = 512, kernel_size = (3, 3), activation = 'relu', padding = 'same', name = 'block4_conv3'))
model.add(MaxPooling2D(pool_size = (2, 2), strides = (2, 2), name = 'block4_pool'))
# BLOCK5
model.add(Conv2D(filters = 512, kernel_size = (3, 3), activation = 'relu', padding = 'same', name = 'block5_conv1'))
model.add(Conv2D(filters = 512, kernel_size = (3, 3), activation = 'relu', padding = 'same', name = 'block5_conv2'))
model.add(Conv2D(filters = 512, kernel_size = (3, 3), activation = 'relu', padding = 'same', name = 'block5_conv3'))
model.add(MaxPooling2D(pool_size = (2, 2), strides = (2, 2), name = 'block5_pool'))
model.add(Flatten())
model.add(Dense(4096, activation = 'relu', name = 'fc1'))
model.add(Dropout(0.5))
model.add(Dense(4096, activation = 'relu', name = 'fc2'))
model.add(Dropout(0.5))
model.add(Dense(1000, activation = 'softmax', name = 'prediction'))
Keras還自帶了一個VGG16的模型
import keras
model = keras.applications.vgg16.VGG16(include_top=True, weights='imagenet', input_tensor=None, input_shape=None, pooling=None, classes=1000)