首页人工智能Pytorch【深度学习(PyTorch...

【深度学习(PyTorch篇)】44.nn.Module中的__getattr__ 和 __setattr__ 方法

本系列文章配套代码获取有以下两种途径:

  • 通过百度网盘获取:
链接:https://pan.baidu.com/s/1XuxKa9_G00NznvSK0cr5qw?pwd=mnsj 提取码:mnsj
  • 前往GitHub获取
https://github.com/returu/PyTorch





Python中,getattrsetattr是两个非常有用的内置函数,用于动态地获取和设置对象的属性
  • getattr

getattr 函数用于获取对象属性的值。其基本语法如下:
# 等价于object.name
getattr(objectname[, default])
如果,getattr方法无法找到所需的属性,那么Python会调用object.__getattr__(‘name’)方法。如果这个对象没有实现__getattr__方法或者遇到__getattr__方法无法处理的情况,那么程序就会抛出AttributeError异常。
  • setattr

setattr 函数用于设置对象的属性值。其基本语法如下:
# 等价于object.name = value
setattr(objectname, value)
如果,object对象实现了__setattr__方法,那么setattr会直接调用object.__setattr__(‘name’ , value),否则调用buildin方法。
PyTorch中,torch.nn.Module 类重写了类似于Python内置的 __getattr__ 和 __setattr__ 的魔法方法,但其实现细节略有不同,主要是为了更好地适应神经网络模块的管理和使用。这些方法在nn.Module中扮演着关键角色,特别是在处理子模块的注册和访问时。
这也是为什么上一篇文章中的nn.Module对象在构造函数中的行为看起来有些怪异的原因。
下面首先实现一个自定义网络用于后续演示:
class CustomLinearLayer(nn.Module):  
    def __init__(self, input_features, output_features):  
        super().__init__()  

        self.name = 'CustomLinearLayer'
        # nn.Parameter()内的参数是网络中可学习的参数
        self.weight = nn.Parameter(torch.Tensor(output_features, input_features))  
        self.bias = nn.Parameter(torch.Tensor(output_features))  

        self.submodel_1 = nn.Linear(5,4)
        self.submodel_2 = nn.Linear(4,2)
        self.submodel_3 = nn.Sequential(nn.Linear(2,3),nn.Linear(3,5))

    def forward(self, x):  
        out = torch.mm(x, self.weight.t()) + self.bias  
        out = self.submodel_1(out)
        out = self.submodel_2(out)
        out = self.submodel_3(out)

        return out  

# 实例化网络
custom_linear = CustomLinearLayer(input_features=10, output_features=5)  

01

__getattr__


在标准的 Python 类中,__getattr__ 方法是在试图访问一个对象的属性,而该属性并没有在 __dict__ 中找到时被调用。在 torch.nn.Module 中,__getattr__ 方法被扩展以支持对 _parameters, _buffers 和 _modules 字典的访问,这使得可以通过点运算符访问这些组件,而不需要显式地使用字典语法。
可以通过以下语法获取nn.Module实例对象的属性字典,其中包含该对象的所有属性和它们的值。这些属性通常是在类的__init__方法中定义的实例变量。
custom_linear.__dict__
例如,假设你有一个 Linear 层,你可以像这样访问它的权重:
linear = nn.Linear(10, 5)
print(linear.weight)
这里的 linear.weight 实际上调用了 linear.__getattr__(‘weight’),然后 __getattr__ 方法在 _parameters 字典中查找 weight 键并返回相应的张量。
  • Parameter对象:

在上述自定义网络示例中,self.bias 是通过 nn.Parameter 定义的,这使得它成为了 nn.Module 的一部分。在 nn.Module 的内部,所有通过 nn.Parameter 创建的属性都会被添加到 _parameters 字典中。这意味着 self.bias 不仅可以通过直接访问和 getattr 函数得到,也可以通过__getattr__ 方法得到。
# 以下三种方式均可
custom_linear.bias
getattr(custom_linear , 'bias')
custom_linear.__getattr__('bias')
但是,对于像name 这样普通属性是直接在 __dict__ 中定义的。因此,尝试通过__getattr__访问name 会引发 AttributeError,因为__getattr__试图在 _parameters_buffers, 和 _modules 中查找 name,但它并不在那里。
# 以下两种方式均可
custom_linear.name
getattr(custom_linear , 'name')
# 该方式会报错:AttributeError
custom_linear.__getattr__('name')
另外,对于_parameters来说,它是 torch.nn.Module 类中的一个实例变量,存储了所有可学习参数由于 _parameters 直接在 __dict__ 中定义,因此当访问 custom_linear._parameters 时,Python 解释器会立即在 custom_linear 的 __dict__ 中找到 _parameters,并返回相应的字典对象。这一过程完全绕过了 __getattr__ 方法,因为 _parameters 已经作为一个直接属性存在,不需要 __getattr__ 的动态查找机制。因此,__getattr__ 方法抛出 AttributeError 异常信息。
# 以下两种方式均可
custom_linear._parameters
getattr(custom_linear , '_parameters')
# 该方式会报错:AttributeError
custom_linear.__getattr__('_parameters')

  • nn.Module对象:

Parameter对象类似,可以用过以下三种方式访问子module

# 以下三种方式均可
custom_linear.submodel_3
getattr(custom_linear , 'submodel_3')
custom_linear.__getattr__('submodel_3')

对于_modules 来说,它是 torch.nn.Module 类中的一个实例变量,存储了所有子模块的信息。并且它被存储在 __dict__ 中。当尝试访问 _modules 时,会直接从 custom_linear 实例的 __dict__ 中找到 _modules,因此不会触发 __getattr__ 方法的执行而导致错误。

# 以下两种方式均可
custom_linear._modules
getattr(custom_linear , '_modules')
# 该方式会报错:AttributeError
custom_linear.__getattr__('_modules')


  • train属性:

training 属性用于指示模型是否处于训练模式,这会影响某些层的行为,比如 Dropout 和 BatchNorm
该属性是通过 torch.nn.Module 的 is_training 方法间接管理的,但直接在模块的 __dict__ 中有其对应的布尔值。因此,当你直接尝试通过 __getattr__ 方法访问 training 属性时,__getattr__ 方法会尝试在 _parameters_buffers, 和 _modules 中查找这个属性,但显然 training 并不在这些字典中。因此,__getattr__ 方法无法找到 training 属性,这通常会导致一个 AttributeError 异常,提示 training 属性没有找到。
正确的访问方式应该是直接使用 custom_linear.training,因为 training 属性是直接在 __dict__ 中定义的,或者通过 torch.nn.Module 的 train() 和 eval() 方法来切换模型的训练状态,而不是通过 __getattr__ 方法来访问。
# 以下两种方式均可
custom_linear.training , custom_linear.submodel_1.training # (True, True)
custom_linear.eval()
getattr(custom_linear , 'training') , getattr(custom_linear.submodel_1 , # (False, False)'training')
# 该方式会报错:AttributeError
custom_linear.__getattr__('training')


02

__setattr__


__setattr__ 方法在设置对象的属性时被调用。在 torch.nn.Module 中,__setattr__ 方法同样被重写以支持参数、缓冲区和子模块的正确管理。当你添加一个新的子模块到 Module 实例中,例如:

class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        self.linear = nn.Linear(105)

在 MyModule 的构造函数中,self.linear = nn.Linear(10, 5) 调用了 __setattr__ 方法。这个方法不仅将 linear 添加到了 __dict__ 中,还将其添加到了 _modules 字典中,以便于后续的访问和序列化。

  • Parameter对象:

可以使用 __setattr__() 方法来重新设置参数属性值,例如,将bias赋值为全1张量。这里直接使用 custom_linear.bias = … 就可以,因为这也会调用 __setattr__() 方法。
custom_linear.bias = nn.Parameter(torch.ones(5).float())
custom_linear.bias
# 输出结果:
# Parameter containing:
# tensor([1., 1., 1., 1., 1.], requires_grad=True)

  • nn.Module对象:

可以使用 __setattr__() 方法来动态地修改模型结构,需要注意的是,这种方式通常是不推荐的,因为它可能导致不可预测的行为,特别是在使用优化器和调度器时。然而,在某些特定场景下,如微调模型、添加适配器层或进行特定的模型修改时,这可能是有用的。

class DynamicModel(nn.Module):  
    def __init__(self):  
        super(DynamicModel, self).__init__()  
        self.conv1 = nn.Conv2d(364, kernel_size=3, stride=1, padding=1)  

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

# 创建模型实例  
model = DynamicModel()  

# 使用setattr()动态添加一个线性层  
new_layer = nn.Linear(64*14*1410)  
setattr(model, 'fc_layer', new_layer) 

# 直接替换 forward 方法 
def updated_forward(self, x):  
    x = self.conv1(x)  
    x = x.view(x.size(0), -1)  # 展平操作
    x = self.fc_layer(x)  # 使用动态添加的线性层  
    return x  

# 更新模型的前向传播方法以使用新的线性层  
model.forward = updated_forward.__get__(model, DynamicModel) 

# 现在可以使用更新后的模型进行前向传播  
input_tensor = torch.rand(1,3,14,14)
output = model(input_tensor)
output.shape
# 输出结果:torch.Size([1, 10])
上述示例代码中,直接替换 forward 方法是一种比较激进的做法,通常更建议通过继承或组合来扩展模型的功能,而不是直接修改其方法。在实际应用中需谨慎使用。
如果只是想动态地添加一些非参数属性(例如,配置信息、统计数据等),那么使用 setattr() 是完全合适的,并且不会影响到模型的训练过程。


更多内容可以前往官网查看

https://pytorch.org/


本篇文章来源于微信公众号: 码农设计师

RELATED ARTICLES

欢迎留下您的宝贵建议

Please enter your comment!
Please enter your name here

- Advertisment -

Most Popular

Recent Comments