文章目录
- 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
根据复合函数求导法则:
- dz/dx = dz/dy * dy/dx = grad_outputdy/dx = grad_outputw
- dz/dw = dz/dy * dy/dw = grad_outputdy/dw = grad_outputx
- 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)