天天看點

CIFAR-10與ImageNet圖像識别

        這次的實驗仍舊是圖像識别,使用的資料集是CIFAR-10,其中包含10個類别的RGB彩色圖檔:飛機、汽車、鳥類、貓、鹿、狗、蛙類、馬、船和卡車,其中訓練圖檔共50000張,測試圖檔共10000張。實驗來自于《21個項目玩轉深度學習:基于TensorFlow的實踐詳解》一書中的實驗,在這裡做一篇學習筆記,筆者不才,歡迎斧正!

         項目檔案如下:

CIFAR-10與ImageNet圖像識别

  。其中,

CIFAR-10與ImageNet圖像識别

一、下載下傳CIFAR-10資料

         運作cifar10_download.py代碼,成功将資料集下載下傳到目錄中。

CIFAR-10與ImageNet圖像識别
CIFAR-10與ImageNet圖像識别

        可以看到資料集中資料檔案名及用途:

CIFAR-10與ImageNet圖像識别

二、Tensorflow的資料讀取機制

讀取資料集必須考慮的一個問題:GPU/CPU因為I/O而空閑的問題

        簡單地将圖檔資料集從記憶體中讀進CPU或GPU中進行計算,讀取的時間便意味在降低記憶體的效率。為了解決這個問題,方法即将讀入資料和計算分别放在兩個線程中,先将資料讀入記憶體隊列中。讀取線程負責讀入資料到記憶體隊列,另一個線程則負責計算,計算需要資料時直接從記憶體隊列中取即可。

CIFAR-10與ImageNet圖像識别

Tensorflow使用“檔案名隊列+記憶體隊列”雙隊列的形式讀入檔案,更好的管理epoch。

        對于一個資料集來講,運作一個epoch就是将這個資料集中的圖檔全部計算一遍。運作一個epoch,就在檔案名隊列中把A、B、C各放入一次,并在之後标注隊列結束。

CIFAR-10與ImageNet圖像識别

        程式運作後,記憶體先讀取A,再依次讀取B、C。

CIFAR-10與ImageNet圖像識别
CIFAR-10與ImageNet圖像識别

        由于系統檢測到“結束”,則自動抛出異常,外部捕捉到這個異常後就可以結束程式了。這就是Tensorflow中讀取資料的基本機制。如果要運作兩個epoch,那麼隻要在檔案名隊列中将A、B、C依次放入兩次再标記結束就可以了。

在Tensorflow中建立檔案名隊列和記憶體隊列

        使用tf.train.string_input_producer函數。這個函數有兩個參數,一個是num_epoch是,即epoch數目;另一個是shuffle,指在一個epoch内檔案的順序是否被打亂。若設定shuffle=False,則每個epoch内,資料仍然按照A、B、C的順序進入檔案名隊列,順序不變。反之,則在一個epoch内資料的前後順序就會被打亂。在Tensorflow中記憶體隊列不需要自己建立,隻需要使用reader讀取資料就可以了。

        在使用tf.train.string_input_producer建立檔案名隊列之後,整個系統還處于停滞狀态,就是說檔案名其實并沒有真正加入到隊列中。此時若開始計算,記憶體隊列為空,計算單元就會一直阻塞。使用tf.train.start_queue_runners之後,才會啟動填充隊列的線程,這時系統就不在停滞了。

        在下面的代碼中,我們讀取三張照片的5個epoch,并把讀取的結果重新存到read檔案夾中。

# coding:utf-8
import os
if not os.path.exists('read'):
    os.makedirs('read/')

# 導入TensorFlow
import tensorflow as tf 

# 建立一個Session
with tf.Session() as sess:
    # 我們要讀三幅圖檔A.jpg, B.jpg, C.jpg
    filename = ['A.jpg', 'B.jpg', 'C.jpg']
    # string_input_producer會産生一個檔案名隊列
    filename_queue = tf.train.string_input_producer(filename, shuffle=False, num_epochs=5)
    # reader從檔案名隊列中讀資料。對應的方法是reader.read
    reader = tf.WholeFileReader()
    key, value = reader.read(filename_queue)
    # tf.train.string_input_producer定義了一個epoch變量,要對它進行初始化
    tf.local_variables_initializer().run()
    # 使用start_queue_runners之後,才會開始填充隊列
    threads = tf.train.start_queue_runners(sess=sess)
    i = 0
    while True:
        i += 1
        # 擷取圖檔資料并儲存
        image_data = sess.run(value)
        with open('read/test_%d.jpg' % i, 'wb') as f:
            f.write(image_data)
# 程式最後會抛出一個OutOfRangeError,這是epoch跑完,隊列關閉的标志
           

          其中,我們使用“filename_queue = tf.train.string_input_producer(filename, shuffle=False, num_epochs=5)” 來建立一個運作5個epoch的檔案名隊列,并使用reader讀取,reader每次讀取一張照片并儲存。

        運作代碼後,程式最後會抛出OutOfRangeError異常,這就是epoch跑完隊列關閉的标志,得到read檔案夾中的圖檔正好是按順序的5個epoch。下圖是shuffle=False時的結果:

CIFAR-10與ImageNet圖像識别

        下面我們再來将CIFAR-10資料集儲存成圖檔形式。運作的代碼是cifar10_extract.py。Cifar資料集包括10000個樣本,每個樣本有3073個位元組,第一個位元組标簽,剩下的3072個位元組是圖像資料。樣本和樣本之間無多餘的位元組分割,是以這幾個二進制檔案的大小是30730000位元組。

  • 第一步,用tf.train.string_input_producer建立隊列。
  • 第二步,用reader.read讀取資料。注意:讀取以檔案格式存放的圖檔是tf.WholeFileReader(),但是Cifar資料是一個檔案中包含多個以位元組存儲的樣本,是以使用tf.FixedLengthRecordReaer()
  • 第三步,調用tf.train.start_queue_runners
  • 最後通過sess.run()取出圖檔結果

三、利用TensorFlow訓練CIFAR-10識别模型

(壹)資料增強

原理部分:       

        一般來說在深度學習中,資料的總量越多,訓練得到的模型效果會越好。在圖像任務中,對輸入的圖像進行簡單的平移、

縮放、顔色變換,并不會影響圖像類别。是以可以依據此增強訓練樣本的個數。

        資料增強(Data Augmentation)方法是指利用平移、縮放、顔色變換,人工增大訓練集樣本個數,使得模型訓練的效果更好。

        常見的圖像資料增強方法:

  • 平移
  • 旋轉
  • 翻轉:水準翻轉或上下翻轉圖像
  • 裁剪:在原有圖像上裁剪出一塊
  • 縮放
  • 顔色變換:對圖像的RGB顔色空間進行一些變換
  • 噪聲擾動:給圖像加入一些人工生成的噪聲

         這些資料增強方法不會改變圖像原有标簽。

TensorFlow中資料增強的實作

        代碼為:

# Randomly crop a [height, width] section of the image.
distorted_image = tf.random_crop(reshaped_image, [height, width, 3])

# Randomly flip the image horizontally.
distorted_image = tf.image.random_flip_left_right(distorted_image)

# Because these operations are not commutative, consider randomizing
# the order their operation.
distorted_image = tf.image.random_brightness(distorted_image,max_delta=63)
distorted_image = tf.image.random_contrast(distorted_image,lower=0.2, upper=1.8)
           

        進行的操作分别是:

  1. 随機裁剪
  2. 對裁剪後的小塊進行水準反轉
  3. 對得到的圖檔進行亮度和對比度的随機訓練

(貳)Cifar-10識别模型

     該模型通過三個子產品來構造訓練圖,最大限度得提高代碼複用率:

  1. 模型輸入:讀取資料集中的圖像并進行預處理
  2. 模型預測:進行統計計算
  3. 模型訓練:計算損失、計算梯度、進行變量更新、儲存最終結果

  (一)模型輸入

      在模型輸入部分将圖像的加載和變換過程放入16個線程中,以減慢訓練過程。

CIFAR-10與ImageNet圖像識别
CIFAR-10與ImageNet圖像識别

        其中,在distorted_inputs()函數中采取對圖像随機左右翻轉、變換圖像亮度、變換圖像對比度等操作。該函數的核心代碼為:

# Randomly crop a [height, width] section of the image.
  distorted_image = tf.random_crop(reshaped_image, [height, width, 3])
  # Randomly flip the image horizontally.
  distorted_image = tf.image.random_flip_left_right(distorted_image)
  # Because these operations are not commutative, consider randomizing
  # the order their operation.
  distorted_image = tf.image.random_brightness(distorted_image,
                                               max_delta=63)
  distorted_image = tf.image.random_contrast(distorted_image,
                                             lower=0.2, upper=1.8)
  # Subtract off the mean and divide by the variance of the pixels.
  float_image = tf.image.per_image_standardization(distorted_image)
           
  • tf.random_crop:為圖檔随機裁剪
  • tf.image.random_flip_left_right:随機左右翻轉
  • tf.image.random_brightness:随機亮度變化
  • tf.image.random_contrast:随機對比度變化
  • tf.image.per_image_standardization:減去均值像素,并除以像素方差(圖檔标準化)

(二)模型預測

        預測流程由inference() 構造。這個函數會添加必要的操作步驟用于計算預測值的logits。

     1、建構第一個卷積層

CIFAR-10與ImageNet圖像識别

        卷積層:

  •  輸入:images
  • 卷積核:寬度為5×5 、通道為3、共64個
  • 步長:1×1
  • padding:SAME
  • 偏置項:通過函數生成的初始化為全0的64維向量
  • W*x+b:通過函數tf.nn.bias_add(conv,biases)實作
  • relu:使用ReLu激活函數完成修正線性激活

        池化層:

  • 輸入:conv1
  • 池化視窗大小:3×3
  • 滑動步長:2×2
  • padding:SAME

       局部響應歸一化

  • 原因:
CIFAR-10與ImageNet圖像識别
  •   公式
CIFAR-10與ImageNet圖像識别

        i:代表下标,你要計算像素值的下标,從0計算起

        j:平方累加索引,代表從j~i的像素值平方求和

        x,y:像素的位置,公式中用不到

        a:代表feature map裡面的 i 對應像素的具體值

        N:每個feature map裡面最内層向量的列數

        k:超參數,由原型中的bias指定

        α:超參數,由原型中的alpha指定

        n/2:超參數,由原型中的deepth_radius指定

        β:超參數,由原型中的belta指定

        2、建構第二個卷積層

CIFAR-10與ImageNet圖像識别

        卷積層

  • 輸入:norm1
  • 卷積核:寬度為5×5、通道數為64、共64個
  • 步長:1×1
  • padding:SAME
  • 偏置項:初始化全為0.1的64維向量
  • W*A+b:通過函數tf.nn.bias_add(conv,biases)實作
  • 修正線性激活函數:ReLu

        池化層:

  • 輸入:norm2
  • 池化視窗大小:3×3
  • 滑動步長:2×2
  • padding:SAME

        3、基于修正線性激活的全連接配接層

CIFAR-10與ImageNet圖像識别
  • 原因:前面的卷積和池化相當于做了特征工程,而全連接配接層則負責特征權重,簡單來說全連接配接的目的即對特征高度提純,友善交給最後的分類器或者回歸。
  • 本質: 由一個特征空間變換到另一個特征空間
  • 為了提高CNN網絡性能,在這裡全連接配接層每個神經元的激勵函數都采用ReLu函數

        4、基于修正線性激活的全連接配接層

CIFAR-10與ImageNet圖像識别

        5、softmax邏輯回歸輸出分類結果

CIFAR-10與ImageNet圖像識别

        現在避免全連接配接的方法是全局平均值法,即将最後一層卷積的輸出結果(featuremap)求平均值。

 (三)模型訓練

        訓練可進行N維分類的網絡常用多項式邏輯回歸,即softmax回歸。Softmax 回歸在網絡的輸出層上附加了一個softmax nonlinearity,并且計算歸一化的預測值和label的1-hot encoding的交叉熵。在正則化過程中,我們會對所有學習變量應用權重衰減損失。模型的目标函數是求交叉熵損失和所有權重衰減項的和,

loss()

函數的傳回值就是這個值。

CIFAR-10與ImageNet圖像識别

       在TensorBoard中可以檢視這個值的變化:

CIFAR-10與ImageNet圖像識别

        使用标準的梯度下降算法訓練的模型,其學習率随時間以指數形式衰減(本實驗隻讓伺服器跑了十萬多次,衰減程度不是很明顯):

CIFAR-10與ImageNet圖像識别

        訓練速度的變化:

CIFAR-10與ImageNet圖像識别

 (叁)測試模型效果

        本實驗跑了3000個訓練樣本,用記事本打開checkpoint發現:

CIFAR-10與ImageNet圖像識别

        說明model_checkpoint_path表示最新的模型是model.ckpt-112632。112632即為第112632步的模型。後面5個all_model_checkpoint_paths表示所有存儲下來的5個模型和它們的步數。

繼續閱讀