TensorFlow 2.0深度學習算法實戰---第9章 過擬合

一切都應該儘可能地簡單,但不能過於簡單。 —艾伯特·愛因斯坦

機器學習的主要目的是從訓練集上學習到數據的真實模型,從而能夠在未見過的測試集上也能夠表現良好,我們把這種能力叫做泛化能力通常來說,訓練集和測試集都採樣自某個相同的數據分佈𝑝(𝑥)。採樣到的樣本是相互獨立的,但是又來自於相同的分佈,我們把這種假設叫做獨立同分布假設(Independent Identical Distribution assumption,簡稱i.i.d.)。

前面已經提到了模型的表達能力,也稱之爲模型的容量(Capacity)。當模型的表達能力偏弱時,比如單一線性層,它只能學習到線性模型,無法良好地逼近非線性模型;但模型的表達能力過強時,它就有可能把訓練集的噪聲模態也學到,導致在測試集上面表現不佳的現象(泛化能力偏弱)。因此針對不同的任務,設計合適容量的模型算法才能取得較好的泛化性能。

9.1 模型的容量

通俗地講,模型的容量或表達能力,是指模型擬合複雜函數的能力。一種體現模型容量的指標爲模型的假設空間(Hypothesis Space)大小,即模型可以表示的函數集的大小。假設空間越大越完備,從假設空間中搜索出逼近真實模型的函數也就越有可能;反之,如果假設空間非常受限,就很難從中找到逼近真實模型的函數。

考慮採樣自真實分佈
pdata ={(x,y)y=sin(x),x[5,5]}p_{\text {data }}=\{(x, y) | y=\sin (x), x \in[-5,5]\}

的數據集,從真實分佈中採樣少量樣本點構成訓練集,其中包含了觀測誤差ϵ,如圖 9.1 中的小圓點。

如果只搜索所有 1 次多項式的模型空間,令偏置爲 0,即𝑦 = 𝑎𝑥,如圖 9.1 中 1次多項式的直線所示,則很難找到一條直線較好地逼近真實數據的分佈。

稍微增大假設空間,令假設空間爲所有的 3 次多項式函數,即y=ax3+bx2+cxy=a x^{3}+b x^{2}+c x,很明顯此假設空間明顯大於 1 次多項式的假設空間,我們可以找到一條曲線,如圖 9.1 3 次多項式曲線所示,它比 1 次多項式模型更好地反映了數據的關係,但是仍然不夠好。

再次增大假設空間,使得可以搜索的函數爲 5 次多項式,即y=ax5+bx4+cx3+dx2+exy=a x^{5}+b x^{4}+c x^{3}+d x^{2}+e x,在此假設空間中,可以搜索到一個較好的函數,如圖 9.1 中 5 次多項式所示。

再次增加假設空間後,如圖 9.1中 7、9、11、13、15、17 次多項式曲線所示,函數的假設空間越大,就越有可能找到一個函數更好地逼近真實分佈的函數模型。

但是過大的假設空間無疑會增加搜索難度和計算代價。實際上,在有限的計算資源的約束下,較大的假設空間並不一定能搜索出更好的函數模型。同時由於觀測誤差的存在,較大的假設空間中可能包含了大量表達能力過強的函數,能夠將訓練樣本的觀測誤差也學習進來,從而傷害了模型的泛化能力。挑選合適容量的學習模型是一個很大的難題.
在這裏插入圖片描述

9.2 過擬合與欠擬合

由於真實數據的分佈往往是未知而且複雜的,無法推斷出其分佈函數的類型和相關參數,因此人們在選擇學習模型的容量時,往往會根據經驗值選擇稍大的模型容量。但模型的容量過大時,有可能出現在訓練集上表現較好,但是測試集上表現較差的現象,如圖9.2 中紅色豎線右邊區域所示;當模型的容量過小時,有可能出現在訓練集和測試集表現皆不佳的現象,如圖 9.2 中紅色豎線左邊區域所示。
在這裏插入圖片描述
當模型的容量過大時,網絡模型除了學習到訓練集數據的模態之外,還把額外的觀測誤差也學習進來,導致學習的模型在訓練集上面表現較好,但是在未見的樣本上表現不佳,也就是模型泛化能力偏弱,我們把這種現象叫作過擬合(Overfitting)。當模型的容量過小時,模型不能夠很好地學習到訓練集數據的模態,導致訓練集上表現不佳,同時在未見的樣本上表現也不佳,我們把這種現象叫作欠擬合(Underfitting)。

這裏用一個簡單的例子來解釋模型的容量與數據的分佈之間的關係。圖 9.3 繪製了某種數據的分佈圖,可以大致推測數據可能屬於某 2 次多項式分佈。

如果我們用簡單的線性函數去學習時,會發現很難學習到一個較好的函數,從而出現訓練集和測試集表現都不理想的現象,如圖 9.3(a)所示,這種現象叫做欠擬合。

但如果用較複雜的函數模型去學習時,有可能學習到的函數會過度地“擬合”訓練集樣本,從而導致在測試集上表現不佳,如圖 9.3©所示,這種現象叫做過擬合。

只有學習的模型和真實模型容量大致匹配時,模型才能具有較好地泛化能力,如圖 9.3(b)所示。
在這裏插入圖片描述
考慮數據點(𝑥, 𝑦)的分佈pdatap_{data},其中
y=sin(1.2πx)y=\sin (1.2 \cdot \pi \cdot x)
在採樣時,添加隨機高斯噪聲𝒩(0,1),共獲得 120 個點的數據集,如圖 9.4 所示,圖中曲線爲真實模型函數的曲線,黑色圓形點爲訓練樣本,綠色矩陣點爲測試樣本。
在這裏插入圖片描述
在已知真實模型的情況下,自然可以設計容量合適的函數假設空間,從而獲得不錯的學習模型,如圖 9.5 所示,我們將模型假設爲 2 次多項式模型,學習得到的函數曲線較好地逼近真實模型的函數曲線。但是在實際場景中,真實模型往往是無法得知的,因此設計的假設空間如果過小,導致無法搜索到合適的學習模型;設計的假設空間過大,導致模型泛化能力過差。
在這裏插入圖片描述
那麼如何去選擇模型的容量?統計學習理論給我們提供了一些思路,其中 VC 維度(Vapnik-Chervonenkis 維度)是一個應用比較廣泛的度量函數容量的方法。儘管這些方法給機器學習提供了一定程度的理論保證,但是這些方法卻很少應用到深度學習中去,一部分原因是神經網絡過於複雜,很難去確定網絡結構背後的數學模型的 VC 維度。

儘管統計學習理論很難給出神經網絡所需要的最小容量,但是卻可以根據奧卡姆剃刀原理(Occam’s razor)來指導神經網絡的設計和訓練。奧卡姆剃刀原理是由 14 世紀邏輯學家、聖方濟各會修士奧卡姆的威廉(William of Occam)提出的一個解決問題的法則,他在《箴言書注》2 卷 15 題說:切勿浪費較多東西,去做用較少的東西,同樣可以做好的事情。也就是說,如果兩層的神經網絡結構能夠很好的表達真實模型,那麼三層的神經網絡也能夠很好的表達,但是我們應該優先選擇使用更簡單的兩層神經網絡,因爲它的參數量更少,更容易訓練,也更容易通過較少的訓練樣本獲得不錯的泛化誤差。

9.2.1 欠擬合

我們來考慮欠擬合的現象。如圖 9.6 中所示,黑色圓點和綠色矩形點均獨立採樣自某拋物線函數的分佈,在已知數據的真實模型的條件下,如果用模型容量小於真實模型的線性函數去迴歸這些數據,會發現很難找到一條線性函數較好地逼近訓練集數據的模態,具體表現爲學習到的線性模型在訓練集上的誤差(如均方誤差)較大,同時在測試集上面的誤差也較大
在這裏插入圖片描述
當我們發現當前的模型在訓練集上誤差一直維持較高的狀態,很難優化減少,同時在測試集上也表現不佳時,我們可以考慮是否出現了欠擬合的現象。這個時候可以通過增加神經網絡的層數增大中間維度的大小等手段,比較好的解決欠擬合的問題。但是由於現代深度神經網絡模型可以很輕易達到較深的層數,用來學習的模型的容量一般來說是足夠的,在實際使用過程中,更多的是出現過擬合現象。

9.2.2 過擬合

繼續來考慮同樣的問題,訓練集黑色圓點和測試機綠色矩形點均獨立採樣自同分布的某拋物線模型,當我們設置模型的假設空間爲 25 次多項式時,它遠大於真實模型的函數容量,這時發現學到的模型很有可能過分去擬合訓練樣本,導致學習模型在訓練樣本上的誤差非常小,甚至比真實模型在訓練集上的誤差還要小。但是對於測試樣本,模型性能急劇下降,泛化能力非常差,如圖 9.7 所示。
在這裏插入圖片描述
現代深度神經網絡中過擬合現象非常容易出現,主要是因爲神經網絡的表達能力非常強,訓練集樣本數不夠,很容易就出現了神經網絡的容量偏大的現象。那麼如何有效檢測並減少過擬合現象呢?接下來我們將介紹一系列的方法,來幫助檢測並抑制過擬合現象。

9.3 數據集劃分

前面我們介紹了數據集需要劃分爲訓練集(Train set)和測試集(Test set),但是爲了挑選模型超參數和檢測過擬合現象,一般需要將原來的訓練集再次切分爲新的訓練集和驗證集(Validation set),即數據集需要切分爲訓練集、驗證集和測試集 3 個子集。

9.3.1 驗證集與超參數

前面已經介紹了訓練集和測試集的區別,訓練集DtrainD^{train}用於訓練模型參數,測試集DtestD^{test}用於測試模型的泛化能力,測試集中的樣本不能參與模型的訓練,防止模型“記憶”住數據的特徵,損害模型的泛化能力。

訓練集和測試集都是採樣自相同的數據分佈,比如MNIST 手寫數字圖片集共有 7 萬張樣本圖片,其中 6 萬張圖片用做訓練集,餘下的 1 萬張圖片用於測試集。訓練集與測試集的分配比例可以由用戶自行定義,比如 80%的數據用於訓練,剩下的 20%用於測試。

當數據集規模偏小時,爲了測試集能夠比較準確地測試出模型的泛化能力,可以適當增加測試集的比例。下圖 9.8 演示了 MNIST 手寫數字圖片集的劃分:80%用於訓練,剩下的 20%用於測試。
在這裏插入圖片描述
但是將數據集僅劃分爲訓練集與測試集是不夠的,由於測試集的性能不能作爲模型訓練的反饋,而我們需要在模型訓練時能夠挑選出較合適的模型超參數,判斷模型是否過擬合等,因此需要將訓練集再次切分爲訓練集DtrainD^{train}和驗證集DvalD^{val},如圖 9.9 所示。劃分過的訓練集與原來的訓練集的功能一致,用於訓練模型的參數,而驗證集則用於選擇模型的超參數(模型選擇,Model selection),它的功能包括:
❑ 根據驗證集的性能表現來調整學習率、權值衰減係數、訓練次數等。
❑ 根據驗證集的性能表現來重新調整網絡拓撲結構
❑ 根據驗證集的性能表現判斷是否過擬合和欠擬合

和訓練集-測試集的劃分類似,訓練集、驗證集和測試集可以按着自定義的比例來劃分,比如常見的 60%-20%-20%的劃分,圖 9.9 演示了 MNIST 手寫數據集的劃分示意圖。
在這裏插入圖片描述
驗證集與測試集的區別在於,算法設計人員可以根據驗證集的表現來調整模型的各種超參數的設置,提升模型的泛化能力,但是測試集的表現卻不能用來反饋模型的調整,否則測試集將和驗證集的功能重合,因此在測試集上的性能表現將無法代表模型的泛化能力

實際上,部分開發人員會錯誤地使用測試集來挑選最好的模型,然後將其作爲模型泛化性能彙報(甚至部分論文也會出現這種做法),此時的測試集其實是驗證集的功能,因此彙報的“泛化性能”本質上是驗證集上的性能,而不是真正的泛化性能。爲了防止出現這種作弊行爲,可以選擇生成多個測試集,這樣即使開發人員使用了其中一個測試集來挑選模型,我們還可以使用其它測試集來評價模型,這也是 Kaggle 競賽常用的做法。

9.3.2 提前停止

一般把對訓練集中的一個 Batch 運算更新一次叫做一個 Step,對訓練集的所有樣本循環迭代一次叫做一個 Epoch。驗證集可以在數次 Step 或數次 Epoch 後使用,計算模型的驗證性能。驗證的步驟過於頻繁,能夠精準地觀測模型的訓練狀況,但是也會引入額外的計算代價,一般建議幾個 Epoch 後進行一次驗證運算。

以分類任務爲例,在訓練時,一般關注的指標有訓練誤差、訓練準確率等,相應地,驗證時也有驗證誤差和驗證準確率等,測試時也有測試誤差和測試準確率等。通過觀測訓練準確率和驗證準確率可以大致推斷模型是否出現過擬合和欠擬合。如果模型的訓練誤差較低,訓練準確率較高,但是驗證誤差較高,驗證準確率較低,那麼可能出現了過擬合現象。如果訓練集和驗證集上面的誤差都較高,準確率較低,那麼可能出現了欠擬合現象。

當觀測到過擬合現象時,可以從新設計網絡模型的容量,如降低網絡的層數、降低網絡的參數量、添加正則化手段、添加假設空間的約束等,使得模型的實際容量降低,從而減輕或解決過擬合現象;當觀測到欠擬合現象時,可以嘗試增大網絡的容量,如加深網絡的層數、增加網絡的參數量,嘗試更復雜的網絡結構。

實際上,由於網絡的實際容量可以隨着訓練的進行發生改變,因此在相同的網絡設定下,隨着訓練的進行,可能觀測到不同的過擬合、欠擬合狀況。如圖 9.10 所示爲分類問題的典型訓練曲線,紅色曲線爲訓練準確率,藍色曲線爲測試準確率。從圖中可以看到,在訓練的前期,隨着訓練的進行,模型的訓練準確率和測試準確率都呈現增大的趨勢,此時並沒有出現過擬合現象;在訓練後期,即使是相同網絡結構下,由於模型的實際容量發生改變,我們觀察到了過擬合的現象,具體表現爲訓練準確度繼續改善,但是泛化能力變弱(測試準確率減低)。
在這裏插入圖片描述
這意味着,對於神經網絡,即使網絡結構超參數保持不變(即網絡最大容量固定),模型依然可能會出現過擬合的現象,這是因爲神經網絡的有效容量和網絡參數的狀態息息相關,神經網絡的有效容量可以很大,也可以通過稀疏化參數、添加正則化等手段降低有效容量。在訓練的前中期,神經網絡的過擬合現象沒有出現,當隨着訓練 Epoch 數的增加,過擬合程度越來越嚴重。圖 9.10 中豎直虛線所處的網絡狀態最佳,沒有出現明顯的過擬合現象,網絡的泛化能力最佳。
在這裏插入圖片描述
那麼如何選擇合適的 Epoch 就提前停止訓練(Early Stopping),避免出現過擬合現象呢?我們可以通過觀察驗證指標的變化,來預測最適合的 Epoch 可能的位置。具體地,對於分類問題,我們可以記錄模型的驗證準確率,並監控驗證準確率的變化,當發現驗證準確率連續𝑛個 Epoch 沒有下降時,可以預測可能已經達到了最適合的 Epoch 附近,從而提前終止訓練。圖 9.11 中繪製了某次具體的訓練過程中,訓練和驗證準確率隨訓練 Epoch 的變化曲線,可以觀察到,在 Epoch 爲 30 左右時,模型達到最佳狀態,提前終止訓練。
在這裏插入圖片描述
算法 1 是採用提前停止的模型訓練算法僞代碼。
在這裏插入圖片描述

9.4 模型設計

通過驗證集可以判斷網絡模型是否過擬合或者欠擬合,從而爲調整網絡模型的容量提供判斷依據。對於神經網絡來說,網絡的層數和參數量是網絡容量很重要的參考指標,通過減少網絡的層數,並減少每層中網絡參數量的規模,可以有效降低網絡的容量。反之,如果發現模型欠擬合,需要增大網絡的容量,可以通過增加層數,增大每層的參數量等方式實現。

爲了演示網絡層數對網絡容量的影響,我們可視化了一個分類任務的決策邊界(Decision boundary)。圖 9.12、圖 9.13、圖 9.14、圖 9.15 分別演示在不同的網絡層數下訓練 2 分類任務的決策邊界圖,其中紅色矩形塊和藍色圓形塊分別代表了訓練集上的 2 類樣本,保持其它超參數一致,僅調整網絡的層數,訓練獲得樣本上的分類效果,如圖中所示,可以看到,隨着網絡層數的加深,學習到的模型決策邊界越來越逼近訓練樣本,出現了過擬合現象。對於此任務,2 層的神經網絡即可獲得不錯的泛化能力,更深層數的網絡並沒有提升性能,反而出現過擬合現象,泛化能力變差,同時計算代價也更高。
在這裏插入圖片描述
在這裏插入圖片描述

9.5 正則化

通過設計不同層數、大小的網絡模型可以爲優化算法提供初始的函數假設空間,但是模型的實際容量可以隨着網絡參數的優化更新而產生變化。以多項式函數模型爲例:
y=β0+β1x+β2x2+β3x3++βnxn+εy=\beta_{0}+\beta_{1} x+\beta_{2} x^{2}+\beta_{3} x^{3}+\cdots+\beta_{n} x^{n}+\varepsilon
上述模型的容量可以通過𝑛簡單衡量。在訓練的過程中,如果網絡參數βk+1\beta_{k+1}, ⋯ , βn\beta_{n}均爲 0,那麼網絡的實際容量退化到𝑘次多項式的函數容量。因此,通過限制網絡參數的稀疏性,可以來約束網絡的實際容量

這種約束一般通過在損失函數上添加額外的參數稀疏性懲罰項實現,在未加約束之前的優化目標是
minL(fθ(x),y),(x,y)Dtrain \min \mathcal{L}\left(f_{\theta}(\boldsymbol{x}), y\right),(\boldsymbol{x}, y) \in \mathbb{D}^{\text {train }}
對模型的參數添加額外的約束後,優化的目標變爲
minL(fθ(x),y)+λΩ(θ),(x,y)Dtrain \min \mathcal{L}\left(f_{\theta}(\boldsymbol{x}), y\right)+\lambda \cdot \Omega(\theta),(\boldsymbol{x}, y) \in \mathbb{D}^{\text {train }}
其中𝛺(𝜃)表示對網絡參數𝜃的稀疏性約束函數。一般地,參數𝜃的稀疏性約束通過約束參數
𝜃的𝐿範數實現,即
Ω(θ)=θiθil\Omega(\theta)=\sum_{\theta_{i}}\left\|\theta_{i}\right\|_{l}
其中θil\left\|\theta_{i}\right\|_{l}表示參數𝜃𝑖的𝑙範數。

新的優化目標除了要最小化原來的損失函數L(x,y)\mathcal{L}(x, y)之外,還需要約束網絡參數的稀疏性𝛺(𝜃),優化算法會在降低L(x,y)\mathcal{L}(x, y)的同時,儘可能地迫使網絡參數𝜃𝑖變得稀疏,它們之間的權重關係通過超參數𝜆來平衡。較大的𝜆意味着網絡的稀疏性更重要;較小的𝜆則意味着網絡的訓練誤差更重要。通過選擇合適的𝜆超參數,可以獲得較好的訓練性能,同時保證網絡的稀疏性,從而獲得不錯的泛化能力。

常用的正則化方式有 L0、L1、L2 正則化.

9.5.1 L0 正則化

L0 正則化是指採用 L0 範數作爲稀疏性懲罰項𝛺(𝜃)的正則化計算方式,即
Ω(θ)=θiθi0\Omega(\theta)=\sum_{\theta_{i}}\left\|\theta_{i}\right\|_{0}
其中 L0 範數θi0\left\|\theta_{i}\right\|_{0}定義爲𝜃𝑖中非零元素的個數。通過約束θiθi0\sum_{\theta_{i}}\left\|\theta_{i}\right\|_{0}的大小可以迫使網絡中的連接權值大部分爲 0,從而降低網絡的實際參數量和網絡容量。但是由於 L0 範數θi0\left\|\theta_{i}\right\|_{0}並不可導,不能利用梯度下降算法進行優化,在神經網絡中使用的並不多。

9.5.2 L1 正則化

採用 L1 範數作爲稀疏性懲罰項𝛺(𝜃)的正則化計算方式叫作 L1 正則化,即
Ω(θ)=θiθi1\Omega(\theta)=\sum_{\theta_{i}}\left\|\theta_{i}\right\|_{1}
其中 L1 範數θi1\left\|\theta_{i}\right\|_{1}定義爲張量𝜃𝑖中所有元素的絕對值之和。L1 正則化也叫 Lasso Regularization,它是連續可導的,在神經網絡中使用廣泛
L1 正則化可以實現如下:

# 創建網絡參數w1,w2
w1=tf.random.normal([4,3])
w2=tf.random.normal([4,2])
w3=tf.random.normal([1,2])
# 計算L1正則化項
loss_reg=tf.reduce_sum(tf.math.abs(w1)+tf.reduce_sum(tf.math.abs(w2)))
print(tf.math.abs())
9.5.3 L2 正則化

採用 L2 範數作爲稀疏性懲罰項𝛺(𝜃)的正則化計算方式叫做 L2 正則化,即
Ω(θ)=θiθi2\Omega(\theta)=\sum_{\theta_{i}}\left\|\theta_{i}\right\|_{2}
其中 L2 範數θi2\left\|\theta_{i}\right\|_{2}定義爲張量𝜃𝑖中所有元素的平方和。L2 正則化也叫 Ridge Regularization,它和 L1 正則化一樣,也是連續可導的,在神經網絡中使用廣泛。
L2 正則化項實現如下:

# 創建網絡參數w1,w2
w1=tf.random.normal([4,3])
w2=tf.random.normal([4,2])
# 計算L2正則化項
loss_reg=tf.reduce_sum(tf.square(w1)+tf.reduce_sum(tf.square(w2)))
print(loss_reg)
9.5.4 正則化效果

繼續以月牙形的 2 分類數據爲例。在維持網絡結構等其它超參數不變的條件下,在損失函數上添加 L2 正則化項,並通過改變不同的正則化超參數𝜆來獲得不同程度的正則化效果。

在訓練了 500 個 Epoch 後,我們獲得學習模型的分類決策邊界,如圖 9.16、圖9.17、圖 9.18、圖 9.19 分佈代表了正則化係數𝜆 = 0.00001、0.001、0.1、0.13時的分類效果。可以看到,隨着正則化係數𝜆的增加,網絡對參數稀疏性的懲罰變大,從而迫使優化算法搜索讓網絡容量更小的模型。在𝜆 = 0.00001時,正則化的作用比較微弱,網絡出現了過擬合現象;但是𝜆 = 0.1時,網絡已經能夠優化到合適的容量,並沒有出現明顯過擬合或者欠擬合現象。

實際訓練時,一般優先嚐試較小的正則化係數𝜆,觀測網絡是否出現過擬合現象然後嘗試逐漸增大𝜆參數來增加網絡參數稀疏性,提高泛化能力。但是,過大的𝜆參數有可能導致網絡不收斂,需要根據實際任務調節。
在這裏插入圖片描述
在這裏插入圖片描述
在不同的正則化係數𝜆下,我們統計了網絡中每個連接權值的數值範圍。考慮網絡的第2 層的權值矩陣𝑾,其 shape 爲[256,256],即將輸入長度爲 256 的向量轉換爲 256 的輸出向量。從全連接層權值連接的角度來看,𝑾一共包含了256 ∙ 256根連接線的權值,我們將它對應到圖 9.20、圖 9.21、圖 9.22、圖 9.23 中的 X-Y 網格中,其中 X 軸的範圍爲[0,255],Y 軸的範圍爲[0,255],X-Y 網格的所有整數點分別代表了 shape 爲[256,256]的權值張量𝑾的每個位置,每個網格點繪製出當前連接上的權值。
從圖中可以看到,添加了不同程度的正則化約束對網絡權值的影響。在𝜆 = 0.00001時,正則化的作用比較微弱,網絡中權值數值相對較大,分佈在[−1.6088,1.1599]區間;在添加較強稀疏性約束𝜆 = 0.13後,網絡權值數值約束在[−0.1104,0.0785]較小範圍中,具體的權值範圍如表格 9.1 所示,同時也可以觀察到正則化後權值的稀疏性變化。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

9.6 Dropout

2012 年,Hinton 等人在其論文《Improving neural networks by preventing co-adaptationof feature detectors》中使用了 Dropout 方法來提高模型性能。Dropout 通過隨機斷開神經網絡的連接,減少每次訓練時實際參與計算的模型的參數量;但是在測試時,Dropout 會恢復所有的連接,保證模型測試時獲得最好的性能。

圖 9.24 是全連接層網絡在某次前向計算時連接狀況的示意圖。圖(a)是標準的全連接神經網絡,當前節點與前一層的所有輸入節點相連。
在添加了 Dropout 功能的網絡層中,如圖 9.24(b)所示,每條連接是否斷開符合某種預設的概率分佈,如斷開概率爲𝑝的伯努利分佈。圖 9.24(b)中的顯示了某次具體的採樣結果,虛線代表了採樣結果爲斷開的連接線,實線代表了採樣結果不斷開的連接線。
在這裏插入圖片描述
在 TensorFlow 中,可以通過 tf.nn.dropout(x, rate)函數實現某條連接的 Dropout 功能,其中 rate 參數設置斷開的概率值𝑝。例如:

# 添加 dropout 操作,斷開概率爲 0.5
x = tf.nn.dropout(x, rate=0.5)

也可以將 Dropout 作爲一個網絡層使用,在網絡中間插入一個 Dropout 層。例如:

# 添加 Dropout 層,斷開概率爲 0.5
model.add(layers.Dropout(rate=0.5))

爲了驗證 Dropout 層對網絡訓練的影響,我們在維持網絡層數等超參數不變的條件下,通過在 5 層的全連接層中間隔插入不同數量的 Dropout 層來觀測 Dropout 對網絡訓練的影響。如圖 9.25、圖 9.26、圖 9.27、圖 9.28 所示,分佈繪製了不添加 Dropout 層,添加 1、2、4 層 Dropout 層網絡模型的決策邊界效果。可以看到,在不添加 Dropout 層時,網絡模型與之前觀測的結果一樣,出現了明顯的過擬合現象;隨着 Dropout 層的增加,網絡模型訓練時的實際容量減少,泛化能力變強
在這裏插入圖片描述
在這裏插入圖片描述

9.7 數據增強

除了上述介紹的方式可以有效檢測和抑制過擬合現象之外,增加數據集規模是解決過擬合最重要的途徑。但是收集樣本數據和標籤往往是代價昂貴的,在有限的數據集上,通過數據增強技術可以增加訓練的樣本數量,獲得一定程度上的性能提升。數據增強(DataAugmentation)是指在維持樣本標籤不變的條件下,根據先驗知識改變樣本的特徵,使得新產生的樣本也符合或者近似符合數據的真實分佈

以圖片數據爲例,我們來介紹怎麼做數據增強。數據集中的圖片大小往往是不一致的,爲了方便神經網絡處理,需要將圖片縮放到某個固定的大小,如圖 9.29 所示,是縮放後的固定224 × 224大小的圖片。對於圖中的人物圖片,根據先驗知識,我們知道旋轉、縮放、平移、裁剪、改變視角、遮擋某局部區域都不會改變圖片的主體類別標籤,因此針對圖片數據,可以有多種數據增強方式。
在這裏插入圖片描述
TensorFlow 中提供了常用圖片的處理函數,位於 tf.image 子模塊中。通過tf.image.resize 函數可以實現圖片的縮放功能,我們將數據增強一般實現在預處理函數preprocess中,將圖片從文件系統讀取進來後,即可進行圖片數據增強操作。例如:

import matplotlib.pyplot as plt
import tensorflow as tf
from imageio import imread, imwrite
import os
import  warnings
warnings.filterwarnings('ignore')

img = imread('lena512color.tiff')

print(img.shape)
plt.imshow(img)
plt.axis('off')
plt.show()
# 轉換格式成png,否則無法進行tensorflow圖像處理
if not os.path.isfile("lena512color.png"):
     imwrite(im=img, format='png', uri='lena512color.png')

def preprocess(x):
 # 預處理函數
 # x: 圖片的路徑
 x = tf.io.read_file(x)
 x = tf.image.decode_jpeg(x, channels=3) # RGBA
 # 圖片縮放到 244x244 大小,這個大小根據網絡設定自行調整
 x = tf.image.resize(x, [244, 244])

pic_lena = preprocess('lena512color.png')
print(pic_lena.shape)
plt.imshow(pic_lena / 255.)
plt.axis('off')
plt.show()

(244, 244, 3)
9.7.1 旋轉

旋轉圖片是非常常見的圖片數據增強方式,通過將原圖進行一定角度的旋轉運算,可以獲得不同角度的新圖片,這些圖片的標籤信息維持不變,如圖 9.30 所示。
在這裏插入圖片描述
通過 tf.image.rot90(x, k=1)可以實現圖片按逆時針方式旋轉 k 個 90 度,例如:

# 圖片逆時針旋轉 180 度
rot_x = tf.image.rot90(pic_lena, 2)
plt.imshow(rot_x / 255.)
plt.axis('off')
plt.show()
9.7.2 翻轉

圖片的翻轉分爲沿水平軸翻轉和豎直軸翻轉,分別如圖 9.31、圖 9.32 所示。在TensorFlow 中,可以通過 tf.image.random_flip_left_righttf.image.random_flip_up_down 實現圖片在水平方向和豎直方向的隨機翻轉操作,例如:

 # 隨機水平翻轉
rot_x=tf.image.random_flip_left_right(pic_lena)
plt.imshow(rot_x/255.)
plt.show()

# 隨機豎直翻轉
rot_x=tf.image.random_flip_up_down(pic_lena)
plt.imshow(rot_x/255.)
plt.show()

在這裏插入圖片描述

9.7.3 裁剪

通過在原圖的左右或者上下方向去掉部分邊緣像素,可以保持圖片主體不變,同時獲得新的圖片樣本。在實際裁剪時,一般先將圖片縮放到略大於網絡輸入尺寸的大小,再裁剪到合適大小。例如網絡的輸入大小爲224 × 224,那麼可以先通過 resize 函數將圖片縮放到244 × 244大小,再隨機裁剪到224 × 224大小。代碼實現如下:

# 圖片先縮放到稍大尺寸
x = tf.image.resize(pic_lena, [244, 244])
# 再隨機裁剪到合適尺寸
x = tf.image.random_crop(pic_lena, [224,224,3])

圖 9.33 是縮放到244 × 244大小的圖片,圖 9.34 某次隨機裁剪到224 × 224大小的例子,圖 9.35 也是某次隨機裁剪的例子。
在這裏插入圖片描述

9.7.4 生成數據

通過生成模型在原有數據上進行訓練,學習到真實數據的分佈,從而利用生成模型獲得新的樣本,這種方式也可以在一定程度上提升網絡性能。如通過條件生成對抗網絡(Conditional GAN,簡稱 CGAN)可以生成帶標籤的樣本數據,如圖 9.36 所示。
在這裏插入圖片描述

9.7.5 其它方式

除了上述介紹的典型圖片數據增強方式以外,可以根據先驗知識,在不改變圖片標籤信息的條件下,任意變換圖片數據,獲得新的圖片。圖 9.37 演示了在原圖上疊加高斯噪聲後的圖片數據,圖 9.38 演示了通過改變圖片的觀察視角後獲得的新圖片,圖 9.39 演示了在原圖上隨機遮擋部分區域獲得的新圖片。
在這裏插入圖片描述

9.8 過擬合問題實戰

前面我們大量使用了月牙形狀的 2 分類數據集來演示網絡模型在各種防止過擬合措施下的性能表現。本節實戰我們將基於月牙形狀的 2 分類數據集的過擬合與欠擬合模型,進行完整的實戰。

9.8.1 構建數據集

我們使用的數據集樣本特性向量長度爲 2,標籤爲 0 或 1,分別代表了兩種類別。藉助於 scikit-learn 庫中提供的 make_moons 工具,我們可以生成任意多數據的訓練集。首先打開 cmd 命令終端,安裝 scikit-learn 庫,命令如下:

# pip 安裝 scikit-learn 庫
pip install -U scikit-learn

爲了演示過擬合現象,我們只採樣了 1000 個樣本數據,同時添加標準差爲 0.25 的高斯噪聲數據。代碼如下:

# 導入數據集生成工具
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
#從moon分佈中隨機採樣1000個點,並切分爲訓練集和測試集
N_SAMPLES = 1000
TEST_SIZE = None
X,y=make_moons(n_samples=N_SAMPLES,noise=0.25,random_state=100)
X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=TEST_SIZE,random_state=42)

make_plot 函數可以方便地根據樣本的座標 X 和樣本的標籤 y 繪製出數據的分佈圖:

import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np


def mscatter(x, y, ax=None, m=None, **kw):
    import matplotlib.markers as mmarkers
    if not ax: ax = plt.gca()
    sc = ax.scatter(x, y, **kw)
    if (m is not None) and (len(m) == len(x)):
        paths = []
        for marker in m:
            if isinstance(marker, mmarkers.MarkerStyle):
                marker_obj = marker
            else:
                marker_obj = mmarkers.MarkerStyle(marker)
            path = marker_obj.get_path().transformed(
                marker_obj.get_transform())
            paths.append(path)
        sc.set_paths(paths)
    return sc

def make_plot(X, y, plot_name, file_name, XX, YY, preds, dark=False):
    # 繪製數據集的分佈, X 爲 2D 座標, y 爲數據點的標籤
    if dark:
        plt.style.use('dark_background')
    else:
        sns.set_style("whitegrid")
    axes = plt.gca()
    axes.set_xlim([-2, 3])
    axes.set_ylim([-1.5, 2])
    axes.set(xlabel="$x_1$", ylabel="$x_2$")
    plt.title(plot_name, fontsize=20, fontproperties='SimHei')
    plt.subplots_adjust(left=0.20)
    plt.subplots_adjust(right=0.80)
    if XX is not None and YY is not None and preds is not None:
        plt.contourf(XX, YY, preds.reshape(XX.shape), 25, alpha=0.08, cmap=plt.cm.Spectral)
        plt.contour(XX, YY, preds.reshape(XX.shape), levels=[.5], cmap="Greys", vmin=0, vmax=.6)
    # 繪製散點圖,根據標籤區分顏色m=markers
    markers = ['o' if i == 1 else 's' for i in y.ravel()]
    mscatter(X[:, 0], X[:, 1], c=y.ravel(), s=20, cmap=plt.cm.Spectral, edgecolors='none', m=markers, ax=axes)
    # 保存矢量圖
    plt.savefig(file_name)
    plt.show()

繪製出採樣的 1000 個樣本分佈,如圖 9.40 所示,紅色方塊點爲一個類別,藍色圓點爲另一個類別。

# 繪製數據集分佈
 make_plot(X_train,y_train,None,'dataset.svg',None,None,None)

在這裏插入圖片描述

9.8.2 網絡層數的影響

爲了探討不同的網絡深度下的過擬合程度,我們共進行了 5 次訓練實驗。在𝑛 ∈ [0,4]時,構建網絡層數爲𝑛 + 2層的全連接層網絡,並通過 Adam 優化器訓練 500 個 Epoch,獲得網絡在訓練集上的分隔曲線,如圖 9.12、圖 9.13、圖 9.14、圖 9.15 中所示。

from tensorflow.keras import Sequential,layers
import seaborn as sns

def network_layers_influence(X_train, y_train):
    # 構建 5 種不同層數的網絡
    for n in range(5):
        # 創建容器
        model = Sequential()
        # 創建第一層
        model.add(layers.Dense(8, input_dim=2, activation='relu'))
        # 添加 n 層,共 n+2 層
        for _ in range(n):
            model.add(layers.Dense(32, activation='relu'))
        # 創建最末層
        model.add(layers.Dense(1, activation='sigmoid'))
        # 模型裝配與訓練
        model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
        model.fit(X_train, y_train, epochs=500, verbose=1)
        # 繪製不同層數的網絡決策邊界曲線
        xx = np.arange(-2, 3, 0.01)# 可視化的 x 座標範圍爲[-2, 3]
        yy = np.arange(-1.5, 2, 0.01) # 可視化的 y 座標範圍爲[-1.5, 2]
        # 利用訓練好的模型,用(XX,YY)生成的數據特徵來預測標籤的結果
        XX, YY = np.meshgrid(xx, yy)
        preds = model.predict_classes(np.c_[XX.ravel(), YY.ravel()])
        title = "網絡層數:{0}".format(2 + n)
        file = "網絡容量_%i.png" % (2 + n)
        make_plot(X_train, y_train, title, file, XX, YY, preds)

network_layers_influence(X_train,y_train)

在這裏插入圖片描述
在這裏插入圖片描述

9.8.3 Dropout 的影響

爲了探討 Dropout 層對網絡訓練的影響,我們共進行了 5 次實驗,每次實驗使用 7 層的全連接層網絡進行訓練,但是在全連接層中間隔插入 0~4 個 Dropout 層,並通過 Adam優化器訓練 500 個 Epoch,網絡訓練效果如圖 9.25、圖 9.26、圖 9.27、圖 9.28 所示。
在這裏插入圖片描述
在這裏插入圖片描述

def dropout_influence(x_train,y_train):
    # 構建5種不同數量的Dropout層的網絡
    for n in range(5):
        # 創建容器
        model=Sequential()
        # 創建第一層
        model.add(layers.Dense(8,input_dim=2,activation='relu'))
        counter=0
        # 網絡層數固定爲5
        for _ in range(5):
            model.add(layers.Dense(64,activation='relu'))
        # 添加n個Dropout
        if counter<n:
            counter+=1
            model.add(layers.Dropout(rate=0.5))

        # 輸出層
        model.add(layers.Dense(1,activation='sigmoid'))
        # 模型裝配
        model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['accuracy'])
        # 訓練
        model.fit(X_train,y_train,epochs=500,verbose=1)
        # 繪製不同 Dropout 層數的決策邊界曲線
        # 可視化的 x 座標範圍爲[-2, 3]
        xx = np.arange(-2, 3, 0.01)
        # 可視化的 y 座標範圍爲[-1.5, 2]
        yy = np.arange(-1.5, 2, 0.01)
        # 生成 x-y 平面採樣網格點,方便可視化
        XX, YY = np.meshgrid(xx, yy)
        preds = model.predict_classes(np.c_[XX.ravel(), YY.ravel()])
        title = "無Dropout層" if n == 0 else "{0}層 Dropout層".format(n)
        file = "Dropout_{}.png" .format(n)
        make_plot(X_train, y_train, title, file, XX, YY, preds)

dropout_influence(X_train,y_train)
9.8.4 正則化的影響

爲了探討正則化係數𝜆對網絡模型訓練的影響,我們採用 L2 正則化方式,構建了 5 層的神經網絡,其中第 2、3、4 層神經網絡層的權值張量 W 均添加 L2 正則化約束項,代碼如下:

def build_model_with_regularization(_lambda):
    # 創建帶正則化項的神經網絡
    model = Sequential()
    model.add(layers.Dense(8, input_dim=2, activation='relu'))  # 不帶正則化項
    # 2-4層均是帶 L2 正則化項
    model.add(layers.Dense(256, activation='relu', kernel_regularizer=regularizers.l2(_lambda)))
    model.add(layers.Dense(256, activation='relu', kernel_regularizer=regularizers.l2(_lambda)))
    model.add(layers.Dense(256, activation='relu', kernel_regularizer=regularizers.l2(_lambda)))
    # 輸出層
    model.add(layers.Dense(1, activation='sigmoid'))
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])  # 模型裝配
    return model

在保持網絡結構不變的條件下,我們通過調節正則化係數𝜆 =0.00001、0.001、0.1、0.12、0.13來測試網絡的訓練效果,並繪製出學習模型在訓練集上的決策邊界曲線,如圖 9.16、圖 9.17、圖 9.18、圖 9.19 所示。
在這裏插入圖片描述
在這裏插入圖片描述

from tensorflow.keras import regularizers

def build_model_with_regularization(_lambda):
    # 創建帶正則化項的神經網絡
    model = Sequential()
    model.add(layers.Dense(8, input_dim=2, activation='relu'))  # 不帶正則化項
    # 2-4層均是帶 L2 正則化項
    model.add(layers.Dense(256, activation='relu', kernel_regularizer=regularizers.l2(_lambda)))
    model.add(layers.Dense(256, activation='relu', kernel_regularizer=regularizers.l2(_lambda)))
    model.add(layers.Dense(256, activation='relu', kernel_regularizer=regularizers.l2(_lambda)))
    # 輸出層
    model.add(layers.Dense(1, activation='sigmoid'))
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])  # 模型裝配
    return model
def plot_weights_matrix(model, layer_index, plot_name, file_name):
    # 繪製權值範圍函數
    # 提取指定層的權值矩陣
    weights = model.layers[layer_index].get_weights()[0]
    shape = weights.shape
    # 生成和權值矩陣等大小的網格座標
    X = np.array(range(shape[1]))
    Y = np.array(range(shape[0]))
    X, Y = np.meshgrid(X, Y)
    # 繪製3D圖
    fig = plt.figure()
    ax = fig.gca(projection='3d')
    ax.xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
    ax.yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
    ax.zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
    plt.title(plot_name, fontsize=20, fontproperties='SimHei')
    # 繪製權值矩陣範圍
    ax.plot_surface(X, Y, weights, cmap=plt.get_cmap('rainbow'), linewidth=0)
    # 設置座標軸名
    ax.set_xlabel('網格x座標', fontsize=16, rotation=0, fontproperties='SimHei')
    ax.set_ylabel('網格y座標', fontsize=16, rotation=0, fontproperties='SimHei')
    ax.set_zlabel('權值', fontsize=16, rotation=90, fontproperties='SimHei')
    # 保存矩陣範圍圖
    plt.savefig(file_name + ".svg")
    plt.close(fig)
def regularizers_influence(X_train, y_train):
    for _lambda in [1e-5, 1e-3, 1e-1, 0.12, 0.13]:  # 設置不同的正則化係數
        # 創建帶正則化項的模型
        model = build_model_with_regularization(_lambda)
        # 模型訓練
        model.fit(X_train, y_train, epochs=500, verbose=1)
        # 繪製權值範圍
        layer_index = 2
        plot_title = "正則化係數:{}".format(_lambda)
        file_name = "正則化網絡權值_" + str(_lambda)
        # 繪製網絡權值範圍圖
        plot_weights_matrix(model, layer_index, plot_title, file_name)
        # 繪製不同正則化係數的決策邊界線
        # 可視化的 x 座標範圍爲[-2, 3]
        xx = np.arange(-2, 3, 0.01)
        # 可視化的 y 座標範圍爲[-1.5, 2]
        yy = np.arange(-1.5, 2, 0.01)
        # 生成 x-y 平面採樣網格點,方便可視化
        XX, YY = np.meshgrid(xx, yy)
        preds = model.predict_classes(np.c_[XX.ravel(), YY.ravel()])
        title = "正則化係數:{}".format(_lambda)
        file = "正則化_%g.svg" % _lambda
        make_plot(X_train, y_train, title, file, XX, YY, preds)

regularizers_influence(X_train, y_train)

綜上所述,附上所有代碼。

#!/usr/bin/env python
# encoding: utf-8


import matplotlib.pyplot as plt
# 導入數據集生成工具
import numpy as np
import seaborn as sns
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
from tensorflow.keras import layers, Sequential, regularizers
from mpl_toolkits.mplot3d import Axes3D

plt.rcParams['font.size'] = 16
plt.rcParams['font.family'] = ['STKaiti']
plt.rcParams['axes.unicode_minus'] = False

OUTPUT_DIR = 'output_dir'
N_EPOCHS = 500

def load_dataset():
    # 採樣點數
    N_SAMPLES = 1000
    # 測試數量比率
    TEST_SIZE = None

    # 從 moon 分佈中隨機採樣 1000 個點,並切分爲訓練集-測試集
    X, y = make_moons(n_samples=N_SAMPLES, noise=0.25, random_state=100)
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=TEST_SIZE, random_state=42)
    return X, y, X_train, X_test, y_train, y_test
 
def make_plot(X, y, plot_name, file_name, XX=None, YY=None, preds=None, dark=False, output_dir=OUTPUT_DIR):
    # 繪製數據集的分佈, X 爲 2D 座標, y 爲數據點的標籤
    if dark:
        plt.style.use('dark_background')
    else:
        sns.set_style("whitegrid")
    axes = plt.gca()
    axes.set_xlim([-2, 3])
    axes.set_ylim([-1.5, 2])
    axes.set(xlabel="$x_1$", ylabel="$x_2$")
    plt.title(plot_name, fontsize=20, fontproperties='SimHei')
    plt.subplots_adjust(left=0.20)
    plt.subplots_adjust(right=0.80)
    if XX is not None and YY is not None and preds is not None:
        plt.contourf(XX, YY, preds.reshape(XX.shape), 25, alpha=0.08, cmap=plt.cm.Spectral)
        plt.contour(XX, YY, preds.reshape(XX.shape), levels=[.5], cmap="Greys", vmin=0, vmax=.6)
    # 繪製散點圖,根據標籤區分顏色m=markers
    markers = ['o' if i == 1 else 's' for i in y.ravel()]
    mscatter(X[:, 0], X[:, 1], c=y.ravel(), s=20, cmap=plt.cm.Spectral, edgecolors='none', m=markers, ax=axes)
    # 保存矢量圖
    plt.savefig(output_dir + '/' + file_name)
    plt.close()

def mscatter(x, y, ax=None, m=None, **kw):
    import matplotlib.markers as mmarkers
    if not ax: ax = plt.gca()
    sc = ax.scatter(x, y, **kw)
    if (m is not None) and (len(m) == len(x)):
        paths = []
        for marker in m:
            if isinstance(marker, mmarkers.MarkerStyle):
                marker_obj = marker
            else:
                marker_obj = mmarkers.MarkerStyle(marker)
            path = marker_obj.get_path().transformed(
                marker_obj.get_transform())
            paths.append(path)
        sc.set_paths(paths)
    return sc

def network_layers_influence(X_train, y_train):
    # 構建 5 種不同層數的網絡
    for n in range(5):
        # 創建容器
        model = Sequential()
        # 創建第一層
        model.add(layers.Dense(8, input_dim=2, activation='relu'))
        # 添加 n 層,共 n+2 層
        for _ in range(n):
            model.add(layers.Dense(32, activation='relu'))
        # 創建最末層
        model.add(layers.Dense(1, activation='sigmoid'))
        # 模型裝配與訓練
        model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
        model.fit(X_train, y_train, epochs=N_EPOCHS, verbose=1)
        # 繪製不同層數的網絡決策邊界曲線
        # 可視化的 x 座標範圍爲[-2, 3]
        xx = np.arange(-2, 3, 0.01)
        # 可視化的 y 座標範圍爲[-1.5, 2]
        yy = np.arange(-1.5, 2, 0.01)
        # 生成 x-y 平面採樣網格點,方便可視化
        XX, YY = np.meshgrid(xx, yy)
        preds = model.predict_classes(np.c_[XX.ravel(), YY.ravel()])
        title = "網絡層數:{0}".format(2 + n)
        file = "網絡容量_%i.png" % (2 + n)
        make_plot(X_train, y_train, title, file, XX, YY, preds, output_dir=OUTPUT_DIR + '/network_layers')

def dropout_influence(X_train, y_train):
    # 構建 5 種不同數量 Dropout 層的網絡
    for n in range(5):
        # 創建容器
        model = Sequential()
        # 創建第一層
        model.add(layers.Dense(8, input_dim=2, activation='relu'))
        counter = 0
        # 網絡層數固定爲 5
        for _ in range(5):
            model.add(layers.Dense(64, activation='relu'))
        # 添加 n 個 Dropout 層
        if counter < n:
            counter += 1
            model.add(layers.Dropout(rate=0.5))

        # 輸出層
        model.add(layers.Dense(1, activation='sigmoid'))
        # 模型裝配
        model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
        # 訓練
        model.fit(X_train, y_train, epochs=N_EPOCHS, verbose=1)
        # 繪製不同 Dropout 層數的決策邊界曲線
        # 可視化的 x 座標範圍爲[-2, 3]
        xx = np.arange(-2, 3, 0.01)
        # 可視化的 y 座標範圍爲[-1.5, 2]
        yy = np.arange(-1.5, 2, 0.01)
        # 生成 x-y 平面採樣網格點,方便可視化
        XX, YY = np.meshgrid(xx, yy)
        preds = model.predict_classes(np.c_[XX.ravel(), YY.ravel()])
        title = "無Dropout層" if n == 0 else "{0}層 Dropout層".format(n)
        file = "Dropout_%i.png" % n
        make_plot(X_train, y_train, title, file, XX, YY, preds, output_dir=OUTPUT_DIR + '/dropout')

def build_model_with_regularization(_lambda):
    # 創建帶正則化項的神經網絡
    model = Sequential()
    model.add(layers.Dense(8, input_dim=2, activation='relu'))  # 不帶正則化項
    # 2-4層均是帶 L2 正則化項
    model.add(layers.Dense(256, activation='relu', kernel_regularizer=regularizers.l2(_lambda)))
    model.add(layers.Dense(256, activation='relu', kernel_regularizer=regularizers.l2(_lambda)))
    model.add(layers.Dense(256, activation='relu', kernel_regularizer=regularizers.l2(_lambda)))
    # 輸出層
    model.add(layers.Dense(1, activation='sigmoid'))
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])  # 模型裝配
    return model

def plot_weights_matrix(model, layer_index, plot_name, file_name, output_dir=OUTPUT_DIR):
    # 繪製權值範圍函數
    # 提取指定層的權值矩陣
    weights = model.layers[layer_index].get_weights()[0]
    shape = weights.shape
    # 生成和權值矩陣等大小的網格座標
    X = np.array(range(shape[1]))
    Y = np.array(range(shape[0]))
    X, Y = np.meshgrid(X, Y)
    # 繪製3D圖
    fig = plt.figure()
    ax = fig.gca(projection='3d')
    ax.xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
    ax.yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
    ax.zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
    plt.title(plot_name, fontsize=20, fontproperties='SimHei')
    # 繪製權值矩陣範圍
    ax.plot_surface(X, Y, weights, cmap=plt.get_cmap('rainbow'), linewidth=0)
    # 設置座標軸名
    ax.set_xlabel('網格x座標', fontsize=16, rotation=0, fontproperties='SimHei')
    ax.set_ylabel('網格y座標', fontsize=16, rotation=0, fontproperties='SimHei')
    ax.set_zlabel('權值', fontsize=16, rotation=90, fontproperties='SimHei')
    # 保存矩陣範圍圖
    plt.savefig(output_dir + "/" + file_name + ".svg")
    plt.close(fig)

def regularizers_influence(X_train, y_train):
    for _lambda in [1e-5, 1e-3, 1e-1, 0.12, 0.13]:  # 設置不同的正則化係數
        # 創建帶正則化項的模型
        model = build_model_with_regularization(_lambda)
        # 模型訓練
        model.fit(X_train, y_train, epochs=N_EPOCHS, verbose=1)
        # 繪製權值範圍
        layer_index = 2
        plot_title = "正則化係數:{}".format(_lambda)
        file_name = "正則化網絡權值_" + str(_lambda)
        # 繪製網絡權值範圍圖
        plot_weights_matrix(model, layer_index, plot_title, file_name, output_dir=OUTPUT_DIR + '/regularizers')
        # 繪製不同正則化係數的決策邊界線
        # 可視化的 x 座標範圍爲[-2, 3]
        xx = np.arange(-2, 3, 0.01)
        # 可視化的 y 座標範圍爲[-1.5, 2]
        yy = np.arange(-1.5, 2, 0.01)
        # 生成 x-y 平面採樣網格點,方便可視化
        XX, YY = np.meshgrid(xx, yy)
        preds = model.predict_classes(np.c_[XX.ravel(), YY.ravel()])
        title = "正則化係數:{}".format(_lambda)
        file = "正則化_%g.svg" % _lambda
        make_plot(X_train, y_train, title, file, XX, YY, preds, output_dir=OUTPUT_DIR + '/regularizers')


def main():
    X, y, X_train, X_test, y_train, y_test = load_dataset()
    # 繪製數據集分佈
    make_plot(X, y, None, "月牙形狀二分類數據集分佈.svg")
    # 網絡層數的影響
    network_layers_influence(X_train, y_train)
    # Dropout的影響
    dropout_influence(X_train, y_train)
    # 正則化的影響
    regularizers_influence(X_train, y_train)
    
if __name__ == '__main__':
    main()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章