Lesson 4.5 梯度下降优化基础:数据归一化与学习率调度
Lesson 4.5 梯度下降優化基礎:數據歸一化與學習率調度
在上一小節中,我們討論了關于隨機梯度下降和小批量梯度下降的基本算法性質與使用流程。我們知道,在引入了一定的樣本隨機性之后,能夠幫助參數點跨越局部最小值點,但對于機器學習的任何算法,在引入隨機性提升算法性能的時候也將面臨隨機性本身所造成的麻煩。在上一小節中我們看到,隨機梯度下降會伴隨著收斂過程不穩定、收斂結果持續震蕩等問題,當然面對更加復雜的數據集,隨機梯度下降還會面臨收斂所需迭代次數過多等問題。當然,相比隨機梯度下降,上述問題有所緩解,但很明顯通過增加每次迭代的樣本數量來減少隨機性進而緩解上述問題并不是長久之計。要真正幫助隨機梯度下降和小批量梯度下降解決隨機性所造成的“麻煩”,就必須采用一些圍繞迭代過程的優化方法,而所有的圍繞迭代過程的優化方法中,最基礎也是最通用的兩種方法,分別是數據歸一化方法和學習率調度。
# 科學計算模塊 import numpy as np import pandas as pd# 繪圖模塊 import matplotlib as mpl import matplotlib.pyplot as plt# 自定義模塊 from ML_basic_function import *機器學習領域的優化方法分類很多類,包括模型結果優化、評估指標優化、收斂過程優化等等,此處介紹的優化方法是圍繞梯度下降過程的收斂過程優化方法。
一、數據歸一化方法
數據歸一化方法的本質是一種對數據進行線性轉換的方法,通過構建一種樣本空間之間的線性映射關系來進行數據數值的轉化,這種轉化并不會影響數據分布,即不會影響數據的內在規律,只是對數據的數值進行調整。數據歸一化有很多方法,并且在機器學習領域有諸多用途,不僅是能夠作為梯度下降的優化算法,同時還能幫助一些數據集避免量綱不一致等問題。
經典機器學習領域的數據歸一化算法主要有兩種,分別是0-1標準化(Max-Min Normalization)和Z-Score標準化。我們先討論歸一化基本流程,再探討歸一化對機器學習算法在各方面的影響。
關于歸一化和標準化的概念辨析
??一般來說,歸一化和標準化都是指對數據進行數值轉化,根據維基百科的解釋,都是Feature scaling(特征縮放)的方法,并且都可以稱為normalization。但某些場景下也會有不同的稱呼,例如將0-1標準化稱為normalization,也就是歸一化,而把Z-Score標準化稱為Standardization,即標準化。課上對二者概念不做具體區分。
1.數據歸一化計算公式
1.1 0-1標準化
0-1標準化是最簡單同時也是最常用的標準化方法。該方法通過在輸入特征中逐列遍歷其中里的每一個數據,將Max和Min的記錄下來,并通過Max-Min作為基數(即Min=0,Max=1)進行數據的歸一化處理,基本公式為:xnormalization=x?MinMax?Min{x}_{normalization}=\frac{x-Min}{Max-Min}xnormalization?=Max?Minx?Min?
實際計算過程中需要逐列進行處理,即用每一列中的元素減去當前列的最小值,再除以該列的極差。例如:
至此,我們將a的兩列都放縮到了0-1區間內,這也是0-1標準化名稱的由來。當然,我們可以定義一個函數完成上述過程:
def maxmin_norm(X):"""max—min normalization標準化函數"""maxmin_range = X.max(axis=0) - X.min(axis=0)return (X - X.min(axis=0)) / maxmin_range maxmin_norm(a) #array([[0. , 0. ], # [0.2, 0.2], # [0.4, 0.4], # [0.6, 0.6], # [0.8, 0.8], # [1. , 1. ]])0-1標準化也被稱為離差標準化
1.2 Z-Score標準化
和0-1標準化不同,Z-score標準化利用原始數據的均值(mean)和標準差(standard deviation)進行數據的標準化。同樣是逐列進行操作,每一條數據都減去當前列的均值再除以當前列的標準差,在這種標準化操作下,如果原數據服從正態分布,處理之后的數據服從標準正態分布。Z-Score標準化計算公式如下:xnormalization=x?μσ{x}_{normalization}=\frac{x-\mu }{\sigma }xnormalization?=σx?μ?
其中μ\muμ代表均值,σ\sigmaσ代表標準差。當然,我們也可通過如下方式對張量進行Z-Score標準化處理。
和0-1標準化不同,Z-Score標準化并不會將數據放縮在0-1之間,而是均勻地分布在0的兩側。類似這種數據也被稱為Zero-Centered Data,在深度學習領域有重要應用。當然我們可以將上述過程封裝為一個函數:
def z_score(X):"""Z-Score標準化函數"""return (X - X.mean(axis=0)) / X.std(axis=0)一種更加嚴謹的做法,是在分母項、也就是標準差上加上一個非常小的常數μ\muμ,從而使得分母恒大于0。
z_score(a) #array([[-1.46385011, -1.46385011], # [-0.87831007, -0.87831007], # [-0.29277002, -0.29277002], # [ 0.29277002, 0.29277002], # [ 0.87831007, 0.87831007], # [ 1.46385011, 1.46385011]])Z-Score標準化也被稱為0均值標準化
1.3 非線性標準化
除了0-1標準化和Z-Score標準化外,還有一類使用非線性函數進行歸一化操作的方法。其中最具代表性的是Sigmoid標準化。
Sigmoid標準化其實非常好理解,就是利用Sigmoid函數對數據集的每一列進行處理,由于Sigmoid函數特性,處理之后的數據也將被壓縮到0-1之間。
a #array([[ 0, 1], # [ 2, 3], # [ 4, 5], # [ 6, 7], # [ 8, 9], # [10, 11]])# 借助此前定義的Sigmoid函數 sigmoid(a) #array([[0.5 , 0.73105858], # [0.88079708, 0.95257413], # [0.98201379, 0.99330715], # [0.99752738, 0.99908895], # [0.99966465, 0.99987661], # [0.9999546 , 0.9999833 ]])當然,相比Sigmoid標準化,Z-Score標準化實際用途更廣。
2.數據歸一化算法執行過程
我們以0-1標準化為例,來探討數據歸一化處理對量綱的影響以及在實際建模過程中的計算流程。當然其他標準化也類似。
對于0-1標準化來說,表面上看起來只是將每一列數據都放縮至0-1區間內,但實際上卻有著非常多的用途。一個最簡單的使用場景是,當數據集中不同列的量綱不一致時,通過對每一列的0-1標準化處理,能夠消除因為這種不一致而引發的算法學習偏差。例如,在鳶尾花數據中,每一列都是以厘米作為單位,整體數據分布相對統一,但如果把其中某一列改為毫米、而其他幾列改為米作為單位,則以毫米為單位的列數值將特別大,而其他幾列數值將特別小,如此一來就會對包括線性方程在內的一系列模型建模造成重大影響,模型將無法“均勻的”從各列中提取信息。
iris_df = pd.read_csv("iris.csv") iris_df
以線性回歸為例進行說明,假設現在有數據集滿足y=200x1?x2+1y=200x_1-x_2+1y=200x1??x2?+1關系,數據基本情況如下:
數據的真實規律是第一個特征其實對標簽的取值起到非常重大的作用,但在實際數據獲取記錄過程中,如果量綱錯配,即給了第一個特征一個非常大的量綱、第二個特征一個非常小的量綱,那么數據情況和實際建模情況就將如下所示:
features[:,:1] = features[:,:1] * 0.1 #取出來是一個二維數組,features[:,0]取出來是一維數組 features[:,1:2] = features[:,1:2] * 10 features #array([[ 0.13292122, -7.70033452, 1. ], # [ -0.03162804, -9.90810387, 1. ], # [ -0.10708163, -14.3871328 , 1. ], # ..., # [ 0.15507578, -3.5986144 , 1. ], # [ -0.13626716, -6.13535618, 1. ], # [ -0.14402913, 5.04394254, 1. ]])則在進行線性回歸建模時,算得各列線性關系如下:
np.linalg.lstsq(features, labels, rcond=-1)[0] #array([[ 1.99999619e+03], # [-9.99852807e-02], # [ 9.99705410e-01]]) np.linalg.lstsq(features, labels, rcond=-1)[0][0] #array([1999.99618924]) np.linalg.lstsq(features, labels, rcond=-1)[0][1] #array([-0.09998528]) 1999.99618924 / -0.09998528 #-20002.906320210335此時模型為了捕捉第一列相對更加重要的特性,計算所得的第一列特征取值非常大,甚至是第二列特征系數的10的5次方倍左右。盡管上述模型結果仍然是可以使用的結果,但特征系數差異性的增加(由200倍變成20000倍差異)會導致兩個問題,其一是部分系數太小而導致計算精度問題,其二則是在特征重要性判別上會忽視系數較小的特征。而為了能夠消除這種量綱差異所帶來的規律挖掘方面的影響,我們就需要采用歸一化方法。例如對上述數據集進行歸一化處理之后再進行建模過程如下:
features_max = features[:, :2].max(0) features_min = features[:, :2].min(0) features_range = features[:, :2].max(0) - features[:, :2].min(0)(features[:, :2] - features_min) / features_range #array([[0.72219451, 0.35489507], # [0.44561184, 0.32515777], # [0.31878564, 0.26482801], # ..., # [0.75943302, 0.41014271], # [0.26972912, 0.37597436], # [0.25668241, 0.52655264]]) maxmin_norm(features[:, :2]) #array([[0.72219451, 0.35489507], # [0.44561184, 0.32515777], # [0.31878564, 0.26482801], # ..., # [0.75943302, 0.41014271], # [0.26972912, 0.37597436], # [0.25668241, 0.52655264]]) features[:, :2] = maxmin_norm(features[:, :2]) features #array([[0.72219451, 0.35489507, 1. ], # [0.44561184, 0.32515777, 1. ], # [0.31878564, 0.26482801, 1. ], # ..., # [0.75943302, 0.41014271, 1. ], # [0.26972912, 0.37597436, 1. ], # [0.25668241, 0.52655264, 1. ]]) #標簽歸一化(1)回歸問題(2)0——1標準化 # (1)轉化為正值注意,關于標簽是否需要歸一化的問題,一般來說這并不是一個典型的操作,在絕大多數情況下我們也并不會對標簽進行歸一化操作。但此處,由于需要在歸一化后全都為正數的特征上進行回歸類問題預測,且標簽取值有正有負,因此可以考慮對標簽進行歸一化處理,以方便觀測后續模型參數。不過盡管如此,此處的標簽歸一化也并不是必須的。
w = np.linalg.lstsq(features, maxmin_norm(labels), rcond=-1)[0] w #array([[ 0.99847747], # [-0.00622912], # [ 0.00410845]])此時我們能發現,盡管參數取值和背后真實規律有差異,但基本能夠體現第一個特征重要性遠高于第二個特征的一般規律,并且二者基本相差200倍的關系。
-0.00622912 * 200 #-1.2458240.00410845 * 200 #0.8216899999999999很明顯,該結果是一個相對更加準確的結果。而如果我們現在假設數據集滿足上述關系,那么對于新進來的數據,應該如何預測呢?其實非常簡單,只需要借助訓練數據中計算出的極值對新數據進行處理即可,例如我們又獲得數據如下:
features1, labels1 = arrayGenReg(num_examples = 100, w = [200, -1, 1], delta=0.01) features1[:,:1] = features1[:,:1] * 0.1 features1[:,1:2] = features1[:,1:2] * 10相同總體中獲取的滿足相同規律的數據,當然新數據理論上應該是“不帶”標簽的。
features1[:, :2] = (features1[:, :2] - features_min) / features_range features1[:5] #array([[0.90106853, 0.51624561, 1. ], # [0.73491277, 0.6617905 , 1. ], # [0.25451935, 0.53039679, 1. ], # [0.47933854, 0.43441831, 1. ], # [0.24460548, 0.63027853, 1. ]])此時模型預測輸出結果為:
yhat = features1.dot(w) yhat[:5] #array([[0.90058932], # [0.73377993], # [0.25493639], # [0.48001114], # [0.24441544]])此時需要知道的是,由于在訓練參數w的時候是對標簽也進行了歸一化處理的,所以當前預測結果和真實標簽還差了一個歸一化過程,此時如果是面對完全未知情況進行預測,我們需要講yhat逆向歸一化處理,即
labels_min = labels.min(0) labels_max = labels.max(0) labels_range = labels.max(0) - labels.min(0) yhat = yhat * labels_range + labels_min yhat[:5] #array([[ 479.25093691], # [ 280.4665185 ], # [-290.1646276 ], # [ -21.94619293], # [-302.70229372]])比較真實標簽:
labels1[:5] #array([[ 479.25042102], # [ 280.45978588], # [-290.16538845], # [ -21.94008706], # [-302.70455066]])能夠看出,在經過歸一化處理之后模型能夠排除量綱差異所導致的學習偏倚問題,最終得到一個準確率較高的結果。
當然,如果是劃分訓練集和測試集進行建模并且進行歸一化操作,那么在遵循“在訓練集上訓練,在測試集上進行測試”的基本原則下,我們首先在訓練集上進行數據歸一化處理并記錄各列的極值,然后當模型訓練完成之后,再借助訓練集各列的極值來對測試集數據進行歸一化,再帶入模型進行測試。當然,如果這個過程對標簽也進行了歸一化處理,則標簽的歸一化過程和特征歸一化過程無異,唯一需要注意的是如果是對未知數據進行預測,即需要模型輸出和真實采集到數據類似的結果,則需要在模型輸出的歸一化的標簽基礎上進行逆向歸一化處理。
此外,一般來說如果是Z-Score標準化,則無需對標簽進行標準化處理。
3.數據歸一化算法評價
此處我們通過量綱不一致問題引出歸一化方法,但歸一化方法卻并不一定、且不僅僅應用于處理量綱不一致問題中。
首先,并非所有模型都受到數據各列的絕對數值大小影響,在通用的模型中,線性模型和距離類模型是兩類典型的會受到各列絕對數值大小影響的模型,例如線性回歸、KNN、K-Means(一種無監督的聚類模型)等,并且邏輯回歸在使用ECOC編碼進行類別判別時也是利用距離來判別樣本最終歸屬,此時,由于各列的絕對數值會影響模型學習的偏重,模型會更加側重于學習那些數值比較大的列,而無法“均勻”的從各列中提取有效信息,因此有時會出現較差的模型結果。但有些模型卻不受此影響,典型的如樹模型。
辯證的看,“均勻”的從各列提取有效信息其實也并不一定是最好的做法,本身對于有監督學習算法來說,大多數數據集各列的重要性就不是等價的。但是,比起無法“均勻”的從各列提取有效信息,更可怕的是我們會不受控制的“不均勻”的去提取有效信息,這也是歸一化要解決的核心問題。
其次,我們需要知道,一旦對數據進行歸一化處理,數據就將失去可解釋性,也就是失去了量綱。例如對于鳶尾花數據來說,原始數據代表花瓣花萼的長寬測量結果,而如果我們對其進行歸一化處理,則每條數據就無法再給予明確的現實意義,這也是在很多要求可解釋性的情況下我們應該避免使用歸一化方法的原因。
不僅是歸一化方法,其實所有的樣本空間的映射都會改變數據集的可解釋性。
其三,歸一化方法屬于仿射變換的一種特殊形式,而所有的仿射變換其實都不會影響數據集原始分布,也就是并不影響數據集真實規律,只會影響某些算法挖掘規律的難度(也就是受到特征絕對數值影響的算法)。例如對如下數據,我們可以觀察其歸一化前后的數據分布變化情況:
# 設置隨機數種子 np.random.seed(24) # 擾動項取值為0.01 features, labels = arrayGenReg(delta=0.01)# 繪制圖像進行觀察 plt.subplot(121) plt.plot(features[:, 0], labels, 'o') plt.subplot(122) plt.plot(maxmin_norm(features[:, 0]), maxmin_norm(labels), 'o')仿射變換指的是樣本空間平移(加減某個數)和放縮(乘除某個數)的變換。0-1標準化過程中,平移就是減去每一列最小值,放縮就是除以某一列的極差。
最后,也是最重要的一點,那就是對于梯度下降算法來說,歸一化能夠提高收斂速度,例如下圖所示,經過歸一化處理之后的數據,在進行損失函數構造時損失函數的等高線圖將更加均勻,此時梯度下降的收斂速度也將更快,具體理論理解詳見下文論述,而在實際使用過程中,經過歸一化的數據在梯度下降過程中往往收斂更快,這其實是相比消除量綱影響,歸一化方法更加重要應用場景。
在提高收斂速度方面,Z-Score效果要好于0-1標準化。
此處可以舉例說明。還是以前兩節的等高線圖案例進行說明,嘗試對比在進行數據歸一化前后兩組數據損失函數的等高線圖及收斂軌跡圖。
則歸一化前的SSE損失函數為:SSE=(2?w?b)2+(4?3w?b)SSE = (2-w-b)^2+(4-3w-b)SSE=(2?w?b)2+(4?3w?b)
而歸一化之后的損失函數為:SSE=(2+w?b)2+(4?w?b)2SSE = (2+w-b)^2 + (4-w-b)^2SSE=(2+w?b)2+(4?w?b)2
對比兩個損失函數的等高線圖及梯度下降軌跡圖:
能夠看出經過歸一化處理的之后的數據損失函數等高線更加均勻,收斂效率更高。
關于歸一化能夠讓等高線更加均勻從而加快迭代收斂過程的理解:
??從理論角度出發,其實梯度下降過程每一步參數點移動的方向是能夠讓梯度最快速下降的方向,也就是圖片上垂直于等高線的方向。但這種所謂的最快速的方向只在開始移動的一瞬間滿足,由于梯度是連續變化的函數,因此當移動了一小步之后“最優方向”其實就可能發生了變化,但參數只能在下次移動時再改變方向,因此中間其實很長一段距離參數并不不一定是沿著最優方向在進行移動。這里需要注意,如果下一次移動的方向和上一次移動方向一致或者類似,那就說明這次移動過程中參數并沒有偏離方向太多,反之則這次移動走了很多彎路。而當損失函數的等高線是均勻分布時,外圈的垂直線也就是內圈的垂直線,此時參數兩次移動過程大概率最優方向一致,也就是說相同的移動能夠更大程度降低損失函數值,而如果類似圖1中的情況,內外圈分布不均勻,則參數兩次迭代過程最優方向將發生偏移,也就是說明上一次迭代過程有很長一段距離沒有沿著最優方向迭代,該次迭代只降低了有限的損失函數計算值。經次過程不斷迭代,由于經過歸一化的損失函數每次迭代效率都更高,因此相比其他損失函數,經過歸一化的數據只需要更少次的迭代就能抵達最小值點,這也就是加快收斂速度的根本原因。
另外需要注意的是,收斂更快往往也意味著能夠收斂至更靠近全局最小值的點。
4.Z-Score標準化算法評價及橫向對比
從大類上來分,Z-Score的使用場景要遠高于0-1標準化使用場景。當然這也并不是絕對的,要區分二者使用情景,我們首先需要進一步了解二者算法性能。
- 生成Zero-Centered Data
一般來說,由于Z-Score標準化生成數據的Zero-Centered特性,使得其在深度學習領域備受歡迎(是Batch Normalization的一種特殊情況)。而在機器學習領域對于標簽同時存在正負值的回歸類問題,使用Z-Score能夠避免對標簽進行歸一化。
- 標準正態分布
由于該方法同時也是正態分布轉換為標準正態分布的計算公式,因此如果原始數據滿足正態分布,則經過Z-Score轉化之后就能轉化為標準正態分布,進而可以利用標準正態分布諸多統計性質。
- 保留極端值分布
還有一點非常實用的功能,就是相比0-1標準化,Z-Score標準化能夠保留極端值的分布。例如有數據如下:
a = np.arange(8).reshape(4, 2) a[1, 1] = 100 a #array([[ 0, 1], # [ 2, 100], # [ 4, 5], # [ 6, 7]])其中第二列的第二個值就是極端值,如果對a進行0-1標準化處理,則計算結果如下:
maxmin_norm(a) #array([[0. , 0. ], # [0.33333333, 1. ], # [0.66666667, 0.04040404], # [1. , 0.06060606]])我們會發現,由于極端值的存在,會將其他數值壓縮在一個非常小的范圍內。而如果此時我們采用Z-Score進行標準化,則計算結果如下:
z_score(a) #array([[-1.34164079, -0.65692457], # [-0.4472136 , 1.72970047], # [ 0.4472136 , -0.56049527], # [ 1.34164079, -0.51228063]])我們發現,極端值仍然還是極端值(相對該列其他數值而言),此時我們即可采用極端值處理方法對其進行處理(刪除或者蓋帽)。
不過相比0-1標準化,Z-Score標準化問題也比較明顯,那就是需要進行均值和方差的計算,而該過程將耗費更大的計算量。
二、梯度下降算法優化初階
歸一化和學習率調度,是梯度下降算法優化的基本方法。
1.數據歸一化與梯度下降算法優化
接下來,我們討論歸一化與梯度下降之間的關系。此前我們通過簡單例子觀察了數據歸一化對梯度下降的影響——即歸一化能夠改變損失函數形態,而這種改變將顯著加快梯度下降的迭代收斂過程,直觀判斷是歸一化之后的損失函數等高線圖更加均勻。本小節我們將從梯度下降算法優化角度出發,討論數據歸一化和梯度下降之間的關系。
在機器學習模型優化體系中,構建損失函數和損失函數求解是模型優化的兩大核心命題,通過損失函數的構建和求解,就能夠找到模型最優參數。但對于很多復雜模型來說,損失函數構建和求解并非易事,而梯度下降作為損失函數求解的重要方法,如何優化梯度下降求解過程,使其能夠“又快又好”的找到最小值點,就成了決定建模成敗的核心因素。當然,從梯度下降到隨機梯度下降再到小批量梯度下降,我們可以理解其為算法層面上的優化,但除此以外還有許多圍繞優化梯度下降求解過程的算法和方法,例如本節介紹的歸一化和學習率調度方法。
當然,優化方法的學習也需要遵循循序漸進的過程,本節我們仍然還是在線性回歸損失函數、也就是凸函數上進行基本優化思路的介紹和基本優化方法的學習,圍繞現線性回歸的凸函數損失函數求解,其實是可以使用最小二乘法一步到位求出數值解的,但也正是因為其最優解明確存在,也就給了我們進行對照實驗的基礎。我們將利用梯度下降算法,在更加復雜的數據集上,探索如何使用優化方法,來逼近明確存在的全域最小值點,并在這個過程中深化對優化方法的理解,進而能夠在后續更加復雜的損失函數上、甚至是非凸的損失函數上,憑借我們的理解和所掌握的工具,用好最小二乘法這把利器進行更快更好的最優參數的求解。
- 數據準備
此處我們選取Lesson 1中的鮑魚數據集,并且采用其中相關性比較強的幾列進行建模分析。數據集讀取過程如下:
aba_data = pd.read_csv("abalone.csv") aba_data
其中,我們選取鮑魚數據集中的Length(身體長度)和Diameter(身體寬度/直徑)作為特征,Whole weight(體重)作為標簽進行線性回歸建模分析。
然后分別準備一份原始數據與歸一化后的數據。
features = np.concatenate((features, np.ones_like(labels)), axis=1)# 深拷貝features用于歸一化 features_norm = np.copy(features)# 歸一化處理 features_norm[:, :-1] = z_score(features_norm[:, :-1]) features #array([[0.455, 0.365, 1. ], # [0.35 , 0.265, 1. ], # [0.53 , 0.42 , 1. ], # ..., # [0.6 , 0.475, 1. ], # [0.625, 0.485, 1. ], # [0.71 , 0.555, 1. ]]) features_norm #array([[-0.57455813, -0.43214879, 1. ], # [-1.44898585, -1.439929 , 1. ], # [ 0.05003309, 0.12213032, 1. ], # ..., # [ 0.6329849 , 0.67640943, 1. ], # [ 0.84118198, 0.77718745, 1. ], # [ 1.54905203, 1.48263359, 1. ]]) features.shape #(4177, 3)- 建模過程
首先是參數初始化與定義核心參數
# 設置初始參數 np.random.seed(24) n = features.shape[1] w = np.random.randn(n, 1) w_norm = np.copy(w)# 記錄迭代過程損失函數取值變化 Loss_l = [] Loss_norm_l = []# 迭代次數/遍歷數據集次數 epoch = 100w #array([[ 1.32921217], # [-0.77003345], # [-0.31628036]])接下來,首先進行梯度下降算法嘗試。
for i in range(epoch):w = w_cal(features, w, labels, lr_gd, lr = 0.02, itera_times = 1)Loss_l.append(MSELoss(features, w, labels))w_norm = w_cal(features_norm, w_norm, labels, lr_gd, lr = 0.02, itera_times = 1)Loss_norm_l.append(MSELoss(features_norm, w_norm, labels))- 觀察結果
和上一小節介紹的一樣,我們可以通過損失函數變化曲線來觀察梯度下降執行情況
plt.plot(list(range(epoch)), np.array(Loss_l).flatten(), label='Loss_l') plt.plot(list(range(epoch)), np.array(Loss_norm_l).flatten(), label='Loss_norm_l') plt.xlabel('epochs') plt.ylabel('MSE') plt.legend(loc = 1)
我們發現,經過歸一化后的數據集,從損失函數變化圖像上來看,收斂速度更快(損失函數下降速度更快),且最終收斂到一個更優的結果。
這里需要注意,由于我們沒有對標簽進行歸優化,因此兩個迭代過程可以直接進行絕對數值的大小關系比較。
- 對比全域最小值點
當然,由于上述損失函數是凸函數,因此我們可以用最小二乘法直接求解全域最優解。這里需要注意,由于歸一化只對數據進行平移和放縮而不改變數據分布規律,因此即使最小值點位置不同,但最終對應的對標簽的預測結果應該保持一致,并且全域最小值點對應MSE數值也應該一致。
w1 = np.linalg.lstsq(features, labels, rcond=-1)[0] w1 #array([[ 1.87229017], # [ 2.33724788], # [-1.1056427 ]]) w2 = np.linalg.lstsq(features_norm, labels, rcond=-1)[0] w2 #array([[0.22482186], # [0.2319204 ], # [0.82874216]]) features.dot(w1) #array([[0.59934481], # [0.16902955], # [0.8683152 ], # ..., # [1.12792415], # [1.19810388], # [1.5208559 ]]) features_norm.dot(w2) #array([[0.59934481], # [0.16902955], # [0.8683152 ], # ..., # [1.12792415], # [1.19810388], # [1.5208559 ]]) MSELoss(features_norm, w2, labels) #array([[0.03318563]]) MSELoss(features, w1, labels) #array([[0.03318563]])- 結論分析
通過上述計算結果,我們不難分析,其實在進行梯度下降計算過程中,在以0.02作為學習率進行迭代的過程中,兩組模型都沒有收斂到全域最小值點,也就是出現了類似如下情況:
plt.title('lr=0.001') show_trace(gd(lr=0.001))
但有趣的是,為何在相同學習率下,在歸一化之后的數據集上進行梯度下降,卻更加接近全域最小值點,這又是什么原因呢?回顧此前我們所討論的歸一化對損失函數的影響,從等高線圖上來看是等高線變得更加均勻,但實際上是整個損失函數在不同區域對應梯度都更加均勻,從而在靠近最小值點附近的梯度也比歸一化之前的損失函數梯度要大,也就是說,雖然學習率相同,但由于歸一化之后最小值點附近梯度要更大,因此同樣的迭代次,在歸一化之后的損失函數上參數點將移動至更加靠近最小值地附近的點。也就類似如下情況:
學習率相同,迭代次數相同,但經過歸一化的損失函數更加陡峭,靠近最小值點附近梯度更大,因此最終收斂到一個更加靠近最小值點附近的點。而這個問題要如何解決,我們首先想到的是增加學習率,但問題是學習率增加多少才合適呢?試著增加10倍看下結果:
我們發現,在提高學習率之后,梯度下降效果略有提升,能夠收斂到一個更加趨近于全域最小值的點,并且歸一化之后的損失函數收斂速度明顯更快。但以當前學習率,還是無法收斂止最小值點,此時我們可以不斷嘗試,直到“測出”最佳學習率為止。當然,在Scikit-Learn中其實也提供了這種類似枚舉去找出最佳超參數取值的方法,但如果是面對超大規模數據集的建模,受到計算資源的限制,我們其實是無法反復建模來找到最優學習率的,此時就需要采用一種更加先進的計算流程來解決這個問題。
伴隨數據集復雜程度提升,尋找最小值點過程將越來越復雜。并且哪怕是凸函數,很多情況也無法用最小二乘法一步到位求出最優解,仍然需要依靠梯度下降來求解,此時能夠使用梯度下降的優化算法來幫助進行順利求解,就變得至關重要。
2.學習率調度
- 基本概念
其實梯度下降優化的核心目標就是希望“更快更好”的找到最小值點,歸一化是通過修改損失函數來達成這個目標,而所謂學習率調度,則是通過調整學習率來達到這個目標。值得注意的是,此時找到一個確定的最優學習率并不是目標,“更快更好”找到最小值點才是目標,因此我們完全可以考慮在迭代過程動態調整學習率。而所謂學習率調度,也并不是一個尋找最佳學習率的方法,而是一種伴隨迭代進行、不斷調整學習率的策略。
學習率調度方法有很多種,目前流行的也達數十種之多,而其中一種最為通用的學習率調度方法是學習率衰減法,指的是在迭代開始時設置較大學習率,而伴隨著迭代進行不斷減小學習率。通過這樣的學習率設置,能夠讓梯度下降收斂速度更快、效果更好。
- 實踐過程
例如在上述例子中,我們不妨設置這樣的減速衰減的一個學習調度策略,衰減過程比例由如下函數計算得出:
lr_lambda = lambda epoch: 0.95 ** epoch lr_lambda(0) #1.0 lr_lambda(2) #0.9025 lr_l = [] for i in range(10):lr_l.append(lr_lambda(i)) lr_l #[1.0, # 0.95, # 0.9025, # 0.8573749999999999, # 0.8145062499999999, # 0.7737809374999998, # 0.7350918906249998, # 0.6983372960937497, # 0.6634204312890623, # 0.6302494097246091]即假設初始學習率為0.5,則第一次迭代時實際學習率為0.5*1,第二輪迭代時學習率為0.5*0.95,以此類推。
據此,我們可以優化梯度下降迭代過程,此時我們對比恒定學習率和學習率衰減的兩個梯度下降過程,并且都采用歸一化后的數據集進行計算:
# 設置初始參數 np.random.seed(24) n = features.shape[1] w = np.random.randn(n, 1) w_lr = np.copy(w)# 記錄迭代過程損失函數取值變化 Loss_l = [] Loss_lr_l = []# 迭代次數/遍歷數據集次數 epoch = 20for i in range(epoch):w = w_cal(features_norm, w, labels, lr_gd, lr = 0.2, itera_times = 10) #迭代十次更新wLoss_l.append(MSELoss(features_norm, w, labels))w_lr = w_cal(features_norm, w_lr, labels, lr_gd, lr = 0.5*lr_lambda(i), itera_times = 10)Loss_lr_l.append(MSELoss(features_norm, w_lr, labels))plt.plot(list(range(epoch)), np.array(Loss_l).flatten(), label='Loss_l') plt.plot(list(range(epoch)), np.array(Loss_lr_l).flatten(), label='Loss_lr_l') plt.xlabel('epochs') plt.ylabel('MSE') plt.legend(loc = 1) Loss_lr_l[-1] #array([[0.03416214]]) Loss_l[-1] #array([[0.03671235]])這里有一點進行了微調,那就是我們實際上是令梯度下降計算過程中每迭代10次更新一次學習率,總共更新了20次學習率,即總共迭代了200次,最后10次更新時學習率為0.5*lr_lambda(20),即
0.5*lr_lambda(20) #0.17924296120427094整體來看學習率變化區間橫跨0.18-0.5之間,而最終上述學習率調度也確實起到了更好的效果。
- 算法評價
接下來,簡單總結學習率調度的使用場景和注意事項。
首先,在很多海量數據處理場景下,學習率調度的重大價值在于能夠提供對學習率超參數設置更大的容錯空間。在很多情況下,搜索出一個最佳學習率取值進而設置恒定學習率進行梯度下降,難度會遠高于設置一組學習率衰減的參數。并且有的時候,剛開始學習率設置過大其實也可以通過多輪迭代進行調整,其所消耗的算力也遠低于反復訓練模型尋找最佳恒定學習率。
其次,盡管上述例子我們是在梯度下降中使用學習率衰減這一調度策略,但實際上更為一般的情況是學習率調度和小批量梯度下降或者隨機梯度下降來配合使用。一般來說梯度下降的使用場景在于小規模數據集且損失函數較為簡單的情況,此時可利用梯度下降+枚舉找到最佳學習率的策略進行模型訓練,其相關操作的技術門檻相對較低(枚舉法可借助Scikit-Learn的網格搜索);而對于更大規模的數據集且損失函數情況更加復雜時,則需要考慮小批量梯度下降+學習率調度方法來進行梯度下降求解損失函數。
當然,除了學習率衰減外還有很多學習率調度策略,甚至有些學習率調度策略會間接性提高和降低學習率,來幫助梯度下降找到最小值點。更多學習率調度策略我們將在后續進階內容中繼續介紹。
3.小批量梯度下降與迭代收斂速度
小批量梯度下降不僅可以幫助損失函數跨越局部最小值點,同時也能加快梯度下降的收斂速度。例如以0.02作為學習率、batch_size為50的情況下,測試梯度下降收斂過程:
# 設置初始參數 np.random.seed(24) n = features.shape[1] w = np.random.randn(n, 1) w_norm = np.copy(w)# 記錄迭代過程損失函數取值變化 Loss_l = [] Loss_norm_l = []# 迭代次數/遍歷數據集次數 epoch = 50np.random.seed(24) w = np.random.randn(3, 1) sgd_cal(Xtrain, w, ytrain, lr_gd, batch_size=1, epoch=3000, lr=0.02) #array([[ 0.76959334], # [-0.29175077], # [-0.17624047]])# 執行迭代計算 for i in range(epoch):w = sgd_cal(features, w, labels, lr_gd, batch_size=50, epoch=1, lr=0.02)Loss_l.append(MSELoss(features, w, labels))w_norm = sgd_cal(features_norm, w_norm, labels, lr_gd, batch_size=50, epoch=1, lr=0.02)Loss_norm_l.append(MSELoss(features_norm, w_norm, labels))# 觀察計算結果 plt.plot(list(range(epoch)), np.array(Loss_l).flatten(), label='Loss_l') plt.plot(list(range(epoch)), np.array(Loss_norm_l).flatten(), label='Loss_norm_l') plt.xlabel('epochs') plt.ylabel('MSE') plt.legend(loc = 1)在遍歷50次數據集的情況下就已經能夠基本逼近全域最優解,這說明局部規律的不一致性其實也將有助于提升模型收斂效率。同時我們也能發現數據歸一化也能夠有效提升小批量梯度下降的收斂速度。(小批量梯度下降和歸一化都能加快梯度下降收斂速度)
4.梯度下降組合優化策略
當然,無論是數據歸一化、學習率調度還是采用小批量梯度下降,這些方法并不互斥,我們完全可以組合進行使用。
# 設置初始參數 np.random.seed(24) n = features.shape[1] w = np.random.randn(n, 1) w_opt = np.copy(w)# 記錄迭代過程損失函數取值變化 Loss_l = [] Loss_opt_l = []# 迭代次數/遍歷數據集次數 epoch = 100 w #array([[ 1.32921217], # [-0.77003345], # [-0.31628036]])# 執行迭代計算 for i in range(epoch):w = w_cal(features, w, labels, lr_gd, lr = 0.2, itera_times = 1)Loss_l.append(MSELoss(features, w, labels))w_opt = sgd_cal(features_norm, w_opt, labels, lr_gd, batch_size=50, epoch=1, lr=0.5*lr_lambda(i))Loss_opt_l.append(MSELoss(features_norm, w_opt, labels))# 觀察計算結果 plt.plot(list(range(epoch)), np.array(Loss_l).flatten(), label='Loss_l') plt.plot(list(range(epoch)), np.array(Loss_opt_l).flatten(), label='Loss_norm_l') plt.xlabel('epochs') plt.ylabel('MSE') plt.legend(loc = 1) Loss_opt_l[-1] #array([[0.03318614]]) Loss_l[-1] #array([[0.06004797]])據此,我們不難發現,在經過一系列優化手段處理之后的梯度下降過程,即使用歸一化進行數據處理、采用小批量梯度下降、并且采用學習率調度方法來進行參數迭代求解過程,效果會遠遠好于使用原始梯度下降算法來進行求解,不僅收斂速度更快,并且最終也能夠收斂至一個更好的結果(更加逼近全域最小值點),這也就是優化方法能夠讓梯度下降迭代過程“更好更快”收斂的直接體現。
當然,在后續的梯度下降算法使用過程中,如無特殊要求,我們將默認使用數據歸一化+學習率
衰減+小批量梯度下降算法來進行求解。而更進一步的,關于學習率衰減的其他策略及配合設置的初始學習率參數,小批量梯度下降中的小批數據量等超參數的設置,我們將在后續使用過程中進行更進一步的探討。
總結
以上是生活随笔為你收集整理的Lesson 4.5 梯度下降优化基础:数据归一化与学习率调度的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Lesson 4.34.4 梯度下降(G
- 下一篇: Lesson 4.6 逻辑回归的手动实现