利用lstm 和gru 训练一个语言模型
这个语言模型 就是输入一个词预测下一个词是什么
**********************************************************************************************************
emb: torch.Size([32, 32, 650])
hidden ([2,32,650],[2,32,650])
这里的Hidden 是包括 hidden 和cell (hidden,cell)
output torch.Size([32, 32, 650]) 是 [seq_len,batch_Size, embed_size]
RNN 的输出 是前面的Hidden 和当前的Input 预测出来的 prey shape 自认和输入的shape 一样 [seq_len,batch_size,embed_szie]
抛开批维度 来看 输入就是 [seq_lenth,embed_Size]>>>>output 输出 [seq_len,embed_Size]
hidden 则是最后的隐藏状态 维度 [1,hidden_Size] 这里我们一般 hidden_size==embed_size
因为每次一个序列输出之后 我们都只是拿到最后的隐藏状态 中间状态我们都没有拿 和cell状态
如果加上批处理维度 在加上2层 [layers,batch_size,hidden_size] cell 的维度一样 GRu 没有这cell 只有一个状态
如果是双层的化 hidden 的是 size ==[layers*2,batch_Size,hidden_size]
一般我们会进行一个双向的合并 hidden[-1]+hidden[-2] 进行相加
模型的本质就是上一个隐藏状态 [1,1,hidden_Size]+[1,1,embed_size]>>>>output[1,1,embed_Size]
根据 LsTM的推导公式是可以看出 我么计算当前 Hidden的时候 只用到上个Hidden 没有用到cell cell是根据 ft it ct算出来的
************************************************************************************************************
"""
https://github.com/pytorch/text
学习语言模型,以及如何训练一个语言模型
学习torchtext的基本使用方法
构建 vocabulary
word to inde 和 index to word
学习torch.nn的一些基本模型
Linear
RNN
LSTM
GRU
RNN的训练技巧
Gradient Clipping
如何保存和读取模型
我们会使用 torchtext 来创建vocabulary, 然后把数据读成batch的格式。请大家自行阅读README来学习torchtext。
"""
import torchtext
from torchtext.vocab import Vectors
import torch
import numpy as np
import random
USE_CUDA=torch.cuda.is_available()
device=torch.device('cuda' if USE_CUDA else 'cpu')
#为了保证实验结果可以复现 我们经常会吧各种random seed 固定在某一个值
random.seed(53113)
np.random.seed(53113)
torch.manual_seed(53113)
if USE_CUDA:
torch.cuda.manual_seed(53113)
BATCH_SIZE=32
EMBEDDING_SIZE=650
MAX_VOCAB_SIZE=50000
"""
我们会继续使用上次的text8作为我们的训练,验证和测试数据
TorchText的一个重要概念是Field,它决定了你的数据会如何被处理。我们使用TEXT这个field来处理文本数据。
我们的TEXT field有lower=True这个参数,所以所有的单词都会被lowercase。
torchtext提供了LanguageModelingDataset这个class来帮助我们处理语言模型数据集。
build_vocab可以根据我们提供的训练数据集来创建最高频单词的单词表,max_size帮助我们限定单词总量。
BPTTIterator可以连续地得到连贯的句子,BPTT的全程是back propagation through time。
"""
TEXT=torchtext.data.Field(lower=True)
train,val,test=torchtext.datasets.LanguageModelingDataset.splits(path='.',
train='/root/torch/data/text8/text8.train.txt',
validation='/root/torch/data/text8/text8.dev.txt',
test='/root/torch/data/text8/text8.test.txt',text_field=TEXT)
TEXT.build_vocab(train,max_size=MAX_VOCAB_SIZE)
print('vocabulary size:{}'.format(len(TEXT.vocab)))
#vocabulary size:50002
VOCAB_SIZE=len(TEXT.vocab)
train_iter,val_iter,test_iter=torchtext.data.BPTTIterator.splits(
(train,val,test),batch_size=BATCH_SIZE,device=device,bptt_len=32,repeat=False,shuffle=True)
"""
为什么我们的单词表有50002个单词而不是50000呢?因为TorchText给我们增加了两个特殊的token,表示未知的单词,表示padding。
模型的输入是一串文字,模型的输出也是一串文字,他们之间相差一个位置,因为语言模型的目标是根据之前的单词预测下一个单词。
"""
"""
torch.text size ===[bptt_len,batch_size]
第一维是输入句子的长度 第二维度是批次
batch=[torchtext.data.batch.Batch of size 32]
[.text]:[torch.LongTensor of size 32x32]
[.target]:[torch.LongTensor of size 32x32]
取出一批中第一个样本
print(" ".join([TEXT.vocab.itos[i] for i in batch.text[:,1].data]))
print(" ".join([TEXT.vocab.itos[i] for i in batch.target[:,1].data]))
combine in pairs and then group into trios of pairs which are the smallest visible units of matter this parallels with the
structure of modern atomic theory in which pairs or triplets of supposedly fundamental quarks combine to create most
typical forms of matter they had also suggested the possibility of splitting an atom which as we know today is
in pairs and then group into trios of pairs which are the smallest visible units of matter this parallels with the structure
of modern atomic theory in which pairs or triplets of supposedly fundamental quarks combine to create most typical
forms of matter they had also suggested the possibility of splitting an atom which as we know today is the
"""
it=iter(train_iter)
batch=next(it)
"""
定义模型
继承nn.Module
初始化函数
forward函数
其余可以根据模型需要定义相关的函数
"""
import torch
import torch.nn as nn
class RNNModel(nn.Module):
"""
rnn_type gru or lstm>>>"LSTM"
ntoken 词典维度 >>>50002
ninp input 词嵌入维度 embed_size>>>>650
nlayers 多少层>>>>>>>2
nhide 隐藏层>>>>>>>>650
('LSTM',VOCAB_SIZE=50002,EMBEDDING_SIZE=650,EMBEDDING_SIZE=650,nlayer=2,dropout=0.5)
"""
def __init__(self,rnn_type,ntoken,ninp,nhid,nlayers,dropout=0.5):
super(RNNModel,self).__init__()
"""
dropout 层 用来做正则化
词嵌入层
循环网络层
一个线性层 用来从hidden_state 到词典表的映射输出
"""
self.dropout=nn.Dropout(dropout)
#ntoken 词典维度 ninp 词嵌入维度
#词嵌入层[bpttlen,ntoken]>>[bpttlen,ninp]
#[32,50002]>>>>>[32,650]
self.encoder=nn.Embedding(ntoken,ninp)
if rnn_type in ['LSTM','GRU']:
#根据属性获取 相关网络层
# ninp embed_size nhide 隐藏层维度 nlayers 多少层
#ninp=650 nhid=650 nlayers=2
self.rnn=getattr(nn,rnn_type)(ninp,nhid,nlayers,dropout=dropout)
else:
try:
nonlinearity={'RNN_TANH':'tanh','RNN_RELU':'relu'}[rnn_type]
except KeyError:
raise ValueError(""" an invalid option foe '--model' was supplied
options are ['LSTM','GRU','RNN_THAN' or 'RNN_RELU']""")
self.rnn=nn.RNN(ninp,nhid,nlayers,nonlinearity=nonlinearity,dropout=dropout)
self.decoder=nn.Linear(nhid,ntoken)
self.init_weights()
self.rnn_type=rnn_type
self.nhid=nhid
self.nlayers=nlayers
def init_weights(self):
initrange=0.1
self.encoder.weight.data.uniform_(-initrange,initrange)
self.decoder.bias.data.zero_()
self.decoder.weight.data.uniform_(-initrange,initrange)
"""
input= data torch.text size ===[bptt_len,batch_size] >>[32,32]
"""
def forward(self,input,hidden):
"""
forward pass
--word embedding
---输入循环神经网络
--一个线性层 从Hidden state 转换为输出单词 词典
emb==[bptt-len,batch_Size,embed_size]
emb: torch.Size([32, 32, 650])
"""
#emb: torch.Size([32, 32, 650])
emb=self.dropout(self.encoder(input))
#print("emb:",emb.size())
"""
**********************************************************************************************************
emb: torch.Size([32, 32, 650])
hidden ([2,32,650],[2,32,650])
这里的Hidden 是包括 hidden 和cell (hidden,cell)
output torch.Size([32, 32, 650]) 是 [seq_len,batch_Size, embed_size]
RNN 的输出 是前面的Hidden 和当前的Input 预测出来的 prey shape 自认和输入的shape 一样 [seq_len,batch_size,embed_szie]
抛开批维度 来看 输入就是 [seq_lenth,embed_Size]>>>>output 输出 [seq_len,embed_Size]
hidden 则是最后的隐藏状态 维度 [1,hidden_Size] 这里我们一般 hidden_size==embed_size
因为每次一个序列输出之后 我们都只是拿到最后的隐藏状态 中间状态我们都没有拿 和cell状态
如果加上批处理维度 在加上2层 [layers,batch_size,hidden_size] cell 的维度一样 GRu 没有这cell 只有一个状态
如果是双层的化 hidden 的是 size ==[layers*2,batch_Size,hidden_size]
一般我们会进行一个双向的合并 hidden[-1]+hidden[-2] 进行相加
模型的本质就是上一个隐藏状态 [1,1,hidden_Size]+[1,1,embed_size]>>>>output[1,1,embed_Size]
根据 LsTM的推导公式是可以看出 我么计算当前 Hidden的时候 只用到上个Hidden 没有用到cell cell是根据 ft it ct算出来的
************************************************************************************************************
"""
output,hidden=self.rnn(emb,hidden)
output=self.dropout(output)
#output: torch.Size([32, 32, 650])
#print("output:",output.size())
#[32*32,650]>>>[1024,50002]
decoded=self.decoder(output.view(-1,output.size(2)))
#decoded: torch.Size([1024, 50002])
#print("decoded:",decoded.size())
"""
返回形状 [32,32,50002]
hidden ==([2,32,650],[2,32,650])
"""
return decoded.view(output.size(0),output.size(1),decoded.size(1)),hidden
def init_hidden(self,bsz,requires_grad=True):
weight=next(self.parameters())
#LSTM 2个中间状态
if self.rnn_type=='LSTM':
"""
([nlayers,batch_size,nhid])
([2,32,650],[2,32,650])
"""
return (weight.new_zeros((self.nlayers,bsz,self.nhid),requires_grad=requires_grad),
weight.new_zeros((self.nlayers,bsz,self.nhid),requires_grad=requires_grad))
else:
"""
[2,32,650]
"""
return weight.new_zeros((self.nlayers,bsz,self.nhid),requires_grad=requires_grad)
"""
初始化模型
('LSTM',VOCAB_SIZE=50002,EMBEDDING_SIZE=650,EMBEDDING_SIZE=650,nlayer=2,dropout=0.5)
"""
model =RNNModel('LSTM',VOCAB_SIZE,EMBEDDING_SIZE,EMBEDDING_SIZE,2,dropout=0.5)
if USE_CUDA:
model=model.cuda()
"""
我们首先定义评估模型的代码。
模型的评估和模型的训练逻辑基本相同,唯一的区别是我们只需要forward pass,不需要backward pass
"""
def evaluate(model, data):
model.eval()
total_loss = 0.
it = iter(data)
total_count = 0.
with torch.no_grad():
hidden = model.init_hidden(BATCH_SIZE, requires_grad=False)
for i, batch in enumerate(it):
data, target = batch.text, batch.target
if USE_CUDA:
data, target = data.cuda(), target.cuda()
hidden = repackage_hidden(hidden)
with torch.no_grad():
output, hidden = model(data, hidden)
loss = loss_fn(output.view(-1, VOCAB_SIZE), target.view(-1))
total_count += np.multiply(*data.size())
total_loss += loss.item()*np.multiply(*data.size())
loss = total_loss / total_count
model.train()
return loss
"""
我们需要定义下面的一个function,帮助我们把一个hidden state和计算图之前的历史分离。
"""
# Remove this part
#hidden ([2,32,650],[2,32,650]))
def repackage_hidden(h):
"""Wraps hidden states in new Tensors, to detach them from their history."""
"""
#hidden ([2,32,650],[2,32,650]))
"""
if isinstance(h, torch.Tensor):
return h.detach()
else:
return tuple(repackage_hidden(v) for v in h)
"""
定义Loss_fn 和optimizer
"""
loss_fn=nn.CrossEntropyLoss()
learning_rate=1e-3
optimizer=torch.optim.Adam(model.parameters(),lr=learning_rate)
scheduler=torch.optim.lr_scheduler.ExponentialLR(optimizer,0.5)
"""
训练模型:
模型一般需要训练若干个epoch
每个epoch我们都把所有的数据分成若干个batch
把每个batch的输入和输出都包装成cuda tensor
forward pass,通过输入的句子预测每个单词的下一个单词
用模型的预测和正确的下一个单词计算cross entropy loss
清空模型当前gradient
backward pass
gradient clipping,防止梯度爆炸
更新模型参数
每隔一定的iteration输出模型在当前iteration的loss,以及在验证集上做模型的评估
"""
GRAD_CLIP = 1.
NUM_EPOCHS = 2
val_losses = []
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 torch.text size ===[bptt_len,batch_size] >>[32,32]
target size ===[bptt_len,batch_size] >>[32,32]
第一维是输入句子的长度 第二维度是批次
print("data:",data.size(),"target:",target.size())
data: torch.Size([32, 32]) target: torch.Size([32, 32])
"""
data, target = batch.text, batch.target
if USE_CUDA:
data, target = data.cuda(), target.cuda()
#hidden ([2,32,650],[2,32,650]))
hidden = repackage_hidden(hidden)
model.zero_grad()
"""
output==[32,32,50002] ..[bptt_len,batch_size,vocab_size]
hidden ([2,32,650],[2,32,650]))
"""
output, hidden = model(data, hidden)
"""
#[1024,50002] target.view(-1) ===[1024,]
target 的编码维度也是50002维度 onehot编码
然后开始求预测的这2个分布的交叉熵 越小分布越相似
"""
loss = loss_fn(output.view(-1, VOCAB_SIZE), target.view(-1))
#print("target.view(-1):",target.view(-1).size())
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), GRAD_CLIP)
optimizer.step()
if i % 1000 == 0:
print("epoch", epoch, "iter", i, "loss", loss.item())
if i % 10000 == 0:
val_loss = evaluate(model, val_iter)
if len(val_losses) == 0 or val_loss < min(val_losses):
print("best model, val loss: ", val_loss)
torch.save(model.state_dict(), "lm-best.th")
else:
scheduler.step()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
val_losses.append(val_loss)
"""
加载模型 在验证集合 和测试集合上各跑一遍
先初始化一个模型
"""
best_model=RNNModel('LSTM',VOCAB_SIZE,EMBEDDING_SIZE,EMBEDDING_SIZE,2,dropout=0.5)
if USE_CUDA:
best_model=best_model.cuda()
"""
加载模型参数 这是torch的推荐保存方式
"""
best_model.load_state_dict(torch.load("lm-best.th"))
"""
在验证集合 测试集合 上泡一下数据
"""
val_loss=evaluate(best_model,val_iter)
print('perplexity:',np.exp(val_loss))
test_loss=evaluate(best_model,test_iter)
"""
""numpy.exp():返回e的幂次方,e是一个常数为2.71828
"""
print("perplexity:test_loss:",np.exp(test_loss))
"""
使用训练好的模型生成一些句子。
"""
"""
([2,1,650],[2,1,650])这个产生一个隐藏状态
1 是batch_size
初始化个批次的数据
"""
hidden=best_model.init_hidden(1)
device=torch.device('cuda' if USE_CUDA else 'cpu')
"""
产生一个(1,1)的数字 作为输入 正常输入 【32,32】]1
1代表 bpttlen 1 代表批次
"""
input=torch.randint(VOCAB_SIZE,(1,1),dtype=torch.long).to(device)
words=[]
# 产生100个单词
for i in range(100):
"""
input[1,1] hidden([2,1,650],[2,1,650])
output[1,1,50002] hidden ([2,1,650],[2,1,650])
输入一个单词 预测一个单词 循环100次
"""
output,hidden=best_model(input,hidden)
#word_weights=[50002] 取最后一维度
word_weights=output.squeeze().exp().cpu()
# 按照权重 产生 50002维度 那个可能的值得索引 下标 也就是产生一个单词的索引
# 拿到单词的idx 可以拿到单词
# 按照权重产生单词 这样每次拿到的单词是不同的额 增加的多变性
word_idx=torch.multinomial(word_weights,1)[0]
#作为下一次的输入
input.fil_(word_idx)
word=TEXT.vocab.itos[word_idx]
words.append(word)
print("".join(words))