pytorch自动求导以及自定义网络层

一 利用Variable自动求导

1.1 Variable

1.1.1 定义

  在pytorch中,我们需要能够构建计算图的 tensor,这就是 Variable数据结构。Variable 是对 tensor 的封装,操作和 tensor 是一样的,但是每个 Variabel都有三个属性,Variable 中的 tensor本身.data,对应 tensor 的梯度.grad以及这个 Variable 是通过什么方式得到的.grad_fn。

1.1.2 特性

  • requires_grad
    变量可以有梯度,求导。

  • volatile
    主要以用于inference过程中。若是某个过程,从 x 开始 都只需做预测,不需反传梯度的话,那么只需设置x.volatile=True ,那么 x 以后的运算过程的输出均为 volatileTrue ,即 requires_gradFalse。虽然inference 过程不必backward(),所以requires_grad 的值为False 或 True,对结果是没有影响的,但是对程序的运算效率有直接影响;所以使用volatile=True ,就不必把运算过程中所有参数都手动设一遍requires_grad = False 了,方便快捷。

  • detach

y = A(x)
temp=y.detach()
z = B(temp)
z.backward()

def detach(self):
  result = NoGrad()(self)  # this is needed, because it merges version counters
  result._grad_fn = None
  return result

  如果我们有两个网络 , 两个关系是这样的 现在我们想用 来为B网络的参数来求梯度,但是又不想求A网络参数的梯度。接着我们看一下detach的源码,将grad_fn设置为None,也就是说切断了变量temp与上一个网络反向传播的途径。不知道temp如何得到的。有关detach与clone的区别:https://blog.youkuaiyun.com/guofei_fly/article/details/104486708/

  • retain_graph
import torch
x = torch.randn((1,4),dtype=torch.float32,requires_grad=True)
y = x ** 2
z = y * 4
output1 = z.mean()
output2 = z.sum()
output1.backward()    # 这个代码执行正常,但是执行完中间变量都free了,所以下一个出现了问题
#正确的写法是  output1.backward(retain_graph=True) 
output2.backward()    # 这时会引发错误

  所以retain_graph主要用于处理,计算节点数值保存了,但是计算图x-y-z-out结构被释放了的情况。

  • create_graph
    官方的意思是对反向传播过程再次构建计算图,可通过backward of backward实现求高阶导数。默认False,目前还没看到具体的应用,看到再补上。

1.2 自动求导过程

1.2.1 输出为标量

首先我们看一段代码:

import torch as t 
from torch.autograd import Variable as V
a=V(t.Tensor([2,3]),requires_grad=True)
b=a+3
c=b*3
out=c.mean()
out.backward()

print("a.data\n",a.data)
print("a.grad\n",a.grad)
print("a.grad_fn\n",a.grad_fn)
print("b.data\n",b.data)
print("b.grad\n",b.grad)
print("b.grad_fn\n",b.grad_fn)
print("out.data\n",out.data)
print("out.grad\n",out.grad)
print("out.grad_fn\n",out.grad_fn)


对应的函数是:
o u t = 3 [ ( a 1 + 3 ) + ( a 2 + 3 ) ] 2 = 3 [ a + 3 ] 2 out=\frac{3\left [ \left ( a_1 + 3 \right )+\left ( a_2 + 3 \right ) \right ]}{2}=\frac{3\left [ \mathbf{a}+3 \right ]}{2} out=23[(a1+3)+(a2+3)]=23[a+3]
所以\(\mathbf{a}\)向量的梯度为:
∂ o u t ∂ a = ( 3 2 , 3 2 ) \frac{\partial out}{\partial \mathbf{a}}=(\frac{3}{2},\frac{3}{2}) aout=(23,23)
真实的运行结果为:

a.data
 tensor([2., 3.])
a.grad
 tensor([1.5000, 1.5000])
a.grad_fn
 None
b.data
 tensor([5., 6.])
b.grad
 None
b.grad_fn
 <AddBackward0 object at 0x000002C22F441A20>
out.data
 tensor(16.5000)
out.grad
 None
out.grad_fn
 <MeanBackward1 object at 0x000002C22F441160>

我们得到以下结论:

  • grad_fn表示变量通过怎样的计算方式得到,叶节点变量None
  • grad为梯度,中间变量不存储grad,只有叶节点存储。
  • data为运行过程中变量的值

1.2.2 输出为向量

1.2.2.1 雅可比矩阵的介绍

  Rn→Rm为一个从欧式n维空间转换到欧式m维空间的函数,并且由m个实函数组成: y1(x1,…,xn), …, ym(x1,…,xn)。若将该函数的偏导数(若存在)组成一个m行n列的矩阵, 那么这个矩阵就是所谓的雅可比矩阵:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ssA1tUgy-1570678546020)(learn-pytorch/yakebi.jpg)]

1.2.2.2 实例

首先我们再看一段代码

import torch as t 
from torch.autograd import Variable as V
a=V(t.Tensor([[2,4]]),requires_grad=True)
b=t.zeros(1,2)
b[0,0]=a[0,0]**2+a[0,1]
b[0,1]=a[0,1]**3+a[0,0]
out=2*b
out.backward(t.Tensor([[1,1]]))
#backward参数与out维度相同

print("a.data\n",a.data)
print("a.grad\n",a.grad)
print("a.grad_fn\n",a.grad_fn)
print("b.data\n",b.data)
print("b.grad\n",b.grad)
print("b.grad_fn\n",b.grad_fn)
print("out.data\n",out.data)
print("out.grad\n",out.grad)
print("out.grad_fn\n",out.grad_fn)

对应的函数为:
o u t = 2 b = 2 ( ( a 1 ) 2 + a 2 , ( a 2 ) 3 + a 1 ) \mathbf{out}=2\mathbf{b}=2\left ( \left (a_1\right )^2+a_2,\left (a_2\right )^3+a_1 \right) out=2b=2((a1)2+a2,(a2)3+a1)
我们求得\(\mathbf{out}\)对\(\mathbf{a}\)雅可比矩阵为:
\begin{pmatrix}
\frac{\partial out_1}{\partial a_1}=4a_1 & \frac{\partial out_1}{\partial a_2}=2 \\
\frac{\partial out_2}{\partial a_1}=2& \frac{\partial out_2}{\partial a_2}=6{a_2}^2
\end{pmatrix}
也就是:
\begin{pmatrix}
8 & 2\
2&96
\end{pmatrix}
真实的运行结果为:

a.data
 tensor([[2., 4.]])
a.grad
 tensor([[10., 98.]])
a.grad_fn
 None
b.data
 tensor([[ 8., 66.]])
b.grad
 None
b.grad_fn
 <CopySlices object at 0x0000020221827F60>
out.data
 tensor([[ 16., 132.]])
out.grad
 None
out.grad_fn
 <MulBackward0 object at 0x0000020221827C50>

我们得到以下结论:

  • \(\mathbf{a}\)向量的梯度由\(\mathbf{out}\)对\(\mathbf{a}\)雅可比矩阵得出。也就是\( a.grad=(\frac{\partial out_1}{\partial a_1}+\frac{\partial out_2}{\partial a_1}=10 , \frac{\partial out_1}{\partial a_2}+\frac{\partial out_2}{\partial a_2}=98)\),那么这是为什么呢?与backward输入的[[1,1]]参数有何联系?接下来看下一节。

1.2.3 输出为矩阵:backward()参数的意义

1.2.3.1 矩阵求导的介绍

  基础是利用矩阵微分,复杂结合链式法则。矩阵微分和矩阵的迹有很大的关系,矩阵论中有详细的描述。
矩阵微分的介绍:https:/www.cnblogs.compinardp/10791506.html
矩阵求导常用公式:https://blog.youkuaiyun.com/WPR1991/article/details/82929843

1.2.3.2 实例

首先看一段代码

import torch as t 
from torch.autograd import Variable as V
x=t.Tensor([[100,200],[101,201]])
w=V(t.Tensor([[0.1],[0.2]]),requires_grad=True)
b=V(t.Tensor([[0.01],[0.02]]),requires_grad=True)
y=t.Tensor([[1],[0]])
out=(t.mm(x,w)+b)-y
out.backward(t.Tensor([[1],[2]]))

print("w.data\n",w.data)
print("w.grad\n",w.grad)
print("w.grad_fn\n",w.grad_fn)
print("b.data\n",b.data)
print("b.grad\n",b.grad)
print("b.grad_fn\n",b.grad_fn)
print("out.data\n",out.data)
print("out.grad\n",out.grad)
print("out.grad_fn\n",out.grad_fn)

  我们模拟一个超级简单的网络\(\mathbf{out} ={\left (\mathbf{X}\mathbf{W}+\mathbf{B}\right )}-\mathbf{Y} \),全部为矩阵。\( \mathbf{X} \)为我们输入的有2个属性的2组数据,\(\mathbf{Y}\)为2组数据的标签,所以这两个只需要Tensor封装,不需要Variable封装,\(\mathbf{out}\)为差损失。\(\mathbf{W}\)与\(\mathbf{B}\)为随机初始化的参数,需要计算梯度并通过梯度下降等方法变化,代码中没有写优化算法,我们只想看看一次反向传播后变量的梯度是多少。理论上可以得出:
∂ o u t ∂ W = X = ( 100 200   101 201 ) \frac{\partial \mathbf{out}}{\partial \mathbf{W}} = \mathbf{X} = \begin{pmatrix} 100 & 200\\\ 101 & 201 \end{pmatrix} Wout=X=(100 101200201)
∂ o u t ∂ B = I = ( 1 0   0 1 ) \frac{\partial \mathbf{out}}{\partial \mathbf{B}} = \mathbf{I} = \begin{pmatrix} 1 & 0\\\ 0 & 1 \end{pmatrix} Bout=I=(1 001)

我们看一下运行结果

w.data
 tensor([[0.1000],
        [0.2000]])
w.grad
 tensor([[302.],
        [602.]])
w.grad_fn
 None
b.data
 tensor([[0.0100],
        [0.0200]])
b.grad
 tensor([[1.],
        [2.]])
b.grad_fn
 None
out.data
 tensor([[49.0100],
        [50.3200]])
out.grad
 None
out.grad_fn
 <SubBackward0 object at 0x000001E4B0857C18>

可以看到\(\mathbf{W}\)与\(\mathbf{B}\)的梯度是理论值与backward参数运算的结果.
\(\mathbf{W}\)的梯度为
\begin{pmatrix}
100+101\times 2 \
200+201\times 2
\end{pmatrix}
也就是
\begin{pmatrix}
302 \\
602
\end{pmatrix}
\(\mathbf{B}\)和\(\mathbf{W}\)计算方式相同。
我们可以得出以下的结论:

  • backward的参数的实际意义是:当每个变量在反向传播计算梯度时候,不同的样本将给与不同的权重影响。这个权重就是backward的参数,当参数全部为1,表示所有的样本都一样,意味着拿到一个mini-batch的所有数据平均梯度。backward的参数与y的维度相同。

1.2.4 利用自动求导找函数的最小值

原来的文章https://blog.youkuaiyun.com/weixin_42892943/article/details/94716387

首先我们看一段代码

import numpy as np
import torch as t 
from torch.autograd import Variable as V

def fun(x):
    return (x[0]**2+x[1]-11)**2+(x[0]+x[1]**2-7)**2

x = V(t.Tensor([0.,0.]),requires_grad=True)
optimizer = t.optim.Adam([x],lr=1e-3)
for step in range(20000):
    pred = fun(x)
    optimizer.zero_grad()
    pred.backward()
    optimizer.step()
    if step%2000 == 0:
        print('step{}: x = {}, x.grad = {}, f(x) = {}'.format(step,x.toli(),x.grad,pred.item()))

我们构建了一个函数:
z = ( x 2 + y − 11 ) 2 + ( x + y 2 − 7 ) 2 z=(x^2+y-11)^2+(x+y^2-7)^2 z=(x2+y11)2+(x+y27)2
其图像为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mzFddpyN-1570678546023)(learn-pytorch/fun.png)]
我们利用adam算法优化器寻找函数的最小值。结果如下:

step0: x = [0.0009999999310821295, 0.0009999999310821295], x.grad = tensor([-14., -22.]), f(x) = 170.0
step2000: x = [2.3331806659698486, 1.9540692567825317], x.grad = tensor([-35.3487, -13.8643]), f(x) = 13.730920791625977
step4000: x = [2.9820079803466797, 2.0270984172821045], x.grad = tensor([-0.7803,  0.5791]), f(x) = 0.014858869835734367
step6000: x = [2.999983549118042, 2.0000221729278564], x.grad = tensor([-0.0008,  0.0004]), f(x) = 1.1074007488787174e-08
step8000: x = [2.9999938011169434, 2.0000083446502686], x.grad = tensor([-0.0003,  0.0002]), f(x) = 1.5572823031106964e-09
step10000: x = [2.999997854232788, 2.000002861022949], x.grad = tensor([-9.5367e-05,  5.7221e-05]), f(x) = 1.8189894035458565e-10
step12000: x = [2.9999992847442627, 2.0000009536743164], x.grad = tensor([-2.8610e-05,  1.7166e-05]), f(x) = 1.6370904631912708e-11
step14000: x = [2.999999761581421, 2.000000238418579], x.grad = tensor([-9.5367e-06,  5.7220e-06]), f(x) = 1.8189894035458565e-12
step16000: x = [3.0, 2.0], x.grad = tensor([0., 0.]), f(x) = 0.0
step18000: x = [3.0, 2.0], x.grad = tensor([0., 0.]), f(x) = 0.0

我们可以看到x逐渐接近最低点,梯度在不断的减小。

二 数据集预处理的自定义构建

2.1 transforms中的源码

class Normalize(object):

    def __init__(self, mean, std, inplace=False):
        self.mean = mean
        self.std = std
        self.inplace = inplace

    def __call__(self, tensor):
        return F.normalize(tensor, self.mean, self.std, self.inplace)

    def __repr__(self):
        return self.__class__.__name__ + '(mean={0}, std={1})'.format(self.mean, self.std)

以Normalize为例子,我们看到每个处理方式由__init__,call,__repr__组成。

2.2 自定义自己的预处理方式

  torchvision.transforms的方法都是随机的,如果样本和标签都是图片,需要转动相同的随机角度。需要自己来定义预处理方式。这时候可以借用torchvision.transforms.functional来构造自己的函数,同时处理样本标签,得到新的样本标签后再分开处理。

import torchvision.transforms.functional as TF
import torchvision.transforms as T 

#自定义旋转角度:方法一,二
class FixedRotation(object):
    def __init__(self, startangle,endangle):
        self.startangle = startangle
        self.endangle = endangle

    def __call__(self,img):
        return  my_transforms1(img,startangle,endangle)
        # return  my_transforms2(img,startangle,endangle)

    def __repr__(self):
    	return self.__class__.__name__ +'from{}to{}'.format(startangle,endangle)

def my_transforms1(img, startangle,endangle):
    angle = random.randint(startangle,endangle)
    image = img.rotate(angles[angle])
    return image

def my_transforms2(image):
    angle = random.randint(-30, 30)
    image = TF.rotate(image, angle)
    return image

2.3 自定义一个数据集预处理

2.3.1 torch.utils.data.Dataset

自定义一个数据集需要继承类torch.utils.data.Dataset。类中主要方法有3个:

  • __init__(self,path)该方法用来初始化类和对数据进行加载,数据的加载就是针对不同的数据读入到内存中。

  • __getitem__(self, index)该方法是把读入的输出传给PyTorch(迭代器的方式)。

  • __len__(self)该方法是数据大小,迭代一次数据的大小。

2.3.2 torch.utils.data.DataLoader()

torch.utils.data.DataLoader类主要使用torch.utils.data.sampler实现,sampler是所有采样器的基础类,提供了迭代器的迭代(iter)和长度(len)接口实现,同时sampler也是通过索引对数据进行洗牌(shuffle)等操作。因此,如果DataLoader不适用于你的数据,需要重新设计数据的分批次,可以充分使用所提供的smapler。

  • batch-size。样本每个batch的大小,默认为1。
  • shuffle。是否打乱数据,默认为False。
  • sampler。定义一个方法来绘制样本数据,如果定义该方法,则不能使用shuffle。
  • num_workers。数据分为几批处理(对于大数据)。
  • collate_fn。整理数据,把每个batch数据整理为tensor。(一般使用默认调用default_collate(batch))。
  • pin_memory。将获取的数据张量放在固定的内存中,从而能够更快地将数据传输到支持cuda的gpu
  • drop_last。用于处理最后一个batch的数据。因为最后一个可能不能够被整除,如果设置为True,则舍弃最后一个,为False则保留最后一个,但是最后一个可能很小。

主要讲一下sampler与collate_fn.

三 神经网络的自定义构建

3.1 详解nn.module

  这里先看一下nn.module类源码中重要的属性方法,这里放一段代码,等下下面会用到,我们建立了一个前传后的钩子函数,一个有纯参数组成的module,一个由submodule组成的module,并用这两个module组成个复杂的大module。


import torch as t 
from torch import nn 
from torch.nn import functional as F
from torch.autograd import Variable as V

def for_hook(module, input, output):
    print(module)
    for val in input:
        print("input val:",val)
    for out_val in output:
        print("output val:", out_val)

class Linear(nn.Module): # 继承nn.Module
    def __init__(self, in_features, out_features):
        super(Linear, self).__init__() # 等价于nn.Module.__init__(self)
        self.w = nn.Parameter(t.randn(in_features, out_features))
        self.b = nn.Parameter(t.randn(out_features))
    def forward(self, x):
        x = x.mm(self.w) # x.@(self.w)
        return x + self.b.expand_as(x)

class MyLinear(nn.Module):
    def __init__(self):
        super(MyLinear, self).__init__()
        self.subsubmodule=Linear(3,3)
        self.subsubmodule.register_forward_hook(for_hook)
    def forward(self,x):
        x = self.subsubmodule(x)
        x = F.relu(x)
        return x

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()

        self.register_buffer("buf1",t.ones(3,3)) #为module注册一个缓存区

        self.register_parameter("param1",nn.Parameter(t.ones(3,3))) #与下一句等价,注册一个参数,本质就是一个变量
        self.param2 = nn.Parameter(t.rand(3, 3))

        self.add_module("submodel1",nn.Linear(3, 3)) #与下一句等价,注册一个子module
        self.submodel2 = nn.Linear(3, 3) 

        self.submodel3 = MyLinear()
        self.modulelist1 = nn.ModuleList([nn.Linear(3,3),nn.Linear(3,3)])
        self.modulelist2 = nn.Sequential(nn.Linear(3,3),nn.Linear(3,3))
        self.bn = nn.BatchNorm1d(3)
    def forward(self, input):
        x = input.mm(self.param1)
        x = self.submodel1(x)
        x = self.submodel3(x)
        # x = self.modulelist1(x) error
        x = self.modulelist2(x)
        x = self.bn(x)
        return x
        
net = Net()
x=t.rand(2,3)
y=net(x)

3.1.1 属性

class Module(object):
	def __init__(self):
	    self._parameters = OrderedDict()
	    self._modules = OrderedDict()
	    self._buffers = OrderedDict()
	    self._backward_hooks = OrderedDict()
	    self._forward_hooks = OrderedDict()
	    self.training = True
	    self._forward_pre_hooks = OrderedDict()
	    self._state_dict_hooks = OrderedDict()
	    self._load_state_dict_pre_hooks = OrderedDict()

解释一下重要的几个属性,我把它分为三类。

  • 用于存储数据的:
    • _parameters:字典,保存用户直接设置的parameter,self.param1 = nn.Parameter(t.randn(3, 3))会被检测到,在字典中加入一个key为’param’,value为对应parameter的item。不会查看到子module的参数。
    • _modules:子module,通过self.submodel = nn.Linear(3, 4)指定的子module会保存于此。同时子moudle可以自己定义。
    • _buffers:缓存,每个moudle都可以注册自己的缓存。如batchnorm使用momentum机制,每次前向传播需用到上一次前向传播的结果。
  • 用于是否继续向前传播的:
    • training:通过判断training值来决定正向传播策略。
  • 钩子函数来实现对前传,后传,保存,回复等操作的触发,_backward_hooks,_forward_hooks,_forward_pre_hooks,_state_dict_hooks,_load_state_dict_pre_hooks这些字典主要用于存储钩子。

我们看几个属性:

net._buffers:
 OrderedDict([('buf1', tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]]))])

net._modules:
 OrderedDict([('submodule1', Linear(in_features=3, out_features=3, bias=True)), ('submodule2', Linear(in_features=3, out_features=3, bias=True)), ('submodule3', MyLinear(
  (subsubmodule): Linear()
)), ('modulelist1', ModuleList(
  (0): Linear(in_features=3, out_features=3, bias=True)
  (1): Linear(in_features=3, out_features=3, bias=True)
)), ('modulelist2', Sequential(
  (0): Linear(in_features=3, out_features=3, bias=True)
  (1): Linear(in_features=3, out_features=3, bias=True)
)), ('bn', BatchNorm1d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True))])

net._parameters:
 OrderedDict([('param1', Parameter containing:
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], requires_grad=True)), ('param2', Parameter containing:
tensor([[0.3535, 0.6803, 0.7144],
        [0.2985, 0.1329, 0.2111],
        [0.3999, 0.0395, 0.1407]], requires_grad=True))])

net.training:
 True

net.submodule3.subsubmodule._forward_hooks:
 OrderedDict([(0, <function for_hook at 0x0000028C74F23E18>)])

3.1.2 方法

根据属性我们来分开介绍一些重要的nn.moudle的重要方法。

3.1.2.1 缓存,子模型,参数的设置,操作,查看的方法
  • self.register_buffer(name[string],buf[Tensor])
  • self.register_parameter(name[string],param[nn.Parameter])等价与self.name=param
  • self.add_module(name[string],submodule[nn.Module]) 等价于self.name=submodule
  • self.named_parameters() 生成器,产生所有参数的name与param
  • self.named_buffers() 生成器,产生所有参数的name与buf
  • self.named_module() 生成器,产生所有参数的name与module,包括module与子module
  • self.named_children() 生成器,产生所有参数的name与submodule
  • self.cuda() 转移到cuda上
  • self.cpu() 转移到cpu上
  • self.float() 参书变换
  • self.to() 多态性
          .. function:: to(device=None, dtype=None, non_blocking=False)
    
          .. function:: to(dtype, non_blocking=False)
    
          .. function:: to(tensor, non_blocking=False)
    
    
  • self.apply(fn) 源码
      def apply(self, fn):
          for module in self.children():
              module.apply(fn)
          fn(self)
          return self
    
    

我们看一些方法:

net.named_parameters()所有参数名称:
param1
param2
submodule1.weight
submodule1.bias
submodule2.weight
submodule2.bias
submodule3.subsubmodule.w
submodule3.subsubmodule.b
modulelist1.0.weight
modulelist1.0.bias
modulelist1.1.weight
modulelist1.1.bias
modulelist2.0.weight
modulelist2.0.bias
modulelist2.1.weight
modulelist2.1.bias
bn.weight
bn.bias

net.named_modules()所有模型名称:
submodule1
submodule2
submodule3
submodule3.subsubmodule
modulelist1
modulelist1.0
modulelist1.1
modulelist2
modulelist2.0
modulelist2.1
bn

net.named_children()所有子模型名称:
submodule1
submodule2
submodule3
modulelist1
modulelist2
bn

net.named_buffers()所有缓存名称:
buf1
bn.running_mean
bn.running_var
bn.num_batches_tracked
3.2.1.2 传播的方法
  • self.train() #使用Dropout与BN层训练时开启
  • self.eval() #使用Dropout与BN层测试时开启
  • self.zero_grad() #清空所有参数的梯度
3.2.1.3 钩子的方法
  • self.register_forward_hook(hook) 设立该module前传后的钩子
  • self.register_backward_hook(hook) 设立该module后传后的钩子
  • self.register_forward_pre_hook(hook) 设立该module前传前的钩子

正向传播时候当钩子的函数监控的module发生正向传播,触发钩子函数:

Linear()
input val: tensor([[ 0.3910, -0.9092, -0.9559],
        [ 0.4495, -0.9752, -1.0354]], grad_fn=<AddmmBackward>)
output val: tensor([ 1.3830,  1.3145, -0.7691], grad_fn=<SelectBackward>)
output val: tensor([ 1.5223,  1.4328, -0.7066], grad_fn=<SelectBackward>)

3.3 自定义functional

其实整个网络都是依靠与基础的functional,functional实现了卷积,损失等等一系列功能,那么我们怎么自己写一个functional呢?其实pytorch官方给出了拓展方式,用numpy与scipy(实现卷积等操作封装度高于numpy)即可,如果你的操作这些库依旧不能满足,那么需要定制C++底层,这里我们先不学习,主要学习使用python下的定制functional。
https://www.cnblogs.com/hellcat/p/8453615.html
https://pytorch.org/tutorials/advanced/numpy_extensions_tutorial.html

3.3.1 官方源码的解析

class BadFFTFunction(Function):
    @staticmethod
    def forward(ctx, input):
        numpy_input = input.detach().numpy() 
        result = abs(rfft2(numpy_input))
        return input.new(result)

    @staticmethod
    def backward(ctx, grad_output):
        numpy_go = grad_output.numpy()
        result = irfft2(numpy_go)
        return grad_output.new(result)

def incorrect_fft(input):
    return BadFFTFunction.apply(input)

这段代码是官方源码,我们认真看一下:这段代码没有数学意义,仅仅是为了演示过程,我们看到forword与backword两个过程。

  • forword过程输入input,输出output,而backword过程输入grad_output,输出grad_input。
  • forword输入的参数量(除去ctx)等于backward输出的参数量,forword输出的参数量等于backward输入的参数量(除去ctx)。
  • 通常我们的grad_input是\( \frac{\partial output}{\partial intput} \)与grad_output运算的结果,grad_output由人工输入,或者上一层传递而来,意义其实是不同的样本对与梯度下降的影响程度。
  • 此外ctx本质是一个缓存区,用于正向传播的缓存在反向传播时候使用。

3.3.2 定制的函数

接下来我们尝试写一个超级简单的网络。Y=XW+b,假设X维度(5,10),W维度(10,1),b维度(5,1),Y的维度(5,1)。意思是我们有一个数据集,包含5个样本,每个样本对应一个标签,每个样本10个属性。我们想要用W,b来拟合X的属性与标签Y的关系。forward很简单,backward则需要手工计算梯度。

class Test2Function(Function):
    @staticmethod
    def forward(ctx,input,w,b):
        numpy_input = input.detach().numpy() 
        numpy_w = w.detach().numpy() 
        numpy_b = b.detach().numpy() 
        result = np.dot(numpy_input,numpy_w)+numpy_b
        ctx.save_for_backward(input,w,b)
        return input.new(result)

    @staticmethod
    def backward(ctx, grad_output):
        #此时的grad_output是backword()输入的参数
        input,w,b= ctx.saved_tensors
        #这里我进行了手工求导,具体可在演算纸上进行
        grad_input = np.dot( grad_output.detach().numpy(),w.detach().numpy().T)
        grad_w =  np.dot(input.detach().numpy().T,grad_output.detach().numpy())
        grad_b = grad_output.detach().numpy()
        return t.from_numpy(grad_input),t.from_numpy(grad_w),t.from_numpy(grad_b)


class TestMoudle(nn.Module):
    def __init__(self):
        super(TestMoudle, self).__init__()
        self.w = nn.Parameter(t.ones(10,1))
        self.b= nn.Parameter(t.ones(5, 1))

    def forward(self, input):
        return Test2Function.apply(input, self.w, self.b)

如果是loss函数的定制,在backward过程中直接返回grad_output,或者不写backward,会继承父类的backward。

3.4 需要辨析注意的点

3.4.1 nn.Parameter与Variable的关系

nn.Parameter是Variable的子类,用于在nn.module中设置参数变量。

3.4.2 nn.ModuleList与nn.Sequential()区别

nn.ModuleList和nn.Sequential()都可以在nn.module初始化时候设置网络层,也都可以被有关数据存储的方法,属性所识别,唯一不同的是nn.Sequential()具有__call__()功能,forward过程中直接处理上一层。此外如果在nn.module初始化时用了列表来包含一系列网络层,这些网络层将不会被有关数据存储的方法,属性所识别。

3.4.3 何时使用from torch.nn import functional as F中的F

F通常是无参数的,所以我们不必在nn.module初始化时候声明,而在forward中直接使用。这样写的原因主要是让我们更加方便整理代码。

四 分布式多GPU应用与部署

https://zhuanlan.zhihu.com/p/113694038

五 可视化

5.1 Visdom

之前一直在用的可视化工具,后来发现定制图片需要开会员。现在决定要放弃了。
https://ptorch.com/news/77.html

5.2 Tensorboardx

支持一下tensorboardX
https://blog.youkuaiyun.com/wsp_1138886114/article/details/87602112#TensorBoardX_120

5.3 怎么画网络结构

如果你想实时查看自己的网络模型可以用tensorboardx。
如果想画一个精美的图建议ppt,此外plotneuralnet
https://www.pytorchtutorial.com/plotneuralnet/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值