@@ -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+ 接下来,我们讨论如何划分样本,从而为学习更好地控制自动求导建立了一个完美的例子。
0 commit comments