项目地址:https://github.com/ZMpursue/LSTM_Write_Poetrypython
本教程将经过一个示例对LSTM进行介绍。经过搭建训练LSTM网络,咱们将训练一个模型来生成唐诗。本文将对该实现进行详尽的解释,并阐明此模型的工做方式和缘由。并不须要过多专业知识,可是可能须要新手花一些时间来理解的模型训练的实际状况。为了节省时间,请尽可能选择GPU进行训练。git
下载安装命令 ## CPU版本安装命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle ## GPU版本安装命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu
1 简介
本项目基于paddlepaddle2.0,结合长短时间记忆(Long short-term memory, LSTM),以唐诗为数据集经过监督学习的方式,训练生成唐诗。github
参考文档:http://colah.github.io/posts/2015-08-Understanding-LSTMs/网络
1.1 LSTM解决什么问题?
RNN突出的优势之一就是能够用来链接先前的信息到当前的任务上,有时候,咱们仅仅须要知道先前的信息来执行当前的任务。例如,咱们有一个语言模型用来基于先前的词来预测下一个词。若是试着预测 “the clouds are in the ___ ” 最后的词,咱们并不须要任何其余的上下文下一个词显然就应该是sky。在这样的场景中,相关的信息和预测的词位置之间的间隔是很是小的,RNN 能够充分使用先前信息来预测。app
可是一样会有一些更加复杂的场景。假设咱们试着去预测“I grew up in France… I speak fluent French”最后的词。当前的信息建议下一个词多是一种语言的名字,可是若是咱们须要弄清楚是什么语言,咱们是须要先前提到的离当前位置很远的 France 的上下文的。这说明相关信息和当前预测位置之间的间隔就确定变得至关的大,在这个间隔不断增大时,RNN 会丧失学习到链接如此远的信息的能力。svg
LSTM的出现为了解决长序列训练过程当中的梯度消失和梯度爆炸问题。简单来讲,相比普通的RNN,LSTM可以在更长的序列中有更好的表现。函数
1.2 什么是LSTM?
Long Short Term 网络,通常就叫作 LSTM ——是一种 RNN 特殊的类型,能够学习长期依赖信息。LSTM 由Hochreiter & Schmidhuber (1997)提出,并在近期被Alex Graves进行了改良和推广。在不少问题,LSTM 都取得至关巨大的成功,并获得了普遍的使用。
LSTM 经过专门的设计来避免长期依赖问题。
全部 RNN 都具备一种重复神经网络模块的链式的形式。在标准的 RNN 中,这个重复的模块只有一个很是简单的结构,例如一个 tanh 层。post
LSTM 一样是这样的结构,可是重复的模块拥有一个不一样的结构。不一样于 单一神经网络层,这里是有四个,以一种很是特殊的方式进行交互。学习
如今,咱们先来熟悉一下图中使用的各类元素的图标。优化
在上面的图例中,每一条黑线传输着一整个向量,从一个节点的输出到其余节点的输入。粉色的圈表明 pointwise 的操做,诸如向量的和,而黄色的矩阵就是学习到的神经网络层。合在一块儿的线表示向量的链接,分开的线表示内容被复制,而后分发到不一样的位置。
1.3 LSTM核心思想
LSTM 的关键就是细胞状态,水平线在图上方贯穿运行。细胞状态相似于传送带。直接在整个链上运行,只有一些少许的线性交互。信息在上面流传保持不变会很容易。
LSTM 有经过精心设计的称做为“门”的结构来去除或者增长信息到细胞状态的能力。门是一种让信息选择式经过的方法。他们包含一个 sigmoid 神经网络层和一个 pointwise 乘法操做。Sigmoid 层输出 0 到 1 之间的数值,描述每一个部分有多少许能够经过。0 表明“不准任何量经过”,1 就指“容许任意量经过”!
LSTM 拥有三个门,来保护和控制细胞状态。
1.4 LSTM详解
在咱们 LSTM 中的第一步是决定咱们会从细胞状态中丢弃什么信息。这个决定经过一个称为遗忘门层完成。该门会读取 h t − 1 h_{t-1} ht−1和 x t x_t xt,输出一个在 0 到 1 之间的数值给每一个在细胞状态 C t − 1 C_{t-1} Ct−1中的数字。1 表示“彻底保留”,0 表示“彻底舍弃”。让咱们回到语言模型的例子中来基于已经看到的预测下一个词。在这个问题中,细胞状态可能包含当前主语的性别,所以正确的代词能够被选择出来。当咱们看到新的主语,咱们但愿忘记旧的主语。
下一步是肯定什么样的新信息被存放在细胞状态中。这里包含两个部分。第一,sigmoid 层称输入门层决定什么值咱们将要更新。而后,一个 tanh 层建立一个新的候选值向量[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gg7pFr6E-1607831223631)(https://latex.codecogs.com/svg.latex?\tilde{C}t)]会被加入到状态中。下一步,咱们会讲这两个信息来产生对状态的更新。在咱们语言模型的例子中,咱们但愿增长新的主语的性别到细胞状态中,来替代旧的须要忘记的主语。
如今是时候去更新上一个状态值 C t − 1 C_{t−1} Ct−1了,将其更新为 C t C_t Ct。前面的步骤以及决定了应该作什么,咱们只需实际执行便可。
咱们将上一个状态值乘以 f t f_t ft,以此表达期待忘记的部分。以后咱们将获得的值加上 i t ∗ C ~ t i_t*\tilde{C}_t it∗C~t。这个获得的是新的候选值, 按照咱们决定更新每一个状态值的多少来衡量.
在语言模型的例子中,对应着实际删除关于旧主题性别的信息,并添加新信息,正如在以前的步骤中描述的那样。
最后,咱们须要决定要输出的内容。此输出将基于咱们的单元状态,但将是过滤后的版本。首先,咱们运行一个Sigmoid层,该层决定要输出的单元状态的哪些部分。而后,咱们经过激活函数Tanh(将值规范化介于−1和1之间)并乘以Sigmoid的输出,这样咱们就只输出咱们决定的部分。
对于语言模型示例,因为它只是看到一个主语,所以可能要输出与动词相关的信息,以防万一。例如,它可能输出主语是单数仍是复数,以便咱们知道若是接下来是动词,则应将动词以哪一种形式组合。
2 定义超参数
from paddle.io import Dataset import paddle.fluid as fluid import numpy as np import paddle class Config(object): num_layers = 3 # LSTM层数 data_path = 'work/tang_poem.npz' # 诗歌的文本文件存放路径 lr = 1e-3 # 学习率 use_gpu = True # 是否使用GPU epoch = 20 batch_size = 4 # mini-batch大小 maxlen = 125 # 超过这个长度的以后字被丢弃,小于这个长度的在前面补空格 plot_every = 1000 # 隔batch 可视化一次 max_gen_len = 200 # 生成诗歌最长长度 model_path = "work/checkpoints/model.params.50" # 预训练模型路径 prefix_words = '欲穷千里目,更上一层楼' # 不是诗歌的组成部分,用来控制生成诗歌的意境 start_words = '老夫聊发少年狂,' # 诗歌开始 model_prefix = 'work/checkpoints/model.params' # 模型保存路径 embedding_dim = 256 # 词向量维度 hidden_dim = 512 # LSTM hidden层维度
3.定义DataLoader
数据集由唐诗组成,包含唐诗57580首125字(不足和多余125字的都被补充或者截断)、ix2word以及word2ix共三个字典存储为npz格式
paddle.enable_static() datas = np.load(Config.data_path,allow_pickle=True) data = datas['data'] #加载映射表 ix2word = datas['ix2word'].item() word2ix = datas['word2ix'].item() class dataset(Dataset): def __init__(self, data): super(dataset,self).__init__() self.data = data def __getitem__(self, idx): poem = data[idx] return poem def __len__(self): return len(self.data) train_dataset = dataset(data) poem = paddle.static.data(name='poem', shape=[None,125], dtype='float32') if Config.use_gpu: device = paddle.set_device('gpu') places = paddle.CUDAPlace(0) else: device = paddle.set_device('cpu') places = paddle.CPUPlace() paddle.disable_static(device) train_loader = paddle.io.DataLoader( train_dataset, places=places, feed_list = [poem], batch_size=Config.batch_size, shuffle=True, num_workers=2, use_buffer_reader=True, use_shared_memory=False, drop_last=True, )
4.定义网络
网络由一层Embedding层和三层LSTM层再经过全链接层组成
- input:[seq_len,batch_size]
- 通过embedding层,embeddings(input)
- output:[batch_size,seq_len,embedding_size]
- 通过LSTM,lstm(embeds, (h_0, c_0)),输出output,hidden
- output:[batch, seq_len, hidden_size]
- Reshape再进过Linear层判别
- output:[batch*seq_len, vocabsize]
import paddle.fluid class Peom(paddle.nn.Layer): def __init__(self,vocab_size,embedding_dim,hidden_dim): super(Peom, self).__init__() self.embeddings = paddle.nn.Embedding(vocab_size,embedding_dim) self.lstm = paddle.nn.LSTM( input_size=embedding_dim, hidden_size=hidden_dim, num_layers=Config.num_layers, ) self.linear = paddle.nn.Linear(in_features=hidden_dim,out_features=vocab_size) def forward(self,input_,hidden=None): seq_len, batch_size = paddle.shape(input_) embeds = self.embeddings(input_) if hidden is None: output,hidden = self.lstm(embeds) else: output,hidden = self.lstm(embeds,hidden) output = paddle.reshape(output,[seq_len*batch_size,Config.hidden_dim]) output = self.linear(output) return output,hidden
5.训练过程
- 输入的input为(batch_size,seq_len)
- 经过input_,target = data_[:,:-1],data_[:,1:]将每句话分为前n-1个字做为真正的输入,后n-1个字做为label,size都是(batch_size, seq_len-1)
- 通过网络,得出output:((seq_len-1)*batch, vocab_size)
- 经过label通过reshape将target变成((seq_len-1)*batch)
损失函数为:crossEntropy,优化器为:Adam
loss_= [] def train(): model = Peom( len(word2ix), embedding_dim = Config.embedding_dim, hidden_dim = Config.hidden_dim ) # state_dict = paddle.load(Config.model_path) # model.set_state_dict(state_dict) optim = paddle.optimizer.Adam(parameters=model.parameters(),learning_rate=Config.lr) lossf = paddle.nn.CrossEntropyLoss() for epoch in range(Config.epoch): for li,data in enumerate(train_loader()): optim.clear_grad() data = data[0] #data = paddle.transpose(data,(1,0)) x = paddle.to_tensor(data[:,:-1]) y = paddle.to_tensor(data[:,1:],dtype='int64') y = paddle.reshape(y,[-1]) y = paddle.to_tensor(y,dtype='int64') output,hidden = model(x) loss = lossf(output,y) loss.backward() optim.step() loss_.append(loss.numpy()[0]) if li % Config.plot_every == 0: print('Epoch ID={0}\t Batch ID={1}\t Loss={2}'.format(epoch, li, loss.numpy()[0])) results = list(Config.start_words) start_words_len = len(Config.start_words) # 第一个词语是<START> input = paddle.to_tensor([word2ix['<START>']]) input = paddle.reshape(input,[1,1]) hidden = None # 如有风格前缀,则先用风格前缀生成hidden if Config.prefix_words: # 第一个input是<START>,后面就是prefix中的汉字 # 第一个hidden是None,后面就是前面生成的hidden for word in Config.prefix_words: output, hidden = model(input, hidden) input = paddle.to_tensor([word2ix[word]]) input = paddle.reshape(input,[1,1]) # 开始真正生成诗句,若是没有使用风格前缀,则hidden = None,input = <START> # 不然,input就是风格前缀的最后一个词语,hidden也是生成出来的 for i in range(Config.max_gen_len): output, hidden = model(input, hidden) # print(output.shape) # 若是还在诗句内部,输入就是诗句的字,不取出结果,只为了获得 # 最后的hidden if i < start_words_len: w = results[i] input = paddle.to_tensor([word2ix[w]]) input = paddle.reshape(input,[1,1]) # 不然将output做为下一个input进行 else: # print(output.data[0].topk(1)) _,top_index = paddle.fluid.layers.topk(output[0],k=1) top_index = top_index.numpy()[0] w = ix2word[top_index] results.append(w) input = paddle.to_tensor([top_index]) input = paddle.reshape(input,[1,1]) if w == '<EOP>': del results[-1] break results = ''.join(results) print(results) paddle.save(model.state_dict(), Config.model_prefix) train()
6.Loss变化过程
import matplotlib.pyplot as plt plt.figure(figsize=(15, 6)) x = np.arange(len(loss_)) plt.title('Loss During Training') plt.xlabel('Number of Batch') plt.plot(x,np.array(loss_)) plt.savefig('work/Loss During Training.png') plt.show()
7.生成唐诗
7.1 模式一 <首句续写唐诗>
例如:”老夫聊发少年狂“
老夫聊发少年狂,嫁得双鬟梳似桃。
青丝长发娇且红,輭舞脸低时未昏。
娇嚬欲尽一双转,笑语千里相竞言。
朝阳上去花欲尽,花时且落花前过。
秾雨霏霏满地晓,红妆白鸟飞下郭。
灯前织女嫁新租,袖里垂纶舞袖舞。
罗袖焰扬簷下樱,一宿十二花绵绵。
西施夹道春风暖,嫩粉萦丝弄金蘂。
7.2 模式二<藏头诗>
例如:”夜月一帘幽梦春风十里柔情“
夜半星初洽,月明星未稀。
一缄琼烛动,帘外玉环飞。
幽匣光华溢,梦中形影微。
春风吹蕙笏,风绪拂莓苔。
十月涵金井,里尘氛祲微。
柔荑暎肌骨,情酒围唇肌。
# 给定首句生成诗歌 def generate(model, start_words, prefix_words=None): results = list(start_words) start_words_len = len(start_words) # 第一个词语是<START> input = paddle.to_tensor([word2ix['<START>']]) input = paddle.reshape(input,[1,1]) hidden = None # 如有风格前缀,则先用风格前缀生成hidden if prefix_words: # 第一个input是<START>,后面就是prefix中的汉字 # 第一个hidden是None,后面就是前面生成的hidden for word in prefix_words: output, hidden = model(input, hidden) input = paddle.to_tensor([word2ix[word]]) input = paddle.reshape(input,[1,1]) # 开始真正生成诗句,若是没有使用风格前缀,则hidden = None,input = <START> # 不然,input就是风格前缀的最后一个词语,hidden也是生成出来的 for i in range(Config.max_gen_len): output, hidden = model(input, hidden) # print(output.shape) # 若是还在诗句内部,输入就是诗句的字,不取出结果,只为了获得 # 最后的hidden if i < start_words_len: w = results[i] input = paddle.to_tensor([word2ix[w]]) input = paddle.reshape(input,[1,1]) # 不然将output做为下一个input进行 else: # print(output.data[0].topk(1)) _,top_index = paddle.fluid.layers.topk(output[0],k=1) top_index = top_index.numpy()[0] w = ix2word[top_index] results.append(w) input = paddle.to_tensor([top_index]) input = paddle.reshape(input,[1,1]) if w == '<EOP>': del results[-1] break results = ''.join(results) return results # 生成藏头诗 def gen_acrostic(model, start_words, prefix_words=None): result = [] start_words_len = len(start_words) input = paddle.to_tensor([word2ix['<START>']]) input = paddle.reshape(input,[1,1]) # 指示已经生成了几句藏头诗 index = 0 pre_word = '<START>' hidden = None # 存在风格前缀,则生成hidden if prefix_words: for word in prefix_words: output, hidden = model(input, hidden) input = paddle.to_tensor([word2ix[word]]) input = paddle.reshape(input,[1,1]) # 开始生成诗句 for i in range(Config.max_gen_len): output, hidden = model(input, hidden) _,top_index = paddle.fluid.layers.topk(output[0],k=1) top_index = top_index.numpy()[0] w = ix2word[top_index] # 说明上个字是句末 if pre_word in { '。', ',', '?', '!', '<START>'}: if index == start_words_len: break else: w = start_words[index] index += 1 # print(w,word2ix[w]) input = paddle.to_tensor([word2ix[w]]) input = paddle.reshape(input,[1,1]) else: input = paddle.to_tensor([top_index]) input = paddle.reshape(input,[1,1]) result.append(w) pre_word = w result = ''.join(result) return result #读取训练好的模型 model = Peom( len(word2ix), embedding_dim = Config.embedding_dim, hidden_dim = Config.hidden_dim ) state_dict = paddle.load('work/checkpoints/model.params.50') model.set_state_dict(state_dict) print('[*]模式一:首句续写唐诗:') #生成首句续写唐诗(prefix_words为生成意境的诗句) print(generate(model, start_words="春江潮水连海平,", prefix_words="滚滚长江东逝水,浪花淘尽英雄。")) print('[*]模式二:藏头诗:') #生成藏头诗 print(gen_acrostic(model, start_words="夜月一帘幽梦春风十里柔情", prefix_words="落花人独立,微雨燕双飞。"))
下载安装命令 ## CPU版本安装命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle ## GPU版本安装命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu
本文同步分享在 博客“Redflashing”(CSDN)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。