Pytorch Learning
Date : 2020/3/20 - 2020/3/27 (Week 5)
这一周,我们学习并分析在机器学习任务中使用 pytorch 必用的 module (torch.nn) 的源代码实现。
./torch/nn 源代码分析
torch/nn目录结构以及init.py
# __init__.py:
from .modules import *
from .parameter import Parameter
from .parallel import DataParallel
from . import init
from . import utils
我们在使用中往往使用的语句:
import torch.nn as nn
执行时,默认导入./torch/nn/modules
, ./torch/nn/parameter
,./torch/nn/parallel
, torch/nn/init.py
,torch/nn/utils
这些文件的内容。(递归地,又引入了modules.__init__
,parallel.__init__
的内容 )
这样,我们不需要其他额外的 import
就可以使用我们常用的 nn.Module
来定义我们的模型了。
对于backends
, functional.py
, _functions
需要在代码前重新Import。例如我们常用的
import torch.nn.functional as F
就是导入了functional.py
, 同样地,backends
和_functions
是 functional.py
实现各种函数时所用到的。
torch/nn/parameter.py 源代码分析
- 我们首先比较好分析的入手,
torch/nn/modules/module.py
的先决,torch/nn/parameter.py
: torch/nn/parameter.py
本内容就一个Parameter
类定义,下分析其代码实现:
class Parameter(torch.Tensor):
r"""A kind of Tensor that is to be considered a module parameter.
Parameters are :class:`~torch.Tensor` subclasses, that have a
very special property when used with :class:`Module` s - when they're
assigned as Module attributes they are automatically added to the list of
its parameters, and will appear e.g. in :meth:`~Module.parameters` iterator.
Assigning a Tensor doesn't have such effect. This is because one might
want to cache some temporary state, like last hidden state of the RNN, in
the model. If there was no such class as :class:`Parameter`, these
temporaries would get registered too.
Arguments:
data (Tensor): parameter tensor.
requires_grad (bool, optional): if the parameter requires gradient. See
:ref:`excluding-subgraphs` for more details. Default: `True`
"""
def __new__(cls, data=None, requires_grad=True):
if data is None:
data = torch.Tensor()
return torch.Tensor._make_subclass(cls, data, requires_grad)
def __deepcopy__(self, memo):
if id(self) in memo:
return memo[id(self)]
else:
result = type(self)(self.data.clone(memory_format=torch.preserve_format), self.requires_grad)
memo[id(self)] = result
return result
def __repr__(self):
return 'Parameter containing:\n' + super(Parameter, self).__repr__()
def __reduce_ex__(self, proto):
# See Note [Don't serialize hooks]
return (
torch._utils._rebuild_parameter,
(self.data, self.requires_grad, OrderedDict())
)
Parameter
类是torch.Tensor
类的子类,区别于torch.Tensor
当其被指定为Module
属性的时候,他们会自动加入Module.parameters
迭代器。__new__
: 创建对象(类准备将自身实例化时调用)在__init__
方法前调用。__deepcopy__
: 一旦复制出来了,就是独立的,原对象的改变不影响deepcopy 的对象值。__repr__
: 方向在print
时调用,在其中又调用父类的__repr__
方法。实例属性和类属性:Python 是动态语言,可以在
__init__
动态的添加实例属性。而和编译语言一样,Python 的类属性需要直接定义在类中,举例来说:class Car(object): country = u'中国' def __init__(self,owner=None): self.owner = owner self.country = "china"
Car
类中含有country
这一类属性和owner
,country
两个实例属性。
torch/nn/init.py 源代码分析
这一部分在实际的使用中非常常用,经常用在 module
子类的__init__
方法中,定义模型参数的初始化方法。常见的初始化方法包括kaiming_normal_
, xavier_uniform_
,normal_
等,这些在论文中提出的初始化方法能够起到一定控制 gradient 流的作用,从而加速学习效率,十分重要,下举一例说明平时我们如何使用 torch.nn.init
。该例来自 CS231n assignment2。
class MyConvNet(nn.Module):
def __init__(self, input_channel, num_class):
super().__init__()
self.conv_1 = nn.Conv2d(input_channel, 32, kernel_size=(3, 3), padding=1)
nn.init.kaiming_normal_(self.conv_1.weight)
self.bn_1 = nn.BatchNorm2d(32)
self.pooling_1 = nn.MaxPool2d((2, 2), stride = 2)
self.conv_2 = nn.Conv2d(32, 32, kernel_size=(3, 3))
nn.init.kaiming_normal_(self.conv_2.weight)
self.bn_2 = nn.BatchNorm2d(32)
self.pooling_2 = nn.MaxPool2d((2, 2), stride = 2)
self.fc_1 = nn.Linear(32 * 7 * 7, 128)
nn.init.kaiming_uniform_(self.fc_1.weight)
self.bn_3 = nn.BatchNorm1d(128)
self.fc_2 = nn.Linear(128, 10)
nn.init.kaiming_normal_(self.fc_2.weight)
def forward(self, x):
conv_1_out = F.relu(self.bn_1(self.conv_1(x)))
pool_1_out = self.pooling_1(conv_1_out)
assert pool_1_out.shape == (x.shape[0], 32, 16, 16) , "pool_1_out shape wrong"
conv_2_out = F.relu(self.bn_2(self.conv_2(pool_1_out)))
pool_2_out = self.pooling_2(conv_2_out)
assert pool_2_out.shape == (x.shape[0], 32, 7, 7) , "pool_2_out shape wrong"
pool_2_out_flatten = flatten(pool_2_out)
assert pool_2_out_flatten.shape == (x.shape[0], 32 * 7 * 7), "flatten shape wrong"
fc_1_out = F.relu(self.bn_3(self.fc_1(pool_2_out_flatten)))
assert fc_1_out.shape == (x.shape[0], 128)
scores = self.fc_2(fc_1_out)
return scores
这一部分主要包括以下几个部分:
no_grad_*
funcitons (_no_grad_uniform_
,_no_grad_normal_
,_no_grad_trunc_normal_
,_no_grad_fill_
,_no_grad_zero_
)_no_grad_uniform_
: 返回被 with torch.no_grad() 环境包裹的tensor.uniform_(a, b), 均匀分布_no_grad_normal_
: 返回被 with torch.no_grad() 环境包裹的 tensor.normal_(mean, std), 正太分布_no_grad_trunc_normal_
:返回被 with torch.no_grad() 环境包裹的截断正太分布[a, b] 区间内近似服从正态分布。_no_grad_fill_
:返回被 with torch.no_grad() 环境包裹的 tensor.fill_(val) 填充函数_no_grad_zero_
:返回被 with torch.no_grad() 环境包裹的 tensor.zero_() 函数
使用
no_grad_*
funcitons 完成的 built-in 函数(uniform_
,normal_
,trunc_normal_
,constant_
,ones_
,zeros
)- 这五个函数分别对应
_no_grad_uniform_
,_no_grad_normal_
,_no_grad_trunc_normal_
,_no_grad_fill_
,_no_grad_zero_
。
- 这五个函数分别对应
常见的初始化方法
kaiming_normal_
,kaiming_uniform_
,xavier_normal_
,xavier_uniform_
,eye_
,dirac_
,orthogonal_
等。Note: 这里不是按照原顺序排列的方法。而是按照使用的频繁程度排序。
_make_deprecate
:对于使用 old_name 的情况给出 warnings :建议使用后面加下划线的方法,将old_name 函数重定向到 new_name 方法。
Summary
- 今天我们大致上分析了torch.nn 的调用结构,以及torch.nn.parameters,和torch.nn.init 三个部分。明白了在import torch.nn 的时候完成了工作,清晰了module的参数parameter本质也是torch.tensor, 了解了torch.nn.init中常见的初始化方法,了解到如果不使用加下划线的初始化方法带来的warnings原理。
- 之后会继续分析torch.nn文件中其余部分。