首页人工智能Pytorch【深度学习(PyTorch...

【深度学习(PyTorch篇)】45.autograd.Function类

本系列文章配套代码获取有以下两种途径:

  • 通过百度网盘获取:
链接:https://pan.baidu.com/s/1XuxKa9_G00NznvSK0cr5qw?pwd=mnsj 提取码:mnsj
  • 前往GitHub获取
https://github.com/returu/PyTorch





在之前文章中,分别使用手动计算梯度以及自动求导功能实现线性回归:

【深度学习(PyTorch篇)】17.实现线性回归

【深度学习(PyTorch篇)】18.自动求导(autograd)

使用Pytorch的自动求导机制时,无需手动推导模型,给定一个前向表达式,无论嵌套如何,Pytorch都会自动计算表达式相对其输入参数的梯度。
但是,上述情况仅支持可以使用autograd自动求导的函数。如果,在实际操作中需要自定义一个复杂的函数,但不支持自动求导时,就需要使用autograd.Function类来自定义微分操作。
01

autograd.Function


在 PyTorch 中,自动微分引擎是通过一个称为 autograd 的库来实现的,它支持动态计算图。通过继承 autograd.Function 并实现其特定的方法,用户可以定义自己的层或操作,并且这些操作可以无缝地集成到 PyTorch 的自动微分框架中。当你创建一个自定义的 autograd.Function 时,通常需要实现两个主要方法:
  • forward(self, *input): 定义操作在前向传播时的行为。需要在这里执行自定义计算,并返回结果。
  • backward(self, *grad_output): 定义操作在反向传播时的行为。给定输出的梯度(grad_output),需要计算并返回输入的梯度。
下面是一个简单示例,演示如何创建一个自定义的 autograd.Function,该操作将输入张量中的每个元素平方:
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.02.03.0], requires_grad=True)  
y = Square.apply(x)  
y.sum().backward()  # 对y进行求和,以便创建一个标量(scalar)输出,然后对这个标量进行反向传播
print(x.grad)  # 输出应该是 [2., 4., 6.]
在上述扩展autograd的示例中,需要注意以下几点:
  • 自定义的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() 来计算梯度。


02

示例


下面使用自定义的 Function 类来计算梯度,完成之前的线性回归模型。
  • 生成数据:

生成数据部分与之前相同,就不做过多介绍:
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

  • 循环训练,求解参数值:
将之前自动求导操作修改为使用自定义的 Function 类来计算梯度
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/


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

RELATED ARTICLES

欢迎留下您的宝贵建议

Please enter your comment!
Please enter your name here

- Advertisment -

Most Popular

Recent Comments