【深度學習系列】用PaddlePaddle和Tensorflow實現經典CNN網絡AlexNet
上周我們用PaddlePaddle和Tensorflow實現了圖像分類,分別用自己手寫的一個簡單的CNN網絡simple_cnn和LeNet-5的CNN網絡識別cifar-10數據集。在上周的實驗表現中,經過200次迭代后的LeNet-5的準確率為60%左右,這個結果差強人意,畢竟是二十年前寫的網絡結構,結果簡單,層數也很少,這一節(jié)中我們講講在2012年的Image比賽中大放異彩的AlexNet,并用AlexNet對cifar-10數據進行分類,對比上周的LeNet-5的效果。
什么是AlexNet?
AlexNet在ILSVRC-2012的比賽中獲得top5錯誤率15.3%的突破(第二名為26.2%),其原理來源于2012年Alex的論文《ImageNet Classification with Deep Convolutional Neural Networks》,這篇論文是深度學習火爆發(fā)展的一個里程碑和分水嶺,加上硬件技術的發(fā)展,深度學習還會繼續(xù)火下去。
AlexNet網絡結構
由于受限于當時的硬件設備,AlexNet在GPU粒度都做了設計,當時的GTX 580只有3G顯存,為了能讓模型在大量數據上跑起來,作者使用了兩個GPU并行,并對網絡結構做了切分,如下:

網絡結構
Input輸入層
輸入為224×224×3的三通道RGB圖像,為方便后續(xù)計算,實際操作中通過padding做預處理,把圖像變成227×227×3。
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個節(jié)點;
- 激活函數:采用ReLU;
- 采用參數為0.5的Dropout操作
最終輸出數據為4096個神經元節(jié)點。
F7全連接層
該層為全連接層 + Dropout
- 使用4096個節(jié)點;
- 激活函數:采用ReLU;
- 采用參數為0.5的Dropout操作
最終輸出為4096個神經元節(jié)點。
輸出層
該層為全連接層 + Softmax
- 使用1000個輸出的Softmax
最終輸出為1000個分類。
AlexNet的優(yōu)勢
1. 使用了ReLu激活函數
----原始Relu-----
AlexNet引入了ReLU激活函數,這個函數是神經科學家Dayan、Abott在《Theoretical Neuroscience》一書中提出的更精確的激活模型。原始的Relu激活函數(可參見 Hinton論文:《Rectified Linear Units Improve Restricted Boltzmann Machines》)我們比較熟悉,即max(0,x)max(0,x),這個激活函數把負激活全部清零(模擬上面提到的稀疏性),這種做法在實踐中即保留了神經網絡的非線性能力,又加快了訓練速度。
但是這個函數也有缺點:
- 在原點不可微
反向傳播的梯度計算中會帶來麻煩,所以Charles Dugas等人又提出Softplus來模擬上述ReLu函數(可視作其平滑版):f(x)=log(1+ex)f(x)=log(1+ex)實際上它的導數就是一個
f′(x)=11+e−x(1)(1)f′(x)=11+e−x
- 過稀疏性
當學習率設置不合理時,即使是一個很大的梯度,在經過ReLu單元并更新參數后該神經元可能永不被激活。
----Leaky ReLu----
為了解決上述過稀疏性導致的大量神經元不被激活的問題,Leaky ReLu被提了出來:
其中αα是人工制定的較小值(如:0.1),它一定程度保留了負激活信息。
還有很多其他的對于ReLu函數的改進,如Parametric ReLu,Randomized ReLu等,此處就不再展開講了。
2. Local Response Normalization 局部響應均值
LRN利用相鄰feature map做特征顯著化,文中實驗表明可以降低錯誤率,公式如下:
公式的直觀解釋如下:

由于 αα都是經過了RELU的輸出,所以一定是大于0的,函數1(k+α∑x2)β1(k+α∑x2)β,取文中參數的圖像如下(橫坐標為∑x2∑x2):

- 當∑x2∑x2值較小時,即當前節(jié)點和其鄰居節(jié)點輸出值差距不明顯且大家的輸出值都不太大,可以認為此時特征間競爭激烈,該函數可以使原本差距不大的輸出產生顯著性差異且此時函數輸出不飽和
- 當∑x2∑x2 值較大時,說明特征本身有顯著性差別但輸出值太大容易過擬合,該函數可以令最終輸出接近0從而緩解過擬合提高了模型泛化性。
3. Dropout
Dropout是文章亮點之一,屬于提高模型泛化性的方法,操作比較簡單,以一定概率隨機讓某些神經元輸出設置為0,既不參與前向傳播也不參與反向傳播,也可以從正則化角度去看待它。(關于深度學習的正則化年初的時候在公司做過一個分享,下次直接把pdf放出來)
從模型集成的角度來看:

無Dropout網絡:
有Dropout網絡:
其中pp為Dropout的概率(如p=0.5,即讓50%的神經元隨機失活),nn為所在的層。
它是極端情況下的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為帶有l(wèi)rn的
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的網絡結構并不是非常清楚,如果要改網絡結構也不知道如何改,這樣其實不好,僅僅把模型跑通只是第一步,后續(xù)還有很多工作要做,這也是作為算法工程師的價值體現之一。本文對于alexnet的網絡結構參考我之前的領導寫的文章,如過有什么不懂的可以留言。
ps:為了方便大家及時看到我的更新,我搞了一個公眾號,以后文章會同步發(fā)布與公眾號和博客園,這樣大家就能及時收到通知啦,有不懂的問題也可以在公眾號留言,這樣我能夠及時看到并回復。(公眾號剛開始做,做的比較粗糙,里面還沒有東西 = =,后期會慢慢完善~~)
可以通過掃下面的二維碼或者直接搜公眾號:CharlotteDataMining 就可以了,謝謝關注^_^
參考文獻
1.AlexNet: http://www.cs.toronto.edu/~fritz/absps/imagenet.pdf
我的博客即將同步至騰訊云+社區(qū),邀請大家一同入駐。





























