我遵循了关于使用 RNN 生成句子的教程,并且尝试修改它以生成位置序列,但是我在定义正确的模型参数(例如 input_size、output_size、hidden_dim、batch_size)时遇到了麻烦。
背景:
我有 596 个 x,y 位置序列,每个序列看起来像 [[x1,y1],[x2,y2],...,[xn,yn]]。每个序列代表车辆的 2D 路径。我想训练一个模型,给定一个起点(或部分序列),可以生成这些序列之一。
-我已填充/截断序列,以便它们的长度均为 50,这意味着每个序列是形状为 [50,2]
的数组 - 然后我将此数据分为 input_seq 和 target_seq:
input_seq: torch.Size([596, 49, 2]) 的张量。包含所有 596 个序列,每个序列没有最后一个位置。
target_seq:torch.Size([596, 49, 2]) 的张量。包含所有 596 个序列,每个序列没有第一个位置。
模型类:
class Model(nn.Module):
def __init__(self, input_size, output_size, hidden_dim, n_layers):
super(Model, self).__init__()
# Defining some parameters
self.hidden_dim = hidden_dim
self.n_layers = n_layers
#Defining the layers
# RNN Layer
self.rnn = nn.RNN(input_size, hidden_dim, n_layers, batch_first=True)
# Fully connected layer
self.fc = nn.Linear(hidden_dim, output_size)
def forward(self, x):
batch_size = x.size(0)
# Initializing hidden state for first input using method defined below
hidden = self.init_hidden(batch_size)
# Passing in the input and hidden state into the model and obtaining outputs
out, hidden = self.rnn(x, hidden)
# Reshaping the outputs such that it can be fit into the fully connected layer
out = out.contiguous().view(-1, self.hidden_dim)
out = self.fc(out)
return out, hidden
def init_hidden(self, batch_size):
# This method generates the first hidden state of zeros which we'll use in the forward pass
# We'll send the tensor holding the hidden state to the device we specified earlier as well
hidden = torch.zeros(self.n_layers, batch_size, self.hidden_dim)
return hidden
我使用以下参数实例化模型:
input_size 为 2([x,y] 位置)
output_size 2([x,y] 位置)
hidden_dim 2([x,y] 位置)(或者这应该是 50,如完整序列的长度?)
model = Model(input_size=2, output_size=2, hidden_dim=2, n_layers=1)
n_epochs = 100
lr=0.01
# Define Loss, Optimizer
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
# Training Run
for epoch in range(1, n_epochs + 1):
optimizer.zero_grad() # Clears existing gradients from previous epoch
output, hidden = model(input_seq)
loss = criterion(output, target_seq.view(-1).long())
loss.backward() # Does backpropagation and calculates gradients
optimizer.step() # Updates the weights accordingly
if epoch%10 == 0:
print('Epoch: {}/{}.............'.format(epoch, n_epochs), end=' ')
print("Loss: {:.4f}".format(loss.item()))
当我运行训练时循环,它因以下错误而失败:
ValueError Traceback (most recent call last)
<ipython-input-9-ad1575e0914b> in <module>
3 optimizer.zero_grad() # Clears existing gradients from previous epoch
4 output, hidden = model(input_seq)
----> 5 loss = criterion(output, target_seq.view(-1).long())
6 loss.backward() # Does backpropagation and calculates gradients
7 optimizer.step() # Updates the weights accordingly
...
ValueError: Expected input batch_size (29204) to match target batch_size (58408).
我尝试修改 input_size、output_size、hidden_dim 和 batch_size 并重塑张量,但我尝试的越多,我就越困惑。有人能指出我做错了什么吗?
此外,由于批次大小在 Model.forward(self,x) 中定义为 x.size(0),这意味着我只有一个批次大小为 596,对吗?拥有多个小批量的正确方法是什么?
让我们来分解一下你的 PyTorch RNN 模型中的维度问题,并澄清如何使用批处理。
理解维度
-
input_size
: 这应该是每个时间步的输入向量的大小。在你描述为[x, y]
位置的场景中,input_size
应该是 2 。 -
hidden_size
: 这控制着 RNN 隐藏状态的维度,并且是一个可以根据你模型的复杂性和你想要捕获的序列中依赖关系的数量进行调整的超参数。 它不直接对应于序列长度或坐标数量。 更大的hidden_size
值通常可以表示更复杂的模式,但也需要更多的计算资源。 -
output_size
: 这应该是每个时间步的期望输出向量的大小。由于你想预测一个[x, y]
位置,output_size
也应该是 2 。 -
batch_size
: 这表示在一次迭代(或“批处理”)中处理的序列数。虽然你可以在单个批处理中处理所有 596 个序列,但这对于内存来说可能太多了。将你的数据分成更小的批处理(例如,32 或 64)通常更有利于训练,并允许模型更快地更新。
代码中的错误
你代码中的主要错误在于你如何计算损失并处理目标的形状:
-
CrossEntropyLoss
用于分类: 你使用的是nn.CrossEntropyLoss
,它是为分类问题设计的,其中输出代表不同类别的概率。对于回归任务(例如预测[x, y]
坐标),你应该使用nn.MSELoss
(均方误差)或nn.L1Loss
(平均绝对误差)。 -
目标形状不匹配:
CrossEntropyLoss
希望目标为每个时间步的类索引(整数)。你正试图提供形状为[596, 49, 2]
的浮点目标,这会导致不匹配。
修改后的代码和解释
以下是修正后的代码,并解释了如何实现批处理:
import torch
import torch.nn as nn
class Model(nn.Module):
def __init__(self, input_size, hidden_size, output_size, n_layers):
super(Model, self).__init__()
self.hidden_size = hidden_size
self.n_layers = n_layers
self.rnn = nn.RNN(input_size, hidden_size, n_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x, hidden=None):
# 如果没有提供隐藏状态,则初始化
if hidden is None:
hidden = self.init_hidden(x.size(0))
out, hidden = self.rnn(x, hidden)
out = self.fc(out)
return out, hidden
def init_hidden(self, batch_size):
return torch.zeros(self.n_layers, batch_size, self.hidden_size)
# 超参数
input_size = 2
hidden_size = 128 # 选择一个合适的隐藏大小
output_size = 2
n_layers = 1
batch_size = 32
n_epochs = 100
lr = 0.001
# 模型、损失和优化器
model = Model(input_size, hidden_size, output_size, n_layers)
criterion = nn.MSELoss() # 针对回归使用 MSELoss
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
# 数据加载器(用于批处理)
# 假设你的数据在名为 'dataset' 的张量中
dataset = torch.randn(596, 49, 2) # 替换为你的实际数据
dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True)
# 训练循环
for epoch in range(n_epochs):
for batch_idx, data in enumerate(dataloader):
# 清除梯度
optimizer.zero_grad()
# 将输入数据和目标数据分开(假设最后一个时间步是目标)
input_seq = data[:, :-1, :]
target_seq = data[:, 1:, :]
# 前向传播
outputs, _ = model(input_seq)
# 计算损失
loss = criterion(outputs, target_seq)
# 反向传播和优化
loss.backward()
optimizer.step()
if batch_idx % 10 == 0:
print(f'Epoch: {epoch+1}/{n_epochs}, Batch: {batch_idx+1}/{len(dataloader)}, Loss: {loss.item():.4f}')
说明:
-
DataLoader
: 代码现在使用torch.utils.data.DataLoader
来有效地处理你的数据并将其分成批处理。 -
批处理循环: 外循环迭代 epoch,内循环迭代 dataloader 提供的批处理。
-
输入/目标拆分: 在每个批处理中,输入序列 (
input_seq
) 被视为除最后一个时间步之外的所有时间步,而目标序列 (target_seq
) 被视为除第一个时间步之外的所有时间步(因为你正尝试预测下一个位置)。 -
MSELoss: 代码现在使用
nn.MSELoss
来计算预测坐标和目标坐标之间的均方误差。 -
形状: 注意,模型的输出
outputs
将具有形状[batch_size, sequence_length - 1, output_size]
,这与target_seq
的形状相匹配。
通过这些调整,你的代码应该能够正确处理维度并使用批处理来训练 RNN 模型。记住,
hidden_size
是一个超参数,你可能需要根据你的数据的复杂性对其进行实验以找到最佳值。