PyTorch名称分类中的LSTM



我正在尝试中的示例https://pytorch.org/tutorials/intermediate/char_rnn_classification_tutorial.html但是我使用的是LSTM模型而不是RNN。数据集由不同的名称(大小不同(及其对应的语言组成(语言总数为18种(,目标是训练一个给定某个名称并输出其所属语言的模型。

我现在的问题是:

  • 如何在LSTM中处理可变大小的名称,即Hector和Kim
  • 每次在LSTM中都会处理一个完整的名称(字符的连续性(,因此softmax函数的输出具有(#characters of name, #target classes)的形状,但我只想获得(1,#target of classes),以决定它对应于哪个类的每个名称。我试图只获得最后一行,但结果非常糟糕
class LSTM(nn.Module):
def __init__(self, embedding_dim, hidden_dim, vocab_size, tagset_size):
super(LSTM, self).__init__()
self.hidden_dim = hidden_dim
self.word_embeddings = nn.Embedding(vocab_size, embedding_dim)
# The LSTM takes word embeddings as inputs, and outputs hidden states
# with dimensionality hidden_dim.
self.lstm = nn.LSTM(embedding_dim, hidden_dim)
# The linear layer that maps from hidden state space to tag space
self.hidden2tag = nn.Linear(hidden_dim, tagset_size)
self.softmax = nn.LogSoftmax(dim = 1)

def forward(self, word):
embeds = self.word_embeddings(word)
lstm_out, _ = self.lstm(embeds.view(len(word), 1, -1))
tag_space = self.hidden2tag(lstm_out.view(len(word), -1))
tag_scores = self.softmax(tag_space)
return tag_scores
def initHidden(self):
return Variable(torch.zeros(1, self.hidden_dim))
lstm = LSTM(n_embedding_dim,n_hidden,n_characters,n_categories)
optimizer = torch.optim.SGD(lstm.parameters(), lr=learning_rate)
criterion = nn.NLLLoss()
def train(category_tensor, line_tensor):
# i.e. line_tensor = tensor([37,  4, 14, 13, 19,  0, 17,  0, 10,  8, 18]) and category_tensor = tensor([11])
optimizer.zero_grad()
output = lstm(line_tensor)
loss = criterion(output[-1:], category_tensor) # VERY BAD
loss.backward()
optimizer.step()
return output, loss.data.item()

其中line_tensor的大小可变(取决于每个名称的大小(,并且是字典中字符及其索引之间的映射

让我们逐步深入了解解决方案

界定问题

考虑到您的问题陈述,您将不得不使用LSTM进行分类,而不是其典型的标记使用。LSTM在特定的时间步长展开,这就是为什么递归模型的输入和输出维度是的原因

  • 输入:batch size X time steps X input size
  • 输出:batch size X time steps X hidden size

既然你想用它来分类,你有两个选项:

  1. 在所有时间步/展开的输出上放置一个密集层[下面的例子使用了这个]
  2. 忽略除最后一个输出外的所有时间步长,并在最后一个时间步长上放置密集层

因此,我们的LSTM模型的输入是每个LSTM时间步长输入一个字符的名称,输出将是与其语言对应的类。

如何处理可变长度输入/名称

我们还有两个选择。

  1. 将相同长度的名称批处理在一起。这叫做桶装
  2. 根据你拥有的名字的平均大小来修正最大长度。填充较小的名称并剪掉较长的名称[下面的示例使用的最大长度为10]

我们需要嵌入层吗

没有。嵌入层通常用于学习单词的良好矢量表示。但在字符模型的情况下,输入是字符而不是单词,因此添加嵌入层没有帮助。字符可以直接编码为数字,嵌入层在捕捉不同字符之间的关系方面做得很少。你仍然可以使用嵌入层,但我坚信它不会有帮助。

玩具角色LSTM模型代码

import numpy as np
import torch
import torch.nn as nn
# Model architecture 
class Recurrent_Model(nn.Module):
def __init__(self, output_size, time_steps=10):
super(Recurrent_Model, self).__init__()
self.time_steps = time_steps
self.lstm = nn.LSTM(1,32, bidirectional=True, num_layers=2)
self.linear = nn.Linear(32*2*time_steps, output_size)
def forward(self, x):        
lstm_out, _ = self.lstm(x)
return self.linear(lstm_out.view(-1,32*2*self.time_steps))
# Sample input and output
names = ['apple', 'dog', 'donkey', "elephant", "hippopotamus"]
lang = [0,1,2,1,0]
def pad_sequence(name, max_len=10):
x = np.zeros((len(name), max_len))
for i, name in enumerate(names):
for j, c in enumerate(name):
if j >= max_len:
break
x[i,j] = ord(c)
return torch.FloatTensor(x)
x = pad_sequence(names)
x = torch.unsqueeze(x, dim=2)
y = torch.LongTensor(lang)
model = Recurrent_Model(3)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), 0.01)
for epoch in range(500):
model.train()
output = model(x)
loss = criterion(output, y)
print (f"Train Loss: {loss.item()}")
optimizer.zero_grad()
loss.backward()
optimizer.step()

备注

  1. 所有张量都加载到内存中,因此如果您有巨大的数据集,则必须使用数据集和数据加载器来避免OOM错误
  2. 您必须将数据拆分为训练测试,并在测试数据集(标准的模型构建材料(上进行验证
  3. 在将输入张量传递给模型(同样是标准的模型构建材料(之前,您必须对其进行规范化

最后

那么,如何确保您的模型体系结构没有错误或正在学习呢。正如Andrej karpathy所说,在一个小数据集上对模型进行过拟合,如果它是过拟合的,那么我们就没事了。

虽然根据应用程序域可能有不同的方法,但对可变大小输入的常见方法是将它们填充到MAX_SIZE。要么定义一个足够大的MAX_SIZE,要么选择数据集中最大的名称来定义它。

填充应该是零或其他适合标记化方案的空字符。

嵌入对于NLP模型非常重要,正如您所看到的,LSTM层希望您为其提供最终的嵌入维度。

self.embedding = nn.Embedding(vocab_size, embedding_dim)

vocab_size=数据集中唯一名称的数目。

embedding_dim=适当的数字。用不同的维度进行实验,什么能得到更好的结果?5.512?

self.lstm = nn.LSTM(embedding_dim, 
hidden_dim)

最新更新