到目前為止,我們?cè)诨谔荻鹊膶W(xué)習(xí)方法中遇到了兩個(gè)極端:第 12.3 節(jié)使用完整數(shù)據(jù)集來計(jì)算梯度和更新參數(shù),一次一個(gè)傳遞。相反, 第 12.4 節(jié)一次處理一個(gè)訓(xùn)練示例以取得進(jìn)展。它們中的任何一個(gè)都有其自身的缺點(diǎn)。當(dāng)數(shù)據(jù)非常相似時(shí),梯度下降并不是特別有效。隨機(jī)梯度下降在計(jì)算上不是特別有效,因?yàn)?CPU 和 GPU 無法利用矢量化的全部功能。這表明可能存在介于兩者之間的東西,事實(shí)上,這就是我們迄今為止在討論的示例中一直使用的東西。
12.5.1。矢量化和緩存
決定使用小批量的核心是計(jì)算效率。在考慮并行化到多個(gè) GPU 和多個(gè)服務(wù)器時(shí),這一點(diǎn)最容易理解。在這種情況下,我們需要向每個(gè) GPU 發(fā)送至少一張圖像。每臺(tái)服務(wù)器 8 個(gè) GPU 和 16 個(gè)服務(wù)器,我們已經(jīng)達(dá)到了不小于 128 的小批量大小。
當(dāng)涉及到單個(gè) GPU 甚至 CPU 時(shí),事情就有點(diǎn)微妙了。這些設(shè)備有多種類型的內(nèi)存,通常有多種類型的計(jì)算單元和它們之間不同的帶寬限制。例如,CPU 有少量寄存器,然后是 L1、L2,在某些情況下甚至是 L3 緩存(在不同處理器內(nèi)核之間共享)。這些緩存的大小和延遲都在增加(同時(shí)它們的帶寬在減少)??梢哉f,處理器能夠執(zhí)行的操作比主內(nèi)存接口能夠提供的要多得多。
首先,具有 16 個(gè)內(nèi)核和 AVX-512 矢量化的 2GHz CPU 最多可以處理2?109?16?32=1012每秒字節(jié)數(shù)。GPU 的能力很容易超過這個(gè)數(shù)字的 100 倍。另一方面,中端服務(wù)器處理器的帶寬可能不會(huì)超過 100 GB/s,即不到保持處理器所需帶寬的十分之一喂。更糟糕的是,并非所有內(nèi)存訪問都是平等的:內(nèi)存接口通常為 64 位寬或更寬(例如,在 GPU 上高達(dá) 384 位),因此讀取單個(gè)字節(jié)會(huì)產(chǎn)生更寬訪問的成本。
其次,第一次訪問的開銷很大,而順序訪問相對(duì)便宜(這通常稱為突發(fā)讀?。?。還有很多事情要記住,比如當(dāng)我們有多個(gè)套接字、小芯片和其他結(jié)構(gòu)時(shí)的緩存。 有關(guān)更深入的討論,請(qǐng)參閱此 維基百科文章。
緩解這些限制的方法是使用 CPU 高速緩存的層次結(jié)構(gòu),這些高速緩存的速度實(shí)際上足以為處理器提供數(shù)據(jù)。這是深度學(xué)習(xí)中批處理背后的驅(qū)動(dòng)力。為了簡單起見,考慮矩陣-矩陣乘法,比如 A=BC. 我們有多種計(jì)算方法A. 例如,我們可以嘗試以下操作:
我們可以計(jì)算 Aij=Bi,:C:,j,即,我們可以通過點(diǎn)積的方式逐元素計(jì)算它。
我們可以計(jì)算 A:,j=BC:,j,也就是說,我們可以一次計(jì)算一列。同樣我們可以計(jì)算 A一排Ai,:一次。
我們可以簡單地計(jì)算A=BC.
我們可以打破B和C分成更小的塊矩陣并計(jì)算A一次一個(gè)塊。
如果我們遵循第一個(gè)選項(xiàng),每次我們想要計(jì)算一個(gè)元素時(shí),我們都需要將一行和一列向量復(fù)制到 CPU 中 Aij. 更糟糕的是,由于矩陣元素是順序?qū)R的,因此當(dāng)我們從內(nèi)存中讀取兩個(gè)向量之一時(shí),我們需要訪問許多不相交的位置。第二種選擇要有利得多。在其中,我們能夠保留列向量C:,j在 CPU 緩存中,同時(shí)我們繼續(xù)遍歷B. 這將內(nèi)存帶寬要求減半,訪問速度也相應(yīng)加快。當(dāng)然,選項(xiàng) 3 是最可取的。不幸的是,大多數(shù)矩陣可能無法完全放入緩存(畢竟這是我們正在討論的內(nèi)容)。然而,選項(xiàng) 4 提供了一個(gè)實(shí)用的替代方法:我們可以將矩陣的塊移動(dòng)到緩存中并在本地將它們相乘。優(yōu)化的庫會(huì)為我們解決這個(gè)問題。讓我們看看這些操作在實(shí)踐中的效率如何。
除了計(jì)算效率之外,Python 和深度學(xué)習(xí)框架本身引入的開銷也相當(dāng)可觀?;叵胍幌?,每次我們執(zhí)行命令時(shí),Python 解釋器都會(huì)向 MXNet 引擎發(fā)送命令,而 MXNet 引擎需要將其插入計(jì)算圖中并在調(diào)度期間對(duì)其進(jìn)行處理。這種開銷可能非常有害。簡而言之,強(qiáng)烈建議盡可能使用矢量化(和矩陣)。
%matplotlib inline import time import numpy as np import torch from torch import nn from d2l import torch as d2l A = torch.zeros(256, 256) B = torch.randn(256, 256) C = torch.randn(256, 256)
%matplotlib inline import time from mxnet import autograd, gluon, init, np, npx from mxnet.gluon import nn from d2l import mxnet as d2l npx.set_np() A = np.zeros((256, 256)) B = np.random.normal(0, 1, (256, 256)) C = np.random.normal(0, 1, (256, 256))
%matplotlib inline import time import numpy as np import tensorflow as tf from d2l import tensorflow as d2l A = tf.Variable(tf.zeros((256, 256))) B = tf.Variable(tf.random.normal([256, 256], 0, 1)) C = tf.Variable(tf.random.normal([256, 256], 0, 1))
由于我們將在本書的其余部分頻繁地對(duì)運(yùn)行時(shí)間進(jìn)行基準(zhǔn)測試,因此讓我們定義一個(gè)計(jì)時(shí)器。
class Timer: #@save """Record multiple running times.""" def __init__(self): self.times = [] self.start() def start(self): """Start the timer.""" self.tik = time.time() def stop(self): """Stop the timer and record the time in a list.""" self.times.append(time.time() - self.tik) return self.times[-1] def avg(self): """Return the average time.""" return sum(self.times) / len(self.times) def sum(self): """Return the sum of time.""" return sum(self.times) def cumsum(self): """Return the accumulated time.""" return np.array(self.times).cumsum().tolist() timer = Timer()
class Timer: #@save """Record multiple running times.""" def __init__(self): self.times = [] self.start() def start(self): """Start the timer.""" self.tik = time.time() def stop(self): """Stop the timer and record the time in a list.""" self.times.append(time.time() - self.tik) return self.times[-1] def avg(self): """Return the average time.""" return sum(self.times) / len(self.times) def sum(self): """Return the sum of time.""" return sum(self.times) def cumsum(self): """Return the accumulated time.""" return np.array(self.times).cumsum().tolist() timer = Timer()
class Timer: #@save """Record multiple running times.""" def __init__(self): self.times = [] self.start() def start(self): """Start the timer.""" self.tik = time.time() def stop(self): """Stop the timer and record the time in a list.""" self.times.append(time.time() - self.tik) return self.times[-1] def avg(self): """Return the average time.""" return sum(self.times) / len(self.times) def sum(self): """Return the sum of time.""" return sum(self.times) def cumsum(self): """Return the accumulated time.""" return np.array(self.times).cumsum().tolist() timer = Timer()
逐元素賦值簡單地遍歷所有行和列 B和C分別賦值給A.
# Compute A = BC one element at a time timer.start() for i in range(256): for j in range(256): A[i, j] = torch.dot(B[i, :], C[:, j]) timer.stop()
1.5775339603424072
# Compute A = BC one element at a time timer.start() for i in range(256): for j in range(256): A[i, j] = np.dot(B[i, :], C[:, j]) A.wait_to_read() timer.stop()
2547.9816353321075
# Compute A = BC one element at a time timer.start() for i in range(256): for j in range(256): A[i, j].assign(tf.tensordot(B[i, :], C[:, j], axes=1)) timer.stop()
164.52903413772583
更快的策略是按列分配。
# Compute A = BC one column at a time timer.start() for j in range(256): A[:, j] = torch.mv(B, C[:, j]) timer.stop()
1.0594699382781982
# Compute A = BC one column at a time timer.start() for j in range(256): A[:, j] = np.dot(B, C[:, j]) A.wait_to_read() timer.stop()
6.320310592651367
timer.start() for j in range(256): A[:, j].assign(tf.tensordot(B, C[:, j], axes=1)) timer.stop()
0.5073747634887695
最后,最有效的方式是在一個(gè)塊中執(zhí)行整個(gè)操作。請(qǐng)注意,將任意兩個(gè)矩陣相乘 B∈Rm×n和 C∈Rn×p大約需要 2mnp浮點(diǎn)運(yùn)算,當(dāng)標(biāo)量乘法和加法被視為單獨(dú)的運(yùn)算時(shí)(實(shí)際上是融合的)。因此,乘以兩個(gè)256×256矩陣需要0.03 億個(gè)浮點(diǎn)運(yùn)算。讓我們看看各自的操作速度是多少。
# Compute A = BC in one go timer.start() A = torch.mm(B, C) timer.stop() gigaflops = [0.03 / i for i in timer.times] print(f'performance in Gigaflops: element {gigaflops[0]:.3f}, ' f'column {gigaflops[1]:.3f}, full {gigaflops[2]:.3f}')
performance in Gigaflops: element 0.019, column 0.028, full 2.167
# Compute A = BC in one go timer.start() A = np.dot(B, C) A.wait_to_read() timer.stop() gigaflops = [0.03 / i for i in timer.times] print(f'performance in Gigaflops: element {gigaflops[0]:.3f}, ' f'column {gigaflops[1]:.3f}, full {gigaflops[2]:.3f}')
performance in Gigaflops: element 0.000, column 0.005, full 1.746
timer.start() A.assign(tf.tensordot(B, C, axes=1)) timer.stop() gigaflops = [0.03 / i for i in timer.times] print(f'performance in Gigaflops: element {gigaflops[0]:.3f}, ' f'column {gigaflops[1]:.3f}, full {gigaflops[2]:.3f}')
performance in Gigaflops: element 0.000, column 0.059, full 1.155
12.5.2。小批量
在過去,我們理所當(dāng)然地認(rèn)為我們會(huì)讀取小批量數(shù)據(jù)而不是單個(gè)觀察來更新參數(shù)。我們現(xiàn)在給出一個(gè)簡短的理由。處理單個(gè)觀測值需要我們執(zhí)行許多單個(gè)矩陣-向量(甚至向量-向量)乘法,這是非常昂貴的,并且代表底層深度學(xué)習(xí)框架會(huì)產(chǎn)生大量開銷。這既適用于在應(yīng)用于數(shù)據(jù)時(shí)評(píng)估網(wǎng)絡(luò)(通常稱為推理),也適用于計(jì)算梯度以更新參數(shù)時(shí)。也就是說,這適用于我們執(zhí)行的任何時(shí)候 w←w?ηtgt在哪里
(12.5.1)gt=?wf(xt,w)
我們可以通過一次將其應(yīng)用于一小批觀察來提高此操作的計(jì)算效率。也就是我們替換梯度gt一個(gè)人對(duì)一小批人的一次觀察
(12.5.2)gt=?w1|Bt|∑i∈Btf(xi,w)
讓我們看看這對(duì) gt: 因?yàn)閮烧選t以及小批量的所有元素Bt從訓(xùn)練集中均勻地隨機(jī)抽取,梯度的期望保持不變。另一方面,方差顯著減少。由于小批量梯度由 b=def|Bt|被平均的獨(dú)立梯度,它的標(biāo)準(zhǔn)偏差減少了一個(gè)因素b?12. 這本身就是一件好事,因?yàn)檫@意味著更新更可靠地與完整梯度對(duì)齊。
天真地說,這表明選擇一個(gè)大的 minibatch Bt將是普遍可取的。las,在某個(gè)時(shí)間點(diǎn)之后,與計(jì)算成本的線性增加相比,標(biāo)準(zhǔn)偏差的額外減少是最小的。在實(shí)踐中,我們選擇一個(gè)足夠大的小批量來提供良好的計(jì)算效率,同時(shí)仍然適合 GPU 的內(nèi)存。為了說明節(jié)省的成本,讓我們看一些代碼。我們?cè)谄渲袌?zhí)行相同的矩陣乘法,但這次分解為一次 64 列的“小批量”。
timer.start() for j in range(0, 256, 64): A[:, j:j+64] = torch.mm(B, C[:, j:j+64]) timer.stop() print(f'performance in Gigaflops: block {0.03 / timer.times[3]:.3f}')
performance in Gigaflops: block 0.655
timer.start() for j in range(0, 256, 64): A[:, j:j+64] = np.dot(B, C[:, j:j+64]) timer.stop() print(f'performance in Gigaflops: block {0.03 / timer.times[3]:.3f}')
performance in Gigaflops: block 1.983
timer.start() for j in range(0, 256, 64): A[:, j:j+64].assign(tf.tensordot(B, C[:, j:j+64], axes=1)) timer.stop() print(f'performance in Gigaflops: block {0.03 / timer.times[3]:.3f}')
performance in Gigaflops: block 2.796
正如我們所見,小批量的計(jì)算基本上與全矩陣一樣有效。需要注意的是。在 第 8.5 節(jié)中,我們使用了一種正則化,這種正則化在很大程度上取決于小批量中的方差量。當(dāng)我們?cè)黾雍笳邥r(shí),方差會(huì)減少,并且由于批量歸一化而帶來的噪聲注入的好處也會(huì)隨之減少。有關(guān)如何重新縮放和計(jì)算適當(dāng)項(xiàng)的詳細(xì)信息,請(qǐng)參見例如 Ioffe ( 2017 ) 。
12.5.3。讀取數(shù)據(jù)集
讓我們看看如何從數(shù)據(jù)中有效地生成小批量。下面我們使用 NASA 開發(fā)的數(shù)據(jù)集測試不同飛機(jī)的機(jī)翼噪聲 來比較這些優(yōu)化算法。為了方便我們只使用第一個(gè)1,500例子。數(shù)據(jù)被白化以進(jìn)行預(yù)處理,即我們?nèi)コ挡⒎讲钪匦抡{(diào)整為 1每個(gè)坐標(biāo)。
#@save d2l.DATA_HUB['airfoil'] = (d2l.DATA_URL + 'airfoil_self_noise.dat', '76e5be1548fd8222e5074cf0faae75edff8cf93f') #@save def get_data_ch11(batch_size=10, n=1500): data = np.genfromtxt(d2l.download('airfoil'), dtype=np.float32, delimiter='t') data = torch.from_numpy((data - data.mean(axis=0)) / data.std(axis=0)) data_iter = d2l.load_array((data[:n, :-1], data[:n, -1]), batch_size, is_train=True) return data_iter, data.shape[1]-1
#@save d2l.DATA_HUB['airfoil'] = (d2l.DATA_URL + 'airfoil_self_noise.dat', '76e5be1548fd8222e5074cf0faae75edff8cf93f') #@save def get_data_ch11(batch_size=10, n=1500): data = np.genfromtxt(d2l.download('airfoil'), dtype=np.float32, delimiter='t') data = (data - data.mean(axis=0)) / data.std(axis=0) data_iter = d2l.load_array( (data[:n, :-1], data[:n, -1]), batch_size, is_train=True) return data_iter, data.shape[1]-1
#@save d2l.DATA_HUB['airfoil'] = (d2l.DATA_URL + 'airfoil_self_noise.dat', '76e5be1548fd8222e5074cf0faae75edff8cf93f') #@save def get_data_ch11(batch_size=10, n=1500): data = np.genfromtxt(d2l.download('airfoil'), dtype=np.float32, delimiter='t') data = (data - data.mean(axis=0)) / data.std(axis=0) data_iter = d2l.load_array((data[:n, :-1], data[:n, -1]), batch_size, is_train=True) return data_iter, data.shape[1]-1
12.5.4。從零開始實(shí)施
回憶一下3.4 節(jié)中的小批量隨機(jī)梯度下降實(shí)現(xiàn) 。在下文中,我們提供了一個(gè)稍微更通用的實(shí)現(xiàn)。為方便起見,它與本章稍后介紹的其他優(yōu)化算法具有相同的調(diào)用簽名。具體來說,我們添加狀態(tài)輸入states并將超參數(shù)放入字典中hyperparams。另外,我們會(huì)在訓(xùn)練函數(shù)中平均每個(gè)minibatch樣本的損失,因此優(yōu)化算法中的梯度不需要除以batch size。
def sgd(params, states, hyperparams): for p in params: p.data.sub_(hyperparams['lr'] * p.grad) p.grad.data.zero_()
def sgd(params, states, hyperparams): for p in params: p[:] -= hyperparams['lr'] * p.grad
def sgd(params, grads, states, hyperparams): for param, grad in zip(params, grads): param.assign_sub(hyperparams['lr']*grad)
接下來,我們實(shí)現(xiàn)一個(gè)通用的訓(xùn)練函數(shù),以方便使用本章后面介紹的其他優(yōu)化算法。它初始化了一個(gè)線性回歸模型,可以用來用小批量隨機(jī)梯度下降和隨后介紹的其他算法來訓(xùn)練模型。
#@save def train_ch11(trainer_fn, states, hyperparams, data_iter, feature_dim, num_epochs=2): # Initialization w = torch.normal(mean=0.0, std=0.01, size=(feature_dim, 1), requires_grad=True) b = torch.zeros((1), requires_grad=True) net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss # Train animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[0, num_epochs], ylim=[0.22, 0.35]) n, timer = 0, d2l.Timer() for _ in range(num_epochs): for X, y in data_iter: l = loss(net(X), y).mean() l.backward() trainer_fn([w, b], states, hyperparams) n += X.shape[0] if n % 200 == 0: timer.stop() animator.add(n/X.shape[0]/len(data_iter), (d2l.evaluate_loss(net, data_iter, loss),)) timer.start() print(f'loss: {animator.Y[0][-1]:.3f}, {timer.sum()/num_epochs:.3f} sec/epoch') return timer.cumsum(), animator.Y[0]
#@save def train_ch11(trainer_fn, states, hyperparams, data_iter, feature_dim, num_epochs=2): # Initialization w = np.random.normal(scale=0.01, size=(feature_dim, 1)) b = np.zeros(1) w.attach_grad() b.attach_grad() net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss # Train animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[0, num_epochs], ylim=[0.22, 0.35]) n, timer = 0, d2l.Timer() for _ in range(num_epochs): for X, y in data_iter: with autograd.record(): l = loss(net(X), y).mean() l.backward() trainer_fn([w, b], states, hyperparams) n += X.shape[0] if n % 200 == 0: timer.stop() animator.add(n/X.shape[0]/len(data_iter), (d2l.evaluate_loss(net, data_iter, loss),)) timer.start() print(f'loss: {animator.Y[0][-1]:.3f}, {timer.sum()/num_epochs:.3f} sec/epoch') return timer.cumsum(), animator.Y[0]
#@save def train_ch11(trainer_fn, states, hyperparams, data_iter, feature_dim, num_epochs=2): # Initialization w = tf.Variable(tf.random.normal(shape=(feature_dim, 1), mean=0, stddev=0.01),trainable=True) b = tf.Variable(tf.zeros(1), trainable=True) # Train net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[0, num_epochs], ylim=[0.22, 0.35]) n, timer = 0, d2l.Timer() for _ in range(num_epochs): for X, y in data_iter: with tf.GradientTape() as g: l = tf.math.reduce_mean(loss(net(X), y)) dw, db = g.gradient(l, [w, b]) trainer_fn([w, b], [dw, db], states, hyperparams) n += X.shape[0] if n % 200 == 0: timer.stop() p = n/X.shape[0] q = p/tf.data.experimental.cardinality(data_iter).numpy() r = (d2l.evaluate_loss(net, data_iter, loss),) animator.add(q, r) timer.start() print(f'loss: {animator.Y[0][-1]:.3f}, {timer.sum()/num_epochs:.3f} sec/epoch') return timer.cumsum(), animator.Y[0]
讓我們看看優(yōu)化是如何進(jìn)行批量梯度下降的。這可以通過將小批量大小設(shè)置為 1500(即示例總數(shù))來實(shí)現(xiàn)。因此,模型參數(shù)每個(gè)時(shí)期僅更新一次。進(jìn)展甚微。事實(shí)上,在 6 個(gè)步驟之后,進(jìn)度停滯了。
def train_sgd(lr, batch_size, num_epochs=2): data_iter, feature_dim = get_data_ch11(batch_size) return train_ch11( sgd, None, {'lr': lr}, data_iter, feature_dim, num_epochs) gd_res = train_sgd(1, 1500, 10)
loss: 0.249, 0.036 sec/epoch
def train_sgd(lr, batch_size, num_epochs=2): data_iter, feature_dim = get_data_ch11(batch_size) return train_ch11( sgd, None, {'lr': lr}, data_iter, feature_dim, num_epochs) gd_res = train_sgd(1, 1500, 10)
loss: 0.254, 5.565 sec/epoch
def train_sgd(lr, batch_size, num_epochs=2): data_iter, feature_dim = get_data_ch11(batch_size) return train_ch11( sgd, None, {'lr': lr}, data_iter, feature_dim, num_epochs) gd_res = train_sgd(1, 1500, 10)
loss: 0.244, 0.027 sec/epoch
當(dāng)批量大小等于 1 時(shí),我們使用隨機(jī)梯度下降進(jìn)行優(yōu)化。為了簡化實(shí)施,我們選擇了一個(gè)恒定(盡管很?。┑膶W(xué)習(xí)率。在隨機(jī)梯度下降中,每當(dāng)處理一個(gè)示例時(shí),模型參數(shù)都會(huì)更新。在我們的例子中,這相當(dāng)于每個(gè)時(shí)期 1500 次更新。正如我們所看到的,目標(biāo)函數(shù)值的下降在一個(gè) epoch 之后變慢了。盡管這兩個(gè)過程在一個(gè)時(shí)期內(nèi)處理了 1500 個(gè)示例,但在我們的實(shí)驗(yàn)中,隨機(jī)梯度下降比梯度下降消耗更多的時(shí)間。這是因?yàn)殡S機(jī)梯度下降更頻繁地更新參數(shù),并且一次處理單個(gè)觀測值的效率較低。
sgd_res = train_sgd(0.005, 1)
loss: 0.242, 0.767 sec/epoch
sgd_res = train_sgd(0.005, 1)
loss: 0.243, 65.299 sec/epoch
sgd_res = train_sgd(0.005, 1)
loss: 0.244, 6.689 sec/epoch
最后,當(dāng) batch size 等于 100 時(shí),我們使用 minibatch 隨機(jī)梯度下降進(jìn)行優(yōu)化。每個(gè)時(shí)期所需的時(shí)間比隨機(jī)梯度下降所需的時(shí)間和批量梯度下降所需的時(shí)間短。
mini1_res = train_sgd(.4, 100)
loss: 0.242, 0.028 sec/epoch
mini1_res = train_sgd(.4, 100)
loss: 0.251, 20.161 sec/epoch
mini1_res = train_sgd(.4, 100)
loss: 0.246, 0.085 sec/epoch
將批處理大小減少到 10,每個(gè)時(shí)期的時(shí)間都會(huì)增加,因?yàn)槊總€(gè)批處理的工作負(fù)載執(zhí)行效率較低。
mini2_res = train_sgd(.05, 10)
loss: 0.247, 0.107 sec/epoch
mini2_res = train_sgd(.05, 10)
loss: 0.243, 20.888 sec/epoch
mini2_res = train_sgd(.05, 10)
loss: 0.243, 0.698 sec/epoch
現(xiàn)在我們可以比較前四個(gè)實(shí)驗(yàn)的時(shí)間與損失??梢钥闯觯M管隨機(jī)梯度下降在處理的示例數(shù)量方面比 GD 收斂得更快,但它比 GD 使用更多的時(shí)間來達(dá)到相同的損失,因?yàn)橹饌€(gè)示例計(jì)算梯度示例效率不高。Minibatch 隨機(jī)梯度下降能夠權(quán)衡收斂速度和計(jì)算效率。小批量大小為 10 比隨機(jī)梯度下降更有效;就運(yùn)行時(shí)間而言,100 的小批量甚至優(yōu)于 GD。
d2l.set_figsize([6, 3]) d2l.plot(*list(map(list, zip(gd_res, sgd_res, mini1_res, mini2_res))), 'time (sec)', 'loss', xlim=[1e-2, 10], legend=['gd', 'sgd', 'batch size=100', 'batch size=10']) d2l.plt.gca().set_xscale('log')
d2l.set_figsize([6, 3]) d2l.plot(*list(map(list, zip(gd_res, sgd_res, mini1_res, mini2_res))), 'time (sec)', 'loss', xlim=[1e-2, 10], legend=['gd', 'sgd', 'batch size=100', 'batch size=10']) d2l.plt.gca().set_xscale('log')
d2l.set_figsize([6, 3]) d2l.plot(*list(map(list, zip(gd_res, sgd_res, mini1_res, mini2_res))), 'time (sec)', 'loss', xlim=[1e-2, 10], legend=['gd', 'sgd', 'batch size=100', 'batch size=10']) d2l.plt.gca().set_xscale('log')
12.5.5。簡潔的實(shí)現(xiàn)
在 Gluon 中,我們可以使用Trainer類來調(diào)用優(yōu)化算法。這用于實(shí)現(xiàn)通用訓(xùn)練功能。我們將在當(dāng)前章節(jié)中使用它。
#@save def train_concise_ch11(trainer_fn, hyperparams, data_iter, num_epochs=4): # Initialization net = nn.Sequential(nn.Linear(5, 1)) def init_weights(module): if type(module) == nn.Linear: torch.nn.init.normal_(module.weight, std=0.01) net.apply(init_weights) optimizer = trainer_fn(net.parameters(), **hyperparams) loss = nn.MSELoss(reduction='none') animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[0, num_epochs], ylim=[0.22, 0.35]) n, timer = 0, d2l.Timer() for _ in range(num_epochs): for X, y in data_iter: optimizer.zero_grad() out = net(X) y = y.reshape(out.shape) l = loss(out, y) l.mean().backward() optimizer.step() n += X.shape[0] if n % 200 == 0: timer.stop() # `MSELoss` computes squared error without the 1/2 factor animator.add(n/X.shape[0]/len(data_iter), (d2l.evaluate_loss(net, data_iter, loss) / 2,)) timer.start() print(f'loss: {animator.Y[0][-1]:.3f}, {timer.sum()/num_epochs:.3f} sec/epoch')
#@save def train_concise_ch11(tr_name, hyperparams, data_iter, num_epochs=2): # Initialization net = nn.Sequential() net.add(nn.Dense(1)) net.initialize(init.Normal(sigma=0.01)) trainer = gluon.Trainer(net.collect_params(), tr_name, hyperparams) loss = gluon.loss.L2Loss() animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[0, num_epochs], ylim=[0.22, 0.35]) n, timer = 0, d2l.Timer() for _ in range(num_epochs): for X, y in data_iter: with autograd.record(): l = loss(net(X), y) l.backward() trainer.step(X.shape[0]) n += X.shape[0] if n % 200 == 0: timer.stop() animator.add(n/X.shape[0]/len(data_iter), (d2l.evaluate_loss(net, data_iter, loss),)) timer.start() print(f'loss: {animator.Y[0][-1]:.3f}, {timer.sum()/num_epochs:.3f} sec/epoch')
#@save def train_concise_ch11(trainer_fn, hyperparams, data_iter, num_epochs=2): # Initialization net = tf.keras.Sequential() net.add(tf.keras.layers.Dense(1, kernel_initializer=tf.random_normal_initializer(stddev=0.01))) optimizer = trainer_fn(**hyperparams) loss = tf.keras.losses.MeanSquaredError() animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[0, num_epochs], ylim=[0.22, 0.35]) n, timer = 0, d2l.Timer() for _ in range(num_epochs): for X, y in data_iter: with tf.GradientTape() as g: out = net(X) l = loss(y, out) params = net.trainable_variables grads = g.gradient(l, params) optimizer.apply_gradients(zip(grads, params)) n += X.shape[0] if n % 200 == 0: timer.stop() p = n/X.shape[0] q = p/tf.data.experimental.cardinality(data_iter).numpy() # `MeanSquaredError` computes squared error without the 1/2 # factor r = (d2l.evaluate_loss(net, data_iter, loss) / 2,) animator.add(q, r) timer.start() print(f'loss: {animator.Y[0][-1]:.3f}, {timer.sum()/num_epochs:.3f} sec/epoch')
使用 Gluon 重復(fù)上一個(gè)實(shí)驗(yàn)顯示相同的行為。
data_iter, _ = get_data_ch11(10) trainer = torch.optim.SGD train_concise_ch11(trainer, {'lr': 0.01}, data_iter)
loss: 0.242, 0.111 sec/epoch
data_iter, _ = get_data_ch11(10) train_concise_ch11('sgd', {'learning_rate': 0.05}, data_iter)
loss: 0.244, 23.568 sec/epoch
data_iter, _ = get_data_ch11(10) trainer = tf.keras.optimizers.SGD train_concise_ch11(trainer, {'learning_rate': 0.05}, data_iter)
loss: 0.253, 1.290 sec/epoch
12.5.6。概括
由于減少了深度學(xué)習(xí)框架產(chǎn)生的開銷以及更好的內(nèi)存局部性和 CPU 和 GPU 上的緩存,矢量化使代碼更高效。
隨機(jī)梯度下降產(chǎn)生的統(tǒng)計(jì)效率與一次處理大批量數(shù)據(jù)產(chǎn)生的計(jì)算效率之間存在權(quán)衡。
小批量隨機(jī)梯度下降提供了兩全其美的方法:計(jì)算和統(tǒng)計(jì)效率。
在小批量隨機(jī)梯度下降中,我們處理通過訓(xùn)練數(shù)據(jù)的隨機(jī)排列獲得的批量數(shù)據(jù)(即,每個(gè)觀察每個(gè)時(shí)期只處理一次,盡管是隨機(jī)順序)。
建議在訓(xùn)練期間降低學(xué)習(xí)率。
一般來說,當(dāng)以時(shí)鐘時(shí)間衡量時(shí),minibatch 隨機(jī)梯度下降比隨機(jī)梯度下降和梯度下降更快收斂到更小的風(fēng)險(xiǎn)。
12.5.7。練習(xí)
修改 batch size 和 learning rate,觀察目標(biāo)函數(shù)值的下降率和每個(gè) epoch 消耗的時(shí)間。
閱讀 MXNet 文檔并使用Trainer類 set_learning_rate函數(shù)將小批量隨機(jī)梯度下降的學(xué)習(xí)率在每個(gè)時(shí)期后降低到其先前值的 1/10。
將小批量隨機(jī)梯度下降與實(shí)際從訓(xùn)練集中進(jìn)行替換采樣的變體進(jìn)行比較。會(huì)發(fā)生什么?
一個(gè)邪惡的精靈在不告訴你的情況下復(fù)制你的數(shù)據(jù)集(即,每次觀察發(fā)生兩次,你的數(shù)據(jù)集增長到原來大小的兩倍,但沒有人告訴你)。隨機(jī)梯度下降、小批量隨機(jī)梯度下降和梯度下降的行為如何變化?
-
隨機(jī)梯度下降
+關(guān)注
關(guān)注
0文章
4瀏覽量
1010 -
pytorch
+關(guān)注
關(guān)注
2文章
809瀏覽量
13959
發(fā)布評(píng)論請(qǐng)先 登錄
小批量電路板制作***
PCB樣品及小批量收費(fèi)標(biāo)準(zhǔn)
分享-----快速小批量SMT樣品貼片
機(jī)器學(xué)習(xí):隨機(jī)梯度下降和批量梯度下降算法介紹

梯度下降算法及其變種:批量梯度下降,小批量梯度下降和隨機(jī)梯度下降
PyTorch教程12.4之隨機(jī)梯度下降

PyTorch教程12.5之小批量隨機(jī)梯度下降

PyTorch教程-12.4。隨機(jī)梯度下降
從設(shè)計(jì)到生產(chǎn),PCB小批量生產(chǎn)解密
MES系統(tǒng)如何支持多品種小批量生產(chǎn)

評(píng)論