pytorch源码:Python层

Posted 你的奋斗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pytorch源码:Python层相关的知识,希望对你有一定的参考价值。

尝试使用了pytorch,相比其他深度学习框架,pytorch显得简洁易懂。花时间读了部分源码,主要结合简单例子带着问题阅读,不涉及源码中C拓展库的实现。

一个简单例子

实现单层softmax二分类,输入特征维度为4,输出为2,经过softmax函数得出输入的类别概率。代码示意:定义网络结构;使用SGD优化;迭代一次,随机初始化三个样例,每个样例四维特征,target分别为1,0,1;前向传播,使用交叉熵计算loss;反向传播,最后由优化算法更新权重,完成一次迭代。

 
   
   
 
  1. import torch

  2. import torch.nn as nn

  3. import torch.nn.functional as F

  4. class Net(nn.Module):

  5.    def __init__(self):

  6.        super(Net, self).__init__()

  7.        self.linear = nn.Linear(4, 2)

  8.    def forward(self, input):

  9.        out = F.softmax(self.linear(input))

  10.        return out

  11. net = Net()

  12. sgd = torch.optim.SGD(net.parameters(), lr=0.001)

  13. for epoch in range(1):

  14.    features = torch.autograd.Variable(torch.randn(3, 4), requires_grad=True)

  15.    target = torch.autograd.Variable(torch.LongTensor([1, 0, 1]))

  16.    sgd.zero_grad()

  17.    out = net(features)

  18.    loss = F.cross_entropy(out, target)

  19.    loss.backward()

  20.    sgd.step()

从上面的例子,带着下面的问题阅读源码:

  • pytorch的主要概念:Tensor、autograd、Variable、Function、Parameter、Module(Layers)、Optimizer;


  • 自定义Module如何组织网络结构和网络参数;


  • 前向传播、反向传播实现流程


  • 优化算法类如何实现,如何和自定义Module联系并更新参数。


pytorch的主要概念

pytorch的主要概念官网有很人性化的教程Deep Learning with PyTorch: A 60 Minute Blitz, 这里简单概括这些概念:

Tensor

类似numpy的ndarrays,强化了可进行GPU计算的特性,由C拓展模块实现。如上面的torch.randn(3, 4) 返回一个3*4的Tensor。和numpy一样,也有一系列的Operation,如

 
   
   
 
  1. x = torch.rand(5, 3)

  2. y = torch.rand(5, 3)

  3. print x + y

  4. print torch.add(x, y)

  5. print x.add_(y)

Varaiable与autograd

Variable封装了Tensor,包括了几乎所有的Tensor可以使用的Operation方法,主要使用在自动求导(autograd),Variable类继承C.VariableBase,由C拓展类定义实现。

Variable是autograd的计算单元,Variable通过Function组织成函数表达式(计算图):

  • data 为其封装的tensor值


  • grad 为其求导后的值


  • creator 为创建该Variable的Function,实现中grad_fn属性则指向该Function。


如:

 
   
   
 
  1. import torch

  2. from torch.autograd import Variable

  3. x = Variable(torch.ones(2, 2), requires_grad=True)

  4. y = x + 2

  5. print y.grad_fn

  6. print "before backward: ", x.grad

  7. y.backward()

  8. print "after backward: ", x.grad

输出结果:

 
   
   
 
  1. <torch.autograd.function.AddConstantBackward object at 0x7faa6f3bdd68>

  2. before backward:  None

  3. after backward:  Variable containing:

  4. 1

  5. [torch.FloatTensor of size 1x1]

调用y的backward方法,则会对创建y的Function计算图中所有requires_grad=True的Variable求导(这里的x)。例子中显然dy/dx = 1。

Parameter

Parameter 为Variable的一个子类,后面还会涉及,大概两点区别:

  • 作为Module参数会被自动加入到该Module的参数列表中;


  • 不能被volatile, 默认require gradient。


Module

Module为所有神经网络模块的父类,如开始的例子,Net继承该类,_init_中指定网络结构中的模块,并重写forward方法实现前向传播得到指定输入的输出值,以此进行后面loss的计算和反向传播。

Optimizer

Optimizer是所有优化算法的父类(SGD、Adam、...),_init_中传入网络的parameters, 子类实现父类step方法,完成对parameters的更新。

自定义Module

该部分说明自定义的Module是如何组织定义在构造函数中的子Module,以及自定义的parameters的保存形式,eg:

 
   
   
 
  1. class Net(nn.Module):

  2.    def __init__(self):

  3.        super(Net, self).__init__()

  4.        self.linear = nn.Linear(4, 2)

  5.    def forward(self, input):

  6.        out = F.softmax(self.linear(input))

  7.        return out

首先看构造函数,Module的构造函数初始化了Module的基本属性,这里关注parameters和modules,两个属性初始化为OrderedDict(),pytorch重写的有序字典类型。parameters保存网络的所有参数,modules保存当前Module的子Module。

module.py:

 
   
   
 
  1. class Module(object):

  2.   def __init__(self):

  3.        self._parameters = OrderedDict()

  4.        self._modules = OrderedDict()

  5.        ...

下面来看自定义Net类中self.linear = nn.Linear(4, 2)语句和modules、parameters如何产生联系,或者self.linear及其参数如何被添加到modules、parameters字典中。答案在Module的_setattr_方法,该Python内建方法会在类的属性被赋值时调用。

module.py:

 
   
   
 
  1. def __setattr__(self, name, value):

  2.      def remove_from(*dicts):

  3.          for d in dicts:

  4.              if name in d:

  5.                  del d[name]

  6.      params = self.__dict__.get('_parameters')

  7.      if isinstance(value, Parameter): # ----------- <1>

  8.          if params is None:

  9.              raise AttributeError(

  10.                  "cannot assign parameters before Module.__init__() call")

  11.          remove_from(self.__dict__, self._buffers, self._modules)

  12.          self.register_parameter(name, value)

  13.      elif params is not None and name in params:

  14.          if value is not None:

  15.              raise TypeError("cannot assign '{}' as parameter '{}' "

  16.                              "(torch.nn.Parameter or None expected)"

  17.                              .format(torch.typename(value), name))

  18.          self.register_parameter(name, value)

  19.      else:

  20.          modules = self.__dict__.get('_modules')

  21.          if isinstance(value, Module):# ----------- <2>

  22.              if modules is None:

  23.                  raise AttributeError(

  24.                      "cannot assign module before Module.__init__() call")

  25.              remove_from(self.__dict__, self._parameters, self._buffers)

  26.              modules[name] = value

  27.          elif modules is not None and name in modules:

  28.              if value is not None:

  29.                  raise TypeError("cannot assign '{}' as child module '{}' "

  30.                                  "(torch.nn.Module or None expected)"

  31.                                  .format(torch.typename(value), name))

  32.              modules[name] = value

  33.          ......

调用self.linear = nn.Linear(4, 2)时,父类_setattr被调用,参数name为“linear”, value为nn.Linear(4, 2),内建的Linear类同样是Module的子类。所以<2>中的判断为真,接着modules[name] = value,该linear被加入modules字典。

同样自定义Net类的参数即为其子模块Linear的参数,下面看Linear的实现:

linear.py:

 
   
   
 
  1. class Linear(Module):

  2.    def __init__(self, in_features, out_features, bias=True):

  3.        super(Linear, self).__init__()

  4.        self.in_features = in_features

  5.        self.out_features = out_features

  6.        self.weight = Parameter(torch.Tensor(out_features, in_features))

  7.        if bias:

  8.            self.bias = Parameter(torch.Tensor(out_features))

  9.        else:

  10.            self.register_parameter('bias', None)

  11.        self.reset_parameters()

  12.    def reset_parameters(self):

  13.        stdv = 1. / math.sqrt(self.weight.size(1))

  14.        self.weight.data.uniform_(-stdv, stdv)

  15.        if self.bias is not None:

  16.            self.bias.data.uniform_(-stdv, stdv)

  17.    def forward(self, input):

  18.        return F.linear(input, self.weight, self.bias)

同样继承Module类,_init中参数为输入输出维度,是否需要bias参数。在self.weight = Parameter(torch.Tensor(outfeatures, infeatures))的初始化时,同样会调用父类Module的setattr, name为“weight”,value为Parameter,此时<1>判断为真,调用self.registerparameter(name, value),该方法中对参数进行合法性校验后放入self._parameters字典中。

Linear在reset_parameters方法对权重进行了初始化。

最终可以得出结论自定义的Module以树的形式组织子Module,子Module及其参数以字典的方式保存。

前向传播、反向传播

前向传播

例子中out = net(features)实现了网络的前向传播,该语句会调用Module类的forward方法,该方法被继承父类的子类实现。net(features)使用对象作为函数调用,会调用Python内建的_call_方法,Module重写了该方法。

module.py:

 
   
   
 
  1. def __call__(self, *input, **kwargs):

  2.   for hook in self._forward_pre_hooks.values():

  3.       hook(self, input)

  4.   result = self.forward(*input, **kwargs)

  5.   for hook in self._forward_hooks.values():

  6.       hook_result = hook(self, input, result)

  7.       if hook_result is not None:

  8.           raise RuntimeError(

  9.               "forward hooks should never return any values, but '{}'"

  10.               "didn't return None".format(hook))

  11.   if len(self._backward_hooks) > 0:

  12.       var = result

  13.       while not isinstance(var, Variable):

  14.           var = var[0]

  15.       grad_fn = var.grad_fn

  16.       if grad_fn is not None:

  17.           for hook in self._backward_hooks.values():

  18.               wrapper = functools.partial(hook, self)

  19.               functools.update_wrapper(wrapper, hook)

  20.               grad_fn.register_hook(wrapper)

  21.   return result

_call_方法中调用result = self.forward(input, *kwargs)前后会查看有无hook函数需要调用(预处理和后处理)。

例子中Net的forward方法中out = F.softmax(self.linear(input)),同样会调用self.linear的forward方法F.linear(input, self.weight, self.bias)进行矩阵运算(仿射变换)。

functional.py:

 
   
   
 
  1. def linear(input, weight, bias=None):

  2.    if input.dim() == 2 and bias is not None:

  3.        # fused op is marginally faster

  4.        return torch.addmm(bias, input, weight.t())

  5.    output = input.matmul(weight.t())

  6.    if bias is not None:

  7.        output += bias

  8.    return output

最终经过F.softmax,得到前向输出结果。F.softmax和F.linear类似前面说到的Function(Parameters的表达式或计算图)。

反向传播

得到前向传播结果后,计算loss = F.cross_entropy(out, target),接下来反向传播求导数d(loss)/d(weight)和d(loss)/d(bias):

 
   
   
 
  1. loss.backward()

backward()方法同样底层由C拓展,这里暂不深入,调用该方法后,loss计算图中的所有Variable(这里linear的weight和bias)的grad被求出。

Optimizer参数更新

在计算出参数的grad后,需要根据优化算法对参数进行更新,不同的优化算法有不同的更新策略。

optimizer.py:

 
   
   
 
  1. class Optimizer(object):

  2.    def __init__(self, params, defaults):

  3.        if isinstance(params, Variable) or torch.is_tensor(params):

  4.            raise TypeError("params argument given to the optimizer should be "

  5.                            "an iterable of Variables or dicts, but got " +

  6.                            torch.typename(params))

  7.        self.state = defaultdict(dict)

  8.        self.param_groups = list(params)

  9.       ......

  10.    def zero_grad(self):

  11.        """Clears the gradients of all optimized :class:`Variable` s."""

  12.        for group in self.param_groups:

  13.            for p in group['params']:

  14.                if p.grad is not None:

  15.                    if p.grad.volatile:

  16.                        p.grad.data.zero_()

  17.                    else:

  18.                        data = p.grad.data

  19.                        p.grad = Variable(data.new().resize_as_(data).zero_())

  20.    def step(self, closure):

  21.        """Performs a single optimization step (parameter update).

  22.        Arguments:

  23.            closure (callable): A closure that reevaluates the model and

  24.                returns the loss. Optional for most optimizers.

  25.        """

  26.        raise NotImplementedError

Optimizer在init中将传入的params保存到self.paramgroups,另外两个重要的方法zerograd负责将参数的grad置零方便下次计算,step负责参数的更新,由子类实现。

以列子中的sgd = torch.optim.SGD(net.parameters(), lr=0.001)为例,其中net.parameters()返回Net参数的迭代器,为待优化参数;lr指定学习率。

SGD.py:

 
   
   
 
  1. class SGD(Optimizer):

  2.    def __init__(self, params, lr=required, momentum=0, dampening=0,

  3.                 weight_decay=0, nesterov=False):

  4.        defaults = dict(lr=lr, momentum=momentum, dampening=dampening,

  5.                        weight_decay=weight_decay, nesterov=nesterov)

  6.        if nesterov and (momentum <= 0 or dampening != 0):

  7.            raise ValueError("Nesterov momentum requires a momentum and zero dampening")

  8.        super(SGD, self).__init__(params, defaults)

  9.    def __setstate__(self, state):

  10.        super(SGD, self).__setstate__(state)

  11.        for group in self.param_groups:

  12.            group.setdefault('nesterov', False)

  13.    def step(self, closure=None):

  14.        """Performs a single optimization step.

  15.        Arguments:

  16.            closure (callable, optional): A closure that reevaluates the model

  17.                and returns the loss.

  18.        """

  19.        loss = None

  20.        if closure is not None:

  21.            loss = closure()

  22.        for group in self.param_groups:

  23.            weight_decay = group['weight_decay']

  24.            momentum = group['momentum']

  25.            dampening = group['dampening']

  26.            nesterov = group['nesterov']

  27.            for p in group['params']:

  28.                if p.grad is None:

  29.                    continue

  30.                d_p = p.grad.data

  31.                if weight_decay != 0:

  32.                    d_p.add_(weight_decay, p.data)

  33.                if momentum != 0:

  34.                    param_state = self.state[p]

  35.                    if 'momentum_buffer' not in param_state:

  36.                        buf = param_state['momentum_buffer'] = d_p.clone()

  37.                    else:

  38.                        buf = param_state['momentum_buffer']

  39.                        buf.mul_(momentum).add_(1 - dampening, d_p)

  40.                    if nesterov:

  41.                        d_p = d_p.add(momentum, buf)

  42.                    else:

  43.                        d_p = buf

  44.                p.data.add_(-group['lr'], d_p)

  45.        return loss

SGD的step方法中,判断是否使用权重衰减和动量更新,如果不使用,直接更新权重param := param - lr * d(param)。例子中调用sgd.step()后完成一次epoch。这里由于传递到Optimizer的参数集是可更改(mutable)的,step中对参数的更新同样是Net中参数的更新。

小结

到此,根据一个简单例子阅读了pytorch中Python实现的部分源码,没有深入到底层Tensor、autograd等部分的C拓展实现,后面再继续读一读C拓展部分的代码。

原创文章, 转载注明出处


以上是关于pytorch源码:Python层的主要内容,如果未能解决你的问题,请参考以下文章

机器学习之神经网络的公式推导与python代码(手写+pytorch)实现

Android 事件分发事件分发源码分析 ( Activity 中各层级的事件传递 | Activity -> PhoneWindow -> DecorView -> ViewGroup )(代码片段

将两行torch代码转为pytorch或python代码,代码如下

pytorch源码:C拓展

self attention pytorch代码

手写数字识别python代码 卷积层,池化层,正向传播(relu:激活函数)