用nltk模仿莎士比亚写十四行诗

前言

找完《大道争锋》中的诗句还不过瘾,如果能自己来写才有点意思。于是我就从英文诗开始,用莎士比亚的风格来写十四行诗。

开始编程

寻找素材

既然要模仿莎士比亚写十四行诗,首先就得搞到莎士比亚的原文,给我们的AI一个学习素材。在国内找纯净一点的资源属实不容易,动辄就是各种付费,上个梯子出去别人直接把原文大大方方贴出来,没有对比就没有伤害啊。

莎士比亚十四行诗全文:http://www.shakespeares-sonnets.com/Archive/allsonn.htm

可以看到语料还是非常纯净的:
在这里插入图片描述

处理语料

即使这个语料已经非常的纯净,但是要作为提供给AI的输入还是有所欠缺。比如由罗马数字构成的编号。同时,我们在整理词库时并不希望区分大小写,需要统一地转换为小写,于是就有了如下代码:

import nltk

#将所有诗句划分为单词储存
sentences=[]
with open('sonnets.txt') as f:
    for line in f.readlines():
        line=line.strip().lower()
        if len(line)>10:
            sentences.append(nltk.word_tokenize(line))

这里我们没有再使用之前的f=open()结构,而是用with open() as f,这样的好处是当我们退出该代码块时,open()会自动结束而不用我们手动close()

同时,我们也没有用f.read()来读入,因为这次是写诗,最好是把每一行都分隔开来。于是我们采用f.readlines()读入,这个函数同样是把整个文本读进来,但不同的是它返回的是一个列表,表中的元素是一行一行的字符串。

我们用strip()来去掉开头的奇怪字符,用lower()将所有字母转换为小写,判断一下字符串长度从而筛去诗歌的标号。

最后,用nltk.word_tokenize()将每行诗句里的单词提取出来,构成一个二维列表。
在这里插入图片描述

建立词库

做完初步处理之后,我们要建立一个词库,把莎士比亚用到的词整理成一个列表。

#建立词库
voc=set()
for line in sentences:
    for word in line:
        voc.add(word)
voc.add('<s>')
voc.add('</s>')
voc=list(voc)

先开一个set用于去重,然后直接遍历整个文本,往集合里添加元素,最后再加上一个起始符一个休止符(这个的作用后面再说)。最后,用list()函数将集合转换为列表。

提取N元语法链

本次AI写诗用到的是N-gram算法,简单来说就是通过前n-1个词推测第n个词,即一个n-1阶马尔可夫链:将来发生的事只与前n-1个状态有关,与更之前的无关。所以我们要先把文本拆分成n个单词一组的N元语法链:

#构建N元语法链,n=3
n=3
all_ngrams=[]
for line in sentences:
    paddedline=nltk.lm.preprocessing.pad_both_ends(line,n)
    ngrams=list(nltk.ngrams(paddedline,n))
    all_ngrams.append(ngrams)

其中,nltk.lm.preprocessing.pad_both_ends(line,n)函数可以帮我们填充一行诗句的两端,开头填充<s>,结尾填充</s>,这也是为什么要在词库中加入这两个字符。

nltk.ngrams(paddedline,n)函数则可以直接对给定的n拆分出N元语法链,甚至不用自己动手。

在这里插入图片描述

训练模型

这里我们用的是MLE(最大似然估计)模型,有一篇博客讲得比较好。简单来说,是给定模型,找一个参数使模型出现的概率最大。

比如说,从一个口袋里摸球并放回,连摸十次,结果是9个黑球1个白球,参数为黑球占总球数的比例,模型是连摸十次9黑1白,我们就需要找到一个比例,让这个模型出现的概率最大。很显然,这个比例为0.9。

回到写诗上,我们所用的模型就是给出n-1个词,找到第n个词,使得前n-1个词这么出现的概率最大。

以上都是原理部分,事实上nltk库已经封装好了,只管用就行。(Python真是太强辣!)

#训练模型
lm=nltk.lm.MLE(n)
lm.fit(all_ngrams,voc)

fit()函数有两个参数,一个是他的“学习资料”,即我们之前拆分出来的N元语法链;还有就是我们的词库。这两个参数均为列表形式。

写诗

模型训练完毕后,只要用起来就可以了:

#写诗!
sonnet=[]
text_seed=['<s>']*(n-1)
while len(sonnet)<14:
    while True:
        try:
            line=lm.generate(8,text_seed=text_seed)
        except ValueError:
            continue
        else:
            line=[word for word in line if word not in['<s>','</s>']]
            sonnet.append(" ".join(line))
            break
print("\n".join(sonnet))

这里运用的方法比较简单粗暴,有很多可以优化的地方。比如我的文本种子均为n-1个<s>,直接让程序以莎士比亚惯用的开头开始写。如果想更有针对性或者上下文更连贯,可以自定义种子或者用上一句生成的诗句里的词当种子;另外我这里规定了单词数(包括标点符号)为8个,也就是说每行诗句都是8个单词,而莎士比亚显然不是这么写的,这就会导致每句话不完整,最理想的情况应该是结尾为</s>时作为合格的输出保存。但是由于我使用的封装好的函数,修改无从下手只能作罢。

值得一提的是,在这里我第一次运用了try结构。因为不一定每次都能生成出诗句来,如果报错了代码块就会停止运行,需要重新启动。而try结构就能在报错时让程序接着运行,保留之前正确运行出的结果。

还有line=[word for word in line if word not in['<s>','</s>']]语句,算时远超C艹的一种简洁写法,直接把列表中的<s></s>给筛去了,简单明了,可读性也高。

运行结果

其实光看生成的英文还是多少有点狗屁不通,而且我们的AI也没学过语法什么的。不过在谷歌翻译“润色”一番后,这些诗句莫名有了点內味儿。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值