为什么我的模型能跑,但效果很差?(这个效果差指的是模型的结果接近于random)
或许你和我一样错误的处理了数据的 shape。
这里以 RNN 为例,但这个问题是普适的。
事情的起因源于做李宏毅机器学习 HW02 的 Boss baseline,最后的步骤是转向使用 RNN,而 sample code 中的代码并不是 RNN 形式的,所以需要修改 Module 类和修改输入以适配模型。
这里是一段错误样例代码(这里 reshape 可以换成 view),简单列一下以便后面讲解:
class Classifier(nn.Module):
def __init__(self, input_dim, output_dim=41, hidden_layers=1, hidden_dim=256):
super(Classifier, self).__init__()
self.input_size = 39 # 这一项是RNN的"input_dim",RNN需要对"单"个数据进行处理
self.hidden_size = 512 # 这一项是RNN的"hidden_dim"
self.num_layers = 3 # 这一项是RNN的"hidden_layers"
# 创建一个RNN层,输入维度为39,隐藏状态维度为512,层数为3,input.shape=(seq_len, batch_size, input_size)
self.rnn = nn.RNN(input_size=self.input_size, hidden_size=self.hidden_size, num_layers=self.num_layers)
# 后接一层全连接的神经网络
self.fc = nn.Linear(self.hidden_size, output_dim)
def forward(self, x):
# 通过RNN层,得到输出和最后一个隐藏状态
# x.shape = (seq_len, batch_size, RNN_input_size)
x, _ = self.rnn(x) # => (seq_len, batch_size, RNN_hidden_size)
# 取最后一个时间步的输出作为分类的输入
x = x[-1] # => (batch_size, RNN_hidden_size)
# 通过线性层,得到最终的分类结果
x = self.fc(x) # =>(batch_size, labels)
return x
...
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)
for i, batch in enumerate(tqdm(train_loader)):
features, labels = batch # features.shape: (batch_size, seq_len * input_size)
features = features.reshape(seq_len, batch_size, input_size).to(device) # 这里是错误的,正确处理顺序应当为:(batch_size, seq_len, input_size),更正确的处理是 (-1, seq_len, input_size),-1 表示自动计算该维度,因为最后可能不够一个 batch_size,会报错。
...
outputs = model(features) # outputs.shape: (batch_size, labels)
loss = criterion(outputs, labels)
...
dataloader
返回的数据 shape
是 (batch_size, input_dim)
。
所以应该修改成 features = features.reshape(batch_size, seq_len, input_size).to(device)
。
如果你和我一开始一样:因为知道 rnn
的 input.shape=(seq_len, batch_size, input_size)
,所以懒得使用 batch_first=True
,但不熟悉 dataloader
具体返回的数据 shape
。
那么,你很有可能会使用 features = features.reshape(seq_len, batch_size, input_size).to(device)
,这样可以训练,但效果奇差,而且:) 不会报错。
因为 features.shape = (batch_size, input_dim)
,而你选择了将它 reshape
为 (seq_len, batch_size, input_size)
。
你需要格外注意上面这点,必须正确处理 feature
的 reshape
,可以不用设置 batch_first=True
,因为这样顶多就需要把 self.rnn(x)
改成 self.rnn(x.permute(1, 0, 2))
。
下面是正确处理的代码:
class Classifier(nn.Module):
def __init__(self, input_dim, output_dim=41, hidden_layers=1, hidden_dim=256):
super(Classifier, self).__init__()
self.input_size = 39 # 这一项是RNN的"input_dim",RNN需要对"单"个数据进行处理
self.hidden_size = 512 # 这一项是RNN的"hidden_dim"
self.num_layers = 3 # 这一项是RNN的"hidden_layers"
# 创建一个RNN层,输入维度为39,隐藏状态维度为1024,层数为3,input.shape=(seq_len, batch_size, input_size)
self.rnn = nn.RNN(input_size=self.input_size, hidden_size=self.hidden_size, num_layers=self.num_layers)
# 后接一层全连接的神经网络
self.fc = nn.Linear(self.hidden_size, output_dim)
def forward(self, x):
# 通过RNN层,得到输出和最后一个隐藏状态
# x.shape = (batch_size, seq_len, RNN_input_size)
x, _ = self.rnn(x.permute(1, 0, 2)) # => (seq_len, batch_size, RNN_hidden_size)
# 取最后一个时间步的输出作为分类的输入
x = x[-1] # => (batch_size, RNN_hidden_size)
# 通过线性层,得到最终的分类结果
x = self.fc(x) # =>(batch_size, labels)
return x
...
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)
for i, batch in enumerate(tqdm(train_loader)):
features, labels = batch # features.shape: (batch_size, seq_len * input_size)
features = features.reshape(-1, seq_len, input_size)
...
outputs = model(features) # outputs.shape: (batch_size, labels)
loss = criterion(outputs, labels)
...
PyTorch
中 rnn()/lstm()...
有一个参数是 batch_first
,这个参数如果设置成 True
,那么意味着 input
的 shape
从 (seq_len, batch_size, input_size) => (batch_size, seq_len, input_size)
,一开始我并不理解为什么有这个参数,直到我发现了 features.shape = (batch_size, input_dim)
,设置成 True
能少去些弯绕。
P.S. 当 batch_first = True
时,如果要取最后一个状态,forward()
中的代码需要从 x = x[-1]
改成 x = x[:, -1]
。