1. 程式人生 > 實用技巧 >使用自己的資料集建立神經網路訓練模型

使用自己的資料集建立神經網路訓練模型

該內容來自---https://blog.csdn.net/weixin_43974748/article/details/89600269

使用Tensorflow建立自己的資料集,並訓練

介紹環境

win10 + pycharm + CPU

介紹背景

要求用卷積神經網路對不同水分的玉米進行分類(最後的目標是實現迴歸,以後研究),神經網路雖然是科研神器,但是在工業上的應用效果遠遠不如實驗室中的好。我們找到的教程無非是mnist,表情識別,等官方的資料集。對於一個小白來說雖然上手容易,但是收穫這得有限。這篇部落格我希望把每個知識都講到儘量的通俗易懂,希望這篇處女作可以給小白指導。文中部分程式碼參考了ywx1832990,在此感謝。受限於水平,有講解錯誤的地方,也歡迎留言探討。

話不多說 直接上程式碼

step1:建立兩個TFrecords

# pycharm中此模組名為genertateds.py

import os
import tensorflow as tf
from PIL import Image
# 源資料地址
cwd = r'C:\Users\pc\Desktop\orig_picture'
# 生成record路徑及檔名
train_record_path =r"C:\Users\pc\Desktop\outputdata\train.tfrecords"
test_record_path =r"C:\Users\pc\Desktop\outputdata\test.tfrecords
" # 分類 classes = {'11.8','13','14.8','16.5','18','20.6','22.8','26.1','28.7','30.6'} def _byteslist(value): """二進位制屬性""" return tf.train.Feature(bytes_list = tf.train.BytesList(value = [value])) def _int64list(value): """整數屬性""" return tf.train.Feature(int64_list = tf.train.Int64List(value = [value]))
def create_train_record(): """建立訓練集tfrecord""" writer = tf.python_io.TFRecordWriter(train_record_path) # 建立一個writer NUM = 1 # 顯示建立過程(計數) for index, name in enumerate(classes): class_path = cwd + "/" + name + '/' l = int(len(os.listdir(class_path)) * 0.7) # 取前70%建立訓練集 for img_name in os.listdir(class_path)[:l]: img_path = class_path + img_name img = Image.open(img_path) img = img.resize((128, 128)) # resize圖片大小 img_raw = img.tobytes() # 將圖片轉化為原生bytes example = tf.train.Example( # 封裝到Example中 features=tf.train.Features(feature={ "label":_int64list(index), # label必須為整數型別屬性 'img_raw':_byteslist(img_raw) # 圖片必須為二進位制屬性 })) writer.write(example.SerializeToString()) print('Creating train record in ',NUM) NUM += 1 writer.close() # 關閉writer print("Create train_record successful!") def create_test_record(): """建立測試tfrecord""" writer = tf.python_io.TFRecordWriter(test_record_path) NUM = 1 for index, name in enumerate(classes): class_path = cwd + '/' + name + '/' l = int(len(os.listdir(class_path)) * 0.7) for img_name in os.listdir(class_path)[l:]: # 剩餘30%作為測試集 img_path = class_path + img_name img = Image.open(img_path) img = img.resize((128, 128)) img_raw = img.tobytes() # 將圖片轉化為原生bytes # print(index,img_raw) example = tf.train.Example( features=tf.train.Features(feature={ "label":_int64list(index), 'img_raw':_byteslist(img_raw) })) writer.write(example.SerializeToString()) print('Creating test record in ',NUM) NUM += 1 writer.close() print("Create test_record successful!") def read_record(filename): """讀取tfrecord""" filename_queue = tf.train.string_input_producer([filename]) # 建立檔案佇列 reader = tf.TFRecordReader() # 建立reader _, serialized_example = reader.read(filename_queue) features = tf.parse_single_example( serialized_example, features={ 'label': tf.FixedLenFeature([], tf.int64), 'img_raw': tf.FixedLenFeature([], tf.string) } ) label = features['label'] img = features['img_raw'] img = tf.decode_raw(img, tf.uint8) img = tf.reshape(img, [128, 128, 3]) img = tf.cast(img, tf.float32) * (1. / 255) - 0.5 # 歸一化 label = tf.cast(label, tf.int32) return img, label def get_batch_record(filename,batch_size): """獲取batch""" image,label = read_record(filename) image_batch,label_batch = tf.train.shuffle_batch([image,label], # 隨機抽取batch size個image、label batch_size=batch_size, capacity=2000, min_after_dequeue=1000) return image_batch,label_batch def main(): create_train_record() create_test_record() if __name__ == '__main__': main()

注1:這裡值得一提的是 from PIL import Image 在jupyter中某次更新後,會出現無法使用的現象。建議使用jupyter的朋友不要更新。如果更新了可以解除安裝,重新安裝之前的版本

注2: windows下 cwd = r’C:\Users\pc\Desktop\orig_picture’的執行可能會產生一些作業系統層面的格式錯誤,在下面必須嚴格遵守 class_path = cwd + “/” + name + ‘/’ 這種格式,否則會出現格式錯誤。

解析

  1.  train_record_path =r"C:\Users\pc\Desktop\outputdata\train.tfrecords"
     test_record_path =r"C:\Users\pc\Desktop\outputdata\test.tfrecords"
   這兩句程式碼的意思是定義了兩個路徑,因為這個模組的作用就是建立TFrecord(一種Tensorflow中管理資料的格式,只要想使用Tensorflow,就需要用TFrecord。TFRecord內部使用的是二進位制編碼,它可以很好的把資料一次性通過一個二進位制檔案讀取進來,而不是一張圖片一張圖片的讀取,節省了時間,增加了效率。)所以需要告訴計算機一個儲存的路徑

  2.  class中不同的數字 classes = {‘11.8’,‘13’,‘14.8’,‘16.5’,‘18’,‘20.6’,‘22.8’,‘26.1’,‘28.7’,‘30.6’} 就是不同的水分

  3.  writer = tf.python_io.TFRecordWriter() 這一部分是一個TFRecord的生成器,一般伴隨著writer.write(),使用完之後需要關閉生成器,即:writer.close()

  4.  tf.train.Example:可以理解為一個包含了Features的記憶體塊,並通過feature將圖片的二進位制資料和label進行統一封裝(把資料和標籤統一儲存), 然後將example轉化為一種字串的形式, 使用tf.python_io.TFRecordWriter() 寫入到TFRecords檔案中。

  5.  def read_record(filename) 意思是把剛剛建立的tfrecord讀取進來 ,至於這裡面的函式具體定義了什麼,不要深究,我們不是造輪子,會修改其中的關鍵資訊即可,如reshape成128*128的三通道圖片。

  6.  tf.cast()的意思是把tensorflow中的張量資料做一個型別轉換,轉換成了float32型別

  7.  def get_batch_record 獲取批的圖片資料和標籤 。在這裡使用了剛剛定義的read_record。tf.train.shuffle_batch是不按照順序的從佇列中讀取資料,最後找兩個變數image_batch和label_batch接收一下。capacity引數的意義是佇列中元素的最大數量,這個無所謂,別設定太小,也別太大,根據你的資料集來決定。

step2:配置圖片的引數+定義前向傳播過程

#這個模組在pycharm中的名字是forward.py

import tensorflow as tf
# 配置引數
# 圖片size
IMAGE_SIZE = 128
NUM_CHANNELS = 3
NUM_LABELS = 10
# 第一層卷積層的尺寸和深度
CONV1_DEEP = 64
CONV1_SIZE = 5
# 第二層卷積層的尺寸和深度
CONV2_DEEP = 128
CONV2_SIZE = 5
# 全連線層的節點個數
FC_SIZE = 10
def get_Weight(shape,regularizer_rate = None):     # 定義weight如需正則化需傳入zhengzehualv預設值為None
    Weight = tf.Variable(tf.truncated_normal(shape=shape,stddev=0.1),dtype=tf.float32)    # tensorflow API推薦隨機初始化

    if regularizer_rate != None:
        regularizer = tf.contrib.layers.l2_regularizer(regularizer_rate)
        tf.add_to_collection('losses',regularizer(Weight))

    return Weight

def get_biase(shape):       # 定義biase
    biase = tf.Variable(tf.constant(value=0.1,shape=shape),dtype=tf.float32)    # tensorflow API推薦初始化0.1
    return biase

def create_conv2d(x,w):     # 定義卷積層
    conv2d = tf.nn.conv2d(x,w,strides=[1,1,1,1],padding='SAME')     # 步幅為1、SAME填充
    return conv2d

def max_pooling(x):         # 定義最大值池化
    pool = tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')   # ksize為2、步幅為2、SAME填充
    return pool

def create_fc(x,w,b):       # 定義全連線層
    fc = tf.matmul(x,w) + b
    return fc

# 定義前向傳播的過程
# 這裡添加了一個新的引數train,用於區分訓練過程和測試過程。

def inference(input_tensor, train, regularizer_rate):
    with tf.variable_scope('layer1-conv1'):
        conv1_Weights = get_Weight([CONV1_SIZE,CONV1_SIZE,NUM_CHANNELS,CONV1_DEEP])     # 5*5*64
        conv1_baises = get_biase([CONV1_DEEP])
        conv1 = tf.nn.bias_add(create_conv2d(input_tensor,conv1_Weights),conv1_baises)
        conv1 = tf.nn.relu(conv1)       # 使用ReLu啟用函式

    with tf.name_scope('layer2-pool1'):        # 64*64*64
        pool1 = max_pooling(conv1)

    with tf.variable_scope('layer3-conv2'):
        conv2_Weights = get_Weight([CONV2_SIZE,CONV2_SIZE,CONV1_DEEP,CONV2_DEEP])       # 5*5*128
        conv2_biases = get_biase([CONV2_DEEP])
        conv2 = tf.nn.bias_add(create_conv2d(pool1,conv2_Weights),conv2_biases)
        conv2 = tf.nn.relu(conv2)

    with tf.name_scope('layer4-pool2'):         # 32*32*128
        pool2 = max_pooling(conv2)

        pool_shape = pool2.get_shape().as_list()
        # pool_shape為[batch_size,32,32,128]
        # 計算將矩陣拉直成向量之後的長度,這個長度就是矩陣長度及深度的乘積。
        nodes = pool_shape[1] * pool_shape[2] * pool_shape[3]
        # 通過tf.reshape函式將第四層的輸出變成一個batch的向量
        reshaped = tf.reshape(pool2, [pool_shape[0], nodes])

    # 宣告第五層全連線層的變數並實現前向傳播過程
    with tf.variable_scope('layer5-fc1'):
        fc1_Weights = get_Weight([nodes,FC_SIZE],regularizer_rate)
        fc1_biases = get_biase([FC_SIZE])
        fc1 = tf.nn.relu(create_fc(reshaped,fc1_Weights,fc1_biases))
    # 訓練過程新增dropout防止過擬合
        if train:
            fc1 = tf.nn.dropout(fc1, 0.5)

    # 宣告第六層全連線層的變數並實現前向傳播過程
    with tf.variable_scope('layer6-fc2'):
        fc2_Weights = get_Weight([FC_SIZE,NUM_LABELS],regularizer_rate)
        fc2_biases = get_biase([NUM_LABELS])
        logit = create_fc(fc1,fc2_Weights,fc2_biases)
        # fc2 = tf.nn.relu(fc2)     需要softmax層時使用啟用函式


        return logit

  注1:在寫程式碼的過程中,我們應該把需要配置的引數在開頭宣告出來(一邊寫,寫到哪個引數,就返回開頭定義變數)

  1.  IMAGE_SIZE = 128 呼應了上一個模組中的reshape
  2.  NUM_CHANNELS = 3 這裡涉及到影象的知識,簡單來說,設定為3就是彩色圖片,設定為1就是灰度圖
  3.  NUM_LABELS = 10 就是你所提前設定好的標籤數(即需要分成多少個類)

  4.  CONV1_DEEP = 64 這裡的conv卷積層,其實就是我們所說的filter。這裡深度deep的意思就是一次性使用多少個filter (就是過濾器的個數,使用幾個不同的過濾器,就是確定了深度是幾)
  5.  CONV1_SIZE = 5 確定卷積(filter)的size,即使用55的"觀察視窗" 這樣我們第一層的資訊量就變成了了55*64

第二層卷積層同理(不懂的小夥伴可以留言)

  6.  FC_SIZE = 10 定義全連線層的個數,全連線層的一般是兩層,最後一層全連線層就是輸出層。全連線層的作用就是特徵的加權,最後10個類的所有權值加在一起應該是等於1的。

  7.  def get_Weight 這裡定義的是後面需要的權重值。
  8.  tf.truncated_normal(shape, mean, stddev) :shape表示生成張量的維度(這一部分後面用到了,注意一下),mean是均值,stddev是標準差。這個函式產生正太分佈,均值和標準差自己設定。
  9.  regularizer_rate的意思是選擇一種正則化方式,沒有指定的話預設使用l2正則化。l2正則化簡單理解就是可以去掉權重值中的超過兩次冪的高此項,個人理解是防止過擬合用的。

  10.  def get_biase(shape): 定義一個偏置
  11.  tf.constant用來定義一個常量,這裡的value一般初始化成一個常數或者一個數組。這裡初始化成0.1是沒有為什麼的,不一定非要使用0.1。shape引數和卷積層的深度有關(即,和使用的filter數量有關,不能像0.1一樣寫死了,是需要變化的)
  12.  tf.Variable(initializer,name)用來初始化一個變數。引數initializer是初始化引數,name是可自定義的變數名稱

  13.  def create_conv2d(x,w)定義一個卷積層,這部分也是很核心的部分
  14.  tf.nn.conv2d(x,w,strides=[1,1,1,1],padding=‘SAME’)
    x是輸入的張量資料 w是權值
    strides=[1,1,1,1] (解釋一下這個引數,其實不需要過多的糾結,只需要記住,strides在官方定義中就必須是一個一維且具有四個元素的張量,其規定前後必須為1,所以四個引數直接定死了兩個,中間的兩個引數一般而言都是相同的,步長為1就寫 strides=[1,1,1,1] ,步長為2就寫 strides=[1,2,2,1] ,由於步長不能過大,因為會損失大量的資訊,所以一般選擇步長要麼為1,要麼為2)
    padding = ‘SAME’ 同樣不需要糾結,就使用SAME即可。本質上說padding是一種filter進行卷積時的策略,一共只有兩種策略可以選擇,這部分到後面的調優環節再考慮不遲,通常情況下,直接使用SAME是完全沒有問題的

  15.  def max_pooling(x) 池化層的表面形式來看,十分類似於之前的卷積層。但是二者做的事情是不一樣的。池化層的作用是為了避免無關的資訊過多,干擾結果,或者影響計算速度所進行的一種降維,是一種特徵工程。注意:池化層的輸入就是卷積層的輸出,這裡隱含了一個條件,就是池化層必須在卷積層之後。
  16.  tf.nn.max_pool(value, ksize, strides, padding, name=None)中 第一個引數value是最需要注意的,這裡的value輸入的是feature map(在卷積層中,資料是以一維或者三維的形式存在的。RGB圖片可以理解為3個二維圖片疊在一起,其中每一個稱為一個feature map。而灰度圖就僅僅只存在一個feature map)
  17.  ksize是池化視窗的大小,取一個四維向量,一般是[1, 1, 1, 1]或者[1, 2, 2, 1]。如果在這裡你還在糾結為什麼是[1, 1, 1, 1]或者[1, 2, 2, 1],大可不必。在學習的初期,我們不應該被這種細枝末節的細節去分散走我們大多的精力,耗散我們的耐心,降低我們的成就感。你只需要知道,大家都這麼用就好。
  18.  strides和padding引數的設定同上(沒明白的請留言)

  19.  def create_fc(x,w,b): 定義一個全連線層,全連線層做的事情,前面已經有提過這裡不做贅述,還希望深入瞭解全連線層作用的同學可以自行查詢一些資料

  20.  def inference(input_tensor, train, regularizer_rate): 定義一個函式,構造神經網路:神經網路的結構為:輸入→卷積層→池化層→卷積層→池化→全連線→全連線。
  21.  train這個引數在後面坐了一次if判斷,關係到是否要進行dropout
     dropout可以理解為在防止過擬合。他的具體過程簡要來說,就是不一次性給機器看到所有的資料,每次訓練都隨機較少一部分資料。
  22.  with tf.name_scope的意思就是方便你看程式碼的,類似於名稱空間,不理解可以不看這部分,照著寫就可以了。無關痛癢。

在程式碼執行的中間部分位置使用了一種啟用函式–relu。relu函式可以理解為一種可以讓機器學習到非線性特徵的啟用函式。因為我們之前做的無非是矩陣運算,這些都是屬於線性運算(要不為啥是線性代數呢…)然而真是的世界中,事物往往都不是線性存在的,加入relu之後我們可以讓機器學到這種非線性的特徵

step3:定義反向傳播網路

如果說之前我們在做的的是從前往後推導,模擬的是人類神經元的思維過程。那麼反向傳播網路在我看來,就已經超出了人類生物模型的範圍,而變成了一種數學上的推導。可以說,在從前往後推導中我們已經建立一種可以用的模型,但是這個模型可以說是“慘不忍睹”,準確率低的令人大致,loss高得不忍直視。我們的前輩想到一種方法,提高模型的準確率。就是所謂的反向傳播。通俗解釋就是,基於現在的準確率,反向優化建模中所有的引數,再用經過優化的引數,重複建模的過程,再得到一個準確率,一般來說,經過一次優化的引數會提高模型的準確率。那麼不斷重複之前的過程,重複n多次,在不出現過擬合的前提下,我們最終就可以得到一個很優秀的模型。下面上程式碼

#這個模組命名為backward.py
#這裡需要注意,import genertateds 和 import forward 匯入的是剛才定義好的模組,這裡體現出了python的繼承特點。對python理解不深的同學需要花時間理解一下什麼是繼承

import tensorflow as tf
import forward
import os
import genertateds
# 定義神經網路相關引數
BACTH_SIZE = 100
LEARNING_RATE_BASE=0.1
LEARNING_RATE_DECAY = 0.99
REGULARAZTION_RATE = 0.0001
TRAINING_STEPS = 150
MOVING_AVERAGE_DECAY = 0.99
train_num_examples = 500
# 模型儲存的路徑和檔名
MODEL_SAVE_PATH = "LeNet5_model_of_corn1234/"
MODEL_NAME = "LeNet5_model_of_corn1234"
# 定義訓練過程
def train():
    # 定義輸入輸出的placeholder
    x = tf.placeholder(tf.float32, [
        BACTH_SIZE,
        forward.IMAGE_SIZE,
        forward.IMAGE_SIZE,
        forward.NUM_CHANNELS])
    y_ = tf.placeholder(tf.int32, [None], name='y-input')       # label為int型別

    y = forward.inference(x,True,REGULARAZTION_RATE)            # 訓練過程需要使用正則化

    global_step = tf.Variable(0, trainable=False)               # 記錄step、不可訓練的變數

    # 定義滑動平均類
    variable_average = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
    variable_average_op = variable_average.apply(tf.trainable_variables())
    # 定義損失函式
    # cross_entropy_mean = tf.reduce_mean(tf.square(y - y_))    # 使用softmax層時的loss函式

    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'))
    # 定義指數衰減學習率
    learning_rate = tf.train.exponential_decay(LEARNING_RATE_BASE,
                                               global_step,
                                               train_num_examples/BACTH_SIZE,
                                               LEARNING_RATE_DECAY,
                                               staircase=True)
    # 使用AdamOptimizer優化器、記錄step
    train_step = tf.train.AdamOptimizer(learning_rate).minimize(loss,global_step=global_step)
    # 控制計算流程(自己這麼理解的...)
    with tf.control_dependencies([train_step, variable_average_op]):
        train_op = tf.no_op(name='train')

    # 初始化TensorFlow持久化類
    saver = tf.train.Saver()
    # 讀取訓練集
    image_batch,label_batch = genertateds.get_batch_record(genertateds.train_record_path,100)

    with tf.Session() as sess:
        # 初始化所有變數
        init_op = tf.global_variables_initializer()
        sess.run(init_op)
        # 斷點檢查
        ckpt = tf.train.get_checkpoint_state(MODEL_SAVE_PATH)
        # 有checkpoint的話繼續上一次的訓練
        if ckpt and ckpt.model_checkpoint_path:
            saver.restore(sess,ckpt.model_checkpoint_path)
        # 建立執行緒
        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(sess,coord)
        # 開始訓練
        for i in range(TRAINING_STEPS):
            xs, ys = sess.run([image_batch,label_batch])
            _, loss_value, step = sess.run([train_op, loss, global_step],
                                           feed_dict={x:xs,y_:ys})
            # 每20輪儲存一次模型
            if i % 20 == 0:
                # 輸出當前的訓練情況
                print("After %d training step(s),loss on training batch is %g." % (step, loss_value))
                # 儲存當前模型
                saver.save(
                    sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=global_step)
        # 關閉執行緒
        coord.request_stop()
        coord.join(threads)
def main():
    train()

if __name__ == '__main__':
    main()

  1.  BACTH_SIZE = 100
    這個引數是批尺寸。這是在說,每次拿全部資料中的的一部分過來訓練。減少單次的數量,減少時間。如果本身的資料不是很多的話,完全可以直接把全部資料拿過來訓練,不需要BACTH_SIZE 這個引數。其實我本身的資料也不是很多,設定這個引數是為了通用性。需要注意的是BACTH_SIZE的值不能太大,也不能太小。太大記憶體吃不消,也打不到分批的效果。太小可能會出現由於每次取的batch差異性太大,從而使梯度互相抵消,造成結果不收斂。

  2.  LEARNING_RATE_BASE=0.1 (學習率的基值)
  3.  LEARNING_RATE_DECAY = 0.99 (一般設為接近於1的值)
    這兩個引數可以合在一起說,因為一般而言,在訓練模型的初期,學習率是比較大的,隨著訓練的步數越來越多,再使用很大的學習率會出現無法收斂的現象(步子太大,本來很小的步子就可以達到最優點,可是直接邁過了最優點。所以經驗而談,最開始可以大步慢走,越深入,越接近最優點,就應該使用小步快跑的方式)這兩個引數在後面就是tensorflow中一個調整學習率的API需要的引數。所謂的LEARNING_RATE_BASE=0.1就是初始學習率,LEARNING_RATE_DECAY = 0.99就是學習率的衰減率

  4.  REGULARAZTION_RATE = 0.0001 這個引數可以參看前面我對forward.py的描述

  5.  TRAINING_STEPS = 150 這個很好理解,就是後面訓練的步數

  6.  MOVING_AVERAGE_DECAY = 0.99
  7.  train_num_examples = 500
    這兩個引數我們暫時按下不表,後面馬上會說到

  8.  MODEL_SAVE_PATH = “LeNet5_model_of_corn1234/”
  9.  MODEL_NAME = “LeNet5_model_of_corn1234”
    以上這兩步看著不起眼,但是確實是整個系統中十分重要的一步,也是反向傳播網路的靈魂。筆者開始也是在這個位置沒有深入理解,導致後面的思維偏離正規。我們需要再次確認,我們現在定義的這個神經網路是在幹什麼?我希望看到這篇文章的人都可以先不要往後看。先思考5分鐘。明白神經網路在幹什麼,這是對一個新手來說是非常非常重要的。

那麼我們下面公佈答案,最簡單的話說,就是建模。

不管我們在神經網路中,把程式寫的多麼花哨,多麼天花亂墜,神經網路做的事情也僅僅是建模。為什麼在網上看到神經網路,最後輸出的往往是“準確率”? 難道不是應該對我們的輸入進行分類,最後給出一個明確的結果嗎?為什麼會是一個所謂的“準確率”呢?

不是的,在我們這個構建神經網路的過程中,我們在做的是建模,後面的準確率的本質是,你的測試集在對你構建的模型進行評估,評估模型的準確率。在這裡其實並不涉及到具體的分類。

那麼具體的分類怎麼做呢?答案是,把訓練好的模型儲存下來,然後再寫另外一套別的程式(這部分,我自己還沒有具體的理性認識,研究明白了後面會寫新的部落格)然後我們就要說道剛剛的兩句程式碼了,這兩句的程式碼其實就是在宣告一個模型儲存的路徑和檔名,便於我們以後載入自己的模型,對新的輸入進行評估。

  10.  def train():這個 模組整個就是在具體的表現訓練的過程
  11.  x和 y_是兩個佔位符。所謂佔位符,也是不需要太過糾結的細節問題,簡單說,就是資料不會是上來一次性全都給你的,而是需要在不同的時間點,分批次給你的。也就是說,輸入是在變化的,那麼這樣,我們在寫程式的時候就不可以把程式的輸入寫死。定義佔位符可以完美的解決問題,資料不斷的被輸入到佔位符,程式每次向佔位符要資料,然後佔位符中的資料更新。

  12.  y = forward.inference(x,True,REGULARAZTION_RATE) 這裡用了forward.py模組中的一個函式,不理解的同學大可以向上翻。
  13.  global_step = tf.Variable(0, trainable=False) 在這裡定義了一個“不可訓練的變數” ,這個不可訓練的變數需要人為設定,並將該引數傳入我們的模型。這個引數在後面馬上可以用到。

  14.  variable_average = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
      variable_average_op = variable_average.apply(tf.trainable_variables())
    這兩句程式碼定義了一個滑動平均類,什麼是滑動平均類呢?簡而言之,它也是用來更新引數的一個函式,而這種更新引數的方式就是滑動平均,這僅僅是一個名字而已。這個函式初始化需要提供一個衰減速率(decay),用於控制模型的更新速度。MOVING_AVERAGE_DECAY就是這裡的衰減速度。可以看到,第二不到嗎,等式左邊的引數是可以更新的變數,global不參與這裡的更新,所以把它設定為不可更新。

  15.  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’))
    這一部分是在定義交叉熵損失函式,交叉熵softmax是在做什麼事呢?其實softmax說白了就是在做迴歸處理。神經網路的本來輸出的並不是概率,而是一些經過了複雜的加權,加偏置,非線性處理之後的一個值而已,如何才能讓這樣一個值變成我們理解的概率值?這就需要softmax。softmax=一部分/一個整體,最後輸出的自然就是一個概率值了。(這裡不寫公式,想看的話網上到處都有) loss最後就等於經過交叉熵之後對張量取平均

  16.  learning_rate = tf.train.exponential_decay(LEARNING_RATE_BASE,global_step,train_num_examples/BACTH_SIZE, LEARNING_RATE_DECAY,staircase=True)
    這裡再定義指數衰減學習率,LEARNING_RATE_BASE是學習率基礎值。global_step就是那個手動指定的不能訓練的引數
  17.  train_num_examples/BACTH_SIZE是衰減的速度,即更新學習率的速度(注意區分,不是學習率本身的變化幅度,而是多長時間需要去變一次。即總訓練樣本/每次的feed的batch)LEARNING_RATE_DECAY前面提過,是學習率的衰減率(即學習率本身變化的幅度)staircase=True的意思就是梯度衰減,False為平滑衰減。這裡借用一張圖來解釋,何為梯度衰減,何為平滑衰減

  18.  train_step = tf.train.AdamOptimizer(learning_rate).minimize(loss,global_step=global_step)
    這裡需要說明的是,我們最常使用的優化器有兩種:隨機梯度下降法(SGD) 與 AdamOptimizer,這裡我們僅僅需要站在巨人的肩膀上就好。
    隨機梯度下降的意思,就是在不斷的靠近區域性(全域性)最優點的過程,不斷的求導,求導,來達到導數為0的點。每一步求導,基於當前在隨機資料批量上的損失,一點點的對引數進行調節
    而我們使用AdamOptimizer優化器、記錄step。它屬於梯度下降的一個分支。它利用梯度的一階矩估計和二階矩估計動態調整每個引數的學習率。可以更好地控制學習速度,使得每一次迭代的學習率都會在一個範圍內,不會過大或者過小。

  19.  with tf.control_dependencies([train_step, variable_average_op]):
      train_op = tf.no_op(name=‘train’)
    這裡是一個上下文管理器(我自己依舊是按照名稱空間來理解的)這部分程式碼完全不需要背,可以理解為一個固定流程

  20.  saver = tf.train.Saver()
      image_batch,label_batch = genertateds.get_batch_record(genertateds.train_record_path,100)
    這兩步,對於有基礎的人,應該比較好理解。無非是一個儲存模型,一個用來讀取訓練集中的圖片和標籤。

  21.  with tf.Session() as sess:
    這裡面在做的事有:

      1.初始化所有變數(必須加,不要問為什麼,加就好了。Tensorflow要求你必須加這兩步,否則不work)
      2.斷點檢查 這個意思是你可以在上次訓練的模型基礎上,接著訓練,不需要每次都從頭開始
      3.建立執行緒 對於執行緒,程序,攜程不理解的同學需要自行google,這裡把這個知識點展開了將不現實(如果需要以後開新的文章介紹) 總而言之,就是你不能把所用東西一次性丟給計算機,而是需要建立一個執行緒,主執行緒執行你本應該執行的程式碼,子執行緒可以去分批次的拿資料,子執行緒拿過來一點,主執行緒就訓練一點,邊拿邊訓練。(就好像是本來只有一隻手幹活,這是手是主執行緒。主執行緒忙著的時候,子執行緒就可以去做一些輔助主執行緒的工作,來配合子執行緒)
      4.訓練,每10輪儲存一次模型(隨時可以中斷,再開啟時會自動從上次中斷的位置來,生成ckpt檔案)

step4:輸出測試集的測試結果

#這個模組我命名為test.py
#匯入之前的三個模組,分別是genertateds,forward,backward
import time
import forward
import backward
import genertateds
import tensorflow as tf

# 等待時間
TEST_INTERVAL_SECS = 5
# 總測試集樣本數量
test_num_examples = 20
def test():
    with tf.Graph().as_default() as g:
        x = tf.placeholder(tf.float32,[test_num_examples,
                                       forward.IMAGE_SIZE,
                                       forward.IMAGE_SIZE,
                                       forward.NUM_CHANNELS])
        y_ = tf.placeholder(tf.int64,[None])
        # 測試過程不需要正則化和dropout
        y = forward.inference(x,False,None)
        # 還原模型中的滑動平均
        variable_average = tf.train.ExponentialMovingAverage(backward.MOVING_AVERAGE_DECAY)
        variable_average_restore = variable_average.variables_to_restore()
        saver = tf.train.Saver(variable_average_restore)
        # 計算準確率
        correct_prediction = tf.equal(tf.argmax(y,1),y_)
        accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
        image_batch,label_batch = genertateds.get_batch_record(genertateds.test_record_path,20)

        while True:
            with tf.Session() as sess:

                ckpt = tf.train.get_checkpoint_state(backward.MODEL_SAVE_PATH)
                if ckpt and ckpt.model_checkpoint_path:

                    coord = tf.train.Coordinator()
                    threads = tf.train.start_queue_runners(sess, coord)

                    image, label = sess.run([image_batch, label_batch])

                    saver.restore(sess,ckpt.model_checkpoint_path)
                    # 從檔名稱中讀取第幾次訓練
                    global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1]
                    accuracy_score = sess.run(accuracy,feed_dict={x:image,y_:label})

                    coord.request_stop()
                    coord.join(threads)
                    print("After %s training step(s),test accuray = %g"%(global_step,accuracy_score))
                else:
                    time.sleep(TEST_INTERVAL_SECS)

def main():
    test()
if __name__ == '__main__':
    main()

  1.  #等待時間
     TEST_INTERVAL_SECS = 5
  2.  #總測試集樣本數量
    test_num_examples = 20
  3.  這裡再宣告兩個變數,宣告等待時間在後面會用到。time.sleep(TEST_INTERVAL_SECS)的意思是讓程式執行到這裡,休眠5秒鐘,什麼都不做。(這個時候cpu可以去處理其他的任何事,只是當前的程式需要休眠)。  test_num_examples = 20是後面的佔位符需要的引數

  4.  def test():
    這個函式裡做了什麼事呢?
    首先明確一點,模組名字是test,那麼自然,這個函式做的就是驗證模型的好壞。怎麼評價模型的還壞?自然是對比著驗證集來看模型準確率
    x和y_是兩個佔位符,前面已經提過
    y由於是在測試,沒有正則化處理

  5.  variable_average = tf.train.ExponentialMovingAverage(backward.MOVING_AVERAGE_DECAY)
      variable_average_restore=variable_average.variables_to_restore()
      saver = tf.train.Saver(variable_average_restore)
    這裡做的之前也有講過,不再贅述

  6.  tf.argmax(input,axis)用來返回返回每行或者每列最大值的索引
  7.  tf.equal是一個判等函式
    結合起來就是在判斷,最大索引值,和測試集中傳進來的值是否一致

  8.  accuracy= tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
    image_batch,label_batch=genertateds.get_batch_record(genertateds.test_record_path,20)

    這兩句程式碼的細節在上個模組中也提到了,忘記了不妨回看,加深印象

  9.  while True:迴圈
  不斷的從ckpt中找到訓練的模型,用來提供給後面,進行test

到此 一個完整的使用資料集搭建神經網路的過程就完成了閉環。