pytorch中的面向对象编程方法

一、__xxx__形式的魔法方法

我们可以经常在python代码片段中看到类的定义,其中第一个被定义的方法往往是__init__,如下所示:

class Accumulator:  
    """在n个变量上累加"""
    def __init__(self, n):
        self.data = [0.0] * n
    def add(self, *args):
        self.data = [a + float(b) for a, b in zip(self.data, args)]
    def reset(self):
        self.data = [0.0] * len(self.data)
    def __getitem__(self, idx):
        return self.data[idx]

我们知道__init__显然是类的构造函数,但为什么要在前后都加上双下划线呢?

原来这是python设计的一种特殊方法,它别称为魔法方法,它可以被运算符隐式调用,下面给出示例:

import torch
class Accumulator:  #@save
    """在n个变量上累加"""
    def __init__(self, n):
        self.data = [0.0] * n
    def add(self, *args):
        self.data = [a + float(b) for a, b in zip(self.data, args)]
    def reset(self):
        self.data = [0.0] * len(self.data)
    def __getitem__(self, idx):
        return self.data[idx]
    
test=Accumulator(3)
test.add(0,1,2)
print(test.__getitem__(2)) #打印2.0
print(test[2])             #打印2.0

可以发现,所谓魔法方法,实质上实现的就是c++中运算符重载的功能。 它让test.__getitem__(2)和test[2]这两种语法都能调用该方法!

对于更多的python的魔法方法,可以看看下面这一篇文章:

Python 中的 `__xxx__` 特殊方法:介绍与使用-CSDN博客

其中,与pytorch紧密相关的一个方法比较重要,这便是__call__方法,它能通过以下方式被直接调用:

test=Accumulator(3)
test.__call__(param)
test(param)
'''两者等价'''

示例如下:

import torch
class Accumulator:  #@save
    """在n个变量上累加"""
    def __init__(self, n):
        self.data = [0.0] * n
    def add(self, *args):
        self.data = [a + float(b) for a, b in zip(self.data, args)]
    def reset(self):
        self.data = [0.0] * len(self.data)
    def __call__(self, idx):
        return self.data[idx]
    
test=Accumulator(3)
test.add(0,1,2)
print(test.__call__(2))    #打印2.0
print(test(2))             #打印2.0

二、nn.Module类的使用以及其重要的两个类方法

神经网络较为复杂,可以被分为块和层。用类去分别定义神经网络的一个层、一个块会更为清晰。而nn.Module便是pytorch设计者为块类和层类设计的父类,如nn.Linear、nn.Flatten和nn.ReLU这样描述层的类,便是继承自nn.Module类。我们也可以继承nn.Module类,编写我们所需要的层和类。

nn.Module类有一个非常非常重要的特征,那便是其__call__函数里调用了forward方法。所以,在继承nn.Module类时,需要为子类写好forward方法,这样才能按照pytorch的习惯发挥子类作为神经网络中层和类的功能!

例如,我现在写一个类用来描述多层感知机这个块,那么,我需要写好forward和__init__两个方法

import torch
from torch import nn
from torch.nn import functional as func

class MulLayerPerceptron(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden=nn.Linear(20,256)
        self.output=nn.Linear(256,10)

    def forward(self,X):
        return self.output(func.relu(self.hidden(X)))
    
net=MulLayerPerceptron()
X=torch.arange(20.0)
print(net(X))

这样,在实例化这个类的对象net后,就可以直接使用net(X)这个函数调用forward方法!

三、Sequential子类

torch框架本省就提供了一个nn.Module的子类——Sequential,这是一个包含多个层的类,可以按顺序执行一系列函数

如上的多层感知机,我就可以这样定义:

net=nn.Sequential(nn.Linear(20,256),nn.Linear(256,10))
X=torch.arange(20.0)
print(net(X))

事实上,其源码的形式如下所示:

class MySequential(nn.Module):
    def __init__(self,*args):
        super().__init__()
        self.layers=[]
        for layer in args:
            self.layers.append(layer)
    
    def forward(self,X):
        for layer in self.layers:
            X=layer(X)
        return X

net=MySequential(nn.Linear(20,256),nn.Linear(256,10))
X=torch.arange(20.0)
print(net(X))

当然,实际上Sequential类会更加复杂。 

使用对net使用add_module方法可以为为Sequential块类添加层,其中第一个参数是层的名称,第二个参数是层的类型。

net=nn.Sequential(nn.Linear(20,256))
net.add_module("mylayer",nn.Linear(256,10))

直接打印net可以查看整个Sequential块的组成情况:

print(net)

Sequential(
  (0): Linear(in_features=20, out_features=256, bias=True)
  (mylayer): Linear(in_features=256, out_features=10, bias=True)
)

可以看出,Sequential的层默认按照数字命名,我们添加的层是自主命名的!

四、参数访问

1、简单结构

对于Sequential类,可以使用类似数组下标访问的方法来访问其每一层:

net=nn.Sequential(nn.Linear(20,256),nn.Linear(256,10))
X=torch.arange(20.0)
print(net[0])
print(net[1])

Linear(in_features=20, out_features=256, bias=True)
Linear(in_features=256, out_features=10, bias=True)

可以用state_dict方法看看net[X]中的各个参数:

net=nn.Sequential(nn.Linear(20,256),nn.Linear(256,10))
print(net[0].state_dict())

一般,nn.Linear结构中的参数包括weight和bias,即权重和偏置,可以用.运算符访问:

net=nn.Sequential(nn.Linear(20,256),nn.Linear(256,10))
print(net[0].weight)
print(net[0].bias)

 其中,weight和bias都分别包含tensor数组(可用data属性访问)和控制是否求梯度的bool变量requires_grad,可以如下进行访问和设置

net=nn.Sequential(nn.Linear(20,256),nn.Linear(256,10))
print(net[0].weight.data[0])
net[0].weight.requires_grad=True

事实上,net[X]返回了nn库自定义的Parameter类型。 

2、嵌套结构
block=nn.Sequential(nn.Linear(20,256),nn.Linear(256,10))
net=nn.Sequential(block,nn.Linear(10,5))
X=torch.arange(20.0)
print(net(X))
print(net)
print(net[0][0])

 运行结果:

tensor([ 1.9557, -2.0318, -1.2498, -0.1433, -0.3641], grad_fn=<ViewBackward0>)
Sequential(
  (0): Sequential(
    (0): Linear(in_features=20, out_features=256, bias=True)
    (1): Linear(in_features=256, out_features=10, bias=True)
  )
  (1): Linear(in_features=10, out_features=5, bias=True)
)
Linear(in_features=20, out_features=256, bias=True)

如上所示,Sequential类可以进行嵌套。嵌套后,我们使用类似多维数组的方式访问

五、参数初始化

对于如nn.Linear这样的层,可以使用nn.init下带有的方法将参数初始化。

def init_normal(m):
    if type(m)==nn.Linear:
        nn.init.normal_(m.weight,mean=0.0,std=0.01)
        nn.init.zeros_(m.bias)

如代码所示,nn.Linear作为参数m传入,那么调用nn.init.normal_可以使weight向量得以初始化。第一个参数使所需要初始化的向量,mean参数规定平均值,std参数规定方差。

nn.init.zeros_方法用于将参数置于0,nn.init.constant_方法可以将参数置为某个常数。

nn.init.zeros_(m.weight)
nn.init.constant_(m.weight,1.0) #将weight向量全置为1

而nn.init.uniform_方法,可以使参数均匀分布。其第一个参数还是需要初始化的tensor向量,a代表均匀分布的下界(默认为0.0),b代表上界(默认为1.0)

nn.init.uniform_(m.weight,a=0.0,b=2.0)

 

对块net使用apply方法,可以让自定义初始化函数在所有层上作用一遍。

block=nn.Sequential(nn.Linear(20,256),nn.Linear(256,10))
net=nn.Sequential(block,nn.Linear(10,5))
X=torch.arange(20.0)

def init_normal(m):
    if type(m)==nn.Linear:
        print("init")
        nn.init.normal_(m.weight,mean=0.0,std=0.01)
        nn.init.zeros_(m.bias)

net.apply(init_normal)

init

init

init

注意,apply非常智能。纵使Sequential中出现嵌套,也可以层层访问,把所有的nn.Linear结构都进行初始化。

这是因为apply函数会把net的所有结构都访问一遍,打印m,就可以知晓其运作的规律:

block=nn.Sequential(nn.Linear(20,256),nn.Linear(256,10))
net=nn.Sequential(block,nn.Linear(10,5))
X=torch.arange(20.0)

def init_normal(m):
    print(m)
    if type(m)==nn.Linear:
        nn.init.normal_(m.weight,mean=0.0,std=0.01)
        nn.init.zeros_(m.bias)

net.apply(init_normal)

Linear(in_features=20, out_features=256, bias=True)
Linear(in_features=256, out_features=10, bias=True)
Sequential(
  (0): Linear(in_features=20, out_features=256, bias=True)
  (1): Linear(in_features=256, out_features=10, bias=True)
)
Linear(in_features=10, out_features=5, bias=True)
Sequential(
  (0): Sequential(
    (0): Linear(in_features=20, out_features=256, bias=True)
    (1): Linear(in_features=256, out_features=10, bias=True)
  )
  (1): Linear(in_features=10, out_features=5, bias=True)
)

 

  • 29
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值