本文探讨深度学习的基本原理。取材于《PyTorch深度学习实战》一书的第5章。也融入了一些自己的内容。

1. 深度学习基本原理初探

1.1 关于深度学习的过程的概述

给定输入数据和期望的输出,以及权重的初始值,给模型输入数据(正向传播),并通过对输出结果与实际结果进行比较来评估误差。
为了优化模型参数(即权重),权重单位变化后的误差变化(误差相对参数的梯度)是使用复合函数的链式法则计算的(即反向传播);然后在误差减小的方向上,更新权重值;重复该过程,直到误差降低到可接受的范围。

1.2 一个实例

假设有一个温度计,它没有刻度和单位。我们给它标上刻度,然后用另一个正常的温度计的测量值,来解释这个特殊温度计上的单位。

首先,我们收集了一些数据,如下:

t_c = [0.5,  14.0, 15.0, 28.0, 11.0,  8.0,  3.0, -4.0,  6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c)
t_u = torch.tensor(t_u)

t_c是以摄氏度为单位的温度;
t_u是未知单位的刻度值。

1.3 线性模型

根据以上数据,先画出点图,结果可以发现,貌似两种单位间是具有线性关系的。因此,我们可以做如下的一个假设,来作为线性模型:

t_c = w * t_u + b

所以,t_u 就是输入数据, t_c 就是实际结果。

1.4 损失函数

假设温度的预测值为t_p, 那么为了描述真实值和预测值的差距,最直接的有2种方法,即绝对值和平方: ∣t_p−t_c∣|t\_p - t\_c|∣t_p−t_c∣ 和 (t_p−t_c)2(t\_p - t\_c)^2(t_p−t_c)2
差的平方比绝对值对错误结果的惩罚更大。通常,有更多轻微错误的结果比有少量严重错误的结果要更好,并且差的平方有助于根据需要优先处理相关问题。
因此,损失函数还是选择差的平方。
下面给出建模函数 model和损失计算函数 loss_fn.

def model(t_u, w, b):return w * t_u + bdef loss_fn(t_p, t_c):squared_diffs = (t_p - t_c) ** 2return squared_diffs.mean()

上面的 squared_diffs是一个 1 维张量(即向量): tensor([n1, n2, n3, ...]);而loss_fn的返回结果是一个 0 维张量(即向量):tensor(num).

接下来,初始化参数(这里可使用随机值,或像下面这样w初始化为1,b初始化为0),调用模型,计算损失:

w = torch.ones(())    # 0维张量, tensor(1,)
b = torch.zeros(())   # 0维张量, tensor(0,)t_p = model(t_u, w, b)
loss = loss_fn(t_p, t_c)  # loss 是一个标量,如 tensor(1763.8846)

1.5 如何减小损失

现在来思考一个重要的问题 – 如何减小损失?
直接的想法是,当存在多个参数时,将所有其他的参数保持不变,只微调一个参数,将其增大或减小一个很小的值,看损失是增大了还是减小了;知道损失的变化结果后,我们就知道该如何调整这个参数了。

现有2个变量,w和b. 先假设b不变,只改变w,会对损失产生什么影响呢?
将w做一个很小的改变,看看损失是增加还是减少了。

delta = 0.1loss_1 = loss_fn(model(t_u, w-delta, b), t_c)
loss_2 = loss_fn(model(t_u, w+delta, b), t_c)loss_rate_of_change_w = (loss_2 - loss_1) / (2.0*delta)

此时,我们关注loss_rate_of_change_w 的正负。

  • 如果它是正的,说明损失增加了,即:增大w会使得损失增加,因此应该减小w;
  • 如果它是负的,说明损失减小了,即:增大w会使得损失减小,因此应该增大w;

由此可知:对原w,无论 loss_rate_of_change_w的正负,减去它,刚好可以起到“向正确的方向调整w”的目的。
但是,直接减,一般是有问题的,因为改变得很可能太大了;而改变得太大了,就失去了准头,甚至连方向也会错。换句话说,需要在正确的方向上进行微调
因此,这里需要引入一个较小的比例因子来调整变化率,在机器学习中,这个因子叫做“学习率(learning rate)”。

learning_rate = 1e-4w = w - learning_rate * loss_rate_of_change_w

然后,对参数b也采取类似的处理。

loss_rate_of_change_b = \(loss_fn(model(t_u, w, b + delta), t_c) - loss_fn(model(t_u, w, b - delta), t_c)) / (2.0 * delta)b = b - learning_rate * loss_rate_of_change_b

loss_rate_of_w最重要的功能就是让我们知道,只要减去它,就是在正确的方向上调整参数;而至于减去多少,则通过乘以学习率来加以调整。

通过重复以上的评估步骤(要选择一个足够小的学习率),将收敛到“在给定数据上使得损失最小”的参数最优值。

1.6 损失函数的偏导数函数

上面的分析,貌似比较完备了,但其实还有一个小小的漏洞 – delta为什么取0.1?为什么不是0.001?取大了会不会把方向搞反了? 应该取多少?

delta取0.1确实是一个拍脑袋的举动。也许我们看着t_c和t_u的数值,就会觉得0.1算是一个比较小的改动。但这里其实并没有理论的支持,完全属于拍脑袋,也就有可能会把方向搞反。方向搞反的话,训练再多次都是徒劳,可能会始终原地踏步或使得误差更大。一朵乌云,就会导致物理学大厦的崩塌。

那么,delta到底是什么?怎么取值?
其实,这个问题也不重要,因为我们并不需要引入它。
我们真正需要知道的是,loss_rate_of_change_wloss_rate_of_change_b的正负情况如何?大致值是多少?
看一下这2个变量的定义,就会发现:原来它们就是偏导数!
这就可以实现上节的直接想法了。

在一个有2个或2个以上参数的模型中,我们计算每个参数的损失的导数,并将它们放入一个导数向量(即梯度)中。
(笔者注:可以把梯度看成是各参数的偏导数的集合。
为了计算损失对于一个参数的导数,可以应用链式法则:先计算损失对于其输入(即模型的输出)的导数,再乘以模型对参数的导数。
dloss_fndw=dloss_fndt_p∗dt_pdw{\frac {\mathrm{d} loss\_fn} {\mathrm{d} w}} = {\frac {\mathrm{d} loss\_fn} {\mathrm{d} t\_p}} * {\frac {\mathrm{d} t\_p} {\mathrm{d} w}}dwdloss_fn​=dt_pdloss_fn​∗dwdt_p​

回忆一下 loss_fn

def loss_fn(t_p, t_c):squared_diffs = (t_p - t_c) ** 2return squared_diffs.mean()

再回忆一下求导公式:
dx2dx=2x{\frac {\mathrm{d} x^2} {\mathrm{d} x}} = 2xdxdx2​=2x

可以给出loss_fn函数对于t_p的导函数:

def dloss_fn(t_p, t_c):dsq_diffs = 2 * (t_p-t_c) / t_p.size(0)return dsq_diffs

(注意,这里除以了一个size,但最终计算梯度的地方会有一个sum()加回来。)

1.7 模型函数的偏导数函数

回忆一下模型函数

def model(t_u, w, b):return w * t_u + b

然后写出它的导函数。
注意,这里有2个自变量,因此需要分别求偏导数,在python就是2个函数了:

def dmodel_dw(t_u, w, b):return t_u def dmodel_db(t_u, w, b):return 1.0

1.8 定义梯度函数

将以上这些导函数根据链式法则合并在一起,就是关于www和bbb的损失梯度的函数:

def grad_fn(t_u, t_c, t_p, w, b):dloss_dtp = dloss_fn(t_p, t_c)dloss_dw = dloss_dtp * dmodel_dw(t_u, w, b)dloss_db = dloss_dtp * dmodel_db(t_u, w, b)return torch.stack([dloss_dw.sum(), dloss_db.sum()])

对应的数学公式如下:
▽w,bL=(∂L∂w,∂L∂b)=(∂L∂m⋅∂m∂w,∂L∂m⋅∂m∂b)\bigtriangledown_{w,b}L = ({\frac {\partial L} {\partial w}}, {\frac {\partial L} {\partial b}}) = ({\frac {\partial L} {\partial m}} \cdot {\frac {\partial m} {\partial w}}, {\frac {\partial L} {\partial m}} \cdot {\frac {\partial m} {\partial b}})▽w,b​L=(∂w∂L​,∂b∂L​)=(∂m∂L​⋅∂w∂m​,∂m∂L​⋅∂b∂m​)

细节释义:

  • 这里的 dloss_dw.sum()就是 loss_rate_of_change_w.
  • 之所以要sum(), 是因为在dloss_fn()中有一步除以t_p.size(0)的计算;
  • 上面的 dloss_dtpdmodel_dw都是一维张量但size相同,它们相乘,就是对应位置的元素相乘,所以结果 dloss_dw仍是一个 1 维张量;
  • 同样,dloss_db也是一个 1 维张量;
  • dloss_dw.sum()是一个 0 维张量,它的shape就是 torch.Size([]),它的值是把 dloss_dw这个一维张量里所有的元素都加起来后形成的一个数值,就是像 tensor(n)这样一个张量。
  • torch.stack()这个函数作用是 “Concatenates a sequence of tensors along a new dimension”, 即,在一个新的维度上将一个张量序列里的张量连接来(默认新维度为第0维)
  • 所以,torch.stack([tensor(n1), tensor(n2)])就是 tensor([n1, n2]).
  • 所以,这里最后返回的就是 tensor([n1, n2])。这就是梯度。也就是下面代码中的grad.

1.9 迭代以适应模型

Epoch,即一个迭代周期。在一个epoch中,我们更新所有训练样本的参数。
代码如下:

def training_loop(n_epochs, learning_rate, params, t_u, t_c,print_params=True):for epoch in range(1, n_epochs + 1):# params是1维张量,有2个元素;而 w 和 b 则是0维张量w, b = paramst_p = model(t_u, w, b)  # 正向传播loss = loss_fn(t_p, t_c)grad = grad_fn(t_u, t_c, t_p, w, b)  # 反向传播params = params - learning_rate * gradif epoch in {1, 2, 3, 10, 11, 99, 100, 4000, 5000}: print('Epoch %d, Loss %f' % (epoch, float(loss)))if print_params:print('    Params:', params)print('    Grad:  ', grad)if epoch in {4, 12, 101}:print('...')if not torch.isfinite(loss).all():breakreturn params

目前的参数配置是:

learning_rate = 1e-4
n_epochs = 100

loss最终收敛到29左右。但是从 epoch 10 到 epoch 100,参数的更新非常小,所以损失下降得非常慢,最终停滞不前。
我们可以通过学习率自适应来解决这个问题。

1.10 归一化输入

如果学习率足够大,能够有效更新其中一个参数,那么对于另一个参数,学习率很可能就变得不稳定。
如果我们改变模型,使得每个参数都有自己的学习率,那么这对于有很多参数的模型,就会太麻烦了。
还有一种方法,就是改变输入,使得各个参数的偏导数不会有太大的不同,即确保输入的范围不会偏离 -1.0 - 1.0 太远。

这里的参数调整是:

learning_rate = 1e-2
n_epochs = 5000
t_un = 0.1 * t_u

在变量名后面加一个n,表示是该变量的归一化版本。

1.11 完整的程序(自己算偏导数和梯度)

完整的程序如下:

import numpy as np
import torch
from matplotlib import pyplot as plt def model(t_u, w, b):return w * t_u + bdef loss_fn(t_p, t_c):squared_diffs = (t_p - t_c)**2return squared_diffs.mean()def grad_fn(t_u, t_c, t_p, w, b):def dloss_fn(t_p, t_c):dsq_diffs = 2 * (t_p - t_c) / t_p.size(0)return dsq_diffsdef dmodel_dw(t_u, w, b):return t_udef dmodel_db(t_u, w, b):return 1.0dloss_dtp = dloss_fn(t_p, t_c)dloss_dw = dloss_dtp * dmodel_dw(t_u, w, b)dloss_db = dloss_dtp * dmodel_db(t_u, w, b)return torch.stack([dloss_dw.sum(), dloss_db.sum()])def training_loop(n_epochs, learning_rate, params, t_u, t_c, print_params=True):for epoch in range(1, n_epochs + 1):w, b = paramst_p = model(t_u, w, b)loss = loss_fn(t_p, t_c)grad = grad_fn(t_u, t_c, t_p, w, b)params = params - learning_rate * gradif epoch in {1, 2, 3, 10, 11, 99, 100, 4000, 5000}:print('Epoch %d, Loss %f' % (epoch, float(loss)))if print_params:print('    Params:', params)print('    Grad:  ', grad)if epoch in {4, 12, 101, 4001}:print('...')return paramst_c = [0.5,  14.0, 15.0, 28.0, 11.0,  8.0,  3.0, -4.0,  6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c)
t_u = torch.tensor(t_u)
t_un = 0.1 * t_uparams = training_loop(n_epochs = 5000, learning_rate = 1e-2, params = torch.tensor([1.0, 0.0]), t_u = t_un, t_c = t_c)t_p = model(t_un, *params)fig = plt.figure(dpi=150)
plt.xlabel("Temperature (°Fahrenheit)")
plt.ylabel("Temperature (°Celsius)")
plt.plot(t_u.numpy(), t_p.detach().numpy())
plt.plot(t_u.numpy(), t_c.numpy(), 'o')
# plt.savefig("temp_unknown_plot.png", format="png")
plt.show()

运行的结果如下:

Epoch 1, Loss 80.364342Params: tensor([1.7761, 0.1064])Grad:   tensor([-77.6140, -10.6400])
Epoch 2, Loss 37.574913Params: tensor([2.0848, 0.1303])Grad:   tensor([-30.8623,  -2.3864])
Epoch 3, Loss 30.871077Params: tensor([2.2094, 0.1217])Grad:   tensor([-12.4631,   0.8587])
...
Epoch 10, Loss 29.030489Params: tensor([ 2.3232, -0.0710])Grad:   tensor([-0.5355,  2.9295])
Epoch 11, Loss 28.941877Params: tensor([ 2.3284, -0.1003])Grad:   tensor([-0.5240,  2.9264])
...
Epoch 99, Loss 22.214186Params: tensor([ 2.7508, -2.4910])Grad:   tensor([-0.4453,  2.5208])
Epoch 100, Loss 22.148710Params: tensor([ 2.7553, -2.5162])Grad:   tensor([-0.4446,  2.5165])
...
Epoch 4000, Loss 2.927680Params: tensor([  5.3643, -17.2853])Grad:   tensor([-0.0006,  0.0033])
...
Epoch 5000, Loss 2.927648Params: tensor([  5.3671, -17.3012])Grad:   tensor([-0.0001,  0.0006])

以上这个程序,实际上就是我们自己实现了反向传播的程序了,即利用梯度,反过来改变参数,以期减小损失。

一个细节:
t_p = model(t_un, *params)
这里应用了Python的参数解包值: *param意味着将params的元素作为单独的参数传递。一般去做解包的是列表或元组,但在这里的 params 是一个 tensor; 对于 tensor 来说,解包就是沿着前导维度来拆分的。
这里的 model(t_un, *params)就相当于 model(t_un, params[0], params[1]).
打印出的图象是:

以上这个程序,实际上就是我们自己实现了反向传播的程序了,即利用梯度,反过来改变参数,以期减小损失。

2. PyTorch自动求导

在之前的介绍中,我们看到了一个简单的反向传播的例子:通过链式规则反向传播导数。计算了模型和损失的复合函数关于其内部参数w和b的梯度。
注意,这里的基本要求是:所有的函数都是可微的。

那么,计算偏导数、梯度这些东西,能不能交给PyTorch来做呢?
当然可以。PyTorch的张量可以记住自己从何而来,根据产生它们的操作和父张量,它们可以根据输入自动提供这些操作的导数链。这就是说,我们不再需要手动推导偏导数的函数。给定一个前向表达式,无论嵌套方式如何,PyTorch都会自动提供表达式相对其输入参数的梯度。 --这就是框架强大的地方!

2.1 requires_grad属性和backward()函数

在原来的程序中,我们传入的参数是这么写的:

params = torch.tensor([1.0, 0.0])

现在可以改写成这样:

params = torch.tensor([1.0, 0.0], requires_grad=True)

这个参数requires_grad=True告诉PyTorch跟踪由对params张量进行操作后产生的张量的整个系谱树。
换句话说,任何将params作为祖先的张量都可以访问params到该张量的函数链。如果这些函数是可微的,导数的值将自动填充到params张量的grad属性。

通常,所有PyTorch张量都有一个名为grad的属性,该值初始为None.

我们所要做的就是,从一个requires_gradTrue的张量开始,调用模型并计算损失,然后反向调用损失张量:

loss = loss_fn(model(t_u, *params), t_c)
loss.backward()params.grad  # tensor([n1, n2])

此处,loss.backward()的作用就是,让PyTorch反向遍历paramsloss的生成图以计算梯度。
而params 的 grad 属性所包含的就是关于 params 的每个元素的损失的导数;把这个数值再变得比较小一些,即乘以一个学习率,再被params减去,即起到了“在正确的方向上调整参数”的目的。

所以,这里调用loss.backward(),就相当于上一节的程序中调用grad_fn(t_u, t_c, t_p, w, b);所以就不需要自己写grad_fn函数了。

2.2 累加梯度函数

注意:
调用backward()函数将导致导数在整个函数链的叶节点的累加。
使用梯度进行参数更新后,因为下一轮训练需要从头开始计算导数,所以需要显式地将梯度归零。
使用的方法就是 zero_()函数,该函数将本Tensor实例的值全部清零。

if params.grad is not None:params.grad.zero_()

现在重写一下训练时自动求导的代码:

def training_loop(n_epochs, learning_rate, params, t_u, t_c):for epoch in range(1, n_epochs + 1):if params.grad is not None:params.grad.zero_()t_p = model(t_u, *params) loss = loss_fn(t_p, t_c)loss.backward()with torch.no_grad():params -= learning_rate * params.gradif epoch % 500 == 0:print('Epoch %d, Loss %f' % (epoch, float(loss)))return params

这里要注意这2句话:

with torch.no_grad():params -= learning_rate * params.grad
  • 在Python中,with语句块的开始和末尾,会自动地调用context manager的 __enter____exit__方法。
  • no_grad是一个类,它继承了 _DecoratorContextManager, 它是一个禁用了梯度计算的上下文管理器
  • no_grad()是调用了 no_grad类的 __call__()方法
  • 这里通过使用Python的with语句将参数的更新封装在 no_grad 的 context 中。
  • 意思就是,在with块中,PyTorch求导机制将不会进行,即:不向前向图添加边。
  • 实际上,当上面执行backward()时,前向图就已经被消费掉了,留下了params叶子节点。但现在的这句减法操作,等于是对叶子节点建立一个新的前向图之前去改变叶子节点,这是不允许的,所以改变params的操作(即改变叶子节点的操作)就需要在 no_grad 的上下文环境进行,即,改变叶子节点的操作不会导致图的变化。

完整的程序如下:

import numpy as np
import torch
from matplotlib import pyplot as plt def model(t_u, w, b):return w * t_u + bdef loss_fn(t_p, t_c):squared_diffs = (t_p - t_c)**2return squared_diffs.mean()def training_loop(n_epochs, learning_rate, params, t_u, t_c):for epoch in range(1, n_epochs + 1):if params.grad is not None:params.grad.zero_()t_p = model(t_u, *params) loss = loss_fn(t_p, t_c)loss.backward()with torch.no_grad():params -= learning_rate * params.gradif epoch % 500 == 0:print('Epoch %d, Loss %f' % (epoch, float(loss)))return paramst_c = [0.5,  14.0, 15.0, 28.0, 11.0,  8.0,  3.0, -4.0,  6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c)
t_u = torch.tensor(t_u)
t_un = 0.1 * t_uparams = training_loop(n_epochs = 5000, learning_rate = 1e-2, params = torch.tensor([1.0, 0.0], requires_grad=True), t_u = t_un, t_c = t_c)t_p = model(t_un, *params)fig = plt.figure(dpi=150)
plt.xlabel("Temperature (°Fahrenheit)")
plt.ylabel("Temperature (°Celsius)")
plt.plot(t_u.numpy(), t_p.detach().numpy())
plt.plot(t_u.numpy(), t_c.numpy(), 'o')
plt.savefig("temp_unknown_plot.png", format="png")
plt.show()

可见,这个程序比前一个完整程序要少了自己写的求导函数和求梯度函数。

运行结果如下:

Epoch 500, Loss 7.860115
Epoch 1000, Loss 3.828538
Epoch 1500, Loss 3.092191
Epoch 2000, Loss 2.957698
Epoch 2500, Loss 2.933134
Epoch 3000, Loss 2.928648
Epoch 3500, Loss 2.927830
Epoch 4000, Loss 2.927679
Epoch 4500, Loss 2.927652
Epoch 5000, Loss 2.927647

图省略。

2.3 优化器

优化器的作用就是,不再需要手动地去更新每个参数,而是由优化器去做;
不同优化器的更新的策略不同。

torch模块有一个optim子模块,可以在其中找到是实现了不同优化算法的类。

import torch.optim as optim
dir(optim)

输出如下:

['ASGD','Adadelta','Adagrad','Adam','AdamW','Adamax','LBFGS','NAdam','Optimizer','RAdam','RMSprop','Rprop','SGD','SparseAdam','__builtins__','__cached__','__doc__','__file__','__loader__','__name__','__package__','__path__','__spec__','_functional','_multi_tensor','lr_scheduler','swa_utils']

每个优化器构造函数的第一个参数都是一个参数列表(又称PyTorch张量,将requires_grad设为True).

每个优化器都有2个public的方法 zero_grad()step.zero_grad(): 将所有参数的grad属性都清零。
另有step()方法:根据特定优化器的优化策略来更新参数值

2.3.1 使用一个梯度下降优化器

params = torch.tensor([1.0, 0.0], requires_grad=True)
learning_rate = 1e-5
optimizer = optim.SGD([params], lr=learning_rate)t_p = model(t_u, *params)
loss = loss_fn(t_p, t_c)
loss.backward()
optimizer.step()

这里的SGD就是“随机梯度下降”的意思。
如果将随机动量因子 mementum 参数设为0,那么该参数的默认值就是0.0,则优化器本身也就是一种批量梯度下降算法。
params的值在调用 step()的时候被更新,不再需要我们手动更新了。
由此,training_loop 可以简化如下:

def training_loop(n_epochs, optimizer, params, t_u, t_c):for epoch in range(1, n_epochs + 1):t_p = model(t_u, *params) loss = loss_fn(t_p, t_c)optimizer.zero_grad()  # 记得grad要清0loss.backward()optimizer.step()if epoch % 500 == 0:print('Epoch %d, Loss %f' % (epoch, float(loss)))return params

除了SGD优化器,还有Adam优化器。它是一个更加复杂的优化器,其中的学习率是自适应设置的。此外,它对参数的缩放不太敏感,以至于我们可以使用原始的(即非归一化)的输入 t_u, 甚至可以将学习率提高到1e-1.

2.3.2 使用优化器的完整程序

import numpy as np
import torch
import torch.optim as optim
from matplotlib import pyplot as plt def model(t_u, w, b):return w * t_u + bdef loss_fn(t_p, t_c):squared_diffs = (t_p - t_c)**2return squared_diffs.mean()def training_loop(n_epochs, optimizer, params, t_u, t_c):for epoch in range(1, n_epochs + 1):t_p = model(t_u, *params) loss = loss_fn(t_p, t_c)optimizer.zero_grad()loss.backward()optimizer.step()if epoch % 500 == 0:print('Epoch %d, Loss %f' % (epoch, float(loss)))return paramst_c = [0.5,  14.0, 15.0, 28.0, 11.0,  8.0,  3.0, -4.0,  6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c)
t_u = torch.tensor(t_u)
t_un = 0.1 * t_uparams = torch.tensor([1.0, 0.0], requires_grad=True)
optimizer = optim.SGD([params], lr = 1e-2)
# optimizer = optim.Adam([params], lr = 1e-2)params = training_loop(n_epochs = 5000, optimizer = optimizer,params = params, t_u = t_un, t_c = t_c)t_p = model(t_un, *params)fig = plt.figure(dpi=150)
plt.xlabel("Temperature (°Fahrenheit)")
plt.ylabel("Temperature (°Celsius)")
plt.plot(t_u.numpy(), t_p.detach().numpy())
plt.plot(t_u.numpy(), t_c.numpy(), 'o')
plt.savefig("temp_unknown_plot.png", format="png")
plt.show()

以上程序中,可以换着使用2种优化器;输出与前面的差不多,就不贴了。

总结:
本文首先根据方向导数和梯度的原理,根据给定的线性回归的案例,写出了grad_fn()函数,等同于模拟了PyTorch中的backward()函数的主要实现;
然后用backward()替换了自己手写的grad_fn();
最后用PyTorch的优化器替换了自己手写的参数更新代码。
总的来说,反向传播就是计算输入参数变化的梯度,然后调参 (即,参数=参数-学习率*参数的偏导数)。

[2022-11-30 update]
(完)

PyTorch深度学习笔记之四(深度学习的基本原理)相关推荐

  1. 深度学习入门之PyTorch学习笔记:深度学习介绍

    深度学习入门之PyTorch学习笔记:深度学习介绍 绪论 1 深度学习介绍 1.1 人工智能 1.2 数据挖掘.机器学习.深度学习 1.2.1 数据挖掘 1.2.2 机器学习 1.2.3 深度学习 第 ...

  2. 学习笔记:深度学习(3)——卷积神经网络(CNN)理论篇

    学习时间:2022.04.10~2022.04.12 文章目录 3. 卷积神经网络CNN 3.1 卷积神经网络的概念 3.1.1 什么是CNN? 3.1.2 为什么要用CNN? 3.1.3 人类的视觉 ...

  3. SVO 学习笔记(深度滤波)

    SVO 学习笔记(深度滤波) 这篇博客 论文中的深度滤波 深度滤波的代码流程 更新Seed对象 初始化Seed对象 结尾 这篇博客  这篇博客将介绍SVO论文中的Mapping部分,主要介绍深度滤波器 ...

  4. 分水岭算法java,OpenCV 学习笔记 04 深度估计与分割——GrabCut算法与分水岭算法...

    1 使用普通摄像头进行深度估计 1.1 深度估计原理 这里会用到几何学中的极几何(Epipolar Geometry),它属于立体视觉(stereo vision)几何学,立体视觉是计算机视觉的一个分 ...

  5. 2020-4-20 深度学习笔记20 - 深度生成模型 3 (实值数据上的玻尔兹曼机)

    第二十章 深度生成模型 Deep Generative Models 中文 英文 2020-4-17 深度学习笔记20 - 深度生成模型 1 (玻尔兹曼机,受限玻尔兹曼机RBM) 2020-4-18 ...

  6. 【长篇博文】Docker学习笔记与深度学习环境的搭建和部署(二)

    长篇博文记录学习流程不容易,请关注.转发.点赞.评论,谢谢! 上一篇文章:Docker学习笔记与深度学习环境的搭建和部署(一) 文章末尾附加nvidia455.23.cuda11.1.cudnn8.0 ...

  7. 2020-4-22 深度学习笔记20 - 深度生成模型 5 (有向生成网络--sigmoid信念网络/可微生成器网络/变分自编码器VAE/生产对抗网络GAN/生成矩匹配网络)

    第二十章 深度生成模型 Deep Generative Models 中文 英文 2020-4-17 深度学习笔记20 - 深度生成模型 1 (玻尔兹曼机,受限玻尔兹曼机RBM) 2020-4-18 ...

  8. 深度强化学习笔记(二)——Q-learning学习与二维寻路demo实现

    深度强化学习笔记(二)--Q-learning学习与二维寻路demo实现 文章目录 深度强化学习笔记(二)--Q-learning学习与二维寻路demo实现 前言 理论 什么是Q-Learning 算 ...

  9. ScalersTalk 机器学习小组第 21 周学习笔记(深度学习-10)

    ScalersTalk 机器学习小组第 21 周学习笔记(深度学习-10) Scalers点评:机器学习小组是成长会的内部小组,这是成长会机器学习小组第21周学习笔记,也是深度学习第10次的复盘笔记 ...

  10. 学习笔记:深度学习(6)——基于深度学习的语言模型

    学习时间:2022.04.22~2022.04.25 文章目录 5. 基于深度学习的语言模型 5.1 从NNLM到词嵌入 5.1.1 神经网络语言模型 NNLM 5.1.2 基于循环神经网络的语言模型 ...

最新文章

  1. Java多线程编程实战:模拟大量数据同步
  2. 树链剖分 ---- 2021杭电多校 1002 I love tree[详解]
  3. SAP QM 检验批里样品数量的确定
  4. 小学五年级就已经开始编程啦吗???
  5. nature machine intelligence
  6. SAP CRM material上传调试
  7. fileinput 加 ftp 加 nginx 加 SpringBoot上传文件
  8. LeetCode 1658. 将 x 减到 0 的最小操作数(哈希)
  9. java代码中何处以main开始_自测题: Java 基础
  10. 建议收藏!数据中台行业发展概况及展望
  11. python数据分析是什么意思_选择python进行数据分析的理由和优势
  12. linux资源异常无法fork,linux 下 fork 后的文件资源处理问题
  13. Centos7 Redis安装
  14. echarts考勤图表
  15. ubuntu 16.04外接显示屏问题
  16. HttpServletRequest获取路径的几个方法
  17. 如何使用SX1278的中断控制发送和接收
  18. 深入CC3200(1)—芯片简介及学习方法
  19. 【性能测试】性能测试的基本流程
  20. C语言 before string,c语言中expected expression before是什么意思?

热门文章

  1. 没技术没能力的人才会颓废
  2. QQ动态模块(初稿截图)
  3. Ubuntu18.04 制作系统ISO镜像并物理机还原(Systemback)
  4. css 解决透明度穿透问题
  5. javascri代码块的加载问题
  6. C语言打印各种图案合集
  7. java bdd 框架_开发者测试: 实现BDD测试框架(JSpec)
  8. python max函数代码_Python Max函数
  9. jQuery带音效旋转式动画切换幻灯片js特效
  10. iOS / iPadOS 15.7.4发布安全更新 旧版iPhone和ipad无法升级系统解决方法