Pytorch|神经网络工具箱nn

哎…年前就看到了这里,我的水平和这一章之间可能隔了一个太平洋(/TДT)/
torch.nn是专门为深度学习设计的模块,核心数据结构是Module,既可以表示神经网络中的某个层,也可以表示一个包含很多层的神经网络。

4.1 nn.Module

全连接层

我们可以继承nn.Module类来写一个自己的Module,这个例子中我们写的是全连接层y=Wx+b

import torch as t
from torch import nn
from torch.autograd import Variable as V

下面这段代码定义了新的类Linear,把nn.Module放在括号中表示它是Linear的父类。这个类中有两个方法,分别是构造方法和前向传播。(没学过Python的我大概看起来废话连篇吧TvT)
构造方法:
init前后有双下划线,表示这是一种魔法方法,是给解释器读的,会在特殊情况下被调用。
super(Linear,self)._ _init _ _()也可以写作nn.Module. _ _init _ _(self)
必须自己定义科学系的参数,并用nn.Parameter(VariableTensor )封装成Parameter——此后Module能够自动检测到他们,并将其作为学习参数。Parameter是一种特殊的Variable,且默认需要求导。
关于variable:
包含三个属性:data,grad,grad_fn
构造函数有两个参数:reguires_grad,volatile

前向传播: 参数就是神经网络的输入值x。将来把这个类的实例化作为函数使用的时候,会输入一个参数,这个参数就是这里的x。

class Linear(nn.Module):
    def __init__(self,in_features,out_features):
        super(Linear,self).__init__()
        self.w=nn.Parameter(t.randn(in_features,out_features))
        self.b=nn.Parameter(t.randn(out_features))
        
    def forward(self,x):
        x=x.mm(self.w)
        return x+self.b.expand_as(x)

layer是一个实例化的Linear(大概规律就是,类会首字母大写,实例化的类一般小写)。第三行可以看到,我们把继承了nn.Module的某个类实例化之后,可以直接当作函数调用,参数是Variable形式的输入值。
另:
1.这里样本好像是竖着堆叠的。
2.输出也是Variable,书上的输出是有Variable标识的,但是jupyter notebook里似乎tensor和variable的写法没有区别。

layer=Linear(4,3)
input=V(t.randn(2,4))
output=layer(input)
output

在这里插入图片描述
named_parameters()和parameters是继承自nn.Module的方法,返回名字-参数组合或者参数。

for name,parameter in layer.named_parameters():
    print(name,parameter)

在这里插入图片描述

多层感知器

这个单隐层网络包括两个全连接层。
构造函数的参数和Linear类似,也是从第0层开始每一层的节点个数。这次构造函数中定义的不再是可学习参数,而是子Module,等号的右边则是前面定义的Linear类型的层。

class Perceptron(nn.Module):
    def __init__(self,in_features,hidden_features,out_features):
        nn.Module.__init__(self)
        self.layer1=Linear(in_features,hidden_features)
        self.layer2=Linear(hidden_features,out_features)
    def forward(self,x):
        x=self.layer1(x)
        x=t.sigmoid(x)
        return self.layer2(x)

子Module中的Parameter也可以被识别,命名规范如下:
1.Parameter直接命名,self.param_name=nn.Parameter(Variable),则名字为param.name
2.子module中的parameter,self.sub_module=SubModel(),则名字为sub_module.param_name。这其中SubModel是类所以用大写,sub_module是对象所以用小写。

perceptron=Perceptron(3,4,1)
for name,param in perceptron.named_parameters():
    print(name,param.size())

在这里插入图片描述
nn模块中实现了绝大多数layer,继承nn.Module,封装parameter,实现forward函数(比如Linear其实在nn中是自带的),输入不是单个数据而是一个batch(即第一维)。

4.2 常用的神经网络层

4.2.1图像相关层

卷积层
from PIL import Image
from torchvision.transforms import ToTensor,ToPILImage
to_tensor=ToTensor()
to_pil=ToPILImage()
lena=Image.open('lena.jpg')
lena

在这里插入图片描述
下面的nn.Conv2d就是nn模块实现了的一个2d卷积层,参数为输入数据的通道数,输出数据的通道数(过滤器个数),过滤器大小和步长。
仅仅这一行还不能完整地定义一个卷积层,因为具体的过滤器还没有放进来。所以给conv.weight(variable)赋值,四个维度分别是输入数据的通道数,输出数据的通道数(过滤器个数),高,宽。
正如前面out=conv(V(input))这里把Tensor转换成了Variable才调用的这一层。

input=to_tensor(lena).unsqueeze(0)
print(to_tensor(lena).shape)

kernel=t.ones(3,3)/-9
kernel[1][1]=1
conv=nn.Conv2d(1,1,(3,3),1,bias=False)
conv.weight.data=kernel.view(1,1,3,3)


out=conv(V(input))
to_pil(out.data.squeeze())

因为输入的是一个数据,所以要用unsqueeze(0)伪装成batch_size=1的batch,于是变成[1,1,256,256],分别表示batch_size,通道数,高,宽。
在这里插入图片描述

池化层
pool=nn.AvgPool2d(2,2)
list(pool.parameters())

在这里插入图片描述
总结一下,sub_module.parameters()的返回值是一种特殊的generator object Module.parameters类型,可以用遍历或者列表来取出其中的值,这些值是当初用nn.Parameter(Variable)封装成为parameter的。具体代码如下:

print("layer.parameters():",layer.parameters())
print("\n\nlayer.named_parameters():",layer.named_parameters())
print("\n\nfor params in layer.parameters():print(params):")
for params in layer.parameters():
    print(params)
print("\n\nlist(layer.parameters():\n")
print(list(layer.parameters()))

在这里插入图片描述
继续看池化层,用一个构造函数就可以唯一定义池化层了,不像卷积层那样还要描述卷积核。

out=pool(V(input))
to_pil(out.data.squeeze(0))

在这里插入图片描述
size缩小到原来的1/2。

全连接层
input=V(t.randn(2,3))
linear=nn.Linear(3,4)
h=linear(input)
h

在这里插入图片描述

批规范化层

nn.BatchNorm1d()的参数是输入值的特征数,前面的h是2*4矩阵,也就是2个样本4个特征。这个函数是处理Z,下一步紧接着的是激活函数。

bn=nn.BatchNorm1d(4)
bn.weight.data=t.ones(4)
bn.bias.data=t.zeros(4)
bn_out=bn(h)
bn_out.mean(0),bn_out.var(0,unbiased=False)

这里很奇怪,按照书上的说法,标准差为4,方差为16。但是我的输出中方差是1。
在这里插入图片描述
而且一般来说,批规范化w=1,b=0的时候,方差确实应该等于1才对呀?

dropout层
dropout=nn.Dropout(0.5)
o=dropout(bn_out)
o

在这里插入图片描述
就随机保留了一半。训练时每一次迭代都会对每一层w进行一次随机dropout,测试时不会进行这个操作。

4.2.2 激活函数

inplace=True:输出覆盖到输入中,这样可以节省空间。书上的解释是,之所以可以覆盖是因为在计算ReLU的反向传播时,只需根据输出就能够推算出反向传播的梯度。但是要求梯度,每一层的Z值是必须知道的,书上的解释是不是意为只需根据每一层的A就能推出上一层的Z?可是Z<0时A=0恒成立,怎么推断呢?

relu=nn.ReLU(inplace=True)
input=V(t.randn(2,3))
print(input)
output=relu(input)
print(output)

在这里插入图片描述
前馈传播网络有两种简化方式:ModuleList和Sequential

Sequential

这是三个定义Sequential这种特殊Module的方法。前两种分别是先定义再添加和直接初始化,第三种,构造函数的参数是一个OrdedDict,而构造一个OrdedDict的参数是一个包含若干键值对的列表。

net1=nn.Sequential()
net1.add_module("conv",nn.Conv2d(3,3,3))
net1.add_module("batchnorm",nn.BatchNorm2d(3))
net1.add_module("activation_layer",nn.ReLU())
net2=nn.Sequential(
nn.Conv2d(3,3,3),
nn.BatchNorm2d(3),
nn.ReLU())
from collections import OrderedDict
net3=nn.Sequential(OrderedDict([
    ("conv1",nn.Conv2d(3,3,3)),
    ("bn1",nn.BatchNorm2d(3)),
    ("relu1",nn.ReLU())
]))

另:这样是不行的——

net4=nn.Sequential(
    ("conv1",nn.Conv2d(3,3,3)),
    ("bn1",nn.BatchNorm2d(3)),
    ("relu1",nn.ReLU()))

在这里插入图片描述
有名字的用名字引用,没名字的用序号引用。

net1.conv,net2[0],net3.conv1

在这里插入图片描述

ModuleList

构造ModuleList的参数是一个列表,不同于构造Sequential(第二种方法)的tuple。

modellist=nn.ModuleList([nn.Linear(3,4),nn.ReLU(),nn.Linear(4,2)])
input=V(t.randn(1,3))
for model in modellist:
    input=model(input)

ModuleList是Module的子类,在Module中使用它时,就能自动识别为子module。

class MyModule(nn.Module):
    def __init__(self):
        super(MyModule,self).__init__()
        self.list=[nn.Linear(3,4),nn.ReLU()]
        self.mudule_list=nn.ModuleList([nn.Conv2d(3,3,3),nn.ReLU()])
    def forward(self):
        pass
model=MyModule()
model

在这里插入图片描述
在打印这个MyModule的时候只出现了ModuleList。这是我们第一次直接输出一个Module,那么输出一个Module会输出什么呢?

class Perceptron(nn.Module):
    def __init__(self,in_features,hidden_features,out_features):
        nn.Module.__init__(self)
        self.layer1=Linear(in_features,hidden_features)
        self.layer2=Linear(hidden_features,out_features)
    def forward(self,x):
        x=self.layer1(x)
        x=t.sigmoid(x)
        return self.layer2(x)
class Perceptron(nn.Module):
    def __init__(self,in_features,hidden_features,out_features):
        nn.Module.__init__(self)
        self.layer1=Linear(in_features,hidden_features)
        self.layer2=Linear(hidden_features,out_features)
    def forward(self,x):
        x=self.layer1(x)
        x=t.sigmoid(x)
        return self.layer2(x)
perceptron=Perceptron(3,4,1)
print(perceptron)

在这里插入图片描述

net1=nn.Sequential()
net1.add_module("conv",nn.Conv2d(3,3,3))
net1.add_module("batchnorm",nn.BatchNorm2d(3))
net1.add_module("activation_layer",nn.ReLU())
net1

在这里插入图片描述
title是这个Module所属的类,然后括号中是这个Module包含的子Module的名字和本体。可见,在ModuleList这个例子中,model下有一个子Module也就是module_list,而这个module_list又包含两个(没有名字用序号表示)的子Module。
另一个证明ModuleList能被自动识别的证据:

for name,param in model.named_parameters():
    print(name,param.size())

在这里插入图片描述
module.named_parameters()输出的,之前只有两种情况,一是构造函数中用nn.Parameter(variable)封装为Parameter,二是子module中包含的parameter(当等号右边是nn.Module实现的某一个子类如nn.Linear时会自动认为这是一个子Module)。这里就是第二种情况,可见module_list中的module被认为是子module的子module。

4.2.4 损失函数

score=V(t.randn(3,2))
label=V(t.Tensor([1,0,1])).long()

criterion=nn.CrossEntropyLoss()
loss=criterion(score,label)
print("score=",score)
print("label=",label)
print("loss=",loss)

在这里插入图片描述

这里有3个样本那应该就算是成本函数cost而不是损失函数loss了?印象中的交叉熵损失算成本函数大多用于分类问题,对一个样本而言,预测值和实际值都应该是实数。为什么这里实际值是实数,而预测值是有两个特征的1维向量呢?那损失函数是怎么计算的呢?

4.3 优化器

所有的优化方法都是继承nn.optim模块中的Optimizer基类。下面举例随机梯度下降法(SGD)
这里在构造函数中定义了两个子module——self.features和self.classifier。看来用nn.Sequential()定义的Module可以被自动变别为子Module,与之前验证过的nn.ModuleList和其他众多子类一样。

class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.features=nn.Sequential(
                  nn.Conv2d(3,6,5),
                  nn.ReLU(),
                  nn.MaxPool2d(2,2),
                  nn.Conv2d(6,16,5),
                  nn.ReLU(),
                  nn.MaxPool2d(2,2)
        )
        self.classifier=nn.Sequential(
                  nn.Linear(16*5*5,120),
                  nn.ReLU(),
                  nn.Linear(120,84),
                  nn.ReLU(),
                  nn.Linear(84,10)
        )
    def forward(self,x):
            x=self.features(x)
            x=x.view(-1,16*5*5)
            x=self.classifier(x)
            return x
net=Net()

下面这一块optim.SGD就是继承了Optimizer的一种优化方法,参数是要优化的网络的全部参数和学习率。其中参数以net.parameters呈现(),前面说到它的返回值类型很特殊:在这里插入图片描述
在执行的时候,每执行一次output都会发生变化,应该是参数在不断变化的缘故。

from torch import optim
optimizer=optim.SGD(params=net.parameters(),lr=1)
optimizer.zero_grad()

input=V(t.randn(1,3,32,32))
output=net(input)
output.backward(output)
optimizer.step()
print(output)

在这里插入图片描述
总结一下,过程就是

  1. 用封装好的优化方法创建一个optimizer。
  2. 用这个optimizer的zero_grad方法(继承自Optimizer)进行梯度清零。
  3. 前向传播+后向传播
    为什么是output.backward()括号里是output,而不是一个与output大小相同的,元素全为1的variable呢?
  4. 用optimizer的step方法(继承自Optimizer)进行一步的梯度优化。

另外,可以为不同子网络设置不同的学习率。下面的SGD的构造函数,参数是一个个字典和一个默认学习率。如果字典有两个键值对,那就是是参数和学习率;如果只有参数而没有学习率,就使用默认学习率。(下面这段还没有定义special_layers所以实际运行会报错)

optimizer=optim.SGD([{"params":net.features.parameters()},{"params":special_layers.parameters(),"lr":1e-2}],lr=1e-5)

取出子module的子module简单,但是取补集就难。
id()是取地址的函数。
map(function, iterable, …)是将iterable内含元素一个一个地作用在function上,返回值与iterable同类型。
所以special_layers_params就相当于是一个包含特殊层地址的列表。
filter过滤器:filter(function, iterable, …)要求function的返回值是布尔类型,把iterable一个一个作用在function上后,保留是true的元素,所以base_params保留了net.parameters()中地址不属于special_layers_params的那些。

special_layers=nn.ModuleList([net.classifier[0],net.classifier[3]])
special_layers_params=list(map(id,special_layers.parameters()))
base_params=filter(lambda p:id(p) not in special_layers_params,net.parameters())
optimizer=t.optim.SGD([{"params":base_params},{"params":special_layers.parameters(),"lr":0.01}],lr=0.01)

为了调整学习率可以新建优化器

#调整学习率,新建一个optimizer
old_lr=0.1
optimizer=optim.SGD([{"params":net.features.parameters()},{"params":special_layers.parameters(),"lr":old_lr*0.1}],lr=1e-5)

nn.functional

nn中的大多数layer在nn.functional中都有一个与之相对应的函数。
不论是用nn.Module实现的层还是用nn.functional.function(),都先将类实例化,实例化的同时就通过构造函数确定了参数的大小(并且自动初始化参数初值)
如果用nn.Module,下一步是调用这个层,会自动辨别参数;而如果用nn.functional.function(),则在函数参数处输入具体数据和参数们。

input=V(t.randn(2,3))
model=nn.Linear(3,4)
output1=model(input)
output2=nn.functional.linear(input,model.weight,model.bias)
output1==output2

在这里插入图片描述
下面这块就是常见的“对没有可学习参数的激活函数使用nn.functional”,这种情况下函数参数也只有input。而第二行直接一步完成了类的实例化和调用。

b=nn.functional.relu(input)
b2=nn.ReLU()(input)
b==b2

对于没有可学习参数的激活函数或池化层可以用nn.functional,dropout虽然没有可学习参数,但训练和测试阶段行为有差别,所以也用nn.Module。
下面这一段混合了nn.Module和nn.functional两种,前者要先在构造函数中把nn.Layer定义成自己的这个module下面的子module,然后再在forward函数中调用它;后者直接就可以在forward中调用。

from torch.nn import functional as F
class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.conv1=nn.Conv2d(3,6,5)
        self.conv2=nn.Conv2d(6,16,5)
        self.fc1=nn.Linear(16*5*5,120)
        self.fc2=nn.Linear(120,84)
        self.fc3=nn.Linear(84,10)
    def forward(self,x):
        x=F.pool(F.relu(self.conv1(x)),2)
        x=F.pool(F.relu(self.conv2(x)),2)
        x=x.view(-1,16*5*5)
        x=F.relu(self.fc1(x))
        x=F.relu(self.fc2(x))
        x=self.fc3(x)
        return x

如果想用functional实现有可学习参数的层,就要在构造函数中手动实现,比如:

class MyLinear(nn.Module):
    def __init__(self):
        super(Mylinear,self).__init__()
        self.weight=nn.Parameter(t.randn(3,4))
        self.bias=nn.Parameter(t.zeros(3))
    def forward(self):
        return F.linear(input,weight,bias)

4.5 初始化策略

from torch.nn import init
linear=nn.Linear(3,4)

t.manual_seed(1)
init.xavier_normal_(linear.weight)

在这里插入图片描述

import math
t.manual_seed(1)
std=math.sqrt(2)/math.sqrt(7.)
linear.weight.data.normal_(0,std)

在这里插入图片描述
init.xavier_normal_(linear.weight)和linear.weight.data.normal_(0,std)效果是一样的,显然后者.normal_(0,std)之前的linear.weight.data是一个Tensor,那么linear.weight是什么类型的呢?
在这里插入图片描述
是一个特别的类,这是nn.Module封装好的层,如果是我们自己定义的层,其中手动封装了parameter,它们的类型也是这个。

class Linear(nn.Module):
    def __init__(self,in_features,out_features):
        super(Linear,self).__init__()
        self.w=nn.Parameter(t.randn(in_features,out_features))
        self.b=nn.Parameter(t.randn(out_features))
        
    def forward(self,x):
        x=x.mm(self.w)
        return x+self.b.expand_as(x)
layer=Linear(4,3)
print(type(layer.w))

在这里插入图片描述
(所以既然是这个类,为什么构造函数中可以写作self.w=nn.Parameter(xxx)而不是self.w=nn.parameter.Parameter(xxx)呢?

再回过头来看这两种初始化的实现方法吧,tensor.normal_(mean,std)应该是将这个tensor的数据都归一化为固定均值和方差的数,我随便代入了一个tensor不过报错了。

m=t.arange(0,24).view(4,6)
m.normal_(0,1)

在这里插入图片描述
下面这一段不太完整的代码展示了如何全面地初始化一个模型,name是字符串,有name.find()方法

for name, params in net.named_parameters():
    if name.find("linear")!=-1:
        params[0] #weight
        params[1] #bias
    elif name.find("conv")!=-1:
        pass
    elif name.find("norm")!=-1:
        pass

4.6 nn.Module深入分析

下面是nn.Module基类的构造函数的源代码
函数前加一根下划线,表示这是一个被保护的变量,约定上不能从外部访问。
OrderedDict,之前我们在创建Sequential的时候遇到过这个类。

def ___init__(self):
    self._parameters=OrderedDict()
    self._modules=OrderedDict()
    self._buffers=OrderedDict()
    self._backward=OrderedDict()
    self._forward_hooks=OrderedDict()
    self.training=OrderedDict()

_parameters:字典。self.param1=mm.Parameter(variable)会被检测到,在该字典中增加一个key为param,value为parameter的item。但是子module的pamareter就不会被检测到。
_modules:子module
_buffers:缓存。如batchnorm使用momentum机制,每次前向传播需用到上次前向传播的结果。
我一直以为momentum的引入是为了在不减小学习率的情况下降低mini_batch引起的震荡,而和batchnorm没有什么关系…为什么说batchnorm使用momentum机制呢?
_backward_hooks与_forward_hooks:钩子技术,用来提取中间变量,类似variable的hook。
_training:BatchNorm层和Dropout层都是训练阶段使用,测试阶段不使用。
其中前三者都有
self._parameters[‘key’]=self.key
self._modules[‘key’]=self.key
self._buffers[‘key’]=self.key

所以,还是以我们最开始定义的全连接层为例:

class Linear(nn.Module):
    def __init__(self,in_features,out_features):
        super(Linear,self).__init__()
        self.w=nn.Parameter(t.randn(in_features,out_features))
        self.b=nn.Parameter(t.randn(out_features))
        
    def forward(self,x):
        x=x.mm(self.w)
        return x+self.b.expand_as(x)

self.w=nn.Parameter(t.randn(in_features,out_features))这句在nn._parameters中增加了{“w”:new_parameter(随便取的名字)},然后,在forward函数中,就可以用self.w代替self._parameters[“w”]来取出这个new_parameter。
下例中定义了参数和子module,直接输出网络的时候,title是这个Module所属的类,括号中是这个Module包含的子Module的名字和本体。

class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        #等价于self.register_parameter("param1",nn.Parameter(t.randn(3,3)))
        self.param1=nn.Parameter(t.rand(3,3))
        self.submodel1=nn.Linear(3,4)
    def forward(self,input):
        x=self.param1.mm(input)
        x=self.submodel1(x)
        return x
net=Net()
net

在这里插入图片描述
如果换一种表述方法,单独取网络中的._modules字典,tytle就变成了OrderedDict,括号内仍是子module名字和本体。

net._modules

在这里插入图片描述
因为 self.submodel1=nn.Linear(3,4),所以很自然地本体就是Linear(in_features=3, out_features=4, bias=True)),而定义为self.param1=nn.Parameter(t.rand(3,3))的param1,它的本体是这样的:

net._parameters

在这里插入图片描述

net.param1

在这里插入图片描述
net.named_parameters()的返回值是所有参数和子module中参数的名字和本体,所以它的范围比net._parameters()中的键值对更广。

for name,param in net.named_parameters():
    print(name,param.size())

在这里插入图片描述
如果是在named_modules中还包含着主module,不过没有名字。

for name,submodel in net.named_modules():
    print(name,submodel)

在这里插入图片描述
下面这一段我一直太理解

bn=nn.BatchNorm1d(2)
input=V(t.rand(3,2),requires_grad=True)
output=bn(input)
bn._buffers

在这里插入图片描述
为了方便地访问层层嵌套的module,可以用函数children访问直接子module,或者用函数modules访问所有子module(包括自己)。之前的net.named_modules()就是例子。

input=V(t.arange(0,12).view(3,4))
model=nn.Dropout()
model(input.float())

在这里插入图片描述
下图中通过直接设置training属性将子module设为eval模式,反之是train模式。如果有多个dropout层,则需要为每个dropout层制定training属性。

model.training=False
model(input)

在这里插入图片描述
还有一种方法是调用model.train()&model.eval()函数,把当前module和子module中的所有training属性都设置好。

print(net.training,net.submodel1.training)
net.eval()
net.training,net.submodel1.training

在这里插入图片描述
之前我们有遍历net.named_modules()中的每一对name和sub_module并输出,现在把它们按照列表输出。

list(net.named_modules())

在这里插入图片描述

variable的hook

反向传播过程中,非叶子节点的导数计算完就被清空,为了查看这些变量的导数,可以用hook函数。
下面这一段代码:tensor.register_hook()是属于torch.Tensor类的一个函数,它的参数是另一个函数,称为钩子函数。钩子函数的返回值是Tensor/None。register_hook()的作用就是,当执行z.backward时,这个钩子函数variable_hook就会执行,参数函数的参数固定是tensor.grad。这个例子中就执行了variable_hook,从而打印了y的梯度。
要给y.register_hook起一个别名以便于移除。

def variable_hook(grad):
    print("y的梯度: \r\n",grad)
x=V(t.ones(3),requires_grad=True)
w=V(t.rand(3),requires_grad=True)
y=x*w
#注册hook
hook_handle=y.register_hook(variable_hook)
z=y.sum()
z.backward()
#除非你每次都要用hook,否则用完之后记得移除hook
hook_handle.remove()

在这里插入图片描述

nn工具箱中的hook

对比一下,variable中,调用register_hook的是Tensor。hook函数是hook(grad)->Tensor or None
在这里,调用register_xxx_hook的是Module,前向传播的hook函数是hook(module,input,output)->None,后向传播的hook函数是hook(module,grad_input,grad_output)->Tensor or None。

model=VGG()
features=t.Tensor()
def hook(module,input,output):
    #把这层的输出复制到feature中
    features.copy_(output.data)
handle=model.layer8.register_forward_hook(hook)
_=model(input)
handle.remove()
getattr setattr

python中有两个常用的builtin方法(builtin:一个模块):
getattr:getattr(obj,“attr1”)相当于obj.attr1,如果找不到这个属性就调用obj._ _ getattr _ _ (“attr1”)这个魔法方法。
setattr:setattr(obj,“name”,value)等价于obj.name=value.如果obj对象实现了_ _ setattr _ _ 方法,就直接调用obj. _ _ setattr _ _(“name”,value),否则就调用builtin函数setattr(obj,“name”,value)。

nn.Module实现了自定义的_ _ setattr _ _ 函数,也就是说,执行module.name=value时,会调用 module. _ _ setattr _ _ (“name”,value)。在这个自定义的_ _ setattr _ _ 函数中,会判断value是否为Parameter或nn.Module对象,如果是则加到_ parameters和_modules两个字典中,否则保存在 _ _ dict _ _中。

module=nn.Module()
module.param=nn.Parameter(t.ones(2,2))
module._parameters

在这里插入图片描述
module_list属于list类,所以被放到_ _ dict _ _ 中。

submodule1=nn.Linear(2,2)
submodule2=nn.Linear(2,2)
module_list=[submodule1,submodule2]
module.submodules=module_list
print("_modules:",module._modules)
print("__dict__['submodules']:",module.__dict__.get('submodules'))

在这里插入图片描述

module_list=nn.ModuleList(module_list)
module.submodules=module_list
print("ModuleList is instance of nn.Module:",isinstance(module_list,nn.Module))
print("_modules:",module._modules)
print("__dict__['submodules']:",module.__dict__.get('submodules'))

在这里插入图片描述
因_modules和_ parameters中的item未保存在 _ _ dict _ _ 中,所以默认的getattr方法无法获取它,因而如果默认的getattr方法无法获取,就调用自定义的__getattr__方法,尝试从_modules,_ parameters, _ buffers这三个字典中获取。

getattr(module,"training")
#error
#module.__getattr__("training")

在这里插入图片描述
对上面的例子我有疑问,代码的意思是默认的getattr方法无法获取它,所以调用了自定义的 _ _ getattr _ _方法,但是module.training是bool值,既不是Parameter也不是nn.Module对象,所以应该保存在 _ _ dict _ _ 中,所以用默认的getattr方法应该可以获取才对?
包括下面的例子也是一样的问题

module.attr1=2
getattr(module,"attr1")
#报错
#module.__getattr__("attr1")

在这里插入图片描述
下面这个倒是比较可以理解,因为param是Parameter类的。

#即module.param,会调用module.__getattr__("param")
getattr(module,"param")

在这里插入图片描述

t.save(net.state_dict(),"net.pth")
net2=Net()
net2.load_state_dict(t.load("net.pth"))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值