【調參17】如何使用貪婪逐層預訓練分析不同層數對深度神經網絡的影響



1. 貪婪逐層預訓練策略

訓練具有多個層次的深度神經網絡具有挑戰性。隨着隱藏層數量的增加,傳播回先前層的誤差信息數量將大量減少。這意味着,靠近輸出層的隱藏層中的權重會正常更新,而靠近輸入層的隱藏層中的權重會極少更新或根本沒有更新。通常,此問題阻止了非常深的神經網絡訓練,稱爲梯度消失問題(vanishing gradient problem)

最初,開發更深的神經網絡模型的一個重要里程碑是貪婪的逐層預訓練技術,通常簡稱爲預訓練(pretraining)

預訓練涉及到先後向模型添加新的隱藏層並進行重新擬合,以使新添加的模型可以從現有的隱藏層中學習輸入,而同時又要保持現有隱藏層的權重不變。模型一次添加一層進行訓練的技術稱爲逐層(layer-wise)

該技術之所以稱爲貪婪(greedy),是因爲採用分段或分層的方法來解決訓練深度網絡的較難問題。貪婪算法將一個問題分解爲多個部分,然後獨立解決每個部分的最佳版本。不幸的是,不能保證將各個最佳部分組合在一起就可以得出最佳的完整解決方案。

預訓練基於這樣的假設:較淺的網絡更容易訓練,並進行分層的訓練過程,而始終只適合於淺層模型。

1.1 預訓練的好處

  • 簡化訓練過程。
  • 促進建立更深層的網絡。
  • 用作權重初始化方案。
  • 可能獲得更低的泛化誤差。

1.2 預訓練的方法

預訓練有兩種主要方法:他們是:

  • 有監督的貪婪逐層預訓練;
  • 無監督的貪婪逐層預訓練。

通常使用 預訓練(pretraining) 一詞不僅指預訓練階段本身,而且指結合了預訓練階段和監督學習階段。監督學習階段可能涉及在預訓練階段之上訓練一個簡單的分類器,或者可能涉及對在預訓練階段學習的整個網絡進行有監督的微調。

當有大量未標記的樣本可用於初始化模型,然後使用數量更少的樣本來微調監督模型權重時,無監督預訓練可能是適當的。

儘管先前層中的權重保持恆定,但通常在添加最後一層之後最後微調網絡中的所有權重。這將預訓練視爲權重初始化方法的一種。它利用了這樣的思想,即爲深度神經網絡選擇初始參數可能會對模型產生顯着的正則化影響(並且在較小程度上可以改善優化)

貪婪的逐層預訓練是深度學習歷史中的一個重要里程碑,它允許早期開發具有比以前更多的隱藏層的網絡。如今,無監督的預訓練已基本被拋棄,除了在自然語言處理領域。例如,最佳實踐是對文本數據使用無監督預訓練,以便通過word2vec提供更豐富的單詞及其相互關係的分佈式表示(distributed representation)。

今天,現在知道訓練全連接的深度架構不需要貪婪的逐層預訓練,使用現代方法(例如更好的激活函數,權重初始化,梯度下降變體和正則化方法)可能會實現更好的性能;但是無監督的預訓練方法是第一個成功的方法。


2. 多分類問題

使用一個小的多類分類問題作爲基礎來證明貪婪的逐層預訓練對模型性能的影響。

scikit-learn類提供了 make_blobs()函數 ,該函數可用於創建具有指定數量的樣本,輸入變量,類以及一個類中樣本的方差的多類分類問題。

使用兩個輸入變量(代表點的x和y座標)和每個組中點的標準偏差2.0進行配置。使用相同的隨機狀態(用於僞隨機數生成器的種子)來確保始終獲得相同的數據點。


這裏班門弄斧一下,一開始對隨機種子也有很大的疑惑,比如np.random.seed(2) 中的“2”,一直以爲是生成隨機數的個數或者重複次數,其實這個理解是錯誤的;這個值表示隨機數生成的初始狀態或者說是第一個隨機數生成的標準,相當於提供了隨機數生成規則(或者說預定義的僞隨機數生成公式)中的初始值,只要這個種子值相同,生成的隨機序列就是相同的。看一下簡單的示例說明問題。
在這裏插入圖片描述
關於 random.seed(),numpy的主要貢獻者 Robert Kern 建議不要使用,而是使用 random.RandomState() 來代替,通過stack overflow 回答,總結主要原因如下:

  • 如果部署設置隨機種子的代碼,則會引入安全漏洞。
  • RandomState()實例基本上使用計算機內部時鐘來設置種子。
    在這裏插入圖片描述
    詳細討論,請查看文末最後兩篇參考文章。

創建虛擬樣本集完整代碼:

from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
from numpy import where

# 生成二維平面中的三類樣本點;
# 其中X的shape爲(1000,2),“2”代表樣本點的橫縱軸座標;y的shape爲(1000,)表示樣本標籤。
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=2)

# 設置繪圖的像素值爲200
plt.figure(dpi=200)

# 統計各類樣本的個數
samples_num = {}

# 繪製各類樣本的散點圖
for class_value in range(3):
    # s通過樣本標籤找出各類樣本對應的索引
    row_ix = where(y == class_value) # 返回元組
    # 統計各類樣本數量
    samples_num[class_value] = row_ix[0].shape[0]
    # 使用不同顏色繪製散點圖
    plt.scatter(X[row_ix, 0], X[row_ix, 1], label=f'class:{class_value},num:{samples_num[class_value]}')

plt.legend()
plt.show()

輸出:
在這裏插入圖片描述


2.1 有監督的 貪婪逐層預訓練

爲上述創建的多分類問題建立一個深層的多層感知器(MLP)模型來探討網絡層數的影響。


2.1.1 準備數據

首先,創建1000個樣本,並將它們均勻地分爲訓練和測試數據集。

from tensorflow.keras.utils import to_categorical
def prepare_data():
    # 生成樣本
    X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=2)
    # 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, testX, trainy, testy

2.1.2 定義模型

其次,創建基本的MLP模型,期望數據集中的兩個輸入變量有兩個輸入,包含10個節點,使用relu激活函數,並配置何愷明權重初始化。輸出層具有三個節點,使用softmax激活函數以預測三個類別中每個類別的概率。使用隨機梯度下降進行擬合,默認學習率爲0.01,動量值爲0.9。使用交叉熵損失函數進行優化。

from tensorflow.keras.layers import Dense
from tensorflow.keras.models import Sequential
from tensorflow.keras.optimizers import SGD

def get_base_model(trainX, trainy):
    # 模型定義
    model = Sequential()
    model.add(Dense(10, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
    model.add(Dense(3, activation='softmax'))
    # 模型編譯
    opt = SGD(lr=0.01, momentum=0.9)
    model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
    # 模型訓練
    model.fit(trainX, trainy, epochs=100, verbose=0)
    return model

2.1.3 評估模型

然後,定義模型評估方法。下面自定義的evaluate_model() 函數將訓練集和測試集以及模型作爲參數,並返回兩個數據集的準確性。

def evaluate_model(model, trainX, testX, trainy, testy):
    _, train_acc = model.evaluate(trainX, trainy, verbose=0)
    _, test_acc = model.evaluate(testX, testy, verbose=0)
    return train_acc, test_acc

2.1.4 貪婪逐層預訓練配置

現在,配置貪婪逐層預訓練。需要一個可以添加新的隱藏層並重新訓練模型,但僅更新新添加的層和輸出層中的權重的函數

首先,存儲當前輸出層,包括其配置和當前的權重。

output_layer = model.layers[-1]

其次,將其從模型中刪除。

model.pop()

然後,將模型中的所有其餘層標記爲不可訓練,這意味着當再次調用 fit() 函數時,它們的權重無法更新。

for layer in model.layers:
	layer.trainable = False

然後,添加一個新的隱藏層,其配置與基本模型中添加的第一個隱藏層相同。

model.add(Dense(10, activation='relu', kernel_initializer='he_uniform'))

然後,可以添加輸出層,並將模型重新擬合到訓練數據集上。

model.add(output_layer)
model.fit(trainX, trainy, epochs=100, verbose=0)

將其封裝爲add_layer()函數,方便調用。

def add_layer(model, trainX, trainy):
    # 保留輸出層,以便添加新的隱藏層
    output_layer = model.layers[-1]
    model.pop()
    
    # 將之前的層設置爲不可訓練,以保證權重不更新
    for layer in model.layers:
        layer.trainable = False
        
    # 添加隱藏層,其配置與基本模型的第一層相同
    model.add(Dense(10, activation='relu', kernel_initializer='he_uniform'))
    model.add(output_layer)
    
    # 訓練模型
    model.fit(trainX, trainy, epochs=100, verbose=0)

最後,根據希望添加到模型中的層數來重複調用此函數。對於模型中的層數,訓練和測試準確率存儲在字典中。

n_layers = 10
for i in range(n_layers):
    # 添加隱藏層
    add_layer(model, trainX, trainy)
    # 評估模型
    train_acc, test_acc = evaluate_model(model, trainX, testX, trainy, testy)
    print('> layers=%d, train=%.3f, test=%.3f' % (len(model.layers), train_acc, test_acc))
    # 將準確率存儲到字典,方便繪圖
    scores[len(model.layers)] = (train_acc, test_acc)

plt.plot(list(scores.keys()), [scores[k][0] for k in scores.keys()], label='train', marker='.')
plt.plot(list(scores.keys()), [scores[k][1] for k in scores.keys()], label='test', marker='.')
plt.legend()
plt.show()

2.1.5 完整代碼

from sklearn.datasets import make_blobs
from tensorflow.keras.layers import Dense
from tensorflow.keras.models import Sequential
from tensorflow.keras.optimizers import SGD
from tensorflow.keras.utils import to_categorical
import matplotlib.pyplot as plt
plt.rcParams['figure.dpi'] = 200

# 1.準備數據
def prepare_data():
    # 生成樣本
    X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=2)
    # one-hot編碼
    y = to_categorical(y) # tf.keras.utils.to_categorical
    # 訓練集、驗證集(測試集)劃分
    n_train = 500
    trainX, testX = X[:n_train, :], X[n_train:, :]
    trainy, testy = y[:n_train], y[n_train:]
    return trainX, testX, trainy, testy

# 2.定義模型
def get_base_model(trainX, trainy):
    # 模型定義
    model = Sequential()
    model.add(Dense(10, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
    model.add(Dense(3, activation='softmax'))
    # 模型編譯
    opt = SGD(lr=0.01, momentum=0.9)
    model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
    # 模型訓練
    model.fit(trainX, trainy, epochs=100, verbose=0)
    return model

# 3.評估模型
def evaluate_model(model, trainX, testX, trainy, testy):
    _, train_acc = model.evaluate(trainX, trainy, verbose=0)
    _, test_acc = model.evaluate(testX, testy, verbose=0)
    return train_acc, test_acc

# 4.貪婪逐層預訓練配置
def add_layer(model, trainX, trainy):
    # 保留輸出層,以便添加新的隱藏層
    output_layer = model.layers[-1]
    model.pop()
    
    # 將之前的層設置爲不可訓練,以保證權重不更新
    for layer in model.layers:
        layer.trainable = False
        
    # 添加隱藏層,其配置與基本模型的第一層相同
    model.add(Dense(10, activation='relu', kernel_initializer='he_uniform'))
    model.add(output_layer)
    
    # 訓練模型
    model.fit(trainX, trainy, epochs=100, verbose=0)
    
# 準備數據
trainX, testX, trainy, testy = prepare_data()
# 基本模型
model = get_base_model(trainX, trainy)
# 創建字典,用於保存不同隱藏層模型的準確率
scores = {}
# 訓練與評估
train_acc, test_acc = evaluate_model(model, trainX, testX, trainy, testy)
# 打印準確率
print('> layers=%d, train=%.3f, test=%.3f' % (len(model.layers), train_acc, test_acc))
# 保存準確率
scores[len(model.layers)] = (train_acc, test_acc)

n_layers = 10

for i in range(n_layers):
    # 添加隱藏層
    add_layer(model, trainX, trainy)
    # 評估模型
    train_acc, test_acc = evaluate_model(model, trainX, testX, trainy, testy)
    print('> layers=%d, train=%.3f, test=%.3f' % (len(model.layers), train_acc, test_acc))
    # 將準確率存儲到字典,方便繪圖
    scores[len(model.layers)] = (train_acc, test_acc)

plt.plot(list(scores.keys()), [scores[k][0] for k in scores.keys()], label='train', marker='.')
plt.plot(list(scores.keys()), [scores[k][1] for k in scores.keys()], label='test', marker='.')
plt.legend()
plt.show()

輸出:

> layers=2, train=0.814, test=0.834
> layers=3, train=0.834, test=0.828
> layers=4, train=0.846, test=0.830
> layers=5, train=0.846, test=0.828
> layers=6, train=0.836, test=0.826
> layers=7, train=0.846, test=0.824
> layers=8, train=0.840, test=0.816
> layers=9, train=0.836, test=0.826
> layers=10, train=0.846, test=0.826
> layers=11, train=0.844, test=0.826
> layers=12, train=0.840, test=0.820

訓練數據集上模型的準確率有所提高,可能是因爲它開始過擬合數據;由於過擬合,測試數據集上的分類準確率下降。
在這裏插入圖片描述
在這種情況下,該圖表明訓練數據集略有過擬合,但是在添加了七個層之後,也許測試集的性能更好。


2.2 無監督的 貪婪逐層預訓練

基本的模型如下:

def base_autoencoder(trainX, testX):
    model = Sequential()
    model.add(Dense(10, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
    
    model.add(Dense(2, activation='linear'))
    model.compile(loss='mse', optimizer=SGD(lr=0.01, momentum=0.9))
    
    model.fit(trainX, trainX, epochs=100, verbose=0)
    train_mse = model.evaluate(trainX, trainX, verbose=0)
    test_mse = model.evaluate(testX, testX, verbose=0)
    print('> reconstruction error train=%.3f, test=%.3f' % (train_mse, test_mse))
    return model

其它部分基本不變。


參考:
https://machinelearningmastery.com/greedy-layer-wise-pretraining-tutorial/
僞隨機數生成器:https://baijiahao.baidu.com/s?id=1622255193902414898&wfr=spider&for=pc
np.random.seed() 理解,關注評論區可能會醍醐灌頂:https://blog.csdn.net/linzch3/article/details/58220569
random.seed() :What does it do?:https://stackoverflow.com/questions/22639587/random-seed-what-does-it-do
random.RandomState():https://stackoverflow.com/questions/5836335/consistently-create-same-random-numpy-array/5837352#5837352
https://stackoverflow.com/questions/37224116/difference-between-randomstate-and-seed-in-numpy

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