[深度學習]網路結構,權重初始化,啟用函式,fine-tune
導語:
- 直接檢視一個神經網路的結構;
- 如何初始化權重,改進準確度;
- 在Keras上建立現行模型;
- 啟用函式的作用;
- 如何fine-tune一個預訓練的Vgg16網路,來分類貓和狗
七行程式碼體驗深度學習的發現
Epochs
一個eposh就是一遍完整資料集的過程。
- 執行多個epoch可以提升準確度。
- 執行多個epoch的時候,分開監測他們的訓練結果。
- 如果你的精確度收攏的並不好,嘗試減小“學習率”
在vgg物件中,可以這樣改變學習率:
vgg.model.optimizer.lr = 0.01
結果視覺化
jupyter進行結果的視覺化很方便,舉一些例子:
- 隨機一些正確的標籤
- 隨機一些錯誤的標籤
- 每個類中正確率最高的標籤
- 每個類中正確率最低的標籤
- 最不確定的標籤(概率接近0.5)
#1. 隨機一些正確的標籤
correct = np.where(preds==val_labels[:,1])[0]
idx = permutation(correct)[:n_view]
plots_idx(idx, probs[idx])
````
![](http://wx1.sinaimg.cn/mw690/a9c4d5f6gy1fotocxj6wdj20qy0dsn81.jpg)
<div class="se-preview-section -delimiter"></div>
```python
#2. 隨機一些錯誤的標籤
incorrect = np.where(preds!=val_labels[:,1])[0]
idx = permutation(incorrect)[:n_view]
plots_idx(idx, probs[idx])
#3. 很大概率是貓,並且的確是貓
correct_cats = np.where((preds==0) & (preds==val_labels[:,1]))[0]
most_correct_cats = np.argsort(probs[correct_cats])[::-1 ][:n_view]
plots_idx(correct_cats[most_correct_cats], probs[correct_cats][most_correct_cats])
# 很大概率是狗,並且的確是狗
correct_dogs = np.where((preds==1) & (preds==val_labels[:,1]))[0]
most_correct_dogs = np.argsort(probs[correct_dogs])[:n_view]
plots_idx(correct_dogs[most_correct_dogs], 1-probs[correct_dogs][most_correct_dogs])
#3. 很大概率是貓,但實際是狗
incorrect_cats = np.where((preds==0) & (preds!=val_labels[:,1]))[0]
most_incorrect_cats = np.argsort(probs[incorrect_cats])[::-1][:n_view]
plots_idx(incorrect_cats[most_incorrect_cats], probs[incorrect_cats][most_incorrect_cats])
#3. 很大概率是狗,但實際是貓
incorrect_dogs = np.where((preds==1) & (preds!=val_labels[:,1]))[0]
most_incorrect_dogs = np.argsort(probs[incorrect_dogs])[:n_view]
plots_idx(incorrect_dogs[most_incorrect_dogs], 1-probs[incorrect_dogs][most_incorrect_dogs])
#5. 最不確定的標籤 (概率最接近0.5).
most_uncertain = np.argsort(np.abs(probs-0.5))
plots_idx(most_uncertain[:n_view], probs[most_uncertain])
還有一個比較常規的方法,來分析分類模型的結果,就是使用混淆矩陣(confusion matrix),Scikit-learn也有一個函式可以做這件事:
cm = confusion_matrix(val_classes, preds)
列印混淆矩陣,或者將它視覺化。
plot_confusion_matrix(cm, val_batches.class_indices)
混淆矩陣(confusion matrix),又稱為可能性表格或是錯誤矩陣。它是一種特定的矩陣用來呈現演算法效能的視覺化效果,通常是監督學習(非監督學習,通常用匹配矩陣:matching matrix)。其每一列代表預測值,每一行代表的是實際的類別。這個名字來源於它可以非常容易的表明多個類別是否有混淆(也就是一個class被預測成另一個class)。所有正確的預測結果都在對角線上,所以從混淆矩陣中可以很方便直觀的看出哪裡有錯誤,因為他們呈現在對角線外面。
預訓練權重
ImageNet 網路以什麼開始的?
與finetuning一個被預訓練過權重的網路相比,用隨機的權重訓練一個卷積神經網路要花費非常大的精力。預訓練網路很有效,是因為它們已經有了ImageNet的特性,權重中已經encode進特性了。典型的講,包含線、邊緣、曲線以及很多有用的表示特定部分的低階濾波器。比如下面一個layer中可以發現一些條紋和圓形的東西。
影象識別過程中這些低階的濾波器都是很有用的。預訓練的權重已經學習到了這些濾波器,我們只需要簡單的”fine-tune”高階的layer,改變我們需要的分類對映。
神經網路基礎
標準的全連線神經網路本質上是一系列矩陣的運算。
上面是原始的表格。
紫色圓圈是輸入向量,黃色圓圈是目標向量y。下面要做一系列矩陣操作來儘可能接近目標向量y。
輸入向量和第一個權值矩陣的第一列相乘,結果是activation向量中第一個值。
輸入向量和第一個權重矩陣的第二列相乘,結果是activation向量中第二個值。同理得到第三個和第四個值。
第一個activations向量和第二個權重矩陣的第一列相乘,得到第二個activations向量的第一個值。同理得到第二個和第三個值。
觀察每個權值矩陣中第一列的乘法。最後如何得到activation向量和我們的目標向量y一樣的值呢?
類似上面,一個神經網路,它的核心就是一系列矩陣,通過矩陣乘法將輸入矩陣對映到輸出矩陣上。每個矩陣之間的中間矩陣就是activation,矩陣自身就是每一層。學習的過程叫做”fitting”,目標就是調整叫做權重矩陣的值,為了在給神經網路一個輸入矩陣的時候,我們有能力產生一個儘可能和真實輸出矩陣接近的輸出矩陣。是通過傳入很多已經標記過得輸入矩陣來達到目的。這就是在訓練集上做的事情。
根據上面的說法,隨機生成矩陣元素的權值。然後執行所有的操作並觀察結果,注意activation是如何輸出的,和我們的目標矩陣y有多大的差距。使用一些最優化演算法,可以使結果儘可能接近目標矩陣y。在這之前,建議以使得啟用輸出至少相對接近目標矩陣的方式來初始化權值。這種方法叫做權重初始化。
有很多的權重初始化器可以選擇。這裡,使用了Xavier Initialization(也叫做Glorot Initialization)。值得注意的是,現在很多的深度學習庫將會為你處理好權重初始化,不需要你自己進行這一步。
梯度下降法
上面講到的優化演算法,最常見的優化演算法,就是在深度學習中無處不在的梯度下降法(Gradient Descent)
標準梯度下降法
標準梯度下降法是一種迭代選擇“引數”(稱之為深度學習的權重),成功減少了所謂的“損失函式”,損失函式是一些簡單的方法,來確定不同的預測輸出,由預測”引數”和給定的輸入,從用相同的輸入來得到相關聯的輸出。一個常見的損失函式是平方誤差之和,它只是預測響應和真實響應之間的差之平方和之和。這種損失函式線上性迴歸過程中很常見。另一個常見的損失函式是log-loss,上面已經定義過了。通常在神經網路中使用這種損失函式。
由於我們的損失函式本質上是衡量我們的預測與期望值相匹配的程度,我們的優化演算法的目標是最小化這個值。我們的預測函式至少有兩種數值,即函式作用的輸入,以及決定我們對輸入做什麼的“引數”。因為我們不能改變輸入,所以我們必須通過選擇能產生接近於期望值的引數來最小化損失函式。
梯度下降是一種迭代的“改進”初始引數值的方法(通過一些程序初始化),以儘量減少損失函式。我們通過計算損失函式關於每個引數的偏導數,並通過在導數方向相反的一步來更新引數。當我們在所有引數中這樣做時,我們(希望)以這樣一種方式更新我們的引數,這些新引數確定的預測減少損失函式。在以後可能會遇到,使用這種方法可能會出現一些問題,以及我們如何處理它。這個過程中的“梯度”是描述損耗函式是如何在每個引數上變化的向量。我們前面提到的另一個值得注意的是學習率(learning rete)。這個值指示我們在更新引數時採取的步驟有多大,這就是所謂的“超引數(hyper-parameter)”。
線性迴歸的例子
這聽起來可能比實際情況複雜得多。舉個例子,我們可以看到線性迴歸中的梯度下降(擬合一條直線)。
如果直線是ax + b,其中a和b是“引數”,我們的目標求得使損失函式最小化的a和b。 損失函式本質上是一個數學函式,如果猜錯了(在我們的例子中是a和b),那麼這個函式將會很高,如果猜對了,那麼這個函式結果就會比較小。 線上性迴歸中,我們使用平方差之和作為損失函式。 在每次迭代中,計算這個函式關於a和b的導數。 這告訴我們損失函式如何改變這兩個引數。 如果關於a的導數是正值,那麼增大a將增大損失函式。 所以就減小a。 如果是負值,增大則會減小損失函式,所以增大a。 無論哪種情況,都朝著與a的導數的正方向相反的方向前進。 一遍一遍的這樣做,直到滿意。
拓展到神經網路
這種優化技術的強大之處在於我們已經對線性模型的引數進行了隨機初始化,並且通過梯度下降迭代,我們可以得到最優解。 這裡的關鍵是,我們用同樣的過程來估計像線性函式那樣簡單的引數,也可以用來估計像神經網路這樣複雜的引數,但有一些注意事項。 線上性迴歸中,我們通常總是能夠得到最好的解決方案。 由於具有數百萬個引數的神經網路的複雜性,這幾乎從未如此。 往往我們永遠不會找到所有引數的最佳最小值。 這就是為什麼我們不直接在神經網路上執行梯度下降,直到滿足一些終止條件,就像我們使用線性迴歸一樣。 相反,我們執行梯度下降,直到我們對結果滿意。
隨機梯度下降
另一個關鍵的區別是我們到目前為止只談到了“標準”梯度下降。 在標準梯度下降中,對所有可用的訓練資料評估損失函式。 不幸的是,由於計算限制,這在神經網路中是不可能的。 因此,我們使用所謂的隨機梯度下降。 這是通過隨機抽樣或者我們的資料的“mini-batch”來產生對這個小批量的預測,並用它們的真實值來評估損失函式。 然後我們像往常一樣更新權重,然後移動到下一個小批量,重複這個過程。 這個過程的“隨機”元素是通過評估不同小批量的損失函式而引入的隨機元素,而不是整個訓練集。 對於每個小批量,損失函式將略有不同,它也將不同於整個訓練集的損失函式。 但事實證明,這並不重要! 隨機梯度下降的神奇之處在於,您可以更新隨機小批量訓練集上的權重,並且您的結果將與您更新整個訓練集上的真實損失函式的權重相同。
舉例
作為例子,下面展示keras如何實現梯度下降,我們將線上性迴歸的context中使用它。
x = random((30,2))
y = np.dot(x, [2., 3.]) + 1.
x[:5]
在這裡所做的是建立y值,通過關係 y = 2 * x1 + 3 * x2 + 1與x1 , x2線性相關。 在keras中,一個簡單的線性模型被稱為Dense layer。 通過我們的輸入和期望的輸出x和y ,Keras將初始化某種形式的隨機權重。 我們會告訴它使用SGD來優化,學習率為0.1,以最小化損失函式均方誤差(mse):
lm = Sequential([ Dense(1, input_shape=(2,)) ])
lm.compile(optimizer=SGD(lr=0.1), loss='mse')
通過評估我們的損失函式,可以看到初始權重有多遠。
lm.evaluate(x, y, verbose=0)
8.6175813674926758
接下來,我們將執行隨機梯度下降。 擬合函式,如下所述,在sgd的每次迭代中,將使用訓練集中的一個輸入/輸出對來評估損失函式並更新引數。 整個訓練集中的一次遍歷計算被稱為epoch。 下面經歷5個epoch。
lm.fit(x, y, nb_epoch=5, batch_size=1)
來看看我們的評估函式:
lm.fit(x, y, nb_epoch=5, batch_size=1)
2.3591119315824471e-05
和期待的一樣,小了很多
fitting 後我們也可以看看權重。 我們期望它們非常接近真實引數(2.0,3.0和1.0)。
lm.get_weights()
的確和預期一致。 如果我們使用的batch size大於1,可以預期我們的權重會更快地收斂到真實權重。
Cats vs Dogs and Finetuning
我們現在已經足夠了解如何修改Vgg16來建立一個模型,來輸出貓和狗的預測。
新增一個Dense Layer
我們在上一節中使用的Dense Layer將輸入向量對映到單個輸出。 我們可以很容易地改變這個輸出到一個任意長度的向量,注意這個輸出的權重結構將只是一個矩陣。
Vgg16的最後一層輸出1000個類別的向量,因為這是比賽要求的類別數量。 在這些類別中,其中的一些當然對應於貓和狗,但在更細微的層面(特定品種)。 我們可以手工找出哪些類別是貓,哪些是狗,只需編寫一些將imagenet分類轉換為貓和狗分類的程式碼。 但這樣做效率不高,我們會錯過一些關鍵資訊。
一個更好的方法是簡單地在imagenet層的頂部新增Dense Layer,並訓練模型,以將貓和狗的輸入影象的imagenet分類對映到貓和狗的標籤。 為什麼這比手動做呢更好? 因為神經網路將利用imagenet分類中的所有可用資訊,而不是簡單地將cat分類對映到cat和dog分類。 例如,德國牧羊犬與骨頭的圖片可能在德國牧羊犬類別和骨頭類別中具有很強的概率。 如果我們只把狗的類別對映到狗身上,然後把其他的資訊丟掉,那麼我們就會失去其他對分類有用的資訊,比如影象中是否有骨頭。
總體方法是:
- 獲取每個影象的真實標籤。
- 獲取每個影象的1,000個imagenet類別預測。
- 將這些預測作為輸入提供給簡單的線性模型。
需要注意的一點是,我們從批處理中得到的標籤需要進行one-hot編碼。 one-hot編碼只需要分類變數,並將其轉換為矩陣,其中每列表示一個類別。 如果影象屬於類別A,那麼矩陣中該影象的行在A類列中有1,而在其他情況下為0。 我們採取步驟將標籤轉換為這些向量的一個重要原因,是因為它與dense layer的輸出形狀相同。 因此,為了訓練目的,我們需要對它們進行one-hot。 現在可以預測。
trn_features = model.predict(trn_data, batch_size=batch_size)
val_features = model.predict(val_data, batch_size=batch_size)
如果我們通過預測將我們的貓與狗的訓練/驗證傳遞給我們的模型,那麼我們將得到1000個imagenet分類概率。 接下來,我們可以做出如下模型:
#1000 輸入,為了保留特性,2輸出,狗和貓
lm = Sequential([ Dense(2, activation='softmax', input_shape=(1000,)) ])
lm.compile(optimizer=RMSprop(lr=0.1), loss='categorical_crossentropy', metrics=['accuracy'])
需要將1000個 imagenet概率,對映到輸出,一個是貓,一個是狗。 初始化後,模型不知道怎麼做。 但是,使用one-hot 編碼標籤,可以使用imagenet預測作為輸入來訓練此layer。 現在我們所要做的就和以前一樣。
batch_size=64
lm.fit(trn_features, trn_labels, nb_epoch=3, batch_size=batch_size,
validation_data=(val_features, val_labels))
這種簡單地將dense layers附著到預訓練的模型,以對期望的類別進行分類的方法,通常使得深度學習的業餘人員獲得令人驚訝的結果,諸如分類面板損傷。 這真的很簡單, 我們真的沒有什麼神奇的功能,只是通過預先訓練好的模型輸出來訓練線性模型。 通過這樣做,我們已經實現了大於97%的分類準確度。
啟用層
之前描述一個神經網路時,解釋它是一系列的矩陣乘法,稱之為層,它轉換一個輸入向量。 在每個中間步驟中,我們將這些層的輸出稱為啟用層。 然而,經過反思,這似乎很奇怪。 如果一個神經網路只是一個矩陣乘法的序列,那麼整個過程就是一個線性過程,可以用一個矩陣表示。
當然不是這種情況。 之前我們關注中間向量的原因是因為在神經網路中,實際上在這些啟用層中存在非線性函式,其在前一層的輸出上操作。 這個新的向量然後被饋送到下一個矩陣中。 一些常用的啟用函式是 tanh ,sigmoid 函式和 relu(rectified linear unit的縮寫)。 Relu實際上是最常見的,雖然聽起來像一些神祕的功能,但它只是函式max(0,x) 。 現在觀察Vgg16中的每個圖層都有啟用函式,它們只是告訴keras如何轉換特定線性圖層的輸出。
事實證明,這種線性變換和非線性啟用的組合能夠逼近任何東西。
Finetuning
如果我們觀察Vgg16的最後一層,可以看到最後一層只是一個輸出1000個元素的dense layer。 因此,將一層意味著要找到貓和狗的dense layer堆疊起來,似乎有些不合理,因為在分類為貓和狗之前,首先強迫神經網路將其分類到imagenet,限制了一些可用的資訊。
相反,我們來刪除最後一層
model.pop()
for layer in model.layers: layer.trainable=False
然後給貓和狗新增一個新層
model.add(Dense(2, activation='softmax'))
注意 :以上3個步驟都是vgg16的 finetune() 方法!
現在我們可以使用上一層的所有4096個啟用(不管它們可能是什麼!)來分類為貓和狗。 這隱含地應該為我們提供更豐富的資訊來分類。
接下來的步驟是像以前一樣簡單地訓練,如果你這樣做,你會發現你的模型會產生比只是在上面增加另一個圖層更好的結果。