博主在学习三值神经网络时,使用了LeNet-5模型,编程代码,需要对LeNet-5模型中的卷积层全连接层进行自定义,搜索他人方法后,博主产生了一个疑问,绝大多数提供的自定义层方法都是继承 nn.Module 模型,而这方法据说是官方提供,自定义线性层代码如下:

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)

博主因涉猎PyTorch时长过短,对此了解不多,初始仅是觉得奇怪,为何自定义层需要继承nn.Module模型,乍一看像是自定义模型而非自定义层

因博主过于小白,当时就想,这么误人子弟的自定义方法不应该笼统地用nn.Module一齐实现,PyTorch是一个成熟的软件,应该知道还有博主这种小小白没办法立刻上手以nn.Module为依赖的自定义层方法(说实话,博主看了几天才看懂上面的代码是啥意思……哭泣……)

所以博主又搜索了GitHub中与已发表论文相关的PyTorch深度学习代码,终于发现了PyTorch自定义层的简单方法,可以细节到区分具体层的种类,而不会产生混淆

以下为博主使用的LeNet-5模型,为直观体现自定义层的方法,博主仅留下了自定义的卷积层与全连接层,即代码中 TernaryConv2d(1, 32, kernel_size=5)TernaryLinear(512, 10) 部分

# LeNet-5模型
class LeNet_5(nn.Module):
    def __init__(self):
        super(LeNet_5, self).__init__()
        self.conv1 = TernaryConv2d(1, 32, kernel_size=5)  # 卷积
        self.fc1 = TernaryLinear(512, 10)  # 全连接

    def forward(self, x):
        x = self.conv1(x)
        x = self.fc1(x)
        return x

以下为博主自定义的卷积层(TernaryConv2d)与全连接层(TernaryLinear)

class TernaryConv2d(nn.Conv2d):
    # def __init__(self, *args, **kwargs):
    # 该方法接受任意个数的参数,其中不指定key的参数会以list形式保存到args变量中,指定key的参数会以dict的形式保存到kwargs变量中
    def __init__(self, *args, **kwargs):
        super(TernaryConv2d, self).__init__(*args, **kwargs)

    def forward(self, input):
        out = F.conv2d(input, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups)
        return out

class TernaryLinear(nn.Linear):
    def __init__(self, *args, **kwargs):
        super(TernaryLinear, self).__init__(*args, **kwargs)

    def forward(self, input):
        out = F.linear(input, self.weight, self.bias)
        return out

以卷积层为例,类名可自行定义,但必须继承 nn.Conv2d ,在该层初始化使用时,若无参数设置,将会默认卷积层的参数,默认参数可查阅官方文档(在 Python API 中)获知,这里博主将其粘贴了过来:

CLASS torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros')

而后,在 def init() 函数中可初始化参数,在 def forward() 函数中可对卷积层自行设计

若想自定义其他类型的层,只需修改继承的 nn.Conv2d 即可,操作非常简单且不会产生歧义

博主将模型与层的代码整合如下,便于复制使用:

class TernaryConv2d(nn.Conv2d):
    # def __init__(self, *args, **kwargs):
    # 该方法接受任意个数的参数,其中不指定key的参数会以list形式保存到args变量中,指定key的参数会以dict的形式保存到kwargs变量中
    def __init__(self, *args, **kwargs):
        super(TernaryConv2d, self).__init__(*args, **kwargs)

    def forward(self, input):
        out = F.conv2d(input, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups)
        return out


class TernaryLinear(nn.Linear):
    def __init__(self, *args, **kwargs):
        super(TernaryLinear, self).__init__(*args, **kwargs)

    def forward(self, input):
        out = F.linear(input, self.weight, self.bias)
        return out


# LeNet-5模型
class LeNet_5(nn.Module):
    def __init__(self):
        super(LeNet_5, self).__init__()
        self.conv1 = TernaryConv2d(1, 32, kernel_size=5)  # 卷积
        self.fc1 = TernaryLinear(512, 10)  # 全连接

    def forward(self, x):
        x = self.conv1(x)
        x = self.fc1(x)
        return x

因官方模型类API过多,博主无法一一查看,博主猜测nn.Module模型中应该与自定义层所使用的nn.Conv2d有着某种继承关系,所以可以如此使用