在《编程珠玑》的第15章有介绍随机生成文本,我上网搜了一下,发现这篇文章,转载一下。
原文: http://blog.csdn.net/z_york/archive/2008/06/14/2546764.aspx
上个星期一个偶然的机会,看到一篇介绍“随机文本生成“的东西(Generating Text ,“随机”二字是我画蛇添足加上去的),挺有趣的,加之这段时间比较有研究算法的兴头,所以就认真地学习了一下,现在把自己的理解和思考 记录下来。
补充一下,Generating Text 是Programming Pearls 一书(相当有名)的其中一节,该书英文全版可以免费在线阅读。
什么是”随机文本“?作者提到的随机文本有两个层次,一个是letter-level,一个是word-level,当然都是针对英语这一自然语言 而言的。所谓letter-level就是”随机“字母拼成的字母序列,word-level就是”随机“英语单词拼成的文本。
可能读者会问,这还不容易?对于letter-level,就是从字母表里面random出一个又一个字母,对于word-level,相同的做 法。 是的,这样干确实够随机够脑残的了,但这样生成的文本很可能没有任何”意义“,请注意,我上面是把”随机“两字特地加了引号,而”意义“两字也特地加了引 号,就是说——这里说的”随机文本“是一种有”意义“的”随机文本“。
太饶舌了,举几个个例子(从上述文章中摘出):
完全没有”意义“的”随机文本“:
稍稍有点”意义“的”随机文本“:
saade ve mw hc n entt da k eethetocusosselalwo gx fgrsnoh,tvettaf aetnlbilo fc lhd okleutsndyeoshtbogo eet ib nheaoopefni ngent
比较有”意义“的”随机文本“:
Ther I the heingoind of-pleat, blur it dwere wing waske hat trooss. Yout lar on wassing, an sit. " " Yould, " " I that vide was nots ther
容易看得出来,作者所认为的有”意义“,就是所生成的文本比较接近自然语言(这里是指英语):对letter-level而言,就是生成的字母序列 比较接近自然语言的规则,就英语来说,就是辅音字母和元音字母的某种微妙的交替出现(如:wh、th、qu、ey、al等等);对word-level来 说,就是单词在文本中出现的顺序符合或部分符合自然语言的文法,读者能读到似是而非的意思(如:i 后面经常出现的是 am,go 后面经常出现的是 to 等等诸如此类的规则)。
好了,简言之,这里讨论的”随机文本生成“,就是指生成那些部分符合自然语言规则,带有似是而非意思的文本(这是本人的体会,非文章本身的定 义)。
为什么要”随机生成文本“?作者并没有着墨太多,举例的几个软件也没有用过,所以就不说了,倒是我碰到过一个这样的应用实例,在本文末尾再提。
如何”随机文本生成“?由前文可知,简单的random脑残大法是没办法达到目标的,那种方法最倾向于产生第一种毫无“意义”的文本。作者给出的算 法是基于“马尔可夫链”和“香农理论”,事实上,本人限于数学水平有限,并不能弄明白这些理论(香农那个倒还学过,属于通信原理里面的内容,马尔可夫就完 全是空白),所以不在这里浪费时间了。倒是关于马尔可夫链,我还看过另一个有趣的帖子:用马尔可夫链和蒙特卡罗模 拟求解天赋"燃烧"和"疾驰热量"的量化价值 ,推荐玩过魔兽世界的同好们观摩……)。
言归正传,虽然理论看似艰深,但作者描述起来倒很简单很形象很好理解,给出的代码也很短小精干。先描述算法。
为了达到生成“有意义”的文本的目的,作者认为算法必须基于一个“输入文本”(这也是马尔可夫链理论吧……),而不是凭空地从字母表/单词表中生造 文本,用形象的语言描述该过程(就letter-level来说),就是:设“输入文本”是一本书(厚的比较好),随机翻一页,随机挑一个字母,输出,并 记之为“当前字母”,翻到另一页(非下一页,而是随机的另一页),寻找“当前字母”出现的位置,如果“当前字母”出现多次,则随机选一个,然后向后看k个 字母,把那第k个字母输出,并把新的字母记为“当前字母”,然后继续翻另一页,直到书翻完了或输出的字母总数达到某预设值,程序结束。
看起来很简单的算法,即使对于某些特殊情况没有特别说明:比如当前字母在下一页没有出现怎么办,或者向后看k个字母失败——已经超出该页了,等等, 仍然可以从作者给出的例子代码中找到完整的算法描述:
letter- level文本生成的程序代码
world- level文本生成的程序代码
补充几点,上述程序都是用C语言写的,作者写得很紧凑。对于第一个程序,用的是简单的扫描法去找“当前字母”在“下一页”的新位置,每次都扫描一次 “输入文本”全文,所以效率其低,仅仅作为一个算法演示而已;第二个程序用到了散列表,效率不错,我认为有相当的实用价值,另外作者实现简易散列表的方法 非常娴熟非常牛逼,实乃我等低端职业程序员居家旅行必备之参考。
我照着上述两个程序依样画葫芦地用python重新实现了一遍(主要是闲得蛋疼拿这个练习一下python),有兴趣的可以点这里 下载,在WinXP+python3.0下 编写和测试,使用方法见文件的“__main__”段,很简单的程序。
回到“随机文本生成到底拿来干什么”这个现实问题,我前段时间恰好碰到一个实例,有一个叫“Fate”(中文译名“黑暗诗史”,在迅雷一搜就有)的 仿暗黑2ARPG游戏,在创建角色的时候,除了可以自己输入角色名外,还可以让它代你生成一个随机的名字,我试了一下,效果不错,能随机生成一些非常见的 英文名字,而且好像还是随机的,当时就觉得挺过瘾的,后来看到了Generating Text一文,才想起它可能用到的算法。
我是这样想的,如果用脑残算法,从一个现成英文名字大全表中random一个出来,很难满足我等暗黑玩家的暗黑心理——头上顶着诸如Adam、 Bob、Carl、Dave这类初中英语课本里面的名字,哪有勇气杀入巴尔王座啊?Fate的程序员当然也知道,所以他随机出来的名字都是有别于常见名字 的,而拼法上又基本符合一般英语规则,比如Zerandra、qwarkty、hypheni这类比较装逼的名字。参考一下上面提到的理论和算法,我想了 下面一种可能的算法(没有动手去实现过测试过,纯属YY):
1、当然需要一份“输入文本”,这里把一份“男女英文名字大全(如果有的话)”作为输入文本
2、对“输入文本”进行一趟扫描生成一散列表,散列表的key是字母(A-Z),value是该字母的坐标(所在名字下标+所在位置下标)的数组, 比如对于字母'A'这个key,其对应的value是:
[ (0, 0), (0, 1), (1, 0), (1, 1), ...] —— 表示字母'A'出现的位置是第0个单词第0个字母、第0个单词第1个字母、第1个单词第0个字母等等,如此类推。
3、开始生成名字: