上週咱們用PaddlePaddle和Tensorflow實現了圖像分類,分別用本身手寫的一個簡單的CNN網絡simple_cnn和LeNet-5的CNN網絡識別cifar-10數據集。在上週的實驗表現中,通過200次迭代後的LeNet-5的準確率爲60%左右,這個結果差強人意,畢竟是二十年前寫的網絡結構,結果簡單,層數也不多,這一節中咱們講講在2012年的Image比賽中大放異彩的AlexNet,並用AlexNet對cifar-10數據進行分類,對比上週的LeNet-5的效果。git
什麼是AlexNet?github
AlexNet在ILSVRC-2012的比賽中得到top5錯誤率15.3%的突破(第二名爲26.2%),其原理來源於2012年Alex的論文《ImageNet Classification with Deep Convolutional Neural Networks》,這篇論文是深度學習火爆發展的一個里程碑和分水嶺,加上硬件技術的發展,深度學習還會繼續火下去。面試
AlexNet網絡結構算法
因爲受限於當時的硬件設備,AlexNet在GPU粒度都作了設計,當時的GTX 580只有3G顯存,爲了能讓模型在大量數據上跑起來,做者使用了兩個GPU並行,並對網絡結構作了切分,以下:網絡
網絡結構app
Input輸入層dom
輸入爲224×224×3的三通道RGB圖像,爲方便後續計算,實際操做中經過padding作預處理,把圖像變成227×227×3。ide
C1卷積層函數
該層由:卷積操做 + Max Pooling + LRN(後面詳細介紹它)組成。 性能
- 卷積層:由96個feature map組成,每一個feature map由11×11卷積核在stride=4下生成,輸出feature map爲55×55×48×2,其中55=(227-11)/4+1,48爲分在每一個GPU上的feature map數,2爲GPU個數;
- 激活函數:採用ReLU;
- Max Pooling:採用stride=2且核大小爲3×3(文中實驗代表採用2×2的非重疊模式的Max Pooling相對更容易過擬合,在top 1和top 5下的錯誤率分別高0.4%和0.3%),輸出feature map爲27×27×48×2,其中27=(55-3)/2+1,48爲分在每一個GPU上的feature map數,2爲GPU個數;
- LRN:鄰居數設置爲5作歸一化。
最終輸出數據爲歸一化後的:27×27×48×2。
C2卷積層
該層由:卷積操做 + Max Pooling + LRN組成
- 卷積層:由256個feature map組成,每一個feature map由5×5卷積核在stride=1下生成,爲使輸入和卷積輸出大小一致,須要作參數爲2的padding,輸出feature map爲27×27×128×2,其中27=(27-5+2×2)/1+1,128爲分在每一個GPU上的feature map數,2爲GPU個數;
- 激活函數:採用ReLU;
- Max Pooling:採用stride=2且核大小爲3×3,輸出feature map爲13×13×128×2,其中13=(27-3)/2+1,128爲分在每一個GPU上的feature map數,2爲GPU個數;
- LRN:鄰居數設置爲5作歸一化。
最終輸出數據爲歸一化後的:13×13×128×2。
C3卷積層
該層由:卷積操做 + LRN組成(注意,沒有Pooling層)
- 輸入爲13×13×256,由於這一層兩個GPU會作通訊(途中虛線交叉部分)
- 卷積層:以後由384個feature map組成,每一個feature map由3×3卷積核在stride=1下生成,爲使輸入和卷積輸出大小一致,須要作參數爲1的padding,輸出feature map爲13×13×192×2,其中13=(13-3+2×1)/1+1,192爲分在每一個GPU上的feature map數,2爲GPU個數;
- 激活函數:採用ReLU;
最終輸出數據爲歸一化後的:13×13×192×2。
C4卷積層
該層由:卷積操做 + LRN組成(注意,沒有Pooling層)
- 卷積層:由384個feature map組成,每一個feature map由3×3卷積核在stride=1下生成,爲使輸入和卷積輸出大小一致,須要作參數爲1的padding,輸出feature map爲13×13×192×2,其中13=(13-3+2×1)/1+1,192爲分在每一個GPU上的feature map數,2爲GPU個數;
- 激活函數:採用ReLU;
最終輸出數據爲歸一化後的:13×13×192×2。
C5卷積層
該層由:卷積操做 + Max Pooling組成
- 卷積層:由256個feature map組成,每一個feature map由3×3卷積核在stride=1下生成,爲使輸入和卷積輸出大小一致,須要作參數爲1的padding,輸出feature map爲13×13×128×2,其中13=(13-3+2×1)/1+1,128爲分在每一個GPU上的feature map數,2爲GPU個數;
- 激活函數:採用ReLU;
- Max Pooling:採用stride=2且核大小爲3×3,輸出feature map爲6×6×128×2,其中6=(13-3)/2+1,128爲分在每一個GPU上的feature map數,2爲GPU個數.
最終輸出數據爲歸一化後的:6×6×128×2。
F6全鏈接層
該層爲全鏈接層 + Dropout
- 使用4096個節點;
- 激活函數:採用ReLU;
- 採用參數爲0.5的Dropout操做
最終輸出數據爲4096個神經元節點。
F7全鏈接層
該層爲全鏈接層 + Dropout
- 使用4096個節點;
- 激活函數:採用ReLU;
- 採用參數爲0.5的Dropout操做
最終輸出爲4096個神經元節點。
輸出層
該層爲全鏈接層 + Softmax
- 使用1000個輸出的Softmax
最終輸出爲1000個分類。
AlexNet的優點
1.使用了ReLu激活函數
----原始Relu-----
AlexNet引入了ReLU激活函數,這個函數是神經科學家Dayan、Abott在《Theoretical Neuroscience》一書中提出的更精確的激活模型。原始的Relu激活函數(可參見 Hinton論文:《Rectified Linear Units Improve Restricted Boltzmann Machines》)咱們比較熟悉,即$max(0,x)$,這個激活函數把負激活所有清零(模擬上面提到的稀疏性),這種作法在實踐中即保留了神經網絡的非線性能力,又加快了訓練速度。
可是這個函數也有缺點:
- 在原點不可微
反向傳播的梯度計算中會帶來麻煩,因此Charles Dugas等人又提出Softplus來模擬上述ReLu函數(可視做其平滑版):$$f(x)=log(1+e^x)$$
實際上它的導數就是一個
\begin{equation}
f'(x)=\frac{1}{1+e^{-x}}
\end{equation}
- 過稀疏性
當學習率設置不合理時,即便是一個很大的梯度,在通過ReLu單元並更新參數後該神經元可能永不被激活。
----Leaky ReLu----
爲了解決上述過稀疏性致使的大量神經元不被激活的問題,Leaky ReLu被提了出來:
\begin{equation}
f(x)=\left\{
\begin{aligned}
ax(x<0) \\
x(x>=0)
\end{aligned}
\right.
\end{equation}
其中$\alpha$是人工制定的較小值(如:0.1),它必定程度保留了負激活信息。
還有不少其餘的對於ReLu函數的改進,如Parametric ReLu,Randomized ReLu等,此處就再也不展開講了。
2.Local Response Normalization 局部響應均值
LRN利用相鄰feature map作特徵顯著化,文中實驗代表能夠下降錯誤率,公式以下:
$$b^i_{x,y}=\frac{a^i_{x,y}}{(k+\alpha \cdot \sum^{min(N-1,i+n/2)}_{j=max(0,i-n/2)}(a^i_{x,y})^2)^\beta}$$
公式的直觀解釋以下:
因爲 $\alpha$都是通過了RELU的輸出,因此必定是大於0的,函數$\frac{1}{(k+\alpha \sum x^2)^\beta}$,取文中參數的圖像以下(橫座標爲$\sum x^2$):
- 當$\sum x^2$值較小時,即當前節點和其鄰居節點輸出值差距不明顯且你們的輸出值都不太大,能夠認爲此時特徵間競爭激烈,該函數可使本來差距不大的輸出產生顯著性差別且此時函數輸出不飽和
- 當$\sum x^2$ 值較大時,說明特徵自己有顯著性差異但輸出值太大容易過擬合,該函數能夠令最終輸出接近0從而緩解過擬合提升了模型泛化性。
3.Dropout
Dropout是文章亮點之一,屬於提升模型泛化性的方法,操做比較簡單,以必定機率隨機讓某些神經元輸出設置爲0,既不參與前向傳播也不參與反向傳播,也能夠從正則化角度去看待它。(關於深度學習的正則化年初的時候在公司作過一個分享,下次直接把pdf放出來)
從模型集成的角度來看:
無Dropout網絡:
$$Y^n=W^nX^{n-1}$$
$$X^n=F(Y^n)$$
有Dropout網絡:
$$Y^n=W^nX^{n-1}$$
$$d^{n-1}\sim Bernoulli(p)$$
$$X^n = d^{n-1} \odot F(Y^n)$$
其中$p$爲Dropout的機率(如p=0.5,即讓50%的神經元隨機失活),$n$爲所在的層。
它是極端狀況下的Bagging,因爲在每步訓練中,神經元會以某種機率隨機被置爲無效,至關因而參數共享的新網絡結構,每一個模型爲了使損失下降會盡量學最「本質」的特徵,「本質」能夠理解爲由更加獨立的、和其餘神經元相關性弱的、泛化能力強的神經元提取出來的特徵;而若是採用相似SGD的方式訓練,每步迭代都會選取不一樣的數據集,這樣整個網絡至關因而用不一樣數據集學習的多個模型的集成組合。
用PaddlePaddle實現AlexNet
1.網絡結構(alexnet.py)
此次我寫了兩個alextnet,一個加上了局部均值歸一化LRN,一個沒有加LRN,對比效果如何
1 #coding:utf-8 2 ''' 3 Created by huxiaoman 2017.12.5 4 alexnet.py:alexnet網絡結構 5 ''' 6 7 import paddle.v2 as paddle 8 import os 9 10 with_gpu = os.getenv('WITH_GPU', '0') != '1' 11 12 def alexnet_lrn(img): 13 conv1 = paddle.layer.img_conv( 14 input=img, 15 filter_size=11, 16 num_channels=3, 17 num_filters=96, 18 stride=4, 19 padding=1) 20 cmrnorm1 = paddle.layer.img_cmrnorm( 21 input=conv1, size=5, scale=0.0001, power=0.75) 22 pool1 = paddle.layer.img_pool(input=cmrnorm1, pool_size=3, stride=2) 23 24 conv2 = paddle.layer.img_conv( 25 input=pool1, 26 filter_size=5, 27 num_filters=256, 28 stride=1, 29 padding=2, 30 groups=1) 31 cmrnorm2 = paddle.layer.img_cmrnorm( 32 input=conv2, size=5, scale=0.0001, power=0.75) 33 pool2 = paddle.layer.img_pool(input=cmrnorm2, pool_size=3, stride=2) 34 35 pool3 = paddle.networks.img_conv_group( 36 input=pool2, 37 pool_size=3, 38 pool_stride=2, 39 conv_num_filter=[384, 384, 256], 40 conv_filter_size=3, 41 pool_type=paddle.pooling.Max()) 42 43 fc1 = paddle.layer.fc( 44 input=pool3, 45 size=4096, 46 act=paddle.activation.Relu(), 47 layer_attr=paddle.attr.Extra(drop_rate=0.5)) 48 fc2 = paddle.layer.fc( 49 input=fc1, 50 size=4096, 51 act=paddle.activation.Relu(), 52 layer_attr=paddle.attr.Extra(drop_rate=0.5)) 53 return fc2 54 55 def alexnet(img): 56 conv1 = paddle.layer.img_conv( 57 input=img, 58 filter_size=11, 59 num_channels=3, 60 num_filters=96, 61 stride=4, 62 padding=1) 63 cmrnorm1 = paddle.layer.img_cmrnorm( 64 input=conv1, size=5, scale=0.0001, power=0.75) 65 pool1 = paddle.layer.img_pool(input=cmrnorm1, pool_size=3, stride=2) 66 67 conv2 = paddle.layer.img_conv( 68 input=pool1, 69 filter_size=5, 70 num_filters=256, 71 stride=1, 72 padding=2, 73 groups=1) 74 cmrnorm2 = paddle.layer.img_cmrnorm( 75 input=conv2, size=5, scale=0.0001, power=0.75) 76 pool2 = paddle.layer.img_pool(input=cmrnorm2, pool_size=3, stride=2) 77 78 pool3 = paddle.networks.img_conv_group( 79 input=pool2, 80 pool_size=3, 81 pool_stride=2, 82 conv_num_filter=[384, 384, 256], 83 conv_filter_size=3, 84 pool_type=paddle.pooling.Max()) 85 86 fc1 = paddle.layer.fc( 87 input=pool3, 88 size=4096, 89 act=paddle.activation.Relu(), 90 layer_attr=paddle.attr.Extra(drop_rate=0.5)) 91 fc2 = paddle.layer.fc( 92 input=fc1, 93 size=4096, 94 act=paddle.activation.Relu(), 95 layer_attr=paddle.attr.Extra(drop_rate=0.5)) 96 return fc3
2.訓練代碼(train_alexnet.py)
1 #coding:utf-8 2 ''' 3 Created by huxiaoman 2017.12.5 4 train_alexnet.py:訓練alexnet對cifar10數據集進行分類 5 ''' 6 7 import sys, os 8 import paddle.v2 as paddle 9 10 #alex模型爲不帶LRN的 11 from alexnet import alexnet 12 #alexnet_lrn爲帶有lrn的 13 #from alextnet import alexnet_lrn 14 with_gpu = os.getenv('WITH_GPU', '0') != '1' 15 16 17 def main(): 18 datadim = 3 * 32 * 32 19 classdim = 10 20 21 # PaddlePaddle init 22 paddle.init(use_gpu=with_gpu, trainer_count=7) 23 24 image = paddle.layer.data( 25 name="image", type=paddle.data_type.dense_vector(datadim)) 26 27 # Add neural network config 28 # option 1. resnet 29 # net = resnet_cifar10(image, depth=32) 30 # option 2. vgg 31 #net = alexnet_lrn(image) 32 net = alexnet(image) 33 out = paddle.layer.fc( 34 input=net, size=classdim, act=paddle.activation.Softmax()) 35 36 lbl = paddle.layer.data( 37 name="label", type=paddle.data_type.integer_value(classdim)) 38 cost = paddle.layer.classification_cost(input=out, label=lbl) 39 40 # Create parameters 41 parameters = paddle.parameters.create(cost) 42 43 # Create optimizer 44 momentum_optimizer = paddle.optimizer.Momentum( 45 momentum=0.9, 46 regularization=paddle.optimizer.L2Regularization(rate=0.0002 * 128), 47 learning_rate=0.1 / 128.0, 48 learning_rate_decay_a=0.1, 49 learning_rate_decay_b=50000 * 100, 50 learning_rate_schedule='discexp') 51 52 # End batch and end pass event handler 53 def event_handler(event): 54 if isinstance(event, paddle.event.EndIteration): 55 if event.batch_id % 100 == 0: 56 print "\nPass %d, Batch %d, Cost %f, %s" % ( 57 event.pass_id, event.batch_id, event.cost, event.metrics) 58 else: 59 sys.stdout.write('.') 60 sys.stdout.flush() 61 if isinstance(event, paddle.event.EndPass): 62 # save parameters 63 with open('params_pass_%d.tar' % event.pass_id, 'w') as f: 64 parameters.to_tar(f) 65 66 result = trainer.test( 67 reader=paddle.batch( 68 paddle.dataset.cifar.test10(), batch_size=128), 69 feeding={'image': 0, 70 'label': 1}) 71 print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics) 72 73 # Create trainer 74 trainer = paddle.trainer.SGD( 75 cost=cost, parameters=parameters, update_equation=momentum_optimizer) 76 77 # Save the inference topology to protobuf. 78 inference_topology = paddle.topology.Topology(layers=out) 79 with open("inference_topology.pkl", 'wb') as f: 80 inference_topology.serialize_for_inference(f) 81 82 trainer.train( 83 reader=paddle.batch( 84 paddle.reader.shuffle( 85 paddle.dataset.cifar.train10(), buf_size=50000), 86 batch_size=128), 87 num_passes=200, 88 event_handler=event_handler, 89 feeding={'image': 0, 90 'label': 1}) 91 92 # inference 93 from PIL import Image 94 import numpy as np 95 import os 96 97 def load_image(file): 98 im = Image.open(file) 99 im = im.resize((32, 32), Image.ANTIALIAS) 100 im = np.array(im).astype(np.float32) 101 im = im.transpose((2, 0, 1)) # CHW 102 im = im[(2, 1, 0), :, :] # BGR 103 im = im.flatten() 104 im = im / 255.0 105 return im 106 107 test_data = [] 108 cur_dir = os.path.dirname(os.path.realpath(__file__)) 109 test_data.append((load_image(cur_dir + '/image/dog.png'), )) 110 111 probs = paddle.infer( 112 output_layer=out, parameters=parameters, input=test_data) 113 lab = np.argsort(-probs) # probs and lab are the results of one batch data 114 print "Label of image/dog.png is: %d" % lab[0][0] 115 116 117 if __name__ == '__main__': 118 main()
用Tensorflow實現AlexNet
1.網絡結構
1 def inference(images): 2 ''' 3 Alexnet模型 4 輸入:images的tensor 5 返回:Alexnet的最後一層卷積層 6 ''' 7 parameters = [] 8 # conv1 9 with tf.name_scope('conv1') as scope: 10 kernel = tf.Variable(tf.truncated_normal([11, 11, 3, 64], dtype=tf.float32, 11 stddev=1e-1), name='weights') 12 conv = tf.nn.conv2d(images, kernel, [1, 4, 4, 1], padding='SAME') 13 biases = tf.Variable(tf.constant(0.0, shape=[64], dtype=tf.float32), 14 trainable=True, name='biases') 15 bias = tf.nn.bias_add(conv, biases) 16 conv1 = tf.nn.relu(bias, name=scope) 17 print_activations(conv1) 18 parameters += [kernel, biases] 19 20 # lrn1 21 with tf.name_scope('lrn1') as scope: 22 lrn1 = tf.nn.local_response_normalization(conv1, 23 alpha=1e-4, 24 beta=0.75, 25 depth_radius=2, 26 bias=2.0) 27 28 # pool1 29 pool1 = tf.nn.max_pool(lrn1, 30 ksize=[1, 3, 3, 1], 31 strides=[1, 2, 2, 1], 32 padding='VALID', 33 name='pool1') 34 print_activations(pool1) 35 36 # conv2 37 with tf.name_scope('conv2') as scope: 38 kernel = tf.Variable(tf.truncated_normal([5, 5, 64, 192], dtype=tf.float32, 39 stddev=1e-1), name='weights') 40 conv = tf.nn.conv2d(pool1, kernel, [1, 1, 1, 1], padding='SAME') 41 biases = tf.Variable(tf.constant(0.0, shape=[192], dtype=tf.float32), 42 trainable=True, name='biases') 43 bias = tf.nn.bias_add(conv, biases) 44 conv2 = tf.nn.relu(bias, name=scope) 45 parameters += [kernel, biases] 46 print_activations(conv2) 47 48 # lrn2 49 with tf.name_scope('lrn2') as scope: 50 lrn2 = tf.nn.local_response_normalization(conv2, 51 alpha=1e-4, 52 beta=0.75, 53 depth_radius=2, 54 bias=2.0) 55 56 # pool2 57 pool2 = tf.nn.max_pool(lrn2, 58 ksize=[1, 3, 3, 1], 59 strides=[1, 2, 2, 1], 60 padding='VALID', 61 name='pool2') 62 print_activations(pool2) 63 64 # conv3 65 with tf.name_scope('conv3') as scope: 66 kernel = tf.Variable(tf.truncated_normal([3, 3, 192, 384], 67 dtype=tf.float32, 68 stddev=1e-1), name='weights') 69 conv = tf.nn.conv2d(pool2, kernel, [1, 1, 1, 1], padding='SAME') 70 biases = tf.Variable(tf.constant(0.0, shape=[384], dtype=tf.float32), 71 trainable=True, name='biases') 72 bias = tf.nn.bias_add(conv, biases) 73 conv3 = tf.nn.relu(bias, name=scope) 74 parameters += [kernel, biases] 75 print_activations(conv3) 76 77 # conv4 78 with tf.name_scope('conv4') as scope: 79 kernel = tf.Variable(tf.truncated_normal([3, 3, 384, 256], 80 dtype=tf.float32, 81 stddev=1e-1), name='weights') 82 conv = tf.nn.conv2d(conv3, kernel, [1, 1, 1, 1], padding='SAME') 83 biases = tf.Variable(tf.constant(0.0, shape=[256], dtype=tf.float32), 84 trainable=True, name='biases') 85 bias = tf.nn.bias_add(conv, biases) 86 conv4 = tf.nn.relu(bias, name=scope) 87 parameters += [kernel, biases] 88 print_activations(conv4) 89 90 # conv5 91 with tf.name_scope('conv5') as scope: 92 kernel = tf.Variable(tf.truncated_normal([3, 3, 256, 256], 93 dtype=tf.float32, 94 stddev=1e-1), name='weights') 95 conv = tf.nn.conv2d(conv4, kernel, [1, 1, 1, 1], padding='SAME') 96 biases = tf.Variable(tf.constant(0.0, shape=[256], dtype=tf.float32), 97 trainable=True, name='biases') 98 bias = tf.nn.bias_add(conv, biases) 99 conv5 = tf.nn.relu(bias, name=scope) 100 parameters += [kernel, biases] 101 print_activations(conv5) 102 103 # pool5 104 pool5 = tf.nn.max_pool(conv5, 105 ksize=[1, 3, 3, 1], 106 strides=[1, 2, 2, 1], 107 padding='VALID', 108 name='pool5') 109 print_activations(pool5) 110 111 return pool5, parameters
完整代碼可見:alexnet_tf.py
實驗結果對比
三個代碼跑完後,對比了一下實驗結果,如圖所示:
能夠看到,在batch_size,num_epochs,devices和thread數都相同的條件下,加了LRN的paddlepaddle版的alexnet網絡結果效果最好,而時間最短的是不加LRN的alexnet,在時間和精度上都比較平均的是tensorflow版的alexnet,固然,tf版的一樣加了LRN,因此LRN對於實驗效果仍是有必定提高的。
總結
AlexNet在圖像分類中是一個比較重要的網絡,在學習的過程當中不只要學會寫網絡結構,知道每一層的結構,更重要的是得知道爲何要這樣設計,這樣設計有什麼好處,若是對某些參數進行一些調整結果會有什麼變化?爲何會產生這樣的變化。在實際應用中,若是須要對網絡結構作一些調整,應該如何調整使得網絡更適合咱們的實際數據?這些纔是咱們關心的。也是面試中經常會考察的點。昨天面試了一位工做五年的算法工程師,問道他在項目中用的模型是alexnet,對於alexnet的網絡結構並非很是清楚,若是要改網絡結構也不知道如何改,這樣其實很差,僅僅把模型跑通只是第一步,後續還有不少工做要作,這也是做爲算法工程師的價值體現之一。本文對於alexnet的網絡結構參考我以前的領導寫的文章,如過有什麼不懂的能夠留言。
ps:爲了方便你們及時看到個人更新,我搞了一個公衆號,之後文章會同步發佈與公衆號和博客園,這樣你們就能及時收到通知啦,有不懂的問題也能夠在公衆號留言,這樣我可以及時看到並回復。(公衆號剛開始作,作的比較粗糙,裏面尚未東西 = =,後期會慢慢完善~~)
能夠經過掃下面的二維碼或者直接搜公衆號:CharlotteDataMining 就能夠了,謝謝關注^_^
參考文獻
1.AlexNet: http://www.cs.toronto.edu/~fritz/absps/imagenet.pdf
個人博客即將同步至騰訊雲+社區,邀請你們一同入駐。