深度學習是人工智能領域近年來最火熱的話題之一,可是對於我的來講,以往想要玩轉深度學習除了要具有高超的編程技巧,還須要有海量的數據和強勁的硬件。不過 TensorFlow 和 Keras 等框架的出現大大下降了編程的複雜度,而遷移學習的思想也容許咱們利用現有的模型加上少許數據和訓練時間,取得不俗的效果。html
這篇文章將示範如何利用遷移學習訓練一個能從圖片中分類不一樣種類的花的模型,它在五種花中能達到 80% 以上的準確度(比瞎蒙高了 60% 哦),並且只須要普通的家用電腦就能夠完成訓練過程。node
人類的思惟能夠將一個領域學習到的知識和經驗,應用到其餘類似的領域中去。因此當面臨新的情景時,若是該情景與以前的經驗越類似,那麼人就能越快掌握該領域的知識。而傳統的機器學習方法則會把不一樣的任務當作是徹底獨立的,好比一個識別貓的模型,若是訓練集中的圖片都是白天的,那麼訓練出來的模型對於識別夜晚的貓這個任務就可能表現得很是差。遷移學習即是受此啓發,試圖將模型從源任務上訓練到的知識遷移到目標任務的應用上。python
舉例說,源任務能夠是識別圖片中車輛,而目標任務能夠是識別卡車,識別轎車,識別公交車等。合理的使用遷移學習能夠避免針對每一個目標任務單獨訓練模型,從而極大的節約了計算資源。git
此外,遷移學習並非一種特定的機器學習模型,它更像是一種優化技巧。一般來講,機器學習任務要求測試集和訓練集有相同的機率分佈,然而在一些狀況下每每會缺少足夠大的有針對性的數據集來知足一個特定的訓練任務。遷移學習提出咱們能夠在一個通用的大數據集上進行必定量的訓練後,再用針對性的小數據集進一步強化訓練。github
接下來的例子中將示範如何將一個圖像識別的深度卷積網絡,VGG,遷移到識別花朵類型的新任務上,在原先的任務中,VGG 只能識別花,可是遷移學習可讓模型不但能識別花,還能識別花的具體品種。編程
VGG 是視覺領域競賽 ILSVRC 在 2014 年的獲勝模型,以 7.3% 的錯誤率在 ImageNet 數據集上大幅刷新了前一年 11.7% 的世界紀錄。VGG16 基本上繼承了 AlexNet 深的思想,而且發揚光大,作到了更深。AlexNet 只用到了 8 層網絡,而 VGG 的兩個版本分別是 16 層網絡版和 19 層網絡版。在接下來的遷移學習實踐中,咱們會採用稍微簡單的一些的 VGG16,他和 VGG19 有幾乎徹底同樣的準確度,可是運算起來更快一些。數組
VGG 的結構圖以下:網絡
VGG 的輸入數據格式是 244 * 224 * 3 的像素數據,通過一系列的卷積神經網絡和池化網絡處理以後,輸出的是一個 4096 維的特徵數據,而後再經過 3 層全鏈接的神經網絡處理,最終由 softmax 規範化獲得分類結果。app
VGG16 模型能夠經過這裏下載(密碼 78g9),模型是一個. npy 文件,本質上是一個巨大的 numpy 對象,包含了 VGG16 模型中的全部參數,該文件大約有 500M,因此可見若是是從頭訓練這樣一個模型是很是耗時的,藉助於遷移學習的思想,咱們能夠直接在這個模型的基礎上進行訓練。框架
咱們要使用的花數據集能夠在這裏下載。
該數據集有包含以下數據:
花的種類 | 圖片數量(張) |
---|---|
daisy | 633 |
dandelion | 898 |
roses | 641 |
sunflowers | 699 |
tulips | 799 |
有了預備知識以後,咱們能夠開始搭建屬於本身的識花網絡了。
首先咱們會將全部的圖片交給 VGG16,利用 VGG16 的深度網絡結構中的五輪卷積網絡層和池化層,對每張圖片獲得一個 4096 維的特徵向量,而後咱們直接用這個特徵向量替代原來的圖片,再加若干層全鏈接的神經網絡,對花朵數據集進行訓練。
所以本質上,咱們是將 VGG16 做爲一個圖片特徵提取器,而後在此基礎上再進行一次普通的神經網絡學習,這樣就將原先的 244 * 224 * 3 維度的數據轉化爲了 4096 維的,而每一維度的信息量大大提升,從而大大下降了計算資源的消耗,實現了把學習物體識別中獲得的知識應用到特殊的花朵分類問題上。
爲了更加方便的使用 VGG 網絡,咱們能夠直接使用 tensorflow 提供的 VGG 加載模塊,該模塊能夠在這裏下載。
首先保證代碼或者 jupyter notebook 運行的工做目錄下有 flowerphotos,tensorflowvgg 這兩個文件夾,分別是花朵數據集和 tensorflowvgg,而後將以前下載的 VGG16 拷貝到 tensorflowvgg 文件夾中。
├── transfer_learning.py(運行代碼) ├── flower_phtots │ ├── daisy │ ├── dandelion │ ├── roses │ └── ... └── tensorflow_vgg ├── vgg16.py ├── vgg16.npy └── ...
而後導入須要用的 python 模塊
import os import numpy as np import tensorflow as tf from tensorflow_vgg import vgg16 from tensorflow_vgg 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)]
# 首先設置計算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))
這樣咱們就能夠獲得一個 codes 數組,和一個 labels 數組,分別存儲了全部花朵的特徵值和類別。
能夠用以下的代碼將這兩個數組保存到硬盤上:
with open('codes', 'w') as f: codes.tofile(f) import csv with open('labels', 'w') as f: writer = csv.writer(f, delimiter='\n') writer.writerow(labels)
一次嚴謹的模型訓練必定是要包含驗證和測試這兩個部分的。首先我把 labels 數組中的分類標籤用 One Hot Encode 的方式替換。
from sklearn.preprocessing import LabelBinarizer lb = LabelBinarizer() lb.fit(labels) labels_vecs = lb.transform(labels)
接下來就是抽取數據,由於不一樣類型的花的數據數量並非徹底同樣的,並且 labels 數組中的數據也尚未被打亂,因此最合適的方法是使用 StratifiedShuffleSplit 方法來進行分層隨機劃分。假設咱們使用訓練集:驗證集:測試集 = 8:1:1,那麼代碼以下:
from sklearn.model_selection import StratifiedShuffleSplit 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)
這時若是咱們輸出數據的維度,應該會獲得以下結果:
Train shapes (x, y): (2936, 4096) (2936, 5) Validation shapes (x, y): (367, 4096) (367, 5) Test shapes (x, y): (367, 4096) (367, 5)
分好了數據集以後,就能夠開始對數據集進行訓練了,假設咱們使用一個 256 維的全鏈接層,一個 5 維的全鏈接層(由於咱們要分類五種不一樣類的花朵),和一個 softmax 層。固然,這裏的網絡結構能夠任意修改,你能夠不斷嘗試其餘的結構以找到合適的結構。
# 輸入數據的維度 inputs_ = tf.placeholder(tf.float32, shape=[None, codes.shape[1]]) # 標籤數據的維度 labels_ = tf.placeholder(tf.int64, shape=[None, labels_vecs.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) # 計算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) # 獲得最後的預測分佈 predicted = tf.nn.softmax(logits) # 計算準確度 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
如今能夠運行訓練了,
# 運行多少輪次 epochs = 20 # 統計訓練效果的頻率 iteration = 0 # 保存模型的保存器 saver = tf.train.Saver() with tf.Session() as sess: sess.run(tf.global_variables_initializer()) for e in range(epochs): for x, y in get_batches(train_x, train_y): feed = {inputs_: x, labels_: y} # 訓練模型 loss, _ = sess.run([cost, 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 = {inputs_: val_x, labels_: val_y} val_acc = sess.run(accuracy, feed_dict=feed) # 輸出用驗證機驗證訓練進度 print("Epoch: {}/{}".format(e, epochs), "Iteration: {}".format(iteration), "Validation Acc: {:.4f}".format(val_acc)) # 保存模型 saver.save(sess, "checkpoints/flowers.ckpt")
接下來就是用測試集來測試模型效果
with tf.Session() as sess: saver.restore(sess, tf.train.latest_checkpoint('checkpoints')) feed = {inputs_: test_x, labels_: test_y} test_acc = sess.run(accuracy, feed_dict=feed) print("Test accuracy: {:.4f}".format(test_acc))
最終我在本身電腦上獲得了 88.83% 的準確度,你能夠繼續調整 batch 的大小,或者模型的結構以獲得一個更好的結果。
對這張有一個七星瓢蟲的蒲公英圖
模型給出的預測值以下
能夠看出模型的效果仍是至關穩定的,並且整個過程當中咱們的計算時間不過超過 30 分鐘,這就是遷移學習的魅力。
固然,其餘的深度學習框架也能夠很方便的實現遷移學習,好比這裏的 Keras 代碼用大約 20 行實現了一個 VGG 遷移識別狗的品種的分類器。