参考代码:https://github.com/yunjey/pytorch-tutorial/tree/master/tutorials/02-intermediate/language_model
lstm简单介绍:https://zhuanlan.zhihu.com/p/24720659
一、language model
语言模型的工作是计算一句话是否为正常的语言。
注意模型中:每个短句无重叠。
batch_size: 样本分批训练的批次大小
seq_len:是序列长度(人为定义大小,一般取30),就是默认的语句长度
corpus:是字典集合,语料库。
第一步:将所有文本切词添加到语料库
#-*- coding:utf-8 -*-
import torch
import os
class Dictionary(object):
def __init__(self):
self.word2idx = {}
self.idx2word = {}
self.idx = 0
def add_word(self, word):
if not word in self.word2idx:
self.word2idx[word] = self.idx
self.idx2word[self.idx] = word
self.idx += 1
def __len__(self):
return len(self.word2idx)
class Corpus(object):
def __init__(self, path='./data'):
self.dictionary = Dictionary()
self.train = os.path.join(path, 'train.txt')
self.test = os.path.join(path, 'test.txt')
def get_data(self, path, batch_size=20):
# Add words to the dictionary
with open(path, 'r') as f:
tokens = 0
for line in f:
words = line.split() + ['<eos>']
tokens += len(words)
for word in words:
self.dictionary.add_word(word)
# Tokenize the file content
# 长整型向量,保存整个文章中词的idx信息
ids = torch.LongTensor(tokens)
token = 0
with open(path, 'r') as f:
for line in f:
words = line.split() + ['<eos>']
for word in words:
ids[token] = self.dictionary.word2idx[word]
token += 1
num_batches = ids.size(0) // batch_size
ids = ids[:num_batches*batch_size]
return ids.view(batch_size, -1)
# Load Penn Treebank Dataset
train_path = './data/train.txt'
sample_path = './sample.txt'
corpus = Corpus()
ids = corpus.get_data(train_path, batch_size) # 样本按顺序单词在字典库中的下标
vocab_size = len(corpus.dictionary) # 整个字典库的收录单词大小
num_batches = ids.size(1) // seq_length
第二步:将文本语料无交叉构造成训练数据矩阵
data矩阵的每一行表示一句话(长度固定30),其中包含分句符。每个值为该句话中该单词所在语料库中的id值(一般用idx表示)。
target矩阵的每一行对应data矩阵每一行一句话向右平移后的(长度为30)的语句。
datas = Variable(ids[:, i:i+seq_length])
targets = Variable(ids[:, (i+1):(i+1)+seq_length].contiguous())
注: ids的大小为[ batchsize*(seq_length*t) ] 应该分t组数据(loader)处理。
第三步:建立模型
模型的输入为上述矩阵,输出为原语句平移1个单位后的语句。
第四步:查看语言模型预测效果
二、pytorch 的Embedding与LSTM接口用法
官方文档: http://pytorch-cn.readthedocs.io/zh/latest/package_references/torch-nn/#recurrent-layers
1、nn.Embedding接口初始化
class Embedding(Module): def __init__( self, num_embeddings, # vocabulary_size len(word2idx.keys()) embedding_dim, # 最终生成的word2vec的维度 padding_idx=None, max_norm=None, norm_type=2, scale_grad_by_freq=False, sparse=False):
2、nn.LSTM接口初始化
class RNNBase(Module): def __init__( self, mode, input_size, # word2vec的维度 hidden_size, # 隐藏层一个节点的维度 num_layers=1, # 隐藏层的个数,层数 bias=True, batch_first=False, dropout=0, bidirectional=False):
范例:
lstm = nn.LSTM(input_size=50, hidden_size=1024, num_layers=2, batch_first=True) input = Variable(torch.randn(1000, 40, 50)) # h0 = Variable(torch.randn(3, 1000, 1024)) # c0 = Variable(torch.randn(3, 1000, 1024)) output, (hn,cn) = lstm(input) print( output.size(),hn.size(),cn.size() ) # output.size()=(1000*40*1024) hn.size()=2*1000*1024 cn.size()=2*1000*1024
上述例子反映:
一个batch有1000个句子, 句子中单词的word2vec的维度为50,句子最大长度为40,不够40的补,多于40的截断。
最后输出的output的size为:1000个句子*句子最大长度为40*每个单词的size为1024, hn的size为:2个隐层*句子最大长度1000*每个单词的维度1024。
该LSTM设计结构如下:
输入单词的维度(input_size)=50, 隐藏层有2层, 隐节点的维度为1024。
三、attention model
参考网址:
https://zhuanlan.zhihu.com/p/22081325 这里的4个公式一定要看
attention model翻译模型整体结构如下:
pytorch的60分钟程序是按顺序执行,翻译对。
如:
法语:"vous etes fort ingenieuse . ",转换成下标形式为[8,1150,97,32,59,1];存在于法语vocabulary集
对应英语: " you are vary clever . "转换成对应下标形式为[77,1150,4,8,11,1];存在于英语vocabulary集
注:这里的是双集合,1代表<EOS>句子末尾。
翻译模型句子1和2长度可以不同, 设置全局变量MAX_Length,按0补全空缺位置。
4个重要公式
train方法是单个epoch执行的部分
# 每个epochs中执行的部分,每个epochs执行一个语句
def train(input_variable, target_variable, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion, max_length=MAX_LENGTH):
# criterion:负对数似然损失函数
encoder_hidden = encoder.initHidden() # 初始化隐藏层
encoder_optimizer.zero_grad() # 初始化优化器
decoder_optimizer.zero_grad() # 初始化优化器
input_length = input_variable.size()[0] # 单个输入语句
target_length = target_variable.size()[0] # 单个标签正确翻译后的语句
# 站位符 输出长度匹配
encoder_outputs = Variable( torch.zeros(max_length, encoder.hidden_size) )
encoder_outputs = encoder_outputs.cuda() if use_cuda else encoder_outputs
loss = 0
############################## Encoder ##############################
# 获取encoder对于单个语句的输出
for ei in range(input_length):
encoder_output, encoder_hidden = encoder( input_variable[ei], encoder_hidden)
encoder_outputs[ei] = encoder_output[0][0]
############################## Attention Decoder ##############################
# decoder 是一步一步按照顺序执行seq中的word
decoder_input = Variable(torch.LongTensor([[SOS_token]]))
decoder_input = decoder_input.cuda() if use_cuda else decoder_input
decoder_hidden = encoder_hidden
# 随机算法
use_teacher_forcing = True if random.random() < teacher_forcing_ratio else False
if use_teacher_forcing:
# Teacher forcing: Feed the target as the next input
for di in range(target_length):
decoder_output, decoder_hidden, decoder_attention = decoder( decoder_input, decoder_hidden, encoder_output, encoder_outputs)
loss += criterion(decoder_output, target_variable[di]) # 计算误差
decoder_input = target_variable[di] # Teacher forcing
else:
# Without teacher forcing: use its own predictions as the next input
for di in range(target_length):
decoder_output, decoder_hidden, decoder_attention = decoder( decoder_input, decoder_hidden, encoder_output, encoder_outputs)
topv, topi = decoder_output.data.topk(1)
ni = topi[0][0]
decoder_input = Variable(torch.LongTensor([[ni]]))
decoder_input = decoder_input.cuda() if use_cuda else decoder_input
loss += criterion(decoder_output, target_variable[di]) # 计算误差
if ni == EOS_token: break
loss.backward()
encoder_optimizer.step()
decoder_optimizer.step()
return loss.data[0] / target_length
Encoder
注: 输入input是word对应的idx值。该程序是按单词一次一次输入到encoder中,最后保留一个hidden层 为Decoder做准备。
decoder_hidden = encoder_hidden
Simple Decoder
注:decode部分是按照word一个一个循环训练的
并且每次输入都要使用上一次的输出作为参数。
Attention Decoder
注:
这里的input是单个word,最开始的word是<SOS>句子的开始
encoder_outputs:是encoder模块层所有的输出。
bmm:对存储在两个批batch1和batch2内的矩阵进行批矩阵乘操作。