1. 程式人生 > >tensorflow學習(5.實際圖片的讀取以及lenet-5的搭建)

tensorflow學習(5.實際圖片的讀取以及lenet-5的搭建)

之前幾篇學習了常用的一些東西,今天這篇主要說明實際圖片讀入,以及網路結構的搭建。在學習新的網路時,一般都希望對網路用於自己的專案,甚至於重新搭建一個網路。今天看到一個不錯的文章,是lenet5的實現,而且使用的是MNIST的png圖片,很適合直觀的進行學習。網址連結是https://blog.csdn.net/Enchanted_ZhouH/article/details/76855108

本文先分析了LeNet-5模型的結構,然後基於LeNet-5模型寫了TensorFlow程式碼實現mnist數字識別,程式碼部分進行了詳細註解,目前也在學習階段,有錯誤歡迎指出,大家一起學習。

       LeNet-5模型結構圖如下圖:

       image

       LeNet-5模型總共有7層。

       第一層:卷積層

       第一層卷積層的輸入為原始的影象,原始影象的尺寸為32×32×1。卷積層的過濾器尺寸為5×5,深度為6,不使用全0補充,步長為1。由於沒有使用全0補充,所以這一層的輸出的尺寸為32-5+1=28,深度為6。這一個卷積層總共有5×5×1×6+6=156個引數,其中6為偏置項引數個數,卷積層的引數個數只和過濾器的尺寸,深度以及當前層節點矩陣的深度有關。因為下一層節點矩陣有28×28×6=4704個節點,每個節點和5×5=25個當前層節點相連,所以本層卷積層總共有4704×(25+1)=122304個連線。

       第二層:池化層

       這一層的輸入為第一層的輸出,是一個28×28×6的節點矩陣。本層採用的過濾器大小為2×2,步長為2,所以本層的輸出矩陣大小為14×14×6。

       第三層:卷積層

       本層的輸入矩陣大小為14×14×6,採用的過濾器大小為5×5,深度為16,不使用全0補充,步長為1。這一層的輸出的尺寸為14-5+1=10,深度為16,即輸出矩陣大小為10×10×16。本層引數有5×5×6×16+16=2416個,連線有10×10×16×(5×5+1)=41600個。

       第四層:池化層

       本層的輸入矩陣大小為10×10×16,採用的過濾器大小為2×2,步長為2,本層的輸出矩陣大小為5×5×16。

       第五層:全連線層

       本層的輸入矩陣大小為5×5×16,在LeNet-5模型的論文中將這一層稱為卷積層,但是因為過濾器的大小就是5×5,所以和全連線層沒有區別,這裡直接看成全連線層。本層輸入為5×5×16矩陣,將其拉直為一個長度為5×5×16的向量,即將一個三維矩陣拉直到一維空間以向量的形式表示,這樣才可以進入全連線層進行訓練。本層的輸出節點個數為120,所以總共有5×5×16×120+120=48120個引數。

       第六層:全連線層

       本層的輸入節點個數為120個,輸出節點個數為84個,總共有120×84+84=10164個引數。

       第七層:全連線層

       本層的輸入節點個數為84個,輸出節點個數為10個,總共有84×10+10=850個引數。

程式碼方面我加了更多的註釋:

from skimage import io,transform
import os
import glob
import numpy as np
import tensorflow as tf


#將所有的圖片重新設定尺寸為32*32
w = 32
h = 32
c = 1

#mnist資料集中訓練資料和測試資料儲存地址
train_path = "mnist/train/"
test_path = "mnist/test/"

#讀取圖片及其標籤函式
def read_image(path):
    label_dir = [path+x for x in os.listdir(path) if os.path.isdir(path+x)]#子檔案目錄
    images = []
    labels = []
    for index,folder in enumerate(label_dir):#enumerate() 函式用於將一個可遍歷的資料物件(如列表、元組或字串)組合為一個索引序列,同時列出資料和資料下標,一般用在 for 迴圈當中。
        for img in glob.glob(folder+'/*.png'):#獲取指定目錄下的所有圖片
            print("reading the image:%s"%img)
            image = io.imread(img)
            image = transform.resize(image,(w,h,c))
            images.append(image)
            labels.append(index)
    return np.asarray(images,dtype=np.float32),np.asarray(labels,dtype=np.int32)#array和asarray都可以將結構資料轉化為ndarray,但是主要區別就是當資料來源是ndarray時,array仍然會copy出一個副本,佔用新的記憶體,但asarray不會

#讀取訓練資料及測試資料
train_data,train_label = read_image(train_path)
test_data,test_label = read_image(test_path)

#打亂訓練資料及測試資料
train_image_num = len(train_data)
train_image_index = np.arange(train_image_num)#arange(start,stop, step, dtype=None)根據start與stop指定的範圍以及step設定的步長,生成一個 ndarray。
np.random.shuffle(train_image_index)#亂序函式,多維時只對一維亂序,說明見https://blog.csdn.net/jasonzzj/article/details/53932645
train_data = train_data[train_image_index]#亂序後的資料
train_label = train_label[train_image_index]

test_image_num = len(test_data)
test_image_index = np.arange(test_image_num)
np.random.shuffle(test_image_index)
test_data = test_data[test_image_index]
test_label = test_label[test_image_index]

#搭建CNN
x = tf.placeholder(tf.float32,[None,w,h,c],name='x')
y_ = tf.placeholder(tf.int32,[None],name='y_')

def inference(input_tensor,train,regularizer):

    #第一層:卷積層,過濾器的尺寸為5×5,深度為6,不使用全0補充,步長為1。
    #尺寸變化:32×32×1->28×28×6
    with tf.variable_scope('layer1-conv1'):
        conv1_weights = tf.get_variable('weight',[5,5,c,6],initializer=tf.truncated_normal_initializer(stddev=0.1))
        conv1_biases = tf.get_variable('bias',[6],initializer=tf.constant_initializer(0.0))
        conv1 = tf.nn.conv2d(input_tensor,conv1_weights,strides=[1,1,1,1],padding='VALID')
        relu1 = tf.nn.relu(tf.nn.bias_add(conv1,conv1_biases))

    #第二層:池化層,過濾器的尺寸為2×2,使用全0補充,步長為2。
    #尺寸變化:28×28×6->14×14×6
    with tf.name_scope('layer2-pool1'):
        pool1 = tf.nn.max_pool(relu1,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')

    #第三層:卷積層,過濾器的尺寸為5×5,深度為16,不使用全0補充,步長為1。
    #尺寸變化:14×14×6->10×10×16
    with tf.variable_scope('layer3-conv2'):
        conv2_weights = tf.get_variable('weight',[5,5,6,16],initializer=tf.truncated_normal_initializer(stddev=0.1))
        conv2_biases = tf.get_variable('bias',[16],initializer=tf.constant_initializer(0.0))
        conv2 = tf.nn.conv2d(pool1,conv2_weights,strides=[1,1,1,1],padding='VALID')
        relu2 = tf.nn.relu(tf.nn.bias_add(conv2,conv2_biases))

    #第四層:池化層,過濾器的尺寸為2×2,使用全0補充,步長為2。
    #尺寸變化:10×10×6->5×5×16
    with tf.variable_scope('layer4-pool2'):
        pool2 = tf.nn.max_pool(relu2,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')

    #將第四層池化層的輸出轉化為第五層全連線層的輸入格式。第四層的輸出為5×5×16的矩陣,然而第五層全連線層需要的輸入格式
    #為向量,所以我們需要把代表每張圖片的尺寸為5×5×16的矩陣拉直成一個長度為5×5×16的向量。
    #舉例說,每次訓練64張圖片,那麼第四層池化層的輸出的size為(64,5,5,16),拉直為向量,nodes=5×5×16=400,尺寸size變為(64,400)
    pool_shape = pool2.get_shape().as_list()
    nodes = pool_shape[1]*pool_shape[2]*pool_shape[3]
    reshaped = tf.reshape(pool2,[-1,nodes])

    #第五層:全連線層,nodes=5×5×16=400,400->120的全連線
    #尺寸變化:比如一組訓練樣本為64,那麼尺寸變化為64×400->64×120
    #訓練時,引入dropout,dropout在訓練時會隨機將部分節點的輸出改為0,dropout可以避免過擬合問題。
    #這和模型越簡單越不容易過擬合思想一致,和正則化限制權重的大小,使得模型不能任意擬合訓練資料中的隨機噪聲,以此達到避免過擬合思想一致。
    #本文最後訓練時沒有采用dropout,dropout項傳入引數設定成了False,因為訓練和測試寫在了一起沒有分離,不過大家可以嘗試。
    with tf.variable_scope('layer5-fc1'):
        fc1_weights = tf.get_variable('weight',[nodes,120],initializer=tf.truncated_normal_initializer(stddev=0.1))
        if regularizer != None:
            tf.add_to_collection('losses',regularizer(fc1_weights))
        fc1_biases = tf.get_variable('bias',[120],initializer=tf.constant_initializer(0.1))
        fc1 = tf.nn.relu(tf.matmul(reshaped,fc1_weights) + fc1_biases)
        if train:
            fc1 = tf.nn.dropout(fc1,0.5)

    #第六層:全連線層,120->84的全連線
    #尺寸變化:比如一組訓練樣本為64,那麼尺寸變化為64×120->64×84
    with tf.variable_scope('layer6-fc2'):
        fc2_weights = tf.get_variable('weight',[120,84],initializer=tf.truncated_normal_initializer(stddev=0.1))
        if regularizer != None:
            tf.add_to_collection('losses',regularizer(fc2_weights))
        fc2_biases = tf.get_variable('bias',[84],initializer=tf.truncated_normal_initializer(stddev=0.1))
        fc2 = tf.nn.relu(tf.matmul(fc1,fc2_weights) + fc2_biases)
        if train:
            fc2 = tf.nn.dropout(fc2,0.5)

    #第七層:全連線層(近似表示),84->10的全連線
    #尺寸變化:比如一組訓練樣本為64,那麼尺寸變化為64×84->64×10。最後,64×10的矩陣經過softmax之後就得出了64張圖片分類於每種數字的概率,
    #即得到最後的分類結果。
    with tf.variable_scope('layer7-fc3'):
        fc3_weights = tf.get_variable('weight',[84,10],initializer=tf.truncated_normal_initializer(stddev=0.1))
        if regularizer != None:
            tf.add_to_collection('losses',regularizer(fc3_weights))
        fc3_biases = tf.get_variable('bias',[10],initializer=tf.truncated_normal_initializer(stddev=0.1))
        logit = tf.matmul(fc2,fc3_weights) + fc3_biases
    return logit

#正則化,交叉熵,平均交叉熵,損失函式,最小化損失函式,預測和實際equal比較,tf.equal函式會得到True或False,
#accuracy首先將tf.equal比較得到的布林值轉為float型,即True轉為1.,False轉為0,最後求平均值,即一組樣本的正確率。
#比如:一組5個樣本,tf.equal比較為[True False True False False],轉化為float型為[1. 0 1. 0 0],準確率為2./5=40%。
regularizer = tf.contrib.layers.l2_regularizer(0.001)
y = inference(x,True,regularizer)
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y,labels=y_)
cross_entropy_mean = tf.reduce_mean(cross_entropy)
loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))
train_op = tf.train.AdamOptimizer(0.001).minimize(loss)
correct_prediction = tf.equal(tf.cast(tf.argmax(y,1),tf.int32),y_)
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))

#每次獲取batch_size個樣本進行訓練或測試
def get_batch(data,label,batch_size):
    for start_index in range(0,len(data)-batch_size+1,batch_size):
        slice_index = slice(start_index,start_index+batch_size)
        yield data[slice_index],label[slice_index]

#建立Session會話
with tf.Session() as sess:
    #初始化所有變數(權值,偏置等)
    sess.run(tf.global_variables_initializer())

    #將所有樣本訓練10次,每次訓練中以64個為一組訓練完所有樣本。
    #train_num可以設定大一些。
    train_num = 10
    batch_size = 64


    for i in range(train_num):

        train_loss,train_acc,batch_num = 0, 0, 0
        for train_data_batch,train_label_batch in get_batch(train_data,train_label,batch_size):
            _,err,acc = sess.run([train_op,loss,accuracy],feed_dict={x:train_data_batch,y_:train_label_batch})
            train_loss+=err;train_acc+=acc;batch_num+=1
        print("train loss:",train_loss/batch_num)
        print("train acc:",train_acc/batch_num)

        test_loss,test_acc,batch_num = 0, 0, 0
        for test_data_batch,test_label_batch in get_batch(test_data,test_label,batch_size):
            err,acc = sess.run([loss,accuracy],feed_dict={x:test_data_batch,y_:test_label_batch})
            test_loss+=err;test_acc+=acc;batch_num+=1
        print("test loss:",test_loss/batch_num)
        print("test acc:",test_acc/batch_num)

這裡和原部落格中區別在於啟用了dropout,大家可以在程式碼裡找一下。

啟用dropout程式碼結果如下:

其實沒有啟用的結果test accuracy在98.83%,應該是資料相對較多,dropout的啟用的神經元數過少的原因。如有錯誤望大家指正。