项目实战(四) - - 语言模型
1. 任务概述
根据上下文语境基于概率预测下一个词,通过对网络训练一定程度后,最后的模型参数可当成词向量使用
2. 算法步骤
- 构造数据集
- 定义模型
Layers:Embedding,LSTM,Linear - 训练与评估
Loss:CrossEntropyLoss()
Optimizer:Adam
3. 代码实现与解析
- 导入相关包
- 构造训练数据集
模型的输入是一串文字,模型的输出也是一串文字,他们之间相差一个位置,因为语言模型的目标是根据之前的单词预测下一个单词 - 定义模型
class RNNModel(nn.Module):
def __init__(self,vocab_size,embeded_size,hidden_size):
super(RNNModel,self).__init__()
self.embeded=nn.Embedding(vocab_size,embeded_size)
self.lstm=nn.LSTM(embeded_size,hidden_size)
self.linear=nn.Linear(hidden_size,vocab_size)
self.hidden_size=hidden_size
def forward(self,text,hidden):
emb=self.embeded(text) # text:seq_length*batch_size
output,hidden=self.lstm(emb,hidden)
// output:(seg_length*batch_size)*hidden_size
out_vocab=self.linear(output.view(-1,output.shape[2])) out_vocab=out_vocab.view(output.size(0),output.size(1),out_vocab.size(-1))
// out_vocab:知道每个位置预测哪个单词,hidden:包含到目前为止所有信息
return out_vocab,hidden
def init_hidden(self,bsz,requires_grad=True):
weight=next(self.parameters())
return (weight.new_zeros((1,bsz,self.hidden_size)),
weight.new_zeros((1,bsz,self.hidden_size)))
// 把一个hidden state和计算图之前的历史分离
def repackage_hidden(h):
if isinstance(h, torch.Tensor):
return h.detach()
else:
return tuple(repackage_hidden(v) for v in h)
- 定义loss function和optimizer
loss_fn = nn.CrossEntropyLoss()
learning_rate = 0.001
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, 0.5)
-
3.5 训练步骤
-
模型一般需要训练若干个epoch
-
每个epoch我们都把所有的数据分成若干个batch
-
把每个batch的输入和输出都包装成cuda tensor
-
forward pass,通过输入的句子预测每个单词的下一个单词
-
用模型的预测和正确的下一个单词计算cross entropy loss
-
清空模型当前gradient
-
backward pass
-
gradient clipping,防止梯度爆炸
-
更新模型参数
-
每隔一定的iteration输出模型在当前iteration的loss,以及在验证集上做模型的评估
-
将训练最好的模型保存,测试时载入
for epoch in range(NUM_EPOCHS):
model.train()
it = iter(train_iter)
hidden = model.init_hidden(BATCH_SIZE)
for i, batch in enumerate(it):
data, target = batch.text, batch.target
hidden = repackage_hidden(hidden)
model.zero_grad()
output, hidden = model(data, hidden)
loss = loss_fn(output.view(-1, VOCAB_SIZE), target.view(-1))
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), GRAD_CLIP)
optimizer.step()
- 测试
// 载入训练效果最好的模型
best_model = RNNModel("LSTM", VOCAB_SIZE, EMBEDDING_SIZE, EMBEDDING_SIZE, 2, dropout=0.5)
best_model.load_state_dict(torch.load("lm-best.th"))
// 在测试数据上计算perplexity
test_loss = evaluate(best_model, test_iter)
print("perplexity: ", np.exp(test_loss))
- 句子生成
//此处随机一个词索引作为输入,生成长度100的句子
hidden = best_model.init_hidden(1)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
input = torch.randint(VOCAB_SIZE, (1, 1), dtype=torch.long).to(device)
words = []
for i in range(100):
output, hidden = best_model(input, hidden)
word_weights = output.squeeze().exp().cpu()
word_idx = torch.multinomial(word_weights, 1)[0]
input.fill_(word_idx)
word = TEXT.vocab.itos[word_idx]
words.append(word)
print(" ".join(words))