Skip to content

Commit 670406b

Browse files
committed
add 4.2.1 md
1 parent 5185956 commit 670406b

File tree

2 files changed

+155
-0
lines changed

2 files changed

+155
-0
lines changed

docs/chapter4/4.2.md

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,158 @@ tensor([ 5.3671, -17.3012], requires_grad=True)
110110
```
111111

112112
你将获得与之前相同的结果。不错!尽管你可以手动计算导数,但这并不再必须。
113+
114+
## 4.2.1 优化器
115+
116+
此代码使用最基础的梯度下降进行优化,这在简单情况下效果很好。你可能已经猜到,存在几种优化策略和技巧可以帮助收敛,特别是当模型变得复杂时。
117+
118+
现在是时候介绍PyTorch从用户代码(例如训练循环)中抽象出来的优化策略了,以使你免于繁琐地更新模型中的每个参数。`torch`模块有一个`optim`子模块,你可以在其中找到实现不同优化算法的类。这里有一个简短的清单:
119+
120+
``` python
121+
import torch.optim as optim
122+
dir(optim)
123+
```
124+
输出:
125+
```
126+
['ASGD',
127+
'Adadelta',
128+
'Adagrad',
129+
'Adam',
130+
'AdamW',
131+
'Adamax',
132+
'LBFGS',
133+
'Optimizer',
134+
'RMSprop',
135+
'Rprop',
136+
'SGD',
137+
'SparseAdam',
138+
...
139+
]
140+
```
141+
142+
每个优化器构造函数都将参数(通常是将`require_grad`设置为True的PyTorch张量)作为第一个输入。传递给优化器的所有参数都保留在优化器对象内,以便优化器可以更新其值并访问其`grad`属性,如图4.10所示。
143+
144+
<div align=center>
145+
<img width="600" src="../img/chapter4/4.10.png" alt="4.10"/>
146+
</div>
147+
<div align=center>图4.10 (A)优化器对参数的引用的概念表示,然后(B)根据输入计算损失,(C)对backward的调用会将grad填充到参数内。此时,(D)优化器可以访问grad并计算参数更新。</div>
148+
149+
每个优化器都有两个方法:`zero_grad``step`。前者将构造时传递给优化器的所有参数的`grad`属性归零;后者根据特定优化器实施的优化策略更新这些参数的值。
150+
现在创建参数并实例化一个梯度下降优化器:
151+
152+
``` python
153+
params = torch.tensor([1.0, 0.0], requires_grad=True)
154+
learning_rate = 1e-5
155+
optimizer = optim.SGD([params], lr=learning_rate)
156+
```
157+
158+
这里,SGD代表随机梯度下降(Stochastic Gradient Descent)。这里的优化器采用原始(vanilla)的梯度下降(只要动量`momentum`设置为默认值0.0)。术语“随机”(stochastic)来自以下事实:通常是通过平均输入样本的随机子集(称为minibatch)产生的梯度来获得最终梯度。然而,优化器本身并不知道是对所有样本(vanilla)还是对其随机子集(stochastic)进行了损失评估,因此两种情况下的算法相同。
159+
160+
无论如何,下面来尝试使用新的优化器:
161+
162+
``` python
163+
t_p = model(t_u, *params)
164+
loss = loss_fn(t_p, t_c)
165+
loss.backward()
166+
optimizer.step()
167+
params
168+
```
169+
输出:
170+
```
171+
tensor([ 9.5483e-01, -8.2600e-04], requires_grad=True)
172+
```
173+
174+
调用`step``params`的值就会更新,无需亲自更新它!调用`step`发生的事情是:优化器通过将`params`减去`learning_rate``grad`的乘积来更新的`params`,这与之前手动编写的更新过程完全相同。
175+
176+
准备好将此代码放在训练循环中了吗?不!需要注意一个大陷阱:不要忘了将梯度清零。如果你在循环中调用了前面的代码,则在每次调用`backward`时,梯度都会在叶节点中累积且会传播得到处都是!
177+
178+
以下就是准备循环的代码,需要在正确的位置(在调用`backward`之前)插入额外的`zero_grad`
179+
180+
``` python
181+
params = torch.tensor([1.0, 0.0], requires_grad=True)
182+
learning_rate = 1e-2
183+
optimizer = optim.SGD([params], lr=learning_rate)
184+
t_p = model(t_un, *params)
185+
loss = loss_fn(t_p, t_c)
186+
optimizer.zero_grad() # 此调用可以在循环中更早的位置
187+
loss.backward()
188+
optimizer.step()
189+
params
190+
```
191+
输出:
192+
```
193+
tensor([1.7761, 0.1064], requires_grad=True)
194+
```
195+
196+
完美!这就是`optim`模块如何抽象出特定的优化方法。你所要做的就是为其提供一个参数的列表(根据深度神经网络模型的需要,该列表可能很长),然后忽略所有细节。
197+
198+
因此训练循环代码如下:
199+
200+
``` python
201+
def training_loop(n_epochs, optimizer, params, t_u, t_c):
202+
for epoch in range(1, n_epochs + 1):
203+
t_p = model(t_u, *params)
204+
loss = loss_fn(t_p, t_c)
205+
optimizer.zero_grad()
206+
loss.backward()
207+
optimizer.step()
208+
if epoch % 500 == 0:
209+
print('Epoch %d, Loss %f' % (epoch, float(loss)))
210+
return params
211+
212+
params = torch.tensor([1.0, 0.0], requires_grad=True)
213+
learning_rate = 1e-2
214+
optimizer = optim.SGD([params], lr=learning_rate)
215+
216+
training_loop(
217+
n_epochs = 5000,
218+
optimizer = optimizer,
219+
params = params,
220+
t_u = t_un,
221+
t_c = t_c)
222+
```
223+
输出:
224+
```
225+
Epoch 500, Loss 7.860116
226+
Epoch 1000, Loss 3.828538
227+
Epoch 1500, Loss 3.092191
228+
Epoch 2000, Loss 2.957697
229+
Epoch 2500, Loss 2.933134
230+
Epoch 3000, Loss 2.928648
231+
Epoch 3500, Loss 2.927830
232+
Epoch 4000, Loss 2.927679
233+
Epoch 4500, Loss 2.927652
234+
Epoch 5000, Loss 2.927647
235+
tensor([ 5.3671, -17.3012], requires_grad=True)
236+
```
237+
238+
你再次得到了与以前相同的结果。不错。你进一步确认了你知道如何手动进行梯度下降!为了测试更多优化器,你只需要实例化一个不同的优化器,例如Adam而不是SGD,其余代码保持原样就可以了。所以这个模块很方便。
239+
240+
这里不会详细介绍Adam,你现在只需要知道它自适应地设置学习率,是一种更加复杂的优化器。此外,它对参数缩放的敏感度很低,以至于你可以使用原始(非标准化)输入`t_u`甚至将学习率提高到1e-1:
241+
242+
``` python
243+
params = torch.tensor([1.0, 0.0], requires_grad=True)
244+
learning_rate = 1e-1
245+
optimizer = optim.Adam([params], lr=learning_rate)
246+
247+
training_loop(
248+
n_epochs = 2000,
249+
optimizer = optimizer,
250+
params = params,
251+
t_u = t_u,
252+
t_c = t_c)
253+
```
254+
输出:
255+
```
256+
Epoch 500, Loss 7.612901
257+
Epoch 1000, Loss 3.086700
258+
Epoch 1500, Loss 2.928578
259+
Epoch 2000, Loss 2.927646
260+
tensor([ 0.5367, -17.3021], requires_grad=True)
261+
```
262+
263+
优化器并不是训练循环中唯一灵活的部分。现在将注意力转移到模型上。为了在相同的数据上使用相同的损失训练神经网络,你只需更改函数`model`即可。不过在当前例子中,这样做是没有意义的,因为我们已经知道将摄氏温度转换为华氏温度就是线性变换。神经网络可以去除你关于近似函数的任意假设。即使内在的映射过程是高度非线性的(例如,用句子描述图像),神经网络也会设法进行训练。
264+
265+
我们已经接触了许多基本概念,使你可以在训练复杂的深度学习模型的同时了解内在情况:反向传播以估计梯度,自动求导以及通过使用梯度下降或其他优化器优化模型的权重。我们要讲的没有多少了,剩余要讲的大部分填补了空白区,无论它有多广泛。
266+
267+
接下来,我们讨论如何划分样本,从而为学习更好地控制自动求导建立了一个完美的例子。

docs/img/chapter4/4.10.png

601 KB
Loading

0 commit comments

Comments
 (0)