Lesson 15.1 学习率调度基本概念与手动实现方法
Lesson 15.1 學習率調度基本概念與手動實現方法
??從本節開始,我們將介紹深度學習中學習率優化方法。學習率作為模型優化的重要超參數,在此前的學習中,我們已經看到了學習率的調整對模型訓練在諸多方面的顯著影響。這里我們先簡單回顧此前遇到的學習率調整的場景:
- 緩解Dead ReLU Problem
??在ReLU激活函數疊加的神經網絡中,由于ReLU本身負值歸零的特性,可能會出現Dead ReLU Problem,根據Lesson 13.4的實驗結果,我們可以通過減小學習率來降低模型落入活性失效陷進的概率。不過降低學習率也代表模型收斂速度更慢;(or KaiMing方法 or BN層) - 提升模型表現
??同時,學習率調整的也將顯著影響模型性能。根據Lesson 14中的實驗結果可知,在學習率絕對數值的調整過程中,學習率對模型性能的影響整體呈現U型(以準確率評估指標時是倒U型)特征,即學習率過大或者過小都不好,學習過大可能會導致模型無法穿越狹窄的通道最終抵達最小值點,而學習率太小則容易在最小值點附近停止收斂,因此在進行模型訓練時,我們需要找到一個適中的準確率取值(如Lesson 14.3中的0.005)。
??整體來看,如果模型學習率設置太大,雖然前期收斂速度較快,但容易出現收斂過程不穩定、收斂結果不佳、或者神經元活性失效等問題;而如果學習率設置太小,雖然收斂過程將相對平穩,并且能夠有效規避神經元活性壞死的問題,但容易出現收斂速度慢、收斂結果不佳等問題。為了深入理解該問題,同時也為了為后續實驗儲備對比數據,首先,我們可以通過設計實驗來觀測不同超參數取值對模型收斂速度、收斂結果影響。
一、學習率對模型訓練影響
??仍然利用Lesson 14中所定義的模型類和數據,通過設置不同學習率,來觀察學習率對模型收斂速度和收斂結果兩方面影響情況。
torch.manual_seed(420)features,labels = tensorGenReg(w=[2,-1,3,1,2],bias=False,deg=2)train_loader,test_loader = split_loader(features,labels,batch_size=50)# 設置隨機數種子 torch.manual_seed(24) # 關鍵參數 num_epochs = 20# 實例化模型 tanh_model1 = net_class2(act_fun = torch.tanh, in_features=5, BN_model='pre') tanh_model2 = net_class2(act_fun = torch.tanh, in_features=5, BN_model='pre') tanh_model3 = net_class2(act_fun = torch.tanh, in_features=5, BN_model='pre') tanh_model4 = net_class2(act_fun = torch.tanh, in_features=5, BN_model='pre')# tanh_model進行模型訓練 train_l1, test_l1 = model_train_test(tanh_model1, train_loader,test_loader,num_epochs = num_epochs, criterion = nn.MSELoss(), optimizer = optim.SGD, lr = 0.03, cla = False, eva = mse_cal)train_l2, test_l2 = model_train_test(tanh_model2, train_loader,test_loader,num_epochs = num_epochs, criterion = nn.MSELoss(), optimizer = optim.SGD, lr = 0.01, cla = False, eva = mse_cal)train_l3, test_l3 = model_train_test(tanh_model3, train_loader,test_loader,num_epochs = num_epochs, criterion = nn.MSELoss(), optimizer = optim.SGD, lr = 0.005, cla = False, eva = mse_cal)train_l4, test_l4 = model_train_test(tanh_model4, train_loader,test_loader,num_epochs = num_epochs, criterion = nn.MSELoss(), optimizer = optim.SGD, lr = 0.001, cla = False, eva = mse_cal)plt.plot(list(range(num_epochs)), train_l1, label='train_mse_0.03') plt.plot(list(range(num_epochs)), train_l2, label='train_mse_0.01') plt.plot(list(range(num_epochs)), train_l3, label='train_mse_0.005') plt.plot(list(range(num_epochs)), train_l4, label='train_mse_0.001') plt.legend(loc = 1)
??由此我們能夠清楚的看到,學習率較大的模型收斂速度較快,但效果卻不一定最好。而學習率非常小的模型不僅收斂速度較慢,并且效果也不盡如人意。對于tanh2來說,在當前數據集上,學習率為0.01時表現較好。
??不過,值得注意的是,盡管學習率的調整會對模型訓練造成很大的影響,但在進行學習率優化的時候一般不會采用Lesson 14.3中同時訓練多組學習率采用不同取值的模型來對比選擇最佳學習率的方法。在實際生產生活中,訓練一個模型已經是非常耗費時間的了,為了一個超參數同時訓練多組模型并不劃算。而要通過學習率數值調整來讓模型“又好又快”的收斂,就需要采用一種名為學習率調度的優化算法。
二、學習率調度基本概念與手動實現方法
1.模型調度基本概念
??既然我們無法通過訓練多組模型來尋找最優學習率,那么一個最基本的想法,就是讓學習率伴隨模型訓練過程動態調整。并且,根據上述實驗結果我們也不難發現,一個比較好的學習率動態調整策略是,先讓學習率取得較大數值,從而能夠讓模型在最開始能夠以較快的速度收斂;然后在經過一段時間迭代之后,將學習率調小,從而能夠讓收斂過程穿過損失函數的“隘口”,抵達更小的解。
??這樣的一個學習率調整的策略,也被稱為學習率調度(Learning rate scheduler)。
注:此處學習率的調整和一般的超參數調整不太一樣。一般來說,模型的超參數調整是需要找出一個有助于模型提升效果的確定性的數值,比如模型層數、激活函數、歸一化方法選取等,而一旦超參數數值確定,無論是訓練過程還是測試過程都將以該參數的取值為準。但學習率參數本質上并不是一個影響模型結構的參數,而是輔助訓練過程的參數,更準確的來說,是輔助模型各線性層(目前為止)參數取得最優解的參數。因此,學習率確實是否是確定的值其實并不重要,學習率取值可以隨著迭代過程不斷變化,只要這樣的一個動態變化的過程最終能夠讓模型“又好又快”的收斂、即讓各線性層參數以較快速度收斂至最小值。
2.手動實現學習率調度
2.1 本地實現方法
??而要在PyTorch中實現學習率的動態調整,首先我們能夠想到的是在fit過程手動調整學習率,例如,第一次fit設置2輪epochs、lr設置為0.03,第二次fit設置2輪epochs、lr設置0.01等等依此類推。我們可以借助Python中的input函數來手動實現學習率伴隨迭代epoch次數的動態調整,具體操作方法如下:
- 實例化模型
- 更新fit函數
??接下來我們對fit函數進行更新,在原有fit函數基礎上加上每一輪迭代后模型評估結果的記錄功能。
def fit_rec(net, criterion, optimizer, train_data,test_data,epochs = 3, cla = False, eva = mse_cal):"""模型訓練函數(記錄每一次遍歷后模型評估指標):param net:待訓練的模型 :param criterion: 損失函數:param optimizer:優化算法:param train_data:訓練數據:param test_data: 測試數據 :param epochs: 遍歷數據次數:param cla: 是否是分類問題:param eva: 模型評估方法:return:模型評估結果"""train_l = []test_l = []for epoch in range(epochs):net.train()for X, y in train_data:if cla == True:y = y.flatten().long() # 如果是分類問題,需要對y進行整數轉化yhat = net.forward(X)loss = criterion(yhat, y)optimizer.zero_grad()loss.backward()optimizer.step()net.eval()train_l.append(eva(train_data, net).detach())test_l.append(eva(test_data, net).detach())return train_l, test_l- 測試函數性能
能夠順利導出模型MSE計算結果,說明函數創建成功。另外,該函數也需要寫入torchLearning.py中。
- 手動動態調整過程
??接下來,我們嘗試借助Python中的input函數功能,來執行手動動態調整模型訓練過程中的學習率。
??所謂手動調整學習率,指的是可以在自定義的訓練間隔中靈活調整模型學習率,而要將手動輸入的數值作為模型當前階段訓練的參數,就需要使用input函數。Python中的input函數通過提供用戶可交互的輸入窗口來捕捉用戶的即時輸入,并將其轉化為字符串形式傳入當前操作空間中。
據此,我們可以設計一個簡單的計算流程,根據手動輸入結果靈活控制每一次迭代的epochs以及對應的學習率數值。并且將每一輪循環結果記錄在一個列表中。
# 設置隨機數種子 torch.manual_seed(24) # 實例化模型 tanh_model = net_class2(act_fun=torch.tanh, in_features=5, BN_model='pre') input("Do you want to continue the iteration? [y/n]") #Do you want to continue the iteration? [y/n] y #'y' l1 = [1, 2, 3] l2 = [2, 3, 4] l1.extend(l2) l1 #[1, 2, 3, 2, 3, 4] l1.append(l2) l1 #[1, 2, 3, 2, 3, 4, [2, 3, 4]] # 創建用于保存記錄結果的空列表容器 train_mse = [] test_mse = []# 創建可以捕捉手動輸入數據的模型訓練流程 while input("Do you want to continue the iteration? [y/n]") == "y": # 詢問是否繼續迭代epochs = int(input("Number of epochs:")) # 下一輪迭代遍歷幾次數據lr = float(input("Update learning rate:")) # 設置下一輪迭代的學習率train_l, test_l = fit_rec(net = tanh_model, criterion = nn.MSELoss(), optimizer = optim.SGD(tanh_model.parameters(), lr = lr), train_data = train_loader,test_data = test_loader,epochs = epochs, cla = False, eva = mse_cal)train_mse.extend(train_l)test_mse.extend(test_l) #Do you want to continue the iteration? [y/n] y #Number of epochs: 30 #Update learning rate: 0.03 #Do you want to continue the iteration? [y/n] y #Number of epochs: 30 #Update learning rate: 0.01 #Do you want to continue the iteration? [y/n] n查看模型運行結果
plt.plot(train_mse, label='train_mse') plt.legend(loc = 1)
同時,我們可以讓模型以0.03作為學習率迭代60個epochs,對比模型訓練效果
能夠看出,手動調整模型學習率的第一個模型在最終模型表現上比第二個模型更好,在同樣遍歷了60次的情況下,模型一能夠收斂至一個更小的MSE。
??此外,我們再令模型一直以學習率為0.01進行訓練,測試模型最終效果。
我們能夠發現,第一個模型收斂速度明顯快于第二個模型,據此也能說明手動調整模型學習率的有效性。
2.2 配合tensorboard實現方法
??當然,上述過程中,由于我們只能當模型全部訓練完畢之后才能進行繪圖,因此上述其實存在一個實踐層面的問題——如果我們無法得知當前階段模型運行情況,我們又如何能夠選擇適當策略對模型進行調整。要解決該問題,就必須借助此前介紹的tensorboard工具進行即時的數據記錄與呈現。
??在Python中,尤其是在jupyter中,代碼只能逐個cell運行,如果input對應的cell沒有執行完畢,則無法進行后續繪圖操作。但tensorboard中writer對象卻能夠即時記錄模型中間結果,并且同步呈現在tensorboard的操作面板上。借此功能,我們便能將在某個cell尚未運行結束之前看到當前階段的運行結果。要完成該過程,則需要在此前代碼基礎之上進行修改。
需要說明的是,這里將用到for循環的多變量同步循環函數zip,也就是多個臨時變量同時遍歷多個有序對象。
l1 = list(range(10)) l1 #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] l1[3:5] #[3, 4] l2 = list(range(1, 3)) l2 #[1, 2] for i, j in zip(l2, l1[3:5]):print(i, j) #1 3 #2 4具體修改后代碼如下:
# 設置隨機數種子 torch.manual_seed(24) # 實例化模型 tanh_model = net_class2(act_fun= torch.tanh, in_features=5, BN_model='pre')# 實例化writer對象 writer = SummaryWriter(log_dir='l3')# 創建空列表容器 train_mse = [] test_mse = []# 創建總遍歷次數計數器 num_epochs = 0提前打開tensorboard服務,在命令行中輸入
import os print(os.path.abspath('.')) #/Users/zhucan待服務啟動完成后,即可執行下述代碼:
while input("Do you want to continue the iteration? [y/n]") == "y":epochs = int(input("Number of epochs:"))lr = float(input("Update learning rate:"))train_l, test_l = fit_rec(net = tanh_model, criterion = nn.MSELoss(), optimizer = optim.SGD(tanh_model.parameters(), lr = lr), train_data = train_loader,test_data = test_loader,epochs = epochs, cla = False, eva = mse_cal)train_mse.extend(train_l)test_mse.extend(test_l)for i, j in zip(list(range(num_epochs, num_epochs+epochs)), train_mse[num_epochs: num_epochs+epochs]):writer.add_scalar('train_mse', j, i)for i, j in zip(list(range(num_epochs, num_epochs+epochs)), test_mse[num_epochs: num_epochs+epochs]):writer.add_scalar('test_mse', j, i)num_epochs += epochs #Do you want to continue the iteration? [y/n] y #Number of epochs: 30 #Update learning rate: 0.03 #Do you want to continue the iteration? [y/n] y #Number of epochs: 30 #Update learning rate: 0.01 #Do you want to continue the iteration? [y/n] n注,上述代碼中train_mse[num_epochs: num_epochs+epochs]就是本次循環中的train_l,代碼中可以用train_l代替。
當我們執行完第一階段的30次遍歷之后,我們即可打開瀏覽器輸入localhost:6006查看當前記錄的模型MSE情況。
然后再執行第二階段的30次遍歷數據的訓練,執行完成后停止訓練,在tensorboard面板中查看前兩輪整體訓練情況
值得注意的是,當我們在使用add_scalar進行數據記錄時,繪制折線圖的橫縱坐標都是必須的,如果缺失了橫坐標,tensorboard中圖像繪制的最終結果將呈現一條豎線。
并且,為了保證每一輪結果“橫坐標”的唯一性,我們需要按照上述方法設置迭代計數器。
??至此,我們通過借助tensorboard動態記錄的特性,完成了模型訓練過程中間結果的查看。當然,由于我們是在循環外設置的遍歷次數計數器,因此哪怕上一次循環已經停止,接下來我們仍然可以繼續在上一次停止時計數器的數值基礎上再開啟新的一輪循環(可以實現短點重連)。
我們再遍歷30次數據,并設置學習率為0.005進行模型訓練,測試代碼性能與模型效果
while input("Do you want to continue the iteration? [y/n]") == "y":epochs = int(input("Number of epochs:"))lr = float(input("Update learning rate:"))train_l, test_l = fit_rec(net = tanh_model, criterion = nn.MSELoss(), optimizer = optim.SGD(tanh_model.parameters(), lr = lr), train_data = train_loader,test_data = test_loader,epochs = epochs, cla = False, eva = mse_cal)train_mse.extend(train_l)test_mse.extend(test_l)for i, j in zip(list(range(num_epochs, num_epochs+epochs)), train_mse[num_epochs: num_epochs+epochs]):writer.add_scalar('train_mse', j, i)for i, j in zip(list(range(num_epochs, num_epochs+epochs)), test_mse[num_epochs: num_epochs+epochs]):writer.add_scalar('test_mse', j, i)num_epochs += epochs #Do you want to continue the iteration? [y/n] y #Number of epochs: 30 #Update learning rate: 0.005 #Do you want to continue the iteration? [y/n] n在tensorboard頁面中查看總共90輪epochs的訓練結果
據此也可驗證上述代碼的“中斷重連”的性能。
??至此,我們就完成了學習率手動調度實驗。在上述操作流程中,我們不僅可以即時調整每一次訓練遍歷數據集的次數、同時靈活設置該訓練過程的學習率,并且能夠借助tensorboard即時呈現模型訓練結果。值得注意的是,盡管在大多數建模場景下,我們都會選擇直接借助PyTorch中的一些更加高級的方法自動調整學習率,但上述操作過程所涉及到的手動分批訓練、模型訓練中間結果呈現等方法,除了能夠讓我們完成學習率調度實驗外,其實都是應對大規模數據集訓練時為靈活調整模型中間過程而必須掌握的基本技術手段。
對于算法工程來說,無論有多么先進的自動化處理方法,我們都需要保留一套手動驗證的方法。
3.常用學習率調度思路
??根據上述實驗,我們基本能夠總結得出,伴隨模型遍歷數據集次數增加、學習率逐漸降低,能夠使得模型整體性能有所提升。但是,上述手動實驗過程中學習率調整的方法還是相對來說比較粗糙,我們只是每隔30個epochs將學習率調小一次,很多結果也是通過嘗試最后得出。在實際生產工作中,我們需要使用理論更加嚴謹的學習率調度方法。
??在長期的實踐經驗總結基礎上,目前來看,從實現思路上劃分,比較通用的學習率調度的策略分為以下五類:
-
冪調度:
??隨著迭代次數增加,學習率呈現冪律變化,例如,lrlrlr為初始(第一輪)學習率,第二輪迭代時學習率調整為lr/2lr/2lr/2,第三輪迭代時學習率調整為lr/3lr/3lr/3等;(實踐過程具體數值有所差異) -
指數調度:
??隨著迭代次數增加,學習率呈現指數變化,例如,lrlrlr為初始(第一輪)學習率,第二輪迭代時學習率調整為lr/101lr/10^1lr/101,第三輪迭代時學習率調整為lr/102=lr/100lr/10^2 = lr/100lr/102=lr/100等;(實踐過程具體數值有所差異) -
分段恒定調度:
??即每隔幾輪迭代調整一次學習率,例如1-10輪學習率為lrlrlr,10-20輪時學習率為lr/100lr/100lr/100等,不難發現此前我們所做的實驗就是一種特殊的分段恒定調度策略; -
性能調度:
??即每隔一段時間觀察誤差變化情況,如果誤差基本不變,則降低學習率繼續迭代; -
周期調度:
??和前面幾種學習率調度策略一味將學習率遞減有所不同,周期調度允許學習率在一個周期內進行先遞增后遞減的變化;
下一節,我們嘗試在PyTorch中調用相關方法來實現學習率調度。
總結
以上是生活随笔為你收集整理的Lesson 15.1 学习率调度基本概念与手动实现方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Lesson 14.3 Batch No
- 下一篇: Lesson 15.2 学习率调度在Py