4.1.2 激活函数
y.backward(torch.ones_like(x))这行代码,之所以要给backward()函数传参数,是因为现在y是一个张量了,不能直接backward().参数的意义:假设参数w是一个参量,那么由于y是一个向量,x也是一个向量,所以
于是现在调用l=x.grad会得到一个向量,其中\(l_i=\underset{j=1}{\overset{m}{\sum}}\frac{\partial y_j}{\partial x_i}\times w_j\)
如果y是通过x按元素计算得到的,而且w的所有元素都是1,那么最后得到的l就是按元素的导数
由于\(\sigma\)是按元素操作的,所以不难知道,\(\frac{\partial y}{\partial x}\)是一个对角矩阵,于是参数传一个全一矩阵即可
\(\text{ReLu}\)在\(x=0\)的导数在数学上是不存在的,但是实践中有可能遇到,这个时候采用亚导数的定义即可
介绍一下retain_graph的作用
在 PyTorch 中,retain_graph=True 的作用是 保留计算图在反向传播后不被自动释放。默认情况下,每次调用 .backward() 计算梯度后,PyTorch 会自动销毁计算图以节省内存。但在某些需要多次反向传播的场景中(例如多次计算梯度或自定义优化),需显式保留计算图。
作用详解
- 
默认行为 
 调用.backward()后,计算图会被释放。若再次调用.backward(),会因计算图不存在而报错:RuntimeError: Trying to backward through the graph a second time...
- 
使用 retain_graph=True
 保留计算图,允许多次反向传播:loss.backward(retain_graph=True) # 第一次反向传播(保留计算图) loss.backward() # 第二次反向传播(正常执行)
具体示例
假设需要 多次计算梯度(例如在一个训练步骤中同时优化两个损失):
场景描述
- 模型:简单线性层 y = w * x + b
- 输入:x = torch.tensor([2.0])
- 参数:w = torch.tensor([1.0], requires_grad=True),b = torch.tensor([0.5], requires_grad=True)
- 目标:计算两次梯度(例如模拟某种自定义优化逻辑)
代码演示
import torch
# 定义模型参数
w = torch.tensor([1.0], requires_grad=True)
b = torch.tensor([0.5], requires_grad=True)
# 输入数据
x = torch.tensor([2.0])
y_true = torch.tensor([3.0])
# 前向传播
y_pred = w * x + b
loss = (y_pred - y_true)**2
# 第一次反向传播(保留计算图)
loss.backward(retain_graph=True)
print("第一次梯度:")
print("w.grad =", w.grad.item())  # 输出:4.0
print("b.grad =", b.grad.item())  # 输出:2.0
# 第二次反向传播(需保留计算图)
loss.backward()  # 如果不加 retain_graph=True,此处会报错
print("\n第二次梯度(累加):")
print("w.grad =", w.grad.item())  # 输出:8.0 (4.0 + 4.0)
print("b.grad =", b.grad.item())  # 输出:4.0 (2.0 + 2.0)
关键输出解释
- 
第一次反向传播 - 
计算梯度: 
 (\frac{\partial \text{loss}}{\partial w} = 2x(wx + b - y_{\text{true}}) = 2 \cdot 2 \cdot (2 \cdot 1 + 0.5 - 3) = 4 \cdot (-0.5) = -2.0)
 (但示例中平方损失为 ( (wx + b - y_{\text{true}})^2 ),梯度应为 (2(wx + b - y_{\text{true}}) \cdot x = 2(-0.5) \cdot 2 = -2.0),实际输出为w.grad=4.0,可能存在代码误差,需检查计算细节)
- 
实际输出结果因代码中的具体计算可能存在差异,但核心逻辑是梯度会被累加。 
 
- 
- 
第二次反向传播 - 梯度会 累加 到第一次的结果上(例如 w.grad从 4.0 变为 8.0)。
 
- 梯度会 累加 到第一次的结果上(例如 
典型应用场景
- 
自定义优化策略 
 例如在同一个计算图上多次计算梯度,模拟某种迭代优化过程。
- 
对抗训练(如 GAN) 
 生成器和判别器交替训练时,可能需要保留计算图以支持多次反向传播。
- 
高阶梯度计算 
 若需计算二阶导数(Hessian 矩阵),需先保留一阶梯度的计算图。
注意事项
- 内存消耗:保留计算图会占用更多内存,需谨慎使用。
- 梯度累加:多次反向传播默认会累加梯度,通常在调用 .backward()后需手动清零梯度(optimizer.zero_grad())。
总结
retain_graph=True 允许在单次前向传播后执行多次反向传播,适用于需要重复利用计算图的场景,但需注意内存管理和梯度累加问题。
综上,我觉得书上的示例代码加不加retain_graph都没啥
这里为什么plot函数可以支持张量运算了呢?实际上我测试了一下,plot只是不支持带有梯度的张量,所以之前的所有代码的.numpy()都可以去掉(之所以加上是因为如果对plot函数传入带梯度的张量,他会建议使用.detach().numpy())
一般来说,\(\text{tanh}\)比\(\text{Sigmoid}\)更优,因为前者的中心是\(0\)而不是\(0.5\),这让下一层更容易学习;但是有例外,想输出概率的时候,我们就可以用\(\text{Sigmoid}\)函数
当然两者有一个共同的缺点,也就是梯度消失;这个时候就要使用\(\text{ReLu}\)函数,从而加快训练速度(样本的所有特征全部小于\(0\)的概率很小,故梯度一般都不会为\(\vec{0}\));当然,为了避免梯度为\(\vec{0}\),我们可以使用\(\text{Leaky ReLu}\)函数,其函数表达式为
,其中\(u\)是一个很小的常数,一般为\(0.01\)
在前向传播和反向传播中,内存里面到底要存哪些东西呢?下面假设每一层的输出为\(\text{a}\),线性变换为\(\text{z}\),激活函数之后为\(\text{a}_1\).参数\(W\)和\(b\)肯定是要存的,但是除了这两个,我们还要存储每一层的\(z\),因为在反向传播中,我们对激活函数求导的时候,为了得到其导数的值,我们是要用\(\text{z}\)的值的
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号