pytorch中的面向对象编程方法

一、__xxx__形式的魔法方法

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

python 复制代码
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设计的一种特殊方法,它别称为魔法方法,它可以被运算符隐式调用,下面给出示例:

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__方法,它能通过以下方式被直接调用:

python 复制代码
test=Accumulator(3)
test.__call__(param)
test(param)
'''两者等价'''

示例如下:

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 __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__两个方法

python 复制代码
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,这是一个包含多个层的类,可以按顺序执行一系列函数

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

python 复制代码
net=nn.Sequential(nn.Linear(20,256),nn.Linear(256,10))
X=torch.arange(20.0)
print(net(X))

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

python 复制代码
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块类添加层,其中第一个参数是层的名称,第二个参数是层的类型。

python 复制代码
net=nn.Sequential(nn.Linear(20,256))
net.add_module("mylayer",nn.Linear(256,10))

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

python 复制代码
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类,可以使用类似数组下标访问的方法来访问其每一层:

python 复制代码
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]中的各个参数:

python 复制代码
net=nn.Sequential(nn.Linear(20,256),nn.Linear(256,10))
print(net[0].state_dict())

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

python 复制代码
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,可以如下进行访问和设置

python 复制代码
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、嵌套结构
python 复制代码
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下带有的方法将参数初始化。

python 复制代码
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_方法可以将参数置为某个常数。

python 复制代码
nn.init.zeros_(m.weight)
nn.init.constant_(m.weight,1.0) #将weight向量全置为1

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

python 复制代码
nn.init.uniform_(m.weight,a=0.0,b=2.0)

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

python 复制代码
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,就可以知晓其运作的规律:

python 复制代码
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)

)

相关推荐
m0_609000426 分钟前
向日葵好用吗?4款稳定的远程控制软件推荐。
运维·服务器·网络·人工智能·远程工作
开MINI的工科男1 小时前
深蓝学院-- 量产自动驾驶中的规划控制算法 小鹏
人工智能·机器学习·自动驾驶
waterHBO1 小时前
python 爬虫 selenium 笔记
爬虫·python·selenium
编程零零七2 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
AI大模型知识分享2 小时前
Prompt最佳实践|如何用参考文本让ChatGPT答案更精准?
人工智能·深度学习·机器学习·chatgpt·prompt·gpt-3
张人玉4 小时前
人工智能——猴子摘香蕉问题
人工智能
草莓屁屁我不吃4 小时前
Siri因ChatGPT-4o升级:我们的个人信息还安全吗?
人工智能·安全·chatgpt·chatgpt-4o
AIAdvocate4 小时前
Pandas_数据结构详解
数据结构·python·pandas
小言从不摸鱼4 小时前
【AI大模型】ChatGPT模型原理介绍(下)
人工智能·python·深度学习·机器学习·自然语言处理·chatgpt
AI科研视界4 小时前
ChatGPT+2:修订初始AI安全性和超级智能假设
人工智能·chatgpt