最近學習了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字典,很容易地定義需要保持的變量及對應名稱:鍵對應使用的名稱,值對應被管理的變量。
增加:
對於代碼中數據集增加,代碼會報錯,這里有同學發現了解決辦法

