【TensorFlow實戰筆記】 遷移學習實戰--卷積神經網絡CNN-Inception-v3模型

IDE:pycharm
Python: Python3.6
OS: win10

代碼已存檔於github中
DL-tenserflow/The_migration_study_Inception-v3/
希望您 star一下,在此 感謝

遷移學習

1.所謂遷移學習,就是將一個問題上訓練好的模型通過簡單的調整使其適用於一個新的問題。
2.說白了就是別人已經訓練好的強大的模型,你不需要去浪費時間訓練,直接拿過來用,雖然這樣最後的acc(準確率)可能會稍微低一些,但是可以節省大量的時間,還是非常划算的

幾個須知的基本概念

1.Inception-v3模型 是一個 卷積神經網絡(CNN)模型 (因爲最近一直在忙着學,過一段時間會把經典的神經網絡和CNN基本概念和基礎知識全都補上)
2.瓶頸層:在最後一層全連接層之前統稱爲瓶頸層,將一個新的圖像經過訓練好的卷積神經網絡直到瓶頸層的過程可以看成對圖像進行特徵提取的過程,輸出的節點向量可以看做爲圖像的的一個更加精簡併且表達能力更像的特徵向量(這也是我感覺最爲神奇的部分)
3.全連接層: 就是神經網絡的每一層直接所有節點都相連。

這裏使用的是經過模型之後再經過一層全連接層進行對圖片的分類

前提準備

1.需要知道基本的神經網絡的架構(前向傳播算法、loss函數、優化算法、基本tf編程,這些我今後會整理)
2.Inception-v3模型 下載Inception-v3模型
3.數據集下載flower_photos
#模型和數據集介紹
1.模型的運用爲tf的持久化,通過模型得到數據輸入所對應的張量,以及計算瓶頸層所對應的張量
模型文件如圖所示:
這裏寫圖片描述
2.數據集:
這裏寫圖片描述
數據集中有五個子文件夾,每個文件夾中名稱即這個文件中所有照片的所屬類別
每個文件夾中存放對應類別的分辨率大小不一的圖片
#程序
以下程序有詳細的標註

"""
@Author:Che_Hongshu
@Function: 遷移學習
@Modify:2018.2.21
"""

#導入相應的包
import glob
import os.path
import random
import numpy as np
import tensorflow as tf
from tensorflow.python.platform import gfile

#Inception-v3 模型瓶頸層的節點個數
BOTTLENECK_TENSOR_SIZE = 2048
#模型中代表瓶頸層張量的名稱
BOTTLENECK_TENSOR_NAME = 'pool_3/_reshape:0'
#圖像輸入張量所對應的名稱
JPEG_DATA_TENSOR_NAME = 'DecodeJpeg/contents:0'
#下載的谷歌訓練好的Inception-v3模型文件目錄
MODEL_DIR = './inception_dec_2015'
#Inception-v3模型文件名
MODEL_FILE = 'tensorflow_inception_graph.pb'
#因爲一個訓練數據會被使用很多次,所以可以將原始圖像通過nception-v3模型計算得到的特徵向量保存到文件中
CACKE_DIR = './bottleneck'
#輸入圖片的文件位置
INPUT_DATA = './flower_photos'

#驗證的數據百分比
VALIDATION_PERCENTAGE = 10
#測試的數據百分比
TEST_PERCENTAGE = 10

#定義神經網絡的設置
LEARNING_RATE = 0.01
STEPS = 4000
BATCH = 100
"""
函數說明: 得到圖片的列表
Parameters:
    testing_percentage-測試集的大小
    validation_percentage-驗證集的大小
Returns:
    result-保存圖片的dict
CSDN:
    http://blog.csdn.net/qq_33431368
Modify:
    2018-2-20
"""
def create_image_lists(testing_percentage, validation_percentage):
    #所有的圖片都存在這個字典裏
    result = {}
    #獲取當前目錄下所有的子目錄 包括此時的目錄
    sub_dirs = [x[0] for x in os.walk(INPUT_DATA)]
    #用於過濾掉此時的目錄
    is_root_dir = True
    #遍歷flower_photos的子目錄
    for sub_dir in sub_dirs:
        #過濾掉
        if is_root_dir:
            is_root_dir = False
            continue
        #獲取當前目錄下所有的有效圖片文件格式
        extensions = ['jpg', 'jpeg', 'JPG', 'JPEG']
        #用於存放有效照片的文件夾,存放的是圖片的名稱
        file_list = []
        #返回path最後的文件名 在這裏爲 此時的文件夾的名稱其實就是分類的名稱
        dir_name = os.path.basename(sub_dir)
        for extension in extensions:
            #得出的path爲flower_photos/類別/*./照片類型  此時爲絕對路徑
            file_glob = os.path.join(INPUT_DATA, dir_name, '*.'+extension)
            #返回所有匹配的文件路徑列表 glob.glob(file_glob)
            file_list.extend(glob.glob(file_glob)) # 現將所有找到的右下圖片加載到filelist上
        if not file_list: continue
        #通過目錄名獲取類別的名稱
        label_name = dir_name.lower()# 變爲小寫
        #初始化當前類別得訓練數據集、測試數據集、驗證數據集
        training_images = []
        testing_images = []
        validation_images = []
        for file_name in file_list:
            #得到有效圖片的文件名
            base_name = os.path.basename(file_name)
            #隨機得到0-100的數字
            chance = np.random.randint(100)
            #生成驗證集,測試集,訓練集
            if chance < validation_percentage:
                validation_images.append(base_name)
            elif chance < (validation_percentage + testing_percentage):
                testing_images.append(base_name)
            else:
                training_images.append(base_name)

        # 生成各圖片集的dict,key爲類別的名稱, value爲圖片的名稱
        result[label_name] ={
            'dir': dir_name,
            'training': training_images,
            'testing': testing_images,
            'validation': validation_images,
        }

    return result
"""
函數說明: 得到指定類別,訓練集類別,編號的圖片的最終地址
Parameters:
    image_lists-所有圖片的字典
    image_dir-圖片的路徑
    label_name-類別
    index-編號
    category-數據集類別
Returns:
    full_path-最終地址
CSDN:
    http://blog.csdn.net/qq_33431368
Modify:
    2018-2-20
"""
def get_image_path(image_lists, image_dir, label_name, index, category):
    #獲取給定類別中所有圖片的信息
    label_lists = image_lists[label_name]
    #根據所屬數據集的名稱獲取集合中的全部圖片信息
    category_list = label_lists[category]
    #圖片的編號
    mod_index = index%len(category_list)
    #相應編號的圖片的名稱,即圖片的文件名
    base_name = category_list[mod_index]
    #圖片所在文件的名稱
    sub_dir = label_lists['dir']
    #圖片的最終地址
    full_path = os.path.join(image_dir, sub_dir, base_name)
    return full_path

"""
函數說明: 得到Inception-v3 模型處理後的圖片的特徵向量的文件地址
Parameters:
    image_lists-所有圖片的字典
    image_dir-圖片的路徑
    label_name-類別
    index-編號
    category-數據集類別
Returns:
    get_image_path(image_lists, CACKE_DIR, label_name, index, category)
CSDN:
    http://blog.csdn.net/qq_33431368
Modify:
    2018-2-20
"""
def get_bottleneck_path(image_lists, label_name, index, category):
    return get_image_path(image_lists, CACKE_DIR, label_name, index, category)

"""
函數說明: 用訓練好的Inception-v3模型處理一張圖片,得到這個圖片的特徵向量
Parameters:
    sess-會話
    image_data-圖片數據
    image_data_tensor-圖片的張量
    bottleneck_tensor-瓶頸層張量
Returns:
    bottleneck_values-圖片的特徵向量(一維)
CSDN:
    http://blog.csdn.net/qq_33431368
Modify:
    2018-2-20
"""
def run_bottleneck_on_image(sess, image_data, image_data_tensor, bottleneck_tensor):
    bottleneck_values = sess.run(bottleneck_tensor, {image_data_tensor: image_data})
    #四維數組壓縮成一位數組
    bottleneck_values = np.squeeze(bottleneck_values)
    return bottleneck_values

"""
函數說明: 如果已經存有這個特徵向量直接返回
        若沒有,則建立文件夾,計算對應圖片的特徵向量
        並且存入對應文件夾(以txt的格式存儲)
Parameters:
    sess-會話
    image_lists-存有所有圖片數據字典
    label_name-類別
    index-編號
    category-數據集的種類
    jpeg_data_tensor-圖片數據張量
    bottleneck_tensor-瓶頸層張量
Returns:
    bottleneck_values-圖片的特徵向量(一維)
CSDN:
    http://blog.csdn.net/qq_33431368
Modify:
    2018-2-21
"""
def get_or_create_bottleneck(sess, image_lists, label_name, index,
                             category, jpeg_data_tensor, bottleneck_tensor):
    #獲取相應類別的圖片
    label_lists = image_lists[label_name]
    #獲取圖片的子文件夾名稱
    sub_dir = label_lists['dir']
    #緩存此類型圖片特徵向量對應的文件路徑
    sub_dir_path = os.path.join(CACKE_DIR, sub_dir)
    #如果不存在即還沒有得出特徵向量,則創建文件夾
    if not os.path.exists(sub_dir_path):
        os.makedirs(sub_dir_path)
    #得到Inception-v3 模型處理後的這個特定圖片的特徵向量的文件地址
    bottleneck_path = get_bottleneck_path(image_lists, label_name, index, category)
    #如果不存在則進行計算特徵向量並保存文件
    if not os.path.exists(bottleneck_path):
        #圖片具體路徑
        image_path = get_image_path(image_lists, INPUT_DATA, label_name, index, category)
        #讀入圖片的原始數據
        image_data = gfile.FastGFile(image_path, 'rb').read()
        #得到這個圖片的對應的特徵向量(一維)
        bottleneck_values = run_bottleneck_on_image(sess, image_data, jpeg_data_tensor,bottleneck_tensor)
        #用逗號連接成一個string 特徵向量用string的格式保存
        bottleneck_string = ','.join(str(x) for x in bottleneck_values)
        #保存到txt文件中
        with open(bottleneck_path, 'w') as bottleneck_file:
            bottleneck_file.write(bottleneck_string)
    else:
        #如果存在特徵向量的具體路徑 說明已經計算出這個特定圖片的特徵向量
        with open(bottleneck_path, 'r') as bottleneck_file:
            bottleneck_string = bottleneck_file.read()
            #還原特徵向量
            bottleneck_values = [float(x) for x in bottleneck_string.split(',')]
    return bottleneck_values
"""
函數說明: 得到隨機的一個batch的圖片作爲訓練數據
Parameters:
    sess-會話
    n_classes-類別的個數
    image_lists-存有所有圖片數據字典
    how_many-想得到的數據集的大小
    category-數據集的種類
    jpeg_data_tensor-圖片數據張量
    bottleneck_tensor-瓶頸層張量
Returns:
    bottlenecks-特徵向量data
    ground_truths-label
CSDN:
    http://blog.csdn.net/qq_33431368
Modify:
    2018-2-21
"""
def get_random_cached_bottlenecks(sess, n_classes, image_lists, how_many,
                                  category, jpeg_data_tensor, bottleneck_tensor):
    bottlenecks = []
    ground_truths = []
    for _ in range(how_many):
        #得熬一個隨機的圖片label
        label_index = random.randrange(n_classes)
        label_name = list(image_lists.keys())[label_index]
        #得到一個隨機的圖片編號
        image_index = random.randrange(65536)
        #得到一個特定數據集的隨機編號隨機label的圖片的特徵向量
        bottleneck = get_or_create_bottleneck(sess, image_lists, label_name, image_index
                                              , category, jpeg_data_tensor, bottleneck_tensor)
        #這個其實就相當於表示上面這個圖片特徵向量對應的label
        ground_truth = np.zeros(n_classes, dtype=np.float32)
        ground_truth[label_index] = 1.0

        #特徵向量的集合list 其實就是一個隨機的訓練batch
        bottlenecks.append(bottleneck)# data
        ground_truths.append(ground_truth)# label

    return bottlenecks, ground_truths
"""
函數說明: 得到全部的測試集數據
Parameters:
    sess-會話
    image_lists-存有所有圖片數據字典
    n_classes-類別的個數
    jpeg_data_tensor-圖片數據張量
    bottleneck_tensor-瓶頸層張量
Returns:
        bottlenecks-特徵向量data
    ground_truths-label
CSDN:
    http://blog.csdn.net/qq_33431368
Modify:
    2018-2-21
"""
def get_te_bottlenecks(sess, image_lists, n_classes, jpeg_data_tensor, bottleneck_tensor):
    bottlenecks = []
    ground_truths = []
    #得到label種類的一個列表
    label_name_list = list(image_lists.keys())
    #python的內置函數enumerate得到label_index爲類別的編號, label_name爲這個類別的名稱
    for label_index, label_name in enumerate(label_name_list):
        category = 'testing'
        #index爲圖片的編號, unused_base_name爲 圖片的名稱 圖片爲特定label的測試集
        for index, unused_base_name in enumerate(image_lists[label_name][category]):
            #得到特徵向量
            bottleneck = get_or_create_bottleneck(sess, image_lists, label_name, index
                                                  , category, jpeg_data_tensor, bottleneck_tensor)
            #得到label數據
            ground_truth = np.zeros(n_classes, dtype=np.float32)
            ground_truth[label_index] = 1.0
            # 特徵向量的集合list 其實就是一個隨機的訓練batch
            bottlenecks.append(bottleneck)# 特徵向量
            ground_truths.append(ground_truth)# label
    return bottlenecks, ground_truths

"""
函數說明:  主函數
CSDN:
    http://blog.csdn.net/qq_33431368
Modify:
    2018-2-21
"""
def main(argv=None):
    # 獲取所有圖片
    image_lists = create_image_lists(TEST_PERCENTAGE, VALIDATION_PERCENTAGE)
    # 得到類別的個數
    n_classes = len(image_lists.keys())
    # 讀取訓練好的Inception-v3模型
    with gfile.FastGFile(os.path.join(MODEL_DIR, MODEL_FILE), 'rb') as f:
        graph_def = tf.GraphDef()
        graph_def.ParseFromString(f.read())
    # 加載讀取這個模型,得到瓶頸層張量,和數據輸入所對應的張量
    bottleneck_tensor, jpeg_data_tensor = tf.import_graph_def(graph_def,
                                          return_elements=[BOTTLENECK_TENSOR_NAME, JPEG_DATA_TENSOR_NAME])
    # 定義新的神經網絡輸入,即新的圖片經過模型之後前向傳播到達瓶頸層時的節點取值(特徵提取)
    bottleneck_input = tf.placeholder(
        tf.float32, [None, BOTTLENECK_TENSOR_SIZE], name='BottleneckInputPlaceholder')
    #定義新的標準答案輸入
    ground_truth_input = tf.placeholder(tf.float32, [None, n_classes], name='GroundTruthInput')
    #定義一層全鏈接神經網絡
    with tf.name_scope('final_training_ops'):
        # 權重和偏置
        weights = tf.Variable(tf.truncated_normal(
            [BOTTLENECK_TENSOR_SIZE, n_classes], stddev=0.1))
        biases = tf.Variable(tf.zeros([n_classes]))
        # 求出前向傳播算法的結果
        logits = tf.matmul(bottleneck_input, weights)+biases
        #通過激活函數去線性化
        final_tensor = tf.nn.softmax(logits)
    #定義交叉損失函數
    cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits, labels=ground_truth_input)
    #算平均損失
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    #優化算法來優化損失函數
    train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(cross_entropy_mean)
    #計算正確率
    with tf.name_scope('evaluation'):
        #argmax(a,axis=1)返回每一行中最大值的索引
        #equal 相等爲 True 不相等爲False
        correct_prediction = tf.equal(tf.argmax(final_tensor, 1), tf.argmax(ground_truth_input, 1))
        #cast將True轉換爲1.0 False轉換爲0.0  之後算平均就爲正確率
        evaluation_step = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    with tf.Session() as sess:
        init = tf.global_variables_initializer()
        sess.run(init)
        #訓練過程
        for i in range(STEPS):
            #獲取一個batch訓練數據
            train_bottlenecks, train_ground_truth = \
                get_random_cached_bottlenecks(sess, n_classes,
                                              image_lists, BATCH,
                                              'training',
                                              jpeg_data_tensor,
                                              bottleneck_tensor)
            sess.run(train_step, feed_dict={bottleneck_input: train_bottlenecks,
                                            ground_truth_input: train_ground_truth})

            #在驗證數據上 測試正確率
            if i % 100 == 0 or i + 1 == STEPS:
                validation_bottlenecks, validation_ground_truth =\
                get_random_cached_bottlenecks(sess, n_classes,
                                              image_lists, BATCH,
                                              'validation',
                                              jpeg_data_tensor,
                                              bottleneck_tensor)
                validation_acc = sess.run(evaluation_step, feed_dict={
                    bottleneck_input: validation_bottlenecks, ground_truth_input: validation_ground_truth})
                print('Step %d: Valdation acc on random sampled %d examples = %.1f%%'%
                      (i, BATCH, validation_acc*100))
        #在測試集測試正確率
        test_bottlenecks, test_ground_truth = \
                get_te_bottlenecks(sess, image_lists, n_classes, jpeg_data_tensor, bottleneck_tensor)
        test_acc = sess.run(evaluation_step, feed_dict={
                    bottleneck_input: test_bottlenecks, ground_truth_input: test_ground_truth})
        print("Final test acc = %.1f%%" % (test_acc*100))

if __name__ == '__main__':
    tf.app.run()

經過大概40多分鐘(30分鐘的處理數據得到特徵文件,訓練10分鐘)的等待 結果如下
這裏寫圖片描述

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