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)

)

相关推荐
IT古董5 分钟前
【深度学习】常见模型-Transformer模型
人工智能·深度学习·transformer
沐雪架构师1 小时前
AI大模型开发原理篇-2:语言模型雏形之词袋模型
人工智能·语言模型·自然语言处理
python算法(魔法师版)2 小时前
深度学习深度解析:从基础到前沿
人工智能·深度学习
小王子10242 小时前
设计模式Python版 组合模式
python·设计模式·组合模式
kakaZhui2 小时前
【llm对话系统】大模型源码分析之 LLaMA 位置编码 RoPE
人工智能·深度学习·chatgpt·aigc·llama
struggle20253 小时前
一个开源 GenBI AI 本地代理(确保本地数据安全),使数据驱动型团队能够与其数据进行互动,生成文本到 SQL、图表、电子表格、报告和 BI
人工智能·深度学习·目标检测·语言模型·自然语言处理·数据挖掘·集成学习
佛州小李哥3 小时前
通过亚马逊云科技Bedrock打造自定义AI智能体Agent(上)
人工智能·科技·ai·语言模型·云计算·aws·亚马逊云科技
Mason Lin3 小时前
2025年1月22日(网络编程 udp)
网络·python·udp
清弦墨客4 小时前
【蓝桥杯】43697.机器人塔
python·蓝桥杯·程序算法
云空4 小时前
《DeepSeek 网页/API 性能异常(DeepSeek Web/API Degraded Performance):网络安全日志》
运维·人工智能·web安全·网络安全·开源·网络攻击模型·安全威胁分析