一、前述
本文分享一篇基於資料集cifa10的經典模型架構和程式碼。
二、程式碼
import tensorflow as tf import numpy as np import math import time from tutorials.image.cifar10 import cifar10 from tutorials.image.cifar10 import cifar10_input # 本節使用的資料集是CIFAR-10,這是一個經典的資料集,包含60000張32*32的彩色影像,其中訓練集50000張,測試集10000張 # 一共標註為10類,每一類圖片6000張。10類分別是 airplane, automobile, bird, cat, deer, dog, frog, horse, ship, truck # 我們載入一些常用庫,比如NumPy和time,並載入TensorFlow Models中自動下載、讀取CIFAR-10資料的類 max_steps = 3000#迭代3000次 batch_size = 128#每次128張圖片 # 下載cifar10資料集的預設路徑 data_dir = 'D:/cifar10_data/cifar-10-batches-bin' def variable_with_weight_losses(shape, stddev, wl):#w1是L1正則中的係數 # 定義初始化weights的函式,和之前一樣依然使用tf.truncated_normal截斷的正太分佈來初始化權值 var = tf.Variable(tf.truncated_normal(shape, stddev=stddev)) if wl is not None: # 給weight加一個L2的loss,相當於做了一個L2的正則化處理 # 在機器學習中,不管是分類還是迴歸任務,都可能因為特徵過多而導致過擬合,一般可以通過減少特徵或者懲罰不重要特徵的權重來緩解這個問題 # 但是通常我們並不知道該懲罰哪些特徵的權重,而正則化就是幫助我們懲罰特徵權重的,即特徵的權重也會成為模型的損失函式的一部分 # 我們使用w1來控制L2 loss的大小 weight_loss = tf.multiply(tf.nn.l2_loss(var), wl, name='weight_loss') # 我們使用tf.add_to_collection把weight loss統一存到一個collection,這個collection名為"losses",它會在後面計算神經網路 # 總體loss時被用上 tf.add_to_collection("losses", weight_loss) return var # 下載cifar10類下載資料集,並解壓,展開到其預設位置 cifar10.maybe_download_and_extract() # 使用cifar10_input類中的distorted_inputs函式產生訓練需要使用的資料,包括特徵及其對應的label,這裡是封裝好的tensor, # 每次執行都會生成一個batch_size的數量的樣本。需要注意的是這裡對資料進行了Data Augmentation資料增強 # 具體實現細節檢視函式,其中資料增強操作包括隨機水平翻轉tf.image.random_flip_left_right() # 隨機剪下一塊24*24大小的圖片tf.random_crop,隨機設定亮度和對比度,tf.image.random_brightness、tf.image.random_contrast # 以及對資料進行標準化,白化 tf.image.per_image_standardization() 減去均值、除以方差,保證資料零均值,方差為1 images_train, labels_train = cifar10_input.distorted_inputs(#可以之解讀資料,然後按每一批次的來讀取 data_dir=data_dir, batch_size=batch_size )#對應計算圖中的一個字圖 cifar10_input封裝的是一個tensor 輸入進來資料,再把處理邏輯輸出出去 # 生成測試資料,不過這裡不需要進行太多處理,不需要對圖片進行翻轉或修改亮度、對比度, # 不過需要裁剪圖片正中間的24*24大小的區塊。(因為訓練的資料是24*24的,通過函式cifar10_input.distorted_inputs讀進來時處理了) # 並進行資料標準化操作 # 測試的是一批資料 images_test, labels_test = cifar10_input.inputs(eval_data=True, data_dir=data_dir, batch_size=batch_size) # 因為batch_size在之後定義網路結構時被用到了,所以資料尺寸中的第一個值即樣本條數需要被預先設定,而不能像以前那樣設定為None # 而資料尺寸中的圖片尺寸為24*24即是剪裁後的大小,顏色通道數則設為3 # 這裡寫batch_size而不是None 因為後面程式碼中get_shape會拿到這裡面的batch_size image_holder = tf.placeholder(tf.float32, [batch_size, 24, 24, 3])#訓練集多少行 label_holder = tf.placeholder(tf.int32, [batch_size])#訓練集多少行,就有多少個Label # 初始設定第一個卷積層,64個卷積核,卷積核大小是5*5,3通道 weight1 = variable_with_weight_losses(shape=[5, 5, 3, 64], stddev=5e-2, wl=0.0)#在卷積這塊不做正則化 kernel1 = tf.nn.conv2d(image_holder, filter=weight1, strides=[1, 1, 1, 1], padding='SAME')#做真正的卷積 bias1 = tf.Variable(tf.constant(0.0, shape=[64]))#0.1也可 conv1 = tf.nn.relu(tf.nn.bias_add(kernel1, bias1))#用啟用函式 # 使用尺寸3*3步長2*2的最大池化層處理資料,這裡最大池化的尺寸和步長不一樣,可以增加資料的豐富性 pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME') # 使用LRN對結果進行處理 # LRN最早見於Alex那篇用CNN參加ImageNet比賽的論文,Alex在論文中解釋LRN層模仿了生物神經系統的"側抑制(單邊抑制)"機制, # 對區域性神經元的活動建立競爭環境,使得其中響應比較大的值變得相對更大,並抑制其他反饋較小的神經元,增強了模型的泛化能力 # Alex在ImageNet(上百萬張圖片)資料集上的實驗表明,使用LRN後CNN在Top1的錯誤率可以降低1.4%,因此其在經典AlexNet中使用了LRN層 # LRN對ReLU這種沒有上限邊界的啟用函式會比較有用,因為它會從附近的多個卷積核的響應中挑選比較大的反饋 # 但不適合Sigmoid這種有固定邊界並且能抑制過大值得啟用函式 # LRN對Relu配合較好,適合Alex架構 norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75) # 建立第二個卷積層 # 上面64個卷積核,即輸出64個通道,所以本層卷積核尺寸的第三個維度即輸入的通道數也需要調整為64 weight2 = variable_with_weight_losses(shape=[5, 5, 64, 64], stddev=5e-2, wl=0.0) kernel2 = tf.nn.conv2d(norm1, weight2, [1, 1, 1, 1], padding='SAME') # 還有這裡的bias值全部初始化為0.1,而不是0.最後,調換了最大池化層和LRN層的順序,先進行LRN層處理,再使用最大池化層 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') # 兩個卷積層之後,是全連線層 # 先把第二個卷積層之後的輸出結果flatten,使用tf.reshape函式將每個樣本都變成一維向量,使用get_shape函式獲取資料扁平化之後的長度 reshape = tf.reshape(pool2, [batch_size, -1]) dim = reshape.get_shape()[1].value # 接著初始化權值,隱含節點384個,正太分佈的標準差設為0.04,bias的值也初始化為0.1 # 注意這裡我們希望這個全連線層不要過擬合,因此設了一個非零的weight loss值0.04,讓這一層具有L2正則所約束。 weight3 = variable_with_weight_losses(shape=[dim, 384], stddev=0.04, wl=0.004) bias3 = tf.Variable(tf.constant(0.1, shape=[384]))#寫0.1是為了Relu小於0時全為0,所以給0.1不至於成為死亡神經元 # 最後我們依然使用ReLU啟用函式進行非線性化 local3 = tf.nn.relu(tf.matmul(reshape, weight3) + bias3) # 接下來還是全連線層,只是隱含節點只有一半,其他一樣 weight4 = variable_with_weight_losses(shape=[384, 192], stddev=0.04, wl=0.004)#全連線的神經元384---192 是不斷減少的,成倍減少 因為在不斷的總結 卷積是不斷地變寬,也是成倍的 bias4 = tf.Variable(tf.constant(0.1, shape=[192])) local4 = tf.nn.relu(tf.matmul(local3, weight4) + bias4) # 最後一層輸出層,依然先建立一層weight,其正太分佈標準差設為一個隱含層節點數的倒數,並且不用L2正則 # 這裡沒有用之前的softmax輸出最後結果,這裡把softmax操作放在了計算loss部分,其實我們不需要對inference的輸出進行softmax # 處理就可以獲得最終分類結果(直接比較inference輸出的各類的數值大小即可),計算softmax主要是為了計算loss,因此softmax操作整合到後面合理 weight5 = variable_with_weight_losses(shape=[192, 10], stddev=1/192.0, wl=0.0) bias5 = tf.Variable(tf.constant(0.0, shape=[10])) logits = tf.add(tf.matmul(local4, weight5), bias5) # 到這裡就完成了整個網路inference(構建)的部分,梳理整個網路結構,設計效能良好的CNN是有一定規律可循的,但是想要針對某個問題設計最合適的 # 網路結構,是需要大量實際摸索的 # 完成模型inference的構建,接下來是計算CNN的loss,這裡依然是用cross_entropy,這裡我們把softmax的計算和cross_entropy的計算 # 合在了一起,即 tf.nn.sparse_softmax_cross_entropy_with_logits() # 這裡使用 tf.reduce_mean() 對 cross entropy計算均值,再使用 tf.add_to_collection()把cross entropy的loss新增到整體 # losses的collection中,最後,使用tf.add_n將整體losses的collection集合中的全部loss求和,得到最終的loss,其中包括 # cross entropy loss, 還有後兩個全連線層中weight的L2 loss def loss(logits, labels): labels = tf.cast(labels, tf.int64)#真實標籤0-9 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)#加到最開始定義的集合這裡最後又兩部分交叉熵損失函式+L2損失函式 return tf.add_n(tf.get_collection('losses'), name='total_loss')#拿到兩部分損失 loss = loss(logits=logits, labels=label_holder)#logits輸出的結果 # 優化器依然選擇Adam Optimizer, 學習速率0.001 train_op = tf.train.AdamOptimizer(1e-3).minimize(loss)#在訓練過程中不斷調整w和b # 使用 tf.nn.in_top_k()函式求輸出結果中 top k的準確率,預設使用top 1,也就是輸出分數最高的那一類的準確率 top_k_op = tf.nn.in_top_k(logits, label_holder, 1)#這裡1是top1 相同為1 進行累加 得出準確率 sess = tf.InteractiveSession() tf.global_variables_initializer().run() # 前面對影像進行資料增強(各種扭曲縮小)的操作需要耗費大量CPU時間,因此distorted_inputs使用了16個獨立的執行緒來加速任務,函式內部會產生執行緒池, # 在需要使用時會通過TensorFlow queue進行排程 # 啟動圖片資料增強的執行緒佇列,這裡一共使用了16個執行緒來進行加速,如果不啟動執行緒,那麼後續inference以及訓練的操作都是無法開始的 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])#真正執行tensor邏輯 返回的是一批次的資料 _, 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)) # 評測模型在測試集上的準確率 # 我們依然像訓練時那樣使用固定的batch_size,然後一個batch一個batch輸入測試資料 num_examples = 10000 # 先計算一共要多少個batch才能將全部樣本評測完 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)
三、總結
最終,在cifar-10資料集上,通過一個短時間小迭代的訓練,可以達到大致73%的準確率,持續增加max_steps,可以期望準確率逐漸增加
如果max_steps比較大,則推薦使用學習速率衰減decay的SGD進行訓練,這樣訓練過程中能達到的準確率峰值會比較高,大致有86%
其中L2正則以及LRN層的使用都對模型準確率有提升作用,它們都可以提升模型的泛化能力
資料增強Data Augmentation在我們的訓練中作用很大,它可以給單幅圖增加多個副本,提高圖片的利用率,防止對某一張圖片結構的學習過擬合
這剛好是利用了圖片資料本身的性質,圖片的冗餘資訊量比較大,因此可以製造不同的噪聲並讓圖片依然可以被識別出來。如果神經網路可以克服這些
噪聲並準確識別,那麼他的泛化能力必然很好。資料增強大大增加了樣本量,而資料量的大小恰恰是深度學習最看重的,深度學習可以在影像識別上領先
其他演算法的一大因素就是它對海量資料的利用效率非常高。其他演算法,可能在資料量大到一定程度時,準確率就不再上升了,而深度學習只要提供足夠
多的樣本,準確率基本持續提升,所以說它是最適合大資料的演算法