TF中的CNN實現CIFAR10分類

CIFAR-10與FC

數據集cifar-10包括約70000張圖片,每張圖片爲32X32X3的格式,總共分爲10各類別的數據。

實現對於cifar數據集的分類,僅僅利用fc層是不現實的,以一個500個神經元的隱含層爲例,共需要32X32X3X500約150萬個參數,但是如此簡單的結構肯定是無法達到所需的識別能力,所以總體需要的參數量可能是上億甚至更多。

過多的參數往往會帶來兩個問題:第一是計算量過大,可能會超過計算機的承載能力,並帶來極長的訓練時間;第二是過多參數會引起過擬合。

爲解決上述問題,將採用卷積神經網絡進行cifar-10的識別。

CNN

卷積神經網絡,其整體結構與之前所述的單層神經網絡大體一致,都有輸入層、fc層以及輸出層,不同的是在cnn中又加入了卷積層、降採樣層以及隨機失活層等。下文對於額外的層進行進一步的描述。

1.卷積層

卷積在信號處理中,是用於增強信號與消除噪聲;在對於圖像的處理上,一個卷積相當於一個模板,對圖像處理進行滑動與點積的計算,最終得到一個特徵圖(feature map);卷積的模板性體現在其本質在於對圖像進行某一種特徵的提取,圖像對於此特徵的近似程度就是最終卷積的值,也就是特徵圖。

卷積層最大的兩個優點就是局部連接與參數共享。

局部連接是對應着fc層的全連接而言的,在卷積層處理數據時,不會對圖像的每一個點進行處理,而是以一定的大小區塊劃分整圖,處理每一區塊得到的卷積結果即爲最終的特徵圖;局部連接劃分的區塊取決於卷積核大小以及步長與是否補零,而對於一個卷積核或者神經元來說,其參數量取決於卷積核自身的尺寸而與輸入的數據尺寸無關。

參數共享,就是指一個卷積核或者說神經元,對一張圖像做滑動卷積時,其內部的值是不變的;這在物理意義上表示爲一個卷積核就是一個模板,處理圖像時就是利用這一模板提取相似性結果,所以模板固定也就是參數固定。

2.降採樣層

降採樣即實現數據的壓縮,進而減少參數量,降低計算量,防止過擬合;一般採用池化的方式進行降採樣,但是也有網絡使用步長大於一的卷積層實現降採樣,所以注意池化只是降採樣的一種形式而非唯一形式。

池化就是對於卷積得到的feature map進行尺寸縮減,主要分爲最大池化與平均池化,分別可以達到抽取主要特徵與抽取平均特徵的目的,池化就是要儘量保存原有特徵,並達到降採樣的效果。

3.隨機失活層

即dropout層,主要是在非輸出層的fc層之後使用,目的也是爲了提高模型的泛化能力,防止過擬合;其思想是將每個神經元當作一個開關,開關的開與關具有一定的隨機性;利用這一機制,在每次訓練時只有部分神經元打開,參與訓練,得到一組權重參數,或者說是當前開關形式下的模型,最終將所有的模型組合,形成一個最終的模型,即爲訓練的結果;利用上述訓練過程,每次不完全訓練,參數量較小,並且最終融合的模型相當於多角度描述特徵,所以有更好的泛化能力。

TF中的卷積相關函數

1.二維卷積

tf.nn.conv2d(input,filter,strides,padding,name=None)
# input 是輸入數據,其格式是[batch, width, height, channel]這樣的四維格式,並且batch常寫作-1
  要求輸入數據應爲浮點類型,即tf.float32或tf.float64
# filter 是卷積層,格式爲 [filter_width, filter_height,channel]的三維格式,其中前兩項表示本層每            個神經元的尺寸,最後的參數表示本層有多少個神經元,一個神經元得到一個特徵圖,最終得到的特徵圖數就是本層的神經元數目,所以這個參數也叫做輸出維度。
# stride 是步長,即一個卷積核載圖像上做滑動點積時,每次滑動的步長;其格式爲[1,2,2,1]類型,即4維格式,每一個參數表示在不同維度上的步長,且一般首末兩個參數爲1.
# padding 即補零,有VAlID與SAME兩個可選參數,表示是否進行補零來保證卷積前後尺寸是否變化

2.池化

tf.nn.average_pool(value,ksize,stride,padding,name=None)
tf.nn.max_pooling(同上)
# value 即卷積之後的值,所以格式也是[batch,width,height,channel]
# ksize 即池化窗口大小,因爲不會在batch與channel上做池化,所以其格式爲[1,2,2,1]的4維格式但是首末值爲1.
#stride padding的意義與二維卷積一致

TF載入CIFAR-10數據集

import urllib.request
import os
import tarfile
import pickle as p
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import OneHotEncoder
import tensorflow as tf
from time import time

url = 'http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz'  # 下載鏈接
filepath = 'data/cifar-10-python.tar.gz'  # 下載好的數據集應在的路徑

# 下載數據集
if not os.path.isfile(filepath):
    result = urllib.request.urlretrieve(url, filepath)
    print('download: ', result)
else:
    print('Data file already exist')

# 解壓數據集
if not os.path.exists("data/cifar-10-batches-py"):
    tfile = tarfile.open('data/cifar-10-python.tar.gz', 'r:gz')
    result = tfile.extractall('data/')
    print('Extracted to ./data/cifar-10-batches-py/')
else:
    print('Directory already exist')


# 批次讀取數據 的函數
def load_CIFAR_batch(filename):
    with open(filename,'rb') as f:
        data_dict = p.load(f,encoding='bytes')
        images = data_dict[b'data']
        labels = data_dict[b'labels']
        images = images.reshape(10000, 3, 32, 32)
        images = images.transpose(0, 2, 3, 1)
        labels = np.array(labels)
        return images, labels


# 完整讀取數據的函數,通過多次調用批次讀取數據的函數實現
def load_CIFAR_data(data_dir):
    images_train =[]
    labels_train = []
    for i in range(5):
        f = os.path.join(data_dir, 'data_batch_%d'%(i+1))
        print('loading', f)
        image_batch, label_batch = load_CIFAR_batch(f)
        images_train.append(image_batch)
        labels_train.append(label_batch)
        Xtrain = np.concatenate(images_train)
        Ytrian = np.concatenate(labels_train)
        del image_batch, label_batch
    Xtest, Ytest = load_CIFAR_batch(os.path.join(data_dir, 'test_batch'))
    return Xtrain, Ytrian, Xtest, Ytest


data_dir = 'data/cifar-10-batches-py'  # 解壓抽取數據集後的路徑
Xtrain, Ytrain, Xtest, Ytest = load_CIFAR_data(data_dir)  # 獲取訓練集測試集相關數據與標籤

對數據進行預處理

# 對數據進行預處理

# 對圖像進行數值標準化,注意標準化之前要先類型轉化爲浮點型
Xtrain_normalize = Xtrain.astype('float32')/255.0
Xtest_normalize = Xtest.astype('float32')/255.0


# 標籤進行獨熱編碼轉換
encoder = OneHotEncoder(sparse=False)
yy = [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9]]
encoder.fit(yy)
Ytrain_reshape = Ytrain.reshape(-1, 1)
Ytrain_onehot = encoder.transform(Ytrain_reshape)  # 50000,1->50000,10
Ytest_reshape = Ytest.reshape(-1, 1)
Ytest_onehot = encoder.transform(Ytest_reshape)
# 使用獨熱編碼是要用於對預測結果和標籤值的類歐氏距離,來表徵預測的偏差程度

定義網絡構造輔助函數

# 定義 權重 構造函數 使用截斷隨機初始化
# 輸入爲權重的尺寸,即卷積核的尺寸與通道數
def weight(shape):
    return tf.Variable(tf.truncated_normal(shape, stddev=0.1), name="W")


# 定義 偏置 構造函數
# 偏置的輸入是一個以一維數組,大小等於卷積核數目
def bias(shape):
    return tf.Variable(tf.constant(0.1, shape=shape), name="b")


# 定義 2維卷積 構造函數,卷積前後尺寸不變
# 輸入爲圖像或特徵圖 與 權重
# 輸出 再加上偏置 就得到卷積結果
def conv2d(x, W):
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')


# 定義 最大池化 構造函數,池化不改變通道數,但是將圖像的長寬各減一半
# 輸入爲卷積結果
def max_pool_2X2(x):
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME

構造網絡

# 定義網絡結構
# 使用了tf的命名空間

# 輸入層
with tf.name_scope("input_layer"):
    # cifar數據集的圖像格式爲32X32X3,批次爲待定參數,用於批次讀取數據
    x = tf.placeholder(tf.float32, [None, 32, 32, 3], name="x")

# 第一層
with tf.name_scope("conv_1"):
    w1 = weight([3, 3, 3, 32])  # 尺寸爲3X3的卷積核,輸入通道是3,輸出是32
    b1 = bias([32])  # 維度與輸出通道數目一致即可
    conv_1 = conv2d(x, w1)+b1  # 進行卷積計算並加上偏置
    conv_1 = tf.nn.relu(conv_1)  # 對卷積結果進行激活  輸出爲 batch,32,32,32
    # print(conv_1.shape)

# 第一池化層
with tf.name_scope("pool1"):
    pool1 = max_pool_2X2(conv_1)  # 對第一層激活結果池化,不改變通道數,
                                  # 改變尺寸爲 batch,16,16,32
    # print(pool1.shape)

# 第二層
with tf.name_scope("conv_2"):
    w2 = weight([3, 3, 32, 64])  # 3X3的卷積核,輸入通道是32,輸出是64
    b2 = bias([64])  # 維度與輸出通道一致即可
    conv_2 = conv2d(pool1, w2)+b2  # 進行卷積計算,注意是對第一池化層數據進行卷積
    conv_2 = tf.nn.relu(conv_2)  # 對卷積結果進行激活 輸出爲 batch,16,16,64
    # print(conv_2.shape)

# 第二池化層
with tf.name_scope("pool2"):
    pool2 = max_pool_2X2(conv_2)  # 對第二層激活結果池化,不改變通道數,
                                  # 改變尺寸爲 batch,8,8,64

# 全連接層 # 注意卷積層向全連接層連接時要先做扁平化
with tf.name_scope("fc"):
     flat = tf.reshape(pool2, [-1, 4096])  # pool2結果是四維的,在傳輸給全連接層時,要先做扁平化
    w3 = weight([4096, 128])  # 輸入維度爲4096,輸出維度爲128,或者說有128個神經元在本fc層
    b3 = bias([128])  # 偏置與輸出維度一致
    # 4096 = 8X8X64,即將pool2得到的feature map數據,合併爲一個維度
    h = tf.nn.relu(tf.matmul(flat, w3)+b3)  # fc層使用矩陣乘法,並使用relu激活
    h_dropout = tf.nn.dropout(h, keep_prob=0.8)  # 引入隨機失活,防止過擬合

# 輸出層
with tf.name_scope("output"):
    w4 = weight([128, 10])  # 輸出維度即爲分類數目
    b4 = bias([10])  # 偏置與輸出維度一致
    # 輸出層使用矩陣乘法,並使用softmax激活
    preb = tf.nn.softmax(tf.matmul(h_dropout, w4)+b4)  # -1,10

超參與優化器、損失函數的設定

# 構建優化器與損失函數
with tf.name_scope("optimizer"):
    y = tf.placeholder(tf.float32, [None, 10], name="label")
    loss_function = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=preb, labels=y))
    optimizer = tf.train.AdamOptimizer(learning_rate=0.0001).minimize(loss_function)

# 構建準確率,實際上是對於一個批次下所有準確度的均值
with tf.name_scope("evaluation"):
    correct_prediction = tf.equal(tf.argmax(preb, 1), tf.argmax(y, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# 設置超參
train_epochs = 2500  # 總的訓練輪數
batch_size = 50  # 但批次訓練數目
total_batch = int(len(Xtrain)/batch_size)  # 一輪訓練多少單批次
epoch_list = []  # 論數集合
accuracy_list = []  # 準確度集合
loss_list = []  # 損失值集合
epoch = tf.Variable(0, name="epoch", trainable=False)  # 定義一個不可訓練的變量,用於統計訓練輪數,以及後續斷點續訓
start_time = time()  # 計時開始

啓動會話設置斷點續訓

# 啓動會話
sess = tf.Session()
sess.run(tf.global_variables_initializer())

# 設置日誌目錄
ckpt_dir = 'CIFAR_log/'
if not os.path.exists(ckpt_dir):
    os.makedirs(ckpt_dir)  # 沒有這個目錄,將自動創建

# 生成存儲對象,用於後續存儲模型
saver = tf.train.Saver(max_to_keep=1)

# 構造斷點訓練
ckpt = tf.train.latest_checkpoint(ckpt_dir)  # 嘗試從日誌目錄讀取存儲的模型
if ckpt!= None:
    saver.restore(sess, ckpt)
    print("載入成功")
else:
    print("載入失敗,開始訓練")

開訓練

# 迭代訓練


# 首先定義批量獲取訓練數據的函數,返回值是經過歸一化的圖像與獨熱後的標籤
def get_train_batch(number, batch_size):
    return Xtrain_normalize[number*batch_size:(number+1)*batch_size], Ytrain_onehot[number*batch_size:(number+1)*batch_size]

# 逐個輪數,逐個批次進行數據訓練
for ep in range(start,train_epochs):
    for i in range(total_batch):
        batch_x, batch_y = get_train_batch(i, batch_size)
        sess.run(optimizer, feed_dict={x: batch_x, y: batch_y})
        if i % 100 == 0:
            print("Step{}".format(i), "finished")
    # 每輪訓練完成,統計損失與準確率
    loss, acc = sess.run([loss_function, accuracy], feed_dict={x: batch_x, y: batch_y})
    epoch_list.append(ep+1) # 統計輪數
    loss_list.append(loss)  # 統計損失
    accuracy_list.append(acc)  # 統計準確率
    print("Train epoch: ", "%2d" % (sess.run(epoch)+1), "Loss: ","{:.6f}".format(loss),"Accuracy: ", acc)

    saver.save(sess, ckpt_dir+"CIFAR_cnn_model.ckpt", global_step=ep+1)  # 每輪保存模型
    sess.run(epoch.assign(ep+1))  # 更新輪數計數
duration = time()-start_time
print("Train duration: ", duration)  統計時長

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章