即使在中等規(guī)模的數(shù)據(jù)集上,訓(xùn)練神經(jīng)網(wǎng)絡(luò)也可能很昂貴。根據(jù)配置空間(第 19.1.1.2 節(jié)),超參數(shù)優(yōu)化需要數(shù)十到數(shù)百次函數(shù)評(píng)估才能找到性能良好的超參數(shù)配置。正如我們?cè)?9.3 節(jié)中看到的 ,我們可以通過(guò)利用并行資源顯著加快 HPO 的整體時(shí)鐘時(shí)間,但這并不會(huì)減少所需的總計(jì)算量。
在本節(jié)中,我們將展示如何加速超參數(shù)配置的評(píng)估。隨機(jī)搜索等方法為每個(gè)超參數(shù)評(píng)估分配相同數(shù)量的資源(例如,epoch 數(shù)、訓(xùn)練數(shù)據(jù)點(diǎn))。圖 19.4.1 描繪了一組使用不同超參數(shù)配置訓(xùn)練的神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)曲線。經(jīng)過(guò)幾個(gè) epoch 之后,我們已經(jīng)能夠在視覺(jué)上區(qū)分性能良好和次優(yōu)的配置。然而,學(xué)習(xí)曲線是嘈雜的,我們可能仍然需要全部 100 個(gè) epoch 來(lái)確定表現(xiàn)最好的一個(gè)。
圖 19.4.1隨機(jī)超參數(shù)配置的學(xué)習(xí)曲線
多保真超參數(shù)優(yōu)化將更多資源分配給有前途的配置,并盡早停止對(duì)性能不佳的配置的評(píng)估。這加快了優(yōu)化過(guò)程,因?yàn)槲覀兛梢詾橄嗤馁Y源總量嘗試更多的配置。
更正式地說(shuō),我們擴(kuò)展了第 19.1.1 節(jié)中的定義 ,這樣我們的目標(biāo)函數(shù) f(x,r)獲得額外的輸入 r∈[rmin,rmax], 指定我們?cè)敢鉃榕渲迷u(píng)估花費(fèi)的資源量x. 我們假設(shè)錯(cuò)誤 f(x,r)隨著r,而計(jì)算成本c(x,r)增加。通常, r表示訓(xùn)練神經(jīng)網(wǎng)絡(luò)的時(shí)期數(shù),但它也可以是訓(xùn)練子集大小或交叉驗(yàn)證折疊數(shù)。
from collections import defaultdict import numpy as np from scipy import stats from d2l import torch as d2l d2l.set_figsize()
19.4.1。連續(xù)減半
使隨機(jī)搜索適應(yīng)多保真度設(shè)置的最簡(jiǎn)單方法之一是連續(xù)減半 (Jamieson 和 Talwalkar,2016 年,Karnin等人,2013 年)。基本思想是從N配置,例如從配置空間隨機(jī)采樣,并訓(xùn)練它們中的每一個(gè) rmin只有時(shí)代。然后,我們丟棄一部分表現(xiàn)最差的試驗(yàn),并對(duì)其余試驗(yàn)進(jìn)行更長(zhǎng)時(shí)間的訓(xùn)練。重復(fù)這個(gè)過(guò)程,更少的試驗(yàn)運(yùn)行更長(zhǎng)時(shí)間,直到至少有一個(gè)試驗(yàn)達(dá)到rmax時(shí)代。
更正式地說(shuō),考慮最低預(yù)算rmin(例如 1 個(gè) epoch),最大預(yù)算rmax,例如 max_epochs在我們之前的例子中,還有一個(gè)減半常數(shù) η∈{2,3,…}. 為簡(jiǎn)單起見(jiàn),假設(shè) rmax=rminηK, 和K∈I. 那么初始配置的數(shù)量是N=ηK. 讓我們定義一組梯級(jí) R={rmin,rminη,rminη2,…,rmax}.
一輪連續(xù)減半的過(guò)程如下。我們從跑步開(kāi)始N試驗(yàn)到第一梯級(jí)rmin. 對(duì)驗(yàn)證錯(cuò)誤進(jìn)行排序,我們保持頂部1/η分?jǐn)?shù)(相當(dāng)于ηK?1配置)并丟棄所有其余的。幸存的試驗(yàn)被訓(xùn)練用于下一個(gè)梯級(jí)(rminηepochs),然后重復(fù)該過(guò)程。在每個(gè)梯級(jí),一個(gè)1/η部分試驗(yàn)存活下來(lái),他們的訓(xùn)練繼續(xù)進(jìn)行η倍大的預(yù)算。有了這個(gè)特別的選擇N, 只有一個(gè)試驗(yàn)將被訓(xùn)練到全部預(yù)算rmax. 一旦這樣一輪連續(xù)的減半完成,我們就會(huì)用一組新的初始配置開(kāi)始下一輪,迭代直到總預(yù)算用完。
圖 19.4.2隨機(jī)超參數(shù)配置的學(xué)習(xí)曲線。
我們將第 19.2 節(jié)HPOScheduler的基類子類化 ,以實(shí)現(xiàn)連續(xù)減半,允許通用 對(duì)象對(duì)配置進(jìn)行采樣(在我們下面的示例中,它將是 a )。此外,用戶必須通過(guò)最少的資源HPOSearcherRandomSearcherrmin, 最大資源 rmax和η作為輸入。在我們的調(diào)度程序中,我們維護(hù)一個(gè)配置隊(duì)列,這些配置仍需要針對(duì)當(dāng)前梯級(jí)進(jìn)行評(píng)估ri. 每次我們跳到下一個(gè)梯級(jí)時(shí),我們都會(huì)更新隊(duì)列。
class SuccessiveHalvingScheduler(d2l.HPOScheduler): #@save def __init__(self, searcher, eta, r_min, r_max, prefact=1): self.save_hyperparameters() # Compute K, which is later used to determine the number of configurations self.K = int(np.log(r_max / r_min) / np.log(eta)) # Define the rungs self.rung_levels = [r_min * eta ** k for k in range(self.K + 1)] if r_max not in self.rung_levels: # The final rung should be r_max self.rung_levels.append(r_max) self.K += 1 # Bookkeeping self.observed_error_at_rungs = defaultdict(list) self.all_observed_error_at_rungs = defaultdict(list) # Our processing queue self.queue = []
一開(kāi)始我們的隊(duì)列是空的,我們用 n=prefact?ηK配置,首先在最小的梯級(jí)上進(jìn)行評(píng)估rmin. 這里, prefact允許我們?cè)诓煌纳舷挛闹兄赜梦覀兊拇a。出于本節(jié)的目的,我們固定 prefact=1. 每次資源可用并且HPOTuner對(duì)象查詢suggest函數(shù)時(shí),我們都會(huì)從隊(duì)列中返回一個(gè)元素。一旦我們完成一輪連續(xù)減半,這意味著我們?cè)u(píng)估了最高資源級(jí)別上的所有幸存配置rmax并且我們的隊(duì)列是空的,我們用一組新的、隨機(jī)采樣的配置重新開(kāi)始整個(gè)過(guò)程??。
@d2l.add_to_class(SuccessiveHalvingScheduler) #@save def suggest(self): if len(self.queue) == 0: # Start a new round of successive halving # Number of configurations for the first rung: n0 = int(self.prefact * self.eta ** self.K) for _ in range(n0): config = self.searcher.sample_configuration() config["max_epochs"] = self.r_min # Set r = r_min self.queue.append(config) # Return an element from the queue return self.queue.pop()
當(dāng)我們收集到一個(gè)新的數(shù)據(jù)點(diǎn)時(shí),我們首先更新搜索器模塊。之后我們檢查我們是否已經(jīng)收集了當(dāng)前梯級(jí)上的所有數(shù)據(jù)點(diǎn)。如果是這樣,我們對(duì)所有配置進(jìn)行排序并推頂 1η配置到隊(duì)列中。
@d2l.add_to_class(SuccessiveHalvingScheduler) #@save def update(self, config: dict, error: float, info=None): ri = int(config["max_epochs"]) # Rung r_i # Update our searcher, e.g if we use Bayesian optimization later self.searcher.update(config, error, additional_info=info) self.all_observed_error_at_rungs[ri].append((config, error)) if ri < self.r_max: # Bookkeeping self.observed_error_at_rungs[ri].append((config, error)) # Determine how many configurations should be evaluated on this rung ki = self.K - self.rung_levels.index(ri) ni = int(self.prefact * self.eta ** ki) # If we observed all configuration on this rung r_i, we estimate the # top 1 / eta configuration, add them to queue and promote them for # the next rung r_{i+1} if len(self.observed_error_at_rungs[ri]) >= ni: kiplus1 = ki - 1 niplus1 = int(self.prefact * self.eta ** kiplus1) best_performing_configurations = self.get_top_n_configurations( rung_level=ri, n=niplus1 ) riplus1 = self.rung_levels[self.K - kiplus1] # r_{i+1} # Queue may not be empty: insert new entries at the beginning self.queue = [ dict(config, max_epochs=riplus1) for config in best_performing_configurations ] + self.queue self.observed_error_at_rungs[ri] = [] # Reset
配置根據(jù)其在當(dāng)前梯級(jí)上觀察到的性能進(jìn)行排序。
@d2l.add_to_class(SuccessiveHalvingScheduler) #@save def get_top_n_configurations(self, rung_level, n): rung = self.observed_error_at_rungs[rung_level] if not rung: return [] sorted_rung = sorted(rung, key=lambda x: x[1]) return [x[0] for x in sorted_rung[:n]]
讓我們看看在我們的神經(jīng)網(wǎng)絡(luò)示例中連續(xù)減半是如何進(jìn)行的。我們將使用rmin=2,η=2, rmax=10, 所以梯級(jí)是2,4,8,10.
min_number_of_epochs = 2 max_number_of_epochs = 10 eta = 2 num_gpus=1 config_space = { "learning_rate": stats.loguniform(1e-2, 1), "batch_size": stats.randint(32, 256), } initial_config = { "learning_rate": 0.1, "batch_size": 128, }
我們只是用新的 SuccessiveHalvingScheduler.
searcher = d2l.RandomSearcher(config_space, initial_config=initial_config) scheduler = SuccessiveHalvingScheduler( searcher=searcher, eta=eta, r_min=min_number_of_epochs, r_max=max_number_of_epochs, ) tuner = d2l.HPOTuner( scheduler=scheduler, objective=d2l.hpo_objective_lenet, ) tuner.run(number_of_trials=30)
error = 0.1623382568359375, runtime = 84.38501596450806
我們可以可視化我們?cè)u(píng)估的所有配置的學(xué)習(xí)曲線。大多數(shù)配置會(huì)提前停止,只有性能較好的配置會(huì)存活到rmax. 將此與香草隨機(jī)搜索進(jìn)行比較,后者將分配rmax到每個(gè)配置。
for rung_index, rung in scheduler.all_observed_error_at_rungs.items(): errors = [xi[1] for xi in rung] d2l.plt.scatter([rung_index] * len(errors), errors) d2l.plt.xlim(min_number_of_epochs - 0.5, max_number_of_epochs + 0.5) d2l.plt.xticks( np.arange(min_number_of_epochs, max_number_of_epochs + 1), np.arange(min_number_of_epochs, max_number_of_epochs + 1) ) d2l.plt.ylabel("validation error") d2l.plt.xlabel("epochs")
Text(0.5, 0, 'epochs')
最后,請(qǐng)注意我們?cè)?SuccessiveHalvingScheduler. 假設(shè)一名工人可以自由運(yùn)行一項(xiàng)工作,并suggest在當(dāng)前梯級(jí)幾乎完全填滿時(shí)被調(diào)用,但另一名工人仍在忙于評(píng)估。由于我們?nèi)鄙賮?lái)自該工作人員的指標(biāo)值,因此我們無(wú)法確定排名靠前的1/η分?jǐn)?shù)打開(kāi)下一個(gè)梯級(jí)。另一方面,我們想給我們的自由工人分配一份工作,這樣它就不會(huì)閑著。我們的解決方案是開(kāi)始新一輪的連續(xù)減半,并??將我們的工人分配到那里的第一次試驗(yàn)。然而,一旦梯級(jí)在 中完成update,我們確保在隊(duì)列的開(kāi)頭插入新配置,因此它們優(yōu)先于下一輪的配置。
19.4.2。概括
在本節(jié)中,我們介紹了多保真超參數(shù)優(yōu)化的概念,我們假設(shè)可以訪問(wèn)目標(biāo)函數(shù)的廉價(jià)評(píng)估近似值,例如在一定數(shù)量的訓(xùn)練時(shí)期后的驗(yàn)證錯(cuò)誤作為驗(yàn)證錯(cuò)誤的代理在全部的紀(jì)元之后。多保真超參數(shù)優(yōu)化允許減少 HPO 的整體計(jì)算,而不僅僅是減少掛鐘時(shí)間。
我們實(shí)施并評(píng)估了連續(xù)減半,這是一種簡(jiǎn)單而高效的多保真 HPO 算法。
Discussions
-
保真
+關(guān)注
關(guān)注
0文章
4瀏覽量
13641 -
pytorch
+關(guān)注
關(guān)注
2文章
809瀏覽量
13814
發(fā)布評(píng)論請(qǐng)先 登錄
Pytorch模型訓(xùn)練實(shí)用PDF教程【中文】
pytorch模型轉(zhuǎn)換需要注意的事項(xiàng)有哪些?
基于PyTorch的深度學(xué)習(xí)入門教程之DataParallel使用多GPU
PyTorch教程6.2之參數(shù)管理

PyTorch教程12.1之優(yōu)化和深度學(xué)習(xí)

PyTorch教程13.7之參數(shù)服務(wù)器

PyTorch教程14.7之單發(fā)多框檢測(cè)

PyTorch教程19.1之什么是超參數(shù)優(yōu)化

PyTorch教程19.2之超參數(shù)優(yōu)化API

PyTorch教程19.4之多保真超參數(shù)優(yōu)化

如何使用PyTorch建立網(wǎng)絡(luò)模型
利用Arm Kleidi技術(shù)實(shí)現(xiàn)PyTorch優(yōu)化

評(píng)論