本系列文章配套代码获取有以下两种途径:
-
通过百度网盘获取:
链接:https://pan.baidu.com/s/1XuxKa9_G00NznvSK0cr5qw?pwd=mnsj
提取码:mnsj
-
前往GitHub获取:
https://github.com/returu/PyTorch
【深度学习(PyTorch篇)】18.自动求导(autograd)
autograd.Function:
-
forward(self, *input): 定义操作在前向传播时的行为。需要在这里执行自定义计算,并返回结果。 -
backward(self, *grad_output): 定义操作在反向传播时的行为。给定输出的梯度(grad_output),需要计算并返回输入的梯度。
import torch
from torch.autograd import Function
class Square(Function):
@staticmethod
def forward(ctx, input):
ctx.save_for_backward(input) # 保存输入以便在backward中使用
return input ** 2
@staticmethod
def backward(ctx, grad_output):
input, = ctx.saved_tensors # 获取在forward中保存的输入
return grad_output * 2 * input # 根据链式法则计算梯度
# 使用自定义的Square函数
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = Square.apply(x)
y.sum().backward() # 对y进行求和,以便创建一个标量(scalar)输出,然后对这个标量进行反向传播
print(x.grad) # 输出应该是 [2., 4., 6.]
-
自定义的Function类需要继承自autograd.Function,因为没有构造函数__init__(),因此实现的forward 和 backward 函数都属于静态方法。
-
forward 函数的输出和 backward 函数的输入一一对应。在本例中,在 forward 方法中,我们简单地将输入张量的每个元素平方,并保存输入张量以便在 backward 方法中使用。在 backward 方法中,我们根据链式法则计算梯度并返回。
-
在反向传播过程中,可能会利用前向传播的某些中间结果。那么,在前向传播过程中,需要保存这些中间结果,否则前向传播过程结束后这些对象就将释放。ctx.save_for_backward(*tensors)方法用于保存前向传播中的某些张量,以便在反向传播时使用。在示例中,ctx.save_for_backward(input)用于保存input张量。这样,在backward方法中,就可以通过ctx.saved_tensors来获取这个保存的输入,并用它来计算梯度。这里的ctx是一个上下文对象,它在forward和backward方法之间传递信息。由于PyTorch的自动微分引擎在backward方法中不会自动传递forward方法中的输入参数,因此我们需要显式地保存任何在反向传播时需要的张量。
-
使用 Function.apply(x) 调用自定义操作,并通过调用 y.backward() 来计算梯度。
示例:
-
生成数据:
import torch
from torch.autograd import Function
# 设置随机数种子,保证运行结果一致
torch.manual_seed(2024)
def get_fake_data(num):
"""
生成随机数据,y = 0.5 * x +30并加上一些随机噪声
"""
x = torch.randint(low = -5, high=30, size=(num,)).to(dtype=torch.float32)
y = 0.5 * x +30 + torch.randn(num,)
return x,y
x , y = get_fake_data(num=11)
# 对输入数据做归一化处理
x_new = x * 0.1
-
定义函数:
定义模型以及损失函数部分与之前相同,就不做过多介绍:
# 定义模型:
def model(x , w , b):
return w * x + b
# 定义损失函数:
def loss_fn(y_p , y):
squared_diffs = (y_p - y)**2
return squared_diffs.mean()
下面实现用于计算梯度的自定义 Function 类。在后续训练循环中,我们通过调用 ComputeGradients.apply 方法来应用这个自定义函数。
# 手动计算梯度的函数
class compute_gradients(Function):
@staticmethod
# 定义前向传播,它接受输入张量,并返回输出张量
def forward(ctx, w , x , b):
ctx.save_for_backward(x) # 保存x用于反向传播,w和b由autograd管理
output = w * x + b
return output
@staticmethod
# 定义反向传播,用于计算输入张量相对于损失函数的梯度
def backward(ctx, grad_output):
x, = ctx.saved_tensors # 加载保存的x
# 因为 output = w * x + b,所以 d(output)/dw = x。因此,grad_w 就是 grad_output * x。
# 由于 w 是一个标量,我们需要对所有样本的梯度求和,因此使用 sum() 函数。
grad_w = (grad_output * x).sum()
grad_x = grad_output * w
# 同理计算grad_b
grad_b = grad_output.sum()
return grad_w , grad_x , grad_b
-
循环训练,求解参数值:
def training_loop(n_epochs , learning_rate , w, b , x , y):
for epoch in range(1 , n_epochs+1):
#前向传播
# 在训练循环中,我们通过调用 ComputeGradients.apply 方法来应用这个自定义函数
y_pred = compute_gradients.apply(w , x_new , b)
loss = loss_fn(y_pred, y)
# 反向传播:自动计算梯度
loss.backward()
# 更新参数
with torch.no_grad():
w -= learning_rate * w.grad
b -= learning_rate * b.grad
if epoch % 10 ==0:
print(f"Epoch : {epoch} , Loss : {loss}")
print(f"Params :w={w}, b={b} , Grad :w={w.grad}, b={b.grad}")
print("-----")
w.grad.zero_() # 清零梯度
b.grad.zero_() # 清零梯度
return w, b
设置相关参数值并运行,可以看到结果与之前一致,说明操作成功。
# 初始化参数
w = torch.tensor(1.0,requires_grad=True)
b = torch.tensor(0.0,requires_grad=True)
training_loop(n_epochs=1000 , learning_rate=1e-2 , w=w, b=b, x=x_new , y=y)
更多内容可以前往官网查看:
https://pytorch.org/


本篇文章来源于微信公众号: 码农设计师