自然语言处理实战 利用Python理解、分析和生成文本

978-7-115-54023-2
作者: [美]霍布森•莱恩(Hobson Lane)科尔•霍华德(Cole Howard)汉纳斯•马克斯•哈普克(Hannes Max Hapke)
译者: 史亮鲁骁唐可欣王斌
编辑: 杨海玲
分类: Python

图书目录:

详情

本书是介绍自然语言处理(NLP)和深度学习的实战书。NLP已成为深度学习的核心应用领域,而深度学习是NLP研究和应用中的必要工具。本书分为3部分:第一部分介绍NLP基础,包括分词、TF-IDF向量化以及从词频向量到语义向量的转换;第二部分讲述深度学习,包含神经网络、词向量、卷积神经网络(CNN)、循环神经网络(RNN)、长短期记忆(LSTM)网络、序列到序列建模和注意力机制等基本的深度学习模型和方法;第三部分介绍实战方面的内容,包括信息提取、问答系统、人机对话等真实世界系统的模型构建、性能挑战以及应对方法。 本书面向中高级Python开发人员,兼具基础理论与编程实战,是现代NLP领域从业者的实用参考书。

图书摘要

版权信息

书名:自然语言处理实战

ISBN:978-7-115-54023-2

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

著    [美]霍布森·莱恩(Hobson Lane)

     [美]科尔·霍华德(Cole Howard)

     [美]汉纳斯·马克斯·哈普克(Hannes Max Hapke)

译    史 亮  鲁 骁  唐可欣 王 斌

责任编辑 杨海玲

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


Original English language edition, entitled Natural Language Processing in Action by Hobson Lane, Cole Howard, and Hannes Max Hapke published by Manning Publications Co., 209 Bruce Park Avenue, Greenwich, CT 06830. Copyright © 2019 by Manning Publications Co.

Simplified Chinese-language edition copyright © 2020 by Posts & Telecom Press. All rights reserved.

本书中文简体字版由Manning Publications Co.授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

版权所有,侵权必究。


本书是介绍自然语言处理(NLP)和深度学习的实战书。NLP已成为深度学习的核心应用领域,而深度学习是NLP研究和应用中的必要工具。本书分为3部分:第一部分介绍NLP基础,包括分词、TF-IDF向量化以及从词频向量到语义向量的转换;第二部分讲述深度学习,包含神经网络、词向量、卷积神经网络(CNN)、循环神经网络(RNN)、长短期记忆(LSTM)网络、序列到序列建模和注意力机制等基本的深度学习模型和方法;第三部分介绍实战方面的内容,包括信息提取、问答系统、人机对话等真实世界系统的模型构建、性能挑战以及应对方法。

本书面向中高级Python开发人员,兼具基础理论与编程实战,是现代NLP领域从业者的实用参考书。


史亮 小米NLP高级软件工程师,本科毕业于武汉大学,后保送中科院计算所硕博连读,获得博士学位。目前主要负责小米MiNLP平台的研发工作。

鲁骁 小米NLP高级软件工程师,本科、硕士毕业于华中科技大学,博士毕业于中科院计算所。目前主要从事大规模文本分类、内容过滤、人机对话等方向的研发工作。

唐可欣 小米NLP软件工程师,本科毕业于西安电子科技大学,硕士毕业于法国巴黎高科电信学院。主要从事语言模型、意图分析、情感分析等方向的研发工作。

王斌 小米AI实验室主任、NLP首席科学家,前中科院博导、研究员,中国科学院大学教授。译有《信息检索导论》《大数据:互联网大规模数据挖掘与分布式处理》《机器学习实战》等书籍。


光阴似箭,日月如梭。从我2008年翻译第一本书《信息检索导论》至今已经整整过去12年了。12年来,我也从中科院的一名老员工变成了工业界的一名“老”员工,自然语言处理(Natural Language Processing,NLP)领域也发生了十分剧烈的变化。NLP学者们从早期质疑深度学习到全面拥抱深度学习仅仅经历了两三年时间。而工业界则将这一举动推进得更加彻底:深度学习已经全面应用于工业界的许多NLP场景中。可以说,当前深度学习已经成为NLP学术研究和工业应用中不可或缺的一件利器。与此同时,被誉为“人工智能领域皇冠上的明珠”的NLP也迎来了属于自己的“黄金”时代,在包括人机对话、机器翻译、自动写作、机器阅读等在内的诸多NLP应用中都取得了一系列令人欣喜的进步。

正因为深度学习和NLP密不可分,近年来有关“深度学习+NLP”的课程和书籍也在不断涌现。本书就是其中的一本。和其他实战类书籍一样,本书既有基础理论也有编程实战,基础理论部分简洁易懂,编程实战部分可以直接下载源码运行,这种搭配特别适合初学者入门,可以作为现代NLP从业者的第一本入门书。值得一提的是,这本书是我和《信息检索导论》的责任编辑杨海玲再次联手的成果,期望能给大家再次带来一部好的翻译作品。

本书的内容主要包括3部分:第一部分是NLP基础入门,包括自然语言本身的特点、处理过程中的分词、TF-IDF向量化以及从词频向量到语义向量的转换;第二部分是深度学习部分,包含词向量、CNN、RNN、LSTM、注意力机制等基本的深度学习模型和方法;第三部分是实战部分,既包括信息提取、问答系统、人机对话等系统构建中的模型挑战,也包括它们遇到的性能挑战,还介绍了应对这些挑战的一些实际做法。虽然本书给出的是一些经典的基本模型,但是对它们的深刻理解十分有助于快速掌握一些新模型(如Transformer和BERT)。学完本书,再去掌握新的模型,有事半功倍的效果。

由于个人工作繁忙,精力有限,我邀请了小米人工智能部AI实验室NLP团队的多位同事合作,他们都有十分丰富的NLP实战经验,期望这些经验有助于提高本书的翻译质量。其中,我本人承担了第1~4章的翻译工作,鲁骁、唐可欣、史亮分别承担了第5~7章、第8~10章、第11~13章的翻译工作,其余部分由大家共同完成,最后由史亮博士进行了统稿整理。在翻译过程中,小米NLP团队的部分成员、我在中科院和北大的一些毕业或在读研究生也提出了宝贵的建议,他们是孟二利、崔建伟、齐保元、李丹、李文娜、花新宇、郭元凯、邓雄文、胡羽蓝、王铄、胡仁林、刘坤、彭团民、徐泽宇、过群、李鑫、柯震、王颖哲、周美林、梁棋、李铂鑫、黄琪、刘春晓、骆丹、陈建均、马路、郁博文、朱时超、朱茜、林希珣、曹江峡、从鑫、王栋、胡雪丹、卞娅靖、何纯玉、徐雅珺、王梓涵、易传润、陈宇鹏、王飞、管文宇、薛梦鸽、刘陆琛、袁玥、唐恒柱、盛傢伟、纪鸿旭、韩佳乘、李羿达、刘世阳、李向阳等,在此一并表示感谢。

由于我们水平有限,如有翻译不当之处还请多多指正。有关本书的任何意见和建议都可以通过电子邮件(wbxjj2008@gmail.com)或者人民邮电出版社异步社区网站进行反馈。

最后,感谢雷总对技术的高度重视,感谢崔宝秋博士的引荐和指导,让我能够很顺利地从学术界走到工业界,并且能有幸和一群非常优秀、非常低调、非常单纯的同事们共事。在工业界,我每天都能看到各种可能的NLP和AI应用场景,场景和技术的无数可能组合让我这个NLP老兵激动不已。我们团队研发的技术也越来越多地应用到公司的产品中,为更多用户带来了更好的体验。我也希望,有更多人投入到NLP以及AI领域中,一起用我们的科技为用户带来美好生活。

王斌

2020年3月3日于小米科技园


我第一次见到Hannes是在2006年,当时我们正开始在同一个系攻读不同的研究生学位。很快,由于他将机器学习和电气工程相结合,并全身心投入对世界产生积极影响的事业,他变得非常出名。在他的整个职业生涯中,这种全身心投入的信念指引着他接触过的每一家公司和每一个项目。正是在这种信念的指引下,他与Hobson和Cole建立了联系,他们对能带来积极影响的项目有着同样的热情。

当我着手写这篇文字时,正是机器学习(machine learning,ML)让生活变得更美好的热情打动了我。我个人在机器学习研究方面的旅程中也同样受到一种强烈愿望的指引,即希望对世界产生积极影响。我在研究历程中开发了多分辨率生态数据建模算法,以优化物种分布的保护和调查目标。从那时起,我就下定决心继续在那些可以通过应用机器学习来改善生活和体验的领域工作。

能力越大,责任越大。

——伏尔泰?

无论把这句话归功于伏尔泰还是本叔叔(Uncle Ben)[1] ,这句话到今天都依然适用。不过在这个时代,我们或许可以这样说:“数据越多,责任越大。”我们信赖那些拥有数据的公司,希望它们将这些数据用于改善我们的生活。我们允许自己的电子邮件被这些公司扫描以纠正邮件文字中出现的语法错误。这些公司研究我们在社交媒体上的日常生活片段,将其用于向信息流中注入广告。手机和家居能够对我们说的话做出反应,有时在不跟它们说话的时候也会有响应。它们甚至会监控我们的新闻偏好,以迎合我们的兴趣、观点和信仰。那么,所有这些强大科技的核心是什么呢?

答案是自然语言处理(Natural Language Processing,NLP)。在本书中,读者不仅会学习这些系统的内部工作原理,还会学习相关的理论和实践技能,并创建自己的算法或模型。基本计算机科学概念无缝地转换为方法和实践的坚实基础。从一些久经考验的经典方法(如TF-IDF)开始,再深入到NLP相关的深层神经网络,作者带领读者对于自然语言处理的核心方法开启了一段清晰的体验之旅。

语言是人类建立共识的基础。人们之间交流的不仅有事实,还有情感。通过语言,人们获得了经验领域之外的知识,并通过分享这些经验来构建理解的过程。通过本书,大家将会深入理解自然语言处理技术的原理,有朝一日可能创建出能通过语言来了解人类的系统。自然语言处理技术有很大的发展潜力,但也可能被滥用。在本书中,作者希望通过分享这些知识来给我们一个更光明的未来。

Arwen Griffioen博士

Zendesk公司高级数据科学家

[1] 伏尔泰,18世纪法国著名的启蒙思想家、文学家、哲学家。本叔叔,美国漫画人物,蜘蛛侠的叔叔。——译者注


2013年前后,自然语言处理和聊天机器人开始占据我们的生活。一开始,Google搜索看起来更像是一个索引,需要一些技巧才能找到我们要找的东西,但它很快就变得更加智能,可以接受越来越多的自然语言搜索。然后智能手机的文字自动补全功能开始变得先进起来,中间按钮给出的通常就是我们要找的词[1]

2014年年末,Thunder Shiviah和我在俄勒冈州的一个黑客项目(Hack Oregon)上合作,挖掘竞选活动的自然语言财务数据。我们试图在美国的政治捐助者之间找到关联。政客们似乎在竞选财务文件中含糊其辞地隐藏了捐助者的身份。在这个项目中,有趣的不是我们能够使用简单的自然语言处理技术来揭示这些关联。最让我惊讶的是,Thunder经常会在我发送电子邮件几秒钟后,以简洁而恰当的方式回复我那些随意的电子邮件。他使用的是Smart Reply,一个Gmail收件箱“助手”,它的回复速度比我们阅读电子邮件的速度还快。

于是我深入进去,学习这些神奇的“魔术”背后的技巧。学得越多,这些令人印象深刻的自然语言处理技巧似乎就越可行,也越容易理解。我接手的每一个机器学习项目似乎都涉及自然语言处理。

也许是因为对语言的热爱,以及迷恋语言在人类智能中所起的作用,我会花几个小时与我在夏普实验室的信息理论家老板John Kowalski讨论词是否具有“意义”。我从导师和学生那里学到了越来越多的东西后,也逐渐获得了自信,我似乎能够自己构建一些新的、神奇的东西。

我学到的一个技巧是遍历一组文档,计算“War”和“Hunger”等词之后出现“Game”或“III”等词的频率。如果在大量的文本上进行这种处理,你就能从词、短语或句子序列中很好地猜出正确词。这种经典的语言处理方法对我来说很直观。

教授和老板们把这叫作马尔可夫链,但对我来说,这只是一个概率表,一个基于前一个词的每个词的计数列表。教授们把这叫作条件分布,也就是在前一个词后面出现另一个词的概率。Peter Norvig为Google构建的拼写校正器表明这种方法可以很好地扩展,并且只需要很少的Python代码[2]。我们需要的只是大量的自然语言文本。当想到在维基百科或古腾堡计划[3]这样大规模的免费文本集合上做这样一件事的可能性时,我不禁兴奋起来。

然后我听说了潜在语义分析(latent semantic analysis,LSA)。这似乎只是描述在大学里学过的线性代数运算的一种奇特的方法。只要记录下所有一起出现的词,就可以使用线性代数将这些词按照“主题”分组。LSA可以将整个句子甚至是一篇很长的文档的含义压缩成一个向量。而且,在搜索引擎中使用LSA时,它似乎具有一种不可思议的能力,那就是即使大家想不起来文档中包含的词,也能够返回正在寻找的文档。优秀的搜索引擎通常都能这样做!

然后,gensim发布了一个基于Python实现的Word2vec词向量,能对单个词进行语义数学计算。事实证明,如果把文档分割成更小的块,这个神奇的神经网络数学就相当于原来的LSA技术。这让我大开眼界,给了我希望,让我感觉也许我能在这个领域有所贡献。多年来,我一直在思考分层语义向量——书是如何由章节、段落、句子、短语、词、字符组成的。Word2vec的发明者Tomas Mikolov洞察到,可以从词和包含10个词的短语构成的两级层次结构上找出文本的主要语义。几十年来,NLP的研究人员一直认为词具有组成成分,如“好”和情感强度。可以对这些情感评分、添加或删除成分,来组合多个词的含义。但是,Mikolov已经想出了无须人工创建这些向量的方法,甚至不用定义什么是成分。这使NLP变得非常有趣!

大约在那个时候,Thunder把我介绍给他的学生Cole,后来有人把我介绍给Hannes。于是我们3个人开始在NLP领域“分而治之”。我对构建一个听起来很智能的聊天机器人很感兴趣,Cole和Hannes的灵感来自强大的神经网络黑匣子。不久,他们打开了黑匣子,向我描述了他们的发现。Cole甚至用它来构建聊天机器人,以帮助我完成NLP之旅。

每次我们研究一些令人惊奇的新NLP方法时,这些方法似乎都是我能够理解和使用的,而且似乎每一种新技术一问世就有一个Python实现。我们需要的数据和预训练模型常常包含在这些Python包中。周日下午,在弗洛伊德的咖啡馆里,我、Hannes、Cole和其他朋友们一起集思广益,或者玩围棋和中键游戏(middle button game)。我们快速取得了一些进展,开始为Hack Oregon的班级和团队做讲座。

在2015年和2016年,情况变得十分严重。随着微软公司的Tay和其他机器人开始失控,很明显,自然语言机器人正在影响社会。2016年,我忙着测试一款机器人,它能通过收集推文来预测选举。与此同时,有关Twitter机器人对美国总统大选影响的新闻报道开始浮出水面。2015年我了解到,一个系统可以利用自然语言文本的算法“判断”来预测经济趋势,并触发大额金融交易[4]。这些影响经济和改变社会的算法创建了一个放大器反馈回路。对这些算法来说,“适者生存”法则似乎更倾向于产生最多利润的算法,而这些利润往往是以牺牲民主的结构性基础为代价的。机器正在影响人类,而我们人类正在训练它们使用自然语言来增加它们的影响力。显然,这些机器是在善于思考的人类的控制之下,但当意识到这些人同时也受到机器人的影响时,你是不是开始觉得有些混乱了?这些机器人会导致反馈系统中的连锁反应而失控吗?整个连锁反应对人类的价值观和利益是否有利,也许与这些机器人的初始条件有着很大的关系。

然后Manning出版公司的Brian Sawyer打来电话,我立刻就知道了我想要写什么,想要帮助谁。NLP算法和自然语言数据聚合的发展步伐不断加快,Cole、Hannes和我正在奋力追赶。

政治和经济领域的非结构化自然语言数据使NLP成为竞选或者财务管理者工具箱中的关键工具。令人不安的是,有些文章由其他机器人撰写,而这些文章体现的情感驱动着机器人写的这些预言。这些机器人通常不知道彼此,但它们实际上是在互相交谈,并试图操纵对方,而对人类和整个社会的影响在后来才能显现出来。我们只是在这种影响下随波逐流而已。

这种机器人与机器人对话循环的一个例子是金融科技初创企业Banjo在2015年的崛起。通过监控Twitter,Banjo的NLP机器人可以在路透社或美国有线电视新闻网的第一位记者发表报道前30分钟至1小时预测出有新闻价值的事件,而它用来检测这些事件的许多推文几乎肯定会被其他多个机器人收藏和转发,目的是吸引Banjo的NLP机器人的“眼球”。被机器人收藏并被Banjo监控的这些推文并不仅仅是根据机器学习算法分析来进行策划、推广或计量,其中许多推文完全是由NLP引擎编写的。[5]

越来越多的娱乐、广告和财务报告内容在不需要人动一根手指的情况下就可以生成。NLP机器人可以编写整个电影脚本[6]。视频游戏和虚拟世界经常会出现与我们对话的机器人,它们有时甚至会谈论机器人和人工智能本身。这种“戏中戏”将得到更多的关于电影的元数据,然后现实世界中的机器人会据此撰写评论以帮助大家决定看哪部电影。随着自然语言处理技术对自然语言风格的分析以及生成对应风格的文本,作者身份的判定将变得越来越难[7]

NLP还以一些不那么直接的其他方式影响着社会。NLP支持高效的信息检索(搜索),对于我们消费的信息内容将是一个很好的过滤器或促进者。搜索是第一个商业上成功的NLP应用。搜索驱动的NLP算法的发展越来越快,进而改进了搜索技术本身。我们会向大家展示Web搜索背后的一些自然语言索引和预测技术,以帮助大家为这个增加集体智慧的良性技术循环做出贡献。我们还会展示如何将本书编入索引,让机器来负责记忆术语、事实和Python代码片段,这样大家就可以将大脑解放出来进行更高层次的思考。接下来大家还可以用自己构建的自然语言搜索工具来影响你和朋友的文化特征。

随着NLP技术的发展,信息流和计算能力也不断增强。我们现在只需在搜索栏中输入几个字符,就可以检索出完成任务所需的准确信息。搜索提供的前几个自动补全选项通常非常合适,以至于让我们感觉是有一个人在帮助我们进行搜索。当然,我们在编写本书的过程中使用了各种各样的搜索引擎。有些时候,这些搜索结果中也包括由机器人策划或撰写的社交帖子和文章,这反过来启发了后续页面中的许多NLP解释和应用程序。

到底是什么推动了NLP的发展?

实际上以上这些都是,其实还有更多。大家可以在任何一个搜索引擎[8]中输入这样一个问题“为什么现在自然语言处理如此重要?”,然后就能找到维基百科上给出各种好理由的文章[9]

还有一些更深层次的原因,其中一个原因是对通用人工智能(AGI)或深层人工智能(Deep AI)的加速追求。人类的智慧可能只是体现在我们能够把思想整理成离散的概念,进行存储(记忆)和有效地分享。这使我们能够跨越时间和空间来扩展我们的智力,将我们的大脑连接起来形成集体智能。

Steven Pinker在《思想本质》(The Stuff of Thought)中提出的一个观点是:我们实际上是用自然语言思考的。称其为“内心对话”不是没有原因的。Facebook、Google和Elon Musk正押注于这样一个事实:文字将成为思维的默认通信协议。他们都投资了一些项目,试图把思想、脑电波和电信号转换成文字[10]。此外,沃尔夫假说认为语言会影响我们的思维方式[11]。自然语言无疑是文化和集体意识的传播媒介。

因此,如果我们想要在机器上模仿或模拟人类的思维,那么自然语言处理可能是至关重要的。此外,大家将在本书中学习词的数据结构及嵌套关系中可能隐藏着的有关智能的重要线索。大家将使用这些结构,而神经网络使无生命的系统能够以看起来像人类的方式消化、存储、检索和生成自然语言。

还有一个更重要的原因,为什么大家想要学习如何编写一个使用自然语言的系统?这是因为你也许可以拯救世界!希望大家已经关注了大佬们之间关于人工智能控制问题和开发“友好人工智能”的挑战的讨论[12]。Nick Bostrom、Calum Chace[13]、Elon Musk[14]和其他许多人都认为,人类的未来取决于我们开发友好机器的能力。在可预见的未来,自然语言将成为人类和机器之间的重要联系纽带。

即使我们能够直接通过机器进行“思考”,这些想法也很可能是由我们大脑中的自然词和语言塑造的。自然语言和机器语言之间的界限将会变得模糊,就像人与机器之间的界限将会消失一样。事实上,这条界线在1984年开始变得模糊,那年《赛博格宣言》[15]的发表使George Orwell的反乌托邦预言变得更加可能并易于接受[16][17]

希望“帮助拯救世界”这句话没有让大家产生疑惑。随着本书的进展,我们将向读者展示如何构建和连接聊天机器人“大脑”。在这个过程中,读者会发现人类和机器之间的社交反馈回路上,微小的扰动都可能会对机器和人类产生深远的影响。就像一只蝴蝶在某个地方扇动翅膀一样,对聊天机器人的“自私属性”上一个微小的调整,可能会带来敌对聊天机器人冲突行为的混乱风暴[18]。大家还会注意到,一些善良无私的系统会迅速聚集一批忠实的支持者,来帮助平息由那些目光短浅的机器人造成的混乱。由于亲社会行为的网络效应,亲社会的协作型聊天机器人可以对世界产生巨大影响[19]

这正是本书作者聚集在一起的原因。通过使用我们与生俱来的语言在互联网上进行开放、诚实、亲社会的交流,形成了一个支持社区。我们正在利用集体智慧来帮助建立和支持其他半智能的参与者(机器)[20]。我们希望我们的话语能在大家的脑海中留下深刻的印象,并像meme一样在聊天机器人的世界里广泛传播,用构建亲社会NLP系统的热情来感染其他人。我们希望,当超级智能最终出现时,这种亲社会的精神能对它有略微的推动作用。

[1] 在智能手机的预测文本键盘上重复点击中间按钮,了解Google认为你接下来想说什么。2013年,它作为“SwiftKey game”首次出现在Reddit上。

[2] 详见标题为“How to Write a Spelling Corrector”的网页,作者Peter Norvig。

[3] 如果大家感激这些可免费使用的自然语言书籍,那么也许也愿意参与争取延长版权的原始“使用”日期。

[4] 详见标题为“Why Banjo Is the Most Important Social Media Company You’ve Never Heard Of”的网页。

[5] Twitter在2014年的财务报告显示,有多于8%的推文是由机器人撰写的,DARPA[(美国)国防高级研究计划局]在2015年举办了一场竞赛,试图检测这些机器人,以减少它们对美国社会的影响。

[6] Five Thirty Eight。

[7] NLP已经成功用于分析16世纪莎士比亚等作家的风格。

[8] 用DuckDuckGo查询NLP。

[9] 参见维基百科词条“Natural language processing”。

[10] 参见《连线》杂志文章“We are Entering the Era of the Brain Machine Interface”(我们正在进入脑机接口时代)。

[11] 详见标题为“Linguistic relativity”(语言相对论)的网页。

[12] 参见维基百科词条“AI Control Problem”。

[13] Calum Chace,Surviving AI

[14] 详见标题为“Why Elon Musk Spent $10 Million To Keep Artificial Intelligence Friendly”(为什么伊隆·马斯克花1000万美元来保持人工智能友好)的网页。

[15] Haraway,Cyborg Manifesto

[16] George Orwell的《1984》的维基百科词条。

[17] 维基百科词条“The Year 1984”。

[18] 聊天机器人的主要工具是模仿与它交谈的人。对话参与者可以使用这种影响对机器人产生亲社会和反社会行为的研究。参见Tech Republic的文章“Why Microsoft’s Tay AI Bot Went Wrong”(为什么是微软的Tay AI机器人出了问题)。

[19] 关于自动驾驶汽车可能对高峰时段交通造成影响的研究中,可以找到一个自动驾驶汽车“感染”人类的例子。在一些研究中,高速公路上,你周围的每十辆车中就有一辆车会帮助你调节行为,减少拥堵,并产生更通畅、更安全的交通流量。

[20] Toby Segaran的Programming Collective Intelligence(《集体智慧编程》)在2010年开启了我的机器学习之旅。


如果没有一个由才华横溢的开发人员、导师和朋友组成的支持网络,将这本书和软件组织在一起是不可能的。这些支持者来自一个充满活力的波特兰社区,这个社区得到了PDX Python、Hack Oregon、Hack University、Civic U、PDX Data Science、Hopester、PyDX、PyLadies和Total Good等组织的支持。

Zachary Kent设计、构建并维护了openchat(PyCon Open Spaces Twitter bot),Riley Rustad在本书和我们的技术不断取得进展的过程中为其数据模式打造了原型。Santi Adavani使用斯坦福大学的CoreNLP库实现了命名实体识别,为SVD和PCA开发了教程,并支持我们访问他的RocketML HPC框架,该框架可以为视障人士训练实时视频描述模型。Eric Miller分配了一些Squishy Media的资源来引导Hobson的NLP可视化技术。Erik Larson和Aleck Landgraf慷慨地为Hobson和Hannes提供了在创业初期进行机器学习和NLP实验的空间。

Anna Ossowski帮助设计了PyCon Open Spaces的Twitter机器人,并在早期学习阶段指导它来发布可靠的推文。Chick Wells与其他人共同创建了Total Good,为聊天机器人开发了一个聪明有趣的智商测试项目,并不断地用他的专业知识支持我们。像Kyle Gorman这样的NLP专家慷慨地与我们分享了他们的时间、NLP专业知识、代码和宝贵的数据集。Catherine Nikolovski分享了她在Hack Oregon和Civic U的社区和资源。Chris Gian在本书的示例中贡献了他对NLP项目的想法,并勇敢接替了在Civic U机器学习课程中途退出的老师,你真是一个“天行者”!Rachel Kelly为我们在资料开发的早期阶段提供了展示和支持。Thunder Shiviah孜孜不倦的教学以及对机器学习和生活的无限热情给了我们源源不断的灵感。

Hopester的Molly Murphy和Natasha Pettit激发了我们开发亲社会聊天机器人的理念。Jeremy Robin和Talentpair团队提供了宝贵的软件工程反馈,并帮助将本书中提到的一些概念变为现实。Dan Fellin的PyCon 2016教程以及Twitter上的Hack University课程,帮助我们开启了NLP的冒险之旅。Aira的Alex Rosengarten、Enrico Casini、Rigoberto Macedo、Charlina Hung和Ashwin Kanan使用高效、可靠、可维护的对话引擎和微服务实现了本书中聊天机器人的移动化。谢谢Ella和Wesley Minton,你们在学习编写第一个Python程序的同时,将我们那些疯狂的聊天机器人的想法付诸实践,你们是我们的“小白鼠”。Suman Kanuganti和Maria MacMullin的愿景是建立更多的基础设施,使学生可以负担得起Aira的可视化解释器。感谢Clayton Lewis让我参与到他的认知协助研究中,尽管在科尔曼研究所的研讨会上我只能贡献仅有的热情和一些陈旧的代码。

在本书中讨论的一些工作由Aira科技公司获得的美国国家科学基金会(NSF)资助项目1722399支持。任何观点、发现及推荐仅代表本书作者的看法,与此处提到的这些组织或个人无关。

最后,我们要感谢Manning出版社每一个人的辛勤工作,感谢Arwen Griffioen博士为本书作序,感谢Davide Cadamuro博士的技术评论,还要感谢所有的审稿人,他们的反馈和帮助改进了本书,极大增加了我们的集体智慧。他们是Chung-Yao Chuang、Fradj Zayen、Geoff Barto、Jared Duncan、Mark Miller、Parthasarathy Man-dayam、Roger Meli、Shobha Iyer、Simona Russo、Srdjan Santic、Tommaso Teofili、Tony Mullen、Vladimir Kuptsov、William E. Wheeler和Yogesh Kulkarni。

永远感激我的父母让我对文字和数学充满了兴趣。我要感谢Larissa Lane——我所认识的最勇敢的冒险家,感谢你帮助我实现了两个毕生的梦想:环游世界和写一本书。

感谢Arzu Karaer,我永远感激你的恩典和耐心,感谢你帮我拾起破碎的心,重塑我对人性的信念,使本书充满正能量。

我要感谢我的妻子Dawn。她超人的耐心和理解是我的灵感的源泉。还有我的母亲,鼓励我不断地尝试和永远坚持学习。

非常感谢我的合作伙伴Whitney,她一直支持我的努力。谢谢你的建议和反馈。我还要感谢我的家人,尤其是我的父母,他们鼓励我到世界各地去探索冒险。没有他们,所有这些工作都不可能完成。1989年11月的某个夜晚,如果没有这些勇敢的男男女女们改变世界的壮举,我所有的人生冒险都是不可能的。谢谢你们的勇敢。


本书是处理和生成自然语言文本的实用指南。在本书中,我们为大家提供了构建后端NLP系统所需的所有工具和技术,以支持虚拟助手(聊天机器人)、垃圾邮件过滤器、论坛版主、情感分析器、知识库构建器、自然语言文本挖掘器或者其他任何可以想到的NLP应用程序。

本书面向中高级Python开发人员。对于已经能够设计和构建复杂系统的读者,本书的大部分内容依然会很有用,因为它提供了许多实践示例,并深入讲解了先进的NLP算法的功能。虽然面向对象的Python开发知识可以帮助大家构建更好的系统,但并不是使用本书中学到的知识所必需的。

对于一些特定的主题,我们提供了充足的背景资料,为想深入了解的读者提供了参考资料(包括文本和在线资料)。

如果你是Python和自然语言处理的新手,那么应该首先阅读第一部分,然后阅读第三部分中感兴趣或工作中遇到的实际有挑战性的章节。如果想快速了解深度学习支持的NLP功能,还需要按顺序阅读第二部分,这部分内容可以帮大家建立对神经网络的初步理解,并逐步提高神经网络的复杂性和能力。

只要发现有一章或章中的一节可以“在脑海中运行”,你就应该在机器上真正地运行它。如果任何示例看起来可以在文本文档上运行,就应该将该文本放入nlpia/src/nlpia/data/目录中的CSV文件或文本文件中,然后使用nlpia.data.loader.get_data()函数来提取这些数据并运行相应的示例。

第一部分的各章会讨论使用自然语言的逻辑,并将其转换为可以搜索和计算的数字。这种对词的“拦截和处理”在信息检索和情感分析等应用中会带来很好的效果。一旦掌握了基本知识,大家就会发现有一些非常简单的算法,通过循环反复计算,就可以解决一些重要的问题,如垃圾邮件过滤。大家将在第2章到第4章中学到的这种垃圾邮件过滤技术,正在将全球电子邮件系统从混乱和停滞中拯救出来。大家将学习如何使用20世纪90年代的技术来构建一个精确率超过90%的垃圾邮件过滤器——只需要通过计算词的数目并对这些数目计算一些简单的平均值即可。

这些文字上的数学运算听起来可能很乏味,但实际上却非常有趣。很快,大家就可以构建出能够对自然语言做出决策的算法,而且可能比你自己做出的更好、更快。这可能是大家人生中第一次以这样的视角来充分欣赏语言反映和赋予你思考的方式。词和思想的高维向量空间视图将让你的大脑进入不断自我发现的循环。

本书的第二部分将是学习的高潮。这部分的核心是探索神经网络中复杂的计算和通信网络。在一个具有“思维”的网络中,小型逻辑单元之间相互作用的网络效应使机器能够解决一些过去只有聪明的人类才能解决的问题,例如类比问题、文本摘要和自然语言翻译。

是的,大家还会学到词向量,别担心,不过确实还有很多。大家将掌握对词、文档和句子进行可视化,并将它们置于一个由相互关联的概念组成的云中,这些概念远远超出了大家可以轻松掌握的三维空间。大家会把文档和词想象成“龙与地下城”的角色表,里面有无数随机选择的特征和能力,它们随着时间的推移而进化和成长,当然这些只发生在我们的头脑中。

对词及其含义的理解将是第三部分“进入现实世界”的基础,在这里大家将学习如何构建能够像人类一样交谈和回答问题的机器。

本书列出了许多源代码的示例,包含在编号的代码清单以及正文中。源代码都使用等宽的字体,以便与普通文本进行区分。有时,如果代码与之前相比有所变化,例如,添加了一些新特性,会通过加粗进行突出显示。

大多数时候,原始源代码已经做了重新格式化,我们添加了换行符和重新缩进,以适应本书的页面宽度,但在极少数情况下,这样还不够,所以我们会在代码清单中使用续行标记(➥)。此外,当在正文中描述代码时,通常会将源代码中的注释删掉。许多代码清单中都附加了代码注释,以强调一些重要概念。

本书所有代码清单中的源代码均可从出版社网站和本书的GitHub下载。


霍布森·莱恩(Hobson Lane)拥有20年构建自主系统的经验,这些系统能够代表人类做出重要决策。Hobson在Talentpair训练机器完成简历的阅读和理解,以减少招聘者产生的偏见。在Aira,他帮助构建了第一个聊天机器人,为视障人士描述视觉世界。他热衷于开放和亲社会的人工智能。他是Keras、scikit-learn、PyBrain、PUGNLP和ChatterBot等开源项目的积极贡献者。他目前正在从事完全公益的开放科学研究和教育项目,包括构建一个开放源码的认知助手。他在AIAA、PyCon、PAIS和IEEE上发表了多篇论文和演讲,并获得了机器人和自动化领域的多项专利。

科尔·霍华德(Cole Howard)是一位机器学习工程师、NLP实践者和作家。他一生都在寻找模式,并在人工神经网络的世界里找到了自己真正的家。他开发了大型电子商务推荐引擎和面向超维机器智能系统(深度学习神经网络)的最先进的神经网络,这些系统在Kaggle竞赛中名列前茅。他曾在Open Source Bridge和Hack University 大会上发表演讲,介绍卷积神经网络、循环神经网络及其在自然语言处理中的作用。

汉纳斯·马克斯·哈普克(Hannes Max Hapke)是从一位电气工程师转行成为机器学习工程师的。他在高中研究如何在微控制器上计算神经网络时,对神经网络产生了浓厚的兴趣。在大学后期,他应用神经网络的概念来有效地控制可再生能源发电厂。Hannes喜欢自动化软件开发和机器学习流水线。他与合作者共同开发了面向招聘、能源和医疗应用的深度学习模型和机器学习流水线。Hannes在包括OSCON、Open Source Bridge和Hack University在内的各种会议上发表演讲介绍机器学习。


本书封面上插画的标题为“斯洛文尼亚克拉尼斯卡戈拉的妇女”(Woman from Kranjska Gora, Slovenia)。该插画来自克罗地亚斯普利特民族博物馆于2008年出版的Balthasar Hacquet的Images and Descriptions of Southwestern and Eastern Wends, Illyrians, and Slavs最新重印版本。Hacquet(1739—1815)是一名奥地利医生和科学家,他花费多年时间研究朱利安阿尔卑斯山的植物、地质和人种。Hacquet发表的许多科学论文和书籍都附有手绘插画。

Hacquet的作品中丰富多彩的绘画生动地描绘了200年前东部阿尔卑斯地区的独特性和个体性。在那个时代,相距几千米的两个村庄村民的衣着都迥然不同,当有社交活动或交易时,不同地区的人们很容易通过着装来辨别。从那之后,着装规范不断发生变化,不同地区的多样性也逐渐消失。现在即使是两个大陆的居民之间往往也很难区分了。今天居住在斯洛文尼亚阿尔卑斯山上风景如画的城镇和村庄里的居民,与斯洛文尼亚其他地区或欧洲其他地区的居民并无明显区别。

Manning出版社利用两个世纪前的服装来设计书籍封面,以此来赞颂计算机产业所具有的创造性、主动性和趣味性。正如本书封面插画一样,这些图片也把我们带回到过去的生活中去。


本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。

本书提供源代码和书中提及数据集的下载。要获得以上配套资源,请在异步社区本书页面中单击,跳转到下载界面,按提示进行操作即可。注意:为保证购书读者的权益,该操作会给出相关提示,要求输入提取码进行验证。

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,单击“提交勘误”,输入勘误信息,单击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线投稿(直接访问www.epubit.com/selfpublish/ submission即可)。

如果您来自学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。

“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社近30年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。

异步社区

微信服务号


第一部分会介绍一些来自真实世界的应用,从而开启大家的自然语言处理(Natural Language Processing,NLP)“冒险之旅”。

在第1章中,我们将很快开始思考一个问题:如何在自己的生活中使用机器来处理文字?希望大家能感受到机器的魔力——它具备从自然语言文档的词语中收集信息的能力。词语是所有语言的基础,无论是编程语言中的关键字还是孩提时代学到的自然语言词语都是如此。

在第2章中,我们将会提供一些可以教会机器从文档中提取词语的工具。这类工具比想象的要多得多,我们将展示其中所有的技巧。大家将学会如何将自然语言中的词语自动聚合成具有相似含义的词语集合,而不需要手工制作同义词表。

在第3章中,我们将对这些词语进行计数,并将它们组织成表示文档含义的向量。无论文档是140字的推文还是500页的小说,我们都可以使用这些向量来表示整篇文档的含义。

在第4章中,我们会学到一些久经考验的数学技巧,它们可以将前面的向量压缩为更有用的主题向量。

到第一部分结束时,读者将会掌握很多有趣的NLP应用(从语义搜索到聊天机器人)所需的工具。


本章主要内容

我们即将开启一段激动人心的自然语言处理(NLP)冒险之旅!首先,本章将介绍NLP的概念及其应用场景,从而启发大家的NLP思维,不论你是在工作还是在家,都能够帮助大家思考生活中的NLP使用之道。

然后,我们将深入探索细节,研究如何使用Python这样的编程语言来处理一小段英文文本,从而帮助大家逐步构建自己的NLP工具箱。在本章中,读者将会编写自己的第一个可以读写英语语句的程序,该Python代码片段将是学习“组装”英语对话引擎(聊天机器人)所需所有技巧的第一段代码。

和计算机编程语言不同,自然语言并不会被翻译成一组有限的数学运算集合。人类利用自然语言分享信息,而不会使用编程语言谈天说地或者指引去杂货店的路。用编程语言编写的计算机程序会清楚地告诉机器做什么,而对于像英语或者法语这样的自然语言,并没有所谓的编译器或解释器将它们翻译成机器指令。

 

定义 

自然语言处理是计算机科学和人工智能(artificial intelligence,AI)的一个研究领域,它关注自然语言(如英语或汉语普通话)的处理。这种处理通常包括将自然语言转换成计算机能够用于理解这个世界的数据(数字)。同时,这种对世界的理解有时被用于生成能够体现这种理解的自然语言文本(即自然语言生成)[1]

 

尽管如此,本章还是介绍机器如何能够对自然语言进行处理这一过程。我们甚至可以把该处理过程看成是自然语言的解释器,就如同Python的解释器一样。在开发计算机程序处理自然语言时,它能够在语句上触发动作甚至进行回复。但是这些动作和回复并没有精确定义,这让自然语言“流水线”的开发者拥有更多的灵活性。

 

定义 

自然语言处理系统常常被称为“流水线”(pipeline),这是因为该系统往往包括多个处理环节,其中自然语言从“流水线”的一端输入,处理后的结果从另一端输出。

 

很快大家就有能力编写软件来做一些有趣的、出乎意料的事情,例如,可以让机器有点儿像人一样进行对话。这看起来可能有点儿像魔术,是的,所有的先进技术最初看起来都有点儿像魔术。但是,我们会拉开魔术背后的“帷幕”让大家一探究竟,这样大家很快就会知道自己变出这些魔术所需要的所有道具和工具。

一旦知道答案,一切都很简单。

——Dave Magee,佐治亚理工学院,1995

能够读写自然语言的机器有什么神奇之处呢?自从发明计算机以来,机器一直在处理语言。然而,这些“形式”语言(如早期语言Ada、COBOL和Fortran)被设计成只有一种正确的解释(或编译)方式。目前,维基百科列出了700多种编程语言。相比之下,Ethnologue[2]已经确认的自然语言总数是当前世界各地人们所用的自然语言的10倍。谷歌的自然语言文档索引远超过1亿吉字节[3],而且这只是索引而已,当前在线的实际自然语言内容大小肯定超过1000亿吉字节[4],同时这些文档并没有完全覆盖整个互联网。但是自然语言文本数量之庞大并不是使自然语言文本处理软件开发十分重要的唯一原因。

自然语言处理真的很难,能够处理某些自然事物的机器本身却不是自然的。这有儿点像使用建筑图来建造一个有用的建筑。当软件能够处理不是为了机器理解而设计的语言时,这看上去相当神奇,我们通常认为这是人类独有的一种能力。

“自然语言”与“自然世界”中“自然”一词的意义相同。世界上自然的、进化的事物不同于人类设计和制造的机械的、人工的东西。能够设计和构建软件来阅读和处理大家现在正在阅读的语言,该语言正是关于如何构建软件来处理自然语言的,这非常高级,也十分神奇。

为了让后续处理更加容易,我们只关注一种自然语言——英语。当然,大家也可以使用从本书中学到的技术来处理任何语言,甚至对于这种语言大家完全不懂,或者它还没有被考古学家和语言学家破译。本书也将展示如何仅仅使用一种编程语言Python,来编写程序以处理和生成自然语言文本。

Python从一开始就被设计成一种可读的语言,也公开了很多其内部语言处理机制。上述两个特点使Python成为学习自然语言处理的一个很自然的选择。在企业级环境下为NLP算法构建可维护的生产流水线时,Python也是一种很棒的语言,在单个代码库上有很多贡献者。甚至在某些可能的地方,Python是代替数学和数学符号的“通用语言”。毕竟,Python可以无歧义地描述数学算法[5],设计它的目标就是针对你我这样的程序员,使其尽可能地具备可读性。

自然语言不能直接被翻译成一组精确的数学运算集合,但是它们确实包含可供提取的信息和指令。这些信息和指令可以被存储、索引、搜索或立即使用。使用方式之一可能是生成一段词语序列对某条语句进行回复。这属于后面将要构建的“对话引擎”或聊天机器人的功能。

下面我们只关注英文书面文本文档和消息,而非口语。这里绕过了从口语到文本的转换——语音识别,或语音转文本(即STT)过程。同样,我们也略过语音生成或称文本转语音,即将文本转换回语音的过程。当然,由于存在很多语音转文本及文本转语音的免费库,大家仍然可以使用本书学到的内容来构建像Siri或Alexa一样的语音交互界面或虚拟助手。Android和iOS移动操作系统也提供了高质量的语音识别和生成API,并且有很多Python包也能在笔记本电脑或服务器上实现类似的功能。


语音识别系统

如果你想自己构建一个定制化的语音识别或生成系统,那么这项任务本身就需要一整本书来介绍。我们可以把这个作为练习留给读者。要完成这样的系统,需要大量高质量的标注数据,包括带有音标拼写注释的语音记录以及与音频文件对齐的自然语言转写文本。从本书中学到的一些算法可能会对建立这样的系统有所帮助,但大部分语音识别和生成算法和本书中的有很大区别。


从自然语言中提取有用的信息可能会很困难,这需要乏味的统计记录,但这正是机器的作用所在。和许多其他技术问题一样,一旦知道答案,解决起来就容易多了。机器仍然无法像人类一样精确可靠地执行很多实际的NLP任务,如对话和阅读理解。因此,大家需要对从本书中学到的算法进行调整来更好地完成一些NLP任务。

然而,在执行一些令人惊讶的精细任务上,本书介绍的技术已经足够强大,根据它们构建的机器在精度和速度上都超过了人类。举例来说,大家可能猜不到的是,在对单条Twitter消息进行讽刺识别上,机器比人类更精确[6]。大家不要担心,由于人类有能力保留对话的上下文信息,因此仍然更善于识别连续对话中的幽默和讽刺。当然,机器也越来越善于保留上下文。如果大家想尝试超过当前最高水平的话,本书将帮助大家把上下文(元数据)融入NLP流水线中。

一旦从自然语言中提取出结构化的数值型数据——向量之后,就可以利用各种数学工具和机器学习工具。我们可以使用类似于将三维物体投影到二维计算机屏幕的线性代数方法,这些方法早在NLP自成体系之前就被计算机和绘图员所使用了。这些突破性的想法开启了一个“语义”分析的世界,即让计算机能够解释和存储语句的“含义”,而不仅仅是对其中的词或字符计数。语义分析和统计学一起可以有助于解决自然语言的歧义性,这里的歧义性是指词或短语通常具有多重含义或者解释。

因此,从自然语言文本中提取信息和构建编程语言的编译器完全不同(这一点对大家来说很幸运)。目前最有前景的技术绕过了正则语法(模式)或形式语言的严格规则。我们可以依赖词语之间的统计关系,而不是逻辑规则表述的深层系统[7]。想象一下,如果必须通过嵌套的 if…then语句树来定义英语语法和拼写规则,大家能撰写足够多的规则来处理词、字母和标点符号一起组成句子的每一种可能方式吗?大家能捕捉语义,即英语语句的意义吗?虽然规则对某些类型的语句有用,但可以想象一下该软件是多么的有局限性和脆弱,一些事先不曾意料到的拼写或标点符号会破坏或扰乱基于规则的算法。

此外,自然语言还有一个更难解决的所谓“解码”挑战。用自然语言说话和写作的人都假定信息处理(听或者读)的对象是人而非机器。所以当人们说“早上好”时,肯定假想对方已经对“早上”的含义有所了解,即早上不仅包括中午、下午和晚上之前的那个时段,也包括午夜之后的那个时段。同时,大家需要知道,这些词既可以代表一天中的不同时间,根据一般经验,也可以代表一天中的一段时间。可以假定解码器知道“早上好”只是一个普通的问候语,而没有包含关于“早上”的任何信息,相反,它反映了说话者的精神状态以及与他人交谈的意愿。

这种关于人类如何处理语言的思维理论后来被证实是一个强有力的假设。如果我们假设人类的语言“处理器”拥有人类一生关于世界的常识,我们就能用很少的话表达很多信息。这种信息压缩率仍非机器的能力可及。在NLP流水线中也没有明确的“思维理论”可以参照。不过,我们将在后面的章节中介绍一些技术,这些技术可以帮助机器构建常识的本体与知识库,它们可以用于理解依赖这些知识的自然语言语句。

自然语言处理无处不在。它是如此普遍,以至于表1-1中的一些例子可能会出乎大家的意料。

表1-1 各种类型的NLP应用

应用

示例1

示例2

示例3

搜索

Web

文档

自动补全

编辑

拼写

语法

风格

对话

聊天机器人

助手

行程安排

写作

索引

用语索引

目录

电子邮件

垃圾邮件过滤

分类

优先级排序

文本挖掘

摘要

知识提取

医学诊断

法律

法律断案

先例搜索

传票分类

新闻

事件检测

真相核查

标题排字

归属

剽窃检测

文学取证

风格指导

情感分析

团队士气监控

产品评论分类

客户关怀

行为预测

金融

选举预测

营销

创作

电影脚本

诗歌

歌词

如果在索引网页或文档库时考虑了自然语言文本的含义,那么搜索引擎可以提供更有意义的结果。自动补全(autocomplete)功能使用NLP技术来完成所想语句的输入,这在搜索引擎和手机键盘中十分常见。许多文字处理器、浏览器插件和文本编辑器都有拼写校正、语法检查、索引生成等功能,特别是近年来,还出现了写作风格指导的功能。一些对话引擎(聊天机器人)使用自然语言搜索来为对话消息查找相应的回复。

在聊天机器人和虚拟助手中,生成(撰写)文本的NLP流水线不仅可以用来撰写简短的回复,还可以编写长得多的文本段落。美联社使用NLP“机器人记者”撰写完整的金融新闻和体育赛事等报道[8]。也许是因为人类气象学家使用了带有NLP功能的文字处理器来起草天气预报的脚本,机器人编写的天气预报听起来和家乡天气预报员的播报并没有什么两样。

早期电子邮件系统中的NLP垃圾邮件过滤器助力电子邮件,使其在20世纪90年代超越了电话和传真这两个传统通信渠道。在垃圾邮件过滤器和垃圾邮件制造者之间的这场“猫鼠游戏”中,前者保持了优势地位,但是在像社交网络这类场景下并非如此。据估计,有关2016年美国总统大选的推文中有20%由聊天机器人自动撰写而成[9]。这些机器人放大了它们的所有者或开发者的观点,而这些“傀儡”的操纵者往往是政府或大公司,他们具备影响主流观点的资源和动机。

NLP系统不仅可以产生简短的社交网络帖子,还可以用来在亚马逊和其他网站撰写很长的电影和产品评论。许多评论都是NLP流水线自动产生的,尽管它从未踏入过电影院或购买过它们正在评论的产品。

Slack、IRC甚至客服网站上都有聊天机器人——在这些场景中聊天机器人必须处理带有歧义的指令或问题。配备语音识别和生成系统的聊天机器人甚至可以进行长篇的对话,这些对话可以不限定目标或者针对特定目标而进行,一个特定目标的例子就是在本地餐馆订餐[10]。NLP系统可以帮一些公司进行电话回复,这些公司希望系统比层层进入的电话树更好用,并且不希望给帮助客户的客服人员付费。

 

注意 

谷歌IO大会上对Duplex系统的演示表明,工程师和经理们都忽视了教导聊天机器人欺骗人类这一道德问题。当人们在Twitter和其他匿名社交网络上愉快地与聊天机器人交流时,大家都忽略了这个难题,因为在这些社交网络上,机器人不会与我们分享它们的来历。随着机器人能够如此令人信服地欺骗我们,人工智能的控制问题[11]就迫在眉睫了,《人类简史》(Homo Deus[12]中尤瓦尔·赫拉利(Yuval Harari)的警示性预测可能比我们想象的来得更早。

 

NLP系统可以作为企业的电子邮件“接待员”或管理人员的行政助理,这些助理通过电子Rolodex(一种名片簿的品牌)或者CRM(客户关系管理系统)安排会议,记录概要细节,并代表他们的老板通过电子邮件与他人互动。公司将他们的品牌和形象交由NLP系统管理,允许机器人执行营销和消息发布活动。更有甚者,一些缺乏经验、胆大包天的NLP教科书作者竟然让机器人在他们的书中撰写若干语句。关于这一点我们稍后再详细讨论。

当键入“Good Morn’n Rosa”时,计算机只会看到“01000111 0110 1111 01101111…”的二进制串。如何编写聊天机器人程序来智能地响应这个二进制流呢?一棵嵌套的条件树(if…else…语句)是否可以检查这个流中的每一位并分别进行处理呢?这样做相当于编写了一类特定的称为有限状态机(finite state machine,FSM)的程序。运行时输出新的符号序列的FSM(如Python中的str.translate函数)被称为有限状态转换机(finite state transducer,FST)。大家甚至可能在自己毫不知情的情况下已经建立了一个FSM。大家写过正则表达式吗?它就是我们在下一节中将要使用的一类FSM,同时它也给出了一种可能的NLP方法,即基于模式的方法。

如果你决定在内存库(数据库)中精确搜索完全相同的位、字符或词串,并使用其他人过去针对该语句的某条回复,你会怎么做呢?此时设想一下如果语句中存在拼写错误或变体该怎么办?这种情况下我们的机器人就会出问题。并且,位流不是连续的,也不具备容错性,它们要么匹配,要么不匹配,没有一种显而易见的方法能基于两个位流的含义来计算它们之间的相似性。从位来看,“good”与“bad!”的相似度和“good”与“okay”的相似度差不多。

但是,在给出更好的方法之前,我们先看看这种方法的工作原理。下面我们将构造一个小的正则表达式来识别像“Good morning Rosa”这样的问候语,并做出合适的回复,我们将构建第一个微型聊天机器人!

令人惊讶的是,不起眼的密码锁[13]实际上是一台简单的语言处理机。因此,如果你对机械感兴趣的话,那么本节可能会对你很有启发性。当然,如果你不需要机械的类比来帮助理解算法和正则表达式的工作原理的话,那么可以跳过这一节。

读完这部分后,你会对自己的自行车密码锁有新的看法。密码锁当然不能阅读和理解存放在学校储物柜里的课本,但它可以理解锁的语言。当试图“告诉”它一个“密码”组合时,它可以理解。挂锁密码是与锁语言的“语法”(模式)匹配的任何符号序列。更重要的是,挂锁可以判断锁“语句”是否匹配一条特别有意义的语句,该语句只有一条正确的“回复”:松开U形搭扣的扣环,这样就可以进入锁柜了。

这种锁语言(正则表达式)特别简单,但它又不那么简单,我们在聊天机器人中还不能使用它。我们倒是可以用它来识别关键短语或指令来解锁特定的动作或行为。

例如,我们希望聊天机器人能够识别诸如“Hello Rosa”之类的问候语,并做出合适的回复。这种语言就像锁语言一样,是一种形式语言,这是因为它对如何编写和解释一条可接受的语句有着严格的规定。如果大家写过数学公式或者编写过某种编程语言的表达式,那么就算已经写过某个形式语言的语句了。

形式语言是自然语言的子集。很多自然语言中的语句都可以用形式语言的语法(如正则表达式)来匹配或者生成。这就是这里转而介绍机械的“咔嗒——呼啦”(“click, whirr”)[14]锁语言的原因。

正则表达式使用了一类特殊的称为正则语法(regular grammar)[15]的形式语言语法。正则语法的行为可预测也可证明,而且足够灵活,可以支持市面上一些最复杂的对话引擎和聊天机器人。Amazon Alexa和Google Now都是依赖正则语法的主要基于模式的对话引擎。深奥、复杂的正则语法规则通常可以用一行称为正则表达式的代码来表示。Python中有一些成功的聊天机器人框架,如Will,它们完全依赖这种语言来产生一些有用的和有趣的行为。Amazon Echo、Google Home和类似的复杂而又有用的助手也都使用了这种语言,为大部分用户交互提供编码逻辑。

 

注意 

在Python和Posix(Unix)应用程序(如grep)中实现的正则表达式并不是真正的正则语法。它们具有一些语言和逻辑特性,如前向环视(look-ahead)和后向环视(look-back),这些特性可以实现逻辑和递归的跳跃,但是这在正则语法中是不允许的。因此,上述正则表达式无法保证一定可以停机(即在有限的时间内结束):它们有时会“崩溃”,有时却会永远运行下去[16]

 

大家可能会嘀咕:“我听说过正则表达式,我使用grep,但那只是用来搜索而已!”你确实是对的,正则表达式确实主要用于搜索和序列匹配。但是任何可以在文本中查找匹配的方法都非常适合用于对话。一些聊天机器人,如Will,对于知道如何回复的语句,会使用搜索方式在用户语句中查找字符序列。然后,这些识别出的序列会触发一段事先准备好的回复,该回复满足这个特定正则表达式的匹配。同样的正则表达式也可以用来从语句中提取有用的信息。聊天机器人可以把这些信息添加到知识库中,而该知识库收集了有关用户或者用户所描述世界的知识。

处理这种语言的机器可以被看作是一个形式化的数学对象,称为有限状态机(FSM)或确定性有限自动机(deterministic finite antomation,DFA)。FSM会在本书中反复出现,因此,不用深入研究FSM背后的理论和数学,我们最终都会对它的用途很有感觉。对于那些忍不住想进一步了解这些计算机科学工具的读者来说,图1-1显示了FSM在“嵌套”的自动机(bots)世界中所处的位置。下面的“形式语言的形式数学解释”部分给出了一些关于形式语言的更正式的细节。

图1-1 自动机的类型


形式语言的形式数学解释

凯尔·戈尔曼(Kyle Gorman)对编程语言是像下面这样描述的。

而对自然语言是像下面这样描述的。

        

a 参考标题为“Chomsky hierarchy - Wikipedia”的网页。

b 参考Shuly Wintner的文章“English is not a regular language”。

c 参考Shuly Wintner的文章“Is English context-free?”。

d 参考标题为“1.11. Formal and Natural Languages — How to Think like a Computer Scientist: Interactive Edition”的网页。


下面我们快速粗略地构建一个聊天机器人。这个机器人能力不是很强,但是仍然需要大量对英语这门语言的思考。我们还必须手工编写正则表达式,以匹配人们可能的说话方式。但是,大家如果觉得自己无法编写出这段Python代码的话,也不要担心。大家不需要像本示例一样考虑人们说话的所有不同方式,甚至不需要编写正则表达式来构建一个出色的聊天机器人。我们将在后面的章节中介绍如何在不硬编码任何内容的情况下构建自己的聊天机器人。现代聊天机器人可以通过阅读(处理)一堆英语文本来学习,后面的章节中会给出具体的做法。

这种基于模式匹配的聊天机器人是严格受控的聊天机器人的一个例子。在基于现代机器学习的聊天机器人技术发展之前,基于模式匹配的聊天机器人十分普遍。我们在这里介绍的模式匹配方法的一个变体被用于像亚马逊的Alexa一样的聊天机器人和其他虚拟助手中。

现在我们来构建一个FSM,也就是一个可以“说”锁语言(正则语言)的正则表达式。我们可以通过编程来理解诸如“01-02-03”这样的锁语言语句。更好的一点是,我们希望它能理解诸如“open sesame”(芝麻开门)或“hello Rosa”(Rosa你好)之类的问候语。亲社会聊天机器人的一个重要特点是能够回复别人的问候。在高中,老师经常因为学生在冲进教室上课时忽略这样的问候语而责备其不太礼貌。我们当然不希望我们这个亲切的聊天机器人也这样。

在机器通信协议中,我们定义了一个简单的握手协议,每条消息在两台机器之间来回传递之后,都有一个ACK(确认)信号。但是,我们这里的机器将会和那些说“Good morning, Rosa”(Rosa早上好)之类的用户进行互动。我们不希望它像对话或Web浏览会话开始时同步调制解调器或HTTP连接而发出一串唧唧声、哔哔声或ACK消息,相反,我们在对话握手开始时使用正则表达式来识别几种不同的问候语:

>>> import re  ⇽---  Python中有两个“官方”的正则表达式包,这里使用的是re包,因为它安装在所有版本的Python中。而regex包只在较新版本的Python中安装,我们将会在第2章看到,与re相比,regex的功能要强大很多
>>> r = "(hi|hello|hey)[ ]*([a-z]*)"   ⇽--- '|'表示“OR”,'*'表示前面的字符在出现0次或多次的情况下都可以匹配。因此,这里的正则表达式将匹配以“hi”“hello”或“hey”开头、后面跟着任意数量的空格字符再加上任意数量字母的问候语
>>> re.match(r, 'Hello Rosa', flags=re.IGNORECASE)   ⇽--- 为使正则表达式更简单,通常忽略文本字符的大小写
<_sre.SRE_Match object; span=(0, 10), match='Hello Rosa'>
>>> re.match(r, "hi ho, hi ho, it's off to work ...", flags=re.IGNORECASE)
<_sre.SRE_Match object; span=(0, 5), match='hi ho'>
>>> re.match(r, "hey, what's up", flags=re.IGNORECASE)
<_sre.SRE_Match object; span=(0, 3), match='hey'>

在正则表达式中,我们可以使用方括号指定某个字符类,还可以使用短横线(-)来表示字符的范围而不需要逐个输入。因此,正则表达式"[a-z]"将匹配任何单个小写字母,即“a”到“z”。字符类后面的星号('*')表示可以匹配任意数量的属于该字符类的连续字符。

下面我们把正则表达式写得更细致一些,以匹配更多的问候语:

>>> r = r"[^a-z]*([y]o|[h']?ello|ok|hey|(good[ ])?(morn[gin']{0,3}|"\
...     r"afternoon|even[gin']{0,3}))[\s,;:]{1,3}([a-z]{1,20})"
>>> re_greeting = re.compile(r, flags=re.IGNORECASE)   ⇽--- 可以编译正则表达式,这样就不必在每次使用它们时指定选项(或标志)
>>> re_greeting.match('Hello Rosa')
<_sre.SRE_Match object; span=(0, 10), match='Hello Rosa'>
>>> re_greeting.match('Hello Rosa').groups()
('Hello', None, None, 'Rosa')
>>> re_greeting.match("Good morning Rosa")
<_sre.SRE_Match object; span=(0, 17), match="Good morning Rosa">
>>> re_greeting.match("Good Manning Rosa")  ⇽--- 注意,这个正则表达式无法识别(匹配)录入错误
>>> re_greeting.match('Good evening Rosa Parks').groups()  ⇽--- 这里的聊天机器人可以将问候语的不同部分分成不同的组,但是它不会知道Rosa是一个著名的姓,因为这里没有一个模式来匹配名后面的任何字符
('Good evening', 'Good ', 'evening', 'Rosa')
>>> re_greeting.match("Good Morn'n Rosa")
<_sre.SRE_Match object; span=(0, 16), match="Good Morn'n Rosa">
>>> re_greeting.match("yo Rosa")
<_sre.SRE_Match object; span=(0, 7), match='yo Rosa'>

 

提示 

引号前的“r”指定的是一个原始字符串,而不是正则表达式。使用Python原始字符串,可以将反斜杠直接传递给正则表达式编译器,无须在所有特殊的正则表达式字符前面加双反斜杠("\\"),这些特殊字符包括空格("\\ ")和花括号或称车把符("\\{ \\}")等。

 

上面的第一行代码(即正则表达式)中包含了很多逻辑,它完成了令人惊讶的一系列问候语,但它忽略了那个“Manning”的录入错误,这是NLP很难的原因之一。在机器学习和医学诊断中,这被称为假阴性(false negative)分类错误。不幸的是,它也会与人类不太可能说的话相匹配,即出现了假阳性(false positive)错误,这同样也是一件糟糕的事情。假阳性和假阴性错误的同时存在意味着我们的正则表达式既过于宽松又过于严格。这些错误可能会使机器人听起来有点儿迟钝和机械化,我们必须做更多的努力来改进匹配的短语,使机器人表现得更像人类。

并且,这项枯燥的工作也不太可能成功捕捉到人们使用的所有俚语和可能的拼写错误。幸运的是,手工编写正则表达式并不是训练聊天机器人的唯一方法。请继续关注后面的内容(本书的其余部分)。因此,我们只需在对聊天机器人的行为进行精确控制(如在向手机语音助手发出命令)时才使用正则表达式。

下面我们继续,通过添加一个输出生成器最终得到一个只用一种技巧(正则表达式)的聊天机器人。添加输出生成器的原因是它总需要说些什么。下面我们使用Python的字符串格式化工具构建聊天机器人回复的模板:

>>> my_names = set(['rosa', 'rose', 'chatty', 'chatbot', 'bot',
...     'chatterbot'])
>>> curt_names = set(['hal', 'you', 'u'])
>>> greeter_name = ''  ⇽--- 我们还不知道机器人的聊天对象是谁,这里我们也不担心这一点
>>> match = re_greeting.match(input())
...
>>> if match:
...     at_name = match.groups()[-1]
...     if at_name in curt_names:
...         print("Good one.")
...     elif at_name.lower() in my_names:
...         print("Hi {}, How are you?".format(greeter_name))

所以,如果你运行这一小段脚本,用“Hello Rosa”这样的短语和机器人聊天,她会回答“How are you”。如果用一个略显粗鲁的名字来称呼聊天机器人的话,她回复就会不太积极,但也不会过于激动,而是试图鼓励用户用更礼貌的语言来交谈。如果你指名道姓地说出可能正在监听某条共线电话或某个论坛上的对话的人名,该机器人就会保持安静,并允许你和任何要找的人聊天。显然这里并没有其他人在监视我们的input()行,但是如果这是一个更大聊天机器人中的函数的话,那么就需要处理这类事情。

受计算资源所限,早期的NLP研究人员不得不使用人类大脑的计算能力来设计和手动调整复杂的逻辑规则来从自然语言字符串中提取信息。这称为基于模式(pattern)的NLP方法。这些模式就像正则表达式那样,可以不仅仅是字符序列模式。NLP还经常涉及词序列、词性或其他高级的模式。核心的NLP构建模块(如词干还原工具和分词器)以及复杂的端到端NLP对话引擎(聊天机器人)(如ELIZA)都是通过这种方式,即基于正则表达式和模式匹配来构建的。基于模式匹配NLP方法的艺术技巧在于,使用优雅的模式来获得想要的内容,而不需要太多的正则表达式代码行。

 

经典心智计算理论 

这种经典的NLP模式匹配方法是建立在心智计算理论(computional theory of mind,CTM)的基础上的。CTM假设类人NLP可以通过一系列处理的有限逻辑规则集来完成。在世纪之交,神经科学和NLP的进步导致了心智“连接主义”理论的发展,该理论允许并行流水线同时处理自然语言,就像在人工神经网络中所做的那样。

 

在第2章中,我们将学习更多基于模式的方法,例如,用于词干还原的Porter工具和用于分词的Treebank分词器。但是在后面的章节中,我们会利用现代计算资源以及更大的数据集,来简化这种费力的手工编码和调优。

如果大家新接触正则表达式,想了解更多,那么可以查看附录B或Python正则表达式的在线文档。但现在还不需要去理解它们,我们将利用正则表达式构建NLP流水线,并提供相关示例。因此,大家不要担心它们看起来像是胡言乱语。人类的大脑非常善于从一组例子中进行归纳总结,我相信在本书的最后这一切都会变得清晰起来。事实证明,机器也可以通过这种方式学习。

有没有一种统计或机器学习方法可以替代上面基于模式的方法?如果有足够的数据,我们能否做一些不一样的事情?如果我们有一个巨大的数据库,该数据库由数千甚至上百万人类的对话数据构成,这些数据包括用户所说的语句和回复,那又会怎么样呢?构建聊天机器人的一种方法是,在数据库中搜索与用户对聊天机器人刚刚“说过”的话完全相同的字符串。难道我们就不能用其他人过去说过的话作为回复吗?

但是想象一下,如果语句中出现一个书写错误或变异,会给机器人带来多大的麻烦。位和字符序列都是离散的。它们要么匹配,要么不匹配。然而,我们希望机器人能够度量字符序列之间的意义差异。

当使用字符序列匹配来度量自然语言短语之间的距离时,我们经常会出错。具有相似含义的短语,如good和okay,通常会有不同的字符序列,当我们通过清点逐个字符的匹配总数来计算距离时,它们会得到较大的距离。而对于具有完全不同含义的序列,如bad和bar,当我们使用数值序列间的距离计算方法来度量它们的距离时,可能会得到过于接近的结果。像杰卡德距离(Jaccard distance)、莱文斯坦距离(Levenshtein distance)和欧几里得距离(Euclidean distance)这样的计算方法有时可以为结果添加足够的“模糊性”,以防止聊天机器人犯微小的拼写错误。但是,当两个字符串不相似时,这些度量方法无法捕捉它们之间关系的本质。它们有时也会把拼写上存在小差异的词紧密联系在一起,而这些小差异可能并不是真正的拼写错误,如bad和bar。

为数值序列和向量设计的距离度量方法对一些NLP应用程序来说非常有用,如拼写校正器和专有名词识别程序。所以,当这些距离度量方法有意义时,我们使用这些方法。但是,针对那些我们对自然语言的含义比对拼写更感兴趣的NLP应用程序来说,有更好的方法。对于这些NLP应用程序,我们使用自然语言词和文本的向量表示以及这些向量的一些距离度量方法。下面,我们一方面讨论这些不同的向量表示以及它们的应用,另一方面我们将逐一向读者介绍每种方法。

我们不会在这个令人困惑的二进制逻辑世界里待太久,但是可以想象一下,我们是第二次世界大战时期著名的密码破译员玛维斯·贝特(Mavis Batey),我们在布莱切利公园(Bletchley Park)刚刚收到了从两名德国军官之间的通信中截获的二进制莫尔斯码(Morse code)消息。它可能是赢得战争的关键。那么我们从哪里开始呢?我们分析的第一步是对这些位流做一些统计,看看是否能找到规律。我们可以首先使用莫尔斯码表(或者在我们的例子中使用ASCII表)为每组位分配字母。然后,如果字符看上去胡言乱语也不奇怪,因为它们是提交给二战中的计算机或译码机的字符。我们可以开始计数、在字典中查找短序列,这部字典收集了所有我们以前见过的词,每次查到序列就在字典中的该条目旁边做一个标记。我们还可以在其他记录本中做一个标记来标明词出现在哪条消息中,并为以前读过的所有文档创建百科全书式的索引。这个文档集合称为语料库(corpus),索引中列出的词或序列的集合称为词库(lexicon)。

如果我们足够幸运,没有生活在战争年代,所看到的消息没有经过严格加密,那么我们将在上述德语词计数中看到与用于交流类似消息的英语词计数相同的模式。不像密码学家试图破译截获的德语莫尔斯码,我们知道这些符号的含义是一致的,不会随着每次键点击而改变以迷惑我们。这种枯燥的字符和词计数正是计算机无须思考就能做的事情。令人惊讶的是,这几乎足以让机器看起来能理解我们的语言。它甚至可以对这些统计向量进行数学运算,这些向量与我们人类对这些短语和词的理解相吻合。当我们在后面的章节中介绍如何使用Word2vec来教机器学习我们的语言时,它可能看起来很神奇,但事实并非如此,这只是数学和计算而已。

但是我们想一下,刚才在努力统计收到消息中的词信息时,我们到底丢失了哪些信息?我们将词装箱并将它们存储为位向量,就像硬币或词条分拣机一样,后者将不同种类的词条定向到一边或另一边,形成一个级联决策,将它们堆积在底部的箱子中。我们的分拣机必须考虑数十万种(即使不是数百万种的话)可能的词条“面额”,每种面额对应说话人或作家可能使用的一个词。我们将每个短语、句子或文档输入词条分拣机,其底部都会出来一个向量,向量的每个槽中都有词条的计数值。其中的大多数计数值都为零,即使对于冗长的大型文档也是如此。但是目前为止我们还没有丢失任何词,那么我们到底丢失了什么?大家能否理解以这种方式呈现出的文档?也就是说,把语言中每个词的计数值呈现出来,而不把它们按照任何序列或顺序排列。我对此表示怀疑。当然,如果只是一个简短的句子或推文,那么可能在大多数情况下我们都能把它们重新排列成其原始或期望的顺序和意义。

下面给出的是在NLP流水线中如何在分词器(见第2章)之后加入词条分拣机的过程。这里的词条分拣机草图中包含了一个停用词过滤器和一个罕见词过滤器。字符串从顶部流入,词袋向量从底部词条栈中词条的高低堆叠中创建。

事实证明,机器可以很好地处理这种词袋,通过这种方式能够收集即便是中等长度的文档的大部分信息内容。在词条排序和计数之后,每篇文档都可以表示为一个向量,即该文档中每个词或词条的整数序列。图1-2中给出了一个粗略的示例,后面在第2章会给出词袋向量的一些更有用的数据结构。

图1-2 词条分拣托盘

这是我们给出的语言的第一个向量空间模型。这些栈和它们包含的每个词的数目被表示成一个长向量,该向量包含了许多0、一些1或2,这些数字散落在词所属栈出现的位置。这些词的所有组合方式构成的向量称为向量空间。该空间中向量之间的关系构成了我们的模型,这个模型试图预测这些词出现在各种不同的词序列(通常是句子或文档)集合中的组合。在Python中,我们可以将这些稀疏的(大部分元素都为空)向量(数值列表)表示为字典。Python中的Counter是一种特殊的字典,它存储对象(包括字符串),并按我们想要的方式为对象计数:

>>> from collections import Counter

>>> Counter("Guten Morgen Rosa".split())
Counter({'Guten': 1, 'Rosa': 1, 'morgen': 1})
>>> Counter("Good morning, Rosa!".split())
Counter({'Good': 1, 'Rosa!': 1, 'morning,': 1})

大家可能会想到一些分拣这些词条的方法,我们会在第2章中实现这一点。大家也可能会想,这些稀疏高维向量(许多栈,每个可能的词对应一个栈)对语言处理不是很有用。但是对于一些引起行业变革的工具,如我们将在第3章中讨论的垃圾短信过滤器,它们已经足够好用了。

可以想象,我们把能找到的所有文档、语句、句子甚至单个词,一个一个地输到这台机器。我们会在每个语句处理完之后,对底部每个槽中的词条计数,我们称之为该语句的向量表示。机器以这种方式产生的所有可能的向量称为向量空间。这种表示文档、语句和词的模型称为向量空间模型。它允许我们使用线性代数来对这些向量进行运算,计算距离和自然语言语句的统计信息,这些信息有助于我们用更少的人工编码来解决更广泛的问题,同时也使得NLP流水线更加强大。

一个关于词袋向量序列的统计学问题是,在特定的词袋下最可能出现的词组合是什么?或者,更进一步,如果用户输入一个词序列,那么数据库中最接近用户提供的词袋向量的词袋是什么?这其实是一个搜索查询。输入词是用户可能在搜索框中键入的词,最接近的词袋向量对应于要查找的目标文档或网页。高效回答上述两个问题的能力足以构建一个机器学习聊天机器人,随着我们给它提供的数据越来越多,它也会变得越来越好。

但是等一下,也许这些向量不像大家以前用过的任何向量。它们的维度非常高。从一个大型语料库中得到的3-gram词汇表可能有数百万个维度。在第3章中,我们将讨论“维数灾难”和高维向量难以处理的其他一些性质。

在第3章中,我们将介绍如何将词合并到更小的向量维数中,以缓解维数灾难问题,并可能为我所用。当将这些向量相互投影以确定向量对之间的距离时,这将是对它们语义相似性而不是统计性词用法的合理估计。这个向量距离度量方法称为余弦距离,我们会在第3章中讨论这一距离,然后在第4章中展示它在降维后的主题向量上的真正威力。我们甚至可以将这些向量投影到二维平面上(更准确的说法是嵌入),以便在图表中对它们进行观察,看看我们的大脑是否能从中找到某些模式。然后,我们可以教计算机识别这些模式,并以反映产生这些向量的词的隐性含义的方式对其进行处理。

想象一下人类可能会写的所有推文、消息或句子。尽管我们确实会不断重复自己写过的东西,但仍然有太多的可能性。当这些词条分别被视为单独的、不同的维度时,我们并不知道“Good morning, Hobs”与“Guten Morgen, Hannes”其实具有相同的含义。我们需要为消息创建一些降维的向量空间模型,这样就可以用一组连续(浮点)值来标记它们。我们可以根据主题和情感等特点对消息和文字进行评级。这样,我们就可以问下面这样的问题:

想想我们能赋予语句的所有评级,我们可以把这些评级按顺序排列,然后为每条语句计算评级,从而为每条语句生成一个向量。我们能为一组语句给出的评级列表或维度应该比可能的语句数量小得多。对于上述所有的问题,意义相同的语句应该有相似的分值。

这些评级向量变成了机器可以编程进行回复的对象。我们可以通过对语句聚类(聚集)进一步简化和泛化向量,使它们在某些维度上接近,而在其他维度上不接近。

但是,计算机应该如何为这些向量的每一个维度赋值呢?我们把向量维度的问题简化成“它包含good这个词吗?”“它包含morning这个词吗?”等问题。我们可以看到,这里可以提出100万个左右的问题,这就是计算机可以分配给一个短语的数值范围。这是第一个实际的向量空间模型,称为位向量语言模型,或者说是独热编码向量的求和结果。我们可以看到,为什么计算机现在变得越来越强大,足以理解自然语言。人类简单生成的数百万个百万维向量,在20世纪80年代的超级计算机上根本无法计算,但在21世纪的普通笔记本电脑上计算则没有任何问题。不仅仅是原始硬件的功率和容量导致NLP越来越实用,增长的常数内存、线性代数算法是机器破解自然语言编码的最后一块“拼图”。

还有一个更简单但更大的表示法可以用于聊天机器人。如果我们的向量维度完全描述了字符的精确序列会怎样?它会包含下面这些问题的答案,如“第一个字母是A吗?是B吗?……”“第二个字母是A吗?”等。这个向量的优点是,它保留了原始文本中包含的所有信息,包括字符和词的顺序。想象一下,一架钢琴一次只能演奏一个音符,它可以演奏52个或更多的音符。自然语言机械钢琴的音符是26个大写字母、26个小写字母再加上钢琴必须知道如何演奏的任何标点符号。钢琴纸卷不会比真正的钢琴宽很多,而且一些长钢琴曲中的音符数量不会超过一个小文档中的字符数量。但是,这种独热字符序列编码表示法主要用于精确记录和重放原始片段,而非编写新内容或提取一件作品的精髓。在这种表示法下,我们不太容易将某一首歌的钢琴纸卷与另一首歌的相比。这个表示比文档的原始ASCII编码表示还要长。为了保留每个字符序列的信息,文档表示的可能数量会爆炸。这里,我们虽然保留了字符和词的顺序,但是扩展了NLP问题的维度。

在上述基于字符的向量空间中,这些文档表示不能很好地通过聚类聚在一起。俄罗斯数学家弗拉基米尔·莱文斯坦(Vladimir Levenshtein)提出了一个非常聪明的方法,可以快速地找到这个空间下序列(字符串)之间的相似性。只使用这种简单的、机械的语言视图,莱文斯坦算法就能使创建一些超级有趣和有用的聊天机器人成为可能。但是,当我们想到如何将这些高维空间压缩/嵌入到具有模糊含义的较低维空间得到所谓的主题向量时,真正神奇的事情发生了。在第4章中,当我们讨论潜在语义索引和潜在狄利克雷分配时,我们将看到隐藏在魔术师幕布后的东西,这两种技术可以创建更密集、更有意义的语句和文档的向量表示。

词的顺序很重要。那些在词序列(如句子)中控制词序的规则被称为语言的语法(grammar,也称文法)。这是之前的词袋或词向量例子中所丢弃的信息。幸运的是,在大多数简短的短语甚至许多完整的句子中,上述词向量近似方法都可以奏效。如果只是想对一个短句的一般意义和情感进行编码的话,那么词序并不十分重要。看一下“Good morning Rosa”这个例子中的所有词序结果:

>>> from itertools import permutations

>>> [" ".join(combo) for combo in\
...     permutations("Good morning Rosa!".split(), 3)]
['Good morning Rosa!',
 'Good Rosa! morning',
 'morning Good Rosa!',
 'morning Rosa! Good',
 'Rosa! Good morning',
 'Rosa! morning Good']

现在,如果试图孤立地解释这些字符串中的每一个(不看其他字符串),那么可能会得出结论,即这些字符串可能都有相似的意图或含义。我们甚至可能注意到Good这个词的大写形式,并把它放在脑海中短语的最前面。但是我们也可能认为Good Rosa是某种专有名词,如餐馆或花店的名字。尽管如此,一个聪明的聊天机器人或者布莱切利公园20世纪40年代的聪明女士可能会用同样无伤大雅的问候语来回应这6种情况中的任何一种:“Good morning my dear General.”[17]

我们(在脑海中)再用一个更长、更复杂的短语来尝试一下,这是一条逻辑语句,其中词的顺序非常重要:

>>> s = """Find textbooks with titles containing 'NLP',
...     or 'natural' and 'language', or
...     'computational' and 'linguistics'."""
>>> len(set(s.split()))
12
>>> import numpy as np
>>> np.arange(1, 12 + 1).prod()  # factorial(12) = arange(1, 13).prod()
479001600

词排列的数量从简单的问候语factorial(3) == 6激增到更长的语句factorial(12) == 479001600!很明显,词序所包含的逻辑对任何希望正确回复的机器而言都很重要。尽管普通的问候语通常不会因为词袋处理而造成混淆,但如果把更复杂的语句放入词袋中,就会丢失大部分意思。就像前面示例中的自然语言查询一样,词袋并不是处理数据库查询的最佳方式。

无论语句是用形式化的编程语言(如SQL)编写的,还是用非形式化的自然语言(如英语)编写的,当语句要表达事物之间的逻辑关系时,词序和语法都非常重要。这就是计算机语言依赖严格的语法和句法规则分析器的原因。幸运的是,自然语言句法树分析器取得了一些最新进展,使得从自然语言中提取出语法和逻辑关系变得可能,并且可以达到显著的精确率(超过90%)[18]。在后面的章节中,我们将介绍如何使用SyntaxNet(Parsey McParseface)和SpaCy这样的包来识别这些关系。

就像上面有关布莱切利公园问候语的例子一样,即使一条语句的逻辑解释并不依赖词序,有时关注词序也可以得到一些十分微妙的相关意义的暗示,这些意义可以辅助更深层次的回复。有关这些更深层的自然语言处理环节将在下一节讨论。此外,第2章会介绍一种技巧,它能够将一些由词序表达的信息融合到词向量表示当中。同时,第2章还会介绍如何改进前面例子中使用的分词器(str.split()),以便更准确地将词向量中的词放到更合适的槽内。这样,“good”和“Good”这样的词会放到同一个栈里,而“rosa”和“Rosa”(不是“Rosa!”)这样的词条将会分配到不同的栈。

构建对话引擎或聊天机器人所需的NLP流水线与Ingersol、Morton和Farris所写的《驾驭文本》(Taming Text)一书中描述的问答系统类似。然而,在5个子系统中列出的一些算法可能对读者来说是全新的。我们帮助大家在Python中实现这些算法,以完成大多数应用程序(包括聊天机器人)所必需的各种NLP任务。

聊天机器人需要4个处理阶段和一个数据库来维护过去语句和回复的记录。这4个处理阶段中的每个阶段都可以包含一个或多个并行或串行工作的处理算法(如图1-3所示)。

(1)解析:从自然语言文本中提取特征、结构化数值数据。

(2)分析:通过对文本的情感、语法合法度及语义打分,生成和组合特征。

(3)生成:使用模板、搜索或语言模型生成可能的回复。

(4)执行:根据对话历史和目标,规划相应语句,并选择下一条回复。

上述4个阶段中的每个阶段都可以使用框图中相应框中列出的一个或多个算法来实现。我们将介绍如何使用Python为这些处理步骤中的每一个步骤实现近乎最高效的性能。另外,我们还会介绍这5个子系统的几种其他实现方法。

大多数聊天机器人将包含这5个子系统(4个处理阶段加上数据库)的所有元素。但是很多应用程序针对其中多个步骤只需要简单的算法。有些聊天机器人更擅长回答事实型问题,而其他一些则更擅长做出冗长、复杂、令人信服的像人一样的回复。上述提到的每一种能力都需要不同的方法,我们会介绍同时实现这两种能力的技术。

图1-3 聊天机器人的循环流水线

此外,深度学习和数据驱动编程(机器学习或概率语言建模)使NLP和聊天机器人的应用迅速多样化。这种数据驱动的方法通过为NLP流水线提供越来越多的期望得以应用的领域中的数据使其更加复杂。当一种新的机器学习方法被发现能够更好地利用这些数据进行更有效的模型泛化或正则化时,那么就有可能实现能力的巨大飞跃。

图1-3所示的聊天机器人NLP流水线包含了本章一开始描述的大多数NLP应用程序的所有构建模块。与《驾驭文本》一书一样,我们将流水线划分为4个主要的子系统或阶段。此外,我们还显式地调用了一个数据库来于记录每个阶段所需的数据,并随着时间的推移保存这些阶段的配置和训练集。这可以在聊天机器人与外界进行交互时对每个阶段进行批量或在线再训练。我们还在生成的文本回复上给出了一个反馈循环,以便使用与处理用户语句相同的算法来处理我们的回复。然后,根据聊天机器人的对话规划或目标,将回复的得分或特征融合到一个目标函数中,以评估和选择可能的最佳回复。本书主要关注在聊天机器人上配置这个NLP流水线,但是大家也可以看到类似于文本检索或搜索的NLP问题,而搜索可能是最常见的NLP应用。很明显,这里的聊天机器人流水线也适用于《驾驭文本》这本书所关注的重点应用——问答系统。

上述流水线在金融预测或商业分析方面的应用可能不那么明显,但是想象一下流水线分析部分生成的特征。这些从分析或特征生成中得到的特征可以针对具体的金融或商业预测任务进行优化。通过这种方式,就可以将自然语言数据输入到机器学习流水线中进行预测。尽管专注于构建聊天机器人,但本书也为大家提供了从搜索到金融预测等广泛的NLP应用程序所需的工具。

在图1-3中有一个处理要素通常不会用于搜索、预测或问答系统,这就是自然语言生成。而对聊天机器人来说,这是它的核心特征。尽管如此,文本生成步骤经常被合并到搜索引擎NLP应用程序中,这可以为这样的引擎带来巨大的竞争优势。对许多流行的搜索引擎(DuckDuckGo、Bing和Google)来说,整合或概括搜索结果的能力是一项制胜特征。可以想象,如果一个金融预测引擎能够根据它从社交媒体网络和新闻源中的自然语言流中检测到的金融业务活动生成语句、推文或整篇文章,那该多么有价值!

下一节将展示如何组合这样一个系统的各层,以便在NLP流水线的每个阶段创建更复杂的功能。

自然语言处理流水线的各个阶段可以看作是层,就像前馈神经网络中的层一样。深度学习就是通过在传统的两层机器学习模型架构(特征提取+建模)中添加额外的处理层来创建更复杂的模型和行为。在第5章中,我们将解释神经网络如何通过将模型错误从输出层反向传播回输入层,从而帮助完成跨层传播学习的过程。但是,这里我们讨论的是那些顶层以及通过独立训练(各层的训练独立)所能达到的结果。

图1-4中的前四层对应于上一节聊天机器人流水线中的前两个阶段(特征提取和特征分析)。例如,词性标注(POS标注)是在聊天机器人流水线的分析阶段生成特征的一种方法。POS标签由默认的SpaCY流水线自动生成,该流水线包括图1-4中所有的前四层。POS标注通常使用有限状态转换机来完成,就像nltk.tag包中的方法一样。

底部的两层(实体关系和知识库)用于构成包含特定领域信息(知识)的数据库。使用所有这6层从特定语句或文档中提取的信息可以与该数据库结合使用进行推理。这里的推理结果是从环境中检测到的一组条件中进行的逻辑推理,就像聊天机器人语句中包含的逻辑一样。图中较深层的这种推理机被认为属于人工智能的领域,机器可以对它们的世界进行推理,并使用这些推理结论做出逻辑决策。然而,聊天机器人只使用上面几层的算法,可以在没有上述知识库的情况下做出合理的决策。这些决策组合起来可能会产生令人惊讶的类人行为。

在接下来的几章,我们将深入到NLP的最上面几层。最上面的3层是进行有意义的情感分析和语义搜索,以及构建仿人聊天机器人所需要的全部内容。事实上,只使用一层,直接使用文本(字符序列)作为语言模型的特性,就可以构建一个有用且有趣的聊天机器人。如果给出足够的示例语句和回复,只进行字符串匹配和搜索的聊天机器人就能够参与到合理的令人信服的对话中。

图1-4 NLP流水线中的示例层

例如,开源项目ChatterBot大大简化了上述流水线,它只计算输入语句和记录在数据库中的语句之间的字符串“编辑距离”(莱文斯坦距离)。如果其语句-回复对数据库中包含匹配的语句,则可以通过复用对应的回复(来自预先“学习”过的人工或机器对话框)作为最新语句的回复。对于该流水线,所需要的只是聊天机器人流水线的步骤3(生成)。在这个阶段,只需要一个暴力搜索算法就可以找到最佳回复。通过这种简单的技术(不需要分词或特征生成),ChatterBot作为Salvius的对话引擎可以维护令人信服的对话过程,而Salvius是由冈瑟·考克斯(Gunther Cox)用回收部件构建的机械机器人。

Will是由Steven Skoczen开发的一个开源的Python聊天机器人框架,它采用了完全不同的方法[19]Will只能通过训练对正则表达式语句作出回复。这是“重人力轻数据”的一种NLP方法。这种基于语法的方法对于问答系统和任务执行助理机器人(如Lex、Siri和Google Now)尤其有效。这些系统通过使用“模糊正则表达式”[20]和其他技术来寻找近似的语法匹配,从而克服了精确正则表达式的脆弱性。模糊正则表达式不做精确匹配,而是无视插入、删除和替换的最大错误数目,在可能的语法规则(正则表达式)列表中寻找最接近的语法匹配结果。然而,要对基于语法的聊天机器人行为的广度和复杂性进行扩展,需要大量的人力开发工作。即使是由地球上一些最大的公司(谷歌、亚马逊、苹果、微软)所构建和维护的最先进的基于语法的聊天机器人,聊天机器人智商的深度和广度方面仍处于中游水平。

浅层NLP能够完成许多强大的任务,而且,几乎不需要(有的话也会极少)人工监督(对文本进行标注或整理)。通常,机器可以持续不断地从它所处的环境(它可以从Twitter或其他来源获取的词流)中学习[21]。我们将在第6章介绍如何做到这一点。

就像人类的智能一样,如果不考虑多个智能维度,单凭一个智商分数是无法轻易衡量NLP流水线的能力的。衡量机器人系统能力的一种常见方法是,根据系统行为的复杂性和所需的人类监督程度这两个维度来衡量。但是对自然语言处理流水线而言,其目标是建立一个完全自动化的自然语言处理系统,会消除所有的人工监督(一旦模型被训练和部署)。因此,一对更好的IQ维度应该能捕捉到自然语言流水线复杂性的广度和深度。

像Alexa或Allo这样的消费产品聊天机器人或者虚拟助手,通常设计为具有极其广泛的知识和功能。然而,用于响应请求的逻辑往往比较浅显,通常由一组触发短语组成,这些短语都使用单个if-then决策分支来生成相同的回复。Alexa(以及底层的Lex引擎)的行为类似于一个单层的、扁平的(ifelifelif……)语句树[22]。谷歌的Dialogflow是独立于谷歌的Allo和谷歌智能助理(Google Assistant)开发出的产品,具有与亚马逊的Lex、Contact Flow和Lambda类似的功能,但是没有用于设计对话树的拖放用户界面。

另一方面,谷歌翻译(Google Translate)流水线(或任何类似的机器翻译系统)依赖一个由特征提取器、决策树和知识图谱组成的深层树结构,其中知识图谱连接着世界知识。有时,这些特征提取器、决策树和知识图谱被显式地编程到系统中,如图1-4所示。另一种快速超越这种手工编码流水线的方法是基于深度学习的数据驱动方法。深度神经网络的特征提取器是自动学习而不是硬编码的,但它们通常需要更多的训练数据才能达到与精心设计的算法相同的性能。

下面我们逐步为聊天机器人建立NLP流水线以便能够在某个知识领域和用户交谈,这期间我们将使用上面提到的这两种方法(神经网络和手工编码算法)。该实战过程将为大家提供所需的技能,以完成大家在各自的工业或商业领域的自然语言处理任务。在此过程中,大家可能会了解如何扩展NLP流水线以拓宽任务范围。图1-5将聊天机器人置于现存的自然语言处理系统中。想象一下与我们交互的聊天机器人,大家认为它们在图中应该处于什么位置?大家有没有试过用难题或类似IQ测试的方法来测试它们的智商[23]?在后面的章节中,大家将有机会确切地做到这一点,以帮助大家确定自己开发的聊天机器人与图中其他机器人的异同。

图1-5 一些NLP系统的二维智商展示

随着阅读的不断深入,我们将构建聊天机器人的各个组成元素。聊天机器人需要所有的NLP工具才能很好地工作:

机器学习给了我们一种方式,让机器表现得就像我们花了一辈子时间用数以百计的复杂正则表达式或算法在它身上一样。只需要提供用户语句以及期望聊天机器人模拟的回复的示例,我们就可以教会机器对类似于正则表达式中定义的模式进行回复。而由机器学习产生的语言“模型”,即有限状态机则好得多,它们对拼写和录入错误不那么敏感。

此外,基于机器学习的NLP流水线更容易用编程实现。我们不需要预测语言符号的每一种可能用法,而只需要给训练流水线提供匹配和不匹配的短语样本。只要在训练过程中给它们贴上标签,聊天机器人就知道哪些是正样本,哪些是负样本,它就会学会对正负样本进行区分。甚至还有一些机器学习方法,几乎不需要“标记”数据。

我们已经给了读者一些学习NLP的令人兴奋的理由。大家想拯救世界,是吗?我们试图通过一些实际的NLP应用程序来引起大家的兴趣,这些应用正在改变我们的沟通、学习、交易甚至思考的方式。不久之后,我们就可以构建一个类似于人类会话行为的系统。大家应该能够在接下来的章节中看到,如何用感兴趣的领域知识来训练聊天机器人或NLP流水线,这些领域从金融、体育到心理学和文学。如果能找到关于某一领域的语料库,那么就能训练机器去理解这个领域。

本书接下来的其余各章将介绍机器学习在NLP中的应用,通过机器学习可以避免我们预测自然语言所有的表述方式。每一章都对本章介绍的聊天机器人的基本NLP流水线进行逐步改进。在学习NLP工具的过程中,我们将构建NLP流水线,它不仅可以用于对话,还可以帮助我们实现商业和生活中的目标。

[1] 通常认为,自然语言处理包括自然语言理解(Natural Language Understanding,NLU)和自然语言生成(Natural Language Generation,NLG)。——译者注

[2] Ethnologue是一个Web出版物,它维护着一些有关自然语言的统计信息。

[3] 详见标题为“How Google’s Site Crawlers Index Your Site - Google Search”的网页。

[4] 我们可以估计出实际自然语言文本的数量至少是谷歌索引的1000倍。

[5] 数学符号是有歧义的,参见维基百科文章“Ambiguity”的“Mathematical notation”一节。

[6] Gonzalo - Ibanez等人发现,即使是受过教育和培训的人类评判员,也无法达到他们在ACM论文中报告的采用简单分类算法达到的68%的精确率。康奈尔大学Matthew Cliché开发的Sarcasm Detector和Web应用程序也达到了类似的精确率(>70%)。

[7] 一些语法规则可以通过计算机科学抽象中的有限状态机来实现。正则语法可以通过正则表达式来实现。Python有两个包可以用于运行正则表达式有限状态机,一个是内嵌的re,另一个是regex,必须要额外安装,但是后者应该很快会代替前者。有限状态机就是一棵树,包括面向每个词条(字符/词/n 元)的if…then…语句以及机器必须要回应或生成的行为动作。

[8] 2015年1月29日发布于科技博客The Verge的一篇文章“AP’s ‘robot journalists’ are writing their own stories now”(美联社的机器人记者正在写自己的报道)。

[9] 2016年10月18日的《纽约时报》,2016年11月的《MIT科技评论》。

[10] 谷歌博客在2018年5月介绍了其Duplex系统。

[11] 参见维基百科词条“AI control problem”。

[12] 2017年3月10日WSJ博客。

[13] 密码锁是锁的一种,开启时用的是一系列的数字或符号。(摘自维基百科)——译者注

[14] Cialdini在他的畅销书《影响力》(Influence)中提出了6条心理学原则。

[15] 正则语法、上下文无关、有关语法中的语法也常常称为文法。——译者注

[16] 2016年7月20日,由于某个正则表达式“崩溃”导致Stack Exchange宕机了30分钟。

[17] 这个和英国二战密码破译的公园有关。——译者注

[18] 有关多个语法分析器精度的一个对比(Spacy达到93%,SyntaxNet达到94%,斯坦福大学的CoreNLP达到90%,还有其他一些分析器)可以参考Spacy官方文档。

[19] 参考Will的GitHub网页,Will是Steven Skoczen和 HipChat社区为HipChat开发的一款聊天机器人。2018年Will被集成到Slack中。

[20] Python的regex包与re保持后向兼容,它加入了模糊性这一特征。将来regex会取代re。类似地,TREagrep或者近似grep是UNIX命令行应用程序grep的一个替代命令。

[21] 简单的神经网络常常用于从字符和词序列中无监督地提取特征。

[22] 将Lambda加入AWS Contact Flow对话树,就能获得更复杂的逻辑和行为。参考“Creating Call Center Bot with AWS Connect”。

[23] Byron Reese建议的一个好问题是“What’s larger? The sun or a nickel?”。本书GitHub上有更多问题供大家起步时参考(src/nlpia/data/iq_test.csv)。


相关图书

深度学习的数学——使用Python语言
深度学习的数学——使用Python语言
动手学自然语言处理
动手学自然语言处理
Web应用安全
Web应用安全
Python高性能编程(第2版)
Python高性能编程(第2版)
图像处理与计算机视觉实践——基于OpenCV和Python
图像处理与计算机视觉实践——基于OpenCV和Python
Python数据科学实战
Python数据科学实战

相关文章

相关课程