本模型采用的是字符级别的诗歌生成(pytorch)

环境:

python3.X

pytorch GPU或CPU版本都行,

另外天有点冷,建议用GPU训练,电脑绝对比暖手宝好用

目录

项目文件结构:

数据已经打包:

1、数据集处理

2、构建模型与训练模型

基于概率语言模型的模型

网络结构

网络输入输出

损失函数

3、生成诗歌

4、配置文件

5、生成效果

6、结语

参考:


项目文件结构:

data:存放预处理好的数据

model:存放训练好的模型

config.py:配置文件

dataHandler.py:数据预处理及生成词典

model.py:模型文件

train.py:训练模型

generation.py:生成诗歌

poetry.txt:全唐诗,四万多首,中华民族艺术瑰宝。

数据已经打包:

链接:https://pan.baidu.com/s/1UAJFf3kKERm_XR0qRNneig 
提取码:e3y5

1、数据集处理

以四万首唐诗的文本作为训练集

它长这样:

文本中每行是一首诗,且使用冒号分割,前面是标题,后面是正文,且诗的长度不一。

对数据的处理流程大致:

  1. 读取文本,按行切分,构成古诗列表。
  2. 将全角、半角的冒号统一替换成半角的。
  3. 按冒号切分诗的标题和内容,只保留诗的内容。
  4. 最后根据诗的内容构建词典,并将处理好的数据保

处理后的诗歌大概长这样

代码如下:

# dataHandler.py
import numpy as np
from config import Configdef pad_sequences(sequences,maxlen=None,dtype='int32',padding='pre',truncating='pre',value=0.):"""# 填充code from kerasPads each sequence to the same length (length of the longest sequence).If maxlen is provided, any sequence longerthan maxlen is truncated to maxlen.Truncation happens off either the beginning (default) orthe end of the sequence.Supports post-padding and pre-padding (default).Arguments:sequences: list of lists where each element is a sequencemaxlen: int, maximum lengthdtype: type to cast the resulting sequence.padding: 'pre' or 'post', pad either before or after each sequence.truncating: 'pre' or 'post', remove values from sequences larger thanmaxlen either in the beginning or in the end of the sequencevalue: float, value to pad the sequences to the desired value.Returns:x: numpy array with dimensions (number_of_sequences, maxlen)Raises:ValueError: in case of invalid values for `truncating` or `padding`,or in case of invalid shape for a `sequences` entry."""if not hasattr(sequences, '__len__'):raise ValueError('`sequences` must be iterable.')lengths = []for x in sequences:if not hasattr(x, '__len__'):raise ValueError('`sequences` must be a list of iterables. ''Found non-iterable: ' + str(x))lengths.append(len(x))num_samples = len(sequences)if maxlen is None:maxlen = np.max(lengths)# take the sample shape from the first non empty sequence# checking for consistency in the main loop below.sample_shape = tuple()for s in sequences:if len(s) > 0:  # pylint: disable=g-explicit-length-testsample_shape = np.asarray(s).shape[1:]breakx = (np.ones((num_samples, maxlen) + sample_shape) * value).astype(dtype)for idx, s in enumerate(sequences):if not len(s):  # pylint: disable=g-explicit-length-testcontinue  # empty list/array was foundif truncating == 'pre':trunc = s[-maxlen:]  # pylint: disable=invalid-unary-operand-typeelif truncating == 'post':trunc = s[:maxlen]else:raise ValueError('Truncating type "%s" not understood' % truncating)# check `trunc` has expected shapetrunc = np.asarray(trunc, dtype=dtype)if trunc.shape[1:] != sample_shape:raise ValueError('Shape of sample %s of sequence at position %s is different from ''expected shape %s'% (trunc.shape[1:], idx, sample_shape))if padding == 'post':x[idx, :len(trunc)] = truncelif padding == 'pre':x[idx, -len(trunc):] = truncelse:raise ValueError('Padding type "%s" not understood' % padding)return xdef load_poetry(poetry_file, max_gen_len):# 加载数据集with open(poetry_file, 'r', encoding='utf-8') as f:lines = f.readlines()# 将冒号统一成相同格式lines = [line.replace(':', ':') for line in lines]# 数据集列表poetry = []# 逐行处理读取到的数据for line in lines:# 有且只能有一个冒号用来分割标题if line.count(':') != 1:continue# 后半部分不能包含禁止词,如果包含,直接跳过这一首诗_, last_part = line.split(':')if '(' in line:last_part = last_part.split('(')[0]# 长度不能超过最大长度if len(last_part) > max_gen_len - 2:continue# 去除这些有禁用词的诗if '【' in line or '_' in line:continuepoetry.append(last_part.replace('\n', ''))# 随机打乱顺序np.random.shuffle(poetry)return poetrydef get_data(config):# 1.获取数据data = load_poetry(config.poetry_file, config.max_gen_len)for poetry in data:print(poetry)# 2.构建词典chars = {c for line in data for c in line}char_to_ix = {char: ix for ix, char in enumerate(chars)}char_to_ix['<EOP>'] = len(char_to_ix)char_to_ix['<START>'] = len(char_to_ix)char_to_ix['</s>'] = len(char_to_ix)ix_to_chars = {ix: char for char, ix in list(char_to_ix.items())}# 3.处理样本# 3.1 每首诗加上首位符号for i in range(0, len(data)):data[i] = ['<START>'] + list(data[i]) + ['<EOP>']# 3.2 文字转iddata_id = [[char_to_ix[w] for w in line] for line in data]# 3.3 补全既定长度,即填充pad_data = pad_sequences(data_id,maxlen=config.poetry_max_len,padding='pre',truncating='post',value=len(char_to_ix) - 1)# 3.4 保存处理好的数据np.savez_compressed(config.processed_data_path,data=pad_data,word2ix=char_to_ix,ix2word=ix_to_chars)return pad_data, char_to_ix, ix_to_charsif __name__ == '__main__':config = Config()pad_data, char_to_ix, ix_to_chars = get_data(config)for l in pad_data[:10]:print(l)n = 0for k, v in char_to_ix.items():print(k, v)if n > 10:breakn += 1n = 0for k, v in ix_to_chars.items():print(k, v)if n > 10:breakn += 1

2、构建模型与训练模型

基于概率语言模型的模型

我们是基于n-gram语言模型的思想,n-gram模型作了一个n−1阶的Markov假设,即认为一个词出现的概率只与它前面的n−1个词相关,所以我们可以通过前n个词预测下一个词,公式表示为:

STM可以考虑序列之间的联系,所以我们选择LSTM作为本次练习的网络。LSTM详细原理请参考:(31条消息) LSTM这一篇就够了_yingqubaifumei的博客-CSDN博客_lstm细胞状态https://blog.csdn.net/yingqubaifumei/article/details/100888147?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164289666916780357223324%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=164289666916780357223324&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-100888147.first_rank_v2_pc_rank_v29&utm_term=LSTM&spm=1018.2226.3001.4187

网络结构

基于语言的概率模型本质就是分类模型,基于前面n个字对下一个字进行预测,找到概率最大的字便是预测结果。其网络结构如下:

从图中可以看出,本模型使用了1层词嵌入层,2层LSTM,一层全连接层。

网络输入输出

训练数据 x和标签 y,将诗的内容错开一位分别作为数据和标签,举个例子,假设有诗是“床前明月光,疑是地上霜。举头望明月,低头思故乡。”,则数据为“床前明月光,疑是地上霜。举头望明月,低头思故乡。”,标签为“床前明月光,疑是地上霜。举头望明月,低头思故乡。”,两者一一对应,y 是 x 中每个位置的下一个字符。

以字符的形式举例是为了方便理解,实际上不论是x还是y,都是将字符编码后的编号序列(数字),这样才能输入神经网络。

损失函数

损失函数采用交叉熵损失函数CrossEntropyLoss

代码如下:

# model.py
import torch
import torch.nn as nn
from torch.autograd import Variable
import torch.nn.functional as Fclass PoetryModel(nn.Module):def __init__(self, vocab_size, embedding_dim, hidden_dim, device, layer_num):super(PoetryModel, self).__init__()self.hidden_dim = hidden_dim# 创建embedding层self.embeddings = nn.Embedding(vocab_size, embedding_dim)# 创建lstm层,参数是输入输出的维度self.lstm = nn.LSTM(embedding_dim, self.hidden_dim, num_layers=layer_num)# 创建一个线性层self.linear1 = nn.Linear(self.hidden_dim, vocab_size)# 创建一个dropout层,训练时作用在线性层防止过拟合self.dropout = nn.Dropout(0.2)self.device = devicedef forward(self, inputs, hidden):seq_len, batch_size = inputs.size()# 将one-hot形式的input在嵌入矩阵中转换成嵌入向量,torch.Size([length, batch_size, embedding_size])embeds = self.embeddings(inputs)# 经过lstm层,该层有2个输入,当前x和t=0时的(c,a),# output:torch.Size([length, batch_size, hidden_idm]), 每一个step的输出# hidden: tuple(torch.Size([layer_num, 32, 256]) torch.Size([1, 32, 256])) # 最后一层输出的ct 和 ht, 在这里是没有用的output, hidden = self.lstm(embeds, hidden)# 经过线性层,relu激活层 先转换成(max_len*batch_size, 256)维度,再过线性层(length, vocab_size)# output = F.relu(self.linear1(output.view(seq_len*batch_size, -1)))output = F.log_softmax(self.linear1(output.view(seq_len * batch_size, -1)), dim=1)# 输出最终结果,与hidden结果return output, hiddendef init_hidden(self, layer_num, batch_size):return (Variable(torch.zeros(layer_num, batch_size, self.hidden_dim)).cuda(),Variable(torch.zeros(layer_num, batch_size, self.hidden_dim)).cuda())
# train.py
import os
import torch
import torch.utils.data as Data
import torch.nn as nn
import torch.optim as optim
from model import PoetryModel
from dataHandler import *
from config import Configclass TrainModel(object):def __init__(self):os.environ["CUDA_VISIBLE_DEVICES"] = '0'self.config = Config()self.device = torch.device('cuda') if self.config.use_gpu else torch.device('cpu')def train(self, data_loader, model, optimizer, criterion, char_to_ix, ix_to_chars):for epoch in range(self.config.epoch_num):for step, x in enumerate(data_loader):# 1.处理数据# x: (batch_size,max_len) ==> (max_len, batch_size)x = x.long().transpose(1, 0).contiguous()x = x.to(self.device)optimizer.zero_grad()# input,target:  (max_len, batch_size-1)input_, target = x[:-1, :], x[1:, :]target = target.view(-1)# 初始化hidden为(c0, h0): ((layer_num, batch_size, hidden_dim),(layer_num, batch_size, hidden_dim))hidden = model.init_hidden(self.config.layer_num, x.size()[1])# 2.前向计算# print(input.size(), hidden[0].size(), target.size())output, _ = model(input_, hidden)loss = criterion(output, target) # output:(max_len*batch_size,vocab_size), target:(max_len*batch_size)# 反向计算梯度loss.backward()# 权重更新optimizer.step()if step == 0:print('epoch: %d,loss: %f' % (epoch, loss.data))if epoch % 5 == 0:# 保存模型torch.save(model.state_dict(), '%s_%s.pth' % (self.config.model_prefix, epoch))# 分别以这几个字作为诗歌的第一个字,生成一首藏头诗,用于看看训练时的效果word = '春江花月夜凉如水'gen_poetry = ''.join(self.generate_head_test(model, word, char_to_ix, ix_to_chars))print(gen_poetry)def run(self):# 1 获取数据data, char_to_ix, ix_to_chars = get_data(self.config)vocab_size = len(char_to_ix)print('样本数:%d' % len(data))print('词典大小: %d' % vocab_size)# 2 设置dataloaderdata = torch.from_numpy(data)data_loader = Data.DataLoader(data,batch_size=self.config.batch_size,shuffle=True,num_workers=0)# 3 创建模型model = PoetryModel(vocab_size=vocab_size,embedding_dim=self.config.embedding_dim,hidden_dim=self.config.hidden_dim,device=self.device,layer_num=self.config.layer_num)model.to(self.device)# 4 创建优化器optimizer = optim.Adam(model.parameters(), lr=self.config.lr, weight_decay=self.config.weight_decay)# 5 创建损失函数,使用与logsoftmax的输出criterion = nn.CrossEntropyLoss()# 6.训练self.train(data_loader, model, optimizer, criterion, char_to_ix, ix_to_chars)def generate_head_test(self, model, head_sentence, word_to_ix, ix_to_word):"""生成藏头诗"""poetry = []head_char_len = len(head_sentence)  # 要生成的句子的数量sentence_len = 0  # 当前句子的数量pre_char = '<START>'  # 前一个已经生成的字# 准备第一步要输入的数据input = (torch.Tensor([word_to_ix['<START>']]).view(1, 1).long()).to(self.device)hidden = model.init_hidden(self.config.layer_num, 1)for i in range(self.config.max_gen_len):# 前向计算出概率最大的当前词output, hidden = model(input, hidden)top_index = output.data[0].topk(1)[1][0].item()char = ix_to_word[top_index]# 句首的字用藏头字代替if pre_char in ['。', '!', '<START>']:if sentence_len == head_char_len:breakelse:char = head_sentence[sentence_len]sentence_len += 1input = (input.data.new([word_to_ix[char]])).view(1,1)else:input = (input.data.new([top_index])).view(1,1)poetry.append(char)pre_char = charreturn poetry

3、生成诗歌

# -*- coding: utf-8 -*-
# generation.py
import os
from config import Config
import numpy as np
from model import PoetryModel
import torchclass Sample(object):def __init__(self):self.config = Config()self.device = torch.device('cuda') if self.config.use_gpu else torch.device('cpu')self.processed_data_path = self.config.processed_data_pathself.model_path = self.config.model_pathself.max_len = self.config.max_gen_lenself.sentence_max_len = self.config.sentence_max_lenself.load_data()self.load_model()def load_data(self):if os.path.exists(self.processed_data_path):data = np.load(self.processed_data_path, allow_pickle=True)self.data, self.word_to_ix, self.ix_to_word = data['data'], data['word2ix'].item(), data['ix2word'].item()def load_model(self):model = PoetryModel(len(self.word_to_ix),self.config.embedding_dim,self.config.hidden_dim,self.device,self.config.layer_num)map_location = lambda s, l: sstate_dict = torch.load(self.config.model_path, map_location=map_location)model.load_state_dict(state_dict)model.to(self.device)self.model = modeldef generate_random(self, start_words='<START>'):"""自由生成一首诗歌"""poetry = []sentence_len = 0input = (torch.Tensor([self.word_to_ix[start_words]]).view(1, 1).long()).to(self.device)hidden = self.model.init_hidden(self.config.layer_num, 1)for i in range(self.max_len):# 前向计算出概率最大的当前词output, hidden = self.model(input, hidden)top_index = output.data[0].topk(1)[1][0].item()# _probas = output.data[0].cpu().numpy()  # 在GPU上的Tensor没办法直接转numpy,先转到CPU再转成numpy# # 按照出现概率,对所有token倒序排列# p_args = np.argsort(-_probas)[:2]# # 排列后的概率顺序# p = _probas[p_args]# # 先对概率归一# p = p / sum(p)# # 再按照预测出的概率,随机选择一个词作为预测结果# choice_index = np.random.choice(len(p), p=p)# top_index = p_args[choice_index]char =self.ix_to_word[top_index]# 遇到终结符则输出if char == '<EOP>':break# 有8个句子则停止预测if char in ['。', '!']:sentence_len += 1if sentence_len == 8:poetry.append(char)breakinput = (input.data.new([top_index])).view(1, 1)poetry.append(char)return poetrydef generate_head(self, head_sentence):"""生成藏头诗"""poetry = []head_char_len = len(head_sentence)  # 要生成的句子的数量sentence_len = 0  # 当前句子的数量pre_char = '<START>'  # 前一个已经生成的字# 准备第一步要输入的数据input = (torch.Tensor([self.word_to_ix['<START>']]).view(1, 1).long()).to(self.device)hidden = self.model.init_hidden(self.config.layer_num, 1)for i in range(self.max_len):# 前向计算出概率最大的当前词output, hidden = self.model(input, hidden)top_index = output.data[0].topk(1)[1][0].item()# _probas = output.data[0].cpu().numpy()  # 在GPU上的Tensor没办法直接转numpy,先转到CPU再转成numpy# # 按照出现概率,对所有token倒序排列# p_args = np.argsort(-_probas)[:3]# # 排列后的概率顺序# p = _probas[p_args]# # 先对概率归一# p = p / sum(p)# # 再按照预测出的概率,随机选择一个词作为预测结果# choice_index = np.random.choice(len(p), p=p)# top_index = p_args[choice_index]char = self.ix_to_word[top_index]# 句首的字用藏头字代替if pre_char in ['。', '!', '<START>']:if sentence_len == head_char_len:breakelse:char = head_sentence[sentence_len]sentence_len += 1input = (input.data.new([self.word_to_ix[char]])).view(1,1)else:input = (input.data.new([top_index])).view(1,1)poetry.append(char)pre_char = charreturn poetrydef generate_poetry(self, mode=1, head_sentence=None):"""模式一:随机生成诗歌模式二:生成藏头诗:return:"""poetry = ''if mode == 1 or (mode == 2 and head_sentence is None):poetry = ''.join(self.generate_random())if mode == 2 and head_sentence is not None:head_sentence = head_sentence.replace(',', u',').replace('.', u'。').replace('?', u'?')poetry = ''.join(self.generate_head(head_sentence))return poetryif __name__ == '__main__':obj = Sample()poetry1 = obj.generate_poetry(mode=2, head_sentence="碧海潮生")print(poetry1)

4、配置文件

为了方便修改和调试模型,基本上所有的可以改的参数都放到配置模型文件中。很简单

# -*- encoding: utf-8 -*-
# config.pyclass Config(object):processed_data_path = "data/tang.npz"  # 保存的数据预处理数据model_path = 'model/tang_5.pth'  # 载入的训练好的模型model_prefix = 'model/tang'  # 模型的保存batch_size = 64  # batch_sizeepoch_num = 10  # 训练的迭代次数epochembedding_dim = 128  # 嵌入层的维度hidden_dim = 128  # LSTM的隐藏层的维度layer_num = 2  # LSTM的层数lr = 0.001  # 初始化学习率weight_decay = 1e-4  # Adam优化器的weight_decay参数use_gpu = True  # 是否使用GPU训练poetry_file = 'poetry.txt'  # 诗歌文件名称max_gen_len = 200  # 生成诗歌最长长度sentence_max_len = 4 # 生成诗歌的最长句子poetry_max_len = 125sample_max_len = poetry_max_len - 1

5、生成效果

怎么说呢,有点那味了。

桃殿开华碧色沈,

花间似水水间流。

影中云落风明月,

落日生光未出分。

碧云行自在,

海路几悠游。

潮暗遥还夜,

生游日见长。

6、结语

初次尝试用pytorch,有纰漏之处,还望指正,模型还有待改进。

参考:

基于循环神经网络(RNN)的古诗生成器_python_脚本之家 (jb51.net)https://www.jb51.net/article/137118.htm

(31条消息) TensorFlow练手项目二:基于循环神经网络(RNN)的古诗生成器_笔墨留年。-CSDN博客_rnn项目https://blog.csdn.net/aaronjny/article/details/79677457

手把手实现AI诗歌生成(AI写诗)相关推荐

  1. AI:为你写诗,为你做不可能的事

    戳蓝字"CSDN云计算"关注我们哦!  最近,一档全程高能的神仙节目,高调地杀入了我们的视野: 没错,就是撒贝宁主持,董卿.康辉等央视名嘴作为评审嘉宾,同时集齐央视"三大 ...

  2. 谷歌AI要为你写诗!让诗意文字浮现在你自拍头像上

    作者|宇伊     出品 | 新芒X 同步首发至 新芒 xinmang.ai 让机器人写诗? 好像不怎么稀奇了.比如微软小冰就有了这个本领,上传一张图片,会假装通过意向抽取,灵感激发,文学风格模型构思 ...

  3. 用Python打造一个AI作家为你写诗(附源码)

    从短篇故事到长达5万词的小说,机器正以不可思议的方式"把玩"文字.网上已经涌现很多例子,越来越多人让机器创作文字作品. 其实,由于自然语言处理(NLP)领域的重大进步,如今计算机的 ...

  4. 惊!揭秘AI人工智能机器人自动写诗的奥秘!

    最近央视某综艺节目中一个AI机器人随机为知名主持人撒贝宁即兴赋诗一首的事情在网络上热传,那么这个AI机器人究竟是如何在这么短的时间内根据一个人的名字写出这么优秀的诗作,甚至连撒贝宁自称为"千 ...

  5. 深度学习框架PyTorch入门与实践:第九章 AI诗人:用RNN写诗

    我们先来看一首诗. 深宫有奇物,璞玉冠何有. 度岁忽如何,遐龄复何欲. 学来玉阶上,仰望金闺籍. 习协万壑间,高高万象逼. 这是一首藏头诗,每句诗的第一个字连起来就是"深度学习". ...

  6. 【PyTorch实战】用RNN写诗

    用RNN写诗 1. 背景 1.1 词向量 1.2 RNN 2. CharRNN 3. 用PyTorch实现CharRNN 4. 结果分析 参考资料 1. 背景 自然语言处理(Natural Langu ...

  7. 清华团队让 AI 写诗“更上一层楼”,诗歌图灵测试迷惑近半数玩家

    作者 | 黄珊 来源 | 数据实战派 比特币 外挖无穷洞,机神犹未休. 卡中窥币影,池里验沙流. 屡载吸金主,孤深渍盗求. 方知区块链,本是古来游. 这首诗歌来自一支清华团队开发的古诗 AI.它的创作 ...

  8. Python 爬虫 之 爬取古代的诗歌,并保存本地(这里以爬取李白的所有诗歌为例)(以备作为AI写诗的训练数据)

    Python 爬虫 之 爬取古代的诗歌,并保存本地(这里以爬取李白的所有诗歌为例)(以备作为AI写诗的训练数据) 目录

  9. 【PyTorch】3 AI诗人RNN实战(LSTM)——完成诗歌剩余部分、生成藏头诗

    用RNN写诗 1. 背景 2. 数据描述 3. 数据使用 4. LSTM函数 5. Model 6. Train 7. Test 8. 全部代码 小结 1. 背景 书上的内容可见此,一些关于此的博客1 ...

  10. TensorFlow文本生成(AI 写诗)

    俗话说的好,"熟读唐诗三百首,不会作诗也会吟".吟诗作对我是做不到的,那就训练一个模型,让它去"背书"吧,背完了再看看它学的怎么样. 当然这里的写诗肯定不会照搬 ...

最新文章

  1. 比特币现金将出新招,推动比特币现金使用
  2. RabbitMQ 官方NET教程(二)【工作队列】
  3. linux网络配置详细
  4. 【机器学习基础】前置知识(五):30分钟掌握常用Matplotlib用法
  5. Xamarin效果第一篇之时间轴
  6. 洛谷 P2725 邮票题解
  7. php pg_fetch_row,pg_fetch_row
  8. Linux下实现一个网卡绑定多个IP地址
  9. bzoj 4561: [JLoi2016]圆的异或并(扫描线+set)
  10. Swing-文本输入组件(一)
  11. Bootstrap3 表单-水平排列的表单
  12. AD16创建元器件库步骤
  13. 英语语法高考英语单词拼写必背全表
  14. ajax方法参数详解,$.ajax()方法参数详解
  15. Kafka kafka-reassign-partitions.sh 命令使用
  16. 一种永不止步的进取精神的勤奋
  17. PAKDD 2021 智能运维算法赛技术分享(精彩直播回放)
  18. [转贴]关于理工科学生应聘非本专业职位的一些建议
  19. 信奥学习规划 信息学竞赛之路(2022.07.31)
  20. 区块链技术应用落地区块链溯源应用

热门文章

  1. python之matplotlib画二元函数图像
  2. PLC可编程控制实验装置及单片机综合实验台
  3. puzzle(010.1)自我指涉的选择题
  4. 【树莓派】挂载移动硬盘 使用transmission 刷pt站
  5. [Android 4.4.2] 泛泰A870 Mokee4.4.2 20140531 RC1.0 by syhost
  6. python 微商城_微商城是自己开发好还是用第三方平台好?
  7. html项目符号实心圆圈,HTML基础 ul type 项目符号为圆圈与方块
  8. 计算机无法连接共享打印机,共享打印机无法连接,小编教你共享打印机无法连接怎么办...
  9. python课本图片_python爬虫当当网python书籍图片
  10. AWS学习(一)——AWS云技术基础