pytorch定义自己的新层(非官方example)

一、解析层的结构

首先我们通过分析官方的源码了解一下什么是层,它包含哪些结构,成员是啥等。

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
        self.weight = nn.Parameter(torch.Tensor(output_features, input_features))
        if bias:
            self.bias = nn.Parameter(torch.Tensor(output_features))
        else:
            self.register_parameter('bias', None)
        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):
        return LinearFunction.apply(input, self.weight, self.bias)

上面的源码是官方的线性层实现方式。

参数含义:

  • input_features是输入向量长度,output_features是输出向量的长度
  • input调用该类时的输入

Linear层包含两个内部参数,也就是我们说的层的权重,weight和bias。两个函数构造函数__init__和前向传播函数forward。

我们可以得到以下结论:

  • pytorch的层继承自nn.module类
  • 层至少包含两个函数成员__init__和前向传播函数forward(如果自定义的操作不可导,还需要实现反向传播的backward)
  • 如果该层含有权重,那么权重必须是nn.Parameter类型,关于Tensor和Variable(0.3版本之前)与Parameter的区别请参看之前博客。简单说就是Parameter默认需要求导,其他两个类型则不会。
  • 可能的话,为自己定义的新层提供默认的参数初始化,以防使用过程中忘记初始化操作。

二、示例

下面我们实现一个简单的层,输入[x,y],输出为z,实现z=a*x+b*y的功能,并通过网络自动学习到参数a,b。

首先分析一下我们所要实现的功能z=a*x+b*y,其中有两个要学习的参数,a和b。假设输入为一个1*2向量,为了利用pytorch的乘法,我们将a,b合起来定义为[1,2]的向量,类型为Parmeter(所有代权重层中参数的类型)。为了更普适性完成z=\sum w _{i}*x _{i} 的任务,我们将参数的形状设为在定义时指定。

层的定义

##################################################################
####in_features->该层的形状,e.g 参数为a,b,则(1,2);为a,b,c,则(1,3)
####reset_parameters()权重默认初始化函数
####forward自己定义的操作
####input->调用该层时的输入  shape->[n,1,2]
#################################################################


class weight_pool(nn.Module):
    def __init__(self, in_features):            
        super(weight_pool, self).__init__()
        self.in_features = in_features
        self.weight = nn.Parameter(torch.Tensor(self.in_features))
        self.reset_parameters()

    def reset_parameters(self):
        stdv = 1. / math.sqrt(self.weight.size(0))
        self.weight.data.uniform_(-stdv, stdv)
        
    def forward(self, input):
        x = input * self.weight
        x = x.sum(dim=1,keepdim=True)
        return x

此时就完成了我们定义的新层。下面通过几组测试来检验我们的新层是否具有学习功能。

1.task1 学习z = x + y

由于需要学习的参数只有两个,理论上只需要两组数据就能完成学习。但是为了更普适,我们输入了五组训练数据

x,y = [1.0,2.0], [1.0,3.0], [2.0,3.0], [3.0,4.0], [9.0,10.0]
z = [3.0], [4.0], [5.0], [7.0], [19.0]

损失函数用MSELoss,学习率0.01,SGD方法,迭代10个epoch。

网络定义如下:

class MyNet(nn.Module):

    def __init__(self):
        super(MyNet, self).__init__()
        self.wpool = weight_pool((1,2))
    def forward(self, x):
        x = self.wpool(x)
        return x

下面展示前10个epoch的loss曲线图,可以看到2个epoch时网络已经接近收敛。此时学习到的参数为1.0324和0.9731,非常接近我们最理想的参数1和1。

为了测试网络的极限性能,我们直接迭代1000个epoch,可以看到此时学习到的参数就是最理想的参数1和1.

task2  学习z = x + 3*y

同样损失函数用MSELoss,学习率0.01,SGD方法,迭代10个epoch

此时由于目标函数较复杂,10个epoch之后,得到结果仅为1.7896和2.3437,与我们的理想结果1和3相差较远。

同样的,我们将迭代次数增加到1000个epoch,可以看到学习到的结果为1.0077和2.9936,与目标结果1和3基本一致。

总结:

通过上面的例子我们可以证明我们自己写的层具有和pytorch原有的层一样,具有学习能力,可以完成我们的目标。最后再总结一下新实现层的要点:

  • 继承nn.module类
  • 层的权重类型为Parameter
  • 至少实现两个函数__init__和forward
  • 自定义操作如不可导,需要实现backward函数。

完整的工程可在此下载。

猜你喜欢

转载自blog.csdn.net/goodxin_ie/article/details/84680433