本文僅作爲以後複習方便查閱,其中主要分以下兩個方面介紹原論文閱讀和代碼復現,代碼復現主要借鑑大神的倉庫,現放論文和倉庫地址如下:
AlexNet論文:AlexNet原論文地址
AlexNet tensorflow實現的源碼:tensorflow實現AlexNet網絡模型
原論文作者主要有以下幾個方面的貢獻:
1.使用Relu激活函數代替tanh和sigmoid,能夠加快模型的收斂速度
2.使用雙層結構,利用GPU 並行機制,加快訓練速度
3.提出LRN,局部歸一化
4.重疊池化
5.數據增強主要爲圖像增強和Dropout
一 Relu激活函數
一般神經網絡的激活函數會選擇sigmoid和tanh,論文作者通過實驗證明,在實現相同的training error rate的過程中Relu要比tanh快的多。
圖中展示了對於一個特定的四層CNN,CIFAR-10數據集訓練中誤差率達到25%所需要的迭代次數。使用Relu做激活函數只需迭代6次,而 tanh需要迭代36次才能達到。
二 GPU並行機制
論文作者使用的GTX 580 GPU只要3GB的內存,不能實現大規模的神經網絡的訓練。因此作者使用兩塊GTX GPU採用並行的方式對AlexNet網絡進行訓練,這樣的好處是並行的網絡不要經過主機內存就可以實現相互多寫,加快了速度。作者通過證實採用並行機制能夠降低top-1和top-5分別達到1.7%和1.2%。
三 RPN局部響應歸一化
Relu本來是不需要隊輸入進行標準化,但本文發現進行局部標準化能夠提升性能。局部歸一化的公式爲:
其中a表示feature map中第i個卷積核(x, y)經過Relu激活函數之後的輸出,n表示相鄰的幾個卷積核。N表示這一層卷積核的數量。
通過RPN局部歸一化作者可以降低top-1和top-5分別爲1.4%和1.2%。
四 重疊池化
正常池化是步長s=2和窗口z=2 重疊池化步長s=2窗口z=3,採用重疊池化可以減少top-1和top-5分別爲0.4%和0.3%。重疊池化可以避免過擬合。
五 網絡結構
Alexnet 主要由五個卷積層和三個全連層組成。最後的全連接層的輸出被送到1000維的softmax函數,其產生1000個預測類。
總體描述如下:
1. Alexnet爲8層網絡結構,其中前五層爲卷積層,後面三層爲全連接層。學習參數6000萬個,神經元個數650000個。
2. AlexNet在兩個GPU上面並行。
3. ALexNet在第2,4,5層均是前一層自己GPU內連接,第3層與前面兩層全連接,全連接是2個GPU全連接。
4. RPN層是在第1,2層卷積之後。
5.pooling層是在RPN層和第5卷積層之後。
6.Relu是在每個卷積層和全連接層後。
7.每層卷積核大小如下圖所示:參考鏈接爲:https://www.learnopencv.com/understanding-alexnet/
使用caffe畫出的網絡圖:http://ethereon.github.io/netscope/#/editor
卷積層輸出計算:
使用VALID填充的:Wout = (Win - F) / S +1
使用SAME填充的:Wout =(Win + 2P - F) / S + 1
池化層計算:
使用VALID填充:Wout = (Win - F) / S + 1
使用SAME填充:Wout = (Win + 2P - F) / S +1
參數計算:
卷積核大小爲K×K,通道數爲M,卷積核數量爲N,biases數量等於卷積核的數量N
參數:K × K × M × N + N
AlexNet各層卷積結果及參數如下表:
layer | input | num | kernel_size/strides | padding | output | parameters |
conv1 | 227*227*3 | 96 | 11*11/4 | VALID | 55*55*96 | 11*11*3*96 + 96 |
norm1 | 55*55*96 | 55*55*96 | ||||
pool1 | 55*55*96 | 3*3/2 | VALID | 27*27*96 | ||
conv2 | 27*27*96 | 256 | 5*5/1 | SAME | 27*27*256 | 5×5×96×256+256 |
norm2 | 27*27*256 | 27*27*256 | ||||
pool2 | 27*27*256 | 3*3/2 | VALID | 13*13*256 | ||
conv3 | 13*13*256 | 384 | 3*3/1 | SAME | 13*13*384 | 3×3×256×384+384 |
conv4 | 13*13*384 | 384 | 3*3/1 | SAME | 13*13*384 | 3×3×384×384+384 |
conv5 | 13*13*384 | 256 | 3*3/1 | SAME | 13*13*256 | 3×3×384×256+256 |
pool3 | 13*13*384 | 3*3/2 | VALID | 6*6*256 | ||
fc6 | 6*6*256 | 1*1*4096 | 6×6×6×4096+4096 | |||
fc7 | 1*1*4096 | 1*1*4096 | 1×1×4096×4096+4096 | |||
fc8 | 1*1*4096 | 1*1*1000 | 1×1×4096×1000+1000 | |||
softmax | 1*1*1000 | 1*1*1000 |
總參數共計:6200萬個
六.數據增強
論文使用的數據增強的第一種方式主要爲平移圖像和水平映射,將256×256圖片隨機截取爲227×227網絡所需要的圖片。
第二種方式爲改變圖片的RGB 通道的灰度,使用PCA抽取特徵。有效增加數據量,減少過擬合現象。
七.Dropout
Dropout是有效的模型集成學習方法,具有0.5的概率將隱藏層神經元設置爲0.運用了這種機制的神經元不會干擾前向傳遞也不影響後續操作。因此當有輸入的時候,神經網絡採樣不用的結構,但是這些結構共享一個權重。這就減少了神經元適應的複雜性。測試時,用0.5的概率隨機失活神經元。dropout減少了過擬合,也使收斂迭代次數增加一倍。失活效果圖如下:
關於dropout的原理更多可以參考如下:https://zhuanlan.zhihu.com/p/23178423
八.源碼復現
網絡層
import numpy as np
import tensorflow as tf
class AlexNet(object):
def __init__(self, x, keep_prob, class_num, skip, model_path='bvlc_alexnet.npy'):
self.x = x
self.keep_pron = keep_prob
self.class_num = class_num
self.skip = skip
self.model_path = model_path
self.buildAlexNet()
def buildAlexNet(self):
# conv1-->relu-->norm1-->pool1 output:55*55*96
conv1 = conv(self.x, 96, 11, 4, 'conv1', 'VALID')
norm1 = local_response_normalization(conv1, 2, 2e-05, 0.75, 'norm1')
pool1 = pooling(norm1, 3, 2, 'pool1', 'VALID') # 27*27*96
# conv2-->relu-->norm2-->pool2 output: 27*27*256
conv2 = conv(pool1, 256, 5, 1, 'conv2', groups=2)
norm2 = local_response_normalization(conv2, 2, 2e-5, 0.75, 'norm2')
pool2 = pooling(norm2, 3, 2, 'pool2', 'VALID') # 13*13*256
# conv3-->relu output: 13*13*384
conv3 = conv(pool2, 384, 3, 1, 'conv3')
# conv4-->relu output: 13*13*384
conv4 = conv(conv3, 384, 3, 1, 'conv4', groups=2)
# conv5-->relu-->pool3 output: 13*13*256
conv5 = conv(conv4, 256, 3, 1, 'conv5', groups=2)
pool3 = pooling(conv5, 3, 2, 'pool3', 'VALID') # 6*6*256
# fc6-->relu-->drop output: 1*1*4096
fc6 = fully_connection(tf.reshape(pool3, [-1, 256 * 6 * 6]), 256 * 6 * 6, 4096, True, 'fc6')
drop6 = dropout(fc6, self.keep_pron, 'drop6')
# fc7-->relu-->drop output: 1*1*4096
fc7 = fully_connection(drop6, 4096, 4096, True, 'fc7')
drop7 = dropout(fc7, self.keep_pron, 'drop7')
# fc8-->relu output: 1*1*1000
self.fc8 = fully_connection(drop7, 4096, self.class_num, False, 'fc8')
# download weights
def load_weights(self, sess):
weigths_path = np.load(self.model_path, encoding='bytes').item()
for name in weigths_path:
if name not in self.skip:
with tf.variable_scope(name, reuse=True):
for data in weigths_path[name]:
if len(data.shape) == 1:
sess.run(tf.get_variable('b', trainable=False).assign(data))
else:
sess.run(tf.get_variable('w', trainable=False).assign(data))
# convolution
def conv(input, filter, kernel_size, stride, name, padding='SAME', groups=1):
channels = int(input.get_shape()[-1])
convolution = lambda a, b: tf.nn.conv2d(a, b, strides=[1, stride, stride, 1], padding=padding)
with tf.variable_scope(name) as scope:
w = tf.get_variable('w', shape=[kernel_size, kernel_size, channels // groups, filter])
b = tf.get_variable('b', shape=[filter])
xNew = tf.split(value=input, num_or_size_splits=groups, axis=3)
wNew = tf.split(value=w, num_or_size_splits=groups, axis=3)
featureMap = [convolution(t1, t2) for t1, t2 in zip(xNew, wNew)]
mergeFeatureMap = tf.concat(axis=3, values=featureMap)
out = tf.nn.bias_add(mergeFeatureMap, b)
return tf.nn.relu(tf.reshape(out, mergeFeatureMap.get_shape().as_list(), name=scope.name))
# max_pool
def pooling(input, kernel_size, stride, name, padding='SAME'):
return tf.nn.max_pool(input, ksize=[1, kernel_size, kernel_size, 1], strides=[1, stride, stride, 1], padding=padding, name=name)
# lrn
def local_response_normalization(input, R, alpha, beta, name=None, bias=1.0):
return tf.nn.local_response_normalization(input, depth_radius=R, beta=beta, alpha=alpha, name=name, bias=bias)
# dropout
def dropout(input, keep_prob, name=None):
return tf.nn.dropout(input, keep_prob=keep_prob, name=name)
# fc
def fully_connection(x, input_size, output_size, relu_flag, name):
with tf.variable_scope(name) as scope:
w = tf.get_variable('w', shape=[input_size, output_size], dtype=tf.float32)
b = tf.get_variable('b', shape=[output_size], dtype=tf.float32)
output = tf.nn.xw_plus_b(x, w, b, name=scope.name)
if relu_flag:
return tf.nn.relu(output)
else:
return output
檢測
# coding: UTF-8
import os
import cv2
import time
import numpy as np
import tensorflow as tf
from alexnet import AlexNet
from caffe_classes import class_names
import matplotlib.pyplot as plt
# mean of imagenet dataset in RGB
imagenet_mean = np.array([104., 117., 124.], np.float)
# get image path
path = os.getcwd()
image_path = os.path.join(path, 'dataset')
image_files = [os.path.join(image_path, f) for f in os.listdir(image_path) if f.endswith('jpeg') or f.endswith('.jpg')]
# read images
images = []
for image in image_files:
images.append(cv2.imread(image))
# define variable
x = tf.placeholder(tf.float32, [1, 227, 227, 3])
keep_prob = tf.placeholder(tf.float32)
# get class score
model = AlexNet(x, keep_prob, 1000, [])
score = model.fc8
softmax = tf.nn.softmax(score)
# get recognition result
fig = plt.figure(figsize=(15, 6))
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
model.load_weights(sess)
for i, image in enumerate(images):
start = time.time()
img = cv2.resize(image.astype(np.float), (227, 227))
img -= imagenet_mean
img = img.reshape((1, 227, 227, 3))
probs = sess.run(softmax, feed_dict={x: img, keep_prob: 1})
class_name = class_names[np.argmax(probs)]
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(image, class_name, (int(image.shape[0] / 3), int(image.shape[1] / 3)), font, 1, (0, 255, 0), 2)
print("{}: {} time: {}".format(i, class_name, time.time() - start))
cv2.imshow("demo", image)
cv2.imwrite('./result/{}.png'.format(i), image, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
if cv2.waitKey(1) & 0xFF == ord('q'):
break
time.sleep(0.5)