TensorFlow遷移學習的識別花試驗


最近學習了TensorFlow,發現一個模型叫vgg16,然后搭建環境跑了一下,覺得十分神奇,而且准確率十分的高。又上了一節選修課,關於人工智能,老師讓做一個關於人工智能的試驗,於是覺得vgg16很不錯,可以直接用。

但發現vgg16是訓練好的模型,拿來直接用太沒水平,於是網上發現說可以用vgg16進行遷移學習。

我理解的遷移學習:

遷移學習符合人們學習的過程,如果要學習一樣新東西,我們肯定會運用或是借鑒之前的學習經驗,這樣能夠快速的把握要點,能夠快速的學習。遷移學習也是如此。

vgg16模型是前人訓練出的能夠識別1000種物品的模型,而且識別率很高,它的模型如圖:

可以數出綠色的模塊一共有16層,通過多層的卷積和池化,會提取圖片特征值,然后把圖片壓縮成一個一維數組輸入到全連接層中,圖中有三層全連接層fc1,fc2,fc3,再經過softmax輸出概率分布的預測結果。

進過試驗,這個模型能夠很好的識別花,但是如果要在此基礎上識別多種花,還需要在此基礎上進行訓練,但是訓練的過程將簡化很多。接下來通過代碼來講解

項目:

Transfer_learning:

  --checkpoint    #用來保存模型

      --   #自動生成的四個文件

  --flower_photos   #圖片

       --daisy

  --dandeline

  --roses

  --sunflowers

  --tulips

        #vgg16模型

  --utils.py

 --vgg16.npy

 --vgg16.py

  --app.py

  --ftrain.py

  --get_features.py

  --transfer_test.py

  --transfer_train.py

  --codes.npy #存儲圖片特征值

  --labels #存儲圖片的標簽

  --1.jpg  #檢測圖片

  --.......

第一步獲取圖片的特征,對於vgg16模型,fc1層之前所做的就是提取圖片特征,我們無需再費事去訓練模型去提取圖片的特征,而是直接使用它提供的完善的模型去提取特征,並且它能夠很好的把握圖片的特征,然后它這些特征存儲起來,用於下面的訓練。

get_features.py

#coding=utf-8
import os
import numpy as np
import tensorflow as tf
import vgg16
import utils


#接下來我們將 flower_photos 文件夾中的花朵圖片都載入到進來,並且用圖片所在的子文件夾作為標簽值。
data_dir = 'flower_photos/'
contents = os.listdir(data_dir)
classes = [each for each in contents if os.path.isdir(data_dir + each)]

#利用vgg16計算得到特征值
# 首先設置計算batch的值,如果運算平台的內存越大,這個值可以設置得越高
batch_size = 10
# 用codes_list來存儲特征值
codes_list = []
# 用labels來存儲花的類別
labels = []
# batch數組用來臨時存儲圖片數據
batch = []

codes = None


with tf.Session() as sess:
    # 構建VGG16模型對象
    vgg = vgg16.Vgg16()
    input_ = tf.placeholder(tf.float32, [None, 224, 224, 3])
    with tf.name_scope("content_vgg"):
        # 載入VGG16模型
        vgg.build(input_)
    
    # 對每個不同種類的花分別用VGG16計算特征值
    for each in classes:
        print ("Starting {} images".format(each))
        class_path = data_dir + each
        files = os.listdir(class_path)
        for ii, file in enumerate(files, 1):
            # 載入圖片並放入batch數組中
            img = utils.load_image(os.path.join(class_path, file))
            batch.append(img.reshape((1, 224, 224, 3)))
            labels.append(each)
            
            # 如果圖片數量到了batch_size則開始具體的運算
            if ii % batch_size == 0 or ii == len(files):
                images = np.concatenate(batch)

                feed_dict = {input_: images}
                # 計算特征值
                codes_batch = sess.run(vgg.relu6, feed_dict=feed_dict)
                
                # 將結果放入到codes數組中
                if codes is None:
                    codes = codes_batch
                else:
                    codes = np.concatenate((codes, codes_batch))
                
                # 清空數組准備下一個batch的計算
                batch = []
                print ('{} images processed'.format(ii))
#code is a two-dimensional array including features of all pictures
#這樣我們就可以得到一個 codes 數組,和一個 labels 數組,分別存儲了所有花朵的特征值和類別。
#可以用如下的代碼將這兩個數組保存到硬盤上:        
#with open('codes', 'w') as f:
np.save("codes.npy",codes)
    #codes.tofile(f)
    #not good size of file is too big
    #pickle.dump(codes,f)
    
import csv
with open('labels', 'w') as f:
    writer = csv.writer(f, delimiter='\n')
    writer.writerow(labels)
    #pickle.dump(labels,f)
    

進過上面代碼我們已經得到了圖片的特征值和標簽,接下來,我們需要設置一個全連接層來訓練,下面代碼中增加了一層256個節點和5個節點的全連接層。

ftrain.py

#coding=utf-8
import os
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelBinarizer
import vgg16
import utils
from sklearn.model_selection import StratifiedShuffleSplit

#模型保存的路徑和名稱
MODEL_SAVE_PATH="./checkpoints/"
MODEL_NAME="flowers.ckpt"
LABELS = "labels"
CODES = "codes.npy"
codes = None
label = []
labels = []

if CODES:
    codes = np.load(CODES)
else:
    print ("No such file,please run get_feature.py first")

if LABELS:
    with open(LABELS,"r") as f: 
        label = f.readlines()
        for line in label:
            line = line.strip()
            labels.append(line)
else:
    print ("No such file,please run get_feature.py first")
#准備訓練集,驗證集和測試集
#首先我把 labels 數組中的分類標簽用 One Hot Encode 的方式替換
lb = LabelBinarizer()
lb.fit(labels)
labels_vecs = lb.transform(labels)
#return codes,labels,labels_vecs

'''
接下來就是抽取數據,因為不同類型的花的數據數量並不是完全一樣的,
而且 labels 數組中的數據也還沒有被打亂,
所以最合適的方法是使用 StratifiedShuffleSplit 方法來進行分層隨機划分。
假設我們使用訓練集:驗證集:測試集 = 8:1:1,那么代碼如下:
'''
ss = StratifiedShuffleSplit(n_splits=1, test_size=0.2)

train_idx, val_idx = next(ss.split(codes, labels))

half_val_len = int(len(val_idx)/2)
val_idx, test_idx = val_idx[:half_val_len], val_idx[half_val_len:]

train_x, train_y = codes[train_idx], labels_vecs[train_idx]
val_x, val_y = codes[val_idx], labels_vecs[val_idx]
test_x, test_y = codes[test_idx], labels_vecs[test_idx]

print ("Train shapes (x, y):", train_x.shape, train_y.shape)
print ("Validation shapes (x, y):", val_x.shape, val_y.shape)
print ("Test shapes (x, y):", test_x.shape, test_y.shape)

#訓練網絡
'''
分好了數據集之后,就可以開始對數據集進行訓練了,
假設我們使用一個 256 維的全連接層,
一個 5 維的全連接層(因為我們要分類五種不同類的花朵),
和一個 softmax 層。當然,這里的網絡結構可以任意修改,
你可以不斷嘗試其他的結構以找到合適的結構。
'''
# 輸入數據的維度

# 標簽數據的維度
labels_ = tf.placeholder(tf.int64, shape=[None, labels_vecs.shape[1]])
inputs_ = tf.placeholder(tf.float32, shape=[None, codes.shape[1]])
# 加入一個256維的全連接的層

fc = tf.contrib.layers.fully_connected(inputs_, 256)

# 加入一個5維的全連接層
logits = tf.contrib.layers.fully_connected(fc, labels_vecs.shape[1], activation_fn=None)

# 得到最后的預測分布
predicted = tf.nn.softmax(logits)

# 計算cross entropy值
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=labels_, logits=logits)

# 計算損失函數
cost = tf.reduce_mean(cross_entropy)

# 采用用得最廣泛的AdamOptimizer優化器
optimizer = tf.train.AdamOptimizer().minimize(cost)
correct_pred = tf.equal(tf.argmax(predicted, 1), tf.argmax(labels_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))


#為了方便把數據分成一個個 batch 以降低內存的使用,還可以再用一個函數專門用來生成 batch。
def get_batches(x, y, n_batches=10):
    """ 這是一個生成器函數,按照n_batches的大小將數據划分了小塊 """
    batch_size = len(x)//n_batches
    
    for ii in range(0, n_batches*batch_size, batch_size):
        # 如果不是最后一個batch,那么這個batch中應該有batch_size個數據
        if ii != (n_batches-1)*batch_size:
            X, Y = x[ii: ii+batch_size], y[ii: ii+batch_size] 
        # 否則的話,那剩余的不夠batch_size的數據都湊入到一個batch中
        else:
            X, Y = x[ii:], y[ii:]
        # 生成器語法,返回X和Y
        yield X, Y

經過上面的代碼,已經把圖片集分成三部分,而且也設置好全連接成,接下來需要把圖片特征喂入,開始訓練模型。

transfer_train.py

import os
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
import vgg16
import utils
import ftrain


#運行
# 運行多少輪次
epochs = 20
# 統計訓練效果的頻率
iteration = 0
# 保存模型的保存器
saver = tf.train.Saver()

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    coord = tf.train.Coordinator()#4
    threads = tf.train.start_queue_runners(sess=sess, coord=coord)#5
    
    for e in range(epochs):
        for x, y in ftrain.get_batches(ftrain.train_x, ftrain.train_y):
            feed = {ftrain.inputs_: x,
                    ftrain.labels_: y}
            # 訓練模型
            
            loss, _ = sess.run([ftrain.cost, ftrain.optimizer], feed_dict=feed)
            print ("Epoch: {}/{}".format(e+1, epochs),
                "Iteration: {}".format(iteration),
                "Training loss: {:.5f}".format(loss))
            iteration += 1
            
            if iteration % 5 == 0:
                feed = {ftrain.inputs_: ftrain.val_x,
                        ftrain.labels_: ftrain.val_y}
                
                val_acc = sess.run(ftrain.accuracy, feed_dict=feed)
                # 輸出用驗證機驗證訓練進度
                print ("Epoch: {}/{}".format(e, epochs),
                    "Iteration: {}".format(iteration),
                    "Validation Acc: {:.4f}".format(val_acc))
        # 保存模型
    saver.save(sess, os.path.join(ftrain.MODEL_SAVE_PATH, ftrain.MODEL_NAME))

在控制台的輸出結果中,我們可以看到隨着迭代次數的增加,損失值在不斷的降低,精確性也在提高。把訓練好的模型保存起來,接下來用測試集來測試模型。

 

transfer_test.py

#coding=utf-8
import os
import numpy as np
import tensorflow as tf
import ftrain
import vgg16
import utils


#用測試集來測試模型效果
saver = tf.train.Saver()
with tf.Session() as sess:
    saver.restore(sess,tf.train.latest_checkpoint(ftrain.MODEL_SAVE_PATH))
    feed = {ftrain.inputs_: ftrain.test_x,
            ftrain.labels_: ftrain.test_y}  
    test_acc = sess.run(ftrain.accuracy,feed_dict=feed)
    print ("Test accuracy: {:.4f}".format(test_acc))

測試代碼,加載訓練好的模型,然后把測試集代碼喂入,計算精確度,控制台可以看到結果,我的結果達到90%以上

然后可以應用這個模型,來識別圖片了,這個模型可以識別5中話,可以自己增加。

app.py

#coding=utf-8
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
import vgg16
import utils
import ftrain

def per_picture():
    #deal with picture
    testPicArr = []
    img_path = input('Input the path and image name:')
    img_ready = utils.load_image(img_path) 
    testPicArr.append(img_ready.reshape((1,224,224,3)))
    images = np.concatenate(testPicArr)
    return images


labels_vecs = ['daisy','dandelion','roses','sunflower','tulips']
labels_vecs = np.array(labels_vecs)
fig=plt.figure(u"Top-5 預測結果") 

saver = tf.train.Saver()
with tf.Session() as sess:
            #圖片預處理
    images = per_picture()
            #輸入vgg16中計算特征值
    vgg = vgg16.Vgg16()
    input_ = tf.placeholder(tf.float32, [None, 224, 224, 3])
    with tf.name_scope("content_vgg"):
    # 載入VGG16模型
        vgg.build(input_)
    feed_dict = {input_: images}
    # 計算特征值
    codes_batch = sess.run(vgg.relu6, feed_dict=feed_dict)
    #返回y矩陣中最大值的下標,如果是二維的加1
    preValue = tf.argmax(ftrain.predicted, 1)   
    #加載訓練好的新模型
    saver.restore(sess, tf.train.latest_checkpoint(ftrain.MODEL_SAVE_PATH))
    #計算預測值
    preValue = sess.run(preValue, feed_dict={ftrain.inputs_:codes_batch})
    print ("The prediction flower is:", labels_vecs[preValue])
    probability = sess.run(ftrain.predicted, feed_dict={ftrain.inputs_:codes_batch})
    top5 = np.argsort(probability[0])
    print ("top5:",top5)
    values = []
    bar_label = []
    for n, i in enumerate(top5): 
        print ("n:",n)
        print ("i:",i)
        values.append(probability[0][i]) 
        bar_label.append(labels_vecs[i]) 
        print (i, ":", labels_vecs[i], "----", utils.percent(probability[0][i])) 
    ax = fig.add_subplot(111) 
    ax.bar(range(len(values)), values, tick_label=bar_label, width=0.5, fc='g')
    ax.set_ylabel(u'probabilityit') 
    ax.set_title(u'Top-5') 
    for a,b in zip(range(len(values)), values):
        ax.text(a, b+0.0005, utils.percent(b), ha='center', va = 'bottom', fontsize=7)   
    plt.show() 

上面代碼過程是,首先要通過vgg16提取該圖片的特征值,然后進行圖片預處理,加載訓練好的模型,輸入進去,獲得結果。

結果展示如下:

       

可以看到准確率很高,我每種圖片只訓練了144張,數據集有800多張。

以上代碼可以在windows 下python3的環境運行。

代碼和數據集參考自:https://cosx.org/2017/10/transfer-learning/

 

對於實現以上過程遇到的問題:

 (1)首先對遷移學習的理解

  對於這個簡單的試驗,遷移學習主要體現在使用訓練好的vgg16模型(存儲在vgg16.npy中)提取圖片的特征值,然后再對這些特征值訓練。

(2)模型的保存和加載

  為了避免反復的去提取圖片特征值(這個很耗時間),把特征值保存在codes.npy,把圖片的標簽存儲在labels中。還有就是存儲訓練模型,這里因為對TensorFlow模型不太了解,所以出一個問題

存儲模型后,在另一個文件中加載模型發現訓練的模型和沒訓練的模型一樣

  原因是因為在另一個文件中使用的不是同一個saver,我又對saver進行了初始化,導致使用了一個嶄新的模型。

(3)模型保存優化部分:

  如果你不給tf.train.Saver()傳入任何參數,那么saver將處理graph中的所有變量。其中每一個變量都以變量創建時傳入的名稱被保存。

有時候在檢查點文件中明確定義變量的名稱很有用。舉個例子,你也許已經訓練得到了一個模型,其中有個變量命名為"weights",你想把它的值恢復到一個新的變量"params"中。

有時候僅保存和恢復模型的一部分變量很有用。再舉個例子,你也許訓練得到了一個5層神經網絡,現在想訓練一個6層的新模型,可以將之前5層模型的參數導入到新模型的前5層中。

你可以通過給tf.train.Saver()構造函數傳入Python字典,很容易地定義需要保持的變量及對應名稱:鍵對應使用的名稱,值對應被管理的變量。

 

增加:

對於代碼中數據集增加,代碼會報錯,這里有同學發現了解決辦法

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM