TensorFlow應用之進階版卷積神經網路CNN在CIFAR-10資料集上分類

marsjhao發表於2020-04-06

一、概述

1. 資料集簡介

本文使用的資料集是CIFAR-10,這是一個經典的資料集,包含了60000張32*32的彩色影像,其中訓練集50000張,測試集10000張,如同其名字,CIFAR-10資料集一共標註為10類,每一類6000張圖片,這10類分別是airplane、automobile、bird、cat、deer、dog、frog、horse、ship和truck。類別之間沒有重疊,也不會一張圖片中出現兩類物體,其另一個資料集CIFAR-100則標註了100類。

更多CIFAR相關的資訊請參見:http://www.cs.toronto.edu/~kriz/cifar.html

2. 模型簡介

在本節的神經網路中,我們採用卷積神經網路CNN,還是用了一些新的技巧:(1)對權重weights進行L2正則化;(2)對圖片資料進行資料增強,即對圖片進行翻轉、隨機剪下等操作,製造了更多的樣本;(3)在每個卷積-池化層後使用了LRN層(區域性相應歸一化層),增強了模型的泛化能力。

3. 資料增強DataAugmentation

資料增強在我們的訓練中作用很大,包括了隨機的水平翻轉、隨機剪下圖片、設定隨機的亮度和對比度以及對資料進行標準化等操作。它可以給單幅圖增加多個副本,提高圖片的利用率,防止對某一個圖片結構的學習過擬合。如果神經網路能夠克服這些噪聲並準確識別,那麼其泛化效能必然會很好。

二、程式解讀

載入常用庫部分首先要新增CIFAR-10相關的cifar10和cifar10_input模組,定義最大迭代輪數max_steps等巨集觀引數。

定義初始化權重weight的函式,使用截斷的正態分佈來初始化權重。這裡通過一個引數wl來控制對weight的正則化處理。在機器學習中,無論是分類還是迴歸任務,都可能會因特徵過多而導致過擬合問題,一般通過特徵選取或懲罰不重要的特徵的權重來解決這個問題。但是對於哪些特徵是不重要的,我們並不能直觀得出,而正則化就是幫助我們懲罰特徵權重的,即特徵的權重也是損失函式的一部分。可以理解為為了使用某個特徵需要付出代價,如果不是這個特徵對於減少損失函式非常有效其權重就會被懲罰減小。這樣就可以有效的篩選出有效的特徵,通過減少特徵權重防止過擬合,即奧卡姆剃刀原則(越簡單越有效)。L1正則化會製造稀疏的特徵,大部分無用的特徵直接置為0,而L2正則化會讓特徵的權重不會過大,使特徵的權重比較平均。對於實現L2正則化,有兩種方法,tf.multiply(tf.nn.l2_loss(var), wl)其中wl是正則化係數;tf.contrib.layers.l2_regularizer(lambda)(var)其中lambda是正則化係數。

隨後使用cifar10模組來下載資料集並解壓、展開到預設位置。再用cifar10_input模組來產生資料。其中cifar10_input.distorted_inputs()cifar10_input.inputs()函式都是TensorFlow的操作operation,操作返回封裝好的Tensor,這就需要在會話中run來實際執行。cifar10_input.distorted_inputs()對資料進行了DataAugmentation(資料增強),包括了隨機的水平翻轉、隨機剪下一塊24*24的圖片、設定隨機的亮度和對比度以及對資料進行標準化。通過這些操作,我們可以獲得更多的帶噪聲的樣本,擴大了樣本容量,對提高準確率有所幫助。需要注意的是,對影像資料進行增強操作會耗費大量的CPU計算時間,因此函式內部使用了16個獨立的執行緒來加速任務,函式內部會產生執行緒池,在需要時會通過TensorFlow queue進行排程,通過tf.train.start_queue_runners()來啟動執行緒佇列。產生測試資料集時則不需要太多操作,僅需要裁剪圖片正中間的24*24大小的區塊並進行資料標準化操作。

建立第一個卷積層,首先通過variable_with_weight_loss函式建立卷積核的引數並進行初始化,卷積核尺寸為5*5,3個顏色通道,64個卷積核(卷積核深度為64),設定weight初始化標準差為0.05(5e-2),不對第一層卷積層的weight進行L2正則化處理,即wl設為0。在ReLU啟用函式之後,我們採用一個3*3的步長為2*2的池化核進行最大池化處理,注意這裡最大池化的尺寸和步長不一致,這樣可以增加資料的豐富性。隨後我們使用tf.nn.lrn()函式,即對結果進行LRN處理。LRN層(區域性響應歸一化層)模仿了生物神經系統的“側抑制”機制,對區域性神經元的活動建立競爭機制,使得其中響應比較大的值變得相對更大,並抑制其他反饋較小的神經元,增強了模型的泛化能力。LRN對ReLU這種沒有上限邊界的啟用函式比較試用,不適合於Sigmoid這種有固定邊界並且能抑制過大值的啟用函式。

相似的步驟建立卷積層2,注意權重weight的shape中,通道數為64,bias初始化為0.1,最後的最大池化層和LRN層調換了順序,先進行LRN層處理後進行最大池化處理。

兩個卷積層後使用一個全連線層3,首先將卷積層的輸出的樣本都reshape為一維向量,獲取每個樣本的長度後作為全連線層的輸入單元數,輸出單元數設為384。權重weight初始化並設定L2正則化係數為0.004,我們希望這一層全連線層不要過擬合。

接下來的全連線層4和前一層很像,隱含節點減少一半到192。全連線層5也類似,隱含單元變為最終的分類總數10。

至此,整個網路的inference部分已經完成,網路結構如下表:

接下來構建CNN'的損失函式loss,定義一個loss函式,tf.add_n()可以將名為losses的collection中的全部loss求和,得到最終包括了cross_entropy的總loss,也包括了後兩個全連線層的權重weight的L2 loss。然後定義優化器,定義計算Top K準確率的操作。

下面就是定義會話並進行迭代訓練。首先通過session的run方法執行images_train,labels_train來獲取每批的訓練資料,再將這個batch的訓練資料傳入train_op和loss的計算,記錄每一個step花費的時間。

最後就是在測試集上測評模型的準確率,這裡是top1準確率,最得迭代訓練3000次後的測評到結果為70.2%。持續增加max_step可以得到更高的準確率。

三、函式使用

1. tf.nn.local_response_normalization / tf.nn.lrn(input,depth_radius=None, bias=None, alpha=None, beta=None, name=None)

input為4-D的Tensor,輸出與input相同的Tensor。

sqr_sum[a, b, c,d] = sum(input[a, b, c, d - depth_radius : d + depth_radius + 1] ** 2)

output = input /(bias + alpha * sqr_sum) ** beta

2. tf.nn.in_top_k(predictions, targets, k, name=None)

該函式可以求輸出結果中top K的準確率,預設使用top1,即輸出分數最高的那一類的準確率。

四、完整程式碼

import cifar10, cifar10_input
import tensorflow as tf
import numpy as np
import time

max_steps = 3000 # 最大迭代輪數
batch_size = 128 # 批大小
data_dir = 'cifar10_data/cifar-10-batches-bin' # 資料所在路徑

# 初始化weight函式,通過wl引數控制L2正則化大小
def variable_with_weight_loss(shape, stddev, wl):
    var = tf.Variable(tf.truncated_normal(shape, stddev=stddev))
    if wl is not None:
        # L2正則化可用tf.contrib.layers.l2_regularizer(lambda)(w)實現,自帶正則化引數
        weight_loss = tf.multiply(tf.nn.l2_loss(var), wl, name='weight_loss')
        tf.add_to_collection('losses', weight_loss)
    return var

cifar10.maybe_download_and_extract()
# 此處的cifar10_input.distorted_inputs()和cifar10_input.inputs()函式
# 都是TensorFlow的操作operation,需要在會話中run來實際執行
# distorted_inputs()函式對資料進行了資料增強
images_train, labels_train = cifar10_input.distorted_inputs(data_dir=data_dir,
                                                            batch_size=batch_size)
# 裁剪圖片正中間的24*24大小的區塊並進行資料標準化操作
images_test, labels_test = cifar10_input.inputs(eval_data=True,
                                                data_dir=data_dir,
                                                batch_size=batch_size)

# 定義placeholder
# 注意此處輸入尺寸的第一個值應該是batch_size而不是None
image_holder = tf.placeholder(tf.float32, [batch_size, 24, 24, 3])
label_holder = tf.placeholder(tf.int32, [batch_size])

# 卷積層1,不對權重進行正則化
weight1 = variable_with_weight_loss([5, 5, 3, 64], stddev=5e-2, wl=0.0) # 0.05
kernel1 = tf.nn.conv2d(image_holder, weight1,
                       strides=[1, 1, 1, 1], padding='SAME')
bias1 = tf.Variable(tf.constant(0.0, shape=[64]))
conv1 = tf.nn.relu(tf.nn.bias_add(kernel1, bias1))
pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1],
                       strides=[1, 2, 2, 1], padding='SAME')
norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)

# 卷積層2
weight2 = variable_with_weight_loss([5, 5, 64, 64], stddev=5e-2, wl=0.0)
kernel2 = tf.nn.conv2d(norm1, weight2, strides=[1, 1, 1, 1], padding='SAME')
bias2 = tf.Variable(tf.constant(0.1, shape=[64]))
conv2 = tf.nn.relu(tf.nn.bias_add(kernel2, bias2))
norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)
pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1],
                       strides=[1, 2, 2, 1], padding='SAME')

# 全連線層3
reshape = tf.reshape(pool2, [batch_size, -1]) # 將每個樣本reshape為一維向量
dim = reshape.get_shape()[1].value # 取每個樣本的長度
weight3 = variable_with_weight_loss([dim, 384], stddev=0.04, wl=0.004)
bias3 = tf.Variable(tf.constant(0.1, shape=[384]))
local3 = tf.nn.relu(tf.matmul(reshape, weight3) + bias3)

# 全連線層4
weight4 = variable_with_weight_loss([384, 192], stddev=0.04, wl=0.004)
bias4 = tf.Variable(tf.constant(0.1, shape=[192]))
local4 = tf.nn.relu(tf.matmul(local3, weight4) + bias4)

# 全連線層5
weight5 = variable_with_weight_loss([192, 10], stddev=1 / 192.0, wl=0.0)
bias5 = tf.Variable(tf.constant(0.0, shape=[10]))
logits = tf.matmul(local4, weight5) + bias5

# 定義損失函式loss
def loss(logits, labels):
    labels = tf.cast(labels, tf.int64)
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
            logits=logits, labels=labels, name='cross_entropy_per_example')
    cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy')
    tf.add_to_collection('losses', cross_entropy_mean)
    return tf.add_n(tf.get_collection('losses'), name='total_loss')

loss = loss(logits, label_holder) # 定義loss
train_op = tf.train.AdamOptimizer(1e-3).minimize(loss) # 定義優化器
top_k_op = tf.nn.in_top_k(logits, label_holder, 1)

# 定義會話並開始迭代訓練
sess = tf.InteractiveSession()
tf.global_variables_initializer().run()
# 啟動圖片資料增強的執行緒佇列
tf.train.start_queue_runners()

# 迭代訓練
for step in range(max_steps):
    start_time = time.time()
    image_batch, label_batch = sess.run([images_train, labels_train]) # 獲取訓練資料
    _, loss_value = sess.run([train_op, loss],
                             feed_dict={image_holder: image_batch,
                                        label_holder: label_batch})
    duration = time.time() - start_time # 計算每次迭代需要的時間
    if step % 10 == 0:
        examples_per_sec = batch_size / duration # 每秒處理的樣本數
        sec_per_batch = float(duration) # 每批需要的時間
        format_str = ('step %d, loss=%.2f (%.1f examples/sec; %.3f sec/batch)')
        print(format_str % (step, loss_value, examples_per_sec, sec_per_batch))

# 在測試集上測評準確率
num_examples = 10000
import math
num_iter = int(math.ceil(num_examples / batch_size))
true_count = 0
total_sample_count = num_iter * batch_size
step = 0
while step < num_iter:
    image_batch, label_batch = sess.run([images_test, labels_test])
    predictions = sess.run([top_k_op],
                           feed_dict={image_holder: image_batch,
                                      label_holder: label_batch})
    true_count += np.sum(predictions)
    step += 1

precision = true_count / total_sample_count
print('precision @ 1 =%.3f' % precision)

'''
step 2900, loss=1.14 (363.7 examples/sec; 0.352 sec/batch)
step 2910, loss=1.05 (372.0 examples/sec; 0.344 sec/batch)
step 2920, loss=1.26 (368.7 examples/sec; 0.347 sec/batch)
step 2930, loss=1.09 (366.4 examples/sec; 0.349 sec/batch)
step 2940, loss=1.02 (366.6 examples/sec; 0.349 sec/batch)
step 2950, loss=1.30 (365.9 examples/sec; 0.350 sec/batch)
step 2960, loss=0.91 (367.1 examples/sec; 0.349 sec/batch)
step 2970, loss=0.96 (364.2 examples/sec; 0.351 sec/batch)
step 2980, loss=1.13 (361.8 examples/sec; 0.354 sec/batch)
step 2990, loss=0.97 (356.0 examples/sec; 0.360 sec/batch)
precision @ 1 =0.702
'''

相關文章