文章目录

  • 1. Function理解
  • 2. Function与Module差异与应用场景
  • 3. MyRelu Function
  • 4. Linear Function


摘自https://zhuanlan.zhihu.com/p/27783097

1. Function理解

Pytorch是利用Variable与Function来构建计算图的。回顾下Variable,Variable就像是计算图中的节点,保存计算结果(包括前向传播的激活值,反向传播的梯度),而Function就像计算图中的边,实现Variable的计算,并输出新的Variable。
简单来说Function就是针对Variable的运算,Function与Variable构成了pytorch的自动求导机制

2. Function与Module差异与应用场景

Function与Module都可以对pytorch进行自定义拓展,使其满足网络的需求,但这两者还是有十分重要的不同:

  • Function一般只定义一个操作,因为其无法保存参数(如果需要这些参数参与到backward的计算当中,则可以通过saved_for_backward来保存参数),因此适用于激活函数、pooling等操作;Module是保存了参数,因此适合于定义一层,如线性层,卷积层,也适用于定义一个网络
  • Function需要定义三个方法:init, forward, backward(需要自己写求导公式);Module:只需定义__init__和forward,而backward的计算由自动求导机制构成
  • 可以不严谨的认为,Module是由一系列Function组成,因此其在forward的过程中,Function和Variable组成了计算图,在backward时,只需调用Function的backward就得到结果,因此Module不需要再定义backward。下面会有用Module将Function包装起来的例子
  • Module不仅包括了Function,还包括了对应的参数,以及其他函数与变量,这是Function所不具备的

3. MyRelu Function

import torch
from torch.autograd import Variable

class MyReLU(torch.autograd.Function):
	# 必须是staticmethod
    @staticmethod
    def forward(ctx, input_):
        # 在forward中,需要定义MyReLU这个运算的forward计算过程
        # 同时可以保存任何在后向传播中需要使用的变量值
        ctx.save_for_backward(input_)         # 将输入保存起来,在backward时使用
        output = input_.clamp(min=0)               # relu就是截断负数,让所有负数等于0
        return output

    @staticmethod
    def backward(ctx, grad_output):
        # grad_output 都是 1.

        # 根据BP算法的推导(链式法则),dloss / dx = (dloss / doutput) * (doutput / dx)
        # dloss / doutput就是输入的参数grad_output、
        # 因此只需求relu的导数,在乘以grad_output
        input_, = ctx.saved_tensors
        # saved_variables已经弃用,统一改用saved_tensors即可
        # input_, = ctx.saved_variables

        # grad_input 都是 1.
        grad_input = grad_output.clone()

        # 上诉计算的结果就是左式。即ReLU在反向传播中可以看做一个通道选择函数,所有未达到阈值(激活值<0)的单元的梯度都为0
        # grad_input 是 0, 1, 1
        grad_input[input_ < 0] = 0                
        return grad_input

调用MyRelu

x = torch.Tensor([-1,1,2])
x = Variable(x, requires_grad=True)
y = MyReLU.apply(x)
# z = y[0] + y[1] + y[2]
# 所以z关于y的导数就是关于y每个元素的导数,都是1.
z = torch.sum(y)

z.backward()

# 输出结果tensor([0., 1., 1.])
print(x.grad)

4. Linear Function

摘自

y = x*w +b # 自己定义的LinearFunction
z = f(y)
下面的grad_output = dz/dy
根据复合函数求导法则:

  1. dz/dx = dz/dy * dy/dx = grad_outputdy/dx = grad_outputw
  2. dz/dw = dz/dy * dy/dw = grad_outputdy/dw = grad_outputx
  3. dz/db = dz/dy * dy/db = grad_output*1
class LinearFunction(torch.autograd.Function):
    # 必须是staticmethod
    @staticmethod
    # 第一个是ctx,第二个是input,其他是可选参数。
    def forward(ctx, input, weight, bias=None):
        print(type(input))
        ctx.save_for_backward(input, weight, bias)
        output = input.mm(weight.t())  # torch.t()方法,对2D tensor进行转置
        if bias is not None:
            # expand_as(tensor)等价于expand(tensor.size()), 将原tensor按照新的size进行扩展
            output += bias.unsqueeze(0).expand_as(output)
        return output

    @staticmethod
    def backward(ctx, grad_output): 
        # grad_output为反向传播上一级计算得到的梯度值
        # input, weight, bias = ctx.saved_variables
        input, weight, bias = ctx.saved_tensors
        grad_input = grad_weight = grad_bias = None
        # 分别代表输入,权值,偏置三者的梯度
        # 判断三者对应的Variable是否需要进行反向求导计算梯度
        if ctx.needs_input_grad[0]:
            grad_input = grad_output.mm(weight)
        if ctx.needs_input_grad[1]:
            grad_weight = grad_output.t().mm(input)
        if bias is not None and ctx.needs_input_grad[2]:
            grad_bias = grad_output.sum(0).squeeze(0)

        return grad_input, grad_weight, grad_bias


class Linear(nn.Module):
    def __init__(self, input_features, output_features, bias=True):
        super(Linear, self).__init__()
        self.input_features = input_features
        self.output_features = output_features
        # nn.Parameter is a special kind of Variable, that will get
        # automatically registered as Module's parameter once it's assigned
        # 这个很重要! Parameters是默认需要梯度的!
        self.weight = nn.Parameter(torch.Tensor(output_features, input_features))
        if bias:
            self.bias = nn.Parameter(torch.Tensor(output_features))
        else:
            # You should always register all possible parameters, but the
            # optional ones can be None if you want.
            self.register_parameter('bias', None)
        # Not a very smart way to initialize weights
        self.weight.data.uniform_(-0.1, 0.1)
        if bias is not None:
            self.bias.data.uniform_(-0.1, 0.1)
    
    def forward(self, input):
        # See the autograd section for explanation of what happens here.
        return LinearFunction.apply(input, self.weight, self.bias)
        # 或者 return LinearFunction()(input, self.weight, self.bias)

调用Linear

x= torch.rand((2, 4), requires_grad=True)
y = Linear(input_features=4, output_features=3)(x)
z = torch.sum(y)
z.backward()
print(x.grad)