原帖的 QuestionClassifier 继承 nn.Module,但 init 一开始就给 self.embedding 赋子模块:
AttributeError: cannot assign module before Module.__init__() call
nn.Module 的注册机制要先初始化
把一个 EmbeddingLayer 赋给 self.embedding 时,nn.Module 会登记子模块,因而父类初始化必须已经完成。原题不是缺少 embedding 参数,而是构造函数调用顺序错误。修复这一层后,还需检查 EncoderRNN 等自定义子层是否遵循同样顺序。
nn.Module 需要先初始化内部参数与子模块注册结构。每个自定义模块构造函数都应先调用父类初始化,然后再创建和挂载层。
下面是可以独立运行的最小结构,对应原题的顺序问题:
import torch
from torch import nn
class QuestionClassifier(nn.Module):
def __init__(self, vocab_size, embedding_dim):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embedding_dim)
self.drop = nn.Dropout(0.1)
def forward(self, token_ids):
return self.drop(self.embedding(token_ids))
model = QuestionClassifier(10, 4)
print("josh050608")
print("modules:", list(model._modules))
print("parameters:", [name for name, _ in model.named_parameters()])
assert model(torch.tensor([[1, 2]])).shape == (1, 2, 4)
在原项目中,在 QuestionClassifier.init 的第一段加 super().__init__(),原来的 EmbeddingLayer、EncoderRNN 和 Dropout 保留在其后。也要检查这两个自定义子类是否同样调用了父类初始化。
不要等到子层已经赋值后再补调用,也不要多次调用父类初始化,否则可能清空之前的注册。旧 Python 写法 super(QuestionClassifier, self).__init__() 也表达同一意图。
验证能够构造实例,named_parameters() 能列出 embedding 与编码器参数,再建立优化器和执行前向。仅去掉赋值行让构造不报错,会同时丢掉真正需要训练的层。
不只验证构造,还要确认参数确实进入训练
下面的独立模型保留 embedding → 编码 → 分类的结构,但用简单平均代替原题未提供的 EncoderRNN。它只验证注册和反向,不是原模型的等价实现:
import torch
from torch import nn
class TextClassifier(nn.Module):
def __init__(self, vocab_size, embedding_dim, classes):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embedding_dim)
self.output = nn.Linear(embedding_dim, classes)
def forward(self, token_ids):
return self.output(self.embedding(token_ids).mean(dim=1))
model = TextClassifier(10, 4, 2)
names = dict(model.named_parameters())
assert "embedding.weight" in names and "output.weight" in names
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
optimizer.zero_grad()
logits = model(torch.tensor([[1, 2], [3, 4]], dtype=torch.long))
loss = nn.CrossEntropyLoss()(logits, torch.tensor([0, 1]))
loss.backward()
assert model.embedding.weight.grad is not None
assert model.output.weight.grad is not None
optimizer.step()
print("registered names:", list(names))
不要用直接给 self.__dict__ 填模块或删除 nn.Module 继承来绕开错误,那会破坏参数发现、设备迁移和 state_dict。也不要在已经创建子层后再次调用父类初始化,可能清空已注册的内容。
回到 QuestionClassifier,在 super 调用之后按原来的参数构建 EmbeddingLayer、EncoderRNN 和 Dropout,再检查 named_parameters() 是否覆盖应训练的层。最后做一个短 token 批次的前向和 backward,确认优化器指向当前模型参数,才能说明构造顺序修复没有丢掉网络组件。
参考:原始求助帖;技术依据:nn.Module(2.5)、Embedding(2.5)。