【神經網路篇】--基於資料集cifa10的經典模型例項

LHBlog發表於2018-03-30

一、前述

本文分享一篇基於資料集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在我們的訓練中作用很大,它可以給單幅圖增加多個副本,提高圖片的利用率,防止對某一張圖片結構的學習過擬合
這剛好是利用了圖片資料本身的性質,圖片的冗餘資訊量比較大,因此可以製造不同的噪聲並讓圖片依然可以被識別出來。如果神經網路可以克服這些
噪聲並準確識別,那麼他的泛化能力必然很好。資料增強大大增加了樣本量,而資料量的大小恰恰是深度學習最看重的,深度學習可以在影像識別上領先
其他演算法的一大因素就是它對海量資料的利用效率非常高。其他演算法,可能在資料量大到一定程度時,準確率就不再上升了,而深度學習只要提供足夠
多的樣本,準確率基本持續提升,所以說它是最適合大資料的演算法

相關文章