代碼環境:
- python-3.7.6
- tensorflow-2.1.0
深度學習神經網絡的一個優勢是可以在相關問題上重用。遷移學習(Transfer learning)指的是對某種程度上相似的問題進行預測建模的技術,然後可以部分或全部重用該技術,以加快訓練速度並改善所關注問題的模型性能。
在深度學習中,這意味着在新模型中重用預訓練網絡模型中的一層或多層權重,或者保持權重固定,對其進行微調,或者在訓練模型時完全調整權重。
1. 遷移學習簡介
遷移學習通常是指對第二個相關問題使用某種方式訓練過的模型的過程。轉移學習(transfer learning)和領域適應(domain adaptation)指的是在一種情況下(即分佈P1)所學到的東西被用來改善另一種情況下的泛化能力(即分佈P2)。
這通常是在有監督的學習環境中理解的,其中輸入是相同的,但是目標可能具有不同的性質。例如,在第一個設置中瞭解一組視覺類別,例如貓和狗;然後在第二個設置中瞭解一組不同的視覺類別,例如螞蟻和黃蜂。
遷移學習的好處是減少了神經網絡模型的訓練時間,並降低了泛化誤差。遷移學習有兩種主要方法:
- 權重初始化。
- 特徵提取。
重用權重可以用作模型訓練的起點,並可以根據新問題進行調整。這種用法將轉移學習視爲一種權重初始化方法。當第一個相關問題比感興趣的問題具有更多的標記數據時,這可能會很有用,並且問題的結構相似性在兩種情況下都可能有用。權重重用的目的是利用第一個設置中的數據來提取信息,這些信息在學習甚至直接在第二個設置中進行預測時都可能有用。
可替代地,可以不響應於新問題而調整網絡的權重,僅訓練重用層之後的新層來解釋其輸出。這種用法將轉移學習視爲一種特徵提取方法。這種方法的一個例子是圖片文本描述,重用經過深層卷積神經網絡模型訓練用於照片分類的模型作爲特徵提取器。
這些用法的變化可能不涉及最初不針對新問題訓練模型的權重,而是稍後以較小的學習率對學習到的模型的所有權重進行微調。
2. 多分類問題實例
使用一個簡單的多類分類問題來探討遷移學習對模型性能的影響。在本部分創建具有兩個特徵(變量)和三個輸出(三類)的簡單實例。
2.1 構建數據
首先,使用scikit-learn庫中的 make_blobs()
函數創建兩個標準差爲2的隨機樣本集,對於不同的樣本數據集中,分別使用相同的隨機狀態(用於僞隨機數生成器的種子)來確保始終獲得相同的數據點。
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed)
其中X是二維數組,分別表示樣本的橫縱軸座標,y表示樣本標籤。
from sklearn.datasets import make_blobs
from numpy import where, random
import matplotlib.pyplot as plt
plt.rcParams['figure.dpi'] = 150
plt.style.use('ggplot')
def samples_for_seed(seed):
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed)
return X, y
def plot_samples(X, y, classes=3):
for i in range(classes):
# select indices of points with each class label
samples_ix = where(y == i)
# plot points for this class with a given color
plt.scatter(X[samples_ix, 0], X[samples_ix, 1])
n_problems = 2
for i in range(1, n_problems+1):
# specify subplot
plt.subplot(2,1,i)
# generate samples
X, y = samples_for_seed(i)
# scatter plot of samples
plot_samples(X, y)
plt.show()
這爲轉移學習提供了一個良好的基礎,因爲問題的每個版本都具有相似的輸入數據且規模相似,儘管目標信息不同(例如,聚類中心)。
2.2 問題1的MLP模型
爲問題1開發一個多層感知器模型(MLP),並將模型保存到文件中,以便以後可以重用權重。
from sklearn.datasets import make_blobs
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.utils import to_categorical
import matplotlib.pyplot as plt
# 準備數據
def samples_for_seed(seed):
# 生成分類樣本
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed)
# one-hot編碼
y = to_categorical(y)
# 劃分訓練集和驗證集
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
return trainX, trainy, testX, testy
def fit_model(trainX, trainy, testX, testy):
# 定義模型
model = Sequential()
model.add(Dense(5, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(3, activation='softmax'))
# 編譯模型
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
# 訓練模型
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
return model, history
def summarize_model(model, history, trainX, trainy, testX, testy):
# 評估模型
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))
# 繪製損失曲線
plt.subplot(211) # plt.subplot(2,1,1)
plt.title('Loss')
plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='test')
plt.legend()
# 繪製準確率曲線
plt.subplot(212) # plt.subplot(2,1,2)
plt.title('Accuracy')
plt.plot(history.history['accuracy'], label='train')
plt.plot(history.history['val_accuracy'], label='test')
plt.legend()
plt.tight_layout()
plt.show()
# 準備數據
trainX, trainy, testX, testy = samples_for_seed(1)
# 訓練模型
model, history = fit_model(trainX, trainy, testX, testy)
# 評估模型
summarize_model(model, history, trainX, trainy, testX, testy)
# 保存模型
model.save('model.h5')
輸出:
Train: 0.922, Test: 0.932
在這種情況下,可以看出該模型可以快速準確地學習到該問題,在大約40個epoch收斂,並且訓練集和驗證集上都很穩定。
2.3 問題2的MLP模型
首先,要了解模型在問題2上的性能基線,該基線可用於與使用轉移學習的模型相比較。
首先設置新的僞隨機數生成器的種子:
trainX, trainy, testX, testy = samples_for_seed(2)
其它保持不變,並且不保存模型,得到輸出:
# 準備數據
trainX, trainy, testX, testy = samples_for_seed(2)
# 訓練模型
model, history = fit_model(trainX, trainy, testX, testy)
# 評估模型
summarize_model(model, history, trainX, trainy, testX, testy)
輸出:
Train: 0.814, Test: 0.828
2.4 問題2使用遷移學習的MLP模型
from keras.models import load_model
def fit_model_2(trainX, trainy, testX, testy):
# 加載問題1的模型
model = load_model('model.h5')
# 編譯模型
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
# 重新訓練模型
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
return model, history
trainX, trainy, testX, testy = samples_for_seed(2)
model, history = fit_model_2(trainX, trainy, testX, testy)
summarize_model(model, history, trainX, trainy, testX, testy)
輸出:
Train: 0.822, Test: 0.828
在這種情況下,可以看出模型確實具有相似的學習曲線,測試集(橙色線)的學習曲線有了明顯的改善,無論是在更早的性能(從第20個epoch開始)還是從更好的性能方面而言高於模型在訓練集上的表現。
2.5 特徵提取與權重初始化性能對比
在每個循環中,加載在問題1上訓練的模型,適合問題2的訓練數據集,然後在問題2的測試集上進行評估。
另外,在加載的模型中配置0、1或2個隱藏層的權重保持固定。保持0個隱藏層固定不變,意味着在學習問題2時,將遷移學習作爲權重初始化方案,可以適應模型中的所有權重。而將2個隱藏層都保持固定意味着使用遷移學習作爲特徵提取方法在訓練期間僅適應模型的輸出層。
from numpy import mean, std
def fit_model_3(trainX, trainy):
model = Sequential()
model.add(Dense(5, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(5, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(3, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
model.fit(trainX, trainy, epochs=100, verbose=0)
return model
# 在基本模型上重複評估
def eval_standalone_model(trainX, trainy, testX, testy, n_repeats):
scores = []
for _ in range(n_repeats):
model = fit_model_3(trainX, trainy)
_, test_acc = model.evaluate(testX, testy, verbose=0)
scores.append(test_acc)
return scores
# 加載遷移學習模型並評估
def eval_transfer_model(trainX, trainy, testX, testy, n_fixed, n_repeats):
'''
n_fixed:表示加載的模型有多少層不可訓練,即權重保持不變。
'''
scores = []
for _ in range(n_repeats):
model = load_model('model.h5')
# 標記遷移學習模型的隱藏層是否可訓練
for i in range(n_fixed):
model.layers[i].trainable = False
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
model.fit(trainX, trainy, epochs=100, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
scores.append(test_acc)
return scores
trainX, trainy, testX, testy = samples_for_seed(2)
n_repeats = 10
dists, dist_labels = [], []
standalone_scores = eval_standalone_model(trainX, trainy, testX, testy, n_repeats)
print('Standalone %.3f (%.3f)' % (mean(standalone_scores), std(standalone_scores)))
dists.append(standalone_scores)
dist_labels.append('standalone')
# 設置不同數量的可訓練的隱藏層,重複評估遷移學習模型的性能
n_fixed = 3
for i in range(n_fixed):
scores = eval_transfer_model(trainX, trainy, testX, testy, i, n_repeats)
print('Transfer (fixed=%d) %.3f (%.3f)' % (i, mean(scores), std(scores)))
dists.append(scores)
dist_labels.append('transfer f='+str(i))
plt.boxplot(dists, labels=dist_labels)
plt.show()
輸出:
Standalone 0.769 (0.108)
Transfer (fixed=0) 0.828 (0.005)
Transfer (fixed=1) 0.819 (0.006)
Transfer (fixed=2) 0.792 (0.007)
在這種情況下,我們可以看到獨立模型在問題2上達到了約77%的準確率,標準差爲10%,所有遷移學習模型的標準差都較小。測試準確性分數的標準偏差之間的這種差異顯示了轉移學習可以爲模型帶來的穩定性,從而減少了通過隨機學習算法引入的最終模型的性能差異。
比較模型的平均測試準確性,可以看出,將模型用作權重初始化方法(fixed = 0)的遷移學習比獨立模型的性能更好,準確度率爲80%;將所有隱藏層固定(fixed = 2),即用作特徵提取方法會導致平均性能比獨立模型差;這表明這種方法在這種情況下過於嚴格。當第一隱藏層保持固定(fixed = 1),得到了最佳性能,約82%的測試分類精度。這表明在這種情況下,該問題將從轉移學習的特徵提取和權重初始化屬性中受益。在該模型中,第二個隱藏層(甚至是輸出層)的權重已用隨機數重新初始化。該比較證明遷移學習的特徵提取特性和權重初始化特性兩者都是有益的。
獨立模型的箱線圖顯示了許多異常值,這表明該模型在平均情況下表現良好,但有可能表現非常差。相反,看到帶有遷移學習的模型的表現更穩定,表現出更緊密的性能分佈。
參考:
https://machinelearningmastery.com/how-to-improve-performance-with-transfer-learning-for-deep-learning-neural-networks/