2.5 創(chuàng)建 LeNet5 卷積神經(jīng)網(wǎng)絡
下面我們將開始構建更多層的神經(jīng)網(wǎng)絡。例如LeNet5卷積神經(jīng)網(wǎng)絡。
LeNet5 CNN架構最早是在1998年由Yann Lecun(見論文)提出的。它是最早的CNN之一,專門用于對手寫數(shù)字進行分類。盡管它在由大小為28 x 28的灰度圖像組成的MNIST數(shù)據(jù)集上運行良好,但是如果用于其他包含更多圖片、更大分辨率以及更多類別的數(shù)據(jù)集時,它的性能會低很多。對于這些較大的數(shù)據(jù)集,更深的ConvNets(如AlexNet、VGGNet或ResNet)會表現(xiàn)得更好。
但由于LeNet5架構僅由5個層構成,因此,學習如何構建CNN是一個很好的起點。
Lenet5架構如下圖所示:
?
我們可以看到,它由5個層組成:
第1層:卷積層,包含S型激活函數(shù),然后是平均池層。
第2層:卷積層,包含S型激活函數(shù),然后是平均池層。
第3層:一個完全連接的網(wǎng)絡(S型激活)
第4層:一個完全連接的網(wǎng)絡(S型激活)
第5層:輸出層
這意味著我們需要創(chuàng)建5個權重和偏差矩陣,我們的模型將由12行代碼組成(5個層 + 2個池 + 4個激活函數(shù) + 1個扁平層)。
由于這個還是有一些代碼量的,因此最好在圖之外的一個單獨函數(shù)中定義這些代碼。
LENET5_BATCH_SIZE = 32
LENET5_PATCH_SIZE = 5
LENET5_PATCH_DEPTH_1 = 6
LENET5_PATCH_DEPTH_2 = 16
LENET5_NUM_HIDDEN_1 = 120
LENET5_NUM_HIDDEN_2 = 84
def variables_lenet5(patch_size = LENET5_PATCH_SIZE, patch_depth1 = LENET5_PATCH_DEPTH_1,
patch_depth2 = LENET5_PATCH_DEPTH_2,
num_hidden1 = LENET5_NUM_HIDDEN_1, num_hidden2 = LENET5_NUM_HIDDEN_2,
image_depth = 1, num_labels = 10):
w1 = tf.Variable(tf.truncated_normal([patch_size, patch_size, image_depth, patch_depth1], stddev=0.1))
b1 = tf.Variable(tf.zeros([patch_depth1]))
w2 = tf.Variable(tf.truncated_normal([patch_size, patch_size, patch_depth1, patch_depth2], stddev=0.1))
b2 = tf.Variable(tf.constant(1.0, shape=[patch_depth2]))
w3 = tf.Variable(tf.truncated_normal([55patch_depth2, num_hidden1], stddev=0.1))
b3 = tf.Variable(tf.constant(1.0, shape = [num_hidden1]))
w4 = tf.Variable(tf.truncated_normal([num_hidden1, num_hidden2], stddev=0.1))
b4 = tf.Variable(tf.constant(1.0, shape = [num_hidden2]))
w5 = tf.Variable(tf.truncated_normal([num_hidden2, num_labels], stddev=0.1))
b5 = tf.Variable(tf.constant(1.0, shape = [num_labels]))
variables = {
'w1': w1, 'w2': w2, 'w3': w3, 'w4': w4, 'w5': w5,
'b1': b1, 'b2': b2, 'b3': b3, 'b4': b4, 'b5': b5
}
return variables
def model_lenet5(data, variables):
layer1_conv = tf.nn.conv2d(data, variables['w1'], [1, 1, 1, 1], padding='SAME')
layer1_actv = tf.sigmoid(layer1_conv + variables['b1'])
layer1_pool = tf.nn.avg_pool(layer1_actv, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')
layer2_conv = tf.nn.conv2d(layer1_pool, variables['w2'], [1, 1, 1, 1], padding='VALID')
layer2_actv = tf.sigmoid(layer2_conv + variables['b2'])
layer2_pool = tf.nn.avg_pool(layer2_actv, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')
flat_layer = flatten_tf_array(layer2_pool)
layer3_fccd = tf.matmul(flat_layer, variables['w3']) + variables['b3']
layer3_actv = tf.nn.sigmoid(layer3_fccd)
layer4_fccd = tf.matmul(layer3_actv, variables['w4']) + variables['b4']
layer4_actv = tf.nn.sigmoid(layer4_fccd)
logits = tf.matmul(layer4_actv, variables['w5']) + variables['b5']
return logits
由于變量和模型是單獨定義的,我們可以稍稍調整一下圖,以便讓它使用這些權重和模型,而不是以前的完全連接的NN:
#parameters determining the model size
image_size = mnist_image_size
num_labels = mnist_num_labels
#the datasets
train_dataset = mnist_train_dataset
train_labels = mnist_train_labels
test_dataset = mnist_test_dataset
test_labels = mnist_test_labels
#number of iterations and learning rate
num_steps = 10001
display_step = 1000
learning_rate = 0.001
graph = tf.Graph()
with graph.as_default():
#1) First we put the input data in a Tensorflow friendly form.
tf_train_dataset = tf.placeholder(tf.float32, shape=(batch_size, image_width, image_height, image_depth))
tf_train_labels = tf.placeholder(tf.float32, shape = (batch_size, num_labels))
tf_test_dataset = tf.constant(test_dataset, tf.float32)
#2) Then, the weight matrices and bias vectors are initialized
variables = variables_lenet5(image_depth = image_depth, num_labels = num_labels)
#3. The model used to calculate the logits (predicted labels)
model = model_lenet5
logits = model(tf_train_dataset, variables)
#4. then we compute the softmax cross entropy between the logits and the (actual) labels
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=tf_train_labels))
#5. The optimizer is used to calculate the gradients of the loss function
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)
# Predictions for the training, validation, and test data.
train_prediction = tf.nn.softmax(logits)
test_prediction = tf.nn.softmax(model(tf_test_dataset, variables))
with tf.Session(graph=graph) as session:
tf.global_variables_initializer().run()
print('Initialized with learning_rate', learning_rate)
for step in range(num_steps):
#Since we are using stochastic gradient descent, we are selecting small batches from the training dataset,
#and training the convolutional neural network each time with a batch.
offset = (step * batch_size) % (train_labels.shape[0] - batch_size)
batch_data = train_dataset[offset:(offset + batch_size), :, :, :]
batch_labels = train_labels[offset:(offset + batch_size), :]
feed_dict = {tf_train_dataset : batch_data, tf_train_labels : batch_labels}
_, l, predictions = session.run([optimizer, loss, train_prediction], feed_dict=feed_dict)
if step % display_step == 0:
train_accuracy = accuracy(predictions, batch_labels)
test_accuracy = accuracy(test_prediction.eval(), test_labels)
message = "step {:04d} : loss is {:06.2f}, accuracy on training set {:02.2f} %, accuracy on test set {:02.2f} %".format(step, l, train_accuracy, test_accuracy)
print(message)
>>> Initialized with learning_rate 0.1
>>> step 0000 : loss is 002.49, accuracy on training set 3.12 %, accuracy on test set 10.09 %
>>> step 1000 : loss is 002.29, accuracy on training set 21.88 %, accuracy on test set 9.58 %
>>> step 2000 : loss is 000.73, accuracy on training set 75.00 %, accuracy on test set 78.20 %
>>> step 3000 : loss is 000.41, accuracy on training set 81.25 %, accuracy on test set 86.87 %
>>> step 4000 : loss is 000.26, accuracy on training set 93.75 %, accuracy on test set 90.49 %
>>> step 5000 : loss is 000.28, accuracy on training set 87.50 %, accuracy on test set 92.79 %
>>> step 6000 : loss is 000.23, accuracy on training set 96.88 %, accuracy on test set 93.64 %
>>> step 7000 : loss is 000.18, accuracy on training set 90.62 %, accuracy on test set 95.14 %
>>> step 8000 : loss is 000.14, accuracy on training set 96.88 %, accuracy on test set 95.80 %
>>> step 9000 : loss is 000.35, accuracy on training set 90.62 %, accuracy on test set 96.33 %
>>> step 10000 : loss is 000.12, accuracy on training set 93.75 %, accuracy on test set 96.76 %
我們可以看到,LeNet5架構在MNIST數(shù)據(jù)集上的表現(xiàn)比簡單的完全連接的NN更好。
2.6 影響層輸出大小的參數(shù)
一般來說,神經(jīng)網(wǎng)絡的層數(shù)越多越好。我們可以添加更多的層、修改激活函數(shù)和池層,修改學習速率,以看看每個步驟是如何影響性能的。由于i層的輸入是i-1層的輸出,我們需要知道不同的參數(shù)是如何影響i-1層的輸出大小的。
要了解這一點,可以看看conv2d()函數(shù)。
它有四個參數(shù):
輸入圖像,維度為[batch size, image_width, image_height, image_depth]的4D張量
權重矩陣,維度為[filter_size, filter_size, image_depth, filter_depth]的4D張量
每個維度的步幅數(shù)。
填充(='SAME'http://xilinx.eetrend.com/'VALID')
這四個參數(shù)決定了輸出圖像的大小。
前兩個參數(shù)分別是包含一批輸入圖像的4D張量和包含卷積濾波器權重的4D張量。
第三個參數(shù)是卷積的步幅,即卷積濾波器在四維的每一個維度中應該跳過多少個位置。這四個維度中的第一個維度表示圖像批次中的圖像編號,由于我們不想跳過任何圖像,因此始終為1。最后一個維度表示圖像深度(不是色彩的通道數(shù);灰度為1,RGB為3),由于我們不想跳過任何顏色通道,所以這個也總是為1。第二和第三維度表示X和Y方向上的步幅(圖像寬度和高度)。如果要應用步幅,則這些是過濾器應跳過的位置的維度。因此,對于步幅為1,我們必須將步幅參數(shù)設置為[1, 1, 1, 1],如果我們希望步幅為2,則將其設置為[1,2,2,1]。以此類推。
最后一個參數(shù)表示Tensorflow是否應該對圖像用零進行填充,以確保對于步幅為1的輸出尺寸不會改變。如果 padding = 'SAME',則圖像用零填充(并且輸出大小不會改變),如果 padding = 'VALID',則不填充。
下面我們可以看到通過圖像(大小為28 x 28)掃描的卷積濾波器(濾波器大小為5 x 5)的兩個示例。
在左側,填充參數(shù)設置為“SAME”,圖像用零填充,最后4行/列包含在輸出圖像中。
在右側,填充參數(shù)設置為“VALID”,圖像不用零填充,最后4行/列不包括在輸出圖像中。
?
?
我們可以看到,如果沒有用零填充,則不包括最后四個單元格,因為卷積濾波器已經(jīng)到達(非零填充)圖像的末尾。這意味著,對于28 x 28的輸入大小,輸出大小變?yōu)?4 x 24 。如果 padding = 'SAME',則輸出大小為28 x 28。
如果在掃描圖像時記下過濾器在圖像上的位置(為簡單起見,只有X方向),那么這一點就變得更加清晰了。如果步幅為1,則X位置為0-5、1-6、2-7,等等。如果步幅為2,則X位置為0-5、2-7、4-9,等等。
如果圖像大小為28 x 28,濾鏡大小為5 x 5,并且步長1到4,那么我們可以得到下面這個表:
?
可以看到,對于步幅為1,零填充輸出圖像大小為28 x 28。如果非零填充,則輸出圖像大小變?yōu)?4 x 24。對于步幅為2的過濾器,這幾個數(shù)字分別為 14 x 14 和 12 x 12,對于步幅為3的過濾器,分別為 10 x 10 和 8 x 8。以此類推。
對于任意一個步幅S,濾波器尺寸K,圖像尺寸W和填充尺寸P,輸出尺寸將為
如果在Tensorflow中 padding = “SAME”,則分子加起來恒等于1,輸出大小僅由步幅S決定。
2.7 調整 LeNet5 的架構
在原始論文中,LeNet5架構使用了S形激活函數(shù)和平均池。 然而,現(xiàn)在,使用relu激活函數(shù)則更為常見。 所以,我們來稍稍修改一下LeNet5 CNN,看看是否能夠提高準確性。我們將稱之為類LeNet5架構:
LENET5_LIKE_BATCH_SIZE = 32
LENET5_LIKE_FILTER_SIZE = 5
LENET5_LIKE_FILTER_DEPTH = 16
LENET5_LIKE_NUM_HIDDEN = 120
def variables_lenet5_like(filter_size = LENET5_LIKE_FILTER_SIZE,
filter_depth = LENET5_LIKE_FILTER_DEPTH,
num_hidden = LENET5_LIKE_NUM_HIDDEN,
image_width = 28, image_depth = 1, num_labels = 10):
w1 = tf.Variable(tf.truncated_normal([filter_size, filter_size, image_depth, filter_depth], stddev=0.1))
b1 = tf.Variable(tf.zeros([filter_depth]))
w2 = tf.Variable(tf.truncated_normal([filter_size, filter_size, filter_depth, filter_depth], stddev=0.1))
b2 = tf.Variable(tf.constant(1.0, shape=[filter_depth]))
w3 = tf.Variable(tf.truncated_normal([(image_width // 4)(image_width // 4)filter_depth , num_hidden], stddev=0.1))
b3 = tf.Variable(tf.constant(1.0, shape = [num_hidden]))
w4 = tf.Variable(tf.truncated_normal([num_hidden, num_hidden], stddev=0.1))
b4 = tf.Variable(tf.constant(1.0, shape = [num_hidden]))
w5 = tf.Variable(tf.truncated_normal([num_hidden, num_labels], stddev=0.1))
b5 = tf.Variable(tf.constant(1.0, shape = [num_labels]))
variables = {
'w1': w1, 'w2': w2, 'w3': w3, 'w4': w4, 'w5': w5,
'b1': b1, 'b2': b2, 'b3': b3, 'b4': b4, 'b5': b5
}
return variables
def model_lenet5_like(data, variables):
layer1_conv = tf.nn.conv2d(data, variables['w1'], [1, 1, 1, 1], padding='SAME')
layer1_actv = tf.nn.relu(layer1_conv + variables['b1'])
layer1_pool = tf.nn.avg_pool(layer1_actv, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')
layer2_conv = tf.nn.conv2d(layer1_pool, variables['w2'], [1, 1, 1, 1], padding='SAME')
layer2_actv = tf.nn.relu(layer2_conv + variables['b2'])
layer2_pool = tf.nn.avg_pool(layer2_actv, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')
flat_layer = flatten_tf_array(layer2_pool)
layer3_fccd = tf.matmul(flat_layer, variables['w3']) + variables['b3']
layer3_actv = tf.nn.relu(layer3_fccd)
#layer3_drop = tf.nn.dropout(layer3_actv, 0.5)
layer4_fccd = tf.matmul(layer3_actv, variables['w4']) + variables['b4']
layer4_actv = tf.nn.relu(layer4_fccd)
#layer4_drop = tf.nn.dropout(layer4_actv, 0.5)
logits = tf.matmul(layer4_actv, variables['w5']) + variables['b5']
return logits
主要區(qū)別是我們使用了relu激活函數(shù)而不是S形激活函數(shù)。
除了激活函數(shù),我們還可以改變使用的優(yōu)化器,看看不同的優(yōu)化器對精度的影響。
2.8 學習速率和優(yōu)化器的影響
讓我們來看看這些CNN在MNIST和CIFAR-10數(shù)據(jù)集上的表現(xiàn)。
?
?
在上面的圖中,測試集的精度是迭代次數(shù)的函數(shù)。左側為一層完全連接的NN,中間為LeNet5 NN,右側為類LeNet5 NN。
可以看到,LeNet5 CNN在MNIST數(shù)據(jù)集上表現(xiàn)得非常好。這并不是一個大驚喜,因為它專門就是為分類手寫數(shù)字而設計的。MNIST數(shù)據(jù)集很小,并沒有太大的挑戰(zhàn)性,所以即使是一個完全連接的網(wǎng)絡也表現(xiàn)的很好。
然而,在CIFAR-10數(shù)據(jù)集上,LeNet5 NN的性能顯著下降,精度下降到了40%左右。
為了提高精度,我們可以通過應用正則化或學習速率衰減來改變優(yōu)化器,或者微調神經(jīng)網(wǎng)絡。
?
可以看到,AdagradOptimizer、AdamOptimizer和RMSPropOptimizer的性能比GradientDescentOptimizer更好。這些都是自適應優(yōu)化器,其性能通常比GradientDescentOptimizer更好,但需要更多的計算能力。
通過L2正則化或指數(shù)速率衰減,我們可能會得到更搞的準確性,但是要獲得更好的結果,我們需要進一步研究。
評論
查看更多