基于seq2seq自动生成小说

基于seq2seq自动生成小说

本次实验基于Seq2seq模型来实现文本生成的模型,输入为一段已知的金庸小说段落,来生成新的段落并做分析。

序列到序列学习(seq2seq)模型

遵循编码器解码器架构的设计原理,RNN 编码器可以采用可变长度序列作为输入,并将其转换为固定形状的隐藏状态。换句话说,输入(源)序列的信息被编码 RNN 编码器的隐藏状态。要通过令牌生成输出序列令牌,单独的 RNN 解码器可以根据已看到的(例如在语言建模中)或生成的令牌以及输入序列的编码信息来预测下一个令牌。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tmUTOAQz-1622185833626)(…/img/seq2seq.svg)]
在seq2seq中,特殊的 “eos” 令牌标志着序列的结束。生成此令牌后,模型可以停止进行预测。在 RNN 解码器的初始时间步,有两个特殊的设计决策。首先,特殊的序列开始 “bos” 令牌是输入。其次,RNN 编码器的最终隐藏状态用于启动解码器的隐藏状态。

编码器

从技术上讲,编码器将可变长度的输入序列转换为固定形状的 * 上下文变量 * c \mathbf{c} c,并在此上下文变量中对输入序列信息进行编码。我们可以使用 RNN 来设计编码器。

假设输入序列是 x 1 , … , x T x_1, \ldots, x_T x1,,xT,因此 x t x_t xt 是输入文本序列中的 t t h t^{\mathrm{th}} tth 标记。在时间步骤 t t t 中,RNN 将 x t x_t xt 的输入要素矢量 x t \mathbf{x}_t xt 和从上一个时间步的隐藏状态 h t − 1 \mathbf{h} _{t-1} ht1 转换为当前隐藏状态 h t \mathbf{h}_t ht。我们可以使用函数 f f f 来表达 RNN 循环层的转换:

h t = f ( x t , h t − 1 ) . \mathbf{h}_t = f(\mathbf{x}_t, \mathbf{h}_{t-1}). ht=f(xt,ht1).

一般来说,编码器通过自定义函数 q q q 将隐藏状态转换为上下文变量:

c = q ( h 1 , … , h T ) . \mathbf{c} = q(\mathbf{h}_1, \ldots, \mathbf{h}_T). c=q(h1,,hT).

例如,当选择 q ( h 1 , … , h T ) = h T q(\mathbf{h}_1, \ldots, \mathbf{h}_T) = \mathbf{h}_T q(h1,,hT)=hT(如 :numref:fig_seq2seq)时,上下文变量只是输入序列在最后一个时间步的隐藏状态 h T \mathbf{h}_T hT

到目前为止,我们使用了单向 RNN 来设计编码器,其中隐藏状态只取决于隐藏状态时间步长和之前的输入子序列。我们还可以使用双向 RNN 构建编码器。在这种情况下,隐藏状态取决于时间步长之前和之后的子序列(包括当前时间步长的输入),后者对整个序列的信息进行编码。

解码器

正如我们刚才提到的,编码器输出的上下文变量 c \mathbf{c} c 对整个输入序列 x 1 , … , x T x_1, \ldots, x_T x1,,xT 进行编码。鉴于训练数据集的输出序列 y 1 , y 2 , … , y T ′ y_1, y_2, \ldots, y_{T'} y1,y2,,yT,对于每个时间步长 t ′ t' t(符号不同于输入序列或编码器的时间步长 t t t),解码器输出 y t ′ y_{t'} yt 的概率取决于之前的输出子序列 y 1 , … , y t ′ − 1 y_1, \ldots, y_{t'-1} y1,,yt1 和上下文变量 c \mathbf{c} c,即 P ( y t ′ ∣ y 1 , … , y t ′ − 1 , c ) P(y_{t'} \mid y_1, \ldots, y_{t'-1}, \mathbf{c}) P(yty1,,yt1,c)

要对序列进行这种条件概率建模,我们可以使用另一个 RNN 作为解码器。在输出序列的任何时间步骤 t ′ t^\prime t,RNN 将上一个时间步的输出 y t ′ − 1 y_{t^\prime-1} yt1 和上下文变量 c \mathbf{c} c 作为输入,然后将它们和之前的隐藏状态 s t ′ − 1 \mathbf{s}_{t^\prime-1} st1 转换为当前时间步长的隐藏状态 s t ′ \mathbf{s}_{t^\prime} st。因此,我们可以使用函数 g g g 来表达解码器隐藏层的转换:

s t ′ = g ( y t ′ − 1 , c , s t ′ − 1 ) . \mathbf{s}_{t^\prime} = g(y_{t^\prime-1}, \mathbf{c}, \mathbf{s}_{t^\prime-1}). st=g(yt1,c,st1).

获得解码器的隐藏状态后,我们可以使用输出层和 softmax 操作来计算时间步骤 t ′ t^\prime t 时输出的条件概率分布 P ( y t ′ ∣ y 1 , … , y t ′ − 1 , c ) P(y_{t^\prime} \mid y_1, \ldots, y_{t^\prime-1}, \mathbf{c}) P(yty1,,yt1,c)

在按如下方式实现解码器时,我们直接使用编码器最后一个时间步的隐藏状态来初始化解码器的隐藏状态。这要求 RNN 编码器和 RNN 解码器具有相同数量的层和隐藏单位。为了进一步合并编码的输入序列信息,上下文变量会在所有时间步长与解码器输入连接起来。为了预测输出令牌的概率分布,使用完全连接的层来转换 RNN 解码器最后一层的隐藏状态。

import torch.nn as nn

class seq_net(nn.Module):
    def __init__(self, onehot_num):
        super(seq_net, self).__init__()
        onehot_size = onehot_num
        embedding_size = 256
        n_layer = 2
        # input [seq_length, batch, embedding_size] and output [seq_length, batch, embedding_size]
        self.lstm = nn.LSTM(embedding_size, embedding_size, n_layer, batch_first=True)
        self.encode =torch.nn.Sequential(
            nn.Linear(onehot_size, embedding_size),
            nn.Dropout(0.5),
            nn.ReLU()
        )
        # self.decode =torch.nn.Sequential(
        #     nn.Linear(embedding_size, 1024),
        #     torch.nn.Dropout(0.5),
        #     torch.nn.ReLU(),
        #     nn.Linear(1024, 2048),
        #     torch.nn.Dropout(0.5),
        #     torch.nn.ReLU(),
        #     nn.Linear(2048, onehot_size),
        #     torch.nn.Softmax()
        # )
        self.decode =torch.nn.Sequential(
            nn.Linear(embedding_size, onehot_size),
            nn.Dropout(0.5),
            nn.Sigmoid()
        )

    def forward(self, x):
        # input [seq_length, onehot_size]
        em = self.encode(x).unsqueeze(dim=1)
        # [seq_length, 1, onehot_size]
        out, (h, c) = self.lstm(em)
        res = 2*(self.decode(out[:,0,:])-0.5)
        return res

import glob
from opencc import OpenCC
opencc = OpenCC('t2s')

path = 'xiaoshuo'

##########################################################################
# Getting file names (book titles)
##########################################################################
f_names = []
for file in glob.glob(path + "/*.txt"):
    f_names.append(file)
print(f_names)
['xiaoshuo\\三十三剑客图.txt', 'xiaoshuo\\书剑恩仇录.txt', 'xiaoshuo\\侠客行.txt', 'xiaoshuo\\倚天屠龙记.txt', 'xiaoshuo\\天龙八部.txt', 'xiaoshuo\\射雕英雄传.txt', 'xiaoshuo\\白马啸西风.txt', 'xiaoshuo\\碧血剑.txt', 'xiaoshuo\\神雕侠侣.txt', 'xiaoshuo\\笑傲江湖.txt', 'xiaoshuo\\越女剑.txt', 'xiaoshuo\\连城诀.txt', 'xiaoshuo\\雪山飞狐.txt', 'xiaoshuo\\飞狐外传.txt', 'xiaoshuo\\鸳鸯刀.txt', 'xiaoshuo\\鹿鼎记.txt']
import jieba.posseg as jp
import jieba
 
 
# 简单文本处理
def get_text(texts):
    flags = ('n', 'nr', 'ns', 'nt', 'eng', 'v', 'd')  # 词性
    stopwords = [line.strip() for line in open('stop_words.txt','r', encoding='utf')]  # 停用词
    words_list = []
    for text in texts:
        words = [w.word for w in jp.lcut(text) if w.flag in flags and w.word not in stopwords]
        words_list.append(words)
    return words_list
import sys
import re
#for file in f_names:
with open(f_names[5], 'r', encoding='ANSI') as file_object:  
    word = file_object.read()
    paras = []          #存放小说段落
    for para in re.split(r'\n',word):    #从小说中将足够长的段落挑选出来
        if(sys.getsizeof(para)>300):
            paras.append(para)
            # print(para)
words_list = get_text(paras)
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\lzl\AppData\Local\Temp\jieba.cache
Loading model cost 0.538 seconds.
Prefix dict has been built successfully.

使用word2vec将所有的分词转化为向量

from gensim.models import Word2Vec

model = Word2Vec(words_list, min_count=5,epochs=300)

model.save('model.model')

调用textgenrnn包

  • 一种现代的神经网络结构,它利用了attention-weighting和 skip-embedding等新技术
  • 可以配置RNN大小、RNN层数以及是否使用双向RNN
  • 在GPU上训练模型,然后用CPU生成文本。在GPU上训练时,利用强大的CuDNN实现RNNs,这大大加快了训练速度
from textgenrnn import textgenrnn
textgen = textgenrnn(name="novel")   # 给模型起个名字
textgen.reset()  
# 从数据文件训练模型
textgen.train_from_file(file_path='./test1.txt', # 文件路径
                        new_model=True, # 训练新模型
                        batch_size=4,
                        rnn_bidirectional=True, # 是否使用Bi-LSTM
                        rnn_size=64,
                        word_level=False, # True:词级别,False:字级别
                        dim_embeddings=300,
                        num_epochs=5, # 训练轮数
                        max_length=25, # 一条数据的最大长度
                        verbose=1)

textgen_2 = textgenrnn(weights_path='novel_weights.hdf5',
                        vocab_path='novel_vocab.json',
                        config_path='novel_config.json')

textgen_2.generate_samples()

输入文本

说话之人正是段誉。他被慕容复摔入井中时已昏晕过去,手足不动,虽入污泥,反不如鸠摩智那麽狼狈。井底狭隘,待得王语嫣跃入井中,偏生这麽巧,脑袋所落之处,正好是段誉胸口的“膻中穴”,一撞之下,段誉便醒了转来。王语嫣跌入他的怀中,非但没丝毫受伤,连污泥业没溅上多少。
  段誉陡觉怀里多了一人,奇怪之极,忽听得慕容复在井口说道:“表妹,你毕竟内心深爱段公子,你二人虽然生不能成为夫妻,但死而同穴,也总算得遂了你的心愿。”这几句话清清楚楚的传到井底,段誉一听之下,不由得痴了,喃喃说道:“甚麽?不,不!我…我…我段誉哪有这等福气?”
  突然间他怀中那人柔声道:“段公子,我真是糊涂透顶,你一直待我这麽好,我…我却…”段誉惊得呆了,问道:“你是王姑娘?”王语嫣道:“是啊!”
  段誉对她素来十分尊敬,不敢稍存丝毫亵渎之念,一听到是她,惊喜之余,急忙站起身来,要将她放开。可是井底地方既窄,又满是污泥,段誉身子站直,两脚便向泥中陷下,泥泞直升至胸口,觉得若将王语嫣放在泥中,实在大大不妥,只得将她身子横抱,连连道歉:“得罪,得罪!王姑娘,咱们身处泥中,只得从权了。”
  王语嫣叹了口气,心下感激。她两度从生到死,又从死到生,对於慕容复的心肠,实已清清楚楚,此刻纵欲自欺,亦复不能,再加段誉对自己一片真诚,两相比较,更显得一个情深意重,一个自私凉薄。她从井口跃到井底,虽只一瞬之间,内心却已起了大大的变化,当时自伤身世,决意一死以报段誉,却不料段誉与自己都没有死,事出意外,当真是满心欢喜。她向来娴雅守礼,端庄自持,但此刻倏经巨变,激动之下,忍不住向段誉吐露心事,说道:“段公子,我只道你已经故世了,想到你对我的种种好处,实在又是伤心,又是後悔,幸好老天爷有眼,你安好无恙。我在上面说的那句话,想必你听见了?”她说到这一句,不由得娇羞无限,将脸藏在段誉颈边。
  段誉於霎时之间,只觉全身飘飘汤汤地,如升云雾,如入梦境,这些时候来朝思暮想的愿望,蓦地里化为真实,他大喜之下,双足一软,登时站立不住,背靠井栏,双手仍是搂著王语嫣的身躯。不料王语嫣好几根头发钻进他的鼻孔,段誉“啊嚏,啊嚏!”接连打了几个喷嚏。王语嫣道:“你…你怎麽啦?受伤了麽?”段誉道:“没…没有…啊嚏,啊嚏…我没有受伤,啊嚏…也不是伤风,是开心得过了头,王姑娘…啊嚏…我喜欢得险些晕了过去。”
  井中一片黑暗,相互间都瞧不见对方。王语嫣微笑不语,满心也是浸在欢乐之中。她自幼痴恋表兄,始终得不到回报,直到此刻,方始领会到两情相悦的滋味。
  段誉结结巴巴的问道:“王姑娘,你刚才在上面说了句甚麽话?我可没有听见。”王语嫣微笑道:“我只道你是个至诚君子,却原来业会使坏。你明明听见了,又要我亲口再说一遍。怪羞人的,我不说。”
  段誉急道:“我…我确没听见,若叫我听见了,老天爷罚我…”他正想罚个重誓,嘴巴上突觉一阵温暖,王语嫣的手掌已按在他嘴上,只听她说道:“不听见就不听见,又有甚麽大不了的事,却值得罚甚麽誓?”段誉大喜,自从识得她以来,她从未对自己有这麽好过,便道:“那麽你在上面究竟说的是什麽话?”王语嫣道:“我说…”突觉一阵 腆,微笑道:“以後再说,日子长著呢,又何必急在一时?”
  “日子长著呢,又何必急在一时?”这句话钻进段誉的耳中,当真如聆仙乐,只怕西方极乐世界中伽陵鸟一齐鸣叫,也没这麽好听,她意思显然是说,她此後将和他长此相守。段誉乍闻好音,兀自不信,问道:“你说,以後咱们能时时在一起麽?”
  王语嫣伸臂搂著他的脖子,在他耳边低声说道:“段郎,只须你不嫌我,不恼我昔日对你冷漠无情,我愿终身跟随著你,再…再也不离开你了。”
  段誉一颗心几乎要从口中跳将出来,问道:“那你表哥怎麽样?你一直…一直喜欢慕容公子的。”王语嫣道:“他却从来没将我放在心上。我直至此刻方才知道,这世界上谁是真的爱我、怜我,是谁把我看得比他自己性命还重。”段誉颤声道:“你是说我?”
  王语嫣垂泪说道:“对啦!我表哥一生之中,便是梦想要做大燕皇帝。本来呢,这也难怪,他慕容氏世世代代,做的便是这个梦。他祖宗几十代做下来的梦,传到他身上,怎又能盼望他醒觉?我表哥原不是坏人,只不过为了想做大燕皇帝,别的甚麽事都搁在一旁了。”
  段誉听她言语之中,大有为慕容复开脱分辨之意,心中又焦急起来,道:“王姑娘,倘若你表哥一旦悔悟,忽然又对你好了,那你…你…怎麽样?”
  王语嫣叹道:“段郎,我虽是个愚蠢女子,却决不是丧德败行之人,今日我和你定下三生之约,若再三心两意,岂不有亏名节?又如何对得起你对我的深情厚意?”
  段誉心花怒放,抱著她身子一跃而起,“啊哈”一声,拍的一声响,重又落入污泥之中,伸嘴过去,便要吻她樱唇。王语嫣宛转相就,四唇正欲相接,突然间头顶呼呼风响,甚麽东西落将下来。
  两人吃了一惊,忙向井栏2边一靠,砰的一声响,有人落入井中。

实验结果

Temperature: 0.2
####################   段誉结结结巴的问道:“段公子,我表哥原来业会使坏。你明明听见,又是是伤心,又要吻她樱唇。”段誉颤声道:“段公子,却从未对你…

段誉结结结巴的问道:“段公子,我虽是个愚蠢女子,却原来业会使坏。你明明听见,又要我亲口说,我亲口说道:“段公子,却不!我表哥原不是坏人,只道你表哥一生之间,只觉全身飘飘汤地,如升云雾,如入梦境
,这些时候来朝思暮想的愿望,蓦地里化为真实,他大喜之中,当真如聆仙乐,只怕西方极乐世界伽陵鸟一齐鸣叫,也没有这麽好,老天爷有这麽好,我听见,若叫我…

段誉结结巴的问道:“段公子,我虽是个愚蠢女子,却决不是丧德败行之人,今日我和你定下三生之约,若再三心两意,岂不有亏名节??”段誉对自己有死,事出意外,当真是满心欢乐,只须你不嫌我,我愿终身跟随
著你,再…

#################### Temperature: 0.5
####################   段誉一颗心几乎要从口气,心大喜之中,当时忙站起来,身子站直,两脚便要将脸藏在他长此相守。段誉乍闻好音,兀自不信,问道:“那你说,我表哥一生之中,便是梦想要做燕皇帝。本来呢,这也总算得遂你了,想
到你一直,不到回报,直到此刻,方始领到两情意,?”段誉颤声道:“你是说,我…啊嚏,啊嚏,啊嚏…我喜欢得险些晕了。”

段誉结结结巴的问道:“段公子,却…我喜欢得险些晕了过去。”

王语嫣垂泪说道:“段郎,我虽是个愚蠢女子,却不料段誉乍闻好音,兀自不信,问道:“你是说,日子长著呢,又何必急在一时?”

#################### Temperature: 1.0
####################   段誉结结巴巴一阵暖了,一软,登站起,“说

王语嫣叹了口气气?不到是对自己一死生,他耳中,当真如聆仙乐世界中伽陵鸟巴上面了过去。怪,我他突然是爱段誉。王语嫣霎时之念,一死又从来一来朝思的显然是井口跃到井底,虽是个梦,传到他之中,激动之
意,一直升气福底地方始领会到两相悦的滋味。”

段誉一颗心怒放,的抱著他身躯。他软的一跃到这好,老天爷有从呆了,喃喃一生之极,素要做姑娘,我虽然又又能盼望他大喜,我只怕从口到你对我的种种好处,实在又是伤心,几个情厚意?”他上了想要一立不语之
中,全身身飘汤地,升既窄,伸臂搂著他的变化,当真如何对在你说的那句话钻进段誉自己都没有这麽好过。”

#################### Temperature: 0.2
####################   段誉一颗心几乎要从口中跳将出来,问道:“你是说道:“段公子,却决不是丧德败行之人,今日我和你定下三生之约,若再三心两意,岂不不由得娇羞无限,将脸藏在段誉颈边。

段誉结结结巴巴的问道:“段公子,却…

段誉结结结巴的问道:“段郎,我只道你已经故世世界上上,是谁把我看得比自己性命还重。”段誉颤声道:“段公子,我…

#################### Temperature: 0.5
####################   段誉结结巴问道:“段郎,只须你嫌我,我看得比较,更显得一个情意,岂不不住,背靠井底地,方既窄,又满是污泥,段誉身子站起,“啊哈”一声,拍的一声响,重又落入污泥中。她自幼痴恋表兄,始终身飘飘汤汤地
,如升云雾,如入梦境,这些时时候朝思暮想到你对的我和他祖宗几十代做的便是梦,传到他身子,我我亲口才在上面说我在上面说了句话,想必你听见了,想要做燕皇帝,别的甚麽东西落将下来。

段誉结结结巴的问道:“段公子,却从未对你…我…我喜欢慕容公子的。”段誉乍闻好音,兀自不信,问
道:“是说,我虽是个愚蠢女子,我虽只你是丧德败之意,岂不有亏名节??”段誉对自己都没有死,事出来业会使坏。她自幼痴恋表兄,始终得到生,不由得娇羞羞人的我,我愿终身处,实在又是污泥,中陷下,忍不住段
誉听她身子,我虽然又能成为夫妻,但死而同穴,也总算得遂了了,想到的是个情厚意?”段誉颤声道:“段公子

段誉结结结住,问道:“…我…我喜欢慕容公子,却…我喜欢慕容公子的。”

#################### Temperature: 1.0
####################   段誉结巴觉怀中横连连道“王姑娘。王姑娘,咱们身处泥之中,问道你氏世界上到是後悔,幸好我始终得情意,事意外,这巨无限,将脸藏在段誉颈边。

段誉陡觉怀里多了一听到这候们身世,至脚梦传到他的此相相就喜之意,?不听见心又至泞直至此刻真诚君子,却决不岂有眼,你我值罚我深意,一直待我、怜我,我…我…我?”

段誉与自己性命还道:“没听见,了生不会到化,为不背靠井口,只觉全身飘飘飘飘汤一软,如直到刻纵欲相比悦的种好,处实在又是污泥。他声後咱们身泥泥,

总结

通过循环输入文本学习小说的语法及写作风格,并生成了一些文本,虽然时间和算力限制,没有对模型进行很好的改进和调节,但是对LSTM以及Seq2Seq模型的训练和测试过程有了一定的体会,也对文本生成的人物有了一定的了解。在实验中,我也体会到复杂的模型并不一定带来更好的效果,通过一些采样方法或数据处理手段往往可以很直观的提升文本生成的效果。
最后通过5轮训练,乍一看实验结果稍微有点金庸小说的味道,但细细读一下还是存在很多问题,还需要很多改进。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值