Python-自然语言处理-全-
Python 自然语言处理(全)
原文:
annas-archive.org/md5/34b9a9247886758452c7c8642a43ec61译者:飞龙
前言
书名《Python 自然语言处理》可以让你大致了解本书的内容。作为读者,你将有机会从零开始学习关于自然语言处理(NLP)的所有方面。在这本书中,我使用非常简单的语言阐明了 NLP 的概念,并且有一些非常酷的实践案例,能够加深你对这一领域的理解。通过实现这些示例,你可以提升你的 NLP 技能。你不觉得这听起来很有趣吗?
现在让我回答一些我从朋友和同事那里收到的关于 NLP 领域的常见问题。这些问题真正激励我写这本书。对我来说,确保所有读者都能理解我为什么要写这本书非常重要。让我们一起找出答案!
在这里,我想回答一些我认为对我的读者至关重要的问题。所以,我将先列出一些问题,然后给出答案。我通常被问到的第一个问题是——什么是 NLP?第二个问题是——为什么 Python 主要用于开发 NLP 应用程序?最后但同样重要的是,最关键的问题是——我可以使用哪些资源来学习 NLP?现在,让我们看看答案!
第一个问题的答案是,简而言之,NLP 就是人类所说、写、读或理解的语言;因此,自然语言是一种交流媒介。通过计算机科学算法、数学概念和统计技术,我们尝试处理语言,使机器也能像人类一样理解语言;这就是所谓的NLP。
现在让我们来回答第二个问题——为什么人们主要使用 Python 来开发 NLP 应用程序?我有一些事实想和你分享。首先,Python 有许多库可以让你在开发 NLP 应用程序时更加轻松。第二个原因是,如果你来自 C 或 C++的编程背景,你不必担心内存泄漏的问题。Python 解释器会为你处理这一切,这样你就可以专注于主要的编码部分。而且,Python 是一种对程序员友好的语言。你只需写少量的代码,就能做更多的事情,相比其他面向对象的语言。因此,所有这些因素促使人们选择 Python 来开发 NLP 以及其他数据科学相关的应用程序,以便进行快速原型开发。
最后的问题对我来说至关重要,因为我以前总是将之前的答案解释给我的朋友们听,但在听完所有这些和其他有趣的内容后,他们会来找我说他们想学习 NLP,那么有什么资源可以用呢?我以前会推荐书籍、博客、YouTube 视频、教育平台如 Udacity 和 Coursera 等等,但几天后,他们又会问我,是否有一本书、一个博客或任何形式的单一资源可以使用。不幸的是,我的回答是没有。在那个阶段,我真的觉得 juggling(同时应付)这些资源对他们来说始终是个难题,这种痛苦的觉悟成为了我写这本书的灵感。
所以在本书中,我尝试涵盖大多数 NLP 的核心内容,这些内容将对每个人都有帮助。好消息是,我提供了使用 Python 的实践示例,读者可以理论和实践相结合地理解所有的概念。阅读、理解和编码是我在本书中所遵循的三大主要过程,以便让读者的学习更加轻松。
本书内容
第一章,介绍,为 NLP 以及 NLP 领域的各个分支提供了一个概述。我们将看到构建 NLP 应用程序的不同阶段,并讨论 NLTK 的安装。
第二章,语料库和数据集的实际理解,展示了语料库分析的各个方面。我们将看到语料库中不同类型的语料和数据属性。我们将涉及不同的语料库格式,如 CSV、JSON、XML、LibSVM 等等。我们还会看到一个网络抓取的例子。
第三章,理解句子结构,帮助你理解自然语言的最重要方面——语言学。我们将探讨词汇分析、句法分析、语义分析、歧义处理等概念。我们将使用 NLTK 来实践理解所有这些概念。
第四章,预处理,帮助你了解各种预处理技术以及如何自定义它们。我们将看到预处理的各个阶段,如数据准备、数据处理和数据转换。除此之外,你还将理解预处理的实际应用。
第五章,特征工程和 NLP 算法,是 NLP 应用程序的核心部分。我们将看到如何使用不同的算法和工具生成机器学习算法的输入,这些算法将用于开发 NLP 应用程序。我们还将理解特征工程中使用的统计概念,并深入了解工具和算法的自定义。
第六章,高级特征工程和 NLP 算法,让你了解 NLP 中处理语义问题的最新概念。我们将看到 word2vec、doc2vec、GloVe 等,以及通过从《权力的游戏》数据集中生成向量来实现 word2vec 的一些实际示例。
第七章,基于规则的 NLP 系统,详细讲解了如何构建基于规则的系统以及在为 NLP 开发时需要注意的所有方面。我们将看到规则的制定过程,并编写规则。我们还将了解如何开发基于模板的聊天机器人。
第八章,用于 NLP 问题的机器学习,为你提供了机器学习技术的新视角。我们将看到用于开发 NLP 应用程序的各种算法。我们还将使用机器学习实现一些很棒的 NLP 应用程序。
第九章,用于 NLU 和 NLG 问题的深度学习,介绍了人工智能的各个方面。我们将了解 人工神经网络(ANNs)的基本概念以及如何构建自己的 ANN。我们将深入理解硬核深度学习,发展深度学习的数学方面,并了解深度学习是如何用于 自然语言理解(NLU)和 自然语言生成(NLG)的。你也可以期待一些很酷的实践示例。
附录 A,高级工具,简要介绍了各种框架,如 Apache Hadoop、Apache Spark 和 Apache Flink。
附录 B,如何提升你的 NLP 技能,是我针对如何保持 NLP 技能更新以及持续学习如何帮助你获得新的 NLP 技能的一些建议。
附录 C,安装指南,包含了所需安装的说明。
本书所需的内容
让我们讨论一下本书的前置要求。别担心,这不是数学或统计学,你只需要了解基本的 Python 编程语法。除此之外,你还需要在计算机上安装 Python 2.7.X 或 Python 3.5.X;我建议使用任何 Linux 操作系统。
Python 依赖库的列表可以在 GitHub 仓库中的 github.com/jalajthanaki/NLPython/blob/master/pip-requirements.txt. 找到。
现在让我们来看一下本书所需的硬件。4GB 内存和至少双核 CPU 的计算机足以执行代码,但对于机器学习和深度学习示例,可能需要更多的内存,可能是 8GB 或 16GB,并且需要使用 GPU 的计算能力。
本书的适用人群
本书面向希望开始学习 NLP 并通过将 NLP 应用于其应用程序来使其更智能的 Python 开发者。
约定
本书中包含了多种文本样式,用以区分不同类型的信息。以下是这些样式的一些示例及其含义说明。
文本中的代码词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 账号名如下所示:“nltk库提供了一些内置语料库。”
代码块设置如下:
import nltk
from nltk.corpus import brown as cb
from nltk.corpus import gutenberg as cg
任何命令行输入或输出都按以下方式书写:
pip install nltk or sudo pip install nltk
新术语和重要词汇以粗体显示。你在屏幕上看到的词语,例如在菜单或对话框中,出现在文本中如下所示:“这将打开一个额外的对话框,在那里你可以选择特定的库,但在我们的情况下,点击所有包,你可以选择包所在的路径。等待所有包下载完成。”
警告或重要提示如下所示。
提示和技巧如下所示。
读者反馈
我们始终欢迎读者的反馈。告诉我们你对这本书的看法——你喜欢或不喜欢什么。读者反馈对我们非常重要,因为它帮助我们开发你能够真正从中获益的书籍。要发送一般反馈,只需发送电子邮件至feedback@packtpub.com,并在邮件主题中注明书名。如果你在某个领域有专业知识,且有兴趣撰写或贡献书籍,请查看我们的作者指南:www.packtpub.com/authors。
客户支持
现在你已经是 Packt 书籍的骄傲拥有者,我们提供了许多工具来帮助你最大化你的购买价值。
下载示例代码
你可以从你的账户下载这本书的示例代码文件,网址是www.packtpub.com。如果你是在其他地方购买的这本书,可以访问www.packtpub.com/support并注册,以便文件直接通过电子邮件发送给你。你可以按照以下步骤下载代码文件:
-
使用你的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标指针悬停在顶部的 SUPPORT 标签上。
-
点击代码下载与勘误。
-
在搜索框中输入书名。
-
选择你想下载代码文件的书籍。
-
从下拉菜单中选择你购买这本书的地方。
-
点击代码下载。
文件下载后,请确保使用最新版本解压或提取文件夹:
-
Windows 平台上的 WinRAR / 7-Zip
-
Mac 平台上的 Zipeg / iZip / UnRarX
-
Linux 平台上的 7-Zip / PeaZip
本书的代码包也托管在 GitHub 上,链接为github.com/PacktPublishing/Python-Natural-Language-Processing。我们还提供了来自我们丰富的书籍和视频目录中的其他代码包,地址为github.com/PacktPublishing/。快来查看吧!
下载本书的彩色图片
我们还提供了一个包含本书中所用截图/图表的彩色图片的 PDF 文件。这些彩色图片将帮助你更好地理解输出的变化。你可以从www.packtpub.com/sites/default/files/downloads/PythonNaturalLanguageProcessing_ColorImages.pdf下载此文件。
勘误表
虽然我们已尽力确保内容的准确性,但错误仍然会发生。如果你在我们的书籍中发现错误——可能是文本或代码中的错误——我们将非常感谢你报告给我们。通过这样做,你不仅能帮助其他读者避免困扰,还能帮助我们改进该书的后续版本。如果你发现任何勘误,请访问www.packtpub.com/submit-errata进行报告,选择你的书籍,点击“勘误提交表单”链接,并输入错误的详细信息。一旦你的勘误被验证,提交将被接受,并且勘误将上传至我们的网站,或加入到该书标题下的勘误列表中。要查看之前提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索框中输入书名。相关信息将出现在勘误部分。
盗版
网络上的版权材料盗版问题在各类媒体中普遍存在。我们在 Packt 公司非常重视版权和许可证的保护。如果你在互联网上遇到任何我们作品的非法复制品,请立即向我们提供该地址或网站名称,以便我们采取相应措施。请通过copyright@packtpub.com联系,并附上涉嫌盗版材料的链接。我们感谢你的帮助,保护我们的作者及我们为你提供有价值内容的能力。
问题
如果你对本书的任何部分有问题,可以通过questions@packtpub.com联系我们,我们将尽最大努力解决问题。
第一章:介绍
在本章中,我们将对自然语言处理(NLP)进行简要介绍,并讨论自然语言处理概念是如何在现实中的人工智能应用中使用的。我们将主要关注用于开发 NLP 应用的 Python 编程范式。之后,本章还会有一个为读者准备的提示部分。如果你真的有兴趣了解不同编程范式在 NLP 中的对比,以及为什么 Python 是最好的编程范式,那么作为读者,你应该阅读本书的前言。作为一名行业专业人士,我尝试过多种编程范式来开发 NLP 应用。我使用过 Java、R 和 Python 来开发 NLP 应用。相信我,Python 在开发使用 NLP 概念的应用时非常简单且高效。
本章将涵盖以下主题:
-
理解 NLP
-
理解基本应用
-
理解高级应用
-
NLP 和 Python 的结合优势
-
NLTK 的环境设置
-
读者提示
理解自然语言处理
在过去几年里,人工智能(AI)的各个分支引起了广泛关注,其中包括数据科学、数据分析、预测分析、NLP 等。
正如本书前言中提到的,我们专注于 Python 和自然语言处理。让我问你一些问题——你真的知道什么是自然语言吗?什么是自然语言处理?在使用各种自然语言处理概念构建专家系统时,涉及哪些其他分支?我们如何利用 NLP 的概念构建智能系统?
让我们开始理解 NLP 的过山车之旅。
什么是自然语言?
-
作为人类,我们通过语言表达我们的思想或感情
-
你说、读、写或听的任何内容大多都是自然语言的形式,因此通常被称为自然语言
-
例如:
-
本书的内容是自然语言的来源
-
你在日常生活中说、听和写的所有内容也是自然语言的形式
-
电影对话也是自然语言的来源
-
你在 WhatsApp 上的对话也被视为自然语言的一种形式
-
什么是自然语言处理?
-
现在你对自然语言有了了解。NLP(自然语言处理)是人工智能的一个子领域。让我们通过一个例子来理解 NLP 的概念。假设你想构建一个与人类通过自然语言互动的机器。这样的智能系统需要计算技术和计算语言学来构建,系统像人类一样处理自然语言。
-
你可以将上述 NLP 的概念与世界顶尖科技公司现有的 NLP 产品相联系,比如谷歌的 Google Assistant、苹果的 Siri 语音助手等。
-
现在你将能够理解 NLP 的定义,具体如下:
-
自然语言处理是计算技术和/或计算语言学处理人类自然语言的能力
-
自然语言处理是计算机科学、人工智能和计算语言学领域的一个分支,涉及计算机与人类(自然)语言之间的交互。
-
自然语言处理可以定义为对人类自然语言进行自动(或半自动)处理。
-
在使用各种 NLP 概念构建专家系统时,还涉及哪些其他分支?图 1.1 是了解在构建使用 NLP 概念的专家系统时涉及多少其他分支的最佳方法:

图 1.1:NLP 概念
图 1.2 和 1.3 传达了在 图 1.1 中提到的每个分支中包含的所有子主题:

图 1.2:NLP 概念的子分支
图 1.3 描述了其余的子分支:
图 1.3:NLP 概念的子分支
如何利用 NLP 概念构建智能系统?图 1.4 是基本模型,指示如何为 NLP 应用程序构建专家系统的开发生命周期在下图中定义:

图 1.4:开发生命周期
让我们看看与 NLP 相关问题的开发生命周期的一些细节:
-
如果您正在解决一个 NLP 问题,您首先需要理解问题陈述。
-
一旦您理解了问题陈述,思考一下您需要解决问题所需的数据或语料库类型。因此,数据收集是解决问题的基本活动。
-
在收集足够的数据后,您可以开始分析数据。我们的语料库的质量和数量如何?根据数据的质量和您的问题陈述,您需要进行预处理。
-
一旦您完成预处理,您需要开始进行特征工程的过程。特征工程是 NLP 和数据科学相关应用程序中最重要的方面。我们将在第五章,特征工程和 NLP 算法以及第六章,高级特征工程和 NLP 算法中更详细地介绍与特征工程相关的方面。
-
从原始预处理数据中选择和提取功能后,您需要决定哪些计算技术对解决您的问题陈述有用,例如,您是否想要应用机器学习技术或基于规则的技术?
-
现在,根据您将使用的技术,应准备好要提供给所选择算法的特征文件。
-
运行您的逻辑,然后生成输出。
-
测试和评估您系统的输出。
-
调整参数以进行优化,并继续直到获得满意的结果。
本章我们将快速覆盖大量信息,如果您遇到不立即能理解的内容,请不要感到迷茫,耐心一点。我们将在下一章开始详细探讨所有细节和示例,这肯定会帮助您理清思路。
理解基础应用
自然语言处理(NLP)是人工智能的一个子领域。NLP 的概念被应用于以下专家系统:
-
语音识别系统
-
问答系统
-
从一种特定语言翻译到另一种特定语言
-
文本摘要
-
情感分析
-
基于模板的聊天机器人
-
文本分类
-
主题分割
我们将在后续章节中学习大多数在前述应用中使用的 NLP 概念。
理解高级应用
高级应用包括以下内容:
-
理解自然语言指令并与人类进行自然语言互动的类人机器人
-
构建一个通用的机器翻译系统是 NLP 领域的长期目标,因为你可以很容易地建立一个将一种特定语言翻译成另一种特定语言的机器翻译系统,但这个系统可能无法帮助你翻译其他语言。借助深度学习,我们可以开发一个通用的机器翻译系统,谷歌最近宣布他们离实现这一目标非常接近。我们将在第九章中使用深度学习构建自己的机器翻译系统,深度学习在 NLP 和 NLG 问题中的应用。
-
NLP 系统是生成给定文档逻辑标题的高级应用之一。此外,借助深度学习,您还可以生成文档标题并进行摘要处理。您将在第九章中看到这种应用,深度学习在 NLP 和 NLG 问题中的应用。
-
NLP 系统,用于生成特定主题或图像的文本,也被认为是一个高级 NLP 应用。
-
高级聊天机器人,能够为人类生成个性化文本并忽略人类写作中的错误,也是我们力图实现的目标。
-
还有许多其他 NLP 应用,您可以在图 1.5:中看到:

图 1.5:NLP 领域的应用
团结的优势 - NLP 与 Python
以下几点说明了为什么 Python 是构建基于 NLP 的专家系统的最佳选择之一:
-
使用 Python 开发基于 NLP 的专家系统原型非常简单且高效
-
有许多开源的 NLP 库可以供 Python 程序员使用。
-
社区支持非常强大
-
易于使用,适合初学者,且复杂度低
-
快速发展:测试和评估简便且不复杂
-
许多新的框架,如 Apache Spark、Apache Flink、TensorFlow 等,都提供了 Python 的 API。
-
基于 NLP 的系统优化比其他编程范式更为简单。
NLTK 环境设置
我建议所有读者从 GitHub 拉取NLPython仓库。仓库的 URL 是 github.com/jalajthanaki/NLPython
我使用的是 Linux(Ubuntu)操作系统,所以如果你不熟悉 Linux,最好先熟悉它,因为大多数高级框架,如 Apache Hadoop、Apache Spark、Apache Flink、Google TensorFlow 等,都需要 Linux 操作系统。
GitHub 仓库包含了如何安装 Linux 的说明,以及本书中将使用的基本 Linux 命令。如果你是 Git 新手,在 GitHub 上也能找到有关 Git 的基础命令。URL 是 github.com/jalajthanaki/NLPython/tree/master/ch1/documentation
我为读者提供了一个安装指南,用于设置本书章节的环境。URL 是 github.com/jalajthanaki/NLPython/tree/master/ch1/installation_guide
安装 nltk 的步骤如下(或者你可以访问 URL: github.com/jalajthanaki/NLPython/blob/master/ch1/installation_guide/NLTK%2BSetup.md):
-
手动安装 Python 2.7.x,但在 Linux Ubuntu 14.04 上已经预装;如果没有安装,可以使用
python -V命令检查你的 Python 版本。 -
配置 pip 以安装 Python 库 (
github.com/jalajthanaki/NLPython/blob/master/ch1/installation_guide/NLTK%2BSetup.md)。 -
打开终端,执行以下命令:
pip install nltk or sudo pip install nltk
-
打开终端,执行
python命令。 -
在 Python shell 中,执行
import nltk命令。
如果你的 nltk 模块已成功安装,系统不会显示任何信息。
-
在 Python shell 中,执行
nltk.download()命令。 -
这将打开一个额外的对话框,你可以选择特定的库,但在我们的情况下,点击 "All packages"(所有包),然后可以选择包存放的路径。等待所有包下载完成。下载可能会花费较长时间。下载完成后,你可以在之前指定的路径下找到名为
nltk_data的文件夹。请查看以下截图中的 NLTK 下载器:

图 1.6:NLTK 下载器
这个仓库包含了安装指南、代码、维基页面等。如果读者有问题或疑问,可以在 Gitter 群组中发布自己的问题。Gitter 群组的 URL 是 gitter.im/NLPython/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link
给读者的建议
本书是一本实用指南。作为一名行业专业人士,我强烈建议所有读者复制已经在 GitHub 上提供的代码,并进行书中给出的练习。这将帮助你加深对 NLP 概念的理解。如果不进行这些实践,你几乎不可能彻底掌握所有的 NLP 概念。顺便说一下,我保证实现这些代码会很有趣。
接下来的章节流程如下:
-
概念的解释
-
概念的应用
-
概念的需求
-
实现这些概念的可能方式(代码在 GitHub 上)
-
概念的挑战
-
克服挑战的小贴士
-
练习
总结
本章为你介绍了 NLP。你现在对 NLP 涉及的分支以及使用 NLP 概念构建专家系统的各个阶段有了一个简要了解。最后,我们为 NLTK 设置了环境。所有安装指南和代码都可以在 GitHub 上找到。
在下一章中,我们将看到 NLP 相关应用中使用了什么样的语料库,以及在分析语料库时需要牢记的所有关键点。我们将处理不同类型的文件格式和数据集。让我们一起探索吧!
第二章:语料库和数据集的实际理解
在这一章中,我们将探索自然语言处理的第一个构建模块。我们将涵盖以下主题,以便对语料库或数据集有一个实际的理解:
-
什么是语料库?
-
我们为什么需要语料库?
-
理解语料库分析
-
理解数据属性的类型
-
探索数据集的不同文件格式
-
获取免费语料库的资源
-
为 NLP 应用准备数据集
-
开发网页抓取应用
什么是语料库?
与自然语言处理相关的应用是基于大量数据构建的。用通俗的话来说,你可以将大量数据集合称为语料库。因此,更正式和技术上讲,语料库可以定义如下:
语料库是存储在计算机上的书面或口语自然语言材料的集合,用于研究语言的使用方式。更准确地说,语料库是一个系统化的计算机化真实语言集合,用于语言学分析和语料库分析。如果你有多个语料库,这些集合称为语料库。
为了开发 NLP 应用,我们需要书面或口语的自然语言材料。我们使用这些材料或数据作为输入数据,并尝试找出能够帮助我们开发 NLP 应用的事实。有时,NLP 应用使用单一语料库作为输入,有时则使用多个语料库作为输入。
使用语料库开发 NLP 应用的原因有很多,以下是其中一些:
-
借助语料库,我们可以进行一些统计分析,如词频分布、词汇共现等。别担心,我们将在本章后面看到一些语料库的基本统计分析。
-
我们可以为各种自然语言处理(NLP)应用定义和验证语言学规则。如果你正在构建一个语法纠错系统,你将使用文本语料库,尝试找出语法上不正确的实例,然后定义帮助我们纠正这些实例的语法规则。
-
我们可以定义一些特定的语言学规则,这些规则依赖于语言的使用方式。借助基于规则的系统,你可以定义语言学规则,并使用语料库验证这些规则。
在语料库中,大量数据可以有以下几种格式:
-
文本数据,指书面材料
-
语音数据,指口语材料
让我们来看看文本数据到底是什么以及如何收集文本数据。文本数据是书面信息的集合。有多种资源可以用来获取书面信息,例如新闻文章、书籍、数字图书馆、电子邮件信息、网页、博客等等。现在,我们都生活在一个数字化的世界中,因此文本信息的数量正在迅速增长。因此,我们可以使用所有这些资源来获取文本数据,并且可以创建自己的语料库。举个例子:如果你想构建一个系统来总结新闻文章,你首先需要收集互联网上的各种新闻文章,然后生成一个新闻文章的集合,这个集合就是你的新闻文章语料库,包含文本数据。你可以使用网页抓取工具从原始 HTML 页面中获取信息。在本章中,我们将开发一个工具。
现在我们来看一下语音数据是如何收集的。语音数据语料库通常有两部分:一部分是音频文件,另一部分是其文本转录。通常,我们可以通过音频录音来获得语音数据。这些音频录音可能包含人们的对话或交流。举个例子:在印度,当你拨打银行客服部门电话时,如果你留心,会发现每个电话都被录音。这就是你可以生成语音数据或语音语料库的方式。对于本书,我们专注于文本数据,而非语音数据。
在某些情况下,语料库也被称为数据集。
语料库有三种类型:
-
单语语料库: 这种类型的语料库只有一种语言。
-
双语语料库: 这种类型的语料库有两种语言。
-
多语种语料库: 这种类型的语料库有多于一种语言。
以下是一些可用语料库的例子:
-
Google Books Ngram 语料库
-
布朗语料库
-
美国国家语料库
我们为什么需要语料库?
在任何自然语言处理(NLP)应用中,我们都需要数据或语料库来构建 NLP 工具和应用程序。语料库是任何与 NLP 相关的应用程序中最关键和最基本的构建块。它为我们提供了定量数据,用于构建 NLP 应用程序。我们还可以使用部分数据来测试和挑战我们对语言的想法和直觉。语料库在 NLP 应用程序中起着非常重要的作用。关于为 NLP 应用程序创建语料库的挑战如下:
-
决定我们需要哪种类型的数据来解决问题陈述
-
数据的可用性
-
数据的质量
-
数据量的充足性
现在你可能想了解前面所有问题的详细信息;为了帮助你轻松理解所有前述内容,我将举一个例子。假设你想制作一个 NLP 工具,用于了解特定患者的医疗状况,并能在经过适当医疗分析后生成诊断。
这里,我们的角度更偏向语料库层面,并且是概括性的。如果你作为 NLP 学习者看待前面的例子,你应该按以下方式处理问题陈述:
-
如果我要解决问题陈述,我需要什么样的数据?
-
临床笔记或病史记录
-
医生与病人之间的对话录音
-
-
你是否拥有这种语料库或数据?
-
如果是,那就太好了!你处于一个很好的位置,可以继续回答下一个问题。
-
如果不是,没关系!不用担心。你需要再处理一个问题,可能是一个困难但有趣的问题。
-
-
是否有开源语料库可用?
-
如果是,下载它,并继续到下一个问题。
-
如果不是,想一想你如何访问数据并构建语料库。可以考虑使用网页抓取工具和技术。但你需要探索网页抓取工具的伦理和法律方面的问题。
-
-
语料库的质量水平如何?
-
浏览语料库,并尝试找出以下几点:
-
如果你根本无法理解数据集,那么该怎么办?
-
多花些时间与数据集互动。
-
像机器一样思考,试着想象如果你被喂入这种数据集,你会处理哪些事情。不要认为你会抛出错误!
-
找出你觉得可以开始的事情。
-
假设你的 NLP 工具已经诊断出一种人类疾病,想象一下如果你是医生的机器,你会问病人什么问题。现在你可以开始理解你的数据集,并思考预处理部分。不要急于处理它。
-
-
如果你能理解数据集,那么接下来该做什么?
-
构建 NLP 系统时,是否需要语料库中的每一个内容?
-
如果是,那么请继续到下一个阶段,我们将在第五章中讨论,特征工程与 NLP 算法。
-
如果不是,那么继续到下一个阶段,我们将在第四章中讨论,预处理。
-
-
-
-
-
数据量是否足够,至少能在概念验证(POC)基础上解决问题?
-
根据我的经验,我更倾向于至少拥有 500MB 到 1GB 的数据用于小型 POC。
-
对于初创企业来说,收集 500MB 到 1GB 的数据也面临挑战,原因如下:
-
初创企业是商业中新手。
-
有时它们非常创新,并且没有现成的可用数据集。
-
即使他们成功构建了 POC,在现实生活中验证他们的产品也是一项挑战。
-
-
请参考图 2.1,以了解前述过程的描述:

图 2.1:为什么我们需要语料库?过程描述。
理解语料库分析
在本节中,我们首先将了解什么是语料库分析。之后,我们将简要探讨语音分析。我们还将了解如何为不同的自然语言处理(NLP)应用分析文本语料库。最后,我们将进行一些实际的文本语料库分析。让我们开始吧!
语料库分析可以定义为一种方法论,用于在真实和交际情境的背景下深入调查语言概念。在这里,我们讨论的是数字存储的语言语料库,这些语料库可以通过计算机进行访问、检索和分析。
语音数据的语料库分析需要对每个数据实例的语音理解进行分析。除了语音分析外,我们还需要进行对话分析,这有助于我们了解特定语言中日常生活中社会互动的发生方式。例如,在现实生活中,如果你正在进行英语口语对话分析,你可能会发现像What's up, dude?这样的句子在对话中比How are you, sir (or madam)?使用得更频繁。
文本数据的语料库分析包括统计性地探查、操纵和概括数据集。因此,对于文本数据集,我们通常会分析语料库中有多少不同的单词,以及某些单词在语料库中的频率。如果语料库中有任何噪声,我们会尝试去除这些噪声。在几乎所有的自然语言处理(NLP)应用中,我们都需要进行一些基本的语料库分析,以便更好地理解我们的语料库。nltk为我们提供了一些内建的语料库,因此我们可以使用这些内建的语料库进行语料库分析。在开始实际操作之前,了解nltk中有哪些类型的语料库是非常重要的。
nltk有四种类型的语料库。让我们来看一下它们:
-
孤立语料库:这种类型的语料库是一个由文本或自然语言组成的集合。此类语料库的例子包括
gutenberg、webtext等。 -
分类语料库:这种类型的语料库是由分为不同类别的文本组成的集合。
这种语料库的一个例子是
brown语料库,它包含了不同类别的数据,如新闻、爱好、幽默等。 -
重叠语料库:这种类型的语料库是由分类文本组成,但这些分类之间是相互重叠的。此类语料库的一个例子是
reuters语料库,它包含了被分类的数据,但定义的分类之间是重叠的。更明确地说,我想定义
reuters语料库的例子。例如,如果你把不同类型的椰子视为一个类别,你会看到椰子油和棉花油是其子类别。因此,在reuters语料库中,各种数据类别是重叠的。 -
时间语料库:这种类型的语料库是一个涵盖一定时间段内自然语言使用情况的集合。
这种语料库的一个例子是
inaugural address语料库。假设你在 1950 年记录了印度某城市的语言使用情况。然后你重复相同的活动,分别记录该城市在 1980 年和 2017 年的语言使用情况。你将记录关于人们如何使用语言以及随时间变化的各项数据属性。
现在,理论讲解足够了,我们来看看实际操作。你可以通过以下链接查看代码:
该章节的代码位于 GitHub 目录 URL:github.com/jalajthanaki/NLPython/tree/master/ch2。
请在此 URL 查看 Python 代码:nbviewer.jupyter.org/github/jalajthanaki/NLPython/blob/master/ch2/2_1_Basic_corpus_analysis.html
这段 Python 代码包含了如何通过nltk API 访问语料库的基本命令。我们使用的是brown和gutenberg语料库,并简要介绍了一些与语料库相关的基本 API。
以下表格描述了基本 API 属性:
| API 属性 | 描述 |
|---|---|
fileids() |
显示语料库中的文件 |
fileids([categories]) |
显示与这些类别对应的语料库文件 |
categories() |
列出语料库的类别 |
categories([fileids]) |
显示与这些文件对应的语料库类别 |
raw() |
显示语料库的原始内容 |
raw(fileids=[f1,f2,f3]) |
显示指定文件的原始内容 |
raw(categories=[c1,c2]) |
显示指定类别的原始内容 |
words() |
显示整个语料库的单词 |
words(fileids=[f1,f2,f3]) |
显示指定fileids的单词 |
words(categories=[c1,c2]) |
显示指定类别的单词 |
sents() |
显示整个语料库的句子 |
sents(fileids=[f1,f2,f3]) |
显示指定fileids的句子 |
sents(categories=[c1,c2]) |
显示指定类别的句子 |
abspath(fileid) |
显示给定文件在磁盘上的位置 |
encoding(fileid) |
显示文件的编码方式(如果已知) |
open(fileid) |
基本上是打开一个流以读取给定的语料库文件 |
root |
显示路径,如果它是本地安装的语料库的根路径 |
readme() |
显示语料库的README文件内容 |
我们已经看过了如何使用nltk加载自定义语料库的代码,并且对可用语料库及我们自定义语料库进行了频率分布分析。
FreqDist类用于编码频率分布,用于统计每个单词在语料库中出现的次数。
所有nltk语料库并非都很嘈杂。它们需要进行基本的预处理,才能从中生成特征。使用nltk的基础语料库加载 API,可以帮助你识别极端的垃圾数据。假设你有一个生物化学语料库,那么可能会包含大量无法准确解析的方程式和复杂的化学名称。在这种情况下,你可以根据问题陈述决定是否在预处理阶段删除它们,或保留它们并在词性标注(POS)级别进行解析的定制。
在实际应用中,语料库通常非常杂乱。使用FreqDist,你可以查看单词的分布情况,了解我们应该考虑和不应该考虑的内容。在预处理阶段,你需要检查许多复杂的属性,如解析结果、词性标注和句子拆分是否恰当。我们将在第四章《预处理》和第五章《特征工程与 NLP 算法》中详细讲解这些内容。
注意,这里语料库的分析是从技术角度进行的。我们并不专注于语料库语言学分析,因此大家不要混淆两者。
如果你想进一步阅读语料库语言学分析的内容,请参考此网址:
en.wikipedia.org/wiki/Corpus_linguistics
如果你想进一步探索nltk API,网址是 www.nltk.org/。
练习
-
计算
brown语料库中fileID: fileidcc12的单词数。 -
创建你自己的语料库文件,使用
nltk加载它,然后检查该语料库的频率分布。
理解数据属性的类型
现在让我们专注于语料库中可能出现的各种数据属性。图 2.3为你提供了不同类型数据属性的详细信息:

图 2.3:数据属性的类型
我想给出一些不同类型语料库的例子。这些例子是概括性的,目的是让大家理解不同类型的数据属性。
分类或定性数据属性
分类或定性数据属性如下:
-
这些类型的数据属性更具描述性
-
示例包括我们的书面笔记,
nltk提供的语料库,以及记录了不同种类狗的品种(如柯利犬、牧羊犬和梗犬)的语料库。
分类数据属性有两个子类型:
-
序数数据:
-
这种数据属性用于衡量非数值的概念,如满意度、幸福感、不适感等。
-
请考虑以下问题,作为例子,你需要从给定的选项中选择答案:
-
问题 1:你今天感觉如何?
-
问题 1 的选项:
-
非常不好
-
不好
-
好
-
开心
-
非常开心
-
-
现在,您将选择任何给定的选项。假设您选择了“好”,没有人能将您感受到的“好”转换成数值。
-
-
所有前面的选项都是非数值概念。因此,它们属于有序数据类别。
-
问题 2:您如何评价我们的酒店服务?
-
问题 2 的选项:
-
坏
-
平均
-
高于平均水平
-
好
-
很好
-
-
-
假设您选择了任意给定选项。所有上述选项都会衡量您的满意度,而由于每个人的回答不同,很难将您的答案转换为数值,因为不同人的答案会有所不同。
-
因为一个人说“好”,另一个人说“高于平均水平”,可能他们对酒店服务的感受是一样的,但给出的回答却不同。简单来说,您可以说,一个选项与另一个选项之间的差异是未知的。所以,您不能精确地为这些数据决定数值。
-
-
名义数据:
-
这种类型的数据属性用于记录不重叠的数据。
-
示例:您的性别是什么?答案是男性或女性,且答案不重叠。
-
再举一个例子:您的眼睛是什么颜色?答案是黑色、棕色、蓝色或灰色。(顺便说一下,我们不考虑市场上销售的彩色隐形眼镜!)
-
在与自然语言处理相关的应用中,我们主要处理的是类别数据属性。因此,从具有类别数据属性的语料库中提取适当的数据点是特征工程的一部分。我们将在第五章特征工程与自然语言处理算法中详细讨论。
一些语料库包含了这两种类别数据的子类型。
数值型或定量数据属性
以下是数值型或定量数据属性:
-
这类数据属性是数值型的,表示一个可衡量的量
-
示例:财务数据、城市人口、人体体重等
数值数据属性有两个子类型:
-
连续数据:
-
这类数据属性是连续的
-
示例:如果您记录一个学生的体重,从 10 到 12 岁,您收集到的关于该学生体重的数据是连续数据;鸢尾花语料库
-
-
离散数据:
-
离散数据只能取某些特定的值
-
示例:如果您掷两个骰子,您只能得到 2、3、4、5、6、7、8、9、10、11 和 12 这些结果;如果您掷两个骰子,结果永远不会是 1 或 1.5
-
再举一个例子:如果您掷硬币,您将得到正面或反面。
-
这类数据属性是分析应用中的主要部分。
探索语料库的不同文件格式
语料库可以有许多不同的格式。实际上,我们可以使用以下文件格式。这些文件格式通常用于存储特征,我们稍后将把这些特征输入到机器学习算法中。关于如何处理以下文件格式的实际内容将在第四章 预处理 及之后的章节中进行介绍。以下是上述文件格式:
-
.txt:这种格式通常是作为原始数据集提供给我们的。gutenberg语料库就是一个示例语料库。一些实际应用中有平行语料库。假设你想制作一个类似 Grammarly 的语法修正软件,那么你将需要一个平行语料库。 -
.csv:这种文件格式通常会在我们参加一些黑客马拉松或者在 Kaggle 上时提供给我们。我们使用这种格式保存从原始文本中提取的特征,而特征.csv文件将用于训练我们的机器进行 NLP 应用。 -
.tsv:为了理解这种文件格式的用法,我们将举一个例子。假设你想制作一个 NLP 系统,建议我们在句子中应该放置逗号的位置。在这种情况下,我们不能使用.csv文件格式来存储特征,因为我们的一些特征属性包含逗号,这会在开始处理特征文件时影响性能。你也可以使用任何自定义的分隔符。你可以使用 \t、|| 等,方便后续处理。 -
.xml:一些知名的 NLP 解析器和工具会以.xml格式提供结果。例如,斯坦福 CoreNLP 工具包会以.xml格式提供解析结果。这种文件格式主要用于存储 NLP 应用的结果。 -
.json:斯坦福 CoreNLP 工具包以.json格式提供其结果。这种文件格式主要用于存储 NLP 应用的结果,并且易于展示和与网页应用集成。 -
LibSVM:这是其中一种特殊的文件格式。参考下面的 图 2.4:

图 2.4:LibSVM 文件格式示例
-
LibSVM支持稀疏训练数据。只有非零值会被包含在训练数据集中。因此,索引指定了实例数据的列(特征索引)。要从传统数据集转换,只需遍历数据,如果X(i,j)的值非零,则输出j + 1: X(i,j)。 -
X(i,j):这是一个稀疏矩阵:-
如果
X(i,j)的值不等于零,则将其包含在LibSVM格式中j+1:这是X(i,j)的值,其中j是矩阵的列索引,从0开始,因此我们加上1
-
否则,请不要将其包含在
LibSVM格式中
-
-
让我们来看下面的例子:
-
示例:1 5:1 7:1 14:1 19:1
-
这里,
1是类别或标签 -
在上述例子中,让我们关注 5:1,其中
5是键,1是值;5:1 是键值对 -
5是列号或数据属性号,是关键字段,并采用LibSVM格式;我们仅考虑包含非零值的数据列,因此在这里,1是该值。 -
参数的值中,索引为 1、2、3、4、6 以及其他未提及的值均为 0,因此我们在示例中未包含这些。
-
-
-
这种数据格式用于 Apache Spark 训练数据,您将从第五章 特征工程与 NLP 算法 开始学习如何将文本数据转换为
LibSVM格式。 -
自定义格式:您可以使用自定义文件格式创建特征文件。(参考
CoNLL数据集。)这是一种自定义的文件格式。由于
CoNLL每年都有不同的共享任务,所以有许多不同的CoNLL格式。图 2.5 显示了一个CoNLL格式的数据示例:

图 2.5:CoNLL 格式的数据示例
访问免费语料库的资源
获取语料库是一项具有挑战性的任务,但在本节中,我将提供一些链接,您可以通过这些链接下载免费语料库,并将其用于构建 NLP 应用程序。
nltk 库提供了一些内置的 corpus。要列出所有语料库名称,请执行以下命令:
import nltk.corpus
dir(nltk.corpus) # Python shell print dir(nltk.corpus) # Pycharm IDE syntax
在 图 2.2 中,您可以看到前面代码的输出;高亮部分表示已经安装的语料库的名称:

图 2.2:nltk 中所有可用语料库的列表
如果你们想使用 IDE 开发一个 Python NLP 应用程序,可以使用 PyCharm 社区版。点击以下 URL 可以查看安装步骤:github.com/jalajthanaki/NLPython/blob/master/ch2/Pycharm_installation_guide.md
如果您想探索更多语料库资源,可以查看 Big Data: 33 Brilliant and Free Data Sources for 2016,Bernard Marr (www.forbes.com/sites/bernardmarr/2016/02/12/big-data-35-brilliant-and-free-data-sources-for-2016/#53369cd5b54d)。
到目前为止,我们已经学习了很多基础内容。现在让我为您展示如何为自然语言处理应用程序准备数据集,该应用程序将借助机器学习进行开发。
为 NLP 应用程序准备数据集
在本节中,我们将介绍一些基本步骤,帮助您为 NLP 或任何数据科学应用程序准备数据集。准备数据集的基本步骤如下:
-
选择数据
-
数据预处理
-
转换数据
选择数据
假设你正在与全球科技巨头如 Google、Apple、Facebook 等公司合作,那么你可以轻松获得大量数据。但是,如果你不是与这些巨头合作,而是在进行独立研究或学习一些 NLP 概念,那么你该如何以及从哪里获取数据集呢?首先,根据你希望开发的 NLP 应用,确定你需要什么样的数据集。同时,考虑你所要构建的 NLP 应用的最终结果。如果你想为医疗健康领域制作一个聊天机器人,你就不应该使用银行客服对话数据集。因此,彻底理解你的应用或问题陈述是非常重要的。
你可以使用以下链接下载免费数据集:
github.com/caesar0301/awesome-public-datasets。
你还可以使用 Google 高级搜索功能,或者使用 Python 的网页抓取库,例如beautifulsoup或scrapy。
在根据应用选择好数据集后,你可以继续进行下一步。
预处理数据集
在这一步中,我们将进行一些基本的数据分析,例如数据集包含了哪些属性。这个阶段有三个子阶段,我们将逐一探讨。你将在第四章中找到更多关于预处理阶段的细节,预处理。在这里,我会给你提供一些基本信息。
格式化
在这一步中,生成你最习惯使用的数据集格式。如果你有一个 JSON 格式的数据集,而你觉得使用 CSV 格式更为方便,那么就将数据集从 JSON 格式转换为 CSV 格式。
清理
在这一步中,我们将清理数据。如果数据集中有缺失值,可以删除该数据记录,或者用最合适的邻近值替换它。如果发现有不必要的数据属性,也可以将其删除。假设你正在做一个语法修正系统,那么你可以删除数据集中的数学方程式,因为你的语法修正应用不需要方程式。
抽样
在这个阶段,你实际上可以尝试弄清楚我们当前数据集中有哪些可用的数据属性,以及哪些数据属性可以由我们推导出来。我们还在试图找出根据我们的应用最重要的数据属性。假设我们正在构建一个聊天机器人,我们将尝试将句子拆解成单词,以识别句子的关键词。因此,单词级别的信息可以从句子中推导出来,单词级别和句子级别的信息对聊天机器人应用来说都非常重要。因此,除去无用的句子外,我们不会删除任何句子。通过抽样,我们尝试提取那些能够很好地代表整体数据集的最佳数据属性。
现在我们可以看看最后一个阶段,即转换阶段。
数据转换
在这一阶段,我们将应用一些特征工程技术,帮助我们将文本数据转换为数值数据,使机器能够理解我们的数据集,并尝试找出数据集中的模式。所以,这一阶段基本上是数据处理阶段。在 NLP 领域,转化阶段,我们可以使用一些编码和向量化技术。别被术语吓到,我们将在第五章特征工程与 NLP 算法和第六章高级特征工程与 NLP 算法中详细讨论所有的数据处理技术和特征提取技术。
所有前面的步骤是为任何 NLP 或数据科学相关应用准备数据集的基本步骤。现在,让我们看看如何通过网页抓取生成数据。
网页抓取
要开发一个网页抓取工具,我们可以使用beautifulsoup和scrapy等库。这里,我给出一些网页抓取的基础代码。
查看图 2.6中的代码片段,该片段用于开发一个使用beautifulsoup的基本网页抓取工具:

图 2.6:使用 beautifulsoup 的基本网页抓取工具
以下图 2.7展示了输出结果:

图 2.7:使用 beautifulsoup 的基本网页抓取工具的输出
你可以在这个链接中找到beautifulsoup和scrapy的安装指南:
github.com/jalajthanaki/NLPython/blob/master/ch2/Chapter_2_Installation_Commands.txt。
你可以通过这个链接找到代码:
github.com/jalajthanaki/NLPython/blob/master/ch2/2_2_Basic_webscraping_byusing_beautifulsuop.py。
如果在运行脚本时收到任何警告,没关系,不用担心警告。
现在,让我们使用scrapy进行一些网页抓取。为此,我们需要创建一个新的 scrapy 项目。
按照命令创建 scrapy 项目。请在终端中执行以下命令:
$ scrapy startproject project_name
我正在创建一个名为web_scraping_test的 scrapy 项目,命令如下:
$ scrapy startproject web_scraping_test
一旦执行前面的命令,你将看到如图 2.8所示的输出:

图 2.8:创建新 scrapy 项目时的输出
创建项目后,执行以下步骤:
-
编辑你已经创建的
items.py文件。 -
在
spiders目录下创建WebScrapingTestspider文件。 -
进入你想抓取的网页,选择该元素的
xpath。你可以通过点击此链接阅读更多关于xpath选择器的信息:
请查看图 2.9中的代码片段。其代码可在 GitHub 网址获取:
github.com/jalajthanaki/NLPython/tree/master/web_scraping_test

图 2.9:我们定义了需要抓取的项目的 items.py 文件
图 2.10用于使用scrapy开发一个基础网页抓取工具:

图 2.10:包含实际代码的蜘蛛文件
图 2.11演示了输出,格式为 CSV 文件:

图 2.11:抓取器的输出被重定向到 CSV 文件
如果你收到任何与 SSL 相关的警告,请参考此链接中的答案:
你可以开发一个绕过 AJAX 和脚本的网页抓取工具,但你在做这个时需要非常小心,因为你必须确保自己没有做任何不道德的事情。因此,我们在这里不会涉及如何绕过 AJAX 和脚本来抓取数据的部分。如果你感到好奇,可以在网上查找别人是如何做到这一点的。你可以使用Selenium库来自动点击执行网页事件。
总结
在本章中,我们了解到语料库是自然语言处理应用的基本构建块。我们还了解了不同类型的语料库及其数据属性。我们简要探讨了语料库的实际分析方面。我们使用nltk API 来简化语料库分析。
在下一章中,我们将通过使用词性、词汇项和分词等语言学概念,探讨自然语言的基本有效方面,这将进一步帮助我们在数据预处理和特征工程中。
第三章:理解句子的结构
在这一章中,我们将探索 NLP 的基本概念。本章是最重要的一章,因为它有助于打下坚实的基础。
我们将涵盖以下主题,以帮助你更好地理解 NLP 的基本概念,这将有助于理解下一章:
-
理解 NLP 的组成部分
-
什么是无上下文文法?
-
形态学分析
-
词汇分析
-
句法分析
-
语义分析
-
处理歧义
-
语篇整合
-
语用分析
理解 NLP 的组成部分
NLP 有两个主要组成部分,我们将理解这两者。
自然语言理解
让我们了解自然语言理解:
-
自然语言理解(NLU)被认为是 NLP 的第一个组成部分
-
NLU 被认为是人工智能-硬(AI-Hard)问题或人工智能-完全(AI-Complete)问题
-
NLU 被认为是一个 AI-Hard 问题,因为我们正在尝试使计算机具备与人类一样的智能
-
NLU 很难,但现在,科技巨头和研究界正在改进传统的机器学习算法,并应用各种类型的深度神经网络,以帮助实现目标(计算机也可以具备处理自然语言(NL)的智能)
-
NLU 被定义为使用计算语言学工具将自然语言输入转化为有用表示的过程
-
NLU(自然语言理解)需要进行以下分析,将自然语言(NL)转化为有用的表示:
-
形态学分析
-
词汇分析
-
句法分析
-
语义分析
-
处理歧义
-
语篇整合
-
语用分析
-
在本书中,我们将专注于 NLU,并开发一个基于 NLP 的系统,利用 NLU 表示。
自然语言生成
让我们了解一下自然语言生成(NLG):
-
NLG 被认为是 NLP 的第二个组成部分。
-
NLG 被定义为机器生成自然语言输出的过程。
-
机器的输出应该是合乎逻辑的,意味着机器生成的任何自然语言(NL)都应该是合逻辑的。
-
为了生成逻辑输出,许多 NLG 系统使用基本事实或基于知识的表示。
-
举个例子,你有一个系统,可以就某个特定话题写一篇文章。如果我指示我的机器生成关于牛的话题的 100 个词,而我的机器生成了 100 个关于牛的话题的词,那么机器生成的输出(这里是关于牛的 100 个词)应该是有效的句子形式,所有句子都应该逻辑正确,且语境也应合理。
NLU 与 NLG 的区别
在这一节中,我们将讨论 NLU 和 NLG 之间的区别:
| NLU | NLG |
|---|---|
| 该组件帮助解释自然语言背后的意义,无论是书面文本还是语音格式。我们可以分析英语、法语、西班牙语、印地语或任何其他人类语言。 | 该组件帮助使用机器生成自然语言。 |
| NLU 通过使用各种工具和技术,如词性标注器、解析器等,从自然语言中生成事实,以便开发 NLP 应用。 | NLG 从像词性标注、解析结果等事实开始生成自然语言。 |
| 它是阅读和解释语言的过程。 | 它是编写或生成语言的过程。 |
NLP 的分支
自然语言处理(NLP)涉及两个主要分支,帮助我们开发 NLP 应用。一个是计算分支,即计算机科学分支,另一个是语言学分支。
参见图 3.1:

图 3.1:NLP 的分支
语言学分支专注于如何使用各种科学技术分析自然语言。因此,语言学分支进行形式、意义和上下文的科学分析。
所有语言学分析都可以借助计算机科学技术实现。我们可以使用这些分析结果,并将分析元素输入机器学习算法,进而构建 NLP 应用。在这里,机器学习算法属于计算机科学,而语言分析则属于语言学。
计算语言学是一个领域,帮助你理解计算机科学和语言学方法的结合。
以下是一些语言学概念的工具,它们通过计算机科学技术得以实现。这些工具通常用于开发 NLP 应用:
-
对于词性标注,使用词性标注器。著名的库有
nltk和pycorenlp。 -
形态分析器用于生成单词级别的词干提取。为此,使用
nltk和polyglot库。 -
解析器用于识别句子的结构。为此,我们使用 Stanford CoreNLP 和
nltk来生成解析树。你也可以使用一个名为spaCy的 Python 包。
定义上下文无关文法
现在让我们关注自然语言理解(NLU),要理解它,首先我们需要理解上下文无关文法(CFG)以及它在自然语言理解中的应用。
上下文无关文法由四个主要组成部分定义。这四个组成部分在 CFG 的符号表示中有所展示:
-
一组非终结符号,
N -
一组终结符,
T -
一个起始符号,
S,它是一个非终结符号 -
一组称为生成规则的生产规则 P,用于生成句子
让我们通过一个例子来更好地理解上下文无关文法的术语:
X -> 
这里,X->
称为短语结构规则或生成规则,P。X ε N表示X属于非终结符号;
ε {N 或 T} 表示
属于终结符号或非终结符号。X可以以
的形式进行重写。该规则告诉你哪个元素可以被重写以生成句子,以及这些元素的顺序。
现在我将以一个实际的 NLP 示例为例。我将使用 CFG 规则生成一个句子。我们将处理简单的句子结构,以便理解这些概念。
让我们思考一下。生成语法正确的英语句子所需的基本元素是什么?你记得吗?想一想!
我希望你记得,名词短语和动词短语是句子的关键元素。所以,从这里开始。我想生成以下句子:
他喜欢板球。
为了生成上面的句子,我提出了以下生成规则:
-
R1: S -> NP VP
-
R2: NP -> N
-
R3: NP -> Det N
-
R4: VP -> V NP
-
R5: VP -> V
-
R6: N -> Person Name | He | She | Boy | Girl | It | cricket | song | book
-
R7: V -> likes | reads | sings
请查看句子He likes cricket的语法分析树,见图 3.2:

图 3.2:使用生成规则的句子语法分析树
现在,让我们来了解一下我们是如何生成语法分析树的:
-
根据生成规则,我们可以看到
S可以被重写为名词短语(NP)和动词短语(VP)的组合;参见规则R1。 -
NP可以进一步被重写为名词(NN)或限定词(Det)后接一个名词;参见规则R2和R3。
-
现在你可以将VP重写为动词(
V)后接一个NP,或者VP可以被重写为仅为V;参见规则R4和R5。 -
这里,
N可以被重写为人名、He、She等。N是一个终结符号;参见规则R6。 -
V可以通过使用规则R7右边的任一选项来重写。V也是一个终结符号。
通过使用所有规则,我们生成了图 3.2中的语法分析树。
如果你无法生成语法分析树,不要担心。我们将在第五章,特征工程与 NLP 算法中看到该概念和实现细节。
这里,我们看到的是一个非常基础且简单的上下文无关文法(CFG)示例。上下文无关文法也叫做短语结构文法。
练习
-
使用本节中给出的规则生成一个语法分析树,并为以下句子生成语法分析树:
她唱歌。
-
生成生成规则并为以下句子制作语法分析树:
那个男孩正在读一本书。
形态学分析
在这里,我们将探索形态学分析领域中使用的基本术语。这些术语和概念将帮助你在解决实际问题时提供帮助。
什么是形态学?
形态学是语言学的一个分支,研究词语如何被构造和形成。
什么是语素?
在语言学中,语素是某种语言中最小的有意义单位。形态学中最重要的部分是语素,它们是形态学的基本单元。
让我们举个例子。单词boy由一个语素组成,而boys由两个语素组成;一个是boy,另一个语素是-s
什么是词干?
词的部分,附加词缀附着在其上,叫做词干。单词tie是根,而Untie是词干。
现在,让我们来理解形态学分析。
什么是形态学分析?
形态学分析被定义为对词如何通过使用语素来构成的语法分析,语素是最小的意义单位。
通常,语素是词缀。这些词缀可以分为四种类型:
-
前缀,出现在词干之前,如unhappy
-
后缀,出现在词干之后,如 happiness
-
插入词缀,出现在词干内部,如 bumili(这在菲律宾的塔加洛语中意味着购买)
-
环绕附加词缀称为环附缀。它附加在词干的开始和结尾。例如,kabaddangan(这在菲律宾的一种语言 Tuwali Ifugao 中意味着帮助)
形态学分析用于词汇切分,词性(POS)标注也使用这种分析。我将在词法分析部分解释 POS,请耐心等待,直到我们将这些点连接起来。
让我们举个例子来实际解释我提出的概念。我想以单词Unexpected为例。参考图 3.3,它向你展示了语素以及形态学分析是如何进行的:

图 3.3:语素与形态学分析
在图 3.3中,我们将Unexpected表示为语素,并对这些语素进行了形态学分析。在这里,Un是一个前缀,ed是一个后缀。Un和ed可以视为词缀,而Unexpect是词干。
让我们参考另一个重要的概念,并试着将它与语素的概念联系起来。我在说的是你如何定义一个词。让我们看看。
什么是词?
词可以从句子中分离出来,作为句子中带有意义的最小单元。这个最小的独立单元叫做词。
请再次参考语素的定义,并尝试将其与词的定义联系起来。我之所以让你这么做,是因为你可能会混淆词和语素,或者你可能不确定它们之间的区别。这种思考是完全可以理解的。如果你没有正确理解它们,它们确实会让人感到困惑。
定义看起来相似,但词和语素之间有非常小的区别。我们可以通过以下表格看到这些区别:
| 语素 | 词 |
|---|---|
| 语素可以独立存在,也可以不独立存在。cat 这个词可以独立存在,但复数标记 -s 不能独立存在。在这里,cat 和 -s 都是语素。 | 一个词可以独立存在。所以,词在句子中基本上是独立存在的单位。 |
| 当一个语素独立存在时,这个语素被称为词根,因为它传达了自身的意义,否则语素大多会附加词缀。语素会附加哪些词缀的分析属于形态学分析的范畴。 | 一个词可以由一个语素组成。 |
| 例如,cat 是一个独立的语素,但当你考虑 cats 时,后缀 -s 出现了,它传达了 cat 是一个语素的信息,并且后缀 -s 表示该语素是 cat 的复数形式。 | 例如:Cat 是一个独立的词。Cats 也是一个独立的词。 |
语素的分类
语素的分类为我们提供了有关形态学分析整体概念的很多信息。参见 图 3.4:

图 3.4:语素的分类
语素有两个主要部分。
自由语素
自由语素可以解释如下:
-
自由语素可以独立存在并作为一个词。它们也叫做无绑定语素或独立语素。
-
让我们看一些例子:
-
Dog, cats, town 和 house。
-
所有前面的词也可以与其他词一起使用。自由语素可以与其他词一起出现。这些词所传达的意义,如果单独看这些词时,会有所不同。
-
让我们来看一些例子:
-
Doghouse, town hall.
-
在这里,doghouse 的含义不同于 dog 和 house 的单独含义。town hall 也是如此。
-
-
-
绑定语素
绑定语素通常需要附加词缀。它们进一步分为两类。
派生语素
派生语素是指当插入语素与词根结合时,改变了语义的意义。
现在,让我们看一些例子:
-
以 unkind 为例。在这个词中,un 是前缀,kind 是词根。前缀 un 作为一个派生语素,将 kind 的意思转变为相反的意思,即 unkind。
-
以 happiness 为例。在这个词中,-ness 是一个派生语素,happy 是词根。所以,-ness 将 happy 转变为 happiness。查看词性标签,happy 是形容词,happiness 是名词。在这里,表示词类的标签,如形容词和名词,被称为词性标签(POS)。
屈折语素
变形语素是加到词语上的后缀,用来赋予词语特定的语法属性。变形语素被视为语法标记,表示时态、数、词性等。因此,用更简单的语言来说,变形语素被认定为修改动词时态、体、语气、人称、数(单数和复数)、性别或格的语素,而不改变词语的意义或词性。
这里是一些示例:
-
在单词dogs中,-
s改变了dog的数量。-s将dog从单数形式转换为复数形式。 -
单词expected包含-ed,这是一个变形语素,修改了动词的时态。
这是生成词干的代码,我们使用了 nltk 和 polyglot 库。你可以通过这个链接找到代码:github.com/jalajthanaki/NLPython/blob/master/ch3/3_1_wordsteam.py
参见图 3.5和图 3.6中的代码片段:

图 3.5:使用 NLTK 从语素生成词干
现在,让我们看看 polyglot 库是如何使用的,参考图 3.6:

图 3.6:使用 Polyglot 库从语素生成词干
代码片段的输出显示在图 3.7中:

图 3.7:图 3.5 和图 3.6 中代码片段的输出
词干和词根有什么区别?
这可以这样解释:
| 词干 | 词根 |
|---|---|
| 为了生成词干,我们需要去除词语中的附加成分 | 词根不能再进一步分解为更小的语素 |
| 从词干中,我们可以通过进一步分解生成词根 | 词干是通过词根加派生语素生成的 |
| 单词Untie是词干 | 单词tie是词根 |
练习
-
对单词“redness”、“quickly”、“teacher”、“unhappy”和“disagreement”中的语素做形态学分析,像我们在图 3.3中做的那样。定义前缀、后缀、动词和词干。
-
使用
nltk和polyglot库生成“redness”,“quickly”,“teacher”,“disagreement”,“reduce”,“construction”,“deconstruction”和“deduce”的词干。 -
生成“disagree”,“disagreement”,“historical”的词干和词根。
词法分析
词法分析定义为将文本分解为单词、短语和其他有意义的元素的过程。词法分析基于单词层面的分析。在这种分析中,我们还关注单词、短语和其他元素(如符号)的含义。
有时,词法分析也被宽泛地描述为词元化过程。因此,在讨论词元化之前,让我们先理解什么是词元以及什么是词性标记(POS 标签)。
什么是词元?
词元被定义为通过词汇分析技术生成的有意义的元素。
什么是词性标注?
词性是具有相似语法属性的单词或词汇项目的类别。属于同一词性(POS)类别的单词在句子的语法结构中具有相似的行为。
在英语中,词性(POS)包括动词、名词、形容词、副词、代词、介词、连词、感叹词,有时还包括数词、冠词或限定词。
词元派生过程
句子是由一连串单词组成的,从句子中我们需要派生出各个有意义的部分,这些部分叫做词元,派生词元的过程叫做分词:
-
从一段文本中派生词元的过程分为两个阶段。如果有很多段落,首先需要进行句子分词,然后是单词分词,并生成词元的意义。
-
分词和词形还原是对词汇分析有帮助的过程。使用
nltk库,我们可以进行分词和词形还原。 -
分词可以定义为识别句子或单词的边界。
-
词形还原可以定义为一个过程,旨在识别句子中单词的正确词性和意义。
-
词形还原还包括词性标注,以消除词元的歧义。在这个过程中,上下文窗口可以是短语级别或句子级别。
你可以在 GitHub 链接找到代码:github.com/jalajthanaki/NLPython/tree/master/ch3
代码片段见图 3.8:

图 3.8:分词的代码片段
图 3.8中的代码输出见图 3.9:

图 3.9:分词和词形还原的输出
词干提取与词形还原的区别
词干提取和词形还原这两个概念都用于通过去除词缀并考虑其含义来规范化给定的单词。它们之间的主要区别如下:
| 词干提取 | 词形还原 |
|---|---|
| 词干提取通常在没有上下文知识的情况下处理单一单词 | 词形还原通常考虑单词及其在句子中的上下文 |
| 词干提取不考虑词性标签 | 词形还原考虑词性标签 |
| 词干提取用于将具有相似基本意义的单词归为一类 | 词形还原的概念用于构建字典或类似 WordNet 的词典。 |
应用
你一定在想,这种词汇分析是如何被用于开发 NLP 应用的。所以,在这里我们列出了一些使用词汇分析概念的 NLP 应用:
-
句子分词和停用词识别等词汇分析通常用于预处理。
-
词法分析也被用于开发词性标注器。词性标注器是一个为文本流生成词性标签的工具。
语法分析
我们已经在词法分析中看到过单词级分析。在这一节中,我们将从更高的层次来考虑问题。我们将重点关注句子的语法和结构,通过考虑句子中的短语。
现在,让我们定义语法分析,并看看它如何在自然语言处理应用中使用。
什么是语法分析?
语法分析被定义为分析告诉我们某些给定句子或句子部分的逻辑意义。我们还需要考虑语法规则,以定义句子的逻辑意义以及其正确性。
让我们举个例子:如果我考虑英语,并且有一个句子如School go a boy,这个句子在逻辑上无法传达其意义,而且语法结构不正确。所以,语法分析告诉我们给定的句子是否传达了其逻辑意义,是否语法正确。
语法分析是自然语言处理的一个成熟领域,涉及自然语言的语法。在语法分析中,使用语法规则来判断哪些句子是合法的。语法已经被应用于开发解析算法,以生成结构表示或解析树。
在这里,我将通过使用nltk和 Python 包装库pycorenlp(用于 Stanford CoreNLP)来生成解析树。请参考图 3.10和图 3.11中的代码片段,输出见图 3.12:

图 3.10:语法分析的代码片段
如何使用斯坦福解析器进行语法分析将在下一个图 3.11中展示:

图 3.11:语法分析的代码片段
你可以看到前面两个代码片段的输出,如下所示。参考图 3.12:

图 3.12:作为语法分析一部分的解析输出
我们将在第五章《特征工程与自然语言处理算法》中看到与解析工具及其开发周期相关的详细信息,特征工程与自然语言处理算法。
语义分析
语义分析基本上关注自然语言的意义。它的定义、各种元素及其应用将在本节中探讨。
现在让我们开始我们的语义之旅,如果你想在这个领域做一些有趣的研究,确实非常吸引人。
什么是语义分析?
语义分析是为自然语言的意义生成表示。你可能会想,如果词汇分析也专注于文本流中单词的意义,那么语义分析和词汇分析有什么区别呢?答案是,词汇分析基于更小的单元,它的重点是单词的意义,而语义分析则专注于更大的块。语义分析可以在短语级别、句子级别、段落级别,甚至有时在文档级别进行。语义分析可以分为两部分,如下所示:
-
对单个词语意义的研究称为词汇语义学
-
研究单个单词如何结合提供句子或段落的意义,涉及处理更大的自然语言单元的背景。
我想举个例子。如果你有一个句子,如白宫很棒,这可以意味着该陈述是关于美国白宫的背景,而也有可能是字面上在说附近的某个房子,其颜色是白色并且很棒。所以,理解句子的正确含义是语义分析的任务。
词汇语义学
词汇语义学包括单词、子词或子单元,如词缀,甚至复合词和短语。在这里,单词、子词等被称为词汇项。
词汇语义学研究包括以下几点:
-
词汇项的分类
-
词汇项的分解
-
不同词汇语义结构之间的差异和相似性
-
词汇语义学是词汇项之间、句子的意义与句法之间的关系。
让我们看看语义分析中涉及的各种元素。
下义词和下义词关系
下义词描述了一个通用术语与指定通用术语实例之间的关系。这里,通用术语叫做上义词,而通用术语的实例叫做下义词。
所以,color 是一个上义词;red、green、yellow 等是下义词。
同义词
同义词是具有相同句法或相同拼写或相同形式的单词,但它们的意义是不同的且彼此无关。
单词“bank”是一个经典的例子。它可以表示一个金融机构或河岸,等等。
多义词
为了理解多义词,我们重点关注句子中的单词。多义词是指具有不同但相关意义的词或短语。这类词也被称为词汇歧义词。
以单词“bank”为例。你可以考虑它的几种含义或意义。
-
Bank 是金融机构
-
Bank 可以被解释为河岸
多义词和同义词有什么区别?
如果一个词用于表达不同的意义,则称该词为多义词。该词的不同含义可能很明显。
如果两个或更多的单词具有相同的发音或相同的拼写,但没有相关的意义,则这些单词被称为同义词。
语义分析的应用
语义分析是一个开放的研究领域,因此它的基本概念可以被以下应用使用:
-
词义消歧是 NLP 中的一项主要任务,其中语义分析得到了广泛应用,并且它仍然是印度语言的一个开放研究领域。
-
我们将在 第七章 中看到 词义消歧 (WSD) 的应用,基于规则的 NLP 系统。
-
word2vec 概念应运而生,用于处理语义相似性。我们将在第六章 高级特征工程与 NLP 算法 中进一步探讨这一点。
处理歧义
当我们进入语义分析时,可能会发现有许多情况对于 NLP 系统来说过于模糊,无法处理。在这些情况下,我们需要了解存在哪些类型的歧义,以及如何处理它们。
歧义是 NLP 和认知科学领域中的一个问题,尚未找到明确的解决方案。有时,句子过于复杂和模糊,只有说话者才能定义句子的原始或确定意义。
如果一个词、短语或句子有多重含义,那么它就是歧义的。如果我们考虑单词 light,它可以表示不重或不暗。这就是单词级别的歧义。短语 porcelain egg container 是结构级别的歧义。所以,在这里我们将看到 NLP 中的不同类型歧义。
首先,让我们看看歧义的类型,然后看看如何利用现有的手段来处理它们。请参考 图 3.13 了解不同类型的歧义:

图 3.13:歧义类型
词汇歧义
词汇歧义是单词级别的歧义。单个词根据其内部结构和句法类别可能具有模糊的含义。让我们看几个例子:
-
句子 1:看那些星星。这里,look 是一个动词。
-
句子 2:那个人给了他一个温暖的目光。这里,look 是一个名词。
-
句子 3:她赢得了三枚银牌。这里,silver 是一个名词。
-
句子 4:她发表了银质演讲。这里,silver 是一个形容词。
-
句子 5:他的压力使头发变白。这里,silvered 是一个动词。
在前面的例子中,特定的单词根据其在句子结构中的使用改变了词性标注。这种歧义可以通过以下两种方法来解决:
-
通过使用准确的词性标注工具,这种歧义可以得到解决。
-
WordNet 的词义包含了针对特定词性标注的多个场景,这也有助于处理歧义。
许多印度语言也存在词汇歧义的问题。
句法歧义
我们已经在句法分析中看到,单词序列是按语法结构组织的。词序列有多种解释方式,每种结构都有不同的解释。在句法歧义中,语法不明确,而不是单词级别的含义。以下是结构歧义的一个例子:
- 那个人看到了拿着望远镜的女孩。这里的歧义在于,不清楚是那个人看到了女孩(她拿着望远镜),还是那个人通过望远镜看到了女孩。这种歧义被称为介词短语(PP)歧义。
处理句法歧义的方法
为了处理这种歧义,我们需要使用统计方法,并获得最大的似然比。我们需要从一方面考虑动词与介词的共现,另一方面考虑介词与名词的共现,然后通过以下公式计算对数似然比:

图 3.14:对数似然比
这里,p(p/v) 是在动词 v 后看到带介词 p 的介词短语(PP)的概率。
并且 p(p/n) 是在名词 n 后看到带介词 p 的介词短语(PP)的概率。
如果 F(v,p,n) < 0,则需要将介词附加到名词上;如果 F(v,p,n) >0,则需要将介词附加到动词上。我们将在第五章中看到实现,特征工程与自然语言处理算法。
语义歧义
语义歧义发生在单词本身的意义可能被误解的情况下。以下是一个例子:
-
ABC 头部寻求武器
-
这里,单词“head”可以指“首席”或“身体部位”,同样,“arms”可以被解释为武器或身体部位。
-
这种歧义被视为语义歧义的一部分。
高准确度地处理语义歧义仍然是一个开放的研究领域。如今,word2vec 表示技术对处理语义歧义非常有用。
语用歧义
语用歧义发生在一个短语的上下文使其有多种不同解释的情况下。我们来看一个例子:
- 把它给那个女孩。这句话可能有多种解释。
现在让我们来看一个较大的上下文:
- 我有巧克力和一包饼干。把它给那个女孩。这里不清楚“它”是指巧克力还是那包饼干。
处理这种歧义仍然是一个开放的研究领域。
语篇整合
语篇整合与语用学密切相关。语篇整合被视为自然语言结构中任何较小部分的更大上下文。自然语言如此复杂,大多数情况下,文本的序列依赖于先前的语篇。
这个概念经常出现在语用歧义中。这一分析处理的是前一句话如何影响下一句话的意义和解释。在这里,语境可以在更大的上下文中进行分析,例如段落级别、文档级别等。
应用
语篇整合的概念已经被以下自然语言处理(NLP)应用使用:
-
这个概念经常在自然语言生成(NLG)应用中使用。
-
聊天机器人是为了实现通用人工智能而开发的。在这种应用中,深度学习已被广泛使用。我们将在第九章中看到基于深度学习的 NLG,自然语言生成与深度学习问题。
语用分析
语用分析处理的是外部世界知识,即文档和/或查询之外的知识。语用学分析关注的是对已描述内容的再解释,旨在从实际意义中提取语言的不同方面,这些方面需要依赖现实世界的知识。
让我们来看一个例子:
-
修剪树木是一个漫长的过程。
-
在这里,修剪树木是计算机科学算法技术中的一个概念。因此,修剪这个词与切割实际的物理树木无关,我们讨论的是计算机科学算法。这是一个模糊的情形;如何处理这种模糊情况也是一个开放的研究领域。大型科技公司使用深度学习技术进行语用分析,尝试生成句子的准确上下文,从而开发出高度精确的 NLP 应用。
摘要
本章探讨了语言学的基础知识,这些知识通常用于开发 NLP 应用。我们已经了解了与自然语言相关的各种分析方法,涵盖了单词层级分析和更大语境的分析。我们还区分了一些关键概念,以解决任何可能的混淆。在本章之后,你可以识别哪些语言学概念或工具对你更有吸引力,便于使用。研究人员如果想从事语言学、计算语言学或以 NLP 为主的计算机科学研究,可以发现潜在的研究领域。
在下一章中,我们将专注于实践和编码方面,开始开发 NLP 应用的旅程。下一章将讨论数据预处理,这是开发 NLP 应用的基本但重要的步骤。预处理包括我们在这里描述的一些概念,我们将结合标准的预处理方法来使用它们。
第四章:预处理
从此处开始,我们的所有章节大多将包含代码。我想提醒所有读者在自己端运行并开发代码。让我们开始编程忍者之旅。
在本章中,我们将学习如何根据不同的 NLP 应用进行预处理。我们将学习以下主题:
-
处理语料库-原始文本
-
处理语料库-原始句子
-
基础预处理
-
实践和定制化预处理
处理语料库-原始文本
在本节中,我们将了解如何获取原始文本,在接下来的章节中,我们将对文本进行预处理并识别句子。
本节的过程见图 4.1:

图 4.1:处理语料库-原始文本的过程
获取原始文本
在本节中,我们将使用三个来源来获取原始文本数据。
以下是数据来源:
-
原始文本文件
-
在脚本中以本地变量的形式定义原始数据文本
-
使用
nltk提供的任何语料库
让我们开始:
-
原始文本文件访问:我在本地计算机上保存了一个
.txt文件,文件中包含以段落形式呈现的文本数据。我希望读取该文件的内容,并将其作为下一步加载。我将运行一个句子分词器以提取其中的句子。 -
在脚本中以本地变量的形式定义原始数据文本:如果我们有少量数据,可以将数据分配给本地字符串变量。例如:Text = 这是一个句子,这是另一个示例。
-
使用
nltk提供的语料库:我们可以从nltk导入可用的语料库,如brown语料库、gutenberg语料库等,并加载其内容。
我已定义了三个函数:
-
fileread():此函数读取文件内容 -
localtextvalue():此函数加载本地定义的文本 -
readcorpus():此函数读取gutenberg语料库内容
请参阅图 4.2中给出的代码片段,其中描述了之前定义的三种情况:

图 4.2:获取原始数据的各种方式
你可以通过点击 GitHub 链接找到代码:github.com/jalajthanaki/NLPython/blob/master/ch4/4_1_processrawtext.py
小写转换
将所有数据转换为小写有助于预处理过程,并且在 NLP 应用的后续阶段(例如解析时)也会有帮助。
因此,将文本转换为小写格式非常简单。你可以通过此 GitHub 链接找到代码:github.com/jalajthanaki/NLPython/blob/master/ch4/4_4_wordtokenization.py
你可以在图 4.3中找到代码片段:

图 4.3:将数据转换为小写
前面代码片段的输出如下:
----------converting data to lower case ----------
i am a person. do you know what is time now?
句子分词
在原始文本数据中,数据是段落形式的。如果你想从段落中提取句子,那么就需要进行句子级别的标记化。
句子标记化是识别句子边界的过程。它也被称为句子边界检测、句子分割或句子边界消歧义。这个过程识别句子的起始和结束点。
一些专门的情况需要为句子标记化器定制规则。
以下是可用于执行句子标记化的开源工具:
-
OpenNLP
-
Stanford CoreNLP
-
GATE
-
nltk
这里我们使用的是nltk句子标记化器。
我们使用来自nltk的sent_tokenize并将其导入为st:
-
sent_tokenize(rawtext):它接受一个原始数据字符串作为参数。 -
st(filecontentdetails):这是我们自定义的原始数据,它作为输入参数提供。
你可以在这个 GitHub 链接上找到代码:github.com/jalajthanaki/NLPython/blob/master/ch4/4_1_processrawtext.py。
你可以在图 4.4中的以下代码片段中查看代码:

图 4.4:nltk 句子标记化器的代码片段
句子标记化的挑战
乍一看,你可能会问,从给定的原始文本中找出句子边界有什么大不了的?
句子标记化因语言而异。
当你需要处理以下情况时,事情变得复杂起来。我们将使用示例来解释这些情况:
-
如果句号后面跟着小写字母,则句子不应在句号后分割。以下是一个例子:
-
句子:他完成了他的 Ph.D. 学位。他很高兴。
-
在上面的例子中,句子标记化器应该在degree后分割句子,而不是在Ph.D.后分割。
-
-
如果句号后面跟着小写字母,则句子应在句号后进行分割。这是一个常见的错误。让我们来看一个例子:
-
句子:这是一个苹果。一个苹果有益于健康。
-
在上面的例子中,句子标记化器应该在apple后分割句子。
-
-
如果句子中有首字母缩写名,则句子不应在缩写后分割:
-
句子:哈利·波特是由 J.K. Rowling 写的。它是一部有趣的作品。
-
在上面的例子中,句子不应在J.后分割。理想情况下,它应在Rowling后分割。
-
-
Grammarly Inc.,语法纠正软件,为句子识别定制了一条规则,并实现了高准确度的句子边界检测。请参见博客链接:
为了克服之前的挑战,你可以采取以下方法,但每种方法的准确性取决于实现。这些方法如下:
-
你可以开发基于规则的系统来提高句子分词器的性能:
-
对于前面的方法,你可以使用命名实体识别(NER)工具、词性标注器或解析器,然后分析所描述工具的输出,以及句子分词器的输出,并修正句子分词器出错的地方。在 NER 工具、词性标注器和解析器的帮助下,你能否修正句子分词器的错误输出?在这种情况下,编写规则,编写代码,检查输出是否符合预期。
-
测试你的代码!你需要检查异常情况。你的代码表现如何?如果是,太棒了!如果不是,稍作修改:
-
你可以通过使用机器学习(ML)或深度学习技术来改进句子分词器:
-
如果你有足够的人工标注数据,那么你可以使用标注数据集训练模型。基于该训练模型,我们可以生成一个新的预测,来确定句子边界应该在哪里结束。
-
在这种方法中,你需要检查模型的表现。
-
-
-
原始文本的词干提取
正如我们在第三章《理解句子结构》中看到的,词干提取是通过删除或替换后缀将句子中的每个单词转换为其词根形式的过程。
在本节中,我们将对原始文本应用Stemmer概念。
这里是代码,我们使用的是nltk中可用的PorterStemmer。请参见图 4.5:

图 4.5:原始文本的 PorterStemmer 代码
前面代码的输出是:
stem is funnier than a bummer say the sushi love comput scientist. she realli want to buy cars. she told me angrily.
当你将前面的输出与原始文本进行比较时,我们可以看到以下变化:
Stemming is funnier than a bummer says the sushi loving computer scientist. She really wants to buy cars. She told me angrily.
如果你想看到差异,你可以参考高亮显示的单词来查看区别。
原始文本的词干提取挑战
最初,词干提取工具是为英语语言开发的。英语语言的词干提取工具准确度较高,但对于像乌尔都语和希伯来语这样的语言,词干提取工具的表现不佳。因此,开发适用于其他语言的词干提取工具是相当具有挑战性的。这仍然是一个开放的研究领域。
原始文本的词形还原
词形还原是一个过程,它识别句子中单词的正确词性(part-of-speech,POS)和意义。
在词形还原中,我们去除屈折词尾,将单词转换为其基本形式,该基本形式在字典或词汇表中存在。如果我们正确使用词汇表和原始文本中所有单词的形态学分析,那么我们可以获得较高的词形还原准确性。
词形还原通过使用标注字典(如 WordNet)将原始文本中的单词转换为其词元。
词形还原与词干提取密切相关。
在词形还原中,我们考虑词性标签,而在词干提取中,我们不考虑词性标签和单词的上下文。
让我们举几个例子来澄清这些概念。以下是这些句子:
-
句子 1:对你来说更好。
- 句子 1 中有一个单词 better。因此,单词 better 的词形还原是 good,作为一个词元。但词干提取是缺失的,因为它需要查阅字典。
-
句子 2:人正在走路。
- 单词 walking 来源于基本单词 walk,在这里,词干提取和词形还原是相同的。
-
句子 3:我们明天见面。
- 这里,to meet 是基本形式。单词 meeting 来源于该基本形式。基本形式 meet 可以是名词,也可以是动词。所以这取决于上下文。词形还原尝试根据词性标签选择正确的词元。
-
请参阅 图 4.6 中的代码片段进行原始文本的词形还原:

图 4.6:原始文本的词干提取和词形还原
前面代码的输出如下所示:
给定的输入是:
text = """Stemming is funnier than a bummer says the sushi loving computer scientist.She really wants to buy cars. She told me angrily.It is better for you. Man is walking. We are meeting tomorrow."""
输出如下所示:
Stemmer
stem is funnier than a bummer say the sushi love comput scientist. she realli want to buy cars. she told me angrily. It is better for you. man is walking. We are meet tomorrow.
Verb lemma
Stemming be funnier than a bummer say the sushi love computer scientist. She really want to buy cars. She tell me angrily. It be better for you. Man be walking. We be meet tomorrow.
Noun lemma
Stemming is funnier than a bummer say the sushi loving computer scientist. She really want to buy cars. She told me angrily. It is better for you. Man is walking. We are meeting tomorrow.
Adjective lemma
Stemming is funny than a bummer says the sushi loving computer scientist. She really wants to buy cars. She told me angrily. It is good for you. Man is walking. We are meeting tomorrow.
Satellite adjectives lemma
Stemming is funny than a bummer says the sushi loving computer scientist. She really wants to buy cars. She told me angrily. It is good for you. Man is walking. We are meeting tomorrow.
Adverb lemma
Stemming is funnier than a bummer says the sushi loving computer scientist. She really wants to buy cars. She told me angrily. It is well for you. Man is walking. We are meeting tomorrow.
在词形还原中,我们使用不同的词性标签(POS 标签)。其缩写说明如下:
-
v代表动词 -
n代表名词 -
a代表形容词 -
s代表卫星形容词 -
r代表副词
你可以看到,在 lemmatizer() 函数内,我已经使用了所有描述过的词性标签。
你可以从 GitHub 链接下载代码:github.com/jalajthanaki/NLPython/blob/master/ch4/4_2_rawtext_Stemmers.py。
词形还原原始文本的挑战
词形还原使用像 WordNet 这样的标注字典。通常,这是一个人工标注的字典。因此,制作不同语言的 WordNet 所需的人工努力和时间是具有挑战性的。
停用词移除
停用词移除是一些 NLP 应用程序(如情感分析、文本摘要等)的重要预处理步骤。
移除停用词以及常见词的移除是一个基础但重要的步骤。以下是将要移除的停用词列表。此列表已从 nltk 生成。请参阅图 4.7 中的代码片段:

图 4.7:查看英语停用词列表的代码
前面代码的输出是 nltk 中可用的停用词列表,参见 图 4.8:

图 4.8:英语停用词列表的 nltk 输出
nltk 提供了现成的英语停用词列表。你也可以根据正在开发的 NLP 应用程序自定义要移除的单词。
你可以在 图 4.9 中看到移除自定义停用词的代码片段:

图 4.9:移除自定义停用词
图 4.9 中代码的输出如下:
this is foo.
图 4.10 中的代码片段执行从原始文本中实际移除停用词的操作,该原始文本是英语文本:

图 4.10:从原始文本中去除停用词
上述代码片段的输出如下:
Input raw sentence: ""this is a test sentence. I am very happy today.""
--------Stop word removal from raw text---------
test sentence. happy today.
练习
以一个位于数据文件夹中名为rawtextcorpus.txt的文件为例,打开文件并以读取模式加载内容,然后使用 nltk 停用词列表去除停用词。请分析输出结果,以更好地理解其工作原理。
直到这一节,我们一直在分析原始文本。下一节,我们将进行句子级别和单词级别的预处理。
处理语料库原始句子
在上一节中,我们处理了原始文本,并探讨了句子级别的概念。在这一节中,我们将讨论在单词级别上进行标记化、词形还原等相关概念。
单词标记化
单词标记化定义为将一段文本切割成单词、短语和有意义的字符串的过程。这个过程叫做单词标记化。该过程的输出是经过标记化后得到的单词,这些被称为标记(token)。
让我们看看图 4.11中的单词标记化代码片段:

图 4.11:单词标记化代码片段
图 4.11中的代码输出如下:
单词标记化的输入为:
Stemming is funnier than a bummer says the sushi loving computer scientist.She really wants to buy cars. She told me angrily. It is better for you.Man is walking. We are meeting tomorrow. You really don''t know..!
单词标记化的输出为:
[''Stemming'', ''is'', ''funnier'', ''than'', ''a'', ''bummer'', ''says'', ''the'', ''sushi'', ''loving'', ''computer'', ''scientist'', ''.'', ''She'', ''really'', ''wants'', ''to'', ''buy'', ''cars'', ''.'', ''She'', ''told'', ''me'', ''angrily'', ''.'', ''It'', ''is'', ''better'', ''for'', ''you'', ''.'', ''Man'', ''is'', ''walking'', ''.'', ''We'', ''are'', ''meeting'', ''tomorrow'', ''.'', ''You'', ''really'', ''do'', ""n''t"", ''know..'', ''!'']
单词标记化的挑战
如果你分析前面的输出,你会发现单词don't被标记化为do, n't know。使用nltk的word_tokenize处理这类单词是相当麻烦的。
为了解决上述问题,你可以编写例外代码并提高准确性。你需要编写模式匹配规则,这些规则解决了定义的挑战,但它们是高度定制化的,并且在不同的应用中有所不同。
另一个挑战涉及一些语言,如乌尔都语、希伯来语、阿拉伯语等。它们在确定单词边界和从句子中找出有意义的标记方面相当困难。
单词词形还原
单词词形还原是我们在第一部分定义的相同概念。我们将快速回顾一下它,然后在单词级别实现词形还原。
单词词形还原是将一个单词从其屈折形式转换为其基本形式。在单词词形还原中,我们会考虑词性标签(POS),根据词性标签,我们可以得出在词汇库 WordNet 中可用的基本形式。
你可以在图 4.12中找到代码片段:

图 4.12:单词词形还原代码片段
单词词形还原的输出如下:
Input is: wordlemma.lemmatize(''cars'') Output is: car
Input is: wordlemma.lemmatize(''walking'',pos=''v'') Output is: walk
Input is: wordlemma.lemmatize(''meeting'',pos=''n'') Output is: meeting
Input is: wordlemma.lemmatize(''meeting'',pos=''v'') Output is: meet
Input is: wordlemma.lemmatize(''better'',pos=''a'') Output is: good
单词词形还原的挑战
构建词汇字典是一个耗时的过程。如果你想要构建一个可以考虑更大上下文的词形还原工具,考虑前文的上下文,那么这个问题仍然是研究中的一个开放领域。
基本预处理
在基本预处理过程中,我们包括了一些简单且易于编码的内容,但当我们为 NLP 应用进行预处理时,它们会引起我们的注意。
正则表达式
现在我们将开始一些有趣的预处理概念,这些概念最为有用。我们将看看一些更高级的正则表达式应用。
对于初学正则表达式的人,我想解释一下正则表达式(regex)的基本概念。
正则表达式有助于从字符序列中查找或查找替换特定模式。当你编写正则表达式时,需要遵循特定的语法。
有许多在线工具可以为你提供开发和测试正则表达式的功能。我最喜欢的在线正则表达式开发工具链接如下:regex101.com/
你也可以参考 Python 正则表达式库的文档:docs.python.org/2/library/re.html
基础级别正则表达式
正则表达式是一个强大的工具,当你需要进行自定义预处理或当你有嘈杂数据时非常有用。
在这里,我展示了一些基本的语法,然后我们将看到在 Python 中的实际实现。在 Python 中,re库是可用的,使用这个库我们可以实现正则表达式。你可以在这个 GitHub 链接找到代码:github.com/jalajthanaki/NLPython/blob/master/ch4/4_5_regualrexpression.py
基本标志
基本标志有I、L、M、S、U、X:
-
re.I:这个标志用于忽略大小写 -
re.M:如果你想在多行中查找模式,这个标志很有用 -
re.L:这个标志用于查找本地依赖 -
re.S:这个标志用于查找点符号匹配 -
re.U:这个标志用于处理 unicode 数据 -
re.X:这个标志用于以更易读的格式编写正则表达式
我们主要使用了re.I、re.M、re.L和re.U标志。
我们正在使用re.match()和re.search()函数。它们都用于查找模式,然后你可以根据应用的需求进行处理。
让我们来看看re.match()和re.search()之间的区别:
-
re.match():它仅检查字符串开头是否存在匹配项。如果在输入字符串的开头找到模式,它会返回匹配的模式,否则返回None。 -
re.search():它检查字符串中的任何位置是否存在匹配项。它会查找给定输入字符串或数据中模式的所有出现。
请参考图 4.13中给出的代码片段:

图 4.13:代码片段,用于查看re.match()与re.search()的区别
图 4.13中的代码片段输出在图 4.14中给出:

图 4.14:re.match() 与 re.search() 的输出
语法如下:
查找字符a和b的单次出现:
Regex: [ab]
查找除a和b之外的字符:
Regex: [^ab]
查找a到z的字符范围:
Regex: [a-z]
查找除z之外的范围:
Regex: [^a-z]
查找所有字符a到z以及A到Z:
Regex: [a-zA-Z]
任意单个字符:
Regex: .
任意空白字符:
Regex: \s
任意非空白字符:
Regex: \S
任意数字字符:
Regex: \d
任意非数字字符:
Regex: \D
任意非单词字符:
Regex: \W
任意单词:
Regex: \w
匹配a或b:
Regex: (a|b)
a的出现次数为零次或一次:
Regex: a? ; ? Matches zero or one occurrence not more than 1 occurrence
a的出现次数为零次或更多:
Regex: a* ; * matches zero or more than that
a的出现次数为一次或更多:
Regex: a+ ; + matches occurrences one or more that one time
精确匹配三次出现的a:
Regex: a{3}
同时匹配a的出现次数为3次或更多:
Regex: a{3,}
同时匹配a的出现次数在3到6之间:
Regex: a{3,6}
字符串的开始:
Regex: ^
字符串的结束:
Regex: $
匹配单词边界:
Regex: \b
非单词边界:
Regex: \B
基本代码片段见图 4.15:

图 4.15:基本正则表达式函数代码片段
图 4.15的代码片段输出见图 4.16:

图 4.16:基本正则表达式函数代码片段的输出
高级正则表达式
有一些正则表达式的高级概念将会非常有用。
前瞻和后瞻用于从数据中查找子串模式。让我们开始。我们将用基础语言理解这些概念。然后我们会看它们的实现。
正向前瞻
正向前瞻会在字符串中匹配子串,前提是定义的模式被该子串所跟随。如果您不理解,那么让我们看一下以下示例:
-
考虑句子:I play on the playground.
-
现在,您希望提取play作为模式,但仅当它跟随ground时。在这种情况下,您可以使用正向前瞻。
正向前瞻的语法是(?=pattern)
正则表达式rplay(?=ground)匹配play,但仅当它后面跟着ground时才匹配。因此,文本中的第一个play不会被匹配。
正向后瞻
正向后瞻会在字符串中匹配子串,前提是定义的模式被该子串所前置。参考以下示例:
-
考虑句子:I play on the playground. It is the best ground.
-
现在您希望提取ground,如果它前面有字符串play。在这种情况下,您可以使用正向后瞻。
正向后瞻的语法是(?<=pattern)
正则表达式r(?<=play)ground匹配ground,但仅当它前面有play时才匹配。
负向前瞻
负向前瞻匹配的是那些后面绝对不会跟着我们在正则表达式中定义的模式的字符串。
让我们举个例子来理解负向前瞻:
-
考虑句子:I play on the playground. It is the best ground.
-
现在您希望提取play,仅当它后面没有字符串ground时。在这种情况下,您可以使用负向前瞻。
负向前瞻的语法是(?!pattern)
正则表达式r play(?!ground)匹配play,但仅当其后不是ground时才匹配。因此,play紧跟on时会被匹配。
否定前瞻
否定前瞻匹配的字符串是明确没有被我们在正则表达式中定义的模式所预先出现的字符串。
让我们看一个例子来理解否定前瞻:
-
考虑这个句子:I play on the playground. It is the best ground.
-
现在你只想提取ground,但仅当其前面没有play字符串时。在这种情况下,你可以使用否定前瞻。
否定前瞻的语法是(?<!pattern)
正则表达式r(?<!play)ground匹配ground,但仅当其前面没有play时。
你可以看到代码片段,这是advanceregex()在图 4.17中的实现:

图 4.17:高级正则表达式代码片段
图 4.17的代码片段输出在图 4.18中给出:

图 4.18:高级正则表达式代码片段的输出
实践中的定制化预处理
当我们开始为 NLP 应用程序进行预处理时,有时你需要根据自己的 NLP 应用程序做一些定制。那时,可能需要考虑我在下面描述的一些要点。
自己决定
本节讨论了当你不知道开发 NLP 应用程序需要什么样的预处理时,如何进行预处理。在这种情况下,你可以做的是向自己提出以下问题并做出决策。
你的 NLP 应用程序是什么?你需要什么样的数据来构建这个 NLP 应用程序?
-
一旦你理解了问题陈述,并且清楚了输出应该是什么,那么你就处于一个很好的位置。
-
一旦你了解了问题陈述和预期输出,接下来就思考一下,你需要从原始数据集中提取哪些数据点。
-
为了理解前面提到的两点,让我们举个例子。如果你想做一个文本摘要应用程序,假设你正在使用网络上的新闻文章,你想用这些文章来构建新闻文本摘要应用程序。现在,你已经建立了一个抓取器,从网络上抓取新闻文章。这些原始新闻文章数据集可能包含 HTML 标签、长文本等。
对于新闻文本摘要,我们如何进行预处理?为了回答这个问题,我们需要问自己几个问题。所以,让我们来看看一些关于预处理的问题。
是否需要进行预处理?
-
现在你有了用于文本摘要的原始数据,并且你的数据集包含 HTML 标签、重复的文本等。
-
如果你的原始数据包含我在第一点中描述的所有内容,那么就需要进行预处理,在这种情况下,我们需要删除 HTML 标签和重复的句子;否则,不需要预处理。
-
你还需要应用小写字母规则。
-
之后,你需要在你的文本摘要数据集上应用句子标记器。
-
最后,你需要在你的文本摘要数据集上应用词汇标记器。
-
是否需要预处理你的数据集,取决于你的问题陈述和原始数据集所包含的数据。
你可以在图 4.19中看到流程图:

图 4.19:文本摘要预处理的基本流程图
需要什么样的预处理?
在我们关于文本摘要的示例中,如果原始数据集包含 HTML 标签、长文本、重复文本,那么在你开发应用程序的过程中,以及在输出中,你不需要以下数据:
-
你不需要 HTML 标签,因此你可以将其删除。
-
你不需要重复的句子,所以你也可以将它们删除。
-
如果文本内容较长,并且你能找到停用词和高频小词,那么你应该将它们删除。
理解预处理的案例研究
我在这里解释的关于定制预处理的内容,当你有一些真实案例研究时,会对你更有意义。
语法纠错系统
-
你正在建立一个语法纠错系统。现在,考虑它的子任务。你想建立一个系统,预测在特定句子中冠词 a、an 和 the 的位置。
-
对于这种类型的系统,如果你认为每次都需要去除停用词,那么,哎呀,你错了,因为这次我们真的不能盲目地删除所有停用词。最终,我们需要预测冠词 a、an 和 the。
-
你可以删除完全没有意义的词,例如当数据集中包含数学符号时,你可以将其删除。但这时,你需要进行详细分析,判断是否能删除短小的词汇,比如缩写,因为你的系统还需要预测哪些缩写不带冠词,哪些带冠词。
现在,让我们看看一个可以应用我们在这里描述的所有预处理技术的系统。让我们按照情感分析中的要点进行操作。
情感分析
情感分析的核心是评估客户的评价,并将其分类为正面、负面和中性类别:
-
对于这种类型的系统,你的数据集包含用户评价,因此用户的写作通常包含日常语言。
-
数据包含非正式语言,因此我们需要去除停用词,例如 Hi、Hey、Hello 等。我们不会通过 Hi、Hey、How are u? 来判断用户评价是正面、负面还是中性。
-
除此之外,你还可以去除重复的评论。
-
你还可以通过使用词汇标记化和词形还原来预处理数据。
机器翻译
机器翻译也是一种广泛使用的 NLP 应用。在机器翻译中,我们的目标是以逻辑的方式将一种语言翻译成另一种语言。所以,如果我们想将英语翻译成德语,那么你可以应用以下预处理步骤:
-
我们可以将整个数据集转换为小写字母。
-
对数据集应用句子分割器,这样你可以获取每个句子的边界。
-
现在,假设你有一个语料库,其中所有的英文句子都在
English_Sentence_File中,所有的德文句子都在German_Sentence_File中。现在,你知道每个英文句子都有一个对应的德文句子存在于German_Sentence_File中。这种类型的语料库被称为平行语料库。所以,在这种情况下,你还需要检查这两个文件中的所有句子是否对齐。 -
你还可以对每个句子的单词应用词干提取。
拼写纠正
拼写纠正也是一种非常有用的预处理工具,它有助于改善你的 NLP 应用。
方法
拼写纠正的概念来源于两个字符串之间的相似性。这一概念用于比较两个字符串。现在这个概念已经广泛应用。我们将考虑一些示例,以更好地理解如何利用这个检查字符串相似性的概念来帮助我们。
当你在谷歌搜索时,如果你的搜索查询中有拼写错误,浏览器会给你一个建议,显示“Did you mean:”,并显示你纠正后的查询和正确的拼写。这个机制可以纠正你的拼写错误,谷歌也有自己的一套方法,每次几乎都能提供完美的结果。谷歌不仅仅做拼写纠正,它还会对你提交的查询进行索引,并展示最适合的结果。所以,拼写纠正背后的概念是两个字符串之间的相似性。
再举一个例子:如果你正在开发一个机器翻译系统,当你看到机器翻译的字符串时,你的下一步可能是验证你的输出。所以现在你将把机器的输出与人工翻译进行比较,而这种情况可能与机器的输出并不完全相同。
如果机器翻译的字符串是:She said could she help me?,那么人工翻译的字符串可能是:She asked could she help me? 当你检查系统翻译字符串与人工翻译字符串的相似性时,你可能会发现said被替换成了 asked。
因此,两个字符串相似性的概念可以应用于许多应用,包括语音识别、自然语言处理(NLP)应用等等。
当我们讨论衡量两个字符串的相似度时,有三种主要操作:插入、删除和替换。这些操作用于拼写修正操作的实现。现在,为了避免复杂性,我们不考虑转置和长字符串编辑操作。
我们从操作开始,然后将具体讨论拼写修正的算法。
插入操作
如果你有一个不正确的字符串,现在通过插入一个或多个字符,你将得到正确的字符串或预期的字符串。
让我们看一个例子。
如果我输入了字符串aple,那么在插入p之后我们会得到apple,这是正确的。如果你输入了字符串staemnt,那么在插入t和e之后你会得到statement,这是正确的。
删除操作
你可能有一个不正确的字符串,可以通过删除一个或多个字符来将其转换为正确的字符串。
下面是一个例子:
如果我输入了caroot,那么为了得到正确的字符串,我们需要删除一个o。之后,我们会得到正确的字符串carrot。
替换操作
如果你通过替换一个或多个字符得到正确的字符串,那就叫做替换操作。
假设你有一个字符串implemantation。要使其正确,你需要将第一个a替换为e,这样你就会得到正确的字符串implementation。
拼写修正算法
我们正在使用最小编辑距离算法来理解拼写修正。
最小编辑距离
这个算法是将一个字符串X转换为另一个字符串Y,我们需要找出将字符串X转换为字符串Y的最小编辑成本。因此,你可以进行插入、删除或替换操作,将字符串X转化为字符串Y,并用最少的字符编辑序列实现。
假设你有一个长度为n的字符串X,和一个长度为m的字符串Y。
按照算法的步骤进行:
Input: Two String, X and Y
Output: cheapest possible sequences of the character edits for converting string from X to Y. D( i , j ) = minimum distance cost for converting X string to Y
让我们看一下以下步骤:
-
设置
n为 P 的长度。设置
m为 Q 的长度。 -
如果
n = 0,返回m并退出。如果
m = 0,返回n并退出。 -
创建一个包含 0 到
m行和 0 到n列的矩阵。 -
初始化第一行为 0 到
n。初始化第一列为 0 到
m。 -
遍历 P 的每个字符(
i从 1 到n)。遍历 Q 的每个字符(
j从 1 到m)。 -
如果 P[i]等于 Q[j],则成本为 0。
如果 Q[i]不等于 Q[j],则成本为 1。
设置矩阵中单元格v[i,j]的值为以下三点的最小值:
-
紧挨上面的单元格加 1:
v[i-1,j] + 1 -
紧挨左侧的单元格加 1:
v[i,j-1] + 1 -
对于最小编辑距离,单元格对角线前一个加上成本:
v[i-1,j-1] +1。如果你使用的是 Levenshtein 距离,则应考虑v[i-1,j-1] +成本。 -
在 步骤 7 到 步骤 9 的迭代完成后,距离值存储在单元格
v[n,m]中。
之前的步骤是开发拼写校正逻辑的基本算法,但我们还可以使用单词的概率分布并考虑这一点。这种算法方法基于动态规划。
让我们通过理解需要删除 u,将字符串 tutour 转换为 tutor。因此,编辑距离为 1。由定义的算法生成的表格用于计算最小编辑距离,如 图 4.20 所示:

图 4.20:计算最小编辑距离
实现
现在,对于拼写校正,我们需要添加一个字典或从大文档中提取单词。因此,在实现中,我们使用了一个大文档,并从中提取了单词。除此之外,我们还使用了文档中单词出现的概率来了解分布情况。您可以通过点击此链接查看有关实现部分的更多详细信息:norvig.com/spell-correct.html
我们已经实现了最小编辑距离 2 的拼写校正。
请参见 图 4.21 中的拼写校正实现:

图 4.21:拼写校正的实现
请参见 图 4.22 中拼写校正的输出。
我们提供了字符串 aple,它成功地转换为 apple:

图 4.22:拼写校正的输出
摘要
在本章中,我们介绍了各种预处理技术,这些技术在您开发 NLP 系统或 NLP 应用程序时会非常有用。我们还提到了一个拼写校正系统,您可以将其视为预处理技术的一部分,因为它对未来开发的许多 NLP 应用程序都很有帮助。顺便说一下,您可以通过点击以下链接访问 GitHub 上的代码:github.com/jalajthanaki/NLPython/tree/master/ch4
在下一章,我们将讨论任何 NLP 系统中最重要的部分:特征工程。NLP 系统的性能主要取决于我们提供给系统的数据类型。特征工程是一项艺术和技能,您将在下一章开始时掌握它。相信我,它是开发 NLP 系统中最重要的组成部分,因此请好好阅读,并将其付诸实践以丰富您的技能。
第五章:特征工程与 NLP 算法
特征工程是开发 NLP 应用中最重要的部分。特征是机器学习(ML)算法的输入参数。这些 ML 算法会根据输入特征生成输出。特征工程是一种艺术和技能,因为它生成最佳的特征,选择最合适的算法来开发 NLP 应用需要大量的努力,并且需要理解特征工程、NLP 和 ML 算法。在第二章,语料库和数据集的实用理解中,我们看到数据是如何收集的,以及数据或语料库的不同格式是什么。在第三章,理解句子结构中,我们讨论了一些 NLP 和语言学的基础但重要的方面。我们将在本章中使用这些概念来导出特征。在第四章,预处理中,我们介绍了预处理技术。现在,我们将处理我们已经预处理过的语料库,并从中生成特征。
参考图 5.1,这将帮助你理解到目前为止我们所覆盖的所有阶段,以及本章的所有重点:

图 5.1:特征生成过程概览
你可以参考第一章中的图 1.4,介绍部分。我们在前面的三章中已经覆盖了前四个阶段。
在本章中,我们将主要关注 NLP 应用的实践方面。我们将涵盖以下主题:
-
什么是特征工程?
-
理解 NLP 的基本特征
-
NLP 的基本统计特征
此外,我们还将探讨如何开发各种工具或库来生成特征,哪些库是我们可以使用的,以及在需要时如何调整开源库或开源工具。
我们还将探讨每个概念的挑战。在这里,我们不会从零开始开发工具,因为这超出了本书的范围,但我们会带你了解开发这些工具所使用的过程和算法。所以,如果你想尝试构建定制化工具,这将对你有所帮助,并且会让你了解如何处理这类问题陈述。
理解特征工程
在深入特征生成技术之前,我们需要理解特征工程及其目的。
什么是特征工程?
特征工程是从原始数据或语料库中生成或导出特征(现象的属性或可衡量的单独特性)的过程,这些特征将帮助我们开发 NLP 应用或解决 NLP 相关问题。
特征可以定义为在构建 NLP 应用程序或预测 NLP 应用程序输出时有用的信息或可衡量的属性。
我们将使用机器学习(ML)技术来处理自然语言,并开发模型以提供最终输出。这个模型被称为机器学习模型(ML 模型)。我们将为机器学习算法提供特征作为输入,以生成机器学习模型。之后,我们将使用生成的机器学习模型为自然语言处理(NLP)应用程序生成适当的输出。
如果你在想哪些信息可以成为特征,那么答案是:任何属性都可以成为特征,只要它在生成一个能够准确有效地产生 NLP 应用程序输出的优秀机器学习模型时是有用的。在这里,你的输入特征完全取决于你的数据集和 NLP 应用程序。
特征是利用领域知识为 NLP 应用程序推导出的。这也是为什么我们要探索自然语言的基本语言学方面,以便能够在特征工程中使用这些概念。
特征工程的目的是什么?
在本节中,我们将查看有助于我们理解特征工程的主要特征:
-
我们有的是计算机无法理解的自然语言原始数据,算法也没有能力接受这些原始自然语言并生成 NLP 应用程序所期望的输出。当你使用机器学习技术开发 NLP 应用程序时,特征起着至关重要的作用。
-
我们需要生成那些能代表我们的语料库的属性,并且能够被机器学习算法理解的属性。机器学习算法只能理解特征的语言进行沟通,而提出适当的属性或特征是一项重要任务。这就是特征工程的全部目的。
-
一旦我们生成了特征,就需要将其作为输入传递给机器学习算法,经过处理这些输入特征后,我们将得到机器学习模型。这个机器学习模型将用于预测或生成新的特征输出。机器学习模型的准确性和效率主要依赖于特征,这也是为什么我们说特征工程是一种艺术和技能。
挑战
以下是特征工程中涉及的挑战:
-
提出好的特征是困难的,有时是复杂的。
-
在生成特征后,我们需要决定应该选择哪些特征,这些特征的选择在我们进行机器学习技术时也起着重要作用。选择适当特征的过程被称为特征选择。
-
有时,在特征选择过程中,我们需要剔除一些不太重要的特征,这些特征的剔除也是特征工程中的一个关键部分。
-
手动特征工程非常耗时。
-
特征工程需要领域专业知识,或者至少需要一些基础的领域知识。
NLP 的基本特征
除了挑战之外,NLP 应用程序在很大程度上依赖于基于各种 NLP 概念手工制作的特征。从现在开始,我们将探讨 NLP 领域中可用的基本特征。让我们深入了解吧!
解析器和解析
通过解析句子,你可以推导出一些对几乎所有 NLP 应用都非常重要的特征。
我们将探讨解析器和解析的概念。稍后,我们将理解上下文无关文法(CFG)和概率上下文无关文法(PCFG)。我们将了解统计解析器是如何开发的。如果你想创建自己的解析器,我们会解释如何进行,或者如果你想调整现有的解析器,我们也会告诉你应该遵循哪些步骤。我们还将使用现有的解析工具进行实际操作。稍后我们将在同一章节中讨论遇到的挑战。
理解解析器的基础
在这里,我将从 NLP 领域的角度解释解析器。解析器的概念也出现在其他计算机科学领域,但我们将聚焦于 NLP 领域,开始理解解析器及其能为我们做什么。
在 NLP 领域,解析器是一个程序,或者更具体地说,是一个工具,它接受以句子或一系列标记的形式呈现的自然语言。它将输入流拆分成更小的部分。这有助于我们理解流中每个元素的句法作用以及句子的基本语法层次含义。在 NLP 中,解析器实际上是使用上下文无关文法或概率上下文无关文法的规则来分析句子的。我们在第三章中已经介绍了 CFG,理解句子的结构。
解析器通常生成解析树或抽象语法树形式的输出。我们将在这里看一些解析树的例子。解析器使用某些文法规则生成包含单词或词汇项的解析树。
请参见图 5.2中的文法规则:

图 5.2:解析器的文法规则
让我们先讨论一下符号:
-
S代表句子 -
NP 代表名词短语
-
VP 代表动词短语
-
V代表动词 -
N代表名词 -
ART 代表冠词 a、an 或 the
请参见使用图 5.3中的文法规则生成的解析树:

图 5.3:根据图 5.2 中定义的文法规则生成的解析树
在图 5.3中,我们将句子转换为解析树格式,正如你所见,句子的每个单词都通过我们在图 5.2中定义的文法符号表示。
解析器主要有两种类型。我们不会深入探讨每种解析器的技术细节,因为它更多涉及到编译器设计方面。相反,我们将探索不同类型的解析器,以便你能清楚地了解在自然语言处理(NLP)中我们通常使用哪种解析器。请参阅图 5.4:

图 5.4:解析器类型
在接下来的章节中,我们还将比较自顶向下解析器和自底向上解析器之间的区别,因为这种区别与过程相关,接下来将介绍每种解析器,以便我们理解它们之间的不同。
让我们深入了解解析的概念。
理解解析的概念
首先,我们来讨论什么是解析。我们来定义一下“解析”这个术语。解析是一个形式化分析过程,利用句子或符号流,并借助定义的正式语法规则,我们可以理解句子的结构和含义。所以,解析使用句子中的每一个单词,并通过构成结构来确定其结构。什么是构成结构呢?构成结构是基于观察哪些单词与其他单词结合形成合理的句子单位。在英语中,主语通常位于句子的开头;句子He is Tom对我们来说是有意义的,而句子is Tom he则没有意义。通过解析,我们实际上是检查并尝试获得一个合理的构成结构。以下是解析器和解析为我们所做的几点说明:
-
解析器工具根据语法规则执行解析过程,并生成一个解析树。这个解析树结构用于验证句子的句法结构。如果句子的解析树遵循语法规则并生成一个有意义的句子,那么我们就说该语法以及使用该语法生成的句子是有效的。
-
在解析的最后,生成一个解析树作为输出,它将帮助你检测句子中的歧义,因为歧义句子往往会产生多个解析树。
让我们来看看自顶向下解析器和自底向上解析器之间的区别:
| 自顶向下解析 | 自底向上解析 |
|---|---|
| 自顶向下解析是由假设驱动的。 | 自底向上解析是由数据驱动的。 |
| 在每个解析阶段,解析器假设一个结构,并按顺序从句子中取出一个单词,测试该单词或符号是否符合假设。 | 在这种解析方法中,首先从输入字符串中取出单词,然后解析器检查是否存在任何预定义的类别,以生成有效的句子结构,最后尝试将它们组合成语法中可接受的结构。 |
| 它以从左到右的方式扫描句子。当语法生成规则推导出词汇项时,解析器通常会检查输入,以查看是否正在推导出正确的句子。 | 这种解析通常从终结符的输入字符串开始。这种类型的解析会查找工作字符串的子字符串,因为如果任何字符串或子字符串与语法的右侧生成规则匹配,它就会用匹配的右侧规则替换左侧非终结符。 |
| 它包括回溯机制。当确定使用了错误的规则时,它会回退并尝试另一个规则。 | 它通常不包括回溯机制。 |
你将在接下来的章节中了解这个解析器是如何构建的。
从零开始开发一个解析器
在本节中,我们将尝试理解最著名的斯坦福解析器的过程,以及开发最成功的统计解析器时使用了哪些算法。
为了了解最终的过程,我们需要首先理解一些构建块和概念。然后,我们将结合所有这些概念,理解构建统计解析器(如斯坦福解析器)的整体过程。
语法的类型
在本节中,我们将看到两种语法类型,这些语法类型将帮助我们理解解析器如何工作的概念。作为先决条件,我们将简单地解释它们,避免过于深入地探讨该主题。我们会尽量简化它们,并探讨一些基本直觉,以帮助理解开发解析器的过程。开始吧!
有两种语法类型。你可以参考图 5.5:
-
上下文无关文法
-
概率上下文无关文法

图 5.5:语法的类型
上下文无关文法
我们在第三章《理解句子结构》中已经看过上下文无关文法的基本概念。我们已经看过 CFG 的正式定义,可以稍微回忆一下。现在,我们将了解在构建解析器时,语法规则有多么重要。
CFG 也被称为短语结构语法。所以,CFG 和短语结构语法是两个术语,但指代同一个概念。现在,让我们看一些与这种语法类型相关的例子,然后讨论为了生成更自然的语法规则形式所遵循的约定。参考图 5.6中的语法规则、词汇和句子:

图 5.6:CFG 规则、词汇和句子
在这里,S 是语法的起点。NP 代表名词短语,VP 代表动词短语。现在我们将应用自上而下的解析法,从右侧非终结符 S 的规则开始,并用 NP 和 VP 替代 S。接着,用 N 替代 NP,用 V 和 NP 替代 VP,然后用 people 替代 N。将 V 替代为 fish,NP 替代为 N,并将 N 替代为 tank。你可以在图 5.7 中看到这一过程的示意图:

图 5.7:由给定语法生成的一个句子的解析树表示
现在,试着为第二个句子自己生成一个解析树。如果你玩一下这个语法规则,你很快就会发现它是一个非常模糊的规则。除此之外,我们还将讨论语言学家用来推导句子结构的更实际的上下文无关文法(CFG)。这是一种更自然的 CFG 形式,非常类似于 CFG 的正式定义,唯一的不同之处是,我们在这种语法中定义了预终结符符号。如果你参考图 5.7,你会看到像 N, V 这样的符号被称为预终结符号。现在,看看图 5.8 中自然形式的 CFG 定义:

图 5.8:更自然形式的 CFG 的正式表示
这里,*****符号表示空序列的存在。我们从 S 符号开始,但在统计解析器中,我们增加了一个额外的阶段,即 TOP 或 ROOT。因此,当我们生成解析树时,最顶层的主节点由 S 表示。更多信息请参见图 5.7。现在,我们将在 S 之前加上一个带有 ROOT 或 TOP 符号的额外节点。
你可能注意到在图 5.6 中有一个奇怪的规则。NP 可以使用 e 进行替代,e 代表空字符串。让我们来看看这个空字符串规则的用途。我们将首先通过一个例子来详细了解这种语法和空字符串规则的作用。我们将从预终结符的概念开始,因为这对你来说可能是新的。以英语中的名词短语为例——任何包含限定词(如 a、an 或 the)以及名词本身的短语。当你将 NP 替代为符号 DT 和 NN 时,你进入了实际的词汇终结符;而当我们将 NP 替代为 DT 和 NN 时,这被称为预终结符符号。现在,让我们谈谈空字符串规则。我们引入这个规则是因为在实际生活中,你会遇到许多句子缺失部分的情况。为了处理这些情况,我们在语法中加入了这个空字符串规则。接下来,我们将给你一个例子,帮助你理解。
我们已经看到了词序列people fish tank。从中,你可以提取出两个短语:一个是fish tank,另一个是people fish。在这两个例子中,都缺少名词。我们将这些短语表示为e fish tank和people fish e。这里,e代表空字符串。你会注意到,在第一个短语中,短语的开头缺少一个名词;更准确地说,缺少的是主语。在第二个例子中,短语的末尾缺少一个名词;更准确地说,缺少的是宾语。这类情况在处理真实的自然语言(NL)数据时非常常见。
还有一件事我们需要描述,这将在语法转换主题中用到。请参考图 5.6,你将看到相关规则。在阅读时,请继续参考这些语法规则。右侧只有空字符串的规则被称为空规则。你可以看到有些规则的右侧和左侧都有一个符号,它们被称为一元规则,因为你可以将一个类别转换为另一个类别,例如NP -> N。还有一些规则,它们的右侧有两个符号,比如VP -> V NP。这些规则被称为二元规则。另外,也有一些规则,它们的右侧有三个符号;我们会应用一些技巧来去除右侧有超过两个符号的规则。我们将很快讨论这些。
现在我们已经看过了 CFG 以及理解它所需的概念。你将在接下来的章节中将这些知识串联起来。现在是时候进入下一部分,它将让你对概率 CFG 有个更清晰的了解。
概率上下文无关语法
在概率语法中,我们加入了概率的概念。别担心——这是我们到目前为止看到的最简单的上下文无关语法(CFG)扩展。接下来,我们将看一下概率上下文无关语法(PCFG)。
让我们正式定义概率上下文无关语法(PCFG),然后探讨它的另一个方面。请参考图 5.9:

图 5.9:PCFG 的正式定义
在这里,T、N、S 和 R 与 CFG 类似;唯一的新内容是概率函数,所以让我们在这里看看,概率函数接受每个语法规则,并给出每个规则的概率值。这个概率映射为一个实数,R。R的范围是[0,1]。我们并不是盲目地取任意的概率值。我们设定了一个约束,规定任何非终结符的概率总和应该等于 1。让我们看一个例子来理解这些内容。你可以在图 5.10中看到带有概率的语法规则:

图 5.10:语法规则的概率
你可以在图 5.11中看到带有概率的词汇语法规则:

图 5.11:词汇规则的概率
如你所见,图 5.10 中有三个 NP 规则,如果你看一下概率分布,你会注意到以下几点:
-
它的概率相加为 1(0.1 + 0.2 + 0.7 = 1.0)
-
可能 NP 会进一步重写为名词,因为它的概率是 0.7
同样,你可以看到,句子开头的第一个规则值为 1.0,这是由于某个先发生的事件。如果你仔细观察,你会注意到我们去除了空字符串规则,以使我们的语法更少歧义。
那么,我们如何使用这些概率值呢?这个问题引导我们进入计算树和字符串概率的描述。
计算树的概率
如果我们要计算一棵树的概率,那是相当简单的,因为你只需要将词汇和语法规则的概率值相乘。这将给我们树的概率。
让我们看一个例子来理解这个计算。这里,我们将选择两棵树和为其生成的句子,people fish tank with rods。
请参阅 图 5.12 和 图 5.13 中的树结构及其相应的概率值,然后再计算每棵树的概率:

图 5.12:解析树
如果我们想要计算图 5.12 中给出的解析树的概率,获取该概率的步骤如下所示。我们从树的顶部开始扫描,所以我们的字符串点是 S,解析树的最顶端节点。在这里,介词修饰动词:
P(t1) = 1.0 * 0.7 * 0.4 * 0.5 * 0.6 * 0.7 * 1.0 * 0.2 * 1.0 * 0.7 * 0.1 = 0.0008232
0.0008232 是树的概率。现在,你可以为另一个解析树(如图 5.13 所示)计算相同的概率。在这个解析树中,介词修饰名词。计算这个解析树的树概率:

图 5.13:第二棵解析树
如果你计算解析树的概率,值应该是 0.00024696。
现在让我们来看看使用树的概率概念计算字符串概率。
计算字符串的概率
相比于计算树的概率,计算字符串的概率要复杂得多。在这里,我们想要计算由词组成的字符串的概率,为此我们需要考虑所有可能生成该字符串的树结构。我们首先需要考虑所有包含该字符串的树,然后通过将不同的概率值加在一起,计算最终的概率值。
让我们重新审视一下我们用来计算树概率的图 5.12和图 5.13。现在,为了计算字符串的概率,我们需要同时考虑树和树的概率,然后将其加起来。按照以下步骤计算字符串的概率:
P(S) = P(t1) +P(t2)
= 0.0008232 + 0.00024696
= 0.00107016
这里,t1 树有很高的概率,所以比起附有NP的 t2,更可能生成附有VP的句子结构。原因在于,t1 有一个带有0.4的VP节点,而 t2 有两个节点,一个是VP,其概率为0.6,另一个是NP,其概率为0.2。当你将这两个概率相乘时,你得到0.12,这比0.4小。所以,t1 解析树是最有可能的结构。
现在你应该理解了不同类型的语法。接下来,是时候探索语法转换的概念,以提高解析效率了。
语法转换
语法转换是一种使语法更加限制的技术,从而提高解析过程的效率。我们将使用查姆斯基范式(CNF)来转换语法规则。在看一个例子之前,让我们先探讨一下 CNF。
让我们先看看 CNF。它说明所有规则应遵循以下规则:
X-> Y Z 或 X-> w 其中 X, Y, Z 属于 N,w 属于 T
这个规则的意思非常简单。任何语法规则的右侧不能有超过两个非终结符;你可以包括那些右侧只有一个终结符的规则。为了将现有的语法转化为 CNF(查姆斯基范式),你可以遵循以下基本步骤:
-
空规则和单一规则可以通过递归函数去除。
-
N元规则通过在语法规则中引入新的非终结符来划分。这适用于右侧有两个以上非终结符的规则。当你使用 CNF 时,你可以通过新的转换规则得到相同的字符串,但它的解析结构可能会有所不同。应用 CNF 后生成的新语法仍然是 CFG(上下文无关文法)。
让我们看一个直观的例子。我们采用之前在图 5.6中定义的语法规则,并应用 CNF 来转换这些语法规则。让我们开始吧。请看以下步骤:
-
我们首先去除空规则。当右侧有NP时,你可以有两个规则,例如S -> NP VP,而当你为NP设置空值时,你会得到S -> VP。通过递归应用这种方法,你将去除语法中的空规则。
-
然后,我们必须尝试去除单一规则。因此,在这种情况下,如果你尝试去除第一个单一规则S -> VP,那么你需要考虑所有左边是VP的规则。当你这样做时,你需要引入新规则,因为
S将立即转到VP。我们将引入规则S -> V NP。你需要继续这样做,直到去除所有的单一规则。当你去掉所有的单一规则,如S -> V,你还需要改变词汇项。
请参考图 5.14,了解 CNF 过程:

图 5.14:CNF 步骤 1 到 5
你可以在图 5.15中看到 CNF 过程的最终结果。

图 5.15:步骤 6 - 应用 CNF 后的最终语法规则
在现实生活中,并不一定需要应用完整的 CNF(范式),而且这样做往往会相当痛苦。它只是让解析更高效,使你的语法规则更简洁。在实际应用中,我们保留单一规则作为语法规则,因为它们告诉我们一个词是作为动词还是名词使用,以及非终结符号信息,这意味着我们拥有词性标签(POS)的信息。
够了,够了,别再讲那些无聊的概念部分了。现在是时候将所有解析器和解析的基本概念结合起来,学习开发解析器所用的算法了。
使用 Cocke-Kasami-Younger 算法开发解析器
对于英语语言,有许多你可以使用的解析器,若要为其他语言构建解析器,可以使用Cocke-Kasami-Younger(CKY)算法。在这里,我们将看看一些对你开发解析器有帮助的信息。我们还将看看 CKY 算法的主要逻辑。
我们需要在开始算法之前,先了解我们所考虑的假设。我们的技术假设是,在这里,每个解析器子树都是独立的。这意味着,如果我们有一个树节点 NP,那么我们只关注这个 NP 节点,而不是它派生出来的节点;每个子树都是独立作用的。CKY 算法可以在立方时间内给出结果。
现在让我们来看一下 CKY 算法的逻辑。该算法从句子中提取单词,并尝试使用自下而上的解析方法生成解析树。在这里,我们将定义一个被称为解析三角形或图表的数据结构。参见图 5.16:

图 5.16:CKY 算法的解析三角形
它的底部单元格表示单个单词,如fish、people、fish和tanks。中间行的单元格表示重叠的单词对,如Fish people、People fish和fish tanks。第三行表示没有重叠的两个单词的对,如Fish people和fish tanks。最后一行表示句子的顶部或根节点。为了理解这个算法,我们首先需要语法规则的规则概率。为了理解算法,我们应该参考图 5.17:

图 5.17:理解 CKY 算法(图片来源:http://spark-public.s3.amazonaws.com/nlp/slides/Parsing-Probabilistic.pdf 第 36 页)
如图 5.17所示,为了解释算法逻辑,我们已将基本概率值输入到最底部的单元格中。在这里,我们需要找到所有符合语法规则的组合。按照给定的步骤继续操作:
-
我们首先从“people”单元格中取NP,从“fish”单元格中取VP。在语法规则中,检查是否有任何语法规则包含序列NP VP,你需要在语法规则的右侧找到这个序列。在这里,我们发现规则是S -> NP VP,概率为 0.9。
-
现在,计算概率值,为此你需要将NP在“people”单元格中的概率值、VP在“fish”单元格中的概率值以及语法规则本身的概率值相乘。因此,NP在“people”单元格中的概率值= 0.35,VP在“fish”单元格中的概率值= 0.06,语法规则S -> NP VP的概率值= 0.9。
-
然后,我们将 0.35(NP在“people”单元格中的概率)* 0.06(VP在“fish”单元格中的概率)* 0.9(语法规则S -> NP VP的概率)相乘。因此,最终的乘积值= 0.35 * 0.06 * 0.9 = 0.0189。0.0189是我们将
S扩展为NP VP语法规则时的最终概率。 -
同样的方式,你可以计算其他组合,如NP来自“people”单元格和NP来自“fish”单元格,并找到语法规则,即右侧的NP NP。在这里,存在NP - NP NP规则。因此,我们计算概率值,0.35 * 0.14 * 0.2 = 0.0098。我们继续这个过程,直到为所有组合生成概率值,然后我们可以看到哪一个组合生成了最大概率。找到最大概率的过程称为Viterbi 最大得分。
-
对于组合S -> NP VP,当单元格生成其向上的非终结符时,我们将获得最大概率。因此,这两个单元格生成
S,即句子。
这是 CKY 算法的核心逻辑。我们来看一个具体的例子来说明这个概念。为了便于书写,我们将解析三角形顺时针旋转 90 度。参见 图 5.18:

图 5.18:CKY 算法的第一步(图片来源:http://spark-public.s3.amazonaws.com)
在这里,单元格 (0,1) 用于 fish,并通过词汇规则进行填充。我们为 N -> fish 赋予 0.2 的概率,因为这是我们语法规则中定义的内容。我们为 V -> fish 赋予 0.6 的概率。现在我们专注于一些只有 N 或 V 在右侧的单一规则。我们需要通过考虑语法规则的概率和词汇概率来计算概率。因此,对于规则 NP -> N,概率是 0.7,而 N -> fish 的概率是 0.2。我们需要将这些值相乘,得到语法规则 NP -> N 的概率 = 0.14。同样,我们为规则 VP -> V 计算概率,结果是 0.1 * 0.6 = 0.6。这样,你需要填充所有四个单元格。
在下一个阶段,我们遵循相同的过程,计算从语法规则生成的每个组合的概率。参见 图 5.19:

图 5.19:CKY 算法的第二阶段(图片来源:http://spark-public.s3.amazonaws.com)
在 图 5.20 中,你可以看到最终的概率值,使用这些概率值,你可以为给定数据选择最佳的解析树:

图 5.20:CKY 算法的最终阶段(图片来源:http://spark-public.s3.amazonaws.com)
现在你知道了如何生成解析树,我们想与您分享一些关于斯坦福解析器的重要信息。它是基于这个 CKY 算法构建的。斯坦福解析器应用了一些技术假设和改进,但以下是用于构建解析器的核心技术。
分步开发解析器
在这里,我们将介绍构建你自己解析器的步骤,并借助 CKY 算法来完成。让我们开始总结:
-
你应该已经标注了带有人类注释的解析树的语料库:如果它是按照 Penn Treebank 注释格式标注的,那么你就可以继续了。
-
使用这个标注过的解析语料库,你可以推导出语法规则,并为每个语法规则生成概率。
-
你应该应用 CNF 进行语法转换。
-
使用具有概率的语法规则,并将其应用于大型语料库;使用带有 Viterbi 最大分数的 CKY 算法来获得最可能的解析结构。如果你提供了大量数据,那么可以使用 ML 学习技术,将此问题视为一个多类分类问题。最后阶段是根据概率值得到给定数据的最佳解析树。
理论讲解到此为止;现在让我们实际使用一些著名的现有分析工具,并查看你能从解析树中生成哪些特征。
现有的分析器工具
在这一部分中,我们将看一下现有的一些分析器,并讨论如何生成一些可以用于机器学习算法或基于规则的系统中的酷特征。
在这里,我们将看到两个分析器:
-
斯坦福分析器
-
spaCy 分析器
斯坦福分析器
让我们从斯坦福分析器开始。你可以从stanfordnlp.github.io/CoreNLP/下载它。下载后,你只需将其解压到你喜欢的任何位置。运行斯坦福分析器的前提是你应该在系统中安装了 Java 运行环境。现在,你需要执行以下命令来启动斯坦福分析器服务:
$ cd stanford-corenlp-full-2016-10-31/
$ java -mx4g -cp "*" edu.stanford.nlp.pipeline.StanfordCoreNLPServer
在这里,你可以将内存从-mx4g更改为-mx3g。
在完全专注于编码部分之前,我们先来看一下分析器中的依赖关系概念。
分析器中的依赖结构显示了句子中哪些词依赖于其他词。在句子中,一些词修饰其他词的含义;另一方面,有些词作为其他词的论元。所有这些关系都通过依赖来描述。在斯坦福分析器中有几种依赖关系,我们将逐一讲解。我们来举个例子,一边讲解一边进行。
句子是:The boy put the tortoise on the rug.
对于这个给定的句子,句子的中心词是put,它修饰了三个部分:boy、tortoise和on the rug。如何找到句子的中心词?通过以下问题来找出答案:
-
谁把它放下的?你会得到答案:boy
-
然后,他放下的是什么东西?你会得到答案:tortoise
-
它被放在哪里?你会得到答案:on the rug
所以,单词put修饰了三样东西。现在看看单词boy,检查它是否有任何修饰词。是的,它有一个修饰词:the。接着,检查the tortoise是否有修饰词。是的,它有一个修饰词:the。对于短语on the rug,on是对rug的补充,而rug是这个短语的中心词,接受修饰词the。参考图 5.21:

图 5.21:句子的依赖结构
斯坦福解析器有诸如nsubjpass(被动名词主语)、auxpass(被动助动词)、prep(介词修饰语)、pobj(介词宾语)、conj(连接词)等依赖关系。我们不会详细讨论这些内容,但值得一提的是,依赖解析也遵循树形结构,并通过称为依赖关系的二元不对称关系进行链接。你可以通过访问斯坦福解析器文档了解每个依赖关系的更多细节:nlp.stanford.edu/software/dependencies_manual.pdf。
你可以在图 5.22中看到基本示例:

图 5.22:句子的依赖解析
现在,如果你想在 Python 中使用斯坦福解析器,你需要使用名为pycorenlp的依赖包。我们将使用它来生成斯坦福解析器的输出。
你可以看到我们使用斯坦福解析器解析句子的示例代码。你也可以解析多个句子。你可以在以下 GitHub 链接找到代码:
github.com/jalajthanaki/NLPython/tree/master/ch5/parserexample。
你可以在图 5.23中看到代码片段:

图 5.23:斯坦福解析器演示的代码片段
你可以在图 5.24中看到此代码的输出:

图 5.24:斯坦福解析器的输出
spaCy 解析器
这个解析器帮助你生成句子的解析结果。这是一个依赖解析器。你可以在以下 GitHub 链接找到代码:
github.com/jalajthanaki/NLPython/blob/master/ch5/parserexample/scpacyparserdemo.py。
你可以在图 5.25中找到该解析器的代码片段:

图 5.25:spaCy 依赖解析器代码
你可以在图 5.26中看到 spaCy 解析器的输出:

图 5.26:spaCy 解析器输出
人们使用斯坦福解析器,因为它提供了良好的准确性,并且在生成输出方面具有很大的灵活性。使用斯坦福解析器,您可以以 JSON 格式、XML 格式或文本格式生成输出。你可能认为我们通过前面的代码获取了解析树,但我们将在下一节讨论从解析结果中可以派生的特征类型。
提取和理解特征
通常,使用解析结果,你可以派生出许多特征,例如生成名词短语和名词短语中的词性标签;你还可以从短语中提取主词。你可以使用每个单词及其标签,也可以将依赖关系作为特征。你可以在图 5.27中看到代码片段:

图 5.27:从句子中获取 NP 的代码
输出片段见图 5.28:

图 5.28:从句子中输出所有的 NP
你可以从每个单词中生成词干和词元,正如我们在第三章,理解句子结构中所看到的那样。
在实际应用中,你可以通过库轻松生成特征,但选择哪些特征至关重要,这取决于你的 NLP 应用。假设你正在开发一个语法修正系统;在这种情况下,你需要考虑句子的所有短语以及每个单词在短语中的词性标签。如果你正在开发一个问答系统,那么名词短语和动词短语是你可以选择的重要特征。
特征选择有点棘手,你需要做一些迭代来了解哪些特征适合你的 NLP 应用。尝试将你的特征存储在一个.csv文件中,这样你可以在后续处理中使用该文件。每个特征都可以是.csv文件中的一列。例如,你可以将 NP 词汇存储在一列,将每个词在 NP 中的词元存储在另一列,等等。现在,假设你有超过 100 列,在这种情况下,你需要找出哪些列(特征)是重要的,哪些是不重要的。根据问题陈述和特征,你可以决定哪些特征是最重要的,有助于我们解决问题。在第八章,面向 NLP 问题的机器学习中,我们将更详细地讨论特征选择。
自定义解析器工具
在实际应用中,数据集通常非常复杂且凌乱。在这种情况下,解析器可能无法给出完美或准确的结果。让我们来看一个例子。
假设你想解析一个包含研究论文文本内容的数据集,这些研究论文属于化学领域。如果你使用斯坦福解析器生成该数据集的解析树,那么包含化学符号和方程式的句子可能无法正确解析。这是因为斯坦福解析器是在 Penn TreeBank 语料库上训练的,因此它在生成化学符号和方程式的解析树时准确性较低。在这种情况下,你有两个选择——要么你可以寻找一个能准确生成符号和方程式解析的解析器,要么如果你有一个已经标注好的语料库,你可以利用这个标注数据重新训练斯坦福解析器。
你可以按照 Penn TreeBank 数据中给出的标注符号为你的数据集进行标注,然后使用以下命令在你的数据集上重新训练斯坦福解析器,保存训练好的模型,并在之后使用它。你可以使用以下命令来重新训练斯坦福解析器:
$ java -mx1500m -cp "stanford-parser.jar" edu.stanford.nlp.parser.lexparser.LexicalizedParser -sentences newline -tokenized -tagSeparator / -outputFormat "penn" englishPCFG.ser.gz /home/xyz/PROJECT/COMPARING_PARSER_NOTES/data/483_18.taggedsents > /home/xyz/PROJECT/COMPARING_PARSER_NOTES/data/483_18.stanford.parsed
挑战
这里是一些与分析器相关的挑战:
-
为希伯来语、古吉拉特语等语言生成分析器非常困难,原因是我们没有标注过的语料库。
-
为融合语言开发分析器非常困难。融合语言意味着你在使用英语的同时,还使用另一种语言,句子中包含多种语言。处理这种句子非常困难。
现在我们已经了解了分析器的一些特点,接下来我们可以讨论下一个概念,即词性标注。这是自然语言处理(NLP)中的一个基本概念。
词性标注与词性标注器
在本节中,我们将讨论备受期待的词性标注话题。
理解词性标注和词性标注器的概念
词性标注(POS tagging)定义为在语料库中标记单词并对应其特定词性的过程。一个单词的词性取决于其定义和上下文。它也被称为语法标注或词类消歧。单词的词性标注还依赖于它与给定短语、句子和段落中相邻和相关单词的关系。
词性标注器(POS tagger)是用于为给定数据分配词性标签的工具。分配词性标注并不是一项简单的任务,因为单词的词性会根据句子结构和语义发生变化。我们来看一个例子。假设我们有单词"dogs";一般来说,我们都知道 dogs 是复数名词,但在某些句子中它充当动词。看看这个句子:The sailor dogs the hatch。在这里,dogs的正确词性应该是动词,而不是复数名词。通常,许多词性标注器使用由宾夕法尼亚大学生成的词性标签。你可以通过以下链接找到单词级别的词性标签及其定义:
www.ling.upenn.edu/courses/Fall_2003/ling001/penn_treebank_pos.html。
现在我们将讨论一些词性标注。Penn Treebank 词性列表中有 36 个词性标注,例如NN表示名词,DT表示限定词,FW表示外来词。对于新出现的词,一般会分配FW标注。拉丁语名称和符号通常会被词性标注工具分配FW标签。因此,如果你有一个(λ)符号,词性标注器可能会为它推荐FW标签。你可以在图 5.29中看到一些单词级别的词性标注:

图 5.29:一些单词级别的词性标注
也有在短语级别和从句级别的词性标注。所有这些标签可以通过以下 GitHub 链接找到:
github.com/jalajthanaki/NLPython/blob/master/ch5/POStagdemo/POS_tags.txt。
查看我们在文件中指定的每个标签,它们在评估解析树结果时非常有用。词性标签及其定义非常直观,如果你了解基础的英语语法,就能轻松理解它们。
你一定很好奇词性标注器是如何构建的。让我们一起看看如何制作自己的词性标注器的过程。
步骤化开发词性标注器
要构建自己的词性标注器,你需要执行以下步骤:
-
你需要一个标注好的语料库。
-
选择特征。
-
使用 Python 库中可用的决策树分类器
scikit-learn进行训练。 -
检查你的准确性。
-
尝试使用你自己训练的模型来预测词性标注(POS tags)。
本节的一个重要部分是,我们将用 Python 编写自己的词性标注器,这样你就能理解前述每个阶段是如何实际执行的。如果你不知道什么是决策树算法,不用担心——我们将在第八章,NLP 应用的机器学习中详细介绍这个话题。
在这里,我们将看到一个实用的示例,帮助你理解开发词性标注器的过程。你可以在每个阶段找到相应的代码片段,也可以通过以下 GitHub 链接访问代码:
github.com/jalajthanaki/NLPython/tree/master/ch5/CustomPOStagger。
查看获取 Pann TreeBank 语料库的代码片段,如图 5.30所示:

图 5.30:从 NLTK 加载 Penn TreeBank 数据
你可以在图 5.31中看到特征选择的代码片段:

图 5.31:提取每个单词的特征
我们需要为每个单词提取特征。你可以在图 5.32中看到一些基本转换的代码片段,例如将数据集拆分为训练集和测试集:

图 5.32:将数据分为训练集和测试集
查看使用决策树算法训练模型的代码,如图 5.33所示:

图 5.33:使用决策树算法的实际训练
查看你提供的句子的词性标注输出,如图 5.34所示:

图 5.34:自定义词性标注器的输出
你现在应该已经理解了制作自己词性标注器的实际操作,但你仍然可以使用一些很棒的现成词性标注器。
即插即用现有的词性标注器
现在有很多词性标注器可用。在这里,我们将使用斯坦福 CoreNLP 和 polyglot 库中的词性标注器。还有其他如 Tree tagger,NLTK 也有一个词性标注器可以使用。你可以通过以下 GitHub 链接找到代码:
github.com/jalajthanaki/NLPython/blob/master/ch5/POStagdemo.
斯坦福词性标注器示例
你可以在图 5.35中看到斯坦福词性标注器的代码片段:

图 5.35:斯坦福词性标注器代码
你可以在图 5.36中看到来自斯坦福词性标注器的输出:

图 5.36:斯坦福词性标注器生成的词性标签
使用 polyglot 生成词性标注
你可以在图 5.37中看到polyglot词性标注器的代码片段:

图 5.37:Polyglot 词性标注器
你可以在图 5.38中看到来自polyglot词性标注器的输出:

图 5.38:Polyglot 词性标注器输出
练习
尝试使用 TreeTagger 库来生成词性标注。你可以在以下链接找到安装详情:
www.cis.uni-muenchen.de/~schmid/tools/TreeTagger/.
使用词性标签作为特征
现在我们已经使用词性标注器为我们的文本数据生成了词性标签,那么我们可以在哪里使用它们呢?接下来我们将看看可以将这些词性标签作为特征的 NLP 应用。
词性标签在你使用机器学习算法构建聊天机器人时非常重要。当机器需要理解不同的句子结构时,词性标签序列非常有用。如果你正在构建一个识别多词表达式(MWE)的系统,它也会很有用。MWE 短语的示例包括:be able to、a little bit about、you know what 等。
如果你有一个句子:He backed off from the tour plan of Paris。在这里,backed off 就是 MWE。为了识别句子中的这类 MWE,你可以使用词性标签和词性标签序列作为特征。你还可以在情感分析中使用词性标签,除此之外还有其他应用。
挑战
以下是词性标签的一些挑战:
-
在模棱两可的句法结构中识别某个单词的正确词性标签是很困难的,如果该单词在上下文中的含义非常不同,那么词性标注器可能会生成错误的词性标签。
-
开发适用于印度语言的词性标注器有点困难,因为对于某些语言,你找不到已标注的数据集。
现在让我们进入下一部分,我们将在其中学习如何在句子中找到不同的实体。
命名实体识别
在本节中,我们将介绍一个名为 命名实体识别 (NER) 的工具。这个工具的使用方法如下:如果你有一句话,例如 美国银行今天宣布了其财报,我们作为人类能够理解 美国银行 是一个金融机构的名字,应该作为一个整体来识别。然而,对于机器来说,要处理和识别这个实体是相当具有挑战性的。这时,NER 工具就派上了用场,帮助我们解决这个问题。
使用 NER 工具,你可以识别出像人名、组织名、地点等实体。NER 工具有一些类别,用于对实体进行分类。在这里,我们考虑的是句子的词汇来查找实体,以及是否存在任何实体。让我们深入了解一下,通过一些可用的 NER 工具,我们能在句子中找到哪些类型的实体。
NER 类别
NER 工具通常将实体划分为一些预定义的类别。不同的 NER 工具有不同类型的类别。斯坦福 NER 工具有三个不同的版本,基于 NER 类别的不同:
-
第一个版本是三类 NER 工具,可以识别地点、人物或组织这三类实体。
-
第二个版本是四类 NER 工具,可以识别地点、人物、组织和杂项。杂项指的是一种多种类型的实体。如果一个实体既不属于地点、人物或组织,但仍然是一个实体,那么你可以将其标记为杂项。
-
第三个版本是一个七类工具,可以识别人物、地点、组织、金钱、百分比、日期和时间。
spaCy 解析器还提供了一个包含以下类别的 NER 包。
-
PERSON类别用于识别人物的名字 -
NORP类别表示国籍、宗教或政治团体 -
FACILITY类别包括建筑物、机场、高速公路等 -
ORG类别用于组织、机构等 -
GPE类别用于城市、国家等 -
LOC类别用于非 GPE 的地点,如山脉和水体 -
PRODUCT类别包括物体、车辆、食物等,但不包括服务 -
EVENT类别用于运动事件、战争、命名的飓风等 -
WORK_OF_ART类别用于书籍、歌曲等作品的标题 -
LANGUAGE类别用于标记任何命名的语言 -
除此之外,spaCy 的 NER 包还包括日期、时间、百分比、金钱、数量、序数和基数等类别
现在是时候做一些实践操作了。在下一节中,我们将使用斯坦福 NER 工具和 spaCy NER 工具。
与现有 NER 工具即插即用
在本节中,我们将讨论编码部分以及如何实际使用这些 NER 工具的信息。我们将从斯坦福 NER 工具开始,然后介绍 Spacy NER。你可以通过以下 GitHub 链接找到代码:
github.com/jalajthanaki/NLPython/tree/master/ch5/NERtooldemo。
一个斯坦福 NER 示例
你可以按照以下方式找到代码和输出片段。你需要下载斯坦福 NER 工具,访问nlp.stanford.edu/software/CRF-NER.shtml#Download。
你可以在图 5.39中看到代码片段:

图 5.39:斯坦福 NER 工具代码
你可以在图 5.40中看到输出片段:

图 5.40:斯坦福 NER 输出
一个 Spacy NER 示例
你可以按照以下方式找到代码和输出片段。你可以在图 5.41中看到代码片段:

图 5.41:spaCy NER 工具代码片段
你可以在图 5.42中看到输出片段:

图 5.42:spaCy 工具的输出
提取和理解特征
NER 标签非常重要,因为它们帮助你理解句子结构,并帮助机器或自然语言处理(NLP)系统理解句子中某些词语的含义。
举个例子。如果你正在构建一个校对工具,那么这个命名实体识别(NER)工具非常有用,因为 NER 工具可以识别出一个人的名字、一个组织的名字、与货币相关的符号、数字格式等,这些都能帮助你的校对工具识别文本中存在的特殊情况。然后,根据 NER 标签,系统可以建议必要的修改。以这句话为例,美国银行今天早上宣布了其财报。在这种情况下,NER 工具为美国银行赋予了组织标签,这帮助我们的系统更好地理解句子的含义和结构。
如果你正在构建一个问答系统,NER 标签也非常重要,因为提取实体在这个系统中至关重要。一旦生成了实体,你可以利用句法关系来理解问题。在这个阶段之后,你就可以处理问题并生成答案。
挑战
对于 NER 系统来说,存在一些挑战,具体如下:
-
NER 工具是在封闭领域的数据集上进行训练的。因此,为某一领域开发的 NER 系统通常无法在其他领域表现良好。这就需要一个通用的 NER 工具,能够适用于所有领域,并且在训练后应具备足够的泛化能力来应对未见过的情况。
-
有时你会发现一些词语既是地点的名字,也可能是人的名字。NER 工具无法处理某个词既可以表示地点名称、也可以表示人名或组织名称的情况。这对于所有的 NER 工具来说都是一个非常具有挑战性的案例。假设你有词语 TATA 医院;单词 TATA 既可以是一个人的名字,也可以是一个组织的名字。在这种情况下,NER 工具无法判断 TATA 是一个人的名字还是一个组织的名字。
-
为微博平台专门构建一个 NER 工具也是一个具有挑战性的任务。
让我们进入下一部分,讲解 n-gram 算法。你将学习一些非常有趣的内容。
n-gram
n-gram 是 NLP 领域中一种非常流行且广泛使用的技术。如果你处理的是文本数据或语音数据,你可以使用这个概念。
让我们看看 n-gram 的正式定义。n-gram 是从给定的文本数据或语音数据序列中提取的 n 个项的连续序列。在这里,项可以是音素、音节、字母、单词或根据你正在解决的应用,可能是碱基对。
有一些版本的 n-gram 对你来说非常有用。如果我们将 n=1,那么这个 n-gram 被称为单元语法。如果我们将 n=2,那么我们得到的是双元语法。如果我们将 n=3,那么这个 n-gram 被称为三元语法,而如果你将 n=4 或 n=5,那么这些 n-gram 版本分别称为四元语法和五元语法。现在让我们从不同领域中举一些例子,以更详细地了解 n-gram。请参阅 NLP 和计算生物学的示例,以了解图 5.43中的单元语法:

图 5.43:单元语法示例序列
你已经看过单元语法。现在我们来看双元语法。双元语法考虑的是重叠对,正如你在以下示例中看到的那样。我们使用相同的 NLP 和计算生物学序列来理解双元语法。见图 5.44:

图 5.44:双元语法示例序列
如果你理解了双元语法重叠配对的概念,那么三元语法对你来说会更容易理解。三元语法只是双元语法的扩展,但如果你仍然感到困惑,那么让我们用通俗的语言为你解释。在图 5.44的前三行中,我们生成了基于字符的双元语法,第四行是基于词的双元语法。我们从第一个字符开始,考虑下一个字符,因为我们考虑的是 n=2,词语也是如此。请看第一行,我们考虑了如AG这样的双元语法作为第一个双元语法。然后,在下一次迭代中,我们再次考虑G并生成GC。在接下来的迭代中,我们再次考虑C,依此类推。要生成三元语法,请参阅我们之前看的相同示例。参考图 5.45:

图 5.45:三元语法示例序列
上述例子是非常自解释的。你可以根据 n 的数量理解我们是如何进行序列化的。在这里,我们取的是重叠的序列,这意味着如果你正在取三元组,并将单词this、is和a作为一个单独的对,下一次你会考虑is、a和pen。这里,单词is重叠了,但这种重叠的序列有助于存储上下文。如果我们使用较大的 n 值——五元组或六元组——我们可以存储更多的上下文,但我们仍然需要更多的空间和时间来处理数据集。
通过实践例子理解 n-gram
现在我们将使用nltk库实现 n-gram。你可以在这个 GitHub 链接中查看代码:
github.com/jalajthanaki/NLPython/tree/master/ch5/n_gram
你可以在图 5.46中看到代码片段:

图 5.46:NLTK n-gram 代码
你可以在图 5.47中看到输出代码片段:

图 5.47:n-gram 的输出
应用
在本节中,我们将看到 n-gram 已经被应用于哪些方面:
-
如果你正在制作一个抄袭检测工具,你可以使用 n-gram 来提取被复制的模式,因为其他抄袭工具就是这样做的,以提供基本功能。
-
计算生物学使用 n-gram 来识别各种 DNA 模式,以便识别任何异常的 DNA 模式;基于此,生物学家决定一个人可能患有什么样的遗传疾病。
现在让我们进入下一个概念,这是一个简单但对 NLP 应用非常有用的概念:词袋模型。
词袋模型
词袋模型(BOW)是 NLP 领域中使用的一种技术。
理解 BOW
这个 BOW 模型让我们的生活变得更简单,因为它简化了 NLP 中使用的表示方法。在这个模型中,数据以文本的形式存在,并被表示为其单词的集合或多重集合,忽略了语法和单词顺序,只保留单词。这里,文本可以是一个句子或文档。让我们通过一个例子更好地理解 BOW。
让我们来看以下这组样本文档:
文本文档 1:John 喜欢看板球。Chris 也喜欢板球。
文本文档 2:John 也喜欢看电影。
基于这两个文本文档,你可以生成以下列表:
List of words= ["John", "likes", "to", "watch", "cricket", "Chris", "too", "also", "movies"]
这个列表被称为BOW。在这里,我们不考虑句子的语法,也不关心单词的顺序。现在是时候看看 BOW 的实际实现了。BOW 通常用于生成特征;生成 BOW 后,我们可以推导出文档中每个单词的词频,随后可以将其输入到机器学习算法中。对于上述文档,你可以生成以下频率列表:
文档 1 的频率计数:[1, 2, 1, 1, 2, 1, 1, 0, 0]
文档 2 的频率计数:[1, 1, 1, 1, 0, 0, 0, 1, 1]
那么,我们是如何生成频率计数列表的呢?为了生成文档 1 的频率计数,考虑一下单词列表并检查每个单词在文档 1 中出现的次数。这里,我们首先拿出单词John,它在文档 1 中出现了一次;文档 1 的频率计数为 1。文档 1 的频率计数:[1]。对于第二个条目,单词like在文档 1 中出现了两次,所以频率计数是 2。文档 1 的频率计数:[1, 2]。现在,我们将取出列表中的第三个单词,单词是to。这个单词在文档 1 中出现了一次,所以我们将频率计数的第三个条目设为 1。文档 1 的频率计数:[1, 2, 1]。我们以相同的方式生成了文档 1 和文档 2 的频率计数。我们将在本章接下来的部分——TF-IDF 中深入学习频率。
使用实际示例理解 BOW
在这一部分,我们将通过scikit-learn来看 BOW 的实际实现。你可以在这个 GitHub 链接找到代码:
github.com/jalajthanaki/NLPython/blob/master/ch5/bagofwordsdemo/BOWdemo.py。
参见图 5.48中的代码片段:

图 5.48:BOW 的 scikit-learn 实现
输出的第一行属于包含单词words的第一个文档,第二行属于包含单词wprds的文档。你可以在图 5.49中看到输出:

图 5.49:BOW 向量表示
比较 n-grams 和 BOW
我们已经了解了 n-grams 和 BOW 的概念。现在,让我们看看 n-grams 和 BOW 是如何不同或相关的。
让我们首先讨论它们的不同之处。这里,区别在于它们在 NLP 应用中的使用方式。在 n-grams 中,单词的顺序很重要,而在 BOW 中,保持单词顺序并不重要。在 NLP 应用中,n-gram 用于考虑单词的实际顺序,以便我们能够了解某个特定单词的上下文;BOW 用于为文本数据集构建词汇表。
现在,让我们看看 n-grams 和 BOW 之间的一些有意义的关系,这将帮助你理解 n-grams 和 BOW 是如何相互关联的。如果你将 n-gram 作为特征,那么 BOW 是通过单一词法(unigram)得出的文本表示。所以,在这种情况下,n-gram 等同于一个特征,而 BOW 等同于使用其中包含的单一词法(one-gram)表示的文本。
现在,让我们看看 BOW 的一个应用。
应用
在这一部分,我们将看看哪些应用使用 BOW 作为 NLP 领域的特征:
-
如果你想制作一个可以将文档分类到不同类别的 NLP 应用,那么你可以使用 BOW。
-
BOW(词袋模型)还用于从数据集中生成词频统计和词汇表。这些衍生出的属性随后被用于自然语言处理应用中,如情感分析、Word2vec 等。
现在是时候看看我们可以使用的一些语义工具,如果我们想在自然语言处理应用中加入语义级别的信息。
语义工具和资源
尽管我们确实有一些最近开发的技术和资源可以用来从自然语言中获取语义,但在自然语言处理领域,准确理解自然语言的含义仍然是一个具有挑战性的任务。在这一部分中,我们将尝试理解这些技术和资源。
潜在语义分析算法使用词频 - 逆文档频率(tf-idf)以及线性代数中的概念,如余弦相似度和欧几里得距离,来找到具有相似含义的词汇。这些技术是分布式语义的一部分。另一种方法是 word2vec。这是由谷歌最近开发的算法,可以帮助我们找到词汇的语义以及具有相似含义的词汇。我们将在第六章,高级特征工程与 NLP 算法中探讨 word2vec 和其他技术。
除了 Word2vec,另一个强大的资源是WordNet,这是我们能使用的最大语料库,并且由人工标注。它还包含了每个词的词义标签。这些数据库对于找出特定词汇的语义非常有帮助。
你可以通过以下链接查看WordNet:wordnet.princeton.edu/
这里,我们列出了一些用于生成语义的最有用的资源和工具。这个领域还有很大的改进空间。
我们已经了解了大多数与自然语言处理领域相关的概念,并且已经看到如何利用这些概念和现有工具来提取特征。现在是时候进入下一个部分,这一部分将为我们提供关于统计特征的信息。
自然语言处理的基本统计特征
在上一部分中,我们介绍了大多数可用于提取特征的自然语言处理(NLP)概念、工具和算法。现在是学习一些统计特征的时刻了。在这里,我们将探讨统计方面的内容。你将了解统计概念如何帮助我们提取一些最有用的特征。
在我们跳入统计特征之前,作为先决条件,你需要理解基本的数学概念、线性代数概念和概率概念。因此,在这里,我们将首先理解这些概念,然后再理解统计特征。
基础数学
我们将从线性代数和概率的基础知识开始;这是因为我们希望你能回忆并记住必要的概念,以便它们在本章以及接下来的章节中能对你有所帮助。我们将在需要时解释必要的数学概念。
NLP 中的线性代数基本概念
在这一部分中,我们不会详细讨论所有线性代数的概念。本部分的目的是让你熟悉基本概念。除了给出的概念外,还有许多其他概念可以用于 NLP 应用。在这里,我们只会介绍必要的概念。关于算法及其数学方面的所有必要细节将在接下来的章节中提供。让我们从基础开始。
在 NLP 和机器学习中,你将经常遇到以下四个术语:
-
标量:它们只是单一的实数
-
向量:它们是一个一维的数字数组
-
矩阵:它们是二维的数字数组
-
张量:它们是 n 维的数字数组
图示表示见图 5.50:

图 5.50:标量、向量、矩阵和张量的图示表示(图片来源:http://hpe-cct.github.io/programmingGuide/img/diagram1.png)
矩阵操作可以通过NumPy库进行。你可以使用SciPy和scikit-learn库进行与向量相关的操作。我们推荐某些库,因为它们的源代码旨在为你提供最佳解决方案,并为你提供高层次的 API,这样你就不需要担心背后发生了什么。但是,如果你想开发自定义的应用程序,你就需要了解每个操作的数学方面。我们还会讨论线性回归、梯度下降和线性代数的概念。如果你真的想深入探讨与机器学习和深度学习相关的数学,以下的学习资料会对你有帮助。
本书的第一部分将对你大有帮助:
可以在此链接找到统计学、线性代数和微积分的备忘单:
github.com/jalajthanaki/NLPython/tree/master/Appendix2/Cheatsheets/11_Math。
如果你对数学比较陌生,我们建议你观看这些视频:
www.khanacademy.org/math/linear-algebra www.khanacademy.org/math/probability www.khanacademy.org/math/calculus-home
www.khanacademy.org/math/calculus-home/multivariable-calculus www.khanacademy.org/math 如果你想了解各种向量相似性概念,那么这篇文章将帮助你: dataaspirant.com/2015/04/11/five-most-popular-similarity-measures-implementation-in-python/
现在让我们进入下一部分,讲解概率的相关内容。这是概率论中的核心概念之一。
自然语言处理中的概率论基本概念
在这一部分,我们将探讨概率论的一些概念。我们还会看一些例子,以帮助你理解这些概念的实际应用。我们将从概率开始,然后讲解独立事件的概念,接着是条件概率。最后,我们会介绍贝叶斯定理。
概率
概率是衡量某个特定事件发生可能性的指标。概率以数字的形式量化,概率的范围介于 0 和 1 之间。0 表示该事件永远不会发生,而 1 则表示该事件必定会发生。机器学习技术广泛应用概率的概念。我们来看一个例子,以帮助复习这个概念。参见图 5.51:

图 5.51:概率示例(图片来源:http://www.algebra-class.com/image-files/examples-of-probability-3.gif)
现在让我们来看一下什么是相关事件和独立事件。
独立事件与相关事件
在这一部分,我们将探讨什么是相关事件与独立事件。之后,我们将学习如何判断一个事件是否是相关事件。首先,让我们从定义开始。
如果一个事件的发生概率不影响另一个事件的发生概率,那么这种事件被称为独立事件。严格来说,如果你有两个事件 A 和 B,且 A 发生的事实不影响 B 发生的概率,那么这两个事件就叫做独立事件。掷公正的硬币是一个独立事件,因为它不依赖于其他任何事件。
有时,某些事件会影响其他事件。当一个事件发生的概率会影响另一个事件发生时,这两个事件被认为是相关的。
例如,如果你从一副 52 张牌中抽取两张牌,而你第一次抽到的是一张王牌,那么第二次抽到王牌的概率就会发生变化,因为你第一次抽到了王牌。让我们计算一下这些不同的概率,看看发生了什么。
一副 52 张牌中有四张王牌。参见图 5.52:

图 5.52:概率方程
在你第一次抽取时,得到一张王牌的概率见于图 5.53:

图 5.53:计算步骤(图片来源: https://dj1hlxw0wr920.cloudfront.net/userfiles/wyzfiles/02cec729-378c-4293-8a5c-3873e0b06942.gif)
现在,如果你不把抽中的卡片放回牌堆,那么第二轮抽到王牌的概率在以下公式中给出。见图 5.54:

图 5.54:依赖事件概率公式(图片来源: https://dj1hlxw0wr920.cloudfront.net/userfiles/wyzfiles/7a45b393-0275-47ac-93e1-9669f5c31caa.gif)
查看图 5.55中的计算步骤:

图 5.55:计算步骤(图片来源: https://dj1hlxw0wr920.cloudfront.net/userfiles/wyzfiles/11221e29-96ea-44fb-b7b5-af614f1bec96.gif)
查看最终答案见于图 5.56:

图 5.56:示例的最终答案(图片来源: https://dj1hlxw0wr920.cloudfront.net/userfiles/wyzfiles/78fdf71e-fc1c-41d2-8fb8-bc2baeb25c25.gif)
如你所见,前面两个概率值是不同的,所以我们说这两个事件是相关的,因为第二个事件依赖于第一个事件。
检查事件是否相关或独立的数学条件如下:如果且仅当以下条件满足时,事件 A 和事件 B 是独立事件:
P(A ∩ B) = P(A) * P(B)
否则,A 和 B 被称为依赖事件。
现在让我们举个例子来理解定义的条件。
示例:一项调查发现,孟买市 72%的人口认为自己是足球迷。如果你从中随机挑选两个人,第一人是足球迷且第二人也是足球迷的概率是多少?第一人是足球迷而第二人不是的概率是多少?
解答:第一人是足球迷并不会影响第二个随机选择的人是否是足球迷。因此,事件是独立的。
这个概率可以通过将给定事件的各自概率相乘来计算。如果第一人和第二人都是足球迷,那么 P(A∩B) = P(A) P(B) = .72 * .72 = .5184。
对于第二个问题:第一人是足球迷,第二人不是:
P(A∩非 B) = P(A) P( B' ) = .72 * ( 1 - 0.72) = 0.202。
在这部分计算中,我们乘以了补集的概率。
在这里,事件 A 和 B 是独立的,因为公式 P(A∩B) = P(A) P(B) 成立。
现在是时候继续讲解下一个概念——条件概率了。
条件概率
在这一部分,我们将讨论一个名为条件概率的概念。我们将运用依赖事件和独立事件的概念来理解条件概率。
事件 B 的条件概率是给定事件 A 已经发生的情况下,事件 B 发生的概率。这个概率写作 P(B|A),即给定 A 的情况下 B 发生的概率。现在让我们看看当事件是独立时,这个条件概率会是什么样。若事件 A 和 B 独立,那么给定事件 A 的情况下事件 B 的条件概率就是事件 B 的概率,即 P(B)。如果事件 A 和 B 不是独立的呢?那么,事件 A 和 B 的交集的概率意味着两个事件都发生的概率,由下列公式定义:
P(A 和 B) = P(A) * P(B|A)
现在我们来看看一个例子。
示例:Jalaj 最喜欢的两样食物是茶和比萨饼。事件 A 表示我早餐喝茶,B 表示我午餐吃比萨饼。在随机选择的日子里,我早餐喝茶的概率 P(A) 是 0.6。午餐吃比萨饼的概率 P(B) 是 0.5,而给定我午餐吃比萨的情况下我早餐喝茶的条件概率 P(A|B) 是 0.7。根据这些信息,请计算 P(B|A) 的条件概率。P(B|A) 表示给定我早餐喝茶的情况下,我午餐吃比萨的概率。通俗地说,就是找出早餐喝茶时午餐吃比萨的概率。
解答
P(A) = 0.6 , P(B) = 0.5 , P(A|B) = 0.7
这里,两个事件是依赖的,因为事件 B 发生的概率改变了事件 A 发生的概率。现在我们需要计算 P(B|A)。
看到公式 P(A 和 B) = P(A) * P(B|A)。要找出 P(B|A),我们首先需要计算 P(A 和 B):
P(A 和 B) = P(B) * P(A|B) = P(A) * P(B|A)
这里,我们知道 P(B) = 0.5 且 P(A|B) = 0.7
P(A 和 B) = 0.5 * 0.7 = 0.35
P(B|A) = P(A 和 B) / P(A) = 0.35 / 0.6 = 0.5833
所以,我们已经找到了依赖事件的条件概率。
现在我们已经了解了概率的基础知识,这些将在接下来的章节中帮助我们理解机器学习算法。我们会随着学习的深入定义更多的概念。scikit-learn、TensorFlow、SparkML 等库已经实现了主要的概率计算,提供了高级 API,并且有选项可以根据你的应用更改预定义参数并设置值。这些参数通常被称为 超参数。找到每个参数最适合的值称为 超参数调优。这个过程有助于我们优化系统。我们将在 第八章 《自然语言处理应用中的机器学习》 中讨论超参数调优和其他主要概念。
这是我们前提部分的结束。从这一部分开始,我们将看到一些有助于从文本中提取特征的统计概念。许多 NLP 应用也使用这些概念。
TF-IDF
TF-IDF 的概念代表词频-逆文档频率。这是数值统计学中的一个概念。通过这个概念,我们能够决定某个单词在当前数据集或语料库中对给定文档的重要性。
理解 TF-IDF
这是一个非常简单但有用的概念。它实际上表示某个特定单词在数据集中出现的次数,以及该单词在理解文档或数据集中的重要性。我们来举个例子。假设你有一个数据集,学生们就“我的车”这一主题写作文。在这个数据集中,a 这个词出现了很多次,相较于数据集中的其他单词,它是一个高频词。数据集中还包含其他词汇,如car、shopping等,它们出现得较少,因此它们的频率较低,相比于a这个词,它们承载了更多的信息。这就是 TF-IDF 的直观理解。
让我们详细解释这个概念。我们也来看看它的数学方面。TF-IDF 有两个部分:词频(Term Frequency)和逆文档频率(Inverse Document Frequency)。我们从词频开始。词频显而易见,但我们还是来走一遍这个概念。词频表示文档或数据集中每个词汇的出现频率。所以,它的公式如下:
TF(t) = (词 t 在文档中出现的次数) / (文档中的总词数)
现在我们来看第二部分——逆文档频率。IDF 实际上告诉我们一个单词对文档的重要性。这是因为在我们计算 TF 时,我们对每个单词赋予了相等的重要性。现在,如果一个词在数据集中出现得更频繁,那么它的词频(TF)值会很高,但它对文档的实际重要性却不高。所以,如果the这个词在文档中出现了 100 次,那么它相较于数据集中出现较少的单词,承载的信息就少了。因此,我们需要定义一种方式,对频繁出现的词汇进行“降权”,同时对稀有词汇进行“加权”,从而决定每个单词的重要性。我们将通过以下公式来实现:
IDF(t) = log10。
所以,我们计算 TF-IDF 的公式如下。
TF * IDF = [(词 t 在文档中出现的次数) / (文档中的总词数)] * log10(文档总数 / 包含词 t 的文档数)。
请注意,在 TF-IDF 中,- 是连字符,而不是减号。实际上,TF-IDF 是 TF 和 IDF 的乘积,比如 TF * IDF。
现在,让我们举个例子,假设你有两个句子,并考虑将这两个句子视为不同的文档,以理解 TF-IDF 的概念:
文档 1:这是一个示例。
文档 2:这是另一个例子。
现在,为了计算 TF-IDF,我们将按照以下步骤进行:
-
我们首先计算每个文档中每个词汇的频率。
-
我们计算 IDF。
-
我们将 TF 和 IDF 相乘。
参见图 5.57:

图 5.57:TF-IDF 示例
现在,让我们看看在图 5.58中 IDF 和 TF * IDF 的计算:

图 5.58:TF-IDF 示例
通过实际示例理解 TF-IDF
在这里,我们将使用两个库来计算 TF-IDF——textblob 和 scikit-learn。你可以在这个 GitHub 链接中看到代码:
github.com/jalajthanaki/NLPython/tree/master/ch5/TFIDFdemo。
使用 textblob
你可以在图 5.59中看到代码片段:

图 5.59:使用 textblob 的 TF-IDF
代码的输出如图 5.60所示:

图 5.60:短词的 TF-IDF 输出
使用 scikit-learn
我们将尝试使用一个小型的莎士比亚数据集生成 TF-IDF 模型。对于一个新的文档及其 TF-IDF 得分模型,我们将为该文档推荐前三个关键词。你可以在图 5.61中看到代码片段:

图 5.61:使用 scikit-learn 生成 TF-IDF 模型
你可以在图 5.62中看到输出:

图 5.62:TF-IDF 模型的输出
现在是时候看看我们可以在哪里使用 TF-IDF 概念了,让我们来看看一些应用。
应用
在本节中,我们将看看一些使用 TF-IDF 的酷应用:
-
一般来说,TF-IDF 可以轻松地进行文本数据分析。你可以获取关于数据集中最准确关键词的信息。
-
如果你正在开发一个文本摘要应用程序,并且选择了某种统计方法,那么 TF-IDF 是生成文档摘要时最重要的特征。
-
TF-IDF 加权方案的变体通常被搜索引擎用来确定文档与给定用户查询的相关性评分和排名。
-
文档分类应用程序结合 BOW 使用这项技术。
现在让我们来看一下 NLP 应用中的向量化概念。
向量化
向量化是 NLP 领域特征提取的一个重要方面。将文本转换为向量格式是一个主要任务。
向量化技术尝试将每个可能的单词映射到一个特定的整数。有许多现成的 API 可以让你的工作更加轻松。scikit-learn提供了DictVectorizer,它将文本转换为独热编码形式。另一个 API 是CountVectorizer,它将文本集合转换为一个标记计数矩阵。最后,还有一些其他的 API 可供使用。我们还可以使用 word2vec 将文本数据转换为向量形式。有关更多详细信息,请参考此链接的From text部分:
scikit-learn.org/stable/modules/classes.html#module-sklearn.feature_extraction.text。
现在让我们看看 NLP 应用中一热编码的概念。一热编码被认为是向量化的一部分。
编码器和解码器
在 NLP 中,编码的概念相当古老且有用。正如我们之前提到的,处理数据集中的类别数据属性并不容易。在这里,我们将探讨一种名为一热编码的编码技术,它帮助我们将类别特征转换为数值格式。
一热编码
在自然语言处理(NLP)应用中,你总是会遇到类别数据。类别数据大多数以单词的形式存在。这些单词组成了词汇表,而这些词汇表中的单词并不容易直接转化为向量。
假设你有一个大小为 N 的词汇表。通过将单词表示为一热编码的形式,我们可以近似表示语言的状态。这种技术用于将单词映射到长度为 n 的向量,其中第 n 位是特定单词存在的指示符。如果你将单词转换为一热编码格式,你会看到像 0000...001、0000...100、0000...010 等向量。词汇表中的每个单词由一个二进制向量的组合表示。这里,每个向量的第 n 位表示词汇表中第 n 个单词的存在。那么,这些单独的向量如何与语料库中的句子或其他单词相关呢?让我们通过一个例子来帮助你理解这个概念。
例如,你有一个句子,Jalaj likes NLP。假设应用一热编码后,这个句子变成了 00010 00001 10000。这种向量是基于词汇表的大小和编码方案生成的。一旦我们有了这个向量表示,就可以对它进行数值运算。在这里,我们将单词转换为向量,将句子转换为矩阵。
理解一热编码的实际例子
在本节中,我们将使用scikit-learn为一个小型数据集生成一热编码。你可以通过这个 GitHub 链接查看代码:
github.com/jalajthanaki/NLPython/tree/master/ch5/onehotencodingdemo。
你可以在图 5.63中看到代码片段:

图 5.63:使用 Pandas 和 scikit-learn 生成一热编码
你可以在图 5.64中看到输出:

图 5.64:一热编码的输出
应用
这些技术非常有用。让我们看看这种映射技术的一些基本应用:
-
许多人工神经网络接受一热编码格式的输入数据,并生成包含语义表示的输出向量
-
word2vec 算法接受以单词形式存在的输入数据,而这些单词则是通过一热编码生成的向量
现在是时候看看解码概念了。解码概念如今大多用于深度学习中。因此,在这里,我们将根据深度学习来定义解码器,因为我们将在第九章,用于自然语言理解和自然语言生成问题的深度学习,中使用这个编码和解码架构来开发翻译系统。
编码器将输入数据映射到不同的特征表示;我们在 NLP 领域使用的是独热编码。解码器将特征表示映射回输入数据空间。在深度学习中,解码器知道哪个向量表示哪个单词,因此它可以根据给定的输入架构解码单词。我们将在讲解序列到序列模型时详细讨论编码器-解码器的概念。
现在,让我们来看看下一个概念——归一化。
归一化
在这里,我们将从语言学和统计学的角度解释归一化。尽管它们不同,但归一化这个词常常会引起许多困惑。我们来解决这个困惑。
归一化的语言学方面
归一化的语言学方面包括文本归一化的概念。文本归一化是将给定文本转化为单一标准形式的过程。让我们通过一个例子来正确理解文本归一化。如果你正在开发一个搜索应用,并且希望用户输入John,那么 John 就成了搜索字符串,所有包含John的字符串也应该显示出来。如果你正在准备搜索数据,通常人们会选择词干化格式;即使你搜索flying或flew,它们最终都是由fly这个词派生出来的形式。所以,搜索系统使用词干形式,其他派生形式则被移除。如果你回想一下第三章,理解句子的结构,你就会记得我们已经讨论过如何推导词形、词干和词根。
归一化的统计学方面
归一化的统计学方面用于特征缩放。如果你的数据集中,某一数据属性的取值范围过大,而其他数据属性的取值范围过小,那么通常我们需要应用统计技术,将所有数据属性或特征带入一个统一的数值范围。执行这种转换的方法有很多种,但在这里我们将介绍最常见且简单的方式——最小-最大缩放。让我们通过方程式和数学示例来理解这个概念。
最小-最大缩放将特征带入[0,1]范围。通用公式如图 5.65所示:

图 5.65:最小-最大归一化方程式
假设你有一些特征值,如 [1, 8, 22, 25];如果你应用前面的公式并计算每个元素的值,那么你将得到一个范围在 [0,1] 之间的特征值。对于第一个元素,z = 1 - 1 / 25 -1 = 0,对于第二个元素,z =8 -1 /25-1 = 0.2917,以此类推。scikit-learn 库提供了一个 API,你可以使用它对数据集进行最小-最大缩放。
在接下来的部分,我们将讨论语言模型。
概率模型
我们将讨论自然语言处理中最著名的概率模型之一,它已被应用于多种任务——语言模型。我们将了解语言模型的基本思想。我们不会深入探讨这个问题,但我们会对语言模型的工作原理以及它的应用场景有一个直观的了解。
理解概率语言建模
语言模型(LM)有两个基本目标:
-
语言模型(LM)的目标是为一句话或单词序列分配概率。
-
语言模型(LM)还告诉我们下一个单词的概率,这意味着它通过观察前一个单词序列来指示下一个最可能的单词。
如果任何模型能够计算前面提到的任务中的任何一个,那么它就被称为语言模型(LM)。语言模型使用条件概率链式法则。条件概率的链式法则实际上是条件概率的一种扩展。我们已经见过这个方程:
P(A|B) = P(A and B) / P(B)
P(A and B) = P(A,B) = P(A|B) P(B)
这里,P(A,B) 被称为联合概率。假设你有多个相互依赖的事件,那么计算联合概率的方程变得更加一般化:
P(A,B,C,D) = P(A) P(B|A)P(C|A,B)P(D|A,B,C)
P(x1,x2,x3,...,xn) =P(x1)P(x2|x1)P(x3|x1,x2)...P(xn|x1,x2,x3,...xn-1)
上述方程式称为条件概率的链式法则。语言模型使用此法则来预测即将出现的词的概率。我们通常通过计算特定事件发生的次数,并将其除以所有可能组合的总数来计算概率,但在语言中我们无法应用此方法,因为对于某些词,你可以生成数百万个句子。因此,我们不使用概率方程式,而是使用一种叫做马尔可夫假设的假设来计算概率。让我们在查看技术定义之前,先直观地理解这个概念。如果你有一个非常长的句子,并且你正在尝试预测句子序列中的下一个词,那么你实际上需要考虑句子中已经出现的所有词来计算下一个词的概率。这个计算非常繁琐,因此我们只考虑最后一个、两个或三个词来计算下一个词的概率;这就是所谓的马尔可夫假设。这个假设是,通过查看句子中最后一个或两个词,你可以计算下一个词的概率。让我们通过一个例子来理解这一点。如果你想计算给定词的概率,那么它只依赖于前一个词。你可以看到这里的方程式:
P(the | its water is so transparent that) = P(the | that) 或者你也可以考虑前两个词 P(the | its water is so transparent that) = P(the | transparent that)
一个简单的语言模型使用的是单元模型(unigram),意味着我们仅考虑单词本身并计算单个词的概率;你只是取单个词的概率并生成随机的词序列。如果你使用二元模型(bigram),则考虑前一个词将决定序列中的下一个词。你可以在图 5.66中看到二元模型的结果:

图 5.66:使用二元语言模型的输出
我们如何计算语言模型中作为核心部分的 n-gram 概率?让我们来看一下二元模型。我们将看到方程式,然后通过例子来讲解。请参见图 5.67中的方程式:

图 5.67:计算序列中下一个最可能词的方程式
这个方程式很容易理解。我们需要计算wi-1和wi一起出现的次数,还需要计算wi-1出现的次数。请参见图 5.68中的例子:

图 5.68:使用语言模型找出最可能的词的例子
如你所见,在三句话中,“we”后面跟着两次“”,所以我们有 P(I|) = 2/3,对于每个单词,我们都会计算其概率。通过使用语言模型(LM),我们可以了解单词对在语料库中的描述,以及语料库中出现的更常见的单词对。如果我们使用四元组或五元组模型,它会为语言模型提供一个良好的结果,因为有些句子在语法结构中具有长依赖关系,尤其是主语和动词之间的关系。因此,使用四元组或五元组模型,你可以构建一个非常好的语言模型。
语言模型的应用
语言模型(LM)在 NLP 领域有很多优秀的应用。大多数 NLP 应用都会在某个阶段使用语言模型。让我们来看一下这些应用:
-
机器翻译系统使用语言模型来计算每个翻译句子的概率,从而决定哪个翻译句子是给定输入句子的最佳翻译。
-
为了拼写正确的应用,我们可以使用二元语言模型(bigram LM)来提供最可能的单词建议。
-
我们可以使用语言模型进行文本摘要。
-
我们可以在问答系统中使用语言模型,根据概率对答案进行排名。
索引化
索引化是一种非常有用的技术。它用于将类别数据转换为数字格式。在 NLP 应用中,你可能会发现数据属性是类别型的,你希望将其转换为某个数字值。在这种情况下,索引化概念可以帮到你。我们可以使用 SparkML 库,它提供了多种 API 来生成索引。SparkML 有一个名为 StringIndexer 的 API,它利用类别数据的频率并根据频率计数分配索引。所以,出现最频繁的类别会得到索引值 0。虽然这有时可能是一种简单的索引生成方式,但在某些分析应用中,你可能会发现这种技术非常有用。你可以在以下链接查看示例:
spark.apache.org/docs/latest/ml-features.html#stringindexer。
SparkML 有一个名为 IndexToString 的 API,当你需要将数值转换回类别值时可以使用它。你可以在以下链接找到示例:
spark.apache.org/docs/latest/ml-features.html#indextostring。
应用
这里是一些使用索引化提取特征的应用:
-
当我们处理一个多类别分类器,并且目标类别是文本格式时,我们想将目标类别标签转换为数字格式,可以使用 StringIndexer。
-
我们还可以使用 IndexToString API 生成
target类别的文本。
现在是时候了解一个叫做排名的概念了。
排名
在许多应用中,排名扮演着关键角色。当你在网页上搜索任何内容时,就会使用到排名的概念。基本上,排名算法用于找出给定输入和生成输出之间的相关性。
让我们看一个例子。当你在网上搜索时,搜索引擎会处理你的查询并生成一些结果。它使用排序算法根据你的查询找到最相关的链接,并将最相关的链接或内容显示在顶部,而最不相关的则显示在底部。当你访问任何在线电子商务网站时也是如此;当你搜索某个产品时,它们会向你展示相关的产品列表。为了提升客户体验,它们会展示那些与你的查询相关、评价良好并且价格合理的产品。这些都作为参数传递给排序算法,用以生成最相关的产品。
排序算法的实现不属于本书的内容。你可以在这里找到更多有用的信息:
medium.com/towards-data-science/learning-to-rank-with-python-scikit-learn-327a5cfd81f。
索引和排序在 NLP 领域并不常用,但当你尝试构建一个与分析相关的机器学习应用时,它们变得尤为重要。它们主要用于学习用户的偏好。如果你正在制作类似 Google News 的 NLP 应用,在其中需要对特定新闻事件进行排序,那么排序和索引就起到了至关重要的作用。在问答系统中,为答案生成排名是最关键的任务,你可以结合使用索引、排序和语言模型,以获得最佳的结果。像语法修正、校对、摘要系统等应用则不使用这一概念。
我们已经看到了在 NLP 应用中可以使用的大部分基本特性。我们将在第八章《NLP 应用中的机器学习》中使用它们,届时我们将使用 ML 算法构建一些实际的 NLP 应用。在接下来的章节中,我们将解释特征工程的优势和挑战。
特征工程的优势
特征工程是 NLP 领域最重要的方面,当你尝试应用 ML 算法来解决 NLP 问题时。如果你能够提取出好的特征,那么你将拥有以下几个优势:
-
更好的特征会给你带来更大的灵活性。即使你选择了一个不太理想的 ML 算法,仍然能得到不错的结果。好的特征让你在选择算法时更具灵活性;即使选择了一个较简单的模型,依然可以得到较好的准确度。
-
如果你选择了好的特征,即使是简单的 ML 算法也能取得好效果。
-
更好的特征将带来更好的准确度。你应该花更多的时间进行特征工程,为你的数据集生成合适的特征。如果你能提取出最佳且合适的特征,那么你就赢得了大部分的胜利。
特征工程的挑战
在这里,我们将讨论 NLP 应用中的特征工程挑战。你一定在想,我们有很多工具和算法的选择,那么最具挑战性的部分是什么呢?让我们来找出答案:
-
在 NLP 领域,你可以轻松地推导出类别特征或基本的 NLP 特征。我们必须将这些特征转换为数字格式。这是最具挑战性的部分。
-
将文本数据转换为数字格式的有效方法相当具有挑战性。在这里,试错法可能会对你有所帮助。
-
尽管有一些技术可以用于将文本数据转换为数字格式,例如 TF-IDF、独热编码、排名、共现矩阵、词嵌入、Word2Vec 等,但方法并不多,因此人们觉得这一部分具有挑战性。
总结
在本章中,我们已经了解了 NLP 领域中广泛使用的许多概念和工具。所有这些概念都是特征工程的基本构建块。当你想生成 NLP 应用时,可以使用这些技术中的任何一种来生成特征。我们已经了解了如何通过解析、词性标注、命名实体识别(NER)、n-gram 和词袋模型生成与自然语言相关的特征。我们还探索了它们的构建方式,以及在需要定制特征来开发 NLP 应用时,如何调整一些现有工具。此外,我们已经学习了线性代数、统计学和概率论的基本概念。我们还了解了将来在机器学习算法中使用的概率论基本概念。我们还研究了一些有趣的概念,如 TF-IDF、索引、排名等,以及作为概率模型一部分的语言模型。
在下一章中,我们将介绍一些高级特征,如 word2vec、Doc2vec、Glove 等。所有这些算法都是词嵌入技术的一部分。这些技术将帮助我们有效地将文本特征转换为数字格式,尤其是当我们需要使用语义时。下一章将为你提供关于 word2Vec 算法的更详细信息。我们将覆盖 word2vec 模型背后的每一个技术细节。我们还将了解如何使用人工神经网络(ANN)来生成单词之间的语义关系,然后我们将从单词级别、句子级别、文档级别等方面探索这一概念的扩展。我们将构建一个包含一些精彩的 word2vec 可视化的应用程序。我们还将讨论向量化的重要性,所以请继续阅读!
第六章:高级特征工程与 NLP 算法
在本章中,我们将探讨一个既简单又令人惊叹的概念,叫做词到向量(word2vec)。这个概念是由 Google 的 Tomas Mikolov 领导的研究团队开发的。众所周知,Google 为我们提供了许多伟大的产品和概念,而 word2vec 就是其中之一。在自然语言处理(NLP)中,开发能够处理词语、短语、句子等语义的工具或技术是一项重要任务,而 word2vec 模型在理解词语、短语、句子、段落和文档的语义方面做得非常出色。我们将深入这个向量化的世界,并在其中待上一段时间。你不觉得这非常神奇吗?我们将从概念开始,最终通过一些有趣且实用的例子来结束。所以,让我们开始吧。
回顾词嵌入
我们已经在第五章中讨论了词嵌入,特征工程与 NLP 算法。我们已经探讨了 NLP 中的语言模型和特征工程技术,在这些模型中,词语或短语被映射到实数向量。将词语转换为实数的技术被称为词嵌入。我们一直在使用向量化技术,以及基于词频-逆文档频率(tf-idf)的向量化技术。那么,让我们直接跳入 word2vec 的世界吧。
理解 word2vec 的基础
在这里,我们将尝试通过使用 word2vec 来处理词级别的语义。接着,我们将扩展我们的概念到段落级和文档级。通过查看图 6.1,你将看到我们在本书中将要涵盖的不同种类的语义:

图 6.1:不同种类的语义
语义学是处理 NLP 领域中意义的一门分支学科。我们已经在第三章中讨论了词汇语义学,理解句子结构。在这里,我们将更深入地讨论分布语义学。语义学中还有其他技术或类型,比如形式语义学和组合语义学;但是在本书中,我们现在不会涵盖这些类型或技术。
分布语义学
分布语义学是一个研究领域,专注于开发量化和分类基于大型文本数据样本中分布特性的语言项目之间语义相似性的技术或理论。
我想给你一个例子,让你了解我所说的分布式语义是什么意思。假设你有关于旅行博客的文本数据。现在,作为一个人,你知道意大利面、面条、汉堡等是可食用的食品,而果汁、茶、咖啡等是饮料。作为人类,我们可以轻松地将可饮用和可食用的食物分类,因为我们与每个词都有一定的语境联系,但机器并不能真正理解这些语义。所有描述食物的项目很可能会在数据集中与某些特定词汇一起出现。因此,这里我们关注的是语料库中词汇的分布,假设具有相似分布的语言项目或词汇具有相似的含义。这被称为分布假设。
我再给你一个例子。假设你有一个研究论文的数据集。数据集中的一些研究论文属于工程类,另一些属于法律类。包含“工程”、“方程式”、“方法”等词汇的文档与工程相关,因此它们应归为一组;而包含“法律”、“律师”、“法学院”等词汇的文档与法律领域的研究论文相关,因此它们应归为另一组。通过使用分布式语义技术,如 word2vec,我们可以通过使用它们的向量值来区分不同领域的词汇。所有具有相似含义的词汇会被归为一组,因为它们在语料库中的分布相似。你可以参考图 6.2,它展示了我们给定的分布式语义示例的向量空间图示,类似语境的词汇聚集在一起:

图 6.2:我们分布式语义示例的向量空间图示
图 6.3,让你了解 word2vec 模型是从哪个分支派生出来的:

图 6.3:从分布式语义中衍生出的主要模型
本章的重点是分布式语义技术,称为word2vec。
定义 word2vec
Word2vec 是通过使用双层神经网络开发的。它以大量文本数据或文本语料库为输入,并从给定的文本中生成一组向量。
换句话说,我们可以说它生成了一个高维度的向量空间。这个向量空间有数百个维度。
请不要害怕高维度。在本章中,我会简化 word2vec 的整个概念。
当我说 word2vec 模型从文本中生成一组向量或向量空间时,你真的会明白我的意思。这里,我们使用的是一个两层神经网络,当前它是一个黑箱,执行某种逻辑并为我们生成向量。在这个向量空间中,语料库中每个唯一的单词都会被分配一个对应的向量。所以,向量空间只是所有词汇在大规模文本语料库中出现的向量表示。
所以,我想你明白了,对吧?基于我们所学的基础,你可以说 word2vec 是生成词嵌入的模型之一。请回忆一下第五章中关于向量化的部分,特征工程与 NLP 算法。我还想在这里强调一点,word2vec 是一种强大的、无监督的词嵌入技术。
无监督分布式语义模型的必要性——word2vec
本节内容让我们对 word2vec 为我们解决的众多挑战有了一个初步了解。这些挑战的解决方案引导我们发现了 word2vec 的真正需求。所以,我们首先来看一些挑战,然后再看看 word2vec 模型是如何解决这些挑战的。
挑战
这里列出了一些我们正在尝试解决的挑战:
-
当我们开发 NLP 应用时,有一个基本问题——我们知道机器无法理解我们的文本,因此需要将文本数据转换为数值格式。
-
有几种方法可以将文本数据转换为数值格式,但我们采用了一些简单的技术,其中之一就是 one-hot 编码,但这种技术存在以下问题:
-
假设你有一句话:“I like apple juice”。现在,假设你对句子中的每个单词应用 one-hot 编码。如果你的语料库中有成千上万的句子,那么向量的维度就等于语料库的整个词汇表,如果这些高维列被用来开发 NLP 应用,那我们就需要强大的计算能力,并且需要对这些高维列进行矩阵运算,因为它们会消耗大量时间。
-
对于语音识别词汇表,词汇量的平均大小是 20,000 个单词。如果我们正在开发一个机器翻译系统,那么可能会使用更多的词汇,比如 50 万个单词。处理这些庞大的向量是一个巨大的挑战。
-
另一个问题是,当你对句子中的某个单词应用 one-hot 编码时,整个条目都会是零,除了实际表示该单词的那个值,而该值是
1。为了简便起见,我们暂时考虑语句:“I like apple juice”。假设我们的语料库中只有一句话。那么,如果我试图对单词apple应用 one-hot 编码,那么apple的 one-hot 表示如下。请参考图 6.4:![]()
-
图 6.4:苹果和果汁的单热编码表示
-
单热编码并不揭示词之间上下文相似性的事实。为了理解这一点,我想举个例子,如果你的语料库中有单词 cat 和 cats,则单热编码并不揭示单词 cat 和 cats 是非常相似的词。
-
如果我对苹果和果汁的单热编码向量进行 AND 运算,那么它将不会表达任何上下文相似性。举个例子,如果我对苹果和果汁的单热编码向量进行 AND 运算,答案是 0。事实上,这些词可以一起出现,并且具有很强的上下文关系,但单热编码本身并不能表达有关词相似性的任何重要信息。
-
如果你想找到准确的词相似性,那么 WordNet 将不能为你提供足够的帮助。WordNet 是由专家制作的,而 WordNet 包含的内容更主观,因为是人类用户创建的。
-
使用 WordNet 需要大量的时间和精力。
-
一些新词,如 Coding Ninja、Wizard 等,是 WordNet 的新词,可能不在网站上。由于缺少这些类型的词,我们无法从 WordNet 推导出其他语义关系。
在解决这些挑战之前的每一个挑战都在这些技术的发展中起了重要作用。在过去的二十年中,已经有很多努力致力于开发一种高效、简洁和相关的词的数值表示。最终,在 2013 年,Google 的托马斯·米科洛夫和他的研究团队提出了 word2vec 模型,这种模型以高效的方式解决了许多之前的挑战。
Word2vec 在发现词的相似性以及保留语义关系方面非常出色,这些是以前的技术(如单热编码或使用 WordNet)无法处理的。
我已经在 word2vec 上提供了很多背景,现在让我们开始理解 word2vec 模型的表示、组件和其他参数。
让我们开始我们神奇的旅程吧!
将 word2vec 模型从黑箱转换为白箱
从这一节开始,我们将了解 word2vec 模型的每个组成部分,以及模型的工作过程。简而言之,我们正在将 word2vec 的黑箱部分转化为白箱。
我们将重点关注以下程序,以了解 word2vec 模型:
-
基于分布相似性的表示
-
理解 word2vec 模型的组成部分
-
理解 word2vec 模型的逻辑
-
理解 word2vec 模型背后的算法和数学
-
关于 word2vec 模型的一些事实
-
应用 word2vec 模型
让我们开始吧!
基于分布相似性的表示
这是自然语言处理领域中一个非常古老且强大的观点。分布相似性的概念是,通过考虑某个特定单词出现的上下文,你可以为该单词的意义提供很多价值,而且它与上下文密切相关。著名语言学家约翰·费尔斯曾有一句非常著名的名言:
“你可以通过它所处的环境来了解一个词。”
让我们举个例子:如果我想要了解“银行业”一词的含义,我将收集成千上万的包含“银行业”一词的句子,然后我会开始查看与“银行业”一词一起出现的其他词汇,并尝试理解“银行业”一词所处的上下文。看一下这些例子,并理解分布相似性:
-
句子 1:银行业由政府进行监管。
-
句子 2:银行机构需要一些技术来改变其传统的运营模式。
在前面的句子中,"银行业"这个词与政府、部门、运营等词语频繁出现。这些词语对理解“银行业”一词的背景和意义非常有帮助。
这些其他词语确实有助于表征“银行业”一词的含义。你还可以利用“银行业”一词来预测该词出现时,最常见和频繁出现的词或短语。
为了更好地表示一个特定单词的含义,并对该单词上下文中出现的其他单词进行预测,我们需要理解该单词的分布式表示。
单词的分布式表示是一种向量形式,其中该单词可以被表示出来。单词以稠密向量的形式表达,而这个稠密向量必须被选择得足够好,以便它能够有效预测该单词上下文中出现的其他词语。现在,我们要对每个需要预测的其他词语也进行类似的操作,这些词语也会附带其他词语。因此,我们使用相似性度量方法,比如向量点积。这是一种递归的方法,每个单词会预测出能够出现在相同上下文中的其他单词,而其他被预测的单词也会通过预测其他单词来执行同样的操作。所以,我们需要一个巧妙的算法来执行这种递归操作。
在这里,请不要混淆分布相似性和分布表示术语。基于分布相似性的表示术语实际上是语义理论的一部分,它帮助我们理解单词在日常使用中的含义;而单词的分布表示是单词以向量形式的表示。要生成单词的向量形式,我们可以使用独热编码或其他任何技术,但这里的主要点是生成一个单词向量,它还带有相似性测量的意义,以便你可以理解单词的上下文含义。当我们谈论分布相似性时,word2vec 就会出现在画面中。
理解 word2vec 模型的组成部分
在这一节中,我们将了解 word2vec 模型的主要三个组成部分,如下所示:
-
word2vec 的输入
-
word2vec 的输出
-
word2vec 模型的构造组件
word2vec 的输入
首先,我们应该意识到我们为开发 word2vec 模型而输入的内容,因为这是一个基础事物,你可以从中开始构建 word2vec。
所以,我想声明我们将使用原始文本语料库作为开发 word2vec 模型的输入。
在实际应用中,我们使用大型语料库作为输入。为简单起见,在本章中我们将使用一个相当小的语料库来理解概念。在本章的后面部分,我们将使用一个大语料库来开发一些很酷的东西,通过使用 word2vec 模型的概念。
word2vec 的输出
这一节对于你的理解非常重要,因为在此之后,你所理解的一切都将是为了实现你在这里设定的输出。到目前为止,我们知道我们想要开发一个单词的向量表示,它承载单词的含义,并表达分布相似性测量。
现在,我将跳转到定义我们的目标和输出。我们想要定义一个模型,旨在预测一个中心词及其上下文中出现的词。因此,我们可以说我们想要预测给定词的上下文的概率。在这里,我们正在设置简单的预测目标。您可以通过参考以下图来理解这个目标:

图 6.5:帮助理解我们的目标
如你所见,前面的图中给出了一些简单的例句。如果我们从第一句中取出单词apple,并根据我们的目标,将单词apple转换为向量形式,使得通过使用apple的向量形式,我们能够预测单词eat在单词apple上下文中出现的概率。相同的逻辑也适用于其他句子。例如,在第三个句子中,我们尝试找出单词scooter的向量,帮助我们预测像driving和work这样的单词在给定单词scooter的上下文中出现的概率。
因此,一般来说,我们的直接目标是将每个单词转换为向量形式,使得它们能够预测出在上下文中出现的单词,并且通过提供上下文,我们可以预测出最适合该上下文的单词的概率。
word2vec 模型的构建组件
到目前为止,我们知道了输入和输出,因此你现在可能在想:我们如何通过我们的输入来实现目标呢?正如我之前提到的,我们需要一个聪明的算法来帮助我们实现目标。研究人员已经为我们做了这项工作,并得出结论:我们可以使用神经网络技术。我想给你一个简短的介绍,解释为什么我们要使用神经网络,但如果你想深入了解它,我建议你阅读一些这些论文。
wwwold.ece.utep.edu/research/webfuzzy/docs/kk-thesis/kk-thesis-html/node12.html.
s3.amazonaws.com/academia.edu.documents/44666285/jhbs.pdf?AWSAccessKeyId=AKIAIWOWYYGZ2Y53UL3A&Expires=1497377031&Signature=CtXl5qOa4OpzF%2BAcergNU%2F6dUAU%3D&response-content-disposition=inline%3B%20filename%3DPhysio_logical_circuits_The_intellectua.pdf.
blogs.umass.edu/brain-wars/files/2016/03/rosenblatt-1957.pdf.
我们之所以使用神经网络技术,是因为神经网络在从大量数据中学习时表现得非常优秀。如果你想构建一个简单、可扩展且易于训练的模型,那么神经网络是最佳的方法之一。如果你阅读我接下来列出的现代研究论文,它们会告诉你同样的真相。下面是这些论文的链接:
这种神经网络在生成分布式相似性方面创造了奇迹。Google 通过使用大量 Wikipedia 语料库来生成 word2vec 模型。请参考图 6.6,它给你展示了输入概况,以及 Google word2vec 模型的一些著名输出。对于我们来说,word2vec 模型仍然是一个强大的“黑盒”,能够生成一些很棒的结果。请看以下图中的 word2vec 黑盒表示:

图 6.6:Google word2vec 模型以 Wikipedia 文本作为输入并生成输出
上面的图像显示我们已经将文本数据作为输入提供给 word2vec 模型。word2vec 模型将我们的文本数据转换为向量形式,这样我们就可以对这些词汇的向量表示进行数学运算。word2vec 最著名的例子是:如果你有 king、man 和 woman 这三个词的向量。那么,如果你对 king 向量减去 man 向量,再加上 woman 向量,你将得到一个表示 queen 向量的结果。以下是这个例子的数学表示:king - man + woman = queen。
现在我们需要关注 word2vec 的架构组件概述。
架构组件
让我们来看看构建 word2vec 模型所涉及的架构组件。
word2vec 模型的主要架构组件是其神经网络。word2vec 模型的神经网络有两层,从这个意义上来说,它并不是一个深度神经网络。事实上,word2vec 并没有使用深度神经网络来生成词汇的向量表示。
这是 word2vec 模型中的一个关键组成部分,我们需要解码它的功能,才能清楚地了解 word2vec 是如何工作的。现在是解码 word2vec 模型神奇逻辑的时刻。
理解 word2vec 模型的逻辑
我们将开始分解 word2vec 模型,并尝试理解其逻辑。word2vec 是一款软件,使用了一堆算法。请参考图 6.7:

图 6.7:Word2vec 构建模块(图片来源:Xin Rong)
如图 6.7所示,主要有三个构建模块。我们将详细查看每一个模块:
-
词汇构建器
-
上下文构建器
-
两层神经网络
词汇构建器
词汇构建器是 word2vec 模型的第一个构建块。它接受原始文本数据,通常以句子的形式。词汇构建器用于从给定的文本语料库中构建词汇表。它会收集语料库中的所有唯一单词并构建词汇表。
在 Python 中,有一个名为gensim的库。我们将使用gensim为我们的语料库生成 word2vec。在gensim中有一些参数可以根据你的应用需求,用来从语料库中构建词汇表。以下是参数列表:
-
min_count:该参数用作阈值。它会忽略所有总频率低于指定值的单词。例如,如果你设置min_count=5,则词汇构建器的输出不会包含出现次数少于五次的单词。词汇构建器的输出只包含在语料库中出现五次或更多的单词。 -
build_vocab(sentences, keep_raw_vocab=False, trim_rule=None, progress_per=10000, update=False):此语法用于从一系列句子(可以是一次性生成的流)中构建词汇表。每个句子必须是一个 Unicode 字符串的列表。
你可以点击这个链接,阅读更多关于其他参数的信息:radimrehurek.com/gensim/models/Word2vec.html.
词汇表中的每个单词都与词汇对象相关联,该对象包含一个索引和一个计数。这是词汇构建器的输出。因此,你可以参考图 6.8,它帮助你理解词汇构建器的输入和输出:

图 6.8:词汇构建器的输入输出流程
上下文构建器
上下文构建器使用词汇构建器的输出,以及作为上下文窗口一部分的单词,作为输入并生成输出。
首先,让我们理解上下文窗口的概念。这个上下文窗口类似于一个滑动窗口。你可以根据将要使用 word2vec 的自然语言处理应用程序定义窗口的大小。通常,NLP 应用程序使用大小为五到十个单词的上下文窗口。如果你选择五个单词作为窗口大小,那么我们需要考虑中心单词左边的五个单词和右边的五个单词。通过这种方式,我们捕获了中心单词的所有周围单词的信息。
这里我想举一个例子,假设上下文窗口的大小为 1,因为我们有一个单句语料库。我有一句话:I like deep learning, 其中deep是中心词。那么接下来,你应该根据窗口大小考虑周围的词。因此,在这里,我需要考虑like和learning这两个词。在下一次迭代中,我们的中心词将是learning,它的周围词是deep,句子的结尾是一个句点(.)。
我希望你已经清晰理解了上下文窗口的概念。现在,我们需要将这个概念与上下文构建器如何使用这个概念以及词汇构建器的输出联系起来。词汇构建器对象包含单词的索引以及该单词在语料库中的频率。通过使用单词的索引,上下文构建器就能知道我们在查看哪个单词,并且根据上下文窗口的大小,它会考虑周围的其他单词。这些中心词和其他周围词将作为输入传递给上下文构建器。
现在你对上下文构建器的输入有了清晰的了解。让我们试着理解上下文构建器的输出。这个上下文构建器生成词对。参考图 6.9,了解词对的概念:

图 6.9:理解词对
这些词对将被提供给神经网络。网络将根据每个词对出现的次数来学习基本统计信息。例如,神经网络可能会看到更多(deep, learning)这个词对的训练示例,而不是(deep, communication)。当训练完成后,如果你将deep作为输入,它将为 learning 或 network 输出更高的概率,而不是为 communication 输出。
所以这个词对是上下文构建器的输出,它将传递给下一个组件,即一个两层的神经网络。参考图 6.10,查看上下文构建器流程的概述:

图 6.10:上下文构建器的输入和输出流
到目前为止,我们已经看到了 word2vec 构建块的两个主要组件。现在,我们的下一个关注点将是最终组件——神经网络。
两层神经网络
在本节中,我们将研究神经网络的输入和输出。除此之外,我们还将关注神经网络的结构部分,这将帮助我们了解单个神经元的样子,神经元的数量,激活函数是什么等等。那么,现在我们开始吧!
word2vec 神经网络的结构细节
Word2vec 使用神经网络进行训练。所以对我们来说,理解神经网络的基本结构非常重要。神经网络的结构细节如下所示:
-
有一个输入层
-
第二层是隐藏层
-
第三层也是最后一层是输出层
Word2vec 神经网络层的详细信息
正如我们所知,神经网络中有两层用于生成词向量。我们将开始详细查看每一层及其输入和输出。在这一部分中,我们不涉及 word2vec 模型背后的数学。稍后在本章中,我们将讨论 word2vec 背后的数学,并在那时让你知道如何映射你的点以便更好地理解。
让我们简要了解每一层的任务:
-
输入层:输入层的神经元数量与词汇表中的单词数量相同
-
隐藏层:隐藏层的大小以神经元数表示,是最终词向量的维度
-
输出层:输出层的神经元数量与输入层相同
第一个输入层的输入是采用独热编码的单词。假设我们的词汇大小为V,这意味着语料库中有V个不同的单词。在这种情况下,表示单词本身的位置信息编码为1,所有其他位置编码为0。请再次参考图 6.4以回顾独热编码的概念。假设这些单词的维度为N。因此,输入到隐藏层的连接可以由我们的输入矩阵WI(输入矩阵符号)表示,大小为V * N,其中矩阵WI的每一行代表一个词汇单词。同样,从隐藏层到输出层的连接意味着来自隐藏层的输出可以由隐藏层输出矩阵WO(隐藏层矩阵符号)描述。WO矩阵的大小为N * V。在这种情况下,WO矩阵的每一列代表给定词汇中的一个单词。请参考图 6.11以清晰地理解输入和输出。同时,我们还将通过一个简短的示例来理解这个概念:

图 6.11:双层神经网络输入和输出结构表示
现在让我们通过例子来讨论。我将以一个非常小的语料库为例。请看下面给出的我们小语料库中的句子:
-
the dog saw a cat
-
the dog chased a cat
-
the cat climbed a tree
上述三个句子中有八个(8)独特的单词。我们需要将它们按字母顺序排列,如果我们要访问它们,那么我们将参考每个单词的索引。请参考下面的表格:
| Words | Index |
|---|---|
| a | 1 |
| cat | 2 |
| chased | 3 |
| climbed | 4 |
| dog | 5 |
| saw | 6 |
| the | 7 |
| tree | 8 |
所以,在这里我们V的值等于8。在我们的神经网络中,我们需要八个输入神经元和八个输出神经元。现在假设我们在隐藏层中有三个(3)神经元。那么,在这种情况下,我们的WI和WO的值定义如下:
-
WI = [V * N] = [8 * 3]
-
WO = [N * V] = [3 * 8]
在训练开始之前,这些矩阵,WI 和 WO,通过使用小的随机值进行初始化,这在神经网络训练中非常常见。仅为说明目的,我们假设 WI 和 WO 初始化为以下值:
WI =

WO =

图片来源: https://iksinc.wordpress.com
我们的目标是让神经网络学习单词 cat 和 climbed 之间的关系。换句话说,我们可以解释为当单词 cat 输入到神经网络时,神经网络应为单词 climbed 提供较高的概率。因此,在词嵌入中,单词 cat 被称为上下文单词,而单词 climbed 被称为目标单词。
输入向量 X 表示单词 cat,它将是 [0 1 0 0 0 0 0 0]t。注意,向量的第二个分量是 1。之所以这样,是因为输入单词 cat 在语料库的排序列表中排在第二位。以同样的方式,目标单词是 climbed,目标向量为 climbed 的表示将是 [0 0 0 1 0 0 0 0 ]t。
第一层的输入是 [0 1 0 0 0 0 0 0]t。
隐藏层输出 Ht 通过以下公式计算:
Ht = XtWI = [-0.490796 -0.229903 0.065460]
从前面的计算中,我们可以看出,由于采用了独热编码表示,这里隐藏神经元的输出类似于 WI 矩阵第二行的权重。现在我们需要对隐藏层和输出层进行类似的计算。隐藏层和输出层的计算定义如下:
HtWO = [0.100934 -0.309331 -0.122361 -0.151399 0.143463 -0.051262 -0.079686 0.112928]
在这里,我们的最终目标是获取输出层中各个单词的概率。通过输出层,我们生成反映下一个单词与输入上下文单词关系的概率。因此,数学表示如下:
概率 (wordk|wordcontext) 对于 k = 1...V
在这里,我们讨论的是概率,但我们的输出是一个向量集合,因此我们需要将输出转换为概率。我们需要确保最终输出层神经元的输出之和等于 1。在 word2vec 中,我们通过使用 softmax 函数将输出层神经元的激活值转换为概率。
Softmax 函数
在本节中,我们将讨论 softmax 函数。softmax 函数用于将输出向量转换为概率形式。我们使用 softmax 函数是因为我们希望将最后一层的输出转换为概率值,而 softmax 函数可以轻松地将向量值转换为概率值。这里,第k层神经元的输出将通过以下公式计算,其中 activation(n) 表示第n层输出神经元的激活值:

通过使用这个公式,我们可以计算语料库中八个单词的概率,概率值如下:
[ 0.143073 0.094925 0.114441 0.111166 0.149289 0.122874 0.119431 0.144800 ]
你一定在想,我是怎么得到这些概率值的。我使用了前面的 softmax 概率公式并生成了最终的概率向量。你可以在下面的代码片段中找到计算 softmax 函数的 Python 代码:
import numpy as np
def stablesoftmax(x):
"""Compute the softmax of vector x in a numerically stable way."""
shiftx = x - np.max(x)
exps = np.exp(shiftx)
return exps / np.sum(exps)
print stablesoftmax([0.100934,-0.309331,-0.122361,-0.151399, 0.143463,-0.051262,-0.079686, 0.112928])
给定的代码将生成以下输出向量:
[ 0.143073 0.094925 0.114441 0.111166 0.149289 0.122874 0.119431 0.144800 ]
如你所见,概率0.111166是为选定的目标词climbed计算的概率。我们知道,目标向量是 [0 0 0 1 0 0 0 0]t,因此我们可以通过预测来计算误差。为了生成预测误差或误差向量,我们需要从目标向量中减去概率向量,一旦我们知道了误差向量或误差值,就可以根据它调整权重。在这里,我们需要调整矩阵WI和WO的权重值。在网络中传播误差并重新调整WI和WO的权重值的技术被称为反向传播。
因此,训练可以通过从语料库中提取不同的上下文-目标词对来继续进行。这就是 word2vec 学习单词之间关系的方式,以便为语料库中的单词开发向量表示。
主要处理算法
Word2vec 有两个不同的版本。这些版本是 word2vec 的主要算法。请参见图 6.12:

图 6.12:word2vec 的版本
在本节中,我们将介绍主要的两种处理算法。这些算法的名称如下:
-
连续词袋模型
-
Skip-gram
连续词袋模型
在连续词袋模型(CBOW)算法中,给定目标词时,上下文是由多个词来表示的。回想一下我们在前面部分提到的例子,其中上下文词是cat,目标词是climbed。例如,我们可以使用cat和tree作为上下文词来预测目标词climbed。在这种情况下,我们需要改变神经网络的结构,特别是输入层。现在,我们的输入层可能不再是单一词的 one-hot 编码向量,而需要增加另一个输入层来表示词tree。
如果你增加上下文词的数量,那么你需要添加一个额外的输入层来表示每个词,并且所有这些输入层都与隐藏层相连接。参考 图 6.13:

图 6.13:CBOW 神经网络架构(图片来源: https://www.semanticscholar.org)
在这里,好的一点是计算公式保持不变;我们只需要计算其他上下文词的 Ht。
跳字模型
skip-gram(SG)模型颠倒了目标词和上下文词的使用。在这里,目标词作为输入以 one-hot 编码向量的形式输入到输入层。隐藏层保持不变。神经网络的输出层重复多次以生成所选数量的上下文词。
让我们以单词 cat 和 tree 作为上下文词,以单词 climbed 作为目标词为例。在 SG 模型中,输入向量将是单词 climbed 的 one-hot 编码词向量 [0 0 0 1 0 0 0 0 ]t,而这次,我们的输出向量应该是单词 cat 和单词 tree 的词向量。因此,输出向量应该是 cat 的 [ 0 1 0 0 0 0 0 0] 和 tree 的 [0 0 0 0 0 0 0 1]。参考 图 6.14 中的跳字模型结构:

图 6.14:跳字模型神经网络架构(图片来源: https://www.semanticscholar.org)
这次输出将不是一个单一的概率向量,而是两个不同的概率向量,因为我们有两个上下文词。在这里,误差向量将按照我们之前定义的方式计算。skip-gram 中的小变化是,所有输出层的误差向量会被加总,通过反向传播调整权重。这意味着我们需要确保每个输出层的矩阵 WO 的权重在训练过程中保持一致。
一些关于 word2vec 和其他技术背后的算法技术和数学将在下一节中解释。所以,准备好处理一些很酷的数学内容吧!
理解 word2vec 模型背后的算法技术和数学原理
本节非常重要,因为我们将在这里讨论在 word2vec 中使用的核心算法。到本节结束时,为了让你理解 word2vec 的概念,所有的秘密都将揭开。因此,本节将把 word2vec 的黑箱变成白箱。在这里,我还将包括数学部分,以便读者能够更好地理解核心概念。即使你不懂数学也不用担心,因为我将提供一些可能对你非常有用的资源。
理解 word2vec 算法的基本数学
首先,我们需要掌握一些基本的数学概念,以便更好地理解算法。我们所需的数学主题如下:
-
向量:向量具有大小和方向。因此,当我们在向量空间中绘制一个向量时,它不仅包含大小,还包含方向。你可以对向量执行基本的数学运算。
-
矩阵:矩阵是由数字或单词频率统计组成的网格。它具有行和列。我们可以通过计数矩阵所包含的行数和列数来定义矩阵的维度。你可以参考这个链接以获取更多关于矩阵的信息。
-
偏导数:如果一个函数包含多个变量,并且我们对该函数关于其中一个变量进行求导,并将其他变量保持不变,这就是偏导数的计算方法。偏导数在向量微积分中应用广泛。
-
偏导数链式法则:链式法则被定义为用于计算两个或更多函数复合的导数的公式。
现在让我们开始理解这些技巧。我已经将所有概念根据每个技巧使用的阶段,粗略分为三大类。
我列出了一些参考链接,你可以从中获得关于每个概念的深入理解。你可以访问这个链接:
你可以参考以下链接来了解向量:
emweb.unl.edu/math/mathweb/vectors/vectors.html.
www.mathsisfun.com/algebra/vectors.html.
你可以参考这个链接以获取更多关于矩阵的信息:
medium.com/towards-data-science/linear-algebra-cheat-sheet-for-deep-learning-cd67aba4526c.
通过使用这个链接,你可以看到一些基础示例:
mathinsight.org/partial_derivative_examples.
www.analyzemath.com/calculus/multivariable/partial_derivatives.html.
你可以参考以下链接来了解偏导数链式法则:
math.oregonstate.edu/home/programs/undergrad/CalculusQuestStudyGuides/vcalc/chain/chain.html.
www.youtube.com/watch?v=HOYA0-pOHsg.
www.youtube.com/watch?v=aZcw1kN6B8Y.
上述列表已经足够帮助理解本章中算法背后的数学原理。
词汇构建阶段使用的技巧
在从数据集中生成词汇时,你可以使用一种优化技术,其中有损计数法是最常用于 word2vec 模型的技术。
有损计数
有损计数算法用于识别数据集中那些频率计数超过用户设定阈值的元素。该算法以数据流作为输入,而不是数据集的有限集合。
使用有损计数法,你会定期从频率表中移除计数非常低的元素。最常访问的词几乎不会有低频计数,哪怕它们有,通常也不会长时间停留在那里。
在这里,频率阈值通常由用户定义。当我们设置参数min_count = 4时,我们会移除在数据集中出现次数少于四次的词,并且不再考虑它们。
在词汇构建阶段使用它
有损计数法非常有用,特别是当你有一个大型语料库并且不想考虑那些出现非常罕见的词时。
在这种情况下,有损计数法非常有用,因为用户可以设置一个最小词频作为阈值,这样频率低于该阈值的词将不会被包含在我们的词汇中。
所以,如果你有一个大型语料库,并且想优化训练速度,那么我们可以使用这个算法。
换句话说,你可以说通过使用这个算法,你缩小了词汇量,从而可以加速训练过程。
应用
除了 word2vec 之外,有损计数算法还用于网络流量测量和 Web 服务器日志分析。
在构建上下文阶段使用的技术
在生成词对上下文时,上下文构建器使用以下技术:
-
动态窗口缩放或动态上下文窗口
-
子抽样
-
剪枝
动态窗口缩放
如你所见,动态窗口缩放是上下文构建器的一部分。我们将看到它如何有用,以及在使用它时会产生什么样的影响。动态窗口缩放也被称为动态上下文窗口。
理解动态上下文窗口技术
在 word2vec 实现中,动态上下文窗口是一种可选的技术,可以用来生成更精确的输出。你也可以将这些技术视为超参数。
动态上下文窗口技术使用权重方案来衡量上下文词与目标词之间的关系。
所以这里的直觉是,离目标词近的词比那些离目标词远的词更为重要。
让我们看看在构建词对时它如何发挥作用。动态上下文窗口考虑到附近的上下文单词对预测目标单词更为重要。在这里,我们通过对实际窗口大小(1 到 L 之间)使用均匀抽样的加权方案。举个例子,假设上下文窗口大小为 5,现在上下文单词的权重是均匀分布的,因此,最接近的单词权重为 5/5,紧接着的上下文单词权重为 4/5,依此类推。所以,最终的上下文单词权重将为 5/5、4/5、3/5、2/5、1/5。因此,通过加权,你可以微调最终结果。
子抽样
子抽样也是我们在构建词对时使用的技术之一,正如我们所知,这些词对是样本训练数据。
子抽样是去除最频繁单词的一种方法。这项技术对去除停用词非常有用。
这些技术还会随机删除单词,而这些随机选择的单词在语料库中出现得更频繁。因此,被删除的单词频率超过某个阈值 t,它们的出现概率为 p,其中 f 标记了单词的语料频率,在我们的实验中使用 t = 10−5。请参考 图 6.15 中给出的方程:

图 6.15:子抽样概率方程
这也作为一种有用的超参数发挥作用,它非常有用,因为我们在去除语料库和上下文窗口中最频繁且不必要的单词,从而提升了训练样本集的质量。
剪枝
剪枝在我们使用上下文构建器为训练目的构建词对时也会使用。当你需要处理大量词汇时,如果你包含了不太频繁的单词,就需要将它们去除。你还可以通过使用 Python gensim 库中的 max_vocab_size 参数来限制词汇表的大小。
让我们看看剪枝在生成 word2vec 时对我们有多大用处。剪枝用于修剪训练样本的大小,并提升其质量。如果你不从数据集中剪除那些很少出现的单词,那么模型的准确性可能会降低。这是一种提升准确性的技巧。
神经网络使用的算法
在这里,我们将看看单个神经元的结构。我们还将深入了解这两个算法的细节,从而理解 word2vec 如何从单词生成向量。
神经元的结构
我们已经看过整体的神经网络结构,但我们还没有看到每个神经元的构成以及神经元的结构。因此,在这一部分中,我们将查看每个单独输入神经元的结构。
我们将查看以下结构:
-
基本神经元结构
-
训练单个神经元
-
单个神经元的应用
-
多层神经网络
-
反向传播
-
word2vec 模型背后的数学
在这一部分,我们将大量包含数学公式。如果你没有数学背景,不用担心。我会用简单的语言给你解释,让你明白每一部分在做什么。
基本神经元结构
请参阅 图 6.16;它展示了基本的神经元结构:

图 6.16:基本神经元结构(图片来源:Xin Rong)
图 6.16 显示了一个基本的神经元结构。这个结构并不新颖。该结构接受输入,并且也有权重作为输入,它们计算加权和。在这里,x1 到 xk 是输入值,w1 到 wk 是对应的权重。因此,加权和通过 图 6.17 中给出的以下方程来表示:

图 6.17:加权和方程
让我们通过一个例子来理解方程的使用。假设你有输入 x=[1 5 6] 和 w=[0.5 0.2 0.1],则加权和 u 等于 [1 * 0.5 + 5 * 0.2 + 6 * 0.1],所以我们的最终答案是 u = [0.5 + 1 + 0.6] = 2.1。这只是一个简单的例子,目的是给你一些关于给定方程实际运作的直观感受。以上是关于我们的输入。现在我们将讨论输出。
为了生成输出,我们可以从基本的神经元结构出发,得出我们的输出是加权和 u 的函数。在这里,y 是输出,f(u) 是加权和的函数。你可以看到在 图 6.18 中给出的方程:

图 6.18:输出 y 是加权和 u 的函数
在神经网络中,我们可以使用不同的可用函数,这些函数被称为 激活函数。这些函数如下所示:
-
阶跃函数
-
Sigmoid 函数
一些伟大的科学家表示,给定的函数是作为激活函数的不错选择。对于本章内容,我们不会深入探讨激活函数的细节,但我们将在 第九章,自然语言处理与生成任务中的深度学习 中详细讨论激活函数的所有细节。因此,我们将把这两个函数作为 word2vec 的激活函数。我们将使用阶跃函数或 sigmoid 函数中的一个,而不是同时使用这两个函数来开发 word2vec。请参阅 图 6.19 中的两个函数的方程:

图 6.19:激活函数,第一个是阶跃函数,第二个是 sigmoid 函数
在这里,f(u) 是阶跃函数,而 f(u) 也是 sigmoid 函数。
当我们画一个圆来表示神经元,类似于 图 6.11 中画的那样,这个圆包含了加权和和激活函数,这也是我们在 图 6.16 中表示虚线圆圈的原因。
在下一节中,我们将看到这些激活函数如何被使用,以及我们如何通过使用误差函数计算预测输出中的误差。我们将详细了解激活函数和误差函数的用法。所以让我们开始吧!
训练一个简单的神经元
现在是时候看看我们如何通过使用激活函数来训练单个神经元,并且让我们了解损失函数来计算预测输出中的误差。
主要思想被定义为误差函数,它实际上告诉我们预测中的错误程度;我们实际上试图使我们的误差值尽可能低。因此,换句话说,我们实际上正在尝试改进我们的预测。在训练过程中,我们使用输入并通过使用误差函数计算误差,并更新神经元的权重并重复我们的训练过程。我们将继续这个过程,直到我们得到最小误差率或最大、最好和准确的输出。
我们将要查看的两个最重要的概念如下所示:
-
定义误差函数(损失函数)
-
理解 word2vec 中的梯度下降
定义误差函数
在这里,我们的输入是向量X,词汇表x1到xk,我们的输出y是输出向量。因此,要计算误差E,我们需要定义误差函数或损失函数,我们使用的是 L2 损失函数。让我们从理解 L2 损失函数的基本概念开始,然后我们将看到它在 word2vec 中的用途。
在机器学习(ML)和深度学习(DL)中,有两种类型的损失函数被广泛使用。顺便说一下,我们将在即将到来的章节中查看 ML 和 DL,这些章节是第八章,自然语言处理(NLP)问题的机器学习 和 第九章,自然语言处理(NLP)和自然语言生成(NLG)问题的深度学习。有两种标准类型的损失函数,但在这里我们只关注最小二乘误差(L2),而在即将到来的章节中,这些章节是 第八章,自然语言处理(NLP)问题的机器学习 和 第九章,自然语言处理(NLP)和自然语言生成(NLG)问题的深度学习,我们将详细查看和比较这些误差函数。两种标准类型的损失函数分别是:
-
最小绝对偏差(L1)
-
最小二乘误差(L2)
最小二乘误差也被称为L2 损失函数。一般来说,损失函数希望在学习数据集时达到它们的最小化值,L2 损失函数也希望在误差值最小的情况下达到它们的值。因此,确切地说,L2 函数希望最小化估计值与现有目标值之间的平方差异。
在训练时单个神经元的结构如图 6.20所示:

图 6.20:训练时的单个神经元
所以,当我们尝试计算单个神经元的 L2 损失函数时,我们将使用 图 6.21 中给出的以下方程:

图 6.21:L2 损失函数方程(最小二乘误差函数)
这里,t 是目标向量值,y 是估计的向量值或预测的向量值,E 是误差函数。我们已经定义了我们的 L2 误差函数。我知道你一定很想知道我们将如何使用这个 L2 误差函数来得到最小的误差值,接下来我们需要理解梯度下降的概念。
理解 word2vec 中的梯度下降
现在,让我们理解一下我们将如何使用 L2 函数,以及它如何在实现准确输出中发挥作用。
正如我们之前所说,我们希望最小化这个函数值,以便准确预测目标值。为此,我们将对 图 6.21 中给出的 L2 函数方程对 y 进行偏导数。推导偏导数的过程,并通过使用这些推导来最小化误差值的过程,被称为 梯度下降。
在这种情况下,结果如 图 6.22 所示:

图 6.22:关于 y 的 L2 损失函数偏导数结果
我们知道输出 y 依赖于 f(u),而 f(u) 又依赖于输入权重值 wi。因此,我们需要应用链式法则来生成误差函数值。如果我们使用偏导数链式法则,我们将得到以下方程,这将在 word2vec 中非常有用。图 6.23 展示了偏导数链式法则的结果:

图 6.23:L2 误差函数的偏导数链式法则结果
在根据误差值计算 L2 损失函数之后,神经网络的输入权重将被更新,这种迭代过程将持续进行,直到我们达到最小的误差值或误差率。
到目前为止,我们一直在推导单个神经元的方程,因此了解这个单个神经元能为我们做什么将非常重要。这是我们接下来要讨论的重点。
单个神经元应用
我们已经学到了很多关于单个神经元的技术和数学内容,所以我真的想带你一起了解在 word2vec 模型中单个神经元的应用。让我们开始吧!
如果你想构建一个模型来识别哪些词是可食用的,哪些是不可食用的,那么我们可以使用单个神经元来构建该模型。这种应用程序将词汇分为可食用类别或不可食用类别,称为二元分类****任务。对于这种任务,神经元会接受 one-hot 编码的向量作为输入,单个神经元将学习哪些词与可食用物品相关,哪些与可食用物品无关。因此,它将生成一个查找表,如你在图 6.24中看到的那样:

图 6.24:单个神经元可以将词汇分类为可食用和不可食用类别
之所以能够如此轻松地构建这种应用,是因为当你不断提供 one-hot 词向量并使用 sigmoid 或阶跃函数作为激活函数时,它就变成了一个标准的分类问题,而这种问题可以通过数学方法轻松解决。我们在前面的部分中已经定义了这一点,因为对于可食用物品,输出向量具有相同的值,而对于不可食用物品,生成的向量代表相同类型的输出向量。最终,我们将能够构建查找表。这种标准的分类问题让我们想起了逻辑回归,我们在应用相同的逻辑回归概念,但这里我们使用的是单个神经元结构。
我们已经了解了足够的单个神经元结构,现在是时候探索多层神经网络了。接下来的部分将为你提供有关多层神经网络的信息。
多层神经网络
多层神经网络是我们在使用 word2vec 时采用的结构。该函数以 one-hot 编码的词向量作为输入,这个向量以及加权和会传递到隐藏层。通过使用激活函数(在这种情况下是 sigmoid 函数),从隐藏层生成输出,并将该输出传递到下一层,即输出层。我们在本章的具有两层的神经网络部分已经看过一个示例。在该部分,我们没有深入探讨数学方面的内容,因此在这里我将带你了解神经网络的数学原理。
所以,让我们通过数学方程式来表示我在前一段中告诉你的内容。参见图 6.25中的多层神经网络结构:

图 6.25:多层神经网络
现在,让我们看看给定神经网络的数学方程式。在这里,我将为你提供高层次的直觉,这将帮助你理解流程,并让你了解输入和输出函数的概念。请参阅图 6.26:

图 6.26:多层神经网络的数学方程式
输入和输出的流动如下所示:
-
图 6.24中的第一个方程是输入层的加权和,输入层的结果将传递到隐藏层。ui是输入层的加权和。隐藏层的激活函数由第二个方程给出。激活函数hi使用 Sigmoid 函数并生成中间输出。
-
隐藏层的加权和将传递到输出层,第三个方程显示了隐藏层加权和的计算。u'j是来自隐藏层的加权和,它将传递到输出层。yj使用来自隐藏层的加权和,即u'j,因为这里的激活函数也是 Sigmoid。
我们已经通过基本的数学表示法看到了输入和输出的流动。
现在,一个主要的问题是如何将这个结构用于训练 word2vec 模型,答案是,我们使用反向传播来训练模型,这将在下一节中讨论。
反向传播
我们已经看到如何使用 L2 损失函数计算误差,L2 损失函数旨在最小化估计值与真实目标值之间的平方差。我们将相同的概念应用于多层神经网络。因此,我们需要定义损失函数,并对其进行求导,以更新神经网络的权重,从而生成最小误差值。在这里,我们的输入和输出是向量。
请参考图 6.27查看神经网络结构,图 6.28展示了我们需要应用的方程来计算多层神经网络中的误差函数:

图 6.27:用于计算误差函数的多层神经网络
现在让我们来看一下神经网络进行推导和数学计算的过程。你可以看到方程如下所示,请参考图 6.28:

图 6.28:计算多层神经网络误差函数的方程
当你计算多层神经网络的误差函数时,你需要非常小心索引以及你正在计算哪个层的误差函数值。如图 6.28所示,我们将从输出层索引开始,反向传播误差到隐藏层,并更新权重。在第五个方程中,你可以看到我们需要计算输出层的误差函数,以便反向传播误差并更新输入层的权重。处理索引在多层神经网络中是一项具有挑战性的任务。但编码实现起来相当简单,因为你只需要写一个for循环来计算每一层的梯度。
现在,我们将把 word2vec 的所有数学方程和概念结合在一起,理解 word2vec 神经网络的最终数学部分。
word2vec 模型背后的数学原理
在本节中,我们将通过将所有前面的方程和概念结合起来,来看最后一部分数学内容,并以概率的形式推导最终方程。我们已经在前一节中看到了概念、基本直觉、计算和示例,Word2vec 神经网络层的详细信息。
word2vec 神经网络使用一个独热编码的词向量作为输入,然后将该向量值传递给下一层,即隐藏层,这实际上是加权和值,它作为输入传入隐藏层。最后的输出层生成词向量值,但为了理解输出,我们会将该向量转换为概率格式,借助 softmax 技术,我们还将输出词向量转换为概率格式。我们将在接下来的章节中看到用于从输出向量生成概率的不同技术,在那之前,只需将 softmax 视为一个神奇函数。
请参考 图 6.29 以理解 word2vec 神经网络背后的数学原理:

图 6.29:word2vec 模型背后的数学
在第一个方程中,我们可以看到输入词向量与权重的加权和,并在第二个方程中得到 h。我们将 h 与隐藏层词向量的加权和 v'wj 相乘。这里,权重和索引已经发生了变化。这次乘法得到了 uj。这里,uj 是激活函数。然后,我们将使用 uj 的值生成概率。因此,最终的方程就是 softmax 函数。让我们通过将第三个方程中的 uj 替换为输入和输出词向量格式来简化方程,最终得到最终方程。请参考 图 6.30:

图 6.30:word2vec 模型的最终概率方程
这次,我们的输出是一个 softmax 函数,因此为了使用反向传播更新权重,我们需要定义损失函数。因此,我们将在这里定义一个以 softmax 函数形式的损失函数,我们将使用 softmax 函数的负对数概率,然后执行梯度下降。请参见 图 6.31:

图 6.31:误差函数梯度下降形式的 softmax 函数负对数概率
在这里,我想大致说明输出向量值是如何被更新的。这是输出层的更新规则。你可以找到以下给出的方程。请参考 图 6.32:

图 6.32:更新输出向量的规则
在这个公式中,我们取原始输出向量,并减去输出节点的预测误差ej,h 是隐藏层的值。所以,这个公式的意思是,如果我们将单词climbed作为输入,并希望预测单词cat作为输出,那么我们如何更新单词climbed的向量,使其与单词cat的向量更加相似?简单来说,我们可以说,我们将单词climbed的部分向量加到单词cat的向量上,除此之外,我们还需要更新其他单词的输出向量,因为所有不作为目标词的单词也应更新其输出向量,使它们与目标词的相似度更低。
更新输入向量的规则也非常有用;更新输入向量的公式如下所示。请参阅图 6.33:

图 6.33:更新输入向量的规则
这个公式有点复杂。所以,这里,直觉是输入向量,它将从预测误差的加权和中减去。意思是,这次我们要更新输入向量cat。我们将以一种方式更新单词cat的向量,使其接近单词climbed的向量。在这里,词语的共现起着重要作用。
我们几乎完成了 word2vec 模型的数学部分。我们已经看到很多在 word2vec 模型中使用的数学公式。现在我们将讨论用于生成最终向量和预测概率的技术。
用于生成最终向量和概率预测阶段的技术
在这一部分,我们将看到如何生成最终的向量。我们还将使用一些启发式方法来高效地生成输出。所以,我们也会讨论这些启发式方法。
如我们之前所见,为了生成词向量,我们需要更新输入向量以及输出向量。假设我们词汇表中有一百万个词,那么更新输入和输出向量的过程将会非常耗时且低效。我们必须解决这个挑战。因此,我们采用一些优化方法来执行相同的操作,这些技术如下所示:
-
层次化 softmax
-
负采样
那么,让我们开始了解这些技术。
层次化 softmax
在层次化 softmax 中,我们不将每个输出向量映射到其对应的词,而是将输出向量视为二叉树的形式。请参阅图 6.34中的层次化 softmax 结构:

图 6.34:层次化 Softmax 结构
所以,在这里,输出向量并不是预测某个词的概率,而是在预测你在二叉树中要走的路径。也就是说,你要么访问这一分支,要么访问另一个分支。参见图 6.35:

图 6.35:使用层次化 softmax 外壳表示的预测路径
在这种情况下,考虑红色激活点向上(此处为浅灰色)和蓝色激活点向下(此处为深灰色),因此你可以看到,在这里,我们可以以较高的概率预测词juice。
在这里,优点是,如果你想反向传播一个错误,那么你只需要更新一个输出向量,并且错误将仅传播到在预测时被激活的三个节点。我们使用霍夫曼二叉树构建来生成二叉树。
负采样
负采样也是一种优化方法。在这种方法中,我们将更新输出词的向量,但不会更新其他所有词的向量。我们只从输出向量以外的词中取样。因此,我们从负样本集中的词中选择一个样本,这就是该技术被称为负采样的原因。
一些与 word2vec 相关的事实
这里有一些使用 word2vec 时你应该记住的事实:
-
到目前为止,你应该已经意识到,word2vec 使用神经网络,而这个神经网络并不是深度神经网络。它只有两层,但它非常有效地找出词语的相似性。
-
word2vec 神经网络使用简单的逻辑激活函数,不使用非线性函数。
-
隐藏层的激活函数是简单的线性函数,因为它直接将输入的加权和传递给下一层。
现在,我们已经看到了 word2vec 的几乎所有主要方面,接下来的部分我们将研究 word2vec 的应用。
word2vec 的应用
让我介绍一些实际应用,其中使用了 word2vec。它们包括:
-
依赖解析器在解析时使用 word2vec 生成更好、更准确的词语之间的依赖关系。
-
命名实体识别也可以使用 word2vec,因为 word2vec 在命名实体识别(NER)中非常擅长发现相似性。所有相似的实体可以聚集在一起,从而获得更好的结果。
-
情感分析利用它来保持语义相似性,从而生成更好的情感结果。语义相似性帮助我们了解人们用来表达意见的词组或单词,运用 word2vec 概念进行情感分析时,你可以生成更好的洞察力和准确性。
-
我们还可以构建一个通过使用写作风格预测一个人名字的应用。
-
如果你想用简单的统计方法高精度地进行文档分类,那么 word2vec 就是你需要的工具。你可以使用这个概念来对文档进行分类,而不需要任何人工标签。
-
单词聚类是 word2vec 的基本产品。所有具有相似含义的单词都会被聚集在一起。
-
Google 使用 word2vec 和深度学习来提升他们的机器翻译产品。
有许多应用场景可以使用 word2vec 的概念。在这里,我们将实现一些有趣的例子。我们将构建有趣的应用程序,并对其进行可视化,这样你就能以更好的方式理解这个概念。
简单例子的实现
在这一部分,我们将实现著名的 word2vec 示例,即将 woman 和 king 相加,man 相减,最终得到的向量表示 queen。
我们不会在我们的数据上训练 word2vec 模型,然后构建自己的 word2vec 模型,因为 Google 已经在大量数据上训练了他们的 word2vec 模型,并为我们提供了预训练模型。如果你想在这么多数据上复制训练过程,那么我们需要大量的计算资源,所以我们将使用 Google 提供的预训练 word2vec 模型。你可以从这个链接下载预训练模型:code.google.com/archive/p/Word2vec/。
点击此链接后,你需要进入标题为“预训练词和短语向量”的部分,下载名为 GoogleNews-vectors-negative300.bin.gz 的模型,并将其解压。
我们将使用 genism 库来构建我们著名的示例。
著名示例(king - man + woman)
我们将通过使用 gensim 库加载二进制模型并复制这个例子。如果你在自己的计算机上运行它,可能需要几分钟时间,所以不用担心,保持脚本运行即可。参考图 6.36中的代码片段:

图 6.36:示例代码片段 King - man + woman = queen
你可以通过点击这个 GitHub 链接查看代码:github.com/jalajthanaki/NLPython/blob/master/ch6/kingqueenexample.py
你可以参考图 6.37查看我们正在生成的输出:

图 6.37:示例输出 King - man + woman = queen
现在,如果你想使用 Google 提供的数据从头开始训练模型,可以通过以下链接下载训练数据集:code.google.com/archive/p/Word2vec/。进入名为 Where to obtain the training data 的部分并下载所有训练数据集,然后参考给出的 GitHub 链接 github.com/LasseRegin/gensim-Word2vec-model,你可以复制整个训练过程,但这将需要很长时间,因为这种训练需要大量的计算能力。
word2vec 的优点
正如我们所看到的,word2vec 是一个非常好的生成分布式相似度的技术。它还有其他优点,我在这里列出了:
-
word2vec 的概念非常容易理解。它们并不复杂,以至于你根本不知道背后发生了什么。
-
使用 word2vec 很简单,它有非常强大的架构。与其他技术相比,它的训练速度较快。
-
训练过程中的人力投入非常少,因为这里不需要人工标注的数据。
-
该技术适用于小规模数据集和大规模数据集。因此,它是一个易于扩展的模型。
-
一旦你理解了概念和算法,你也可以将整个概念和算法应用到你的数据集上。
-
它在捕捉语义相似性方面表现得非常出色。
-
由于这是一种无监督方法,人力投入非常少,因此它是一种节省时间的方法。
word2vec 的挑战
尽管 word2vec 概念非常高效,但也有一些你可能会觉得复杂或具有挑战性的地方。在这里,我将提出一些最常见的挑战。这些挑战列举如下:
-
word2vec 模型容易开发,但调试起来比较困难,因此调试能力是你在为数据集开发 word2vec 模型时面临的主要挑战之一。
-
它无法处理歧义。因此,如果一个词有多个含义,在现实世界中我们可以找到很多这种词汇,那么在这种情况下,嵌入将会在向量空间中反映这些含义的平均值。
word2vec 如何在现实应用中使用?
本节将让你了解哪些类型的 NLP 应用程序使用 word2vec,以及 NLP 应用程序如何使用这一概念。除此之外,我还将讨论一些社区中最常见的问题,帮助你在实际使用 word2vec 时有更清晰的认识。
自然语言处理(NLP)应用程序,如文档分类、情感分析等,都可以使用 word2vec 技术。特别是在文档分类中,word2vec 实现能为你提供更好的结果,因为它保持了语义相似性。
对于情感分析,我们可以应用 word2vec,它能让你了解单词如何分布在数据集中,然后你可以使用自定义参数,如上下文窗口大小、子抽样等。你应该首先生成 词袋(BOW),然后开始在该词袋上训练 word2vec,生成词向量。这些向量可以作为输入特征输入到机器学习算法中,从而生成情感分析的输出。
现在,讨论一下人们在尝试理解和使用 word2vec 技术处理自己数据集时常常提出的一些问题。
现在,让我们开始回答问题吧!
-
我们需要什么样的语料库?:word2vec 技术可以应用于文本数据集。因此,并没有什么特定的文本数据类型不能使用。因此,按照我的看法,你可以在任何数据集上应用 word2vec。
-
我应该始终去除停用词吗?:在谷歌的原始 word2vec 模型中,去除了一些停用词,例如
a被从 word2vec 中移除,但单词 the 没有被移除。因此,并不是强制要求你去除这些单词:- 这完全取决于你的自然语言处理(NLP)应用。如果你正在开发情感分析应用,那么你可以去除所有停用词,但如果你在开发机器翻译应用,那么你应该去除一些停用词,而不是全部。如果你使用 word2vec 来开发词语聚类以理解语言的语法,那么你不应去除任何词语。
-
我应该去除所有停用词吗?:这个问题与前一个问题相关。对此问题的直接回答是否定的。并不是每个 NLP 应用都强制要求盲目去除所有停用词。每个 NLP 应用都是不同的,因此你应该根据你正在构建的 NLP 应用来做决定:
- 如果你查看谷歌的原始 word2vec 模型,你会看到在该模型中,单词
a不在其中,这意味着表示单词a的向量不存在,但表示单词 the 的向量存在。
- 如果你查看谷歌的原始 word2vec 模型,你会看到在该模型中,单词
-
我们将加载原始的 Google word2vec 模型,并通过简单的代码行,查看一些关于停用词的事实。
请参考 图 6.38 中的代码片段:

图 6.38:展示停用词事实的代码片段
对于 the 的向量值输出,请参考 图 6.39:

图 6.39:单词 “the”的词向量示例值
查看输出,你可以看到 word2vec 的词汇表中不包含 a,如 图 6.40 所示:

图 6.40:word2vec 不包含单词 a
- 你不觉得我们在这里为每个单词生成了两个向量吗?:我想告诉你,我们确实为每个单词生成了两个向量。这样做的原因是,句子中的单词既是目标单词也是上下文单词,所以当一个单词作为目标单词出现时,我们会生成向量;当一个单词作为上下文单词出现时,我们也会生成向量。我们在最终输出中考虑目标单词的向量,但当然,你也可以使用这两个向量。如何使用这两个向量并从中产生有意义的结果,这真的是个百万美元的问题!
你应该在什么情况下使用 word2vec?
Word2vec 捕捉语义相似性;这是我们在处理前一个问题的答案时需要牢记的最重要的一点。
如果你有一个 NLP 应用,想要使用分布式语义,那么 word2vec 就是为你准备的!一些 NLP 应用将使用这个概念从 word2vec 模型生成特征和输出向量,或者类似地,向量将作为 ML 算法的输入特征使用。
你应该知道哪些 NLP 应用可以使用 word2vec。如果你了解应用列表,那么你就可以轻松决定是否应该使用它。例如,你可以使用 k-均值聚类进行文档分类;如果你希望文档分类能够携带一些语义属性,那么你也可以使用 word2vec。如果你想构建一个问答系统,那么你需要能够在语义层面区分问题的技术。由于我们需要一些语义层次的信息,我们可以使用 word2vec。
现在,我们已经了解了足够的概念和理论,所以我们将开始我们最喜欢的部分——编程,而这次真的很有趣。
开发一些有趣的东西
在这里,我们将训练我们的 word2vec 模型。我将使用的数据集是 权力的游戏 的文本数据。因此,我们的正式目标是开发 word2vec 来探索 冰与火之歌(来自《权力的游戏》) 中实体之间的语义相似性。好的部分是,我们还会在此基础上进行可视化,以更好地从实际操作中理解这个概念。原始代码的作者是 Yuriy Guts。我只是创建了一个代码封装器以便更好地理解。
我使用了 IPython notebook。基本的依赖包有 gensim、scikit-learn 和 nltk,用于在《权力的游戏》的文本数据上训练 word2vec 模型。你可以在这个 GitHub 链接上找到代码:
github.com/jalajthanaki/NLPython/blob/master/ch6/gameofthrones2vec/gameofthrones2vec.ipynb.
代码包含内联注释,你可以看到输出的代码片段。我们使用了 t-SNE 技术来减少词向量的维度,从而可以使用二维向量进行可视化。如果你想在内存为 2 到 4 GB 的普通计算机上运行 t-SNE,可能会花费较长时间。因此,为了成功运行 t-SNE 代码,你需要更多的内存,并且如果你的计算机内存有限,你可以跳过可视化部分。你可以查看可视化图像。一旦你将模型保存到磁盘上,就可以轻松使用它并生成输出。我已经在图 6.41到图 6.45中给出了示例输出。
你可以观察到这个词Stark的输出结果:

图 6.41:词汇相似度输出(对于单词 stark)
这是最近的词汇输出:

图 6.42:最近词汇的输出
现在我们将查看以下可视化输出图像:

图 6.43:使用 t-SNE 后,你可以在二维空间中可视化向量
现在我们将放大并尝试查看哪些词汇最终聚集在一起。
请查看以下图像,展示与 Kingsguard 相关的人物最终聚集在一起:

图 6.44:人名分组在一起
请查看以下图像,展示了食品产品被很好地分组在一起:

图 6.45:食品名称分组在一起
练习
我是哈利·波特的超级粉丝,因此在这个练习中,你需要从《哈利·波特》书中的文本数据生成 Word2vec。别担心数据集;我已经为你提供了,且它存储在这个 GitHub 链接:github.com/jalajthanaki/NLPython/tree/master/ch6/Harrypotter2vec/HPdataset
祝你好运,生成 HarryPotter2Vec!编码愉快!
word2vec 概念的扩展
word2vec 概念可以扩展到不同层次的文本。这一概念可以应用于段落级别或文档级别,除此之外,你还可以生成全球向量,这就是GloVe。我们将尝试理解这些概念。在这里,我们将对每个概念进行概述。
以下是使用 word2vec 概念构建的扩展概念:
-
Para2vec
-
Doc2vec
-
GloVe
Para2Vec
Para2vec 代表段落向量。段落向量是一种无监督算法,使用固定长度的特征表示。它通过从可变长度的文本片段(如句子、段落和文档)中派生出这种特征表示。
Para2vec 可以通过使用神经网络推导出来。大部分方面与 Word2vec 相同。通常,会考虑三个上下文词并将其输入到神经网络中。然后,神经网络会尝试预测第四个上下文词。在这里,我们尝试最大化对数概率,预测任务通常通过多类分类器来执行。我们使用的函数是 softmax。
请注意,在这里,上下文是固定长度的,通过在段落上使用滑动窗口生成上下文词。段落向量与从同一段落生成的所有上下文共享,但不会跨段落共享。
Para2vec 的优势在于能够从未标记的数据中学习预测单词,这样你就可以在没有足够标记数据集的情况下使用这些技术。
Doc2Vec
Doc2vec(文档向量)是 word2vec 的扩展。它学习关联文档标签和单词,而不是单词之间的关系。在这里,你需要文档标签。你能够使用固定长度的向量表示整个句子。这也是使用了 word2vec 的概念。如果你将带标签的句子输入神经网络,它就会在给定的数据集上执行分类任务。所以,简而言之,你给文本打标签,然后使用这个标记的数据集作为输入,并在这个数据集上应用 Doc2vec 技术。该算法将为给定文本生成标签向量。你可以在这个 GitHub 链接中找到代码:github.com/jalajthanaki/NLPython/blob/master/ch6/doc2vecexample.py
我使用了非常小的数据集,仅仅是为了让你对如何开发 Doc2vec 有一个直观的了解,因此我忽略了开发模型的准确性因素。你可以参考这个参考链接中给出的代码:github.com/jhlau/doc2vec
让我们看看 图 6.46 中的直观代码,并查看 图 6.47 中的输出片段:

图 6.47:doc2vec 的代码片段
你可能会看到以下输出:

图 6.48:示例输出
Doc2vec 的应用
让我们看看可以使用 Doc2vec 的应用场景:
-
文档聚类可以通过使用 Doc2vec 简单实现
-
我们可以对更大块的文本数据进行情感分析,我想你也可以考虑对一个非常大的文本块进行处理,并为这个大块生成情感输出
-
它还用于产品推荐
GloVe
GloVe 代表全局向量。GloVe 是一种无监督学习算法。这个算法生成单词的向量表示。在这里,训练是通过使用聚合的全局单词-单词共现矩阵和语料库中的其他统计信息来执行的,结果表示给你提供了单词向量空间中有趣的线性子结构。所以,共现矩阵是 GloVe 的输入。
GloVe 使用余弦相似度或欧氏距离来估计相似的单词。GloVe 给你带来新颖的视角,并证明了如果你取最近邻,你可以看到这些单词,它们在频繁使用上是非常罕见的。即使如此,GloVe 仍然能够将这些罕见的单词捕捉到相似的聚类中。让我们看一个著名的例子:
例如,当我们使用目标单词“青蛙”时,以下是最接近的单词:
-
青蛙
-
青蛙
-
蟾蜍
-
Litoria
-
Leptodactylidae
-
Rana
-
蜥蜴
-
Eleutherodactylus
另一个例子是与比较级和最高级形式相关的单词聚集在一起,如果你使用可视化工具,可以看到以下输出。参见图 6.48:

图 6.48:GloVe 著名示例的结果
我正在使用GloVe Python 库为你提供一个直观的 GloVe 实际例子。请参见以下 GitHub 链接中的代码:github.com/jalajthanaki/NLPython/blob/master/ch6/gloveexample.py
在我们开始之前,需要下载数据集,请执行以下命令:
wget http://mattmahoney.net/dc/text8.zip -P /tmp
unzip text8.zip
cd /tmp
sudo chmod 777 ./text8
sudo chown yoursystemusername:yoursystemusername ./text8
你可以在图 6.49中看到代码片段,并在图 6.50中看到输出:

图 6.49:GloVe 代码片段
以下是前面代码片段的输出:

图 6.50:GloVe 的示例输出
练习
这个练习更像是一个阅读练习。你应该阅读关于 Para2vec、Doc2vec 和 GloVe 的研究论文。除此之外,你还可以检查是否有任何方法可以为连续字符串(例如 DNA 模式)找到向量表示。这个练习的主要目的是让你了解研究工作是如何完成的。你也可以思考一些向量表示的其他方面,并尝试解决其中的挑战。
向量化在深度学习中的重要性
这更像是我与你的一次讨论。众所周知,计算机无法直接理解自然语言(NL),因此我们需要将自然语言输出转换为数值格式。我们有多种词嵌入技术,以及一些基本的统计技术,如索引、tf-idf、独热编码等。通过使用所有这些技术或其中的一些技术,你可以将文本输入转换为数值格式。你选择哪些技术完全取决于自然语言处理(NLP)应用。因此,转换自然语言输入为数值格式的背后有两个主要原因。基本上,这是因为计算机只能理解数值数据,所以我们必须将文本数据转换为数值数据,而计算机擅长对给定的数值数据进行计算。这是我在转换文本数据时想到的两个主要原因。
让我们了解一下什么是深度学习。在这里,我想给你一个简要的概念。不用担心;我们将在第九章《用于 NLU 和 NLG 问题的深度学习》中详细讨论。当一个神经网络有很多层时,我们称之为深度神经网络。当我们使用多层深度神经网络,并利用它们通过大量数据和计算能力开发 NLP 应用时,这就是深度学习。
现在让我们来谈谈向量化。向量化是一个坚实的数学概念,易于理解和处理。如今,Python 有很多优秀的库,当我们想处理高维度向量形式的数据时,它们让我们的生活变得更加轻松。深度学习范式在很大程度上依赖于向量和矩阵的概念,所以为了深入掌握深度学习,你应该了解向量和矩阵。处理视频或音频等输入数据的深度学习应用程序也使用向量。视频和图像被转换为稠密的向量格式,而当我们谈论文本输入时,word2vec 是生成单词向量的基本构建块。Google TensorFlow 将 word2vec 作为其基本构建块,并利用这些概念改进了 Google 机器翻译、Google 语音识别和 Google 视觉应用程序的效果。因此,向量和矩阵在处理和理解这些数据时,给了我们极大的自由度。
除此之外,我还想给你一些思考。我希望你关注我们如何改进处理文本的方式。毫无疑问,word2vec 是将单词转换为向量形式最简单且高效的方法之一,但我绝对鼓励那些对研究工作感兴趣的读者,扩展这一概念到他们的母语,或发挥创意,贡献出创新的技术,帮助 NLP 社区克服诸如词义歧义等挑战。嗯,这些就是我给你的所有思考!
概述
在本章中,我们已经了解了如何使用 word2vec 来发现语义。简单的向量化技术对我们帮助很大。我们已经看到了它的一些应用。我们也触及了 word2vec 模型的技术细节。为了让你更好地理解这个模型,我向你介绍了许多新的数学和统计学术语。我们将 word2vec 的黑盒转换成了 word2vec 的白盒。我还实现了基础和扩展的示例,以便更好地理解。我们使用了大量的库和 API 来开发 word2vec 模型。我们还看到了在深度学习中使用向量化的优势。然后,我们扩展了 word2vec 的理解,发展出了 para2vec、doc2vec 和 GloVe 的概念。
下一章将基本介绍规则驱动技术如何用于开发自然语言处理(NLP)应用,以及各种 NLP 应用如何使用一种简单却非常有效的技术,称为规则或逻辑,来开发基本且有效的原型。Google 在其机器翻译项目中使用了规则驱动技术,Apple 也采用了这种技术,最后但同样重要的是,Google 还使用规则驱动系统来制作他们自动驾驶汽车的早期原型。我们将讨论规则驱动系统及其架构。我们还将了解规则驱动的 NLP 应用架构是什么。我会为你提供一种思维方式,通过使用这种思维方式,你也可以为你的 NLP 应用制定规则。我们将实现基本的语法规则和基于模式的规则。我们还将从零开始开发一个基于模板的聊天机器人。
第七章:基于规则的自然语言处理系统
我们在第五章,特征工程与自然语言处理算法,以及第六章,高级特征工程与自然语言处理算法中,学习了如何利用语言学和统计学的概念来推导各种特征。为了开发一个自然语言处理应用,这些特征将作为输入传递给算法。这些算法将特征作为输入。正如你所知道的,我们将算法称为黑盒,它们执行某种魔法并给我们相应的输出。参考图 7.1,展示了我们至今为止的学习过程:

图 7.1:我们迄今为止学习的阶段
恭喜你,你已经学到了很多关于自然语言处理的知识,特别是关于自然语言理解(NLU)的知识!
现在是时候探索我们用来开发自然语言处理应用的算法了。我们将这些算法、技术或方法称为我们的黑盒,它们的逻辑就像是某种魔法一样。现在,是时候深入了解这些黑盒并理解其中的奥秘了。
自然语言处理应用的算法(实现技术或方法)可以分为两部分。参考图 7.2:

图 7.2:黑盒的算法、方法或实现技术
在本章中,我们将讨论基于规则的(RB)系统,以及第八章,自然语言处理问题的机器学习,和第九章,自然语言处理和自然语言生成问题的深度学习中的机器学习方法。
在本章中,我们将重点介绍基于规则的系统。我们将讨论以下主题:
-
了解 RB 系统
-
使用基于规则的系统的目的
-
RB 系统架构
-
了解 RB 系统开发生命周期
-
应用
-
使用 RB 系统开发自然语言处理应用
-
将 RB 方法与其他方法进行比较
-
优势
-
缺点
-
挑战
-
RB 系统的最新趋势
那么,让我们开始吧!
了解基于规则的系统
RB 系统也被称为知识基础系统。但首先,我们将了解 RB 系统的含义以及它为我们做了什么?通过这种方法可以实现哪些类型的自然语言处理应用?为了更好地理解,我将通过应用实例来解释这些概念。
RB 系统是什么意思?
基于规则的系统是通过使用现有的知识或规则,我们开发出这样一个系统,利用规则将现有的系统规则应用于语料库,并尝试生成或推理结果。参考图 7.3,这将为你提供关于 RB 系统的概念:

图 7.3:基于规则的系统输入/输出流程
简而言之,你可以说,RB 系统就是将现实生活中的规则或经验应用于可用语料库,按照规则操作信息,并得出某些决策或结果。在这里,规则是由人类生成或创建的。
RB 系统用于以有用的方式解释现有语料库(信息)。在这里,规则作为 RB 系统的核心逻辑。语料库是基于规则或知识进行解释的,因此我们的最终结果取决于这两个因素,一个是规则,另一个是我们的语料库。
现在,我将解释一种AI(人工智能)应用程序,以便理解 RB 系统的核心本质。
作为人类,我们每天都做非常复杂的工作以完成某些任务。为了完成任务,我们使用先前的经验或遵循规则来成功地完成任务。
举个例子:如果你在开车,你会遵循一些规则。你对这些规则有先前的了解。现在,如果你考虑自动驾驶汽车,那么这辆车应该反应并执行人类之前所做的整个任务。但汽车不能理解如何在没有驾驶员的情况下自动驾驶。开发这种无人驾驶汽车是相当复杂且具有挑战性的。
无论如何,你想要创造一辆无人驾驶汽车。你知道汽车需要学习很多规则才能像人类驾驶员一样表现得那么好。在这里,你面临一些主要挑战:
-
这是一种复杂的应用
-
汽车需要学习大量的规则和情况。
-
无人驾驶汽车的准确度应足够高,才能将其推向市场供消费者使用。
因此,为了解决这些挑战,我们遵循以下几个步骤:
-
我们首先尝试将问题陈述分解为问题的小块,这些小块是原始问题陈述的一个子集。
-
我们首先尝试解决问题的小块部分。
-
为了解决这个问题,我们正在尝试提出一些通用规则,这些规则帮助我们解决问题,并帮助我们实现最终目标。
对于我们版本的无人驾驶汽车,我们需要从软件的角度来思考。那么,汽车应该首先学习的步骤是什么?想一想!
这辆车应该学会看到并识别道路上的物体。这是我们汽车的第一步,我们定义了一些通用规则,汽车将使用这些规则来学习并决定道路上是否有物体?然后根据这个来行驶。当汽车看到路况时,应该保持什么速度?等等,(现在请使用基于规则的系统思考一会儿,不要考虑深度学习方面来解决这个步骤)。
对于任务的每一个小部分,我们尝试定义规则并将规则逻辑输入到 RB 系统中。然后,我们检查该规则是否在给定的输入数据上正确工作。我们还会在得到输出后衡量系统的性能。
现在,你一定在想这是一本关于 NLP 的书,为什么我要举一个通用 AI 应用的例子?原因在于,自动驾驶汽车的例子容易理解,大家都能理解。我想强调一些有助于我们理解拥有基于规则系统目的的观点。
让我们通过一个通用的例子来理解其目的:
-
这个自动驾驶汽车的例子帮助你识别出,有时一个人类很容易完成的任务,对于机器而言却是极其复杂的。
-
这类复杂任务需要非常高的准确性!我的意思是,非常高!
-
我们并不指望系统覆盖和学习所有的情况,但无论我们输入什么规则到系统中,它应该以最佳的方式学习这些情况。
-
在 RB 系统中,覆盖的场景较少,但系统的准确性应当很高。这正是我们所需要的。
-
我们的规则来源于现实生活中的人类经验或通过使用人类知识。
-
规则的开发和实施是由人类完成的。
所有这些要点帮助我们决定何时何地使用基于规则的系统。这引导我们定义拥有基于规则系统的目的。那么让我们进入下一部分,定义一个使用基于规则方法的经验法则,适用于任何 NLP 或 AI 相关的应用。
拥有基于规则系统的目的。
通常,基于规则的系统用于开发 NLP 应用和通用 AI 应用。我们需要回答一系列问题,以便清晰地了解基于规则的系统。
我们为什么需要基于规则的系统?
基于规则的系统尝试模仿人类专家的知识,用于 NLP 应用。这里,我们将讨论一些有助于你理解 RB 系统目的的因素:
-
可用的语料库规模较小。
-
输出过于主观。
-
专门领域的专家容易为特定领域生成一些专业规则。
-
机器仅通过观察少量数据很难生成专门的规则。
-
系统输出应当具有很高的准确性。
如果你想使用 RB 系统开发 NLP 应用,所有前述因素都非常关键。这些因素如何帮助你决定是否选择 RB 方法?
你需要问以下问题:
-
你是拥有大量数据还是少量数据?
- 如果你拥有少量数据,那么可以问下一个问题;如果你有大量数据,那么你有更多其他选择。
-
关于你要开发的 NLP 应用,它的输出是主观的还是通用的?
- 如果你拥有少量数据,并且你要开发的应用输出过于主观,而且你知道用少量数据,机器无法概括出模式,那么选择 RB 系统。
-
你要开发的 NLP 应用应该具有非常高的准确性:
-
如果你想开发的应用程序应该具有高精度,几乎与人类一样,且使用的是小数据集,那么请选择 RB 系统。
-
在这里,你还应该记住,RB 系统中的规则是由人类专家创建的。根据该系统生成输出,因此 RB 系统具有很高的准确性,但并不能涵盖所有情境。
-
上述问题定义了我们可以在什么样的情况下使用 RB 系统。如果我要总结之前的问题,我会这样描述:如果你有少量数据,并且知道你需要一个高精度的系统,其中人类专家能够轻松识别出各种情境来制定规则并预测其输出,而机器则很难自己准确地识别出通用规则,那么 RB 系统就是适合你的选择!RB 系统的输出应该模仿人类专家的经验。这就是选择 RB 系统的经验法则。
我们将在第九章,“自然语言处理和自然语言生成中的深度学习”中看到,当你有大量数据时,存在更好的方法。在本章中,RB 方法帮助我们生成非常准确的 NLP 应用。
哪种类型的应用程序可以使用 RB 方法,而不是其他方法?
正如我们之前定义的,RB 系统是在人工领域专家的帮助下开发的。在本节中,我们将举几个例子,帮助证明我们的经验法则:
-
假设我们想构建一个从英语到印度语料库的机器翻译系统,但这些语料库太小。翻译系统必须足够准确才能开发。我们需要同时掌握英语和古吉拉特语的人工专家。我们不想一次性解决所有翻译层次的问题,因此需要先解决小块问题,然后在开发的原型上构建其他部分。因此,在这里,我也会选择 RB 系统。你怎么看?
-
假设我们想开发一个英语语法纠错系统。假设我们有一小部分平行语料库(包含语法错误的文档和没有语法错误的相同文档),通过现有的语料库,我们需要制作一个准确的语法纠错应用,能够识别并纠正语法错误。那么,在这种应用场景下,你会选择哪种方法?想一想,然后给出你的答案!在这里,根据我们的经验法则,我会选择 RB 系统。
练习
-
如果你想开发一个基本的聊天机器人系统,你会选择哪种方法?
-
RB 方法
-
机器学习方法
-
-
如果你想预测给定句子的情感,您会选择哪种方法?
-
RB 方法
-
机器学习方法
-
混合方法
-
都不适用
-
如果你想开发一个基于规则的系统,需要什么资源?
现在你已经理解了为什么我们使用 RB 系统以及我们将其应用于哪些类型的应用程序。第三个重要方面是,如果我们想要为任何 NLP 或 AI 应用程序开发 RB 系统,我们需要什么?
现在我们需要考虑三个主要资源。请参见 图 7.4:

图 7.4:实现 RB 系统的资源
现在,让我们详细了解每个有助于定义 RB 系统组件的资源:
-
领域专家(人类专家/知识专家):为了使用 RB 系统开发应用程序,首先我们需要一位领域专家,一个几乎了解该领域所有知识的人。
假设你想建立一个机器翻译系统,那么你的领域专家可能是对源语言和目标语言的语言学有深入了解的人。他可以利用自己的专业知识和经验提出规则。
-
RB 系统的系统架构师(系统工程师):为了定义 RB 系统的架构,你需要一个团队或个人,具备以下专长:
-
领域的基本知识
-
设计系统架构的深厚知识或高水平经验
-
架构是 RB 系统中最重要的部分,因为架构是决定整个系统效率的关键组件之一。良好的 RB 系统架构设计将提供良好的用户体验、准确高效的输出,除此之外,它还将使得程序员和其他技术团队(如支持团队或测试团队)能够轻松工作。系统架构由系统工程师或系统架构师负责。
- 编码人员(开发人员或知识工程师)实现规则:一旦领域专家开发出规则并且系统架构已经设计好,那么编码人员或开发人员便会介入。编码人员就是我们的真正的忍者!他们使用编程语言实现规则并帮助完成应用程序。他们的编程技能是 RB 系统中不可或缺的一部分。编程可以使用任何编程语言或脚本语言,如 C、C++、Java、Python、Perl、Shell 脚本等。根据架构的不同,你可以选择其中任何一种语言,但不能在单一系统中使用多种语言,除非有精心设计的架构。
稍后在本章中,我们将深入了解架构部分的更多技术内容。
RB 系统架构
我将通过将其分为三个部分来解释 RB 系统的架构:
-
RB 系统作为专家系统的一般架构
-
用于 NLP 应用的 RB 系统的实际架构
-
自定义架构 - NLP 应用的 RB 系统
-
Apache UIMA(非结构化信息管理架构)为 NLP 应用提供的 RB 系统
作为专家系统的规则基础系统的一般架构
如果我们将我们的基于规则的系统描述为专家系统,那么这种基于规则的系统的架构将与图 7.5中的架构相同:

图 7.5:将 RB 系统视为专家系统时的架构
让我们详细看看架构中的每个组件:
-
领域专家:
- 如我们在前一节中所见,领域专家是那些对特定领域有专长的人,他们可以帮助我们生成规则来解决问题
-
开发者或知识工程师:
-
开发者使用由领域专家创建的规则,并通过编程技能将其转换为机器可理解的格式
-
开发者对专家创建的规则进行编码
-
通常,这种编码以伪代码的形式呈现
-
-
知识库:
-
知识库是专家可以放置所有规则的地方
-
领域专家可以添加、更新或删除规则
-
-
数据库或工作存储:
-
所有与元信息相关的规则可以放在工作存储中
-
在这里,我们可以存储规则、特殊场景、可用的列表、示例等
-
我们还会保存要应用规则的数据
-
-
推理引擎:
-
推理引擎是系统的核心部分
-
在这里,我们为规则编写实际的代码
-
当预定义的规则和条件与用户查询或我们给系统输入的数据集匹配时,规则会被触发
-
-
用户推理:
- 有时候,我们的最终用户也会提供一些条件来缩小结果范围,因此当我们的系统生成输出时,这些用户推理也会被考虑在内。
-
用户界面:
-
用户界面帮助用户提交输入,作为回报,他们会得到输出结果
-
这为我们的最终用户提供了一个互动环境
-
-
系统架构师:
-
系统架构师负责整个系统架构
-
系统架构师还决定什么是 RB 系统的最有效架构
-
我们已经看到传统的 RB 系统架构,现在是时候看到 NLP 应用的 RB 系统的实际架构了。
基于规则的系统在自然语言处理(NLP)应用中的实际架构
我已经描述了整体架构,现在我们将看到 RB 系统在 NLP 应用中的实际架构。参考图 7.6:

图 7.6:用于 NLP 应用的 RB 系统的实际架构
让我们详细看看架构中的每个组件。
我们在前一节中已经看到了一些部分,如领域专家、用户界面和系统工程师。所以,在这里,我们专注于新的组件:
-
基于知识的编辑器:
-
领域专家可能不知道如何编码
-
所以我们为他们提供了一个基于知识的编辑器,他们可以使用自然语言编写或创建规则
-
假设我们正在开发一个英语语法纠错系统,并且我们有一个语言学家,他知道如何创建规则,但不知道如何将规则编码。
-
在这种情况下,他们可以使用知识库编辑器来添加、更新或删除规则。
-
所有创建的规则都以普通人类语言的形式进行说明。
-
-
规则翻译器:
-
正如我们所知,所有规则都是以人类语言的形式表示的,因此我们需要将它们翻译或转换成机器可以理解的形式。
-
因此,规则翻译器就是定义规则伪逻辑并给出示例的部分。
-
假设我们考虑我们的语法纠错系统例子。在这里,我们的专家定义了一条规则:如果句子中有单数主语和复数动词,那么就将动词改为单数形式。
-
在规则翻译器中,已定义的规则被转换成这样的格式:如果句子
S有一个单数主语,词性标签为PRP$,而动词词性标签为VBP,则将动词更改为VBZ格式。为了理解规则,也给出了一些示例。
-
-
规则对象类:
-
这个规则对象类充当支持库的容器。
-
它包含各种先决条件库。
-
它有时还包含一个可选的对象类,用于库的优化,进而优化整个系统。
-
对于语法纠错系统,我们可以将解析器、词性标注器、命名实体识别(NER)等工具放入容器中,供规则引擎使用。
-
-
数据库或知识库: 一个数据库包含规则的元数据,例如:
-
已经使用了哪些支持库来自规则对象类?
-
规则的类别是什么?
-
规则的优先级是什么?
-
-
规则引擎:
-
这是核心部分,是 RB 系统的大脑。
-
通过使用规则翻译器、规则对象类和知识库,我们需要开发核心代码,该代码实际运行在用户查询或输入数据集上,并生成输出。
-
你可以使用任何适合你的应用程序及其架构的编程语言来编写代码。
-
对于我们的语法纠错系统,我们将在此阶段编写规则,最终代码将被放入规则引擎的代码库中。
-
这些是开发 NLP 的 RB 系统时有用的所有组件。现在你一定会有一些问题。我们可以根据需要改变系统的架构吗?这样可以吗?要回答这些问题,你需要参考下一部分内容。
定制架构 - 用于 NLP 应用的 RB 系统
根据不同 NLP 应用的需求,你可以更改架构或组件。在这种方法中,可以进行定制。如果你正在设计一个定制的 RB 系统架构,有一些需要注意的点。请问以下问题:
-
你分析和研究过这个问题以及已有的架构吗?
-
在进行定制之前,你需要对应用进行分析。如果存在现有系统,那么需要研究其架构,并从中提取优缺点。
-
花足够的时间进行分析。
-
-
你真的需要定制架构吗?
-
如果经过研究后,你觉得应用架构需要定制,那么写下你为什么真的需要它的原因。
-
列出你所列出的理由,并通过一系列问题帮助你的系统变得更好。如果答案是肯定的,那么你已经走在正确的轨道上。
-
-
它是否有助于简化开发流程?
-
新架构是否真的能帮助你的开发过程更好?如果是,那么你可以考虑使用该架构。
-
大多数时候,定义一个流畅的 RB 系统开发流程是具有挑战性的,但如果你新的定制架构能够帮助你,那将是非常好的事情。
-
这个流畅的过程是否真的稳定了你的 RB 系统?
-
-
它是否易于维护?
-
定制化架构可以帮助你更轻松、高效地维护系统。
-
如果你能将这个功能添加到定制架构中,那就太棒了!
-
-
它是否具有模块化?
- 如果它能提供 RB 系统的模块化,那么它将非常有用,因为你可以轻松添加、删除或更新某些模块。
-
它是否具有可扩展性?
- 借助新架构,你可以扩展系统。你也应该考虑到这一点。
-
它是否易于迁移?
-
如果有了定义的架构,团队应该能够轻松地将系统从一个平台迁移到另一个平台。
-
如果我们想将一个模块从一个系统迁移到另一个系统,那么技术团队和基础设施团队都应该能够轻松完成。
-
-
它是否安全?
- 系统安全是一个主要问题。新的架构如果需要的话,应该具备安全性和用户隐私保护功能。
-
它是否易于部署?
-
如果你未来想要部署一些变更,那么部署过程应该足够简便。
-
如果你想销售最终产品,那么部署过程应该足够简便,以减少你的工作量和时间。
-
-
从开发时间的角度来看,它是否节省时间?
-
使用该架构开发和实现 RB 系统应该是节省时间的。
-
架构本身不应占用太多的实现时间。
-
-
对我们的用户来说,它是否易于使用?
- 架构可能很复杂,但对于最终用户来说,它必须是用户友好且易于使用的。
如果你能采纳上述所有或大部分要点,那么尝试使用你认为最适合系统的架构,解决一小部分问题,然后最后再一次提出所有之前的问题,评估输出结果。
如果你仍然得到正面的答案,那么你可以放心了!在这里,设计没有绝对的对错;关键是找到最适合你的 NLP 应用的架构。
一个问答(Q/A)系统可以使用在图 7.7中展示的架构:

图 7.7:问答 RB 系统架构
你可以看到一种非常不同的架构。这个问答系统的方法是基于本体的 RB 系统。问题处理和文档处理是我们主要的规则引擎。在这里,我们并不考虑开发一个高级的问答系统。我们希望开发一个适合小孩子的问答系统,孩子们可以提问关于故事的问题,系统会根据规则和现有的故事数据返回答案。
让我们详细看看每个组件:
-
当用户提交问题时,解析器会解析问题。
-
使用解释器解析问题,将解析结果与知识库、本体和关键词词典进行匹配。
-
在这里,我们也应用了推理和事实。
-
我们从问题中提取一些事实,并通过查询分类和重构对用户问题进行分类。
-
之后,已经生成的事实和分类过的问题会被发送到文档处理部分,在那里事实会传递给搜索引擎。
-
答案提取是问答系统的核心 RB 引擎,因为它利用事实并应用推理技术,如前向链推理或后向链推理,来提取所有可能的答案。现在你可能想了解一下后向链推理和前向链推理。那么,在这里,我将为你提供一个简要的概述。在前向链推理中,我们从可用数据开始,使用推理规则从数据中提取更多事实,直到达成目标。该技术被用于专家系统中,以了解接下来可能发生的事情。而在后向链推理中,我们从一系列目标开始,倒推以找出哪些条件可能在过去发生,从而导致当前结果的产生。这些技术帮助我们理解为什么会发生这种情况。
-
一旦所有可能的答案生成完毕,它们将会返回给用户。
我心中有一个问题,想问你。
如果你开发一个问答系统,想选择什么类型的数据库?在继续之前,先思考一下!
我倾向于选择 NoSQL 数据库,而不是 SQL 数据库,背后有几个原因。系统应该 24 小时全天候对用户开放。在这里,我们非常关心我们的用户。用户可以随时访问系统,系统的可用性至关重要。因此,我希望选择 NoSQL 数据库。如果将来我们需要对用户的问题和答案进行一些分析,那么我们需要将用户的问题和系统的答案保存在数据库中。继续阅读以了解更多信息:
你可以选择自己的数据仓库或 NoSQL 数据库。如果你是 NoSQL 新手,可以通过这个链接了解 NoSQL:en.wikipedia.org/wiki/NoSQL;如果你对数据仓库这个词不熟悉,可以参考这个链接:en.wikipedia.org/wiki/Data_warehouse。这将帮助我们对用户进行分类,并能够做出真正对用户有意义的创意性改变。我们还可以为每个用户提供定制化的反馈或建议。
练习。
假设你正在开发一个语法纠正系统,你会设计什么样的系统架构?尝试在纸上设计它!让你的思路流露出来。
Apache UIMA - 用于 NLP 应用程序的规则引擎系统。
在本节中,我们将研究用于 NLP 应用程序的规则引擎系统中的一个著名框架。
Apache UIMA 最初是由 IBM 开发的,用于处理非结构化数据。你可以通过点击此链接查看更多详细信息:uima.apache.org/index.html
在这里,我想强调这个框架中的一些要点,它们将帮助你使用规则引擎方法构建自己的自然语言处理(NLP)应用程序。
以下是 UIMA 的一些特性:
-
UIMA 将为我们提供基础设施、组件和框架。
-
UIMA 内建了规则引擎(RB 引擎)和 GATE 库,用于对文本数据进行预处理。
-
以下工具作为组件的一部分可用,我列举了一些:
-
语言识别工具。
-
句子分割工具。
-
NER 工具
-
-
我们可以使用 Java、Ruta 和 C++ 编写代码。
-
这是一个灵活、模块化、易于使用的框架。
-
C/C++ 注释器还支持 Python 和 Perl。
UIMA 的应用/用途包括:
-
IBM Watson 使用 UIMA 来分析非结构化数据。
-
临床文本分析与知识提取系统(Apache cTAKES)使用基于 UIMA 的系统从医疗记录中提取信息。
使用 UIMA 的挑战包括:
-
你需要使用 Java、Ruta 或 C++ 编写规则。尽管为了优化,许多规则引擎(RB)系统使用 C++,但为 Ruta 招募到优秀的人才是一项具有挑战性的任务。
-
如果你是 UIMA 的新手,你需要一些时间来熟悉它。
了解规则引擎系统开发生命周期。
在本节中,我们将讨论规则引擎系统的开发生命周期。如果你以后想要开发自己的系统,这将对你有所帮助。图 7.8 描述了规则引擎系统的开发生命周期。这个图表非常直观,所以不需要额外的说明。
如果我们遵循规则引擎开发生命周期的各个阶段,那么生活将变得更轻松:

图 7.8:规则引擎系统开发生命周期。
应用程序。
在本节中,我将应用程序分为两个部分:一个是 NLP 应用程序,另一个是通用的 AI 应用程序。
使用规则引擎系统的 NLP 应用程序。
在这里,我们列出了一些使用基于规则系统的 NLP 应用:
-
句子边界检测:
-
对一般的英语写作来说,句子边界检测比较容易,但当你处理研究论文或其他科学文档时,它会变得更加复杂。
-
所以,手工制作的后处理规则将帮助我们准确识别句子边界。
-
Grammarly 公司也使用这种方法用于语法纠错系统。
-
-
机器翻译:
-
当我们想到机器翻译系统时,我们脑海中会想到谷歌神经机器翻译(GNMT)系统。
-
对于许多印度语言,谷歌曾经使用一个复杂的基于规则的系统与统计预测系统,因此他们有一个混合系统。
-
2016 年,谷歌推出了基于神经网络的机器翻译系统。
-
许多研究项目仍然使用基于规则的系统进行机器翻译,并且他们中的大多数都在尝试开发那些尚未开发的语言。
-
-
基于模板的聊天机器人:
-
如今,聊天机器人是市场上的新趋势和热潮。
-
它们的基本版本是一种基于模板的方法,在这种方法中,我们有一套已定义的问题或关键词,并将每个关键词的答案进行了映射。
-
这个系统的好处是匹配关键词。因此,如果你使用的是其他语言,但你的聊天信息中包含我们已定义的关键词,那么系统能够为你发送正确的响应信息。
-
坏的一面是,如果你犯了拼写错误,系统将无法以正确的方式做出响应。
-
我们将从零开始开发这个应用程序。我将在下一节中解释编码部分,所以请继续阅读并启动你的计算机!
-
-
语法纠错系统:
-
语法纠错系统也是通过使用规则来实现的。
-
在这个应用程序中,我们可以定义一些简单的规则,也可以定义一些非常复杂的规则。
-
在下一节中,我们将看到一些基本的语法纠错规则,我们将使用 Python 来实现这些规则。
-
-
问答系统:
-
问答系统也使用基于规则的系统,但这里有一个不同之处。
-
问答系统通过语义学来获取提交问题的答案。
-
为了将语义学融入其中,我们使用了基于本体的基于规则方法。
-
使用基于规则的系统的通用 AI 应用:
你已经看到了使用基于规则方法的 NLP 应用。现在,我们来看一下使用基于规则方法与其他技术结合的通用 AI 应用:
-
自动驾驶汽车或无人驾驶汽车:
-
在本章开始时,我举了自动驾驶汽车的例子来突出基于规则系统的目的。
-
自动驾驶汽车也使用了混合方法。许多大公司,从谷歌到特斯拉,都在尝试制造自动驾驶汽车,他们的实验旨在开发最值得信赖的自动驾驶汽车。
-
这个应用程序在初期是通过使用复杂的基于规则(RB)系统开发的。
-
然后,实验转向了机器学习技术的方向。
-
如今,许多公司正在应用深度学习技术以使系统变得更好。
-
-
机器人应用:
-
发展能够补充人类技能的机器人一直是人工智能社区的长期目标。
-
我们的目标是开发能够帮助人类完成工作、处理耗时任务的机器人。
-
假设有一个帮助你做家务的机器人。通过为所有可能的情况定义规则,机器人可以完成这种任务。
-
-
NASA 的专家系统:
- NASA 通过使用通用编程语言CLIPS(C 语言集成生产系统)开发了专家系统。
现在,我想理论部分已经足够了。现在我们应该尝试从零开始开发一些 RB 应用程序。准备好开始编码吧,我们将在下一节开始我们的编程之旅。
使用 RB 系统开发自然语言处理(NLP)应用程序。
在本节中,我们将看看如何使用 RB 系统开发 NLP 应用程序。我们将从头开始开发应用程序。因此,首先你需要以下依赖项。
你可以运行以下命令来安装所有的依赖项:
pip install -r pip-requirements.txt
依赖项的列表可以通过点击此链接找到:github.com/jalajthanaki/NLPython/blob/master/pip-requirements.txt
制定规则的思维过程:
我们谈论了很多关于规则的内容,但这些规则到底是如何推导出来的呢?当语言学家为 NLP 应用程序推导规则时,他们的思维过程是什么样的?那么,让我们从这个思维过程开始。
你需要像语言学家一样思考一段时间。记住你至今在本书中学到的所有概念,成为一名语言学家。
假设你正在为语法修正系统开发规则,特别是针对英语语言。因此,我将描述语言学家的思维过程,而这个思维过程将帮助你在开发规则时:
-
我需要知道什么?
-
你应该了解你所创建规则的语言的语法规则,这里的语言是英语。
-
你应该了解语言的结构、词序和其他语言相关的概念。
-
前面的两点是先决条件。
-
-
我应该从哪里开始?
-
如果你了解所有与语言相关的内容,那么你就需要观察和研究不正确的句子。
-
现在,当你学习不正确的句子时,你需要知道这些句子中有哪些错误。
-
之后,你需要思考错误的分类,错误是与语法相关,还是由于语义歧义引起的。
-
经过这一切后,你可以将你与语言相关的知识映射到句子中的错误上。
-
-
规则是如何推导的?
-
一旦你发现了句子中的错误,那么此时就要专注于你的思维过程。当你捕捉到错误时,你的大脑是如何反应的?
-
想一想你的大脑是如何对你已识别的每个错误作出反应的。
-
你能够捕捉到错误,因为你了解语言的语法事实或其他与语言相关的技术内容(句子语法结构、语义知识等)。你的大脑实际上在帮助你。
-
你的大脑知道如何使用给定的语言正确解读给定的文本。
-
这就是你能够发现错误的原因。与此同时,你有了一些坚实的理由;基于这些理由,你已经识别出错误。
-
一旦识别出错误,根据错误的不同类别,你可以通过使用某些逻辑规则来修改句子的部分内容,从而纠正错误。
-
你可以改变词序,或者你可以改变主谓一致,或者你可以改变一些短语,或者所有这些都一起改变。
-
Bingo!到此为止,你会得到你的规则。你知道错误是什么,你也知道这些步骤是用来将错误句子转化为正确句子的。
-
你的规则逻辑不过就是将错误句子转换成正确句子的步骤。
-
-
我需要注意哪些元素?
-
首先,你需要考虑一种非常简单的方式来修正错误或不正确的句子。
-
尝试制定基于模式的规则。
-
如果无法推导出基于模式的规则,那么检查你是否可以使用解析和/或形态分析器的结果,然后检查其他工具和库。
-
顺便说一下,这里有一个要点。当你定义规则时,还需要考虑规则逻辑在实现上的可行性。
-
是否有可用的工具?如果工具可用,你可以编写规则,或者开发者可以编写规则。
-
如果工具不可用,那么你需要放弃你的规则。
-
当你定义规则并检查是否有可供编码人员使用的工具时,研究就涉及其中。
-
选定的工具应该能够编码规则的特殊情况。
-
如果你的团队中有语言学家,定义规则和研究工具可能是语言学家的基本任务。如果没有,那么作为编码人员,你需要搜索可以用来编写规则逻辑的工具。
-
我们将毫不拖延地开始编码。
从简单的规则开始。
我编写了一个脚本,用于抓取标题为“编程语言”的维基百科页面。
点击这里打开该页面:en.wikipedia.org/wiki/Programming_language
从给定页面的文本中提取编程语言的名称是我们的目标。举个例子:该页面有 C、C++、Java、JavaScript 等编程语言。我想提取它们。这些词可能是句子的一部分,也可能独立出现在文本数据内容中。
现在,看看我们如何通过定义一个简单的规则来解决这个问题。脚本的 GitHub 链接是:github.com/jalajthanaki/NLPython/blob/master/ch7/7_1_simplerule.py
GitHub 上的数据文件链接是:github.com/jalajthanaki/NLPython/blob/master/data/simpleruledata.txt
在这里,我们的任务可以分为三个部分:
-
抓取文本数据
-
定义我们的目标规则
-
编写规则并生成原型和结果
抓取文本数据
在这个阶段,我们将从编程语言的维基页面抓取文本,并将内容导出到文本文件中。你可以在图 7.9中看到代码片段:

图 7.9:用于抓取文本数据的代码片段
抓取数据的输出结果如图 7.10所示:

图 7.10:抓取脚本的输出结果
定义我们的目标规则
现在,如果你查看我们抓取的数据,你可以找到这些句子。经过分析文本后,你需要定义一个规则,用来提取编程语言名称,如 Java、JavaScript、MATLAB 等。然后,想一想,什么样的简单规则或逻辑可以帮助你实现目标。仔细思考,给自己一些时间!集中精力在思考过程中,并尽力找出模式。
如果我要定义一个规则,那么我会在给定数据的上下文中概括我的问题。在分析过程中,我发现大多数编程语言的关键字都会带有单词“language”。我注意到,当句子中出现“language”这个单词时,实际的编程语言名称也很有可能出现在该句子中。例如,C 编程语言是由 ISO 标准指定的。在这个例子中,C 编程语言和单词“language”都出现在句子中。所以,我将执行以下过程。
首先,我需要提取包含单词“language”的句子。接下来,作为第二步,我将开始处理提取的句子,检查句子中是否有任何大写单词或驼峰式大小写单词。然后,如果我发现有任何大写单词或驼峰式大小写单词,我需要将它们提取出来,并把它们放入列表中,因为大多数编程语言都以大写单词或驼峰式大小写格式出现。比如,C、C++、Java、JavaScript 等等。也会有情况,一句话中包含多个编程语言名称。
上述过程就是我们的规则,规则的逻辑形式如下所示:
-
提取包含单词“language”的句子
-
然后尝试找出句子中使用驼峰式大小写或大写形式的单词
-
将所有这些单词放入列表中
-
打印列表
编写规则并生成原型和结果
这个示例给你提供了规则制定过程的实际精髓。这是我们的第一步,因此我们并不太关注准确性。我知道,这不是解决这个问题的唯一方法,也不是最有效的方法。还有其他更有效的方式可以实现同样的功能,但我使用这个方法是因为我觉得这是最简单且最易理解的解决方案。
这个示例可以帮助你理解规则如何编码,并在得到第一个原型的结果后,你可以采取什么步骤来改进输出。
请参见图 7.11中的代码片段:

图 7.11:提取编程语言规则逻辑的实现代码
前面代码片段的输出如下:
['A', 'Programming', 'The', 'Musa', 'Baghdad,', 'Islamic', 'Golden', 'Age".[1]', 'From', 'Jacquard', 'Thousands', 'Many', 'The', 'Some', 'C', 'ISO', 'Standard)', 'Perl)', 'Some', 'A', 'Some,', 'Traits', 'Markup', 'XML,', 'HTML,', 'Programming', 'XSLT,', 'Turing', 'XML', 'Moreover,', 'LaTeX,', 'Turing', 'The', 'However,', 'One', 'In', 'For', 'Another', 'John', 'C.', 'Reynolds', 'He', 'Turing-complete,', 'The', 'The', 'Absolute', 'The', 'These', 'The', 'An', 'Plankalk\xc3\xbcl,', 'German', 'Z3', 'Konrad', 'Zuse', 'However,', 'John', "Mauchly's", 'Short', 'Code,', 'Unlike', 'Short', 'Code', 'However,', 'At', 'University', 'Manchester,', 'Alick', 'Glennie', 'Autocode', 'A', 'The', 'Mark', 'University', 'Manchester', 'The', 'Mark', 'R.', 'A.', 'Brooker', 'Autocode".', 'Brooker', 'Ferranti', 'Mercury', 'University', 'Manchester.', 'The', 'EDSAC', 'D.', 'F.', 'Hartley', 'University', 'Cambridge', 'Mathematical', 'Laboratory', 'Known', 'EDSAC', 'Autocode,', 'Mercury', 'Autocode', 'A', 'Atlas', 'Autocode', 'University', 'Manchester', 'Atlas', 'In', 'FORTRAN', 'IBM', 'John', 'Backus.', 'It', 'It', 'Another', 'Grace', 'Hopper', 'US,', 'FLOW-MATIC.', 'It', 'UNIVAC', 'I', 'Remington', 'Rand', 'Hopper', 'English', 'The', 'FLOW-MATIC', 'Flow-Matic', 'COBOL,', 'AIMACO', 'The', 'These', 'The', 'Each', 'The', 'Edsger', 'Dijkstra,', 'Communications', 'ACM,', 'GOTO', 'The', 'C++', 'The', 'United', 'States', 'Ada,', 'Pascal', 'In', 'Japan', 'The', 'ML', 'Lisp.', 'Rather', 'One', 'Modula-2,', 'Ada,', 'ML', 'The', 'Internet', 'Perl,', 'Unix', 'Java', 'Pascal', 'These', 'C', 'Programming', 'Current', "Microsoft's", 'LINQ.', 'Fourth-generation', 'Fifth', 'All', 'These', 'A', 'Most', 'On', 'The', 'The', 'Since', 'Programming', 'Backus\xe2\x80\x93Naur', 'Below', 'Lisp:', 'Not', 'Many', 'In', 'Even', 'Using', 'The', 'C', 'The', 'Chomsky', 'The', 'Type-2', 'Some', 'Perl', 'Lisp,', 'Languages', 'In', "Lisp's", "Perl's", 'BEGIN', 'C', 'The', 'The', 'For', 'Examples', 'Many', 'Other', 'Newer', 'Java', 'C#', 'Once', 'For', 'The', 'There', 'Natural', 'A', 'Results', 'A', 'The', 'Any', 'In', 'In', 'The', 'A', 'For', 'The', 'Many', 'A', 'These', 'REXX', 'SGML,', 'In', 'High-level', 'BCPL,', 'Tcl,', 'Forth.', 'In', 'Many', 'Statically', 'In', 'In', 'Most', 'C++,', 'C#', 'Java,', 'Complete', 'Haskell', 'ML.', 'However,', 'Java', 'C#', 'Additionally,', 'Dynamic', 'As', 'Among', 'However,', 'Lisp,', 'Smalltalk,', 'Perl,', 'Python,', 'JavaScript,', 'Ruby', 'Strong', 'An', 'Strongly', 'An', 'Perl', 'JavaScript,', 'In', 'JavaScript,', 'Array,', 'Such', 'Strong', 'Some', 'Thus', 'C', 'Most', 'Core', 'The', 'In', 'However,', 'Indeed,', 'For', 'Java,', 'Smalltalk,', 'BlockContext', 'Conversely,', 'Scheme', 'Programming', 'But', 'A', 'By', 'While', 'Many', 'Many', 'Although', 'The', 'One', 'The', 'As', 'Because', 'This', 'Natural', 'However,', 'Edsger', 'W.', 'Dijkstra', 'Alan', 'Perlis', 'Hybrid', 'Structured', 'English', 'SQL.', 'A', 'The', 'The', 'A', 'An', 'There', 'It', 'Although', 'Proprietary', 'Some', 'Oracle', 'Corporation', 'Java', "Microsoft's", 'C#', 'Common', 'Language', 'Runtime', 'Many', 'MATLAB', 'VBScript.', 'Some', 'Erlang', "Ericsson's", 'Thousands', 'Software', 'Programming', 'When', 'However,', 'The', 'On', 'A', 'A', 'These', 'Programming', 'Programs', 'In', 'When', 'Unix', 'It', 'One', 'CPU', 'Some', 'For', 'COBOL', 'Fortran', 'Ada', 'C', 'Other', 'Various', 'Combining', 'C,', 'Java,', 'PHP,', 'JavaScript,', 'C++,', 'Python,', 'Shell,', 'Ruby,', 'Objective-C', 'C#.[70]', 'There', 'A', 'Languages', 'Ideas', 'The', 'For', 'Java', 'Python', 'In', 'Traditionally,', 'These', 'A', 'More', 'An', 'By', 'Some', 'A', 'For', 'English', 'Other']
如你所见,我们的基本规则提取了编程语言,但也提取了无关数据。现在,思考如何限制规则或添加一些约束,使其能够给出准确的输出。这将是你的任务。
练习
请通过添加一些约束来改进前面的输出(提示:你可以应用一些预处理,正则表达式也可以帮助你。)
用于校对应用程序的 Python 模式匹配规则
现在,假设你想制作一个校对工具。那么,在这里我将为你提供一个非常简单的错误,这个错误你可以在任何商务邮件或信件中轻松找到。然后我们将尝试以高准确率修正这些错误。
错误是,当人们在邮件中指定会议时间时,他们可能会指定为 2pm、2PM、2P.M. 或其他变体,但正确的格式应该是 2 p.m. 或 9 a.m.
这个错误可以通过基于模式的规则来修复。以下是规则逻辑。
假设长度为两位的数字从 1 到 12 开始。此数字后,如果出现am和pm且没有空格或没有句点,请添加空格和正确的句点符号。
我将使用正则表达式来实现它。
源模式:
\b([1-9]|0[1-9]|1[0-2]{1,2})(am)\b
\b([1-9]|0[1-9]|1[0-2]{1,2})(pm)\b
目标模式:
r'\b([1-9]|0[1-9]|1[0-2]{1,2})(am)\b', r'\1 a.m.'
r'\b([1-9]|0[1-9]|1[0-2]{1,2})(pm)\b', r'\1 p.m.'
你可以在 GitHub URL 上找到代码:github.com/jalajthanaki/NLPython/blob/master/ch7/7_2_basicpythonrule.py
代码片段见图 7.12:

图 7.12:基于模式的规则代码片段
前面代码片段的输出是:

图 7.13:基于模式的规则输出
给出的示例是一个基础示例,但它帮助你思考如何进行校对。许多简单的规则集合可以应用到数据上,并根据模式,你将获得修正后的结果。
练习
编写类似的规则,帮助修正时间格式 11:30am 或 5:45pm 为 11:30 a.m. 或 5:45 p.m.
语法修正
我们将为简单现在时的主谓一致规则做一个简单的定义。
我们知道在简单现在时中,第三人称单数主语总是使用单数动词,动词后缀为 s/es。
这里是一些不正确句子的示例:
-
他早晨喝番茄汤
-
她会做饭
-
我们在线玩游戏
我们不能对这些不正确的句子执行基于模式的修正。在这里,为了制定规则,我们将解析每个句子并尝试通过解析结果进行检查。我们能制定任何规则吗?我已经解析了句子并生成了解析结果,你可以在图 7.14中看到解析树。这个结果是使用 Stanford 解析器生成的:

图 7.14:示例句子的解析结果
我们需要首先提取NP,它通常带有代词PRP/NNP或名词NN。这个规则可以限制为仅提取PRP。我们可以从句子中提取PRP标签。之后,我们需要提取VP。通过使用代词类型和VP,我们可以向用户建议更改。我猜你们应该记得NP、PRP、NNP等。这些都是词性标记(POS 标签),如我们在第五章中所展示的,特征工程与 NLP 算法。
规则逻辑:
-
提取带有PRP标签的NP
-
提取VP
-
根据PRP,在VP中进行修正
让我们为此编写代码:
我已经安装了 Stanford-corenlp和pycornlp库。你们已经在第五章中学习了如何安装 Stanford 解析器的步骤,特征工程与 NLP 算法。你们将编写代码。所以,这是一个完整的代码挑战。我提供了一段代码,其中为你提取了PRP和VBZ/VBP的模式。你的任务是检查PRP与VBP/VBZ的组合是否正确。如果不正确,则发出警报。你可以在此处找到代码:github.com/jalajthanaki/NLPython/blob/master/ch7/7_3_SVArule.py
你可以在图 7.15和图 7.16中看到代码片段:

图 7.15:声明的 Stanford corenlp 服务器
我已经给你提供了代码,但你需要完成它:

图 7.16:我已经给你提供的代码,但你需要完成它
你可以在图 7.17中看到我未完成的代码输出:

图 7.17:我未完成的代码输出
基于模板的聊天机器人应用
在这里,我们将看到如何为一个聊天机器人应用构建核心引擎,该应用可以帮助贷款申请人申请贷款。我们生成 JSON 格式的输出,因此任何前端开发人员都可以将这个输出集成到网站中。
在这里,我使用了 Flask Web 框架,并为我们的聊天机器人每个问题创建了 Web 服务。
如果你想保存用户数据,你需要安装 MongoDB。MongoDB 的安装步骤在这个链接中:docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/
我在conversationengine.py文件中定义了函数。该文件在 GitHub 上的路径是:github.com/jalajthanaki/NLPython/blob/master/ch7/chatbot/customscripts/conversationengine.py
你可以在flaskengin.py文件中看到 Flask Web 引擎的代码。GitHub 链接是:github.com/jalajthanaki/NLPython/blob/master/ch7/chatbot/customscripts/conversationengine.py
整个文件夹和包的路径是:github.com/jalajthanaki/NLPython/tree/master/ch7/chatbot
代码流程
所以,我在conversationengine.py中编写了函数,根据你所提的问题生成 JSON 响应,这个 JSON 响应可以被前端开发团队用来在聊天机器人 UI 上显示消息。
然后,我使用 Flask 编写了一个 Web 服务,因此你可以在 JSON 中指定的 Web URL 上看到 JSON 响应。
conversationengine.py是核心规则引擎,包含手工编写的规则和代码。请参见图 7.18中的代码片段:

图 7.18:conversationengine.py的代码片段
在这里,我们使用了一个关键字列表和一个响应列表来实现聊天机器人。我还创建了自定义的 JSON 架构来导出对话。如果你来自 Web 开发背景,你可以编写 JavaScript,它将帮助你在前端显示这个 JSON,并展示图形用户界面。
现在,让我们看看图 7.19中的 Web 服务部分:

图 7.19:Flask Web 服务 URL 在flaskengin.py中定义
现在,要运行脚本并查看输出,按照以下步骤操作:
-
首先运行
flaskengin.py -
访问这个 URL:
http://0.0.0.0:5002/,你将看到Hello from chatbot Flask! -
你可以通过使用这个 URL 查看聊天机器人的 JSON 响应:
http://0.0.0.0:5002/welcomemsg_chat -
你可以在图 7.20中看到 JSON 响应:
![]()
图 7.20:聊天机器人的 JSON 响应
-
现在,我们正在向我们的用户提供建议,帮助他们分析他们期望的输入是什么。所以,在这里,你可以看到 JSON 属性
suggestion_message: ["Hi"]。因此,用户将看到一个标签为Hi的按钮。 -
如果你想跳转到下一页或下一个问题,可以使用
next_form_actionURL,并将用户参数放在msg = USER ARGUMENT之后。 -
例如,我正在
http://0.0.0.0:5002/welcomemsg_chat页面。现在,你可以阅读message_bot。它告诉你需要对机器人说Hi -
你可以像这样发送你的
Hi响应:http://0.0.0.0:5002/hi_chat?msg=Hi -
当你在这个 URL 上时:
http://0.0.0.0:5002/hi_chat?msg=Hi,你可以看到聊天机器人会询问你的名字,现在你需要输入你的名字。 -
要输入你的名字并跳转到下一个问题,你需要再次检查 URL 中
next_form_action属性的值 -
这里的值是
/asking_borowers_email_id?msg= -
你需要在
=符号后面输入你的名字,这样 URL 就变成/asking_borowers_email_id?msg=Jalaj Thanaki -
当你使用
http://0.0.0.0:5002/asking_borowers_full_name?msg=Jalaj%20Thanaki时,你可以看到聊天机器人提出下一个问题。 -
首先你需要运行脚本:
github.com/jalajthanaki/NLPython/blob/master/ch7/chatbot/flaskengin.py,然后你可以查看以下 URL:-
http://0.0.0.0:5002/welcomemsg_chat -
http://0.0.0.0:5002/hi_chat?msg=Hi -
http://0.0.0.0:5002/asking_borowers_full_name msg=Jalaj%20Thanaki -
http://0.0.0.0:5002/asking_borowers_email_id?msg=jalaj@gmail.com -
http://0.0.0.0:5002/mobilenumber_asking?msg=9425897412 -
http://0.0.0.0:5002/loan_chat?msg=100000 -
http://0.0.0.0:5002/end_chat?msg=Bye
-
如果你想在 MongoDB 数据库中插入用户数据,这是可能的,代码中有相关内容,但已被注释掉。
基于模板的聊天机器人优点
-
实现容易。
-
时间和成本高效。
-
使用案例在开发之前就已经确定,因此用户体验也会很好。
-
这是一种模式匹配方法,所以如果用户在对话中使用英语和其他语言,聊天机器人也能提供回答,因为它识别出用户提供的英语关键词,并且如果这些英语关键词与聊天机器人的词汇表匹配,聊天机器人就能给出回答。
基于模板的聊天机器人缺点
-
它不能处理未见过的使用案例
-
用户需要处理一个严格的对话流程
-
用户的拼写错误会给聊天机器人带来问题。在这种情况下,我们将使用深度学习
练习
为酒店房间预订客户支持服务开发一个基于模板的聊天机器人应用程序。开发一些问题和答案,并开发应用程序。
将基于规则的方法与其他方法进行比较
基于规则的方法是一种非常可靠的引擎,它为你的应用程序提供高准确性。当你将 RB 方法与 ML 方法或深度学习方法进行比较时,你会发现以下几点:
-
对于 RB 方法,你需要一个领域专家,而对于 ML 方法或深度学习方法,你不需要领域专家
-
RB 系统不需要大量数据,而 ML 和深度学习需要非常大量的数据
-
对于 RB 系统,你需要手动寻找模式,而机器学习(ML)和深度学习技术则会根据数据和输入特征为你自动寻找模式。
-
RB 系统通常是开发最终产品初步版本的一个不错方法,在实践中仍然广受欢迎。
基于规则系统的优点
使用 RB 系统有很多优点。优点如下:
-
可用性:系统对用户的可用性不是问题。
-
成本效益:该系统在最终结果方面具有成本效益且准确。
-
速度:你可以优化系统,因为你了解系统的所有部分。因此,在几秒钟内提供输出不是一个大问题。
-
准确性和较低的错误率:尽管不同场景的覆盖面较少,但 RB 系统涵盖的所有场景都能提供高准确性。由于这些预定义规则,错误率也较低。
-
降低风险:我们在系统准确性方面降低了风险。
-
稳定响应:系统生成的输出依赖于规则,因此输出响应是稳定的,这意味着它不能模糊不清。
-
与人类相同的认知过程:该系统为你提供与人类相同的结果,因为它是由人类手工制作的。
-
模块化:RB 系统的模块化和良好的架构可以帮助技术团队轻松维护。这减少了人工工作和时间。
-
一致性:RB 系统在其实现和输出方面非常一致。这使得最终用户的生活更轻松,因为系统的输出容易被人类理解。
-
易于实现:该方法模仿了人类的思维过程,因此规则的实施对开发人员来说相对容易。
基于规则系统的缺点
RB 系统的缺点如下:
-
大量手工工作:RB 系统要求对领域有深刻的了解,同时需要大量的手工工作。
-
耗时:为一个复杂系统生成规则是一个相当具有挑战性且耗时的过程。
-
学习能力较弱:在这里,系统将根据规则生成结果,因此系统本身的学习能力较差。
-
复杂领域:如果你要构建的应用程序过于复杂,构建 RB 系统可能会耗费大量时间和分析。复杂模式识别是 RB 方法中的一项挑战性任务。
基于规则系统的挑战
让我们来看一下 RB 方法中的一些挑战:
-
模仿人类行为并不容易。
-
选择或设计架构是 RB 系统的关键部分。
-
为了开发 RB 系统,你需要是特定领域的专家,能够为我们生成规则。对于自然语言处理(NLP),我们需要了解如何分析语言的语言学家。
-
自然语言本身就是一个具有挑战性的领域,因为它有许多例外情况,而使用规则覆盖这些例外情况也是一个具有挑战性的任务,尤其当你有大量规则时。
-
阿拉伯语、古吉拉特语、印地语和乌尔都语在 RB 系统中难以实现,因为为这些语言寻找领域专家是一项艰巨的任务。对于这些语言,实施规则的工具也较少。
-
人类努力的时间消耗太大。
理解词义消歧的基础知识。
词义消歧 (WSD) 是自然语言处理中的一个著名问题。首先,让我们了解一下什么是 WSD。WSD 用于在一个单词有多重含义的情况下,确定该单词在句子中的具体意义。当一个单词有多个含义时,机器很难识别正确的意义,解决这个挑战性问题的方法包括使用基于规则的系统或机器学习技术。
本章的重点是 RB 系统。因此,我们将了解 WSD 是如何被解决的。为了使用 RB 系统解决这个复杂问题,您可以采取以下步骤:
-
当你尝试解决任何语言的 WSD 时,你需要大量数据,其中包含多种不同情境下,单词含义会随句子变化的实例。
-
一旦你有了这种数据集,接下来就需要人类专家介入。
-
人类专家用于标注一个单词或多个单词的意义,通常这些标签都有一些预定义的 ID。现在,让我们举个例子:我有这样的句子:I went to river bank(我去了河岸),和 I went to bank to deposit my money(我去银行存钱)。
-
在前面的句子中,单词“bank”有多重含义,且其含义随着整体句子而变化。因此,需要人类专家来标注这些类型的单词。这里的单词是“bank”。
-
因此,人类专家使用预定义的 ID 标注“bank”在“river bank”(河岸)中的意义。假设此时的 ID 是 100。
-
在第二个句子中,单词“bank”通过使用预定义的 ID 被标注为金融机构。假设目前该 ID 是 101。
-
一旦这个标签被赋予,下一阶段就开始了,即选择基于规则的引擎或监督机器学习技术。
-
如果我们决定使用基于规则的系统,那么人类专家需要提出一定的模式或规则,帮助我们消除词义歧义。有时候,对于某些单词,专家可以通过使用解析结果或 POS 标签找到规则,但在大多数情况下,他们做不到。
-
如今,一旦标注完成,标注数据就作为输入,用于开发一个监督学习模型,帮助人类识别单词的词义。
-
有时候,仅靠基于规则的系统无法工作,单独的机器学习方法有时也无法帮你解决问题。根据我的经验,这是同样的情况。我认为混合方法会给你带来更好的结果。
-
在标注数据之后,我们应当构建一个规则库(RB)系统,它能够很好地处理已知的情况,同时也有一些情况我们无法定义规则。为了解决这些情况,我们需要构建一个机器学习模型。
-
你还可以使用向量化概念和深度学习模型来解决词义消歧(WSD)问题。通过使用深度学习来研究词义消歧,你的发现也可以作为一个研究课题。
讨论规则库系统的最新趋势
本节讨论的是当前市场如何使用规则库系统。许多人在不同的论坛上提出了许多问题,他们想了解规则库系统的未来。我想和你讨论一个重要问题,这个问题将帮助你了解 NLP 市场和规则库系统的未来趋势。我们将探讨一些问题。
规则库系统在自然语言处理(NLP)领域是否过时了?我想回答这个问题的答案是“没有”。规则库系统在所有 NLP 应用中得到了广泛应用,包括语法纠正、语音识别、机器翻译等!当你开始制作任何新的 NLP 应用时,这种方法是第一步。如果你想对你的想法进行实验,那么可以通过规则库方法轻松开发原型。进行原型开发时,你需要领域知识和基本的编码技能。你不需要了解高阶数学或机器学习技术。对于基础原型开发,你应该选择规则库系统。
深度学习和基于机器学习(ML)的方法能否取代基于规则库的系统?这个问题是一个相当开放性的问题。我希望在此提供一些事实,帮助你思考这个问题。如今,我们被大量数据淹没,并且拥有廉价的计算能力。人工智能行业及其基于人工智能的项目正在引起广泛关注。前面提到的两点使得深度学习和机器学习方法能够为自然语言处理(NLP)以及其他人工智能应用提供准确的结果。这些方法相比规则库系统需要更少的人工干预。这也是为什么许多人认为规则库系统不会被深度学习和基于机器学习的方法所取代。我认为规则库系统不会完全被取代,但它会与这些方法互为补充。那么,你可能会问,如何做到呢?我的答案是,我认为我们应该采用混合方法,这对我们更为有利。我们可以借助机器学习系统来发现模式或做出预测,然后将这些预测交给规则库系统,规则库系统可以验证这些预测,并为用户选择最佳方案。这实际上帮助我们解决了规则库系统的一个主要挑战——减少人工干预和时间。
对于前面的问题,没有绝对的对错答案。这完全取决于你如何看待这些问题和自然语言处理领域。我只是想给你留下一些思考,自己思考一下,尽量得出你自己的答案。
摘要
在这一章中,我们已经了解了与基于规则的系统相关的所有细节,以及基于规则的方法如何帮助我们为复杂问题开发具有高精度的快速原型。我们已经了解了基于规则的系统架构,学习了基于规则系统的优缺点和挑战。我们还看到,这个系统如何帮助我们开发自然语言处理(NLP)应用程序,例如语法纠错系统、聊天机器人等。我们还讨论了基于规则系统的最新趋势。
在下一章,我们将学习另一种主要方法——机器学习,用来解决 NLP 应用问题。接下来的章节将向你详细介绍开发 NLP 应用时需要使用哪些机器学习算法。我们将探讨监督学习、半监督学习和无监督学习技术,并从零开始开发一些应用。所以,继续阅读!
这次自动驾驶汽车考试
第八章:机器学习在 NLP 问题中的应用
我们已经看到了特征工程的基础和高级层面。我们还了解了基于规则的系统如何用于开发自然语言处理(NLP)应用程序。在本章中,我们将开发 NLP 应用程序,并且为了开发这些应用程序,我们将使用机器学习(ML)算法。我们将从机器学习的基础开始。接下来,我们将看到使用机器学习的 NLP 应用程序的基本开发步骤。我们主要会看到如何在 NLP 领域使用机器学习算法。然后,我们将进入特征选择部分。我们还将看看混合模型和后处理技术。
本章的大纲如下:
-
理解机器学习的基础
-
NLP 应用程序的开发步骤
-
理解机器学习算法和其他概念
-
NLP 应用的混合方法
一起探索机器学习的世界吧!
理解机器学习的基础
首先,我们将了解什么是机器学习。传统上,编程是关于定义所有步骤以达到某个预定结果。在编程的过程中,我们使用编程语言定义每一个细小的步骤,帮助我们实现目标。为了让你有一个基本的理解,我会举一个通用的例子。假设你想写一个程序来帮助你画一个面孔。你可能首先写绘制左眼的代码,然后写绘制右眼的代码,再写鼻子的代码,依此类推。在这里,你是在为每一个面部特征编写代码,但机器学习(ML)颠覆了这种方法。在机器学习中,我们定义结果,程序学习实现该结果的步骤。因此,取而代之的是为每个面部特征编写代码,我们向机器提供数百个人脸样本。我们期望机器能够学习绘制人脸所需的步骤,这样它就能画出一些新的面孔。除此之外,当我们提供新的人的面孔以及一些动物的面孔时,机器应该能够识别哪个面孔像是人的面孔。
我们来看一些常见的例子。如果你想识别某些州的有效车牌,在传统编程中,你需要写出车牌的形状应该是什么样的,颜色应该是什么,字体是什么等等。如果你试图手动为车牌的每个属性编写代码,这些步骤会非常冗长。使用机器学习,我们将向机器提供一些示例车牌,机器会学习步骤,从而能够识别新的有效车牌。
假设你想制作一个可以玩超级马里奥并且能够赢得比赛的程序。那么,定义每个游戏规则对我们来说太困难了。我们通常会定义一个目标,比如你需要在不死的情况下到达终点,机器学习将学习到达终点所需的所有步骤。
有时候,问题过于复杂,甚至我们自己也不知道应该采取哪些步骤来解决这些问题。例如,我们是一家银行,怀疑可能存在一些欺诈行为,但我们不确定如何检测这些行为,甚至不知道该寻找什么。我们可以提供所有用户活动的日志,并找到那些与其他用户行为不同的用户。机器通过自身学习来检测异常的步骤。
ML 无处不在于互联网上。每一家大型科技公司都在以某种方式使用它。当你观看任何 YouTube 视频时,YouTube 会更新并向你推荐你可能喜欢观看的其他视频。甚至你的手机也使用 ML 提供像 iPhone 的 Siri、Google 助手等功能。ML 领域目前正在迅速发展。研究人员使用旧的概念,改变其中的一些,或借用其他研究人员的工作,以使其更高效、更有用。
让我们来看一下传统的 ML 基本定义。1959 年,一位名叫 Arthur Samuel 的研究员赋予了计算机无需明确编程即可学习的能力。他将 ML 的概念从人工智能中的模式识别和计算学习理论中演化出来。1997 年,Tom Mitchell 给出了一个准确的定义,这个定义对于那些能够理解基础数学的人来说非常有用。Tom Mitchell 定义的 ML 是:如果一个计算机程序在某任务 T 上,通过经验 E 和性能评估 P 的衡量,其在 T 上的表现随着经验 E 的增加而提高,那么我们就说该程序已经从经验 E 中学习。
让我们将前面的定义与我们之前的示例联系起来。识别车牌号的任务称为任务T。你将使用车牌号的示例来运行一些 ML 程序,这些示例称为经验E,如果程序成功学习,那么它就能预测下一个未见过的车牌号,这个过程叫做性能评估P。现在是时候探索不同类型的 ML 以及它与 AI 的关系了。
ML 的类型
在本节中,我们将查看不同类型的 ML 以及一些有趣的子分支和超分支关系。
ML 本身源自于被称为人工智能的分支。ML 还有一个近年来备受关注的分支,叫做深度学习,但我们将在第九章中详细讨论人工智能和深度学习,深度学习在 NLP 和 NLG 问题中的应用。
学习技术可以分为不同的类型。在本章中,我们重点关注的是 ML。请参见图 8.1:

图 8.1:ML 与其他分支的子集与超集关系(图片来源: https://portfortune.files.wordpress.com/2016/10/ai-vs-ml.jpg)
ML 技术可以分为三种不同的类型,您可以在图 8.2中看到:

图 8.2:三种机器学习类型(图片来源:https://cdn-images-1.medium.com/max/1018/1*Yf8rcXiwvqEAinDTWTnCPA.jpeg)
我们将详细介绍每种机器学习类型。那么,让我们开始吧!
监督学习
在这种类型的机器学习中,我们将提供一个已标记的数据集作为输入给 ML 算法,算法知道什么是正确的,什么是错误的。在这里,ML 算法学习标签与数据之间的映射关系。它生成 ML 模型,然后生成的 ML 模型可以用来解决给定的任务。
假设我们有一些文本数据,其标签为垃圾邮件和非垃圾邮件。数据集中的每个文本流都有这两个标签之一。当我们应用监督学习算法时,它使用标记数据并生成一个 ML 模型,预测未见过的文本流是垃圾邮件还是非垃圾邮件。这就是监督学习的一个例子。
无监督学习
在这种类型的机器学习中,我们将提供一个未标记的数据集作为输入给 ML 算法。因此,我们的算法无法获得任何关于数据是否正确的反馈。它必须通过自身学习数据的结构来解决给定的任务。使用未标记的数据集更具挑战性,但也更为便捷,因为并不是每个人都有一个完美标注的数据集。大部分数据都是未标记的、杂乱无章且复杂的。
假设我们正在尝试开发一个摘要应用程序。我们可能没有总结出与实际文档相对应的文档摘要。那么,我们将使用原始数据和实际文本文档来为给定文档创建摘要。在这里,机器不会收到关于 ML 算法生成的摘要是否正确的任何反馈。我们还将看到计算机视觉应用的一个例子。对于图像识别,我们将一些卡通角色的未标记图像数据集输入到机器中,并期望机器学习如何分类每个角色。当我们提供一个未见过的卡通角色图像时,机器应能够识别该角色,并将图像放入正确的类别,这是由机器自身生成的。
强化学习
第三种机器学习(ML)类型是强化学习。在这种情况下,ML 算法不会在每次预测后立即给出反馈,而是当 ML 模型达到目标时才会生成反馈。这种学习方式通常用于机器人技术领域,以及开发智能机器人来进行游戏。强化学习与通过试错方法与环境交互的思想密切相关。请参见图 8.3:

图 8.3:强化学习与环境的交互(图片来源:https://devblogs.nvidia.com/parallelforall/wp-content/uploads/2016/04/aeloop-300x183.png)
为了学习基础知识,让我们举一个例子。假设你想做一个能击败人类的国际象棋机器人。这种类型的机器人只有在赢得比赛时才会收到反馈。最近,Google 的 AlphaGo 击败了世界上最强的围棋选手。如果你想了解更多,可以参考以下链接:
techcrunch.com/2017/05/24/alphago-beats-planets-best-human-go-player-ke-jie/.
在本书中,我们不会详细探讨这种类型的机器学习,因为我们的主要重点是 NLP,而不是机器人技术或开发游戏机器人。
如果你真的想深入学习强化学习(RL),你可以参加这个课程:
www.udacity.com/course/reinforcement-learning--ud600.
我知道你一定对不同类型的机器学习有所兴趣。所以,在阅读下一个段落时,请集中注意力。
对于监督学习,每个步骤或预测后都会收到反馈。而在强化学习中,只有当我们的模型达成目标时,我们才会收到反馈。在无监督学习中,即使我们达成了目标或我们的预测是正确的,我们也不会得到反馈。强化学习与现有环境进行互动,并使用试错法,而另外两种类型则不适用试错法。在监督学习中,我们使用有标签的数据,而在无监督学习中,我们使用无标签的数据,强化学习涉及多个目标和决策过程。你可以参考图 8.4:

图 8.4:监督学习、无监督学习和强化学习的比较(图片来源:http://www.techjini.com/wp-content/uploads/2017/02/mc-learning.jpg)
从这一部分开始,你将学习到很多新东西,如果一开始你不理解一些术语,别担心!请耐心一点;我会在本章中通过实际的例子解释每个概念。那么,让我们开始理解使用机器学习的 NLP 应用程序的开发步骤吧。
NLP 应用程序的开发步骤
在这一部分,我们将讨论使用机器学习算法开发自然语言处理(NLP)应用程序的步骤。这些步骤在不同领域之间有所不同。对于 NLP 应用程序,数据可视化并不像分析型应用程序那样起着至关重要的作用,而分析型应用程序的数据可视化会提供很多洞察力。因此,这会因应用程序和领域的不同而有所变化。在这里,我的重点是 NLP 领域和 NLP 应用程序,当我们查看代码时,我一定会回顾我在这里描述的步骤,帮助你将各个点连接起来。
我将开发步骤分为两个版本。第一个版本考虑的是这是你 NLP 应用开发的第一次迭代。第二个版本将帮助你了解在第一次迭代后,你可以考虑的可能步骤。请参考图 8.5:

图 8.5:NLP 应用开发步骤版本
第一次迭代的开发步骤
首先,我们将看一下在使用 ML 开发 NLP 应用的第一次版本时,一般可以用到的步骤。在我的解释过程中,我将参考图 8.6,以便你能更好地理解:

图 8.6:使用 ML 算法开发应用的第一次版本和迭代
我将解释每一个步骤:
-
这个版本的第一步是理解你的问题陈述、应用需求,或你正在尝试解决的目标。
-
第二步是获取你解决目标所需的数据,或者如果你已经有了数据集,那么尝试了解该数据集包含什么,以及你需要什么来构建 NLP 应用。如果你需要其他数据,那么首先问问自己:你能否借助现有的数据集推导出所需的子数据属性?如果可以,那么可能不需要获取新的数据集;如果不行,那么尝试获取一个可以帮助你开发 NLP 应用的数据集。
-
第三步是思考你想要什么样的最终结果,并根据这个结果开始探索数据集,进行一些基本分析。
-
第四步是在对数据进行一般分析后,可以对其应用预处理技术。
-
第五步是从预处理过的数据中提取特征,作为特征工程的一部分。
-
第六步是,使用统计技术可视化特征值。这是 NLP 应用的一个可选步骤。
-
第七步是为你的基准模型构建一个简单的基础模型。
-
最后但同样重要的是,评估基本模型,如果达到预期标准,那就很好;否则,你需要更多的迭代,并且需要遵循另一个版本,我将在下一节中进行描述。
第二次到第 n 次迭代的开发步骤
我们已经看过第一次迭代的步骤,现在我们将看看如何执行第二次迭代,以便提高我们的模型准确性和效率。在这里,我们还试图使我们的模型尽可能简单。所有这些目标将成为这个开发版本的一部分。
现在我们将看看第一次迭代后的步骤。为了基本理解,请参考图 8.7:

图 8.7:ML 构建周期
第二次迭代的一些基本步骤如下:
-
在第一次迭代后,你已经构建了一个模型,现在需要改进它。我建议你尝试不同的机器学习(ML)算法来解决同一个自然语言处理(NLP)应用,并比较其准确度。根据准确度选择最好的三个 ML 算法。这将是第一步。
-
作为第二步,通常你可以对每个选择的 ML 算法进行超参数调优,以获得更好的准确度。
-
如果参数优化对你帮助不大,那么你需要真正集中精力在特征工程部分,这将是你的第三步。
-
现在,特征工程有两个主要部分:特征提取和特征选择。因此,在第一次迭代中,我们已经提取了特征,但为了优化我们的 ML 模型,我们需要在特征选择上进行工作。我们将在本章后面详细介绍所有的特征选择技术。
-
在特征选择中,你基本上是选择那些真正重要的特征、变量或数据属性,这些特征对结果的生成至关重要或者贡献很大。因此,我们将只考虑重要的特征,去除其他的。
-
你还可以去除离群值,执行数据标准化,并在输入数据上应用交叉验证,这将有助于你改进 ML 模型。
-
在执行了这些技巧后,如果你没有得到准确的结果,那么你需要花一些时间推导出新的特征并使用它们。
-
你可以反复执行之前的所有步骤,直到你得到一个令人满意的结果。
这就是你可以开发 NLP 应用的方法。你应该观察你的结果,然后在下一次迭代中采取合理且必要的步骤。聪明地分析,思考所有问题,然后反复调整来解决它们。如果你没有彻底分析你的结果,那么反复调整永远不会帮助你。因此,保持冷静,明智地思考,并不断迭代。别担心;当我们使用 ML 算法开发 NLP 应用时,我们会回顾之前的过程。如果你是研究人员,我强烈建议你理解 ML 算法背后的数学原理,但如果你是初学者且对数学不太熟悉,你可以阅读 ML 库的文档。在这两者之间的同学,尝试理解数学原理并加以实施。
现在,是时候深入探索 ML 世界,学习一些真正出色的算法了。
理解 ML 算法和其他概念
在这里,我们将介绍 NLP 领域中最广泛使用的机器学习算法。我们将根据机器学习的类型来看不同的算法。首先,我们将从监督学习算法开始,然后是无监督学习算法,最后是半监督学习算法。在这里,我们不仅会了解每个算法,还会学习其背后的数学原理。我会尽量简化内容,以便那些没有强大数学背景的人也能理解算法背后的直观概念。之后,我们将看到如何将这些算法应用于实际开发一个 NLP 应用程序。我们将开发一个很酷的 NLP 应用,它将帮助你理解这些算法,而不会让你感到困惑。
那么,让我们开始吧!
监督学习(Supervised ML)
我们在本章前面看到过监督学习的简介。我们所看到和使用的所有技术和数据集都包含了它们的结果、输出或标签,这些内容已经在数据集中给定了。因此,这意味着每当你拥有一个带标签的数据集时,你可以使用监督学习算法。
在开始学习算法之前,我会先介绍两个监督学习算法的主要概念。这也将帮助你决定在解决 NLP 或其他数据科学相关问题时,选择哪个算法:
-
回归
-
分类
回归
回归是一个统计过程,用于估算变量之间的关系。假设你有一堆变量,想要找出它们之间的关系。首先,你需要确定哪些是因变量,哪些是自变量。回归分析可以帮助你理解因变量如何根据自变量的取值变化其行为或值。在这里,因变量依赖于自变量的值,而自变量的取值则不依赖于其他变量。
让我们通过一个例子来帮助你更清楚地理解。如果你有一个数据集,里面包含人的身高数据,你需要根据身高来预测体重,这就是监督学习,而你数据集里已经包含了年龄数据。因此,你有两个属性,也叫变量:身高和体重。现在,你需要根据给定的身高预测体重。那么,稍微思考一下,告诉我哪个数据属性或变量是因变量,哪个是自变量。我希望你有一些想法。现在让我来解答。这里,体重是因变量,它依赖于身高这个自变量。身高是自变量。自变量也被称为预测变量(s)。所以,如果你有因变量和自变量之间的某种映射关系,你就可以根据身高预测体重。
请注意,当我们的输出或因变量是连续值时,使用的是回归方法。在我们的示例中,体重可以是任何值,例如 20 公斤、20.5 公斤、20.6 公斤、60 公斤等等。对于其他数据集或应用,因变量的值可以是任何实数。参见图 8.8:

图 8.8:线性回归示例
分类
在本节中,我们将探讨监督学习的另一个主要概念,即分类技术。这也叫做统计分类。
统计分类用于为给定的新观察值确定类别。因此,我们有许多类别可以将新观察值归类。然而,我们不会盲目地选择任何类别,而是会使用给定的数据集,并根据这个数据集,尝试确定最适合新观察值的类别,并将我们的观察值放入这个类别或类中。
让我们从自然语言处理领域本身举个例子。你有一个数据集,里面包含一堆电子邮件,并且这些电子邮件已经有了分类标签,标签是垃圾邮件(spam)或非垃圾邮件(non-spam)。所以,我们的数据集分为两个类别——垃圾邮件和非垃圾邮件。那么,如果我们收到一封新邮件,我们能否将其分类为垃圾邮件或非垃圾邮件呢?答案是肯定的。为了对新邮件进行分类,我们使用我们的数据集和机器学习算法,给新邮件提供最合适的分类。实现分类的算法叫做分类器。有时,分类器这个术语也指由分类器算法实现的数学函数,这个函数将输入数据映射到某个类别。
请注意,这一点有助于你识别回归和分类之间的区别。在分类中,输出变量采用的是类标签,基本上是离散的或类别性的值。而在回归中,我们的输出变量则是连续的值。参见图 8.9:

图 8.9:为了直观理解,展示的分类可视化
现在我们已经对回归和分类有了一个大致的了解,让我们理解一些基本术语,这些术语我会在解释机器学习算法,特别是分类时不断使用:
-
实例: 这指的是输入,通常它们是以向量的形式存在。这些是属性的向量。在 POS 标注器示例中,我们使用了从每个单词派生的特征,并通过
scikit-learn的 APIDictVectorizer将它们转换为向量。这些向量值被输入到机器学习算法中,所以这些输入向量就是实例。 -
概念: 概念是指将输入映射到输出的一个函数。所以,如果我们有一封电子邮件内容,并且我们试图判断该电子邮件是垃圾邮件还是非垃圾邮件,我们必须关注输入或实例中的某些特定参数,然后生成结果。如何从某个输入中识别出特定输出的过程称为概念。例如,你有一些关于人类身高的数据,以英尺为单位。看到这些数据后,你可以判断这个人是高还是矮。在这里,概念或函数帮助你根据给定的输入或实例找到输出。所以,如果我用数学的形式来表达,那么概念就是一个世界中对象与集合中的成员之间的映射。
-
目标概念: 目标概念是指我们试图找到的实际答案、特定函数或某个特定的想法。作为人类,我们已经理解了很多概念,比如通过阅读电子邮件,我们可以判断它是垃圾邮件还是非垃圾邮件,如果你的判断是正确的,那么你就可以得到实际的答案。你知道什么是垃圾邮件,什么不是,但除非我们实际将其写下来,否则我们无法知道它是否正确。如果我们为数据集中的每个原始数据记录下这些实际答案,那么我们就可以更轻松地识别哪些电子邮件应被视为垃圾邮件,哪些不应被视为垃圾邮件。这有助于你为新的实例找到实际答案。
-
假设类: 假设类是所有可能的函数的集合,它们可以帮助我们对实例进行分类。我们刚刚看到了目标概念,我们正在试图找出一个特定的函数,但在这里我们可以考虑所有可能且潜在的函数的一个子集,这些函数可以帮助我们找到分类问题的目标概念。在这里,我想指出的是,我们看到的这个术语是针对分类任务的,因此不要考虑 x2 函数,因为它是线性函数,而我们正在进行的是分类,而不是回归。
-
训练数据集: 在分类中,我们试图找出目标概念或实际答案。那么,我们如何实际获得这个最终答案呢?为了使用机器学习技术得到最终答案,我们将使用一些样本集、训练集或训练数据集,帮助我们找出实际答案。让我们看看什么是训练集。训练集包含所有与标签配对的输入。监督式分类问题需要一个带有实际答案或实际输出标签的训练数据集。所以,我们不仅是向机器传授什么是垃圾邮件或非垃圾邮件,我们还为机器提供了很多例子,比如这是垃圾邮件,这是非垃圾邮件,等等。对于机器来说,这样就能更容易理解目标概念。
-
ML 模型: 我们将使用训练数据集,并将这些数据输入到 ML 算法中。然后,ML 算法将尝试通过大量训练示例学习概念,并生成输出模型。这个输出模型可以在以后用于预测或判断给定的新邮件是垃圾邮件还是非垃圾邮件。这个生成的输出被称为ML 模型。我们将使用生成的 ML 模型,输入新的邮件,这个 ML 模型将生成是否该邮件属于垃圾邮件类别或非垃圾邮件类别的答案。
-
候选: 候选是我们的 ML 模型告诉我们新示例的潜在目标概念。所以,你可以说候选是机器预测的目标概念,但我们并不知道这个预测或生成的输出——即候选——是否是正确答案。让我们举个例子。我们已经提供了很多邮件示例给机器,机器可能会概括垃圾邮件和非垃圾邮件的概念。我们提供一封新的电子邮件,ML 模型可能会说它是非垃圾邮件,然而,我们需要检查 ML 模型的答案是否正确。这个答案被称为候选。那么,如何检查 ML 模型生成的答案是否与目标概念匹配呢?为了回答这个问题,我将介绍下一个术语——测试集。
-
测试集: 测试集看起来与训练数据集相似。我们的训练数据集包含带标签的电子邮件,例如垃圾邮件或非垃圾邮件。所以,我会拿一个被认为是候选答案的结果,我们将检查在测试集中的答案是否是非垃圾邮件还是垃圾邮件。我们将把我们的答案与测试集中的答案进行比较,并尝试找出候选答案是正确的还是错误的。假设非垃圾邮件是正确答案。现在,你会拿到另一封电子邮件,ML 模型将再次生成非垃圾邮件答案。我们将再次与测试集核对,结果这次 ML 模型生成了错误的答案——邮件实际上是垃圾邮件,但 ML 模型错误地将其分类为非垃圾邮件。所以,测试集帮助我们验证我们的 ML 模型。需要注意的是,训练集和测试集不应相同。因为,如果你的机器使用训练数据集来学习概念,并且你在训练数据集上测试 ML 模型,那么你就没有公平地评估你的 ML 模型。这被认为是在 ML 中作弊。因此,训练集和测试集应该始终不同;测试集是机器从未见过的数据集。我们这么做是因为我们需要检查机器在多大程度上能将给定问题进行泛化。这里,泛化意味着 ML 模型如何应对未知和未见过的例子。如果你仍然感到困惑,那么让我给你另一个例子。你是学生,老师教了你一些事实,并给了你一些例子。起初,你只是死记硬背这些事实。为了检查你是否掌握了正确的概念,老师将给你一场测试,并给你一些新颖的例子,你需要应用所学的知识。如果你能完美地将所学知识应用到测试中的新例子上,那么你实际上已经掌握了概念。这证明了我们可以将老师教的概念进行泛化。我们和机器做的事情是一样的。
现在让我们来了解 ML 算法。
ML 算法
我们已经了解了 ML 的基本概念,现在我们将探索 ML 算法。首先,我们将了解在 NLP 领域中最常用的监督学习 ML 算法。我不会在这里涵盖所有的监督学习 ML 算法,但我会解释那些在 NLP 领域最广泛使用的算法。
在 NLP 应用中,我们主要通过应用各种 ML 技术进行分类。因此,在这里,我们的重点主要是算法的分类类型。其他领域,例如分析学,使用各种类型的线性回归算法,以及分析应用,但我们不会讨论这些算法,因为本书专注于 NLP 领域。由于一些线性回归的概念有助于我们理解深度学习技术,因此我们将在第九章中详细探讨线性回归和梯度下降,并通过实例展示,深度学习在 NLP 和 NLG 问题中的应用。
我们将使用各种算法开发一些自然语言处理应用程序,您可以看到算法是如何工作的,以及如何使用机器学习算法开发自然语言处理应用程序。我们将研究像垃圾邮件过滤这样的应用。
请参阅图 8.10:

图 8.10:我们将要理解的监督分类机器学习算法
现在让我们从核心的机器学习部分开始。
逻辑回归
我知道你一定很困惑,为什么我把逻辑回归放在分类类别里。让我告诉你,这只是给这个算法起的名字,但它用于预测离散的输出,因此这个算法属于分类类别。
对于这个分类算法,我将向您介绍逻辑回归算法的工作原理,并且我们将学习与之相关的一些基本数学知识。然后,我们将看看垃圾邮件过滤应用。
首先,我们将考虑二元类别,比如垃圾邮件或非垃圾邮件、好或坏、赢或输、0 或 1 等等,以理解算法及其应用。假设我想将电子邮件分类为垃圾邮件和非垃圾邮件。垃圾邮件和非垃圾邮件是离散的输出标签或目标概念。我们的目标是预测新的电子邮件是垃圾邮件还是非垃圾邮件。非垃圾邮件也称为ham。为了构建这个自然语言处理应用,我们将使用逻辑回归。
让我们先理解算法的技术细节。
在这里,我正在以非常简单的方式陈述与数学和这个算法相关的事实。理解这个算法的一般方法如下。如果你了解一些机器学习的知识,那么你可以连接这些点;如果你是机器学习的新手,那么不用担心,因为我们将逐步理解每个部分:
-
我们正在定义我们的假设函数,它帮助我们生成目标输出或目标概念。
-
我们正在定义成本函数或误差函数,并且我们选择误差函数的方式是使得我们可以推导出误差函数的偏导数,从而能够轻松计算梯度下降。
-
我们正在尝试最小化误差,以便生成更准确的标签,并准确分类数据。
在统计学中,逻辑回归也被称为logit 回归或logit 模型。这个算法通常作为二分类器使用,这意味着数据应该被分为两个不同的类别。二元逻辑回归模型用于估计二元响应的概率,并根据一个或多个预测变量、独立变量或特征生成响应。这也是一个在深度学习中使用基本数学概念的机器学习算法。
首先,我想解释一下为什么这个算法叫做逻辑回归。原因是该算法使用了逻辑函数或 S 型函数。逻辑函数和 S 型函数是同义词。
我们使用 Sigmoid 函数作为假设函数。假设函数是什么意思呢?嗯,正如我们之前所看到的,机器必须学习数据属性和给定标签之间的映射,以便它能够预测新数据的标签。如果机器能够通过一个数学函数学习这种映射,就能够实现这一目标。这个数学函数就是假设函数,机器将使用它来对数据进行分类并预测标签或目标概念。我们想构建一个二元分类器,所以我们的标签要么是垃圾邮件,要么是非垃圾邮件。因此,从数学上讲,我可以为非垃圾邮件赋值 0,为垃圾邮件赋值 1,或者反之。这些数学分配的标签就是我们的因变量。现在,我们需要让输出标签为零或一。从数学上讲,标签是 y 且 y ε {0, 1}。所以我们需要选择一个假设函数,将输出值转换为零或一。逻辑函数或 Sigmoid 函数正是做到了这一点,这也是为什么逻辑回归使用 Sigmoid 函数作为假设函数的主要原因。
逻辑或 Sigmoid 函数
逻辑或 Sigmoid 函数的数学方程如图所示:

图 8.11:逻辑或 Sigmoid 函数
你可以看到显示 g(z) 的图形。这里,g(z)= Φ(z)。参见 图 8.12:

图 8.12:Sigmoid 或逻辑函数的图形
从前面的图表中,你可以得出以下结论:
-
如果
z值大于或等于零,则逻辑函数给出输出值一。 -
如果
z值小于零,则逻辑函数生成输出零。
你可以看到逻辑函数的数学条件,如下所示:

图 8.13:逻辑函数的数学属性
我们可以使用这个函数来执行二元分类。
现在是时候展示这个 Sigmoid 函数如何作为假设函数来表示:

图 8.14:逻辑回归的假设函数
如果我们取前面的方程并将 z 的值替换为 θᐪx,那么 图 8.11 中的方程将转化为 图 8.15 中的方程:

图 8.15:数学操作后的实际假设函数
这里,h[θ]x 是假设函数,θᐪ 是特征或自变量的矩阵及其转置表示,x 是所有自变量或所有可能的特征集。为了生成假设方程,我们将逻辑函数的 z 值替换为 θᐪx。
使用假设方程,机器实际上尝试学习输入变量或输入特征与输出标签之间的映射。让我们稍微谈谈这个假设函数的解释。你能想出预测类别标签的最佳方法吗?据我看,我们可以利用概率概念预测目标类别标签。我们需要为两类生成概率,无论哪一类的概率较高,都将分配给该特定特征实例。在二元分类中,y 的值或目标类别为零或一。如果你熟悉概率,那么你可以表示在图 8.16中给出的概率方程:

图 8.16:使用概率表示假设函数的解释
因此,那些对概率不熟悉的人,P(y=1|x;θ ) 可以这样阅读 - 给定x,θ 参数化时,y =1 的概率。简单来说,你可以说这个假设函数将为目标输出1生成概率值,其中我们给出特征矩阵x和一些参数θ。稍后我们将看到为什么需要生成概率,以及如何为每个类别生成概率值。
在这里,我们已经完成了理解逻辑回归的一般方法的第一步。
逻辑回归的成本或误差函数
首先,让我们理解成本函数或误差函数。成本函数、损失函数或误差函数是机器学习中非常重要的概念,因此我们将理解成本函数的定义。
成本函数用于检查我们的机器学习分类器的精度如何。在我们的训练数据集中,我们有数据和标签。当我们使用假设函数并生成输出时,我们需要检查我们的预测与实际预测的接近程度。如果我们预测实际输出标签,则我们的假设函数输出与实际标签之间的差异为零或最小,如果我们的假设函数输出与实际标签不同,则它们之间存在很大差异。如果一个电子邮件的实际标签是垃圾邮件,即为 1,而我们的假设函数也生成结果 1,则实际目标值与预测输出值之间的差异为零,因此预测中的误差也为零。如果我们预测的输出为 1,而实际输出为零,则我们的实际目标概念和预测之间的误差最大。因此,对我们来说,预测中的误差最小非常重要。这是误差函数的非常基本的概念。我们稍后会讲到数学部分。有几种类型的误差函数可用,例如 r2 误差、平方误差和其他。根据机器学习算法和假设函数,我们的误差函数也会发生变化。
对于逻辑回归,误差函数将是什么?什么是 θ,如果我需要选择 θ 的某个值,我该如何接近它?那么,这里我将给出所有答案。
让我给你一些线性回归的背景知识。我们通常使用平方误差和残差误差作为线性回归中的代价函数。在线性回归中,我们试图为数据集生成最佳拟合直线。在前面的例子中,给定身高,我想预测体重。我们首先画一条线,并测量每个数据点到这条线的距离。我们将这些距离平方,求和并尝试最小化误差函数。参考图 8.17:

图 8.17:平方误差和残差误差表示图示
你可以看到,每个数据点到直线的距离通过小的垂直线表示。我们将这些距离平方后再求和。我们将使用这个误差函数。我们已经生成了一个关于直线斜率 m 和截距 b 的偏导数。在图 8.17中,我们的 b 大约是 0.9,而 m 大约是三分之二。每次,我们都会计算误差并更新 m 和 b 的值,以便生成最佳拟合直线。更新 m 和 b 的过程叫做梯度下降。通过使用梯度下降,我们更新 m 和 b 的方式使得误差函数的误差值最小化,从而可以生成最佳拟合直线。梯度下降给了我们需要绘制直线的方向。你可以在第九章《深度学习在自然语言处理和生成中的应用》中找到详细的例子。所以,通过定义误差函数并生成偏导数,我们可以应用梯度下降算法,帮助我们最小化误差或代价函数。
现在回到主要问题:我们能否将误差函数用于逻辑回归?如果你对函数和微积分非常了解,那么你的答案可能是否定的。那是正确的答案。让我为那些不熟悉函数和微积分的人解释一下。
在线性回归中,我们的假设函数是线性的,所以我们可以很容易地计算平方误差的总和,但在这里,我们将使用 Sigmoid 函数,这是一种非线性函数。如果你应用我们在线性回归中使用的相同函数,结果将不太理想,因为如果你把 Sigmoid 函数带入平方误差函数并尝试可视化所有可能的值,那么你将得到一个非凸曲线。参考图 8.18:

图 8.18:非凸和凸曲线(图片来源:www.yuthon.com/images/non-convex_and_convex_function.png)
在机器学习中,我们通常使用能够提供凸曲线的函数,因为这样我们就可以使用梯度下降算法来最小化误差函数并达到全局最小值。正如你在图 8.18中看到的,非凸曲线有许多局部最小值,因此要达到全局最小值非常具有挑战性并且耗时,因为你需要应用二阶或n阶优化才能达到全局最小值,而在凸曲线中,你可以肯定且快速地达到全局最小值。
因此,如果我们将我们的 sigmoid 函数代入平方误差之和,就会得到非凸函数,因此我们不会定义与线性回归中使用的相同的误差函数。
我们需要定义一个不同的代价函数,它是凸的,这样我们才能应用梯度下降算法并生成全局最小值。我们将使用统计学中称为似然的概念。为了推导似然函数,我们将使用图 8.16中给出的概率方程,并考虑训练数据集中的所有数据点。这样,我们可以生成以下方程,这就是所谓的似然函数。参见图 8.19:

图 8.19:逻辑回归的似然函数(图片来源:http://cs229.stanford.edu/notes/cs229-notes1.pdf)
现在,为了简化求导过程,我们需要将似然函数转换为单调递增的函数。这可以通过对似然函数取自然对数来实现,这就是对数似然。这个对数似然就是我们用于逻辑回归的代价函数。参见图 8.20中的以下方程:

图 8.20:逻辑回归的代价函数
我们将绘制代价函数并理解它为我们提供的好处。在x轴上,我们有我们的假设函数。我们的假设函数的范围是0到1,因此我们在x轴上有这两个点。从第一个情况开始,y = 1。你可以看到在图 8.21中右上方生成的曲线:

图 8.21:逻辑函数代价函数图
如果你查看任何对数函数的图像,它看起来像是误差函数y=0的图形。在这里,由于我们有一个负号,我们翻转了该曲线,得到的就是我们为y=1值绘制的曲线。在图 8.21中,你可以看到对数图形,以及在图 8.22中翻转后的图形:

图 8.22:对数(x)和-log(x)图形比较,以更好地理解代价函数(图片来源:http://www.sosmath.com/algebra/logs/log4/log42/log422/gl30.gif)
在这里,我们关注的是0和1的值,因此我们正在考虑我们在图 8.21中描绘的图形的那一部分。这个代价函数有一些有趣且有用的特性。如果预测标签或候选标签与实际目标标签相同,那么代价将为零。你可以将其表示为y = 1,如果假设函数预测为Hθ = 1,那么cost = 0;如果Hθ趋向于0,即如果它更接近零,那么代价函数将飙升至∞。
对于y = 0,你可以看到位于图 8.21左上角的图形。这个情况也具有我们之前看到的相同优点和特性。当实际值为0且假设函数预测为1时,代价将飙升至∞。如果假设函数预测为0且实际目标也是0,那么cost = 0。
现在,我们来看看为什么我们选择这个代价函数。原因是这个函数使我们的优化变得简单,因为我们将使用最大对数似然函数,因为它有一个凸曲线,这将帮助我们进行梯度下降。
为了应用梯度下降,我们需要生成关于θ的偏导数,并生成图 8.23中给出的方程:

图 8.23:执行梯度下降的偏导数(图片来源:http://2.bp.blogspot.com/-ZxJ87cWjPJ8/TtLtwqv0hCI/AAAAAAAAAV0/9FYqcxJ6dNY/s1600/gradient+descent+algorithm+OLS.png)
这个方程用于更新参数θ的值;
定义了学习率。这个参数用于设置你的算法学习或训练的速度。如果你设置的学习率过高,算法将无法学习;如果设置过低,训练将需要很长时间。所以你需要明智地选择学习率。
这是我们第二部分的结束,接下来我们可以开始第三部分,它更侧重于实现部分。你可以查看这个 GitHub 链接:
github.com/jalajthanaki/NLPython/blob/master/ch8/Own_Logistic_Regression/logistic.py
这实现了逻辑回归,你可以将其与给定的scikit-learn库中的实现进行比较。这里,代码归功于 Harald Borgen。
我们将使用这个算法进行垃圾邮件过滤。垃圾邮件过滤是自然语言处理(NLP)中的基本应用之一。使用这个算法,我们希望构建一个机器学习模型,将给定的邮件分类为垃圾邮件或正常邮件。接下来,让我们制作一个垃圾邮件过滤应用程序。完整的代码在这个 GitHub 链接上:
在垃圾邮件过滤中,我们将使用scikit-learn的CountVectorizer API 来生成特征,然后使用LogisticRegression进行训练。你可以在图 8.24中看到代码片段:

图 8.24:使用逻辑回归的垃圾邮件过滤
首先,我们进行一些基本的文本分析,帮助我们理解数据。在这里,我们使用scikit-learn的 API Count Vectorizer()将文本数据转换为向量格式。这个 API 在底层使用了词频-逆文档频率(tf-idf)。我们将数据集划分为训练集和测试集,以便检查我们的分类器模型在测试集上的表现。你可以在图 8.25中看到输出结果:

图 8.25:使用逻辑回归的垃圾邮件过滤输出结果
逻辑回归的优点
以下是逻辑回归的优点:
-
它能够处理非线性效应
-
它可以为每个类别生成概率得分,这使得解释变得简单
逻辑回归的缺点
以下是逻辑回归的缺点:
-
这种分类技术仅用于二分类。如果我们希望将数据分类到超过两个类别,我们可以使用其他算法,比如随机森林和决策树。
-
如果你为该算法提供大量特征作为输入,那么特征空间会增大,导致算法表现不佳
-
过拟合的风险较高,这意味着分类器在训练集上表现良好,但无法很好地推广,无法为未见过的数据预测正确的目标标签
现在是时候探索下一个算法——决策树了。
决策树
决策树(DT)是最古老的机器学习算法之一。这个算法非常简单,但又很强大。它为我们提供了一种树状结构来做决策。逻辑回归用于二分类问题,但如果你的数据集有超过两个类别,可以使用决策树。
通过一个例子来理解决策树。假设,Chris 喜欢风帆冲浪,但他有一些偏好——他通常喜欢阳光明媚和有风的日子来享受冲浪,而不喜欢雨天、阴天或者风力较小的日子。参考图 8.26:

图 8.26:理解概念的玩具数据集(图片来源:https://classroom.udacity.com)
如你所见,o(点)代表的是克里斯喜欢风帆冲浪的好天气条件,而x(叉)代表的是克里斯不喜欢风帆冲浪的坏天气条件。
我画出的数据是不可线性分割的,这意味着你无法仅使用一条直线将红色的叉号和蓝色的点分开。你可能会认为,如果目标只是将蓝色的点和红色的叉号分开,那么我可以使用两条线来实现这一点。然而,一条直线能分开蓝色的点和红色的叉号吗?答案是否定的,这就是为什么我告诉你这个数据集是不可线性分割的原因。所以在这种情况下,我们将使用决策树。
决策树到底能为你做什么呢?通俗来说,决策树学习实际上就是在问多个线性问题。让我们来理解一下线性问题的意思。
假设我们问一个问题:有风吗?你有两个答案:是或否。这个问题与风有关,因此我们需要关注图 8.26中的x轴。如果我们的回答是:是的,有风,那么我们应该考虑右侧区域,这里有红色的叉号和蓝色的点;如果我们回答:不,没有风,那么我们需要考虑左侧的所有红色叉号。为了更好地理解,你可以参考图 8.27:

图 8.27:问题的表示:有风吗?(图片来源:https://classroom.udacity.com/courses/ud120)
正如你在图 8.27中看到的,我画了一条通过* x 轴中点的线。我只是选择了一个中点,这没有特定的理由。所以我画了一条黑线。左侧红色的叉号表示:不,有风,右侧的红色叉号表示:是的,有风。在线的左侧,只有红色的叉号,没有一个蓝色点。如果你选择了“不”这个答案,那么实际上你会遍历标有“不”标签的分支。左侧的区域只有红色叉号,没有其他数据点,因此你会得到所有数据点都属于同一类别,即红色叉号表示的类别,对于该分支,你将不会继续提问。如果你选择了“是”,那么我们需要关注右侧的数据点。你可以看到那里有两种类型的数据点,蓝色的点和红色的叉号。所以,为了对它们进行分类,你需要提出一个线性边界,使得该线分隔出的区域内只有一种类型的数据点。我们将通过提问另一个问题来实现:今天是晴天吗?这时,你又有两个可能的答案——是或否。记住,我刚才已经遍历了第一个问题答案为“是”的树枝。所以我的关注点在数据点的右侧,因为那里有红色的叉号和蓝色的点。我们已经在 y 轴上描述了太阳,因此你需要查看那个轴,如果你画一条通过 y 轴中点的线,那么线上的上方表示“是,今天是晴天”。线下方的所有数据点则表示“不,今天不是晴天”。当你画出这样的线并停止扩展时,你就成功地将右侧的数据点分隔开来。所以,线上的上方只包含蓝色点,线下方则是红色叉号。你可以看到图 8.28*中的水平线:

图 8.28:灰色线表示基于第一个问题进行的分类
我们可以观察到,通过一系列的问题或线性问题,我们实际上对表示 Chris 不冲浪的红色叉号和表示 Chris 冲浪的蓝色点进行了分类。
这是一个非常基础的示例,可以帮助你理解决策树是如何处理分类问题的。在这里,我们通过一系列问题构建了这棵树,并生成了多个线性边界来对数据点进行分类。让我们来看一个数字示例,这样会更清晰。请参见图 8.29:

图 8.29:请看二维图中的数据点(图片来源:https://classroom.udacity.com/courses/ud120)
你可以看到给定的数据点。让我们首先从x轴开始。你希望在x轴上选择哪个阈值,以便为这些数据点获得最佳分割?想一想!我想选择一个通过x轴上 3 点的线。现在你有了两个区域。在数学上,我选择了给定数据点的最佳分割,即,x <= 3 和 x > 3。参考图 8.30:

图 8.30:参见第一个线性问题和决策边界图(图片来源:https://classroom.udacity.com/courses/ud120)
让我们先专注于左侧部分。你希望在y轴上选择哪个值,以便在绘制该线后一个区域中只有一种数据点?你选择的y轴阈值是多少,以便在一个区域中有一种数据集,而在另一个区域中有另一种数据集?我将选择通过y轴上的点 2 的线。因此,线上方的数据点属于一类,线下方的数据点属于另一类。在数学上,y <= 2 给出一类,y > 2 给出另一类。参考图 8.31:

图 8.31:参见第二个线性问题和决策边界图(图片来源:https://classroom.udacity.com/courses/ud120)
现在专注于右侧部分;对于那部分,我们也需要根据y轴选择一个阈值。这里,分隔边界的最佳阈值是y = 4,所以y < 4部分只有红叉,而y >= 4部分只有蓝点。最终,通过一系列线性问题,我们能够对数据点进行分类。参考图 8.32:

图 8.32:最终的线性问题和决策边界图(图片来源:https://classroom.udacity.com/courses/ud120)
现在你已经对算法有了一个概念,但你心中可能还有一些疑问。我们有一个获取线的可视化,但决策树算法如何选择最佳的方式来分割数据点,并使用给定的特征生成决策边界呢?假设我有超过两个特征,比如十个特征;那么决策树如何知道它需要在第一次使用第二个特征而不是第三个特征呢?因此,我将通过解释决策树背后的数学来回答所有这些问题。我们将看一个与自然语言处理相关的例子,这样你就可以看到决策树在自然语言处理应用中的使用。
我有一些与决策树相关的问题,我们一个个地回答它们。我们将使用可视化来获取线性边界,但决策树是如何识别应该使用哪些特征以及哪个特征值来分裂数据的呢?让我们来看一下熵这一数学术语。所以,决策树使用熵的概念来决定在哪里分裂数据。让我们来理解熵。熵是树枝中杂质的度量。所以,如果树枝中的所有数据点都属于同一类别,那么熵 E = 0;否则,熵 E > 0 且 E <= 1。如果熵 E = 1,则表示该树枝非常不纯,或者数据点在所有可用类别之间均匀分布。让我们看一个例子,以便你理解熵和杂质的概念。
我们正在制作一个垃圾邮件过滤应用程序,并且有一个特征,那就是词汇和短语类型。现在我们将引入另一个特征,即数据集中出现短语的最小阈值计数。请参见图 8.33:

图 8.33:熵讨论的图表
现在,聚焦右侧的图表。在这个图表中,右侧部分只有一种类型的数据点,使用红色叉号表示。严格来说,所有数据点都是同质的,因为它们属于同一类别。因此,没有杂质,熵的值大约为零。如果你聚焦于左侧图表并观察其右侧部分,你会发现这些数据点属于其他类别标签。这个部分有杂质,因此熵较高。所以,在实现决策树时,你需要找出可以用来定义分裂点的变量以及这些变量。另一个需要记住的事情是,你需要尽量减少数据中的杂质,因此应根据这个原则来进行数据分裂。稍后我们将看到如何选择变量来进行分裂。
现在,让我们首先来看一下熵的数学公式。请参见图 8.34:

图 8.34:熵的数学公式(图片来源:http://dni-institute.in/blogs/wp-content/uploads/2015/08/Entrop.png)
我们来看一下这里的 pi 是什么。它是给定类别的比例值。假设 i 是类别,T 是可用类别的总值。假设你有四个数据点,如果其中两个点属于类别 A,另外两个属于类别 B,那么 T = 2。我们通过使用比例值生成对数值后进行求和。现在是时候进行熵的数学计算了,接下来我将告诉你如何使用熵来对变量或特征进行分裂。我们来看一个计算熵的例子。你可以在图 8.35中找到该例子的相关数据:

图 8.35:垃圾邮件过滤计算的数据显示集
如果你专注于Filtering列,你会看到有两个标签值为Spam,两个标签值为Ham——即SSHH。现在请回答以下问题:
-
我们有多少行数据?答案是四行
-
数据标签
S在Filtering列中出现了多少次?答案是两次 -
数据标签
H在Filtering列中出现了多少次?答案是两次 -
为了生成类标签
S的分数值,你需要使用以下公式进行计算:- pS = S 出现的次数 / 数据总行数 = 2/4 = 0.5
-
现在我们还需要计算
S的p值:- pH = H 出现的次数 / 数据总行数 = 2/4 = 0.5
-
现在我们已经拥有了所有生成熵所需的值。请关注图 8.34中的公式:
- 熵 = -pS log2 -pHlog2 = -0.5 * log(0.5) -0.5log(0.5) = 1.0*
-
你可以使用 Python 的 math 模块来进行这个计算
如你所见,我们得到的熵值是E = 1。这是最不纯的状态,其中数据在可用类别中均匀分布。因此,熵值告诉我们数据的状态是否为不纯状态。
现在我们来看最受期待的问题:我们怎么知道在哪个变量上,或者使用哪个特征进行分割呢?要理解这一点,我们需要了解信息增益。这是决策树算法的核心概念之一。让我介绍信息增益(IG)的公式:
信息增益(IG) = 父节点熵 - [加权平均] 子节点熵
现在我们来看一下这个公式。我们正在计算父节点的熵,并减去子节点的加权熵。如果我们对父节点进行分割,决策树会尝试最大化信息增益。通过使用 IG,决策树会选择我们需要进行分割的特征。这个计算会对所有可用的特征进行,所以决策树会准确知道在哪个位置进行分割。你需要参考图 8.33。
我们已经计算了父节点的熵:E (父节点) = 1。现在我们将专注于单词,并计算信息增益(IG)。让我们检查是否应该使用 IG 通过单词进行分割。在这里,我们专注于Words列。那么,让我回答一些问题,以便你理解 IG 的计算:
-
总共有多少个正面意义的单词?答案是三个
-
总共有多少个负面意义的单词?答案是一个
-
所以,对于这个分支,我们的熵值是E = 0。我们将在计算子节点的加权平均熵时使用这个值。
这里,我们的决策树如图 8.36所示:

图 8.36:决策树的第一次迭代
你可以看到,对于右侧节点,熵为零,所以该分支没有杂质,因此我们可以停在那里。然而,如果你看左侧节点,它有SSH类别,所以我们需要为每个类别标签计算熵。让我们一步步计算左侧节点的熵:
-
PS = 分支中
S标签的数量 / 分支中总的样本数量 = 2/3 -
PH = 分支中
H标签的数量 / 分支中总的样本数量 = 1/3 -
现在熵
E= -2/3 log[2] (2/3) -1/3 log[2] (1/3) = 0.918
在下一步,我们需要计算子节点的加权平均熵。
左侧分支有三个数据点,右侧分支有一个数据点,你可以在图 8.36中看到。所以数值和公式如下:
子节点的加权平均熵 = 左分支数据点 / 总数据点数 * (该分支子节点的熵) + 右分支数据点 / 总数据点数 * (该分支子节点的熵)
子节点的加权平均熵 = [加权平均] 熵(子节点) = ¾ * 0.918 + ¼ * (0) = 0.6885
现在是时候计算信息增益(IG)了:
IG = 熵(父节点) - [加权平均] 熵(子节点)。我们手头有两个部分——E(父节点) = 1 和 [加权平均] 熵(子节点) = 0.6885
所以,最终的计算如下:
IG = 1 - 0.6885 = 0.3115
让我们聚焦在短语出现次数这一列,并计算短语计数为三时的熵,E[三(3)] = 1.0,短语计数为四时的熵是 E[四(4)] = 1.0;现在 [加权平均] 熵(子节点) = 1.0 , IG = 1.0 -1.0 = 0。因此,这次特征分割没有带来任何信息增益。所以我们不应选择这个特征。
现在让我们聚焦在提到短语类别的那一列——不常见短语或常见短语。当我们使用这一列来划分数据点时,我们在一个分支中得到垃圾邮件类别,在另一个分支中得到正常邮件类别。所以这里,你需要自行计算信息增益(IG),但IG = 1。我们得到了最大的 IG。所以我们会选择这个特征进行分割。你可以在图 8.37中看到决策树:

图 8.37:使用短语类型特征生成的决策树
如果特征数量很大,决策树训练的速度会非常慢,因为它会为每个特征计算信息增益,并通过选择提供最大信息增益的特征进行分割。
现在是时候看看应用决策树的 NLP 应用了。我们将重新开发垃圾邮件过滤系统,但这次我们将使用决策树。
我们只需要更改算法来实现垃圾邮件过滤应用程序,并且我们采用了之前生成的相同特征集,这样你就可以比较逻辑回归和决策树在垃圾邮件过滤中的效果。这里,我们将使用scikit-learn中的CountVectorizer API 生成的相同特征。代码请见这个 GitHub 链接:
你可以在图 8.38中看到代码片段:

图 8.38:使用决策树进行垃圾邮件过滤
你可以在图 8.39中看到输出:

图 8.39:使用决策树进行垃圾邮件过滤输出
正如你所看到的,我们得到的准确度比逻辑回归低。现在是时候看看一些可以用来提高 ML 模型准确度的调节参数了。
可调参数
在本节中,我将解释一些可调节的scikit-learn。你可以在以下链接查看文档:
我将解释你可以使用的以下参数:
-
scikit-learn中有一个参数叫做criterion。你可以将其设置为entropy或gini。entropy或gini用于计算信息增益(IG)。因此,它们都有相似的机制来计算 IG,决策树将根据entropy或gini计算得出的 IG 来进行分裂。 -
有一个
min_sample_size参数,默认值为two。因此,决策树分支会一直分裂,直到每个分支上有两个或更多的数据点。有时,决策树会尝试拟合最大量的训练数据,从而导致过拟合训练数据点。为了防止过拟合,你需要将min_sample_size从两个增加到五十或六十。 -
我们可以使用树剪枝技术,采用自底向上的方法。
现在让我们看看决策树的优缺点。
决策树的优点
以下是决策树提供的优点:
-
决策树简单且易于开发
-
决策树易于被人类解读,并且是一种白盒算法
-
它帮助我们确定不同场景下的最差、最好和预期值
决策树的缺点
以下是决策树的缺点:
-
如果你有很多特征,那么决策树可能会出现过拟合问题
-
训练时需要小心你传递的参数
我们已经看到了决策树的不足之处。决策树通常会对训练数据集发生过拟合。我们需要通过调整参数或使用决策树的变体——随机森林机器学习算法来解决这个问题。接下来,我们将了解随机森林算法。
随机森林
这个算法是决策树的一种变体,它解决了过拟合问题。
随机森林能够进行线性回归以及分类任务。在这里,我们专注于分类任务。它使用了一个非常简单的技巧,并且效果非常好。这个技巧就是随机森林使用投票机制来提高测试结果的准确性。
随机森林算法从训练数据集中生成一个随机子集,并利用这个子集生成每个数据子集的决策树。所有这些生成的树被称为随机森林。现在,让我们理解投票机制。一旦我们生成了决策树,我们检查每棵树为特定数据点提供的类别标签。假设我们已经生成了三棵随机森林决策树。其中两棵表示某个特定数据点属于类别 A,第三棵决策树则预测该数据点属于类别 B。算法会考虑票数较高的一方,并将类别标签 A 分配给该数据点。
对于随机森林,所有的分类计算与决策树类似。如我所承诺,我将参考我在第五章中给出的例子——特征工程与自然语言处理算法,即自定义词性标注器的例子。在那个例子中,我们使用了决策树。请参见此 GitHub 链接上的代码:
github.com/jalajthanaki/NLPython/blob/master/ch5/CustomPOStagger/ownpostag.py。
让我们回顾一下这个例子,并理解图 8.40中给出的特性和代码:

图 8.40:scikit-learn 中决策树算法的代码片段
随机森林的优点
以下是随机森林的优点:
-
它有助于我们防止过拟合。
-
它既可以用于回归任务,也可以用于分类任务。
随机森林的缺点
以下是随机森林的缺点:
-
随机森林模型可以轻松地增长,这意味着如果数据集的随机子集较大,我们将得到更多的决策树,从而得到一组决策树,也称为决策树森林,可能会占用大量内存。
-
对于高维特征空间,理解树的每个节点非常困难,尤其是在一个森林中有大量树的情况下。
现在是时候了解我们的下一个机器学习算法——朴素贝叶斯了。
朴素贝叶斯
在这一部分,我们将理解在许多数据科学应用中广泛使用的概率机器学习算法。我们将使用这个算法来开发最著名的自然语言处理应用——情感分析,但在跳入应用之前,我们将先理解朴素贝叶斯算法是如何工作的。那么,开始吧!
朴素贝叶斯机器学习算法基于贝叶斯定理。根据这个定理,我们最重要的假设是事件是独立的,这是一个朴素的假设,这也是为什么这个算法叫做朴素贝叶斯。那么,让我先给你解释一下什么是独立事件。在分类任务中,我们有很多特征。如果我们使用朴素贝叶斯算法,那么我们假设我们提供给分类器的每一个特征都是相互独立的,这意味着某个类别的特征的存在不会影响其他特征。举个例子,你想要判断句子的情感倾向:它非常好!你有一些特征,比如词袋模型、形容词短语等等。即使这些特征彼此之间有关联,或者依赖于其他特征的存在,这些特征所包含的所有属性仍然独立地贡献了该句子具有积极情感的概率。这就是为什么我们称这个算法为“朴素”的原因。
这个算法非常简单,同时也非常强大。如果你有大量数据,它的效果非常好。它可以分类超过两个类别,因此在构建多类别分类器时非常有用。接下来,让我们看看一些能告诉我们朴素贝叶斯算法是如何工作的要点。我们将了解其背后的数学原理和概率定理。
我们首先理解贝叶斯规则。用非常简单的语言来说,你对某个事件有先验概率,然后你在测试数据中找到了该事件的一些证据,并将它们相乘。然后你得到后验概率,帮助你得出最终的预测结果。不要担心这些术语,我们会深入了解这些细节。
让我先给你一个方程式,然后我们将举一个例子,这样你就知道我们需要做什么样的计算了。请查看图 8.41中的方程式:

图 8.41:朴素贝叶斯算法使用贝叶斯定理方程(图片来源:http://www.saedsayad.com/images/Bayes_rule.png)
这里,P(c|x) 是类别 c 的概率,类别 c 是目标,x 是特征或数据属性。P(C) 是类别 c 的先验概率,P(x|c) 是给定目标类别时,预测器的似然估计,即该预测器的概率,P(x) 是预测器的先验概率。
让我们用这个公式来计算一个例子。假设有一个医学测试可以帮助判断一个人是否患有癌症。这个人患这种癌症的先验概率只有 1 %,即 P(c) = 0.01 = 1%,所以 P(not c) = 0.99 = 99%。如果这个人患有癌症,那么这个测试显示为阳性的概率是 90%。因此,P(正结果 | c) = 0.9 = 90%,即使这个人没有癌症,结果仍然显示为阳性的概率是 10%,所以 P(正结果 | not c) = 0.1 = 10%。
现在,我们需要检查这个人是否真的有癌症。如果结果显示为阳性,那么这个概率表示为 P(c | 正结果),如果这个人没有癌症,但结果仍然为阳性,那么该概率表示为 P(not c | 正结果)。我们需要计算这两个概率以推导后验概率。首先,我们需要计算联合概率:
联合概率 P(c | 正结果) = P(c) * P(正结果 | c) = 0.01 x 0.9 = 0.009
联合概率 P(not c | 正结果) = P(not c) * P(正结果 | not c) = 0.99 x 0.1 = 0.099
上述概率称为联合概率。这对于推导最终的后验概率非常有帮助。为了获得后验概率,我们需要应用归一化:
P(正结果) = P(c | 正结果) + P(not c | 正结果)= 0.009 + 0.099 = 0.108
现在,实际的后验概率如下所示:
P(c | 正结果) 的后验概率 = P(c | 正结果) 的联合概率 / P(正结果)= 0.009 / 0.108 = 0.083
P(not c | 正结果) 的后验概率 = P(not c | 正结果) 的联合概率 / P(正结果)= 0.099 / 0.108 = 0.916
如果你将 P(c | 正结果) 的后验概率与 P(not c | 正结果) 的后验概率相加,它应该等于 1。在这种情况下,它确实加起来等于 1。
这里有很多数学内容,我会为你画一个图,帮助你理解这些内容。请参考 图 8.42:

图 8.42:后验概率计算图(图片来源:https://classroom.udacity.com/courses/ud120/lessons/2254358555/concepts/30144285350923)
我们将把这个概念扩展到 NLP 应用中。这里,我们将以一个基于 NLP 的基础示例为例。假设有两个人——Chris 和 Sara。我们有 Chris 和 Sara 的电子邮件详情。他们都使用诸如 life、love 和 deal 这样的词。为了简单起见,我们只考虑这三个词。他们以不同的频率使用这些词。
克里斯在他的邮件中只有 1% 的时间使用“love”一词,而 80% 的时间使用“deal”一词,1% 的时间使用“life”一词。另一方面,萨拉在她的邮件中有 50% 的时间使用“love”一词,20% 的时间使用“deal”一词,30% 的时间使用“life”一词。如果我们有一封新的电子邮件,那么我们需要决定它是克里斯写的还是萨拉写的。先验概率为 P(Chris) = 0.5 和 P(Sara) = 0.5。
邮件中有句子 "Life Deal",所以概率计算为 P(Chris| "Life Deal") = P(life) * P(deal) * P(Chris) = 0.04,P(Sara | "Life Deal") = P(life) * P(deal) * P(Sara) = 0.03。现在,让我们应用归一化并生成实际概率。为此,我们需要计算联合概率 = P(Chris| "Life Deal") + P(Sara | "Life Deal") = 0.07。以下是实际的概率值:
P(Chris| "Life Deal") = 0.04 / 0.07 = 0.57
P(Sara| "Life Deal") = 0.03 / 0.07 = 0.43
句子 "Life Deal" 更可能是克里斯写的。这是示例的结尾,现在是时候进行实际操作了。在这里,我们正在开发最著名的自然语言处理(NLP)应用程序,即情感分析。我们将对文本数据进行情感分析,因此我们可以说情感分析是对人类生成的意见的文本分析。情感分析帮助我们分析客户对某个产品或事件的看法。
对于情感分析,我们将使用词袋模型方法。你也可以使用人工神经网络,但我这里讲解的是一种基本且简单的方法。你可以在这个 GitHub 链接上看到代码:
github.com/jalajthanaki/NLPython/blob/master/ch8/sentimentanalysis/sentimentanalysis_NB.py
我们将使用 scikit-learn 的 TfidVectorizer API 以及 MultinomialNB 朴素贝叶斯。请参见 图 8.43 中的代码片段:

图 8.43:使用朴素贝叶斯进行情感分析的代码片段
请参见 图 8.44 中的输出:

图 8.44:使用朴素贝叶斯进行情感分析的输出
现在是时候看看一些调参参数了。
可调参数
对于这个算法,有时你需要应用平滑处理。那么,什么是平滑处理呢?让我简要介绍一下。某些词出现在训练数据中,我们的算法利用这些数据生成一个机器学习模型。如果机器学习模型在测试数据中遇到不在训练数据中的词,那么此时我们的算法就无法很好地预测。我们需要解决这个问题。作为解决方案,我们需要应用平滑处理,这意味着我们还要计算稀有词的概率,这是 scikit-learn 中的可调参数。这只是一个标志——如果启用,它会执行平滑处理;如果禁用,则不会应用平滑。
朴素贝叶斯的优点
下面是朴素贝叶斯算法的优点:
-
你可以使用朴素贝叶斯算法处理高维特征空间
-
它可以用于分类超过两个类别
朴素贝叶斯的缺点
下面是朴素贝叶斯算法的缺点:
-
如果你有一个由不同单词组成的短语,且这些单词具有不同的含义,那么该算法将无法帮助你。比如,你有一个短语,“Gujarat Lions”。这是一个板球队的名称,但“Gujarat”是印度的一个州,“lion”是一个动物。所以,朴素贝叶斯算法会将这些单词分别处理并单独解释,因此它无法正确理解“Gujarat Lions”。
-
如果某些类别数据仅出现在测试数据集中,而不出现在训练数据中,那么朴素贝叶斯将无法为该数据提供预测。因此,为了解决这个问题,我们需要应用平滑技术。你可以通过这个链接了解更多:
-
现在是时候来看一下最后一个分类算法——支持向量机了。那么,让我们开始吧!
支持向量机
这是我们在本章中将要讨论的最后一个监督式机器学习算法,支持向量机(SVM)。该算法用于分类任务和回归任务,同时也可用于多类分类任务。
SVM 接收带标签的数据,并尝试通过使用一条被称为超平面的线将数据点分类。目标是获得一个最优超平面,用于对现有数据以及新的、未见过的例子进行分类。如何获得最优超平面是我们接下来要理解的内容。
让我们先来理解一下“最优超平面”这一术语。我们需要以这样的方式获得超平面,使得得到的超平面最大化其到所有类别最近点的距离,这个距离被称为间隔。在这里,我们将讨论二分类器。间隔是超平面(或直线)与两个类别中的任意一个类别的最近点之间的距离。SVM 试图最大化这个间隔。参见图 8.45:

图 8.45:SVM 分类器基本图像(图片来源:http://docs.opencv.org/2.4/_images/optimal-hyperplane.png)
在给定的图中,有三条线* a , b 和 c 。现在,选择你认为最能分隔数据点的那条线。我会选择 b ,因为它最大化了两个类别之间的间隔,而另外两条线 a 和 c *则做不到这一点。
请注意,SVM 首先尝试完美地执行分类任务,然后再尝试最大化间隔。因此,对于 SVM,正确执行分类任务是首要任务。SVM 可以获得线性超平面,也可以生成非线性超平面。那么,让我们理解一下其背后的数学原理。
如果你有 n 个特征,那么使用 SVM,你可以绘制n-1维的超平面。如果你有二维特征空间,那么你可以绘制一个一维的超平面。如果你有三维特征空间,那么你可以绘制一个二维的超平面。在任何机器学习算法中,我们实际上都在尝试最小化我们的损失函数,因此我们首先为 SVM 定义损失函数。SVM 使用铰链损失函数。我们使用这个损失函数并尝试最小化我们的损失,同时获得最大的间隔来支持超平面。铰链损失函数的公式如下:
C (x, y, f(x)) = (1 - y * f(x))[+]
这里,x是样本数据点,y是真实标签,f(x)是预测标签,C是损失函数。公式中的+符号表示,当我们计算yf(x)并且结果> = 1时,我们试图从1中减去它并得到负值。我们不希望发生这种情况,因此用+*符号来表示:

现在是定义目标函数的时候,它包含损失函数以及一个称为正则化项的λ项。我们将看到它为我们做了什么。不过,它也是一个调节参数。请参阅 Figure 8.46中的数学公式:

图 8.46:包含正则化项λ的目标函数
SVM 有两个调节参数需要我们注意。一个是表示正则化项的λ。如果正则化项过高,那么我们的机器学习模型会发生过拟合,无法很好地泛化到未见过的数据。如果过低,则会发生欠拟合,从而导致巨大的训练误差。因此,我们也需要为正则化项确定一个精确的值。我们需要注意正则化项,它能帮助我们防止过拟合,同时我们还需要最小化我们的损失函数。因此,我们对这两个项进行偏导数计算;以下是我们可以用来执行梯度下降的正则化项和损失函数的导数,这样我们就能最小化损失并获得准确的正则化值。请参阅Figure 8.47中的偏导数公式:

图 8.47:正则化项的偏导数
请参见Figure 8.48中的损失函数偏导数:

图 8.48:损失函数的偏导数
我们需要计算偏导数的值,并相应地更新权重。如果我们错误分类了数据点,那么我们需要使用以下公式来更新权重。请参考 Figure 8.49:

图 8.49:错误分类条件
所以如果 y 是 < 1,那么我们需要使用 图 8.50 中的以下方程:

图 8.50:使用此方程进行错误分类条件下的权重更新规则
这里,长的 n 形状被称为 eta,表示学习率。学习率是一个调优参数,它告诉你算法应该运行得有多快。学习率需要一个准确的值,因为如果它太高,训练会太快完成,算法可能会错过全局最小值。另一方面,如果它太低,训练可能会耗费太多时间,并且可能永远无法收敛。
如果发生错误分类,那么我们需要更新损失函数以及正则化项。
那么,如果算法正确地分类了数据点呢?在这种情况下,我们不需要更新损失函数;我们只需要更新正则化参数,你可以通过 图 8.51 中给出的方程来看到这一点:

图 8.51:用于正则化的权重更新
当我们有一个合适的正则化值和全局最小值时,就可以对所有点进行 SVM 分类;此时,间隔值也会变为最大。
如果你想使用 SVM 进行非线性分类器,那么你需要应用核技巧。简而言之,我们可以说核技巧是关于将低维特征空间转化为高维特征空间,从而引入非线性属性,以便你可以对数据集进行分类。请参见 图 8.52 中的示例:

图 8.52:非线性 SVM 示例
为了对这些数据进行分类,我们有 X、Y 特征。我们引入了新的非线性特征 X² + Y²,它帮助我们绘制一个超平面,能够正确地分类数据。
所以,现在是时候实现 SVM 算法了,我们将再次开发情感分析应用程序,但这次我使用的是 SVM,并看看准确率有什么不同。你可以在这个 GitHub 链接中找到代码:
github.com/jalajthanaki/NLPython/blob/master/ch8/sentimentanalysis/sentimentanalysis_SVM.py
你可以在 图 8.53 中看到代码片段:

图 8.53:使用 SVM 的情感分析
你可以在 图 8.54 中找到输出:

图 8.54:SVM 输出
现在是时候来看一下调优参数了。让我们看一下吧!
可调参数
让我们来看看一些 SVM 调优参数,它们可以帮助我们:
-
scikit-learn提供了一个对核技巧非常有用的调优参数。你可以使用各种类型的核,例如线性核、rbf 核等。 -
还有其他参数,如
C和 gamma。 -
C 控制平滑决策边界与正确分类训练点之间的权衡。C 的大值会让你获得更多正确的训练点。
-
Gamma 在你尝试设置边界时很有用。如果你为 gamma 设置高值,则只有附近的数据点会被考虑进来画出决策边界;如果 gamma 值较低,则远离决策边界的点也会被考虑进来,以衡量决策边界是否最大化了边界。
现在是时候查看 SVM 的优缺点了。
SVM 的优点
以下是 SVM 算法为我们提供的优势:
-
它对复杂数据集的表现良好
-
它可以用于多类分类器
SVM 的缺点
以下是 SVM 算法的缺点:
-
当数据集非常庞大时,它表现不佳,因为需要大量的训练时间
-
当数据过于嘈杂时,它将无法有效工作
这标志着监督机器学习算法的结束。你学到了很多数学和概念,如果你想深入探索,可以尝试以下练习。
练习
-
你需要探索K-最近邻(KNN)及其在自然语言处理(NLP)领域的应用
-
你需要探索 AdaBoost 及其在 NLP 领域的应用
我们已经介绍了在 NLP 中使用的许多酷炫的分类技术,并将黑盒机器学习算法转化为白盒算法。因此,现在你知道算法内部发生了什么。我们也开发了 NLP 应用程序,现在是时候进入无监督机器学习的领域了。
无监督机器学习
这是另一种类型的机器学习算法。当我们没有任何标记数据时,我们可以使用无监督机器学习算法。在 NLP 领域,常见的情况是你找不到标记数据集,这时这种类型的机器学习算法就能派上用场。
在这里,我们将讨论一种无监督机器学习算法——K-means 聚类。该算法有许多应用。Google 已经将这种无监督学习算法应用于他们的许多产品中。YouTube 视频推荐使用了聚类算法。
以下图像将帮助你了解数据点在无监督机器学习算法中的表示方式。参考图 8.55:

图 8.55:无监督机器学习算法中数据点的普遍表示
正如你在图 8.55中看到的,数据点没有与之关联的标签,但从视觉上看,你可以看到它们形成了一些组或簇。我们实际上将尝试使用无监督机器学习算法来揭示数据中的结构,以便为未见过的数据点提供有价值的洞察。
在这里,我们将研究 k-means 聚类算法,并开发一个与 NLP 领域相关的文档分类示例。那么,让我们开始吧!
k-means 聚类
在本节中,我们将讨论 k-means 聚类算法。我们首先了解该算法。k-means 聚类使用迭代优化技术。
让我们了解一些关于 k-means 算法的基础知识。k 表示我们想要生成的簇的数量。现在,你可以选择一个随机点,并将质心放在这个点上。k-means 聚类中的质心数量不超过 k 的值,这意味着质心数量不超过簇的数量 k。
该算法有以下两个步骤,我们需要重新迭代:
-
第一步是分配质心。
-
第二步是计算优化步骤。
为了理解 k-means 的步骤,我们来看一个例子。在此之前,我建议你查看这个动画图像,它将帮助你更好地理解 k-means:
github.com/jalajthanaki/NLPython/blob/master/ch8/K_means_clustering/K-means_convergence.gif
现在,让我们以一个例子来说明。你有五个数据点,见下表,我们希望将这些数据点分为两个簇,因此 k = 2。请参见 图 8.56:

图 8.56:k-means 聚类数据点计算
我们选择了点 A(1,1) 和点 C(0,2) 来作为我们的质心。这是分配步骤的结束,接下来让我们了解优化步骤。
我们将计算每个点到该质心的欧几里得距离。欧几里得距离的公式在 图 8.57 中给出:

图 8.57:k-means 聚类算法的欧几里得距离
每次,我们都需要计算两个质心之间的欧几里得距离。让我们来检查一下计算。初始质心均值是 C1 = (1,1) 和 C2 = (0,2)。这里,我们想要分成两个簇,这就是我们选择两个质心的原因。
第一次迭代
对于点 A = (1,1):
C1 = (1,1) 所以 ED = 平方根 ((1-1)² + (1-1)²) = 0
C2 = (0,2) 所以 ED = 平方根 ((1-0)² + (1-2)²) = 1.41
这里,C1 < C2,因此点 A 属于簇 1。
对于点 B = (1,0):
C1 = (1,1) 所以 ED = 平方根 ((1-1)² + (0-1)²) = 1
C2 = (0,2) 所以 ED = 平方根 ((1-0)² + (0-2)²) = 2.23
这里,C1 < C2,因此点 B 属于簇 1。
对于点 C = (0,2):
C1 = (1,1) 所以 ED = 平方根 ((0-1)² + (2-1)²) = 1.41
C2 = (0,2) 所以 ED = 平方根 ((0-0)² + (2-2)²) = 0
这里,C1 > C2,因此点 C 属于簇 2。
对于点 D = (2,4):
C1 = (1,1) 所以 ED = 平方根 ((2-1)² + (4-1)²) = 3.16
C2 = (0,2) 所以 ED = 平方根 ((2-0)² + (4-2)²) = 2.82
这里,C1 > C2,因此点 C 属于簇 2。
对于点 E = (3,5):
C1 = (1,1) 所以 ED = 平方根 ((3-1)² + (5-1)²) = 4.47
C2 = (0,2) 所以 ED = Square root ((3-0)² + (5-2)²)= 4.24
这里,C1 > C2,所以点 C 属于聚类 2。
第一次迭代后,我们的聚类如下所示。聚类 C1 包含点 A 和 B,而 C2 包含点 C、D 和 E。因此,在这里,我们需要根据新的聚类点重新计算质心均值:
C1 = XA + XB / 2 = (1+1) / 2 = 1
C1 = YA + YB / 2 = (1+0) / 2 = 0.5
所以新的 C1 = (1,0.5)
C2 = Xc + XD + XE / 3 = (0+2+3) / 3 = 1.66
C2 = Yc +YD + YE / 3 = (2+4+5) / 3 = 3.66
所以新的 C2 = (1.66,3.66)
我们需要按照迭代 1的方式重新进行所有计算。我们得到如下数值。
迭代 2
对于点 A = (1,1):
C1 = (1,0.5) 所以 ED = Square root ((1-1)² + (1-0.5)²) = 0.5
C2 = (1.66,3.66) 所以 ED = Square root ((1-1.66)² + (1-3.66)²) = 2.78
这里,C1 < C2,所以点 A 属于聚类 1。
对于点 B = (1,0):
C1 = (1,0.5) 所以 ED = Square root ((1-1)² + (0-0.5)²) = 1
C2 = (1.66,3.66) 所以 ED = Square root ((1-1.66)² + (0-3.66)²) = 3.76
这里,C1 < C2,所以点 B 属于聚类 1。
对于点 C = (0,2):
C1 = (1,0.5) 所以 ED = Square root ((0-1)² + (2-0.5)²)= 1.8
C2 = (1.66, 3.66) 所以 ED = Square root ((0-1.66)² + (2-3.66)²)= 2.4
这里,C1 < C2,所以点 C 属于聚类 1。
对于点 D = (2,4):
C1 = (1,0.5) 所以 ED = Square root ((2-1)² + (4-0.5)²)= 3.6
C2 = (1.66,3.66) 所以 ED = Square root ((2-1.66)² + (4-3.66)²)= 0.5
这里,C1 > C2,所以点 C 属于聚类 2。
对于点 E = (3,5):
C1 = (1,0.5) 所以 ED = Square root ((3-1)² + (5-0.5)²) = 4.9
C2 = (1.66,3.66) 所以 ED = Square root ((3-1.66)² + (5-3.66)²) = 1.9
这里,C1 > C2,所以点 C 属于聚类 2。
第二次迭代后,我们的聚类如下所示。C1 包含点 A、B 和 C,而 C2 包含点 D 和 E:
C1 = XA + XB + Xc / 3 = (1+1+0) / 3 = 0.7
C1 = YA + YB + Yc / 3 = (1+0+2 ) / 3 = 1
所以新的 C1 = (0.7,1)
C2 = XD + XE / 2 = (2+3) / 2 = 2.5
C2 = YD + YE / 2 = (4+5) / 2 = 4.5
所以新的 C2 = (2.5,4.5)
我们需要继续进行迭代,直到聚类不再变化。这就是为什么这个算法被称为迭代算法。这就是 K-means 聚类算法的直觉。现在我们将看一个实际的文档分类应用示例。
文档聚类
文档聚类有助于推荐系统。假设你有很多研究论文,但没有标签,你可以使用 k-means 聚类算法,它可以帮助你根据文档中出现的单词来形成聚类。你可以构建一个新闻分类应用程序。所有同一类别的新闻应聚合在一起;你有一个超类类别,例如体育新闻,这个体育新闻类别包含关于板球、足球等的新闻。
在这里,我们将电影分类为五种不同的类型。代码归功于 Brandon Rose。你可以通过这个 GitHub 链接查看代码:
github.com/jalajthanaki/NLPython/blob/master/ch8/K_means_clustering/K-mean_clustering.ipynb。
查看图 Figure 8.58 中的代码片段:

图 8.58:K-means 算法的简短代码片段
查看图 Figure 8.59 中的输出:

图 8.59:K-means 聚类的输出
你可以通过这个链接了解层次聚类:
K-means 聚类的优点
这些是 K-means 聚类为我们提供的优点:
-
这对于自然语言处理应用来说是一个非常简单的算法。
-
它解决了主要问题,因为它不需要标记数据或结果标签,你可以使用这个算法处理未标记的数据。
K-means 聚类的缺点
这些是 K-means 聚类的缺点:
- 聚类中心的初始化是非常关键的一部分。假设你有三个聚类,并且你把两个质心放在同一个聚类里,另一个放在最后一个聚类里。不知怎么的,K-means 聚类最小化了所有数据点在聚类中的欧几里得距离,它会变得稳定,因此实际上会有两个质心在一个聚类中,而第三个聚类只有一个质心。在这种情况下,你最终只会得到两个聚类。这就是聚类中的局部最小值问题。
这就是无监督学习算法的结束部分。在这里,你了解了 K-means 聚类算法并开发了文档分类应用。如果你想了解更多关于这个技术的内容,尝试做一下练习吧。
练习
你需要探索层次聚类及其在自然语言处理领域的应用。
我们的下一部分非常有趣。我们将学习半监督机器学习技术。在这里,我们将对这些技术进行概述。那么,让我们了解一下这些技术吧。
半监督学习
半监督机器学习(semi-supervised learning,SSL)基本上是在你拥有一个包含部分数据标签的训练数据集时使用的,另一部分数据没有标签。如果你有这种数据集,你可以应用半监督机器学习算法。当我们拥有非常少量的标注数据和大量的未标注数据时,可以使用半监督技术。如果你想为任何本地语言(除了英语)构建 NLP 工具,并且你拥有很少的标注数据,那么你可以使用半监督方法。在这种方法中,我们将使用一个分类器,它利用标注数据生成一个机器学习模型。这个机器学习模型用于为未标注的数据集生成标签。分类器用于对未标注数据集进行高置信度的预测。你可以使用任何合适的分类算法来对标注数据进行分类。
半监督技术是一个重要的研究领域,尤其是对于 NLP 应用。去年,谷歌研究开发了基于图的半监督技术:
research.googleblog.com/2016/10/graph-powered-machine-learning-at-google.html。
为了更好的理解,您还可以阅读一些这里提供的非常有趣的内容:
medium.com/@jrodthoughts/google-expander-and-the-emergence-of-semi-supervised-learning-1919592bfc49。
arxiv.org/ftp/arxiv/papers/1511/1511.06833.pdf。
www.aclweb.org/anthology/W09-2208。
cogprints.org/5859/1/Thesis-David-Nadeau.pdf。
www.cmpe.boun.edu.tr/~ozgur/papers/896_Paper.pdf。
graph-ssl.wdfiles.com/local--files/blog%3A_start/graph_ssl_acl12_tutorial_slides_final.pdf。
对研究感兴趣的人可以为任何 NLP 应用开发新的半监督学习(SSL)技术。
现在我们已经完成了机器学习算法部分。我们需要理解一些关键点。现在是时候深入探讨这些重要概念了。
其他重要概念
在本节中,我们将探讨一些帮助我们了解使用机器学习算法训练数据集的进展的概念,如何判断生成的机器学习模型是否能够推广到未见过的场景,以及如何识别模型无法正确推广的信号。一旦发现这些情况,应该采取哪些步骤?对于 NLP 应用,有哪些广泛使用的评估矩阵?
那么,让我们找出所有这些问题的答案。我将涵盖以下几个主题,我们会逐一查看:
-
偏差-方差权衡
-
欠拟合
-
过拟合
-
评估矩阵
偏差-方差权衡
在这里,我们将探讨偏差-方差权衡的高层次概念。让我们逐一理解每个术语。
首先让我们理解偏差这个术语。当你使用机器学习算法进行训练,并且你看到生成的机器学习模型在第一次训练迭代后没有表现出明显的不同,那么你可以立即意识到机器学习算法存在高偏差。在这种情况下,机器学习算法无法从给定的数据中学习,因此它没有学习你期望它学习的新知识。如果你的算法偏差非常高,最终它就停止学习。假设你正在构建一个情感分析应用程序,并且已经创建了机器学习模型。现在,你对模型的准确度不满意,想要改进它。你会通过添加一些新特征并更改一些算法参数来训练模型。现在,这个新生成的模型在测试数据上的表现不好或表现不同,这提示你可能存在高偏差。你的机器学习算法无法以预期的方式收敛,从而无法改进模型的结果。
让我们理解第二个术语——方差。你使用任何机器学习算法训练模型时,观察到你得到了非常好的训练准确度。然而,你将相同的机器学习模型应用于未见过的测试数据集时,模型表现不好。这个情况,即训练准确度非常好,但机器学习模型在未见数据上表现不佳,称为高方差的情况。在这种情况下,机器学习模型只能复制它在训练数据中看到的预测或输出,并且没有足够的偏差来对未见过的情况进行泛化。换句话说,你可以说你的机器学习算法正在尝试记住每一个训练样本,最后只是在测试数据集上模仿这些输出。如果你有高方差问题,那么你的模型就会以一种方式收敛,它试图将数据集中的每个示例分类到某个特定类别。这种情况导致了过拟合。我会解释什么是过拟合,所以不用担心!几分钟后我们会讲到。
为了克服前面提到的两种不良情况,我们确实需要一些介于两者之间的东西,也就是说,既没有高偏差,也没有高方差。生成最优偏差和最佳方差的艺术对于机器学习算法至关重要,这样才能得到最佳的机器学习模型。你的机器学习模型可能不完美,但这就是关于生成最佳的偏差-方差权衡。
在接下来的章节中,你将学习欠拟合和过拟合的概念,以及一些技巧,帮助你摆脱高偏差和高方差的情形。
欠拟合
在本节中,我们将讨论欠拟合这一术语。什么是欠拟合,它与偏差-方差权衡有何关系?
假设你使用任何机器学习算法训练数据,并且得到一个较高的训练误差。参考图 8.60:

图 8.60:表示高训练误差的图表(图片来源:http://www.learnopencv.com/wp-content/uploads/2017/02/bias-variance-tradeoff.png)
上述情况,即训练误差非常高的情况,称为欠拟合。机器学习算法在训练数据上无法表现良好。现在,我们不再使用线性决策边界,而是尝试使用更高阶的多项式。参考图 8.61:

图 8.61:高偏差情况(图片来源:http://www.learnopencv.com/wp-content/uploads/2017/02/bias-variance-tradeoff.png)
这张图有一条非常曲折的线,无法在训练数据上做出良好的预测。换句话说,你可以说它的表现与之前的迭代相同。这表明该机器学习模型具有较高的偏差,无法学习新的内容。
过拟合
在本节中,我们将讨论过拟合这一术语。在上一节讲解方差时,我提到了这个术语。所以,现在是时候解释过拟合了,为了说明这一点,我想通过一个例子来说明。
假设我们有一个数据集,并将所有数据点绘制在二维平面上。现在我们尝试对数据进行分类,机器学习算法绘制决策边界以对数据进行分类。你可以参见图 8.62:

图 8.62:过拟合与方差
(图片来源:http://www.learnopencv.com/wp-content/uploads/2017/02/bias-variance-tradeoff-test-error.png)
如果你看左侧的图表,你会看到作为决策边界的线性线。现在,这张图展示了训练误差,因此你在第二次迭代中调整了参数,并且得到了非常好的训练准确率;请参见右侧的图表。你希望在测试数据上也能获得良好的准确率,但机器学习模型在测试数据的预测上表现非常差。因此,这种情况,算法在训练数据上表现非常好但在测试数据上表现不好,称为过拟合。这就是机器学习模型具有高方差,无法对未见数据进行泛化的情况。
现在你已经了解了欠拟合和过拟合,接下来有一些经验法则可以帮助你避免这些情况。始终将你的训练数据分成三部分:
-
60%的数据集应当被视为训练数据集。
-
20%的数据集应当被视为验证数据集或开发数据集,这对获取机器学习算法的中间准确率非常有用,以便捕捉到意外的情况并根据这些情况调整算法。
-
20%的数据集应被保留用于报告最终准确性,这将作为测试数据集。
你还应该应用 k 折交叉验证。k 表示验证需要进行的次数。假设我们设置为三次。我们将训练数据分成三等份。在训练算法的第一次迭代中,我们使用两部分数据进行训练,并用一部分进行测试,因此技术上它将用 66.66%的数据进行训练,33.34%的数据进行测试。然后,在第二次迭代中,机器学习算法使用一部分数据进行训练,并用两部分数据进行测试,最后一次迭代时,它将使用整个数据集进行训练和测试。经过三次迭代后,我们将计算平均误差以找出最佳模型。通常,在合理的数据集大小下,k 应取 10。
你无法实现 100%的准确率,原因主要在于输入数据中存在一些噪声,而这些噪声是你无法去除的,这就是所谓的不可约误差。
所以,机器学习算法中的最终误差公式如下:
总误差 = 偏差 + 方差 + 不可约误差
你真的无法消除不可约误差,所以你应该集中精力处理偏差和方差。请参阅图 8.63,它将有助于展示如何处理偏差和方差之间的权衡:

图 8.63:消除高偏差或高方差情况的步骤(图片来源:www.learnopencv.com/wp-content/uploads/2017/02/Machine-Learning-Workflow.png)
既然我们已经了解了机器学习的一些内容,接下来我们来看看一个非常有用的评估矩阵。
评估矩阵
对于我们的代码,我们检查其准确性,但我们实际上并不理解在评估机器学习模型时,哪些属性发挥了重要作用。所以,在这里,我们将考虑一个在自然语言处理应用中广泛使用的矩阵。
这个评估矩阵叫做F1 分数或F-measure。它有三个主要组成部分;在此之前,我们先了解一些术语:
-
真正例 (TP):这是一个被分类器标记为 A 的数据点,并且它实际属于 A 类。
-
真阴性 (TN):这是分类器对任何类别的适当拒绝,意味着分类器不会随机地将数据点归类为 A 类,而是会拒绝错误的标签。
-
假阳性 (FP):这也叫做I 类错误。让我们通过一个例子来理解这个度量:一个人进行癌症检测,实际上他没有癌症,但检测结果是阳性。这就叫做 FP。
-
假阴性 (FN):这也叫做II 类错误。让我们通过一个例子来理解这个度量:一个人进行癌症检测,他有癌症,但检测结果是阴性。所以实际上忽视了类别标签。这就叫做 FN。
-
精确度:精确度是精确性的度量;即分类器标记为阳性的那些数据点中,实际为阳性的百分比:
精确度 = TP / (TP + FP)
-
召回率:召回率是完整性的衡量标准;分类器将多少百分比的正类数据点标记为正类:
召回率 = TP / (TP + FN)
-
F 值:这只是精确度和召回率的加权衡量。请参见以下方程:
F = 2 * 精确度 * 召回率 / (精确度 + 召回率)
除此之外,你可以使用混淆矩阵来了解每个 TP、TN、FP 和 FN。你还可以使用 ROC 曲线下面积来表示分类器区分负类和正类的能力。ROC = 1.0 表示模型正确预测了所有类别;0.5 的面积表示模型只是做出随机预测。
如果你想探索新的术语和技术,可以做以下练习。
练习
阅读关于欠采样和过采样技术的资料。
现在是时候了解如何在第一次迭代后改进我们的模型了,有时,特征工程在这方面对我们帮助很大。在第五章《特征工程与自然语言处理算法》和第六章《高级特征工程与自然语言处理算法》中,我们解释了如何使用各种自然语言处理和统计学概念从文本数据中提取特征,作为特征工程的一部分。特征工程包括特征提取和特征选择。现在是时候探索特征选择中所涉及的技术了。特征提取和特征选择为我们的自然语言处理应用提供了最重要的特征。一旦我们确定了这些特征,可以使用各种机器学习算法来生成最终结果。
让我们开始了解特征选择的部分。
特征选择
如我之前提到的,特征提取和特征选择是特征工程的一部分,在本节中,我们将重点讨论特征选择。你可能会想,为什么我们要学习特征选择,但这背后有一些原因,我们将逐一探讨。首先,我们将了解特征选择的基本概念。
特征选择也被称为变量选择、属性选择或变量子集选择。特征选择是选择最相关的特征、变量或数据属性的过程,这些特征有助于我们开发更高效的机器学习模型。如果你能够识别哪些特征贡献较大,哪些贡献较小,你可以选择最重要的特征并删除其他不太重要的特征。
不妨退后一步,首先理解我们在通过特征选择来解决的问题是什么。
使用特征选择技术,我们可以获得以下好处:
-
选择相关和适当的特征将有助于简化你的机器学习模型。这将有助于你更轻松地解释机器学习模型,并减少模型的复杂性。
-
使用特征选择技术选择合适的特征将帮助我们提高机器学习模型的准确性。
-
特征选择有助于机器学习算法更快地训练。
-
特征选择还可以防止过拟合。
-
它帮助我们摆脱维度灾难。
维度灾难
让我们理解一下“维度灾难”是什么意思,因为这个概念将帮助我们理解为什么我们需要特征选择技术。维度灾难的意思是,随着特征或维度数量的增加,也就是向机器学习算法中添加新特征时,我们需要的数据量会指数级增长,以便准确地进行泛化。让我们通过一个例子来看一下。
假设你有一条线,一维特征空间,我们在这条线上放置了五个点。你可以看到,每个点在这条线上占据了一些空间。每个点占据了线段空间的五分之一。参见图 8.64:

图 8.64:一个一维特征空间,包含五个数据点
如果你有一个二维特征空间,那么我们需要超过五个数据点才能填充这个空间。所以,对于这两个维度,我们需要 25 个数据点。现在每个点占据空间的 1/25。请参见图 8.65:

图 8.65:一个二维特征空间,包含 25 个数据点
如果你有一个三维特征空间,这意味着我们有三个特征,那么我们需要填充这个立方体。你可以在图 8.66中看到这一点:

图 8.66:一个三维特征空间,包含 1/25 的数据点
然而,要填充这个立方体,你需要准确地 125 个数据点,就像你在图 8.66中看到的那样(假设有 125 个点)。因此,每次我们添加特征时,我们需要更多的数据。我想你们都会同意,数据点的增长是指数级的,从 5、25、125,依此类推。所以,通常情况下,你需要Xd特征空间,其中X是你在训练中使用的数据点数量,d是特征或维度的数量。如果你只是盲目地添加越来越多的特征,以便让你的机器学习算法更好地理解数据集,你实际上是在强迫你的机器学习算法用数据填充更大的特征空间。你可以通过一种简单的方法来解决这个问题。在这种情况下,你需要给你的算法更多的数据,而不是特征。
现在你可能真的认为我在限制你添加新特征。那么,让我来澄清一下这一点。如果有必要添加特征,你可以添加;你只需要选择最适合且最少量的特征,帮助你的机器学习算法从中学习。我真心建议你不要盲目地添加太多特征。
现在,如何得出最佳特征集呢?对于我正在构建的特定应用程序,最佳特征集是什么?我如何知道我的机器学习算法在这个特征集下会表现良好?我将在下一节特征选择技术中回答这些问题。在这里,我将给你一个关于特征选择技术的基本概念。我建议你在我们迄今为止开发的 NLP 应用程序中实际实现这些技术。
特征选择技术
让一切尽可能简单,但不能过于简化
这句阿尔伯特·爱因斯坦的名言,在我们讨论特征选择技术时非常真实。我们已经看到,为了摆脱维度灾难,我们需要特征选择技术。接下来我们将介绍以下几种特征选择技术:
-
过滤方法
-
包装方法
-
嵌入式方法
那么,让我们从每种方法开始。
过滤方法
特征选择是一个独立的活动,与机器学习算法无关。对于数值数据集,当我们进行数据预处理时,通常会使用此方法;而对于 NLP 领域,当我们将文本数据转换为数值格式或向量格式时,应该执行此方法。让我们首先查看图 8.67中该方法的基本步骤:

图 8.67:特征选择的过滤方法(图片来源:https://upload.wikimedia.org/wikipedia/commons/2/2c/Filter_Methode.png)
步骤非常清晰且不言自明。在这里,我们使用统计技术来为每个特征打分,并根据这个分数来决定是保留特征,还是删除它或丢弃它。请参见图 8.68:

图 8.68:特征选择技术列表
让我为你简化图 8.68:
-
如果特征和响应都是连续的,那么我们将执行相关性分析
-
如果特征和响应都是分类的,那么我们将使用卡方检验;在 NLP 中(我们主要使用这个)
-
如果特征是连续的,响应是分类的,那么我们将使用线性判别分析(LDA)
-
如果特征是分类的,响应是连续的,那么我们将使用方差分析(Anova)
我将更集中在 NLP 领域,解释 LDA 和卡方检验的基础。
LDA 通常用于寻找特征的线性组合,这些特征能够描述或区分多个类别的分类变量,而卡方检验(Chi-Square)主要在 NLP 中使用,与 LDA 相比。卡方检验应用于一组分类特征,通过其频率分布来评估特征之间的相关性或关联的可能性。
包装方法
在这种方法中,我们正在寻找最佳特征集。由于每次迭代都需要搜索最佳特征子集,这种方法在计算上非常昂贵。请参见图 8.69中的基本步骤:

图 8.69:包装方法步骤(图片来源: https://upload.wikimedia.org/wikipedia/commons/0/04/Feature_selection_Wrapper_Method.png)
我们可以使用三种子方法来选择最佳特征子集:
-
向前选择
-
向后选择
-
递归特征消除
在向前选择中,我们从没有特征开始,并在每次迭代中添加那些能改善我们机器学习模型的特征。我们会继续这个过程,直到模型的准确性不再提高。
向后选择是另一种方法,我们从所有特征开始,在每次迭代中,找到最佳特征并移除其他不必要的特征,重复此过程直到在机器学习模型中没有进一步的改进。
递归特征消除使用贪心算法找出表现最好的特征子集。它反复创建模型,并在每次迭代时保留表现最好的或最差的特征。下一次,它使用最好的特征来创建模型,直到所有特征都被耗尽;最后,它根据特征被消除的顺序对特征进行排名。
嵌入式方法
在这种方法中,我们结合了过滤方法和包装方法的特点。这种方法由具有自己内置特征选择方法的算法实现。请参考图 8.70:

图 8.70:嵌入式特征选择方法(图片来源: https://upload.wikimedia.org/wikipedia/commons/b/bf/Feature_selection_Embedded_Method.png)
这些方法中最流行的例子是 LASSO 和 RIDGE 回归,它们具有一些内置参数,可以减少过拟合的机会。
你可以参考这些链接,它们对你会很有帮助:
www.analyticsvidhya.com/blog/2016/01/complete-tutorial-ridge-lasso-regression-python/。
machinelearningmastery.com/an-introduction-to-feature-selection/。
machinelearningmastery.com/feature-selection-in-python-with-scikit-learn/。
我们将在下一节中讨论降维。
降维
降维是机器学习中非常有用的概念。如果我们包含了很多特征来开发我们的机器学习模型,那么有时我们会包含一些实际上并不需要的特征。有时我们需要高维特征空间。那么,我们有哪些方法可以更好地理解我们的特征空间呢?因此,我们需要一些技术来帮助我们移除不必要的特征,或者将我们的高维特征空间转换为二维或三维特征,这样我们就能看到发生了什么。顺便说一下,我们在第六章《高级特征工程与 NLP 算法》中使用了这个概念,当时我们开发了一个为《权力的游戏》数据集生成 word2vec 的应用程序。那时,我们使用了 t-分布随机邻域嵌入(t-SNE)降维技术,将我们的结果可视化为二维空间。
在这里,我们将介绍两种最著名的技术,分别是 主成分分析(PCA)和 t-SNE,它们用于将高维数据可视化为二维空间中的数据。让我们开始吧。
PCA
PCA 是一种统计方法,使用正交变换将一组可能存在相关性的特征数据点转换为一组线性不相关的特征值,称为 主成分。主成分的数量小于或等于原始特征的数量。该技术定义了这样的变换方式,使得第一个主成分具有尽可能大的方差,后续的每个特征依此类推。
参考 图 8.71:

图 8.71: PCA(图片来源:https://www.nature.com/article-assets/npg/nbt/journal/v26/n3/images/nbt0308-303-F1.gif)
这个图有助于理解 PCA。我们采用了两个主成分,它们彼此正交,并使方差尽可能大。在 c 图中,我们通过将数据投影到一条直线上,将二维数据降到了一维。
PCA 的缺点是,当你减少维度时,它会丧失数据点所代表的意义。如果可解释性是进行维度缩减的主要原因,那么你不应该使用 PCA;你可以使用 t-SNE。
t-SNE
这是一种帮助我们可视化高维非线性空间的技术。t-SNE 试图保持那些相互靠近的局部数据点组。这项技术将帮助你在需要可视化高维空间时。你可以使用它来可视化使用类似 word2vec、图像分类等技术的应用。更多信息,请参考以下链接:
自然语言处理应用的混合方法
混合方法有时确实能帮助我们改善 NLP 应用程序的结果。例如,如果我们正在开发一个语法纠错系统,其中有一个模块可以识别多词表达式(如 kick the bucket),以及一个基于规则的模块来识别错误模式并生成正确模式。这就是一种混合方法。让我们再举一个相同 NLP 应用程序的例子。你正在制作一个分类器,用于识别句子中名词短语的正确冠词(限定词 - a, an, 和 the)。在这个系统中,你可以分成两个类别——a/an 和 the。我们需要开发一个分类器,生成限定词类别,要么是 a/an,要么是 the。一旦我们为名词短语生成了冠词,我们可以应用一个基于规则的系统,进一步决定第一个类别 a/an 的实际限定词。我们还知道一些英语语法规则,可以帮助我们决定应该用 a 还是 an。这也是混合方法的一种例子。为了更好的情感分析,我们还可以使用混合方法,包括基于词汇的方法、基于机器学习的方法,或者使用预训练的 word2vec 或 GloVe 模型来获得更高的准确性。因此,要保持创造性,理解你的 NLP 问题,以便能够利用不同类型的技术来提升你的 NLP 应用程序。
后处理
后处理是一种基于规则的系统。假设你正在开发一个机器翻译应用程序,而你生成的模型出现了一些特定的错误。你希望机器翻译(MT)模型能够避免这些错误,但要避免这些错误需要大量的特征,这会使训练过程变慢,并使模型过于复杂。另一方面,如果你知道一旦生成输出后,有一些简单的规则或近似方法可以帮助你使其更加准确,那么我们就可以在我们的 MT 模型中使用后处理。那么,混合模型和后处理有什么区别呢?让我来解答你的困惑。在这个例子中,我使用了词汇近似。因此,与其使用规则,你也可以应用近似方法,例如应用一个阈值来调整结果,但只有当你知道它会产生准确的结果时,才应该使用近似方法。这个近似方法应该与 NLP 系统互补,并且足够广泛。
摘要
在本章中,我们探讨了机器学习(ML)的基本概念,以及在自然语言处理(NLP)领域中使用的各种分类算法。在 NLP 中,我们主要使用分类算法,而非线性回归。我们还看了一些非常有趣的例子,如垃圾邮件过滤、情感分析等。我们还回顾了词性标注器(POS tagger)示例,以帮助您更好地理解。我们研究了无监督机器学习算法,以及一些重要的概念,如偏差-方差权衡、欠拟合、过拟合、评估矩阵等。我们还了解了特征选择和降维。我们还简要提到了混合机器学习方法和后处理。因此,在本章中,我们主要了解了如何开发和微调 NLP 应用程序。
在下一章,我们将进入机器学习的新纪元——深度学习。我们将探讨人工智能所需的基本概念。之后,我们将讨论深度学习的基础知识,包括线性回归和梯度下降。我们将了解为什么深度学习在过去几年中成为了最受欢迎的技术。我们将了解与深度学习相关的数学基础概念,探讨深度神经网络的架构,并开发一些有趣的应用程序,如来自自然语言理解(NLU)领域的机器翻译和来自自然语言生成(NLG)领域的文本摘要。我们将使用 TensorFlow、Keras 和一些其他最新的依赖项来实现这些应用。同时,我们还将学习一些基本的优化技术,您可以将其应用于传统机器学习算法和深度学习算法。在下一章,我们将深入探讨深度学习的世界!
第九章:用于 NLU 和 NLG 问题的深度学习
在前几章中,我们已经看过了基于规则的方法和各种机器学习技术来解决 NLP 任务。在本章中,我们将看到一种被称为深度学习(DL)的机器学习技术的前沿子集。在过去的四到五年里,神经网络和深度学习技术在人工智能领域引起了极大的关注,因为许多科技巨头利用这些尖端技术解决现实生活中的问题,而这些技术的结果令人非常印象深刻。像谷歌、苹果、亚马逊、OpenAI 等科技巨头,花费了大量时间和精力去创造创新的解决方案来解决现实中的问题。这些努力主要是为了发展人工通用智能,并使世界变得更加美好。
我们首先将了解总体的 AI,概述其背景,以便让你了解为什么深度学习现在会引起如此大的关注。本章将涉及以下主题:
-
NLU 和 NLG 之间的区别
-
神经网络基础
-
使用各种深度学习技术构建 NLP 和 NLG 应用
在理解了深度学习的基础知识后,我们将讨论一些最近在深度学习领域发生的创新。那么,让我们开始吧!
人工智能概览
在本节中,我们将看到 AI 的各个方面以及深度学习与 AI 的关系。我们将看到 AI 的组成部分、AI 的不同阶段以及 AI 的不同类型;在本节末尾,我们将讨论为什么深度学习是实现 AI 的最有前景的技术之一。
AI 基础
当我们谈到 AI 时,我们会想到智能机器,这是 AI 的基本概念。AI 是一个科学领域,持续朝着让机器具备人类水平的智能方向发展。AI 的基本思想是赋予机器智能,使其能够执行本只能由人类完成的任务。我们正在尝试通过一些很酷的算法技术,在机器中实现人类水平的智能;在这个过程中,机器获得的任何类型的智能都是人工生成的。用于生成 AI 的各种算法技术大多数属于机器学习技术。在深入学习机器学习和深度学习核心部分之前,我们将了解一些与 AI 相关的其他事实。
AI 受到许多领域的影响;在图 9.1中,我们将看到那些对人工智能影响深远的领域,将它们作为一个整体来看:

图 9.1:AI 受其他领域影响
AI 的组成部分
首先,我们将看到 AI 的关键组成部分。这些组件对我们理解世界发展的方向非常有帮助。
在我看来,你可以在图 9.2中看到两个组件:

图 9.2:人工智能的组成部分
让我们详细看看人工智能的组成部分。我们还将看到一些例子。
自动化
自动化是人工智能中一个著名的组成部分。全球各地的人们在自动化领域投入了大量精力,并且我们在机器执行自动任务方面取得了巨大的成功。我们将看一些足够直观的例子,帮助你理解人工智能中的自动化概念。
在汽车行业,我们正在使用自动机器人来制造车辆。这些机器人遵循一套指令,执行特定任务。在这里,这些机器人不是能够与人类互动、提问或回答问题的智能机器人,而是按照一组指令执行任务,以达到高速度、高精度和高效的制造效果。因此,这些机器人是人工智能领域自动化的例子。
另一个例子是在 DevOps 领域。如今,DevOps 正在利用机器学习自动化许多需要大量人工的过程。例如,为了维护内部服务器,DevOps 团队在分析了各种服务器日志后,会收到一堆建议,接着,另一个机器学习模型会根据优先级对警报和建议进行排序。这种应用真正节省了 DevOps 团队的时间,使他们能够按时交付高质量的工作。这类应用帮助我们深刻理解自动化是人工智能中非常重要的组成部分。
现在让我们看看智能作为人工智能的一部分将如何影响世界。
智能
当我们谈到智能时,作为人类,我们的期望非常高。我们的目标是让机器能够理解我们的行为和情感。我们还希望机器能根据人类的行为做出智能反应,所有由机器生成的反应都应该模拟人类的智能。自 20 世纪中期以来,我们一直致力于实现这一目标。全球许多研究人员、科学家团队和社区正在进行大量前沿研究,旨在使机器像人类一样智能。
我们希望,在获得智能之后,机器能够以更高的准确性完成大多数人类任务,这是我们广泛的期望。在过去的四五年里,我们开始成功地实现这一广泛目标。经过多年努力,谷歌最近宣布,Google Assistant 能够听懂人类的自然语言,并像人类一样准确地解释语音信号。另一个例子是,Facebook 研究小组进行了一项非常强大的研究,旨在构建一个能够运用推理进行问答的系统。特斯拉和谷歌的自动驾驶汽车是复杂的人工智能系统,但它们非常有用且智能。自动驾驶汽车和聊天机器人属于窄人工智能的范畴。你还可以在网上找到许多新的例子,时不时地会有新的技术问世。
有一些子组件可以作为智能的一部分。请参见图 9.3:

图 9.3:智能的子组件
智能是前面图中描述的所有组件的结合。所有这些组件——推理、学习、从经验中学习、解决问题、感知和语言智能——对人类来说是非常自然的,但对机器来说却不是。因此,我们需要能够赋予机器智能的技术。
在学习本章稍后我们将使用的技术名称之前,让我们先了解一下人工智能的各个阶段。
人工智能的阶段
人工智能系统有三个主要阶段。我们将详细查看以下各个阶段:
-
机器学习
-
机器智能
-
机器意识
在深入了解人工智能每个阶段的细节之前,请参阅图 9.4:

图 9.4:人工智能的阶段(图片来源:cdn-images-1.medium.com/max/1600/0*aefkt8m-V66Wf5-j.png)
我们将从下到上开始学习,因此我们首先了解机器学习阶段,然后是机器智能,最后是机器意识。
机器学习
在前几章你已经学习了很多关于机器学习的内容,但我想在本章中从人工智能的角度给你讲解它。
机器学习技术是一组算法,解释了如何生成或达到定义的输出。这种算法被智能系统使用,这些系统试图从经验中学习。使用机器学习算法的系统热衷于从历史数据或实时数据中学习。因此,在人工智能的这个阶段,我们专注于通过我们提供给机器学习系统的特征,从数据中学习模式或特定结构的算法。为了更清楚地说明这一点,让我们举个例子。
假设你想构建一个情感分析应用程序。我们可以使用历史标记数据、手工设计的特征和朴素贝叶斯机器学习算法。结果,我们可以拥有一个智能系统,该系统通过学习示例学会了如何为一个未见的新数据实例提供情感标签。
机器智能
机器智能也是一组算法,但大多数算法受到人脑学习和思考方式的深刻影响。利用神经科学、生物学和数学,人工智能研究者提出了一组高级算法,帮助机器从数据中学习,而无需提供手工设计的特征。在这个阶段,算法使用的是无标签或有标签的数据。在这里,你只需要定义最终目标,先进的算法会找到自己的方式来实现预期结果。
如果你将我们在这个阶段使用的算法与传统的机器学习算法进行比较,你会发现主要的区别在于,在这个机器智能阶段,我们没有为任何算法提供手工设计的特征输入。由于这些算法受到人脑的启发,算法本身会学习特征和模式,并生成输出。目前,人工智能的世界正处于这个阶段。全球各地的人们正在使用这些看似非常有前景的先进算法,以期为机器实现类人智能。
人工神经网络(ANNs)和深度学习技术被用来实现机器智能。
机器意识
机器意识是人工智能中最受讨论的主题之一,因为我们的最终目标是达到这一阶段。
我们希望机器能像人类一样学习。作为人类,我们不需要大量数据;我们不需要很长时间就能理解抽象概念。我们通过少量数据或者没有数据来学习。大多数时候,我们是通过我们的经验来学习的。如果我们想构建一个像人类一样具有意识的系统,那么我们应该知道如何为机器生成意识。然而,我们是否完全了解大脑是如何工作和反应的,从而能够将这种知识转移到机器中,让它们像我们一样具有意识呢?不幸的是,目前我们并不完全了解这一点。我们期望,在这个阶段,机器可以在没有数据或只有非常少量数据的情况下学习,并利用自身的经验来达成既定的输出。
目前有很多有趣的研究在进行,我鼓励你查看一下研究员 John Searle 在 YouTube 上分享的视频,他讲解了机器意识:www.youtube.com/watch?v=rHKwIYsPXLg。这个视频可能会为你提供关于人工智能中意识的全新视角。
我们已经看到了人工智能的不同阶段。人工神经网络(ANNs)和深度学习是机器智能阶段的一部分。在深度学习简要概述部分的结尾,你会获得一些必要的细节,帮助你理解为何深度学习成为了人工智能中的新热词。
现在我们将看到人工智能的各种类型。
人工智能的类型
人工智能有三种类型,如下所示:
-
人工窄智能
-
人工通用智能
-
人工超智能
人工窄智能
人工窄智能(ANI)是一种人工智能,涵盖了一些基本任务,如基于模板的聊天机器人、像苹果最初版本的 Siri 那样的基础个人助手应用。
这种类型的智能主要集中在应用的基本原型设计上。它是任何应用的起点,之后你可以改进这个基本原型。你可以通过添加人工通用智能来增强智能层次,但只有在你的最终用户真的需要这种功能时才行。我们也在第七章中看到了这种基本聊天机器人的示例,基于规则的 NLP 系统。
人工通用智能
人工通用智能(AGI)是一种人工智能,用来构建能够执行人类级别任务的系统。什么是人类级别的任务呢?比如构建自动驾驶汽车。谷歌的自动驾驶汽车和特斯拉的自动驾驶仪是最著名的例子。类人机器人也尝试使用这种类型的人工智能。
NLP 级别的示例是复杂的聊天机器人,它们能够忽略拼写错误和语法错误,并理解你的查询或问题。深度学习技术似乎对人类理解自然语言非常有前景。
我们现在正处于这样一个阶段,世界各地的人们和社区使用基本概念,通过相互参考彼此的研究工作,尝试构建具有 AGI 的系统。
人工超级智能
实现人工超级智能(ASI)的方式对我们来说有些困难,因为在这种类型的人工智能中,我们期望机器在学习特定任务时比人类更聪明,并且能够像人类一样执行多任务。这种超级智能目前对我们来说是一个梦想,但我们正在尝试以一种方式来实现它,使得机器和系统始终补充人类技能,并且不会对人类构成威胁。
人工智能的目标和应用
这是我们需要理解人工智能在各个领域的目标和应用的时机和环节。这些目标和应用只是为了让你了解当前人工智能应用的状态,但如果你能想到一些疯狂但有用的应用场景,无论在哪个领域,都应该尝试将其纳入这个列表中。你应该尝试在这些应用中实现不同类型和阶段的人工智能。
现在让我们看看我们希望在哪些领域集成不同阶段的人工智能,并使这些应用具备人工智能功能:
-
推理
-
机器学习
-
自然语言处理
-
机器人技术
-
实现通用智能
-
计算机视觉
-
自动化学习和调度
-
语音分析
你可以参考图 9.5,它展示了许多不同的领域和相关的应用:

图 9.5:人工智能的各种领域和应用(图片来源:http://vincejeffs.com/)
现在让我们看看前面列表中一些领域的应用。
人工智能应用
在这里,我将简要介绍一些人工智能应用。有些应用与自然语言处理(NLP)领域也有关:
-
为任何系统启用推理将是非常令人兴奋的事情。在这个领域,我们可以构建一个问答系统,利用推理来推导出所提问题的答案。
-
如果我们能为 AI 驱动的系统启用推理,那么这些系统将非常擅长决策,并将改善现有的决策系统。
-
在机器学习中,我们希望能够由机器自己决定的完美架构,来实现基于机器学习的应用。依我看,这就是一种 AI 驱动的机器学习应用。
-
当我们讨论 AI 驱动的自然语言处理(NLP)应用时,我们确实需要能够理解人类自然语言语境并像人类一样反应和行为的 NLP 系统。
-
类人机器人是描述 AI 驱动系统的最佳应用。机器人应该具备感知能力,这是一个长期的 AI 目标。
-
当我们讨论通用智能时,依我看,系统应该更像人类那样反应。特别是机器的反应应该与真实的人类行为相匹配。分析某些情况后,机器应当作出与人类相同甚至更好的反应。
-
如今,计算机视觉有许多应用,它们为我们提供了坚实的证据,表明 AI 将在这个领域很快取得突破。这些应用包括物体识别、图像识别、使用图像识别技术检测皮肤癌、从机器生成面部图像、为图像生成文本及其反向操作等。这些应用为 AI 驱动的计算机视觉提供了具体证据。
-
自动化学习和调度是一种为个人提供助理并管理日程的系统。关于 AI 部分,我们真的期望每个系统用户能够获得个性化体验,因此自动化学习个人偏好对于 AI 驱动的调度非常重要。为了实现这一目标,自动化学习系统还应该学会如何为特定用户选择最合适的模型。
-
语音分析是一种不同形式的自然语言处理,然而不幸的是,在本书中我们并未涉及这一概念。在这里,我们讨论的是语音识别系统在 AI 驱动领域中的潜力。通过在语音识别领域启用 AI,我们可以理解人在社会学、心理学和哲学的影响下所生成的环境与思维过程。我们还可以预测他们的个性。
在看到这些令人着迷的应用之后,有三个非常有趣的问题浮现在我们的脑海:是什么原因促使我们产生 AI 驱动的系统?为什么现在是构建 AI 驱动系统的最佳时机?我们该如何构建 AI 驱动的系统?
自 20 世纪中期以来,我们一直在尝试将智能引入机器。在这个过程中,研究人员和科学家提出了许多酷炫的概念。例如,人工神经元,也被称为麦卡洛克-皮茨模型(MCP),它的灵感来源于人脑,这一概念的目的是理解人脑的生物学工作过程,并用数学和物理的语言表示这一过程。因此,这对于实现机器的人工智能(AI)是非常有帮助的。
他们成功地给出了单个神经元如何工作的数学表示,但这个模型的一个结果并不适合训练目的。因此,研究员弗兰克·罗森布拉特在 1958 年的论文中提出了感知机,引入了动态权重和阈值的概念。之后,许多研究人员基于早期的概念发展了反向传播和多层神经网络等概念。研究界希望将这些已开发的概念应用于实际中,第一个实现这一目标的研究员是杰弗里·辛顿,他演示了如何使用广义反向传播算法训练多层神经网络。从那时起,研究人员和学术界开始使用这一广义模型,但在 20 世纪末,数据量远不如现在丰富,计算设备也既慢又昂贵,因此我们没有得到预期的结果。然而,尽管如此,研究人员相信,凭借当时取得的成果,这些概念将是实现 AI 驱动世界的关键。现在,我们拥有大量的数据和快速、廉价且足够强大的计算设备来处理这些数据。当我们将这些旧的人工神经网络(ANN)概念应用于当前时代,开发如通用机器翻译系统、语音识别系统、图像识别系统等应用时,我们取得了非常有希望的成果。举个例子,Google 正在使用 ANN 开发一个通用机器翻译系统,该系统能够翻译多种语言。这是因为我们拥有丰富的大型数据集,以及能够帮助我们通过 ANN 处理这些数据集的快速计算能力。我们使用的神经网络并非只有一层或两层,而是深度多层。所取得的成果令人印象深刻,以至于每一家大型科技公司都在使用深度学习模型来开发 AI 驱动的系统。在我看来,数据、计算能力和扎实的旧概念是开发 AI 驱动系统的关键组成部分。你可以参考图 9.6了解神经网络的简要历史:

图 9.6:人工神经网络的历史(图片来源:https://image.slidesharecdn.com/deeplearning-170124234229/95/deep-learning-9-638.jpg?cb=1485303074)
图 9.7将让你对神经网络的长期历史有所了解:

图 9.7:人工神经网络(ANN)的历史(图片来源:qingkaikong.blogspot.in/2016/11/machine-learning-3-artificial-neural.html)
现在让我们向下一个问题迈进:如何使 AI 成为可能?答案是深度学习。这是使非 AI 系统具备 AI 功能的最常用技术之一。虽然有些情况下深度学习没有被用于使 AI 成为可能,但在 NLP 领域,深度学习主要被用来使 AI 成为可能。为了发展通用智能,我们可以使用深度学习。我们从这项技术中得到了非常有前景的结果。诸如机器生成真人面孔、在嘈杂环境中更准确地理解人类语言、自驾车、为问答系统进行推理等实验,都是其中的一部分。深度学习技术使用大量的数据和高计算能力对给定数据进行系统训练。当我们将合适的深度学习模型应用于大量数据时,我们将得到神奇、令人印象深刻且充满前景的结果。这些就是深度学习如今成为热门话题的原因。所以,我想现在你明白为什么深度学习在 AI 世界中成为流行词了。
在本章后续部分,我们将详细探讨深度学习技术,并使用深度学习开发 NLP 应用。
比较 NLU 和 NLG
我们已经在第三章《句子结构理解》中看到了 NLU 和 NLG 的定义、细节及其区别。在这一节中,我们将从一个 AI 驱动的应用角度对这两个 NLP 子领域进行比较。
自然语言理解
之前,我们看到 NLU 更多的是处理语言结构的理解,无论是单词、短语还是句子。NLU 更侧重于将各种机器学习(ML)技术应用于已经生成的自然语言(NL)。在 NLU 中,我们关注句法和语义。我们还尝试解决与句法和语义相关的各种歧义。我们已经见识过词汇歧义、句法歧义、语义歧义和语用歧义。
现在让我们看看我们可以在哪里使用 AI 来帮助机器更准确高效地理解语言结构和意义。AI 和 ML 技术在解决自然语言(NL)这些方面并不落后。举个例子,深度学习在机器翻译中给我们带来了令人印象深刻的成果。现在,当我们谈论解决句法歧义和语义歧义时,我们可以使用深度学习。假设你有一个命名实体识别(NER)工具,它将使用深度学习和 Word2vec,那么我们就能解决句法歧义。这只是一个应用,但你还可以通过它来改进解析器的结果和词性标注器。
现在让我们来谈谈语用歧义,在这种情况下,我们真的需要人工通用智能(AGI)以及人工超级智能(ASI)。这种歧义出现在你尝试理解一句话的长距离上下文时,需要考虑其他先前写的或说过的话,并且还取决于说话者的表达意图。
让我们看一个语用歧义的例子。你和你的朋友正在交谈,你的朋友很久以前告诉你她加入了一个 NGO,并且会为贫困学生做一些社会活动。现在你问她社会活动如何。在这种情况下,你和你的朋友都知道你们在谈论的是什么社会活动。这是因为作为人类,我们的大脑存储了信息,并且知道何时获取这些信息,如何解读这些信息,以及这些信息与当前与你朋友交谈的内容的相关性。你和你的朋友都能理解彼此问题和答案的背景和相关性,但机器并不具备理解背景和说话者意图的能力。
这就是我们对智能机器的期望。我们希望机器也能理解这种复杂的情况。使机器能够解决语用歧义的能力被纳入了 MSI。这在未来肯定是可能的,但目前我们正处于机器试图采用 AGI 并使用统计技术来理解语义的阶段。
自然语言生成
NLG 是我们尝试教机器如何以合理的方式生成自然语言的一个领域。这本身就是一项具有挑战性的 AI 任务。深度学习在帮助我们完成这种具有挑战性的任务方面确实起到了重要作用。让我给你举个例子。如果你正在使用谷歌的新收件箱,你可能会注意到,当你回复任何邮件时,你会得到三条最相关的回复,以句子的形式出现。谷歌使用了数百万封电子邮件,创建了一个 NLG 模型,并通过深度学习训练它,从而生成或预测任何给定邮件的最相关回复。你可以参考图 9.8:

图 9.8:谷歌新收件箱的智能回复
除了这个应用,还有另一个应用:在看到图片后,机器会提供特定图片的说明。这也是一个使用深度学习的 NLG 应用。生成语言的任务比生成自然语言(NL)要简单一些,即连贯性,而这正是我们需要 AGI 的地方。
我们已经谈论了很多“深度学习”这个术语,但它到底是如何工作的,为什么它如此有前景呢?这个问题我们将在本章的后续部分中探讨。我们将解释 NLU 和 NLG 应用的编码部分。我们还将从零开始开发 NLU 和 NLG 应用。在此之前,你必须了解人工神经网络(ANN)和深度学习的概念。我将在后续章节中加入数学内容,并尽量保持简洁。让我们深入探索人工神经网络和深度学习的世界!
深度学习概述
机器学习是 AI 的一个子领域,深度学习是机器学习的一个子领域。请参见图 9.9:

图 9.9:深度学习作为机器学习的一个子领域
深度学习使用人工神经网络(ANN),其不仅仅是一个或两个层,而是多个层,称为深度神经网络(DNN)。当我们使用 DNN 来解决某个问题,并通过预测该问题的可能结果时,这就是深度学习。
深度学习可以使用带标签的数据或不带标签的数据,因此我们可以说,深度学习可以应用于监督学习技术和无监督学习技术。使用深度学习的主要思想是,利用深度神经网络(DNN)和大量数据,我们希望让机器概括特定任务,并为我们提供一个我们认为只有人类才能生成的结果。深度学习包括一系列技术和算法,可以帮助我们解决自然语言处理(NLP)中的各种问题,例如机器翻译、问答系统、摘要等。除了 NLP 外,您还可以找到其他应用领域,如图像识别、语音识别、物体识别、手写数字识别、面部检测和人造面孔生成等。
深度学习似乎在构建 AGI(通用人工智能)和 ASI(超人工智能)方面给我们带来了希望。您可以在图 9.10中看到一些深度学习应用的实例:

图 9.10:使用深度学习的应用(图片来源:http://www.fullai.org/)
本节将简要概述深度学习。我们将在本章中讨论深度学习的多个方面,但在此之前,我想解释一些与深度学习和人工神经网络(ANN)相关的概念。这些概念将帮助您理解深度学习的技术性内容。
神经网络基础
神经网络的概念是机器学习(ML)中最古老的技术之一。神经网络来源于人类大脑。在本节中,我们将首先了解人脑的组成部分,然后推导出人工神经网络(ANN)。
为了理解人工神经网络(ANN),我们首先需要理解人脑的基本工作流程。您可以参考图 9.11:

图 9.11:人脑神经元(图片来源:https://en.wikipedia.org/wiki/File:Blausen_0657_MultipolarNeuron.png)
人脑由大约数百亿个神经细胞组成,这些细胞称为神经元。每个神经元执行以下三个任务:
-
接收信号:它从树突接收一组信号。
-
决定是否将信号传递到细胞体:它将这些信号整合在一起,决定是否将信息传递到细胞体。
-
发送信号:如果一些信号超过某个阈值,它会通过轴突将这些信号(称为动作电位)传递到下一个神经元。
您可以参考图 9.12,它展示了用于执行生物神经网络中三项任务的组件:

图 9.12:展示执行这三项任务的组件
这只是我们的大脑如何学习和处理决策的一个非常简要的概述。现在的问题是:我们能否构建一个使用非生物基材(如硅或其他金属)的人工神经网络?我们可以构建它,然后通过提供大量的计算能力和数据,我们可以比人类更快地解决问题。
人工神经网络是一种受生物启发的算法,能够学习识别数据集中的模式。
我们在本章之前已经简要了解了人工神经网络的历史,现在是时候详细了解人工神经网络及其历史了。
第一个神经元计算模型
在 1943 年中期,研究人员 McCulloch 和 Pitts 发明了第一个神经元计算模型。他们的模型相当简单。该模型有一个神经元,它接收二进制输入,求和,如果总和超过某个阈值,则输出为 1,否则输出为 0。你可以在 图 9.13 中看到它的示意图:

图 9.13:McCulloch-Pitts 神经元计算模型(神经网络图片来源:http://wwwold.ece.utep.edu/research/webfuzzy/docs/kk-thesis/kk-thesis-html/node12.html)
它看起来非常简单,但由于它是在人工智能的早期发明的,这种模型的发明当时是一项非常了不起的成就。
感知机
在发明第一个神经元计算模型的几年后,一位名叫 Frank Rosenblatt 的心理学家发现,McCulloch-Pitts 模型没有从输入数据中学习的机制。因此,他发明了建立在第一个神经元计算模型基础上的神经网络。Frank Rosenblatt 将这个模型称为 感知机。它也被称为 单层前馈神经网络。我们称这个模型为前馈神经网络,因为在这个神经网络中,数据只沿一个方向流动——前向方向。
现在让我们了解感知机的工作原理,感知机引入了对给定输入进行加权的概念。如果你提供一些输入输出示例的训练集,它应该通过对每个训练示例的权重不断增加或减少来学习一个函数,具体取决于给定输入示例的输出是什么。这些权重值在数学上应用于输入,使得每次迭代后,输出预测变得更加准确。整个过程被称为 训练。请参考 图 9.14 来理解 Rosenblatt 感知机的示意图:

图 9.14:Rosenblatt 感知机的示意图(神经网络图片来源:http://sebastianraschka.com/Articles/2015_singlelayer_neurons.html)
在下一节中,我们将看到与人工神经网络相关的数学概念,如梯度下降、激活函数和损失函数。所以,准备好迎接一些数学内容吧!
理解人工神经网络(ANN)的数学概念
本节非常重要,因为机器学习(ML)、人工神经网络(ANN)和深度学习(DL)使用了大量的数学概念,我们将看到其中一些最重要的概念。这些概念将真正帮助你优化你的机器学习、人工神经网络和深度学习模型。我们还将看到不同类型的激活函数,并提供一些选择激活函数的小贴士。我们将要学习以下数学概念:
-
梯度下降法
-
激活函数
-
损失函数
梯度下降法
梯度下降法是一种非常重要的优化技术,几乎所有的神经网络都在使用它。为了说明这些技术,我想给出一个例子。我有一个数据集,包含了每个学生的考试成绩和学习时长。我们希望仅根据学生的学习时长来预测考试成绩。你可能会说,这看起来像是一个机器学习线性回归的例子。你说得对;我们正在使用线性回归来进行预测。那么,为什么使用线性回归?梯度下降与它有什么关系呢?让我来回答这个问题,然后我们将看看代码和一些有趣的可视化。
线性回归是机器学习中的一种技术,利用统计方法,允许我们研究两个连续的定量变量之间的关系。在这里,这两个变量是学生的成绩和学生的学习时长。通常在进行线性回归时,我们试图得到一条最适合我们数据集的直线,这意味着我们所做的所有计算,目的都是为了找到一条最适合给定数据集的直线。得到这条最适合的直线就是线性回归的目标。
现在我们来讨论线性回归与梯度下降的关系。梯度下降是我们用来优化线性回归准确度并最小化损失或误差函数的最流行优化技术。梯度下降是一种帮助我们最小化误差函数并最大化预测准确度的技术。梯度下降的数学定义是它是一种一阶迭代优化算法。该算法用于通过梯度下降找到函数的局部最小值。每一步的大小与当前点的梯度的负值成正比。你可以通过这个现实生活中的例子来理解梯度下降:假设你站在一座山顶,现在你想要走到山脚下的美丽湖泊,你需要开始下山。此时,你不知道应该朝哪个方向走。你观察周围的地形,尝试找到地形倾斜的地方。这样你就能大致知道应该往哪个方向走。如果你开始按照下坡的方向走,并且每次都遵循这个思路,那么你很有可能会到达湖泊。这正是我们在使用梯度下降的数学公式时所做的。在机器学习和深度学习中,我们把一切都看作是优化问题,所以梯度下降是用于随着时间推移最小化损失函数的技术。
另一个例子是,你有一个深碗,里面放了一个小球,从碗的一端开始。你会发现,经过一段时间后,小球减慢了速度,并试图到达碗底。参见图 9.15:

图 9.15:梯度下降的直观理解(图片来源:https://iamtrask.github.io/img/sgd_optimal.png)
你还必须查看这个 GitHub 链接中的图片:
github.com/jalajthanaki/NLPython/blob/master/ch9/gradientdescentexample/gradient_descent_example.gif
这张图展示了使用梯度下降得到最佳拟合线的过程或步骤。它只是一个可视化图,帮助你大致了解我们将在代码中做的事情。顺便提一下,损失函数、误差函数和代价函数是同义词。梯度下降也被称为最陡下降。
现在让我们来看代码,并在过程中进行解释。在这里,我们不是在开发预测模型,而是实现和理解梯度下降。数据集和代码可以在github.com/jalajthanaki/NLPython/tree/master/ch9/gradientdescentexample找到。
首先,让我们理解一下数据集。它是学生的测试分数和他们学习的小时数的数据集。我们知道这两个属性之间应该存在某种关系——学习的时间越少,学生的分数越差;学习的时间越多,分数就越高。我们将使用线性回归来证明这一关系。X 值表示数据集的第一列,也就是学生学习的小时数,Y 值表示第二列,即测试分数。请参见图 9.16:

图 9.16:数据集的样本数据
让我们定义一个主函数,它将读取我们的数据集以及一些基本的超参数。我们还调用了一个函数,用来计算误差和实际的梯度下降。你可以在图 9.17中看到代码片段:

图 9.17:梯度下降的代码片段
你可以在图 9.18中看到输出结果:

图 9.18:梯度下降的输出结果
正如你在图 9.18中看到的,我们调用了两个函数:compute_error_for_line_points(),它将计算实际值和预测值之间的误差,和gradient_descent_runner(),它将为我们计算梯度。我们需要先了解如何计算误差,然后再进行梯度下降。
计算误差或损失
计算机器学习算法误差的方法有很多,但在本章中我们将使用其中最流行的技术之一:平方和误差。现在我们将直接进入细节。
这个误差函数为我们做了什么?回想一下我们的目标:我们希望得到数据集的最佳拟合直线。请参见图 9.19,它是直线斜率的方程式。这里,m 是直线的斜率,b 是 y 截距,x 和 y 是数据点——在我们的例子中,x 是学生学习的小时数,y 是测试分数。请参见图 9.19:

图 9.19:直线斜率方程(图片来源:https://www.tes.com/lessons/Xn3MVjd8CqjH-Q/y-mx-b)
使用前述的方程,我们绘制了直线并从随机的斜率 m 和 y 截距 b 开始,同时使用我们的第一列数据点作为 x 的值,从而计算出 y 的值。在训练数据中,我们已经有了 y 的值,也就是说,我们知道每个学生的考试成绩。因此,对于每个学生,我们需要计算误差。让我们举一个非常直观的例子;请注意,我们使用的是虚拟值来进行解释。假设你通过随机选取的 m 和 b 值得到 y 的值为 41.0。现在,你有了 y 的实际值,即 52.5,那么预测值与实际值之间的差异是 11.5。这只是针对单一数据点的情况,但我们需要对每个数据点进行计算。因此,为了进行这种误差计算,我们使用了平方距离误差之和。
现在,我们是如何计算平方距离误差之和的,为什么我们要使用平方距离误差之和呢?
那么让我们从第一个问题开始,计算平方距离误差之和的方程见 图 9.20:

图 9.20:计算平方距离误差之和的方程(图片来源:spin.atomicobject.com/wp-content/uploads/linear_regression_error1.png)
如你所见,最后部分的 mx[i]+b 是我们通过选择随机的 m 和 b 值绘制出的直线,实际上我们可以将 y 替换为 mx[i]+b。因此,这里我们计算了原始 y 值与生成的 y 值之间的差异。我们将原始 y 值与生成的 y 值相减,并对每个数据点的差值进行平方。我们平方该值是因为我们不想处理负值,在进行求和操作之前,计算平方后,我们希望衡量总体的大小。我们不关心实际值,因为我们希望最小化这个总体大小。现在回到方程;我们已经计算出了原始 y 值与生成的 y 值之间差异的平方。接下来,我们对所有这些点进行求和;我们将使用 sigma 符号表示对数据集中所有数据点的求和操作。到此为止,我们得到了表示误差大小的总和值,并将这些值除以数据点的总数。经过这个步骤,我们就得到了我们想要的实际误差值。
你可以在这个 GitHub 链接查看动画图片:
github.com/jalajthanaki/NLPython/blob/master/ch9/gradientdescentexample/gradient_descent_example.gif
你可以看到,每次迭代中,直线都会移动,以生成与我们数据集最佳匹配的直线。我们根据误差的值更新 m 和 b 的值。现在,对于每个时间戳,直线是静止的,我们需要计算误差。请参见 图 9.21:

图 9.21:计算给定时间戳时直线与数据点之间的距离(图片来源:http://statutor.org/c3/glmregression/IMAG004.JPG)
现在我们需要根据给定的方程式以技术方式表达我们直观的例子和方程式。在这里,我们计算每个数据点到我们绘制的直线的距离,对它们进行平方,将它们全部加起来,然后除以总点数。因此,在每次迭代或时间戳后,我们可以计算我们的误差值,并了解我们的直线有多糟糕或多好。如果我们的直线不好,那么为了得到最佳拟合线,我们会更新m和b的值。因此,误差值为我们提供了一种指示,即是否有可能改进以生成最佳拟合线。最终,我们希望最小化我们在这里获得的误差值,以生成最佳拟合线。我们如何最小化这个误差并生成最佳拟合线呢?下一步被称为梯度下降。
对于平方误差的一些原因是,对于线性回归来说,这是计算误差的最流行技术。如果您有大数据集,您也可以使用这个技术。
让我们看看编码部分,然后我们将跳转到计算梯度下降的核心部分。参考图 9.22中的代码片段:

图 9.22:计算平方误差和的代码片段
现在让我们看下一步--计算梯度下降。
计算梯度下降
使用误差函数,我们知道是否应该更新我们的直线以生成最佳拟合线,但如何更新直线是我们将在本节中看到的内容。我们如何最小化这个误差并生成最佳拟合线呢?因此,为了回答这个问题,首先让我们对梯度下降和编码部分有一些基本的理解,其中我们只剩下最后一个函数gradient_descent_runner()。参考图 9.23:

图 9.23:理解梯度下降的 3D 图表(图片来源:https://spin.atomicobject.com/wp-content/uploads/gradient_descent_error_surface.png)
正如你在图 9.23中看到的,这是一张三维图。 这两个图是相同的;它们的视角不同。 所以这些图显示了斜率m、y-截距b和误差的所有可能值。这些是包含m、b和误差的三个值的对。这里,x轴是斜率值,y轴是y-截距,z轴是误差值。我们试图找到误差最小的点。如果你仔细观察图形,你会发现曲线的底部,误差值最小。值最小的点在机器学习中被称为局部最小值。在复杂的数据集中,你可能会发现多个局部最小值;但在这里我们的数据集很简单,所以只有一个局部最小值。如果你有一个复杂且高维的数据集,其中有多个局部最小值,那么你需要进行二阶优化,以决定选择哪个局部最小值以获得更好的准确性。在本书中我们不会讲解二阶优化。现在让我们回到图形,在这里我们可以直观地确定给我们最小误差值的点,且同一个点也给出了理想的y-截距b和斜率值m。当我们得到b和m的理想值时,我们将这些值代入我们的y=mx+c方程式,然后奇迹就会发生,我们会得到最佳拟合线。这不是唯一的获取最佳拟合线的方法,但我的目的是让你深入了解梯度下降的概念,以便我们以后可以在深度学习中使用这个概念。
现在,从视觉上你可以看到误差最小的那个点,但如何到达这个点呢?答案是通过计算梯度。梯度也叫做斜率,但这不是斜率值m,所以不要混淆。我们在这里讨论的是朝着最小误差点前进的方向的斜率。因此,我们有一些b值和一些m值,并且每次迭代后,我们都会更新这些b和m值,以便我们能够到达那个最小误差值的点。从三维图像的角度来看,如果你在曲线的顶部,那么每次迭代我们都会计算梯度和误差,然后更新m和b的值,以便我们能够到达曲线的底部。我们需要到达曲线的底部,通过计算梯度值,我们可以知道应该朝哪个方向迈出下一步。因此,梯度是切线,它不断告诉我们需要朝哪个方向移动,无论是向上还是向下,以到达最小误差点并获得理想的b和m值,从而生成最佳拟合线。请参考图 9.24:

图 9.24:梯度值和方向(图片来源:https://sebastianraschka.com/images/faq/closed-form-vs-gd/ball.png)
现在让我们来看最后一个计算梯度下降的方程式。在图 9.25中,你可以看到梯度下降的方程式,它们只是我们误差函数的偏导数。我们取了平方误差和的方程,并对m和b分别进行偏导数计算,从而得出梯度下降。结果如图 9.25所示:

图 9.25:计算梯度下降的方程式(图片来源:https://spin.atomicobject.com/wp-content/uploads/linear_regression_gradient1.png)
左侧的∂符号是偏导数符号。在这里,我们有两个方程式,因为我们使用误差函数并对变量m求偏导数,在第二个方程式中,我们对变量b求偏导数。通过这两个方程式,我们将获得更新后的b和m的值。
为了计算梯度,我们需要对误差函数进行偏导数推导。对于机器学习和深度学习中的一些问题,我们可能不知道误差函数的偏导数,这意味着我们无法计算梯度。因此,我们不知道该如何处理这种类型的函数。你的误差函数应该是可微分的,这意味着它应该有偏导数。另一个需要注意的是,我们使用的是线性方程,但如果你的数据是高维的,那么你可以使用非线性函数,只要你知道误差函数。梯度下降并不会在第一次迭代时就给出最小值。梯度只是告诉我们如何更新m和b的值,更新时是加上正值还是负值。所以梯度给我们一个更新m和b值的方向,这意味着通过计算梯度,我们得到了方向,并且在尽力找到误差值最小且m和b最优的点。
现在是时候再次跳转到代码并完成梯度下降了。请参考图 9.26:

图 9.26:实际梯度下降运行函数的代码片段
在代码中,我们将m_gradient和b_gradient与学习率相乘,因此学习率是一个重要的超参数。在选择其值时要小心。如果你选择一个非常高的值,模型可能根本无法训练。如果选择一个非常低的值,则可能需要很长时间进行训练,并且有过拟合的风险。请参考图 9.27,它能给你关于合适学习率的直觉:

图 9.27:学习率直觉(图片来源:http://cs231n.github.io/assets/nn3/learningrates.jpeg)
这就是线性回归和梯度下降的编码部分。让我们运行代码,图 9.28将给你一个关于输出的直觉:

图 9.28:输出的代码片段
梯度下降有多种类型,我们列举几个,但我们不会深入探讨。你可以探索带动量的梯度下降、Adagrad、Adam 等等。
如果你真的想深入探讨,我会提供一个链接,这对你会有帮助:
www.analyticsvidhya.com/blog/2017/03/introduction-to-gradient-descent-algorithm-along-its-variants/
现在是时候理解激活函数了,让我们开始吧!
激活函数
让我们先来看激活函数。我想给你一个大致的概念,说明我们会在神经网络的哪个阶段使用激活函数。在我们讨论感知器时,我们提到神经网络如果超过某个阈值,将生成输出 1;否则,输出为 0。计算阈值并根据这个阈值生成输出的整个机制由激活函数负责。
激活函数能够提供位于 0 和 1 之间的值。之后,我们可以使用阈值生成输出值 1 或输出值 0。例如,如果我们的阈值是 0.777,激活函数的输出是 0.457,那么我们的结果输出将是 0;如果激活函数的输出是 0.852,那么结果输出将是 1。所以,这就是激活函数在神经网络中工作的方式。
通常,在神经网络中,我们为每个神经元指定一个权重和输入值。我们对它们求和并生成加权和。当我们将这些值通过一个非线性函数时,这个非线性函数会激活一定数量的神经元,从而得到复杂任务的输出;这种通过特定的非线性数学函数激活神经元的过程称为激活函数或传递函数。激活函数通过一定的数学运算将输入节点映射到输出节点。
在人工神经网络(ANN)中使用激活函数的目的是引入非线性。让我们一步步理解这个过程。
让我们专注于神经网络的结构。这个结构可以进一步分为三个部分:
-
架构: 架构是关于决定神经网络中神经元和层次的排列方式。
-
活动: 为了生成复杂任务的输出,我们需要观察神经元的活动——一个神经元如何响应另一个神经元,从而生成复杂的行为。
-
学习规则: 当神经网络生成输出时,我们需要在每个时间点更新 ANN 的权重,以便使用误差函数优化输出。
激活函数是活动部分的一部分。如前所述,我们将引入 ANN 中的非线性。背后的原因是,如果没有非线性,ANN 无法产生复杂行为来解决复杂任务。在深度学习(DL)中,大多数时候我们使用非线性激活函数来获得复杂的行为。除此之外,我们还希望以非线性的方式将输入映射到输出。
如果你没有使用非线性激活函数,那么 ANN 在处理复杂任务时不会给出大量有用的输出,因为你传递的是矩阵。如果你在 ANN 中使用多个层且使用线性激活函数,得到的输出将是输入值、权重和偏置在所有层中的求和。这个输出给你另一个线性函数,这意味着这个线性函数将多层 ANN 的行为转化为单层 ANN。这种行为在解决复杂任务时完全没有用。
我想强调连接主义的思想。在人工神经网络(ANN)中,连接主义是指使用彼此相连的神经元来产生复杂的行为,就像人类的大脑一样,我们无法在不引入非线性性的情况下在 ANN 中实现这种行为。参考图 9.29来理解激活函数:

图 9.29:带有激活函数的 ANN(图片来源:https://cdn-images-1.medium.com/max/800/0*bWX2_ecf3l6lKyVA.png)
在这里,我们将介绍前面图像中提到的这些函数:
-
传递潜力: 这是聚合输入和权重的函数。更具体地说,这个函数执行输入与权重的求和
-
激活函数: 该函数将传递潜力函数的输出作为输入,并通过激活函数应用非线性的数学变换
-
阈值函数: 基于激活函数,阈值函数要么激活神经元,要么不激活神经元
传递潜力是一个简单的求和函数,它会对输入与连接权重的内积进行求和。你可以在图 9.30中看到该方程:

图 9.30:传递潜力的求和方程(图片来源:https://cdn-images-1.medium.com/max/800/0*005k9F1JxQ0oKEeM.png)
这个传递潜力通常是一个点积,但也可以使用任何数学方程,如多项式函数。
另一方面,激活函数应该是任何可微且非线性的函数。它需要是可微的,以便我们可以计算误差梯度,并且这个函数必须具备非线性特性,才能从神经网络中获得复杂行为。通常,我们使用 sigmoid 函数作为激活函数,它将转移势能的输出值作为输入,计算最终输出,然后计算实际输出与生成输出之间的误差。接着,我们将利用计算误差梯度的概念,并应用反向传播优化策略来更新人工神经网络(ANN)连接的权重。
图 9.31表示了转移势能函数,形式为 theta,也称为logit,我们将在 logistic sigmoid 激活函数的方程中使用它:

图 9.31:以 logit 值形式表示的转移势能输出(图片来源:https://cdn-images-1.medium.com/max/800/0*mPYW0-FKPTOSACPP.png)
你可以在图 9.32中看到 logistic sigmoid 函数的方程:

图 9.32:Logistic sigmoid 激活函数(图片来源:https://cdn-images-1.medium.com/max/800/0*SwSxznoodb2762_9.png)
激活函数的核心思想大致是模拟神经元在大脑中彼此之间的通讯方式。每个神经元如果达到一定的阈值,就会通过其动作电位被激活;然后我们就能知道是否激活该神经元。激活函数模拟了大脑动作电位的跃升。深度神经网络(DNN)被称为通用逼近器,因为它们可以在任何时刻计算任何函数。它们可以计算任何可微的线性以及非线性函数。现在你可能会问,我什么时候使用这个激活函数呢?我们将在下一个段落中讨论。
有多种激活函数可供选择。使用时要小心,我们不应该仅仅因为某个函数听起来新颖和酷就去使用它。在这里,我们将讨论如何判断应该使用哪一个激活函数。我们将讨论三种主要的激活函数,因为它们在深度学习中应用广泛,尽管还有其他激活函数也可以使用。
以下是提到的三种激活函数:
-
Sigmoid
-
Tanh
-
ReLU 及其变种
Sigmoid
从数学概念上来看,sigmoid 函数非常容易理解。它的数学公式如图 9.33所示:

图 9.33:Sigmoid 函数方程(图片来源:https://cdn-images-1.medium.com/max/800/1*QHPXkxGmIyxn7mH4BtRJXQ.png)
如你在图 9.33中所见,sigmoid 函数会将给定的方程代入一个数字,并将这个数字压缩到零和一之间,产生一个 S 形曲线。
这个函数是 ANN 中第一个被用作激活函数的,因为它可以解释为神经元的发射率——零表示没有发射,1 表示完全饱和的发射。当我们将这个激活函数应用于深度神经网络(DNN)时,我们会发现它存在一些限制,使得它在如今变得不太流行。
这些函数的一些基本问题如下:
-
它受梯度消失问题的影响
-
它有较慢的收敛速度
-
它不是一个零中心函数
让我们详细了解每个问题:
消失梯度问题:当你使用基于梯度的方法训练某些人工神经网络(ANN),尤其是在使用反向传播的 ANN 中,你会遇到这个问题。这个问题使得很难学习和调整 ANN 中早期层的参数。当你增加更多层时,这个问题会变得更加严重。如果我们明智地选择激活函数,这个问题是可以解决的。我想先给你介绍这个问题的细节,然后我们再讨论其背后的原因。
基于梯度的方法通过理解输入参数和权重的微小变化如何影响神经网络的输出,来学习参数的值。如果这个梯度太小,参数的变化将导致 ANN 输出的变化非常微小。在这种情况下,经过一些迭代后,ANN 将无法有效地学习这些参数,也无法按照我们期望的方式收敛。这正是梯度消失问题的表现。网络输出相对于早期层的参数的梯度变得非常小。你可以说,即使输入层和权重的参数值发生了较大的变化,这对输出的影响也非常小。
我给你提供这些细节是因为你在使用 sigmoid 函数时也可能会遇到同样的问题。最基本的一点是,消失梯度问题依赖于你选择的激活函数。Sigmoid 函数以非线性方式将输入压缩到一个小范围的输出。如果你给 sigmoid 函数一个实数,它会将该数值压缩到 [0,1] 的范围内。因此,输入空间中的大部分区域会映射到一个非常小的范围。即使输入参数发生了很大的变化,输出也会产生非常小的变化,因为该区域的梯度很小。对于 sigmoid 函数,当神经元接近 0 或 1 时,该区域的梯度非常接近于零。在反向传播时,这个局部梯度将与每一层输出门的梯度相乘。因此,如果第一层映射到一个很大的输入区域,我们就会得到一个非常小的梯度以及非常小的第一层输出变化。这个小的变化会传递到下一层,并在第二层输出中产生更小的变化。如果我们有一个深度神经网络(DNN),经过几层后,输出就没有任何变化了。这就是 sigmoid 激活函数的问题。
你可以通过以下链接详细了解消失梯度问题:
ayearofai.com/rohan-4-the-vanishing-gradient-problem-ec68f76ffb9b
低收敛速度:由于消失梯度问题,使用 sigmoid 激活函数的人工神经网络(ANN)有时会收敛得非常慢。
如果你真的想深入了解消失梯度问题,那么你可以查看这个链接:
cs224d.stanford.edu/notebooks/vanishing_grad_example.html
非零中心函数:Sigmoid 函数不是一个零中心的激活函数。这意味着 sigmoid 函数的输出范围是 [0,1],即函数的输出值始终为正,这使得权重的梯度要么全是正的,要么全是负的。这导致梯度更新在不同方向上走得过远,从而使优化变得更加困难。
由于这些限制,最近在 DNN 中不再使用 sigmoid 函数。虽然你可以使用其他函数来解决这些问题,但你也可以只在 ANN 的最后一层使用 sigmoid 激活函数。
TanH
为了克服 sigmoid 函数的问题,我们将引入一种叫做双曲正切函数(TanH)的激活函数。TanH 的方程如 图 9.34 所示:

图 9.34:Tanh 激活函数方程(图片来源:https://cdn-images-1.medium.com/max/800/1*HJhu8BO7KxkjqRRMSaz0Gw.png)
这个函数将输入区域压缩到[-1 到 1]的范围内,因此它的输出是零中心的,这使得优化过程更容易。这个函数也有梯度消失问题,所以我们需要查看其他激活函数。
ReLu 及其变种
整流线性单元(ReLu)是业界最流行的函数。请参见图 9.35中的方程:

图 9.35:ReLu 激活函数方程(图片来源:https://cdn-images-1.medium.com/max/800/1*JtJaS_wPTCshSvAFlCu_Wg.png)
如果你看到 ReLu 的数学方程,你会知道它就是max(0,x),这意味着当x小于零时,值为零,而当x大于或等于零时,值则是斜率为1的线性函数。一位名为 Krizhevsky 的研究者在图像分类方面发表了一篇论文,指出他们使用 ReLu 作为激活函数时,收敛速度是以前的六倍。你可以通过点击www.cs.toronto.edu/~fritz/absps/imagenet.pdf阅读这篇研究论文。这个函数很简单,没有复杂的计算,且与 sigmoid 和 TanH 相比,计算开销更小。这也是这个函数学习速度更快的原因。除此之外,它还没有梯度消失问题。
我们以前会在 DNN 的每一层中应用激活函数。如今,ReLu 被用于大多数 DNN,但它只应用于 DNN 的隐藏层。如果你正在解决分类问题,则输出层应使用 softmax,因为 softmax 函数可以为每个类别提供概率。在 word2vec 算法中,我们使用了 softmax 激活函数。在回归问题中,输出层应使用线性函数,因为信号不发生变化。
除了 ReLu 的所有这些优点之外,它还有一个问题:神经网络的一些单元可能会很脆弱,在训练过程中死亡,这意味着流经 ReLu 神经元的大梯度可能会导致权重更新,从而使它在任何数据点上都不会再激活。因此,从那时起流经它的梯度将始终为零。为了克服 ReLu 的这一限制,引入了 ReLu 的一个变种——Leaky ReLu。当x小于零时(x<0),Leaky ReLu 具有一个小的负斜率。请参见图 9.36:

图 9.36:Leaky ReLu(图片来源:http://wangxinliu.com/images/machine_learning/leakyrelu.png)
还有另一个变种叫做maxout,它是 ReLu 和 Leaky ReLu 的广义形式,但它将每个神经元的参数翻倍,这是一个缺点。
现在你已经了解了足够的激活函数,那么应该使用哪一个呢?答案是 ReLu,但如果有太多神经元死亡,那么使用 Leaky ReLu 或 maxout。这些激活函数应用于隐藏层。对于输出层,如果你正在解决分类问题,使用 softmax 函数;如果你正在解决回归问题,使用线性激活函数。sigmoid 和 TanH 不应该在 DNN 中使用。这是一个相当有趣的研究领域,仍有很大的空间来提出更好的激活函数。
这里有其他一些激活函数,你可以了解一下:恒等函数、二元阶跃函数、ArcTan 等等。在这里,我们将讨论第三个重要概念——损失函数。
损失函数
有时,损失函数也被称为代价函数或误差函数。损失函数给我们提供了一个衡量 ANN 在给定训练样本上的表现好坏的依据。因此,首先我们定义误差函数,并在开始训练 ANN 时得到输出。我们将生成的输出与作为训练数据一部分的期望输出进行比较,并计算该误差函数的梯度值。我们将误差梯度反向传播到网络中,以便更新现有的权重和偏置值,优化我们的输出。误差函数是训练的核心部分。有各种误差函数可供选择。如果你问我应该选择哪个误差函数,那么没有具体的答案,因为所有 ANN 的训练和优化都是基于这个损失函数的。所以它取决于你的数据和问题陈述。如果你问某人他们在 ANN 中使用了哪个误差函数,那么间接地你是在询问他们训练算法的整体逻辑。无论你使用哪个误差函数,确保该函数是可微的。我列出了一些最常见的误差函数:
-
二次损失函数,也叫做均方误差或平方和误差
-
交叉熵损失函数,也叫做伯努利负对数似然或二元交叉熵
-
库尔巴克-莱布勒散度,也叫做信息散度、信息增益、相对熵或KLIC
-
除了这三个,还有许多其他损失函数,如指数损失、赫林格距离、广义的库尔巴克-莱布勒散度和板仓-斋藤距离
通常,我们在回归问题中使用平方和误差,而在分类任务和类别数据中使用交叉熵。
我们已经了解了开发人工神经网络(ANN)最重要的数学和理论概念。在下一部分,我们将看到如何实现我们的第一个 ANN。让我们进入实现部分吧。
ANN 的实现
在本节中,我们将使用numpy作为依赖项,在 Python 中实现我们的第一个人工神经网络(ANN)。在实现过程中,你可以理解梯度下降、激活函数和损失函数是如何被整合进我们的代码中的。除此之外,我们还将看到反向传播的概念。
我们将看到带有反向传播的单层神经网络的实现。
带有反向传播的单层神经网络
在这里,我们首先会看到反向传播的概念,然后我们将开始编写代码,我会在编程时进行讲解。
反向传播
在单层神经网络中,我们将输入传递到第一层。这些层之间的连接有一些权重。我们使用输入、权重和偏差,并将它们相加。这个和通过激活函数后产生输出。这个步骤非常重要,生成的输出应与实际期望输出进行比较。根据误差函数,计算误差。接下来,使用误差函数的梯度来计算误差梯度。这个过程和我们在梯度下降部分看到的相同。误差梯度指示了如何优化生成的输出。误差梯度回传到 ANN 中,开始更新权重,以便在下一次迭代中得到更好的输出。将误差梯度回传到 ANN 中,更新权重,以生成更准确输出的过程被称为反向传播。简而言之,反向传播是一种流行的训练技术,通过梯度下降来更新权重,从而训练神经网络。
其他计算和数学部分将在编程部分展示。现在让我们编写自己的单层前馈神经网络,并实现反向传播。
首先,我们将定义主函数和抽象步骤。在这里,我们将给定输入和输出值。由于我们的数据是有标签的,这属于监督学习的例子。第二步是训练,我们将重复训练 10,000 次。在训练开始时,我们会设置一个随机权重,并根据激活函数和误差函数调整权重。请参考图 9.37:

图 9.37:单层人工神经网络(ANN)主函数的代码片段
在这里,我们使用 sigmoid 作为激活函数。我们将使用 sigmoid 的导数来计算 sigmoid 曲线的梯度。我们的误差函数是将实际输出与生成输出做简单的减法。我们将误差值与梯度相乘,以获得误差梯度,这有助于我们调整神经网络的权重。更新后的权重和输入再次通过 ANN,计算 sigmoid 曲线的梯度下降和误差梯度,并调整权重,直到我们得到最小的误差。请参考图 9.38和图 9.39:

图 9.38:单层人工神经网络(ANN)的代码片段
请参考以下代码片段:

图 9.39:ANN 的代码片段
当你运行代码时,你将获得以下结果。参考图 9.40:

图 9.40:单层 ANN 的输出片段
练习
使用numpy作为依赖,构建一个三层深度 ANN。(提示:在单层 ANN 中,我们使用了单层,而在这里,你将使用三层。反向传播通常使用递归求导,但是在我们的一层示例中没有递归。所以你需要应用递归求导。)
深度学习和深度神经网络
现在,只需从 ANN 转到 DNN。在接下来的部分,我们将讨论深度学习、DNN 的架构,并比较 DL 和 NLP 的 ML 方法。
回顾 DL
我们已经看到了关于 DL 的一些基本细节。这里的目的是简单回顾一下内容作为小小的复习。ANN 不仅仅是两层或三层,而是有许多层的深度神经网络,这种网络叫做 DNN。当我们在大量数据上使用大量计算能力训练多层深度神经网络时,我们称这种过程为深度学习。
让我们看看深度神经网络的架构。
DNN 的基本架构
在这一部分中,我们将看到 DNN 的架构。图示看起来很简单,并通过一些很酷的数学公式定义,例如激活函数、隐藏层的激活函数、损失函数等等。在图 9.41中,你可以看到 DNN 的基本架构:

图 9.41:DNN 的架构(图片来源:https://cdn-images-1.medium.com/max/800/1*5egrX--WuyrLA7gBEXdg5A.png)
那么,为什么我们使用多层深度神经网络?这是有某些原因的吗?拥有多个层次有什么重要意义?
让我解释一下为什么我们使用多层 DNN。假设作为一名编码员,你想开发一个识别水果图像的系统。现在你有一些橙子和苹果的图像,你开发了一个逻辑,例如可以通过水果的颜色来识别图像,并且你还添加了形状作为识别参数。你写了一些代码,并准备好了结果。现在如果有人告诉你,我们还有黑白图像,你就需要重新编写你的代码。一些图像的种类对你来说,作为人类,编码起来太复杂了,尽管你的大脑在识别实际水果名称方面非常擅长。那么,如果你有这样一个复杂的问题,并且你不知道如何编码,或者你对机器解决问题所需要的特征或参数了解不多,那么你就需要使用深度神经网络。这里有几个原因,接下来将提到:
-
DNN 是通过抽象人脑工作原理的概念推导出来的。
-
使用 DNN 时,我们改变了编码的方式。最初,我们将颜色、形状等特征提供给机器,让它在给定的图像中识别水果名称,但使用 DNN 和深度学习(DL)后,我们将许多示例提供给机器,机器会自行学习特征。之后,当我们提供一张新的水果图像给机器时,它将预测水果的名称。
现在你可能想了解 DNN 如何自我学习特征,下面我们列出一些要点:
-
DNN 使用多个非线性处理单元的级联层,用于特征提取和转换。每一层的输出都作为下一层的输入,这一过程与人脑如何传递信息从一个神经元到另一个神经元非常相似。所以我们试图借助 DNN 实现相同的结构。
-
在深度学习(DL)中,特征通过多层表示学习,借助 DNN 的帮助,较高层次的特征或表示是从较低层次的特征中衍生出来的。因此,我们可以说,在 DNN 中提取特征或表示的概念是层次化的。我们通过这些低层次的想法学习新知识,并尝试学习额外的东西。我们的脑袋也以层次化的方式使用和推导概念。这些不同层次的特征或表示与不同的抽象层次相关。
-
DNN 的多层结构帮助机器推导层次化的表示,这也是架构中有多个层次的重要性所在。
-
借助 DNN 和数学概念,机器能够模拟人脑的一些处理过程。
-
深度学习(DL)可以应用于监督数据集和无监督数据集,来开发 NLP 应用,如机器翻译、摘要生成、问答系统、文章生成、图像标签等。
接下来我们将进入下一部分,讨论深度学习在 NLP 中的需求。
深度学习在 NLP 中的应用
NLP 的早期阶段基于规则系统,许多应用的早期原型也是基于规则系统,因为我们没有大量的数据。如今,我们应用机器学习(ML)技术来处理自然语言,采用统计和基于概率的方法,其中我们将单词表示为一热编码格式或共现矩阵。
在这种方法中,我们主要获得的是句法表示,而非语义表示。当我们尝试基于词汇的方式,如词袋模型、n-gram 等时,我们无法区分某些上下文。
我们希望所有这些问题都能通过 DNN 和 DL 解决,因为如今我们拥有可以利用的大量数据。我们已经开发出像 word2vec、GloVe 等优秀的算法,用于捕捉自然语言的语义方面。除此之外,DNN 和 DL 还提供了一些很酷的功能,列举如下:
-
表达能力: 这一能力表达了机器能多好地对一个通用函数进行逼近。
-
可训练性: 这一能力对于自然语言处理应用非常重要,它表示深度学习系统能够多好、多快地学习给定问题,并开始生成有意义的输出。
-
泛化能力: 这表示机器能多好地泛化给定任务,从而能够为未见过的数据预测或生成准确的结果。
除了前述的三种能力,深度学习还提供了其他能力,例如可解释性、模块化、可迁移性、延迟、对抗稳定性和安全性。
我们知道,语言是非常复杂的事物,有时我们也不知道如何解决某些自然语言处理问题。其背后的原因是,世界上有许多语言,它们各自有自己的句法结构、词汇用法和含义,这些用法和含义不能以相同的方式在其他语言中表达。所以我们需要一些能够帮助我们泛化问题并给出好结果的技术。所有这些原因和因素将我们引导到使用深度神经网络(DNN)和深度学习(DL)来解决自然语言处理应用的问题。
现在让我们看看经典自然语言处理技术与深度学习自然语言处理技术之间的区别,因为这将帮助我们理解深度学习如何更有效地帮助我们解决自然语言处理领域相关问题。
经典自然语言处理与深度学习自然语言处理技术的区别
在本节中,我们将比较经典的自然语言处理技术与深度学习技术。因此,开始吧!请参考图 9.42:

图 9.42:经典自然语言处理方法(图片来源:s3.amazonaws.com/aylien-main/misc/blog/images/nlp-language-dependence-small.png)
请参考图 9.43了解深度学习技术:

图 9.43:深度学习方法用于自然语言处理(图片来源:s3.amazonaws.com/aylien-main/misc/blog/images/nlp-language-dependence-small.png)
在经典的自然语言处理技术中,我们在早期阶段对数据进行了预处理,然后从数据中生成特征。在接下来的阶段,我们使用手工制作的特征,这些特征是通过命名实体识别(NER)工具、词性标注器和解析器生成的。我们将这些特征作为输入提供给机器学习算法并训练模型。我们会检查准确性,如果准确性不好,我们会优化一些算法参数并尝试生成更准确的结果。根据自然语言处理应用的不同,您可以包括检测语言的模块,然后生成特征。
现在让我们来看看 NLP 应用中的深度学习技术。在这个方法中,我们会对已有数据进行一些基础的预处理。然后我们将文本输入数据转换为稠密向量的形式。为了生成这些稠密向量,我们将使用诸如 word2vec、GloVe、doc2vec 等词嵌入技术,并将这些稠密向量嵌入输入到 DNN 中。在这里,我们没有使用手工特征,而是根据 NLP 应用使用不同类型的 DNN,例如在机器翻译中,我们使用一种叫做序列到序列模型(sequence-to-sequence model)的 DNN 变种;而在摘要生成中,我们使用另一种变种,即长短时记忆单元(LSTMs)。DNN 的多层结构概括了目标,并学习达成目标的步骤。在这个过程中,机器学习到的是层次化的表示,并给出结果,我们再根据需要验证并调整模型。
如果你真的想看看不同变种的 DNN 编码,可以使用这个 GitHub 链接:
github.com/wagamamaz/tensorflow-tutorial
下一节是本章中最有趣的部分。我们将构建两个主要应用:一个是自然语言理解(NLU),另一个是自然语言生成(NLG)。我们将使用 TensorFlow 和 Keras 作为主要依赖库来编写示例代码。在编码过程中,我们将理解 DNN 的变种,如序列到序列(sequence-to-sequence)和长短时记忆网络(LSTM),以便更好地理解。
你猜我们将要构建什么?我们将构建一个机器翻译器,作为 NLP 应用的一部分,还将从食谱中生成摘要。那么,让我们进入编码部分吧!我会给你一些有趣的练习!
深度学习技术与自然语言理解(NLU)
本节内容以编码为基础,我会在过程中解释相关概念。我们在这里构建的应用是自然语言理解(NLU)中的一个主要应用。
人类有许多种语言在使用、书写或阅读。你曾经尝试过学习一门新语言吗?如果有,你就会知道掌握一门新语言的口语或书写技巧是多么困难。你有没有想过谷歌翻译是如何用来翻译语言的?如果你感兴趣,那就让我们开始使用深度学习技术开发一个机器翻译应用吧。别担心关于我们会使用什么类型的 DNN 这种问题,因为我会详细为你解释。所以,让我们开始翻译吧!
请注意,深度学习(DL)需要大量的计算能力,因此我们不会实际训练模型,尽管我会提供关于训练代码的详细信息,我们将使用已训练好的模型在本地复制结果。给你一个概念:谷歌为了训练语言翻译模型,使用了 100 个 GPU 连续训练一周。所以我们会通过代码,理解概念,使用已经训练好的模型,看看结果。
如果你想使用特定版本的 TensorFlow,你可以使用以下命令。如果你想安装 TensorFlow 0.12 版本,可以使用以下命令进行安装:
$ export TF_BINARY_URL=https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.12.1-cp27-none-linux_x86_64.whl
$ sudo pip install --upgrade $TF_BINARY_URL
如果你想使用 TensorFlow 版本,那么在运行代码时,请更新你的导入语句。你可以使用以下简单命令来安装 CPU 版本的 TensorFlow。我只使用 GPU 版本:
$ pip install tensorflow
如果你想在 GPU 上运行,你可以使用云平台,比如 Google Cloud、AWS 或其他任何云平台,或者你需要一台支持 GPU 的计算机。要安装 GPU 版本的 TensorFlow,你可以通过这个链接进行操作:
机器翻译
机器翻译(MT)是自然语言理解(NLU)领域中一个广泛应用的技术。研究人员和科技巨头们正在进行大量实验,力图打造一个能够翻译任何语言的单一 MT 系统。这个 MT 系统被称为通用机器翻译系统。所以,长远目标是我们希望建立一个能够将英语翻译成德语的 MT 系统,并且同一个 MT 系统还应该能够将英语翻译成法语。我们正努力开发一个能够帮助我们翻译任何语言的系统。接下来,让我们谈谈迄今为止,研究人员在构建通用机器翻译系统方面所做的努力和实验。
1954 年,首次展示了机器翻译 demo,它能够在俄语和英语之间翻译 250 个单词。这是一种基于词典的方法,使用源语言和目标语言之间的单词映射关系进行翻译。翻译是逐词进行的,这种方法无法捕捉句法信息,意味着翻译的准确性较差。
下一个版本是中介语言翻译;它首先将源语言转换成一个中介语言,用来编码并表示源语言的某些规则,如语法、句法等,然后再从中介语言生成目标语言。与第一个版本相比,这种方法有一定的优势,但很快这种方法就被统计机器翻译(SMT)技术取代了。
IBM 采用了这种 SMT 方法;他们将文本分成若干段落,然后将其与对齐的双语语料库进行比较。之后,利用统计技术和概率,选择最可能的翻译结果。
世界上最常用的 SMT 是 Google 翻译,最近,Google 发布了一篇论文,指出他们的机器翻译系统使用深度学习来生成出色的翻译结果。我们使用的是 TensorFlow 库,它是 Google 提供的一个开源深度学习库。我们将通过编码来了解如何使用深度学习进行机器翻译。
我们使用电影字幕作为数据集。这个数据集包括德语和英语两种语言。我们正在构建一个将德语翻译成英语,反之亦然的模型。你可以从 opus.lingfil.uu.se/OpenSubtitles.php 下载数据。在这里,我使用的是数据的 pickle 格式。使用 pickle(这是一个 Python 依赖库),我们可以序列化我们的数据集。
首先,我们使用 LSTM 网络,它用于记住长期和短期的依赖关系。我们使用 TensorFlow 内置的 data_utils 类来预处理数据。接下来,我们需要定义词汇表的大小,以便训练模型。在这里,我们的数据集词汇表较小,因此我们考虑数据集中的所有单词,但我们定义词汇表大小为 30,000 个单词,也就是一个较小的训练数据集。我们将使用 data_utils 类从数据目录读取数据。该类为我们提供了来自两种语言的分词和格式化单词。然后我们定义 TensorFlow 的占位符,它们是输入的编码器和解码器。这两个占位符将是表示离散值的整数张量,它们被嵌入到密集表示中。我们将把词汇表中的单词输入到编码器,并将学习到的编码表示传递给解码器。你可以查看该代码,访问这个 Github 链接:github.com/jalajthanaki/NLPython/tree/master/ch9/MT/Machine_Translation_GR_EN.
现在我们可以构建我们的模型了。你可以在图 9.44、图 9.45 和 图 9.46 中查看代码片段:

图 9.44:机器翻译代码片段

图 9.45:机器翻译代码片段

图 9.46:机器翻译代码片段
现在让我们理解这个基于编码器和解码器的系统。谷歌最近发布了一篇论文,讨论了他们在翻译系统中集成的系统,即 神经机器翻译(NMT)。它是一个基于编码器-解码器的模型,具有新的 NMT 架构。早期,谷歌翻译是从语言 A 翻译到英语,再翻译到语言 B。现在,谷歌翻译可以直接从一种语言翻译到另一种语言。请参考图 9.47:

图 9.47:基于 LSTM 的编码器和解码器架构(图片来源:https://camo.githubusercontent.com/242210d7d0151cae91107ee63bff364a860db5dd/687474703a2f2f6936342e74696e797069632e636f6d2f333031333674652e706e67 )
现在,有了 NMT,就不需要记住逐句翻译了。在 NMT 的帮助下,翻译系统可以编码句子的语义。这种编码是泛化的,因此它可以从中文翻译成英文,从法文翻译成英文,也能翻译像韩文到日文这样的语言对,即使这些翻译对之前从未见过。
现在我们可以使用这个简单的基于 LSTM 的编码器-解码器架构吗?我们将看到该架构的一些基本细节。请参考图 9.48:

图 9.48:用于翻译的 LSTM 循环神经网络(图片来源:https://smerity.com/articles/2016/google_nmt_arch.html)
我们可以使用 LSTM 循环神经网络来编码 A 语言的句子。RNN 会拆分一个隐藏状态S,如图 9.48所示。这个S表示句子的向量化内容。之后,我们将这个向量化形式传递给解码器,解码器逐词生成翻译后的 B 语言句子。理解这个架构不难,对吧?然而,这个架构也有一些缺点。该架构的内存有限。LSTM 的隐藏状态S就是我们试图将整个待翻译句子塞入其中的地方,但S通常只有几百个浮点数那么长。我们需要把句子适配到这个固定的维度,如果我们强行将句子适配到这个固定的维度,那么我们的网络会变得更具损失性,这意味着如果我们强行把句子压缩到一个固定的维度大小,部分信息就会丢失。我们本可以增加 LSTM 的隐藏层大小,因为它们的主要功能是记住长期依赖关系,但如果我们增加隐藏层大小,训练时间就会呈指数增长。因此,我们不应该使用一个需要大量时间才能收敛的架构。
我们将介绍另一种架构——基于注意力的编码器-解码器模型。作为人类,当我们看到一个长句子并且需要翻译时,我们可能会回头看几次源句子,以确保我们捕捉到了所有的细节。人类大脑会反复关注源句子中的相关部分。我们希望神经网络为我们做同样的事情,让它存储并参考 LST 的先前输出。这增加了我们模型的存储量,同时不改变 LSTM 的功能。请参考图 9.49:

图 9.49:基于注意力的 NMT 架构(图片来源:https://heuritech.files.wordpress.com/2016/01/trad_attention1.png?w=470)
一旦我们从编码器获得了 LSTM 的输出并存储下来,我们就可以查询每个输出,询问它与当前解码器中发生的计算的相关性。每个编码器输出都会得到一个相关性评分,我们可以使用 softmax 激活函数将其转换为概率分数。请参考图 9.50:

图 9.50:SoftMax 函数用于生成相关性分数(图片来源: https://smerity.com/)
然后,我们提取一个上下文向量,它是根据编码器输出的加权和,权重取决于它们的相关性。现在让我们回到代码。为了实现这个基于注意力的功能,我们将使用 TensorFlow 内置的嵌入式注意力序列到序列函数。该函数将编码器和解码器的输入作为参数,以及一些额外的超参数。这个函数与我们讨论的架构相同。TensorFlow 有一些非常棒的内置模型,我们可以轻松使用。请参考 图 9.51 查看代码片段:

图 9.51:基于注意力的序列到序列模型的代码片段
请参考 图 9.52 查看前面代码的输出:

图 9.52:机器翻译的输出
你还可以通过这个链接 www.tensorflow.org/tutorials/seq2seq 来运行机器翻译示例,而无需编写自定义代码。这个教程是法语到英语和英语到法语的翻译系统示例。这是一种非常简单的运行方式。我建议你使用这种方法,因为自定义代码一开始会更加复杂。
-
首先,你需要从这个链接下载 2.4GB 的训练数据集
giga-fren.tar:http://www.statmt.org/wmt10/training-giga-fren.tar。 -
现在你需要将这些数据存储在
data_dir目录中,并定期保存你训练的模型。为此,我们需要在train_dir内创建一个检查点目录。 -
然后,你可以执行以下命令:
python translate.py --data_dir [your_data_directory] --train_dir [checkpoints_directory] --en_vocab_size=40000 --fr_vocab_size=40000
- 如果前面的命令占用了大量 GPU 内存,可以执行这个命令:
python translate.py --data_dir [your_data_directory] --train_dir [checkpoints_directory] --size=256 --num_layers=2 -- steps_per_checkpoint=50
- 一旦你的周期(epoch)达到 340 K,批量大小为 64,你可以在此之前使用模型进行翻译,不过准确率会如下所示:
python translate.py --decode --data_dir [your_data_directory] --train_dir [checkpoints_directory]Reading model parameters from /tmp/translate.ckpt-340000> Who is the president of the United States? Qui est le président des États-Unis ?
我们的德语到英语(GR_EN)翻译模型给出了相当不错的结果,我们仅进行了一个训练回合,但如果我们真的想获得与谷歌翻译系统相同的准确性,那么我们需要使用高计算能力,如连续几周使用 100 个 GPU 来训练该模型。在这里,我们肯定不会实现这个模型,但我会解释它的工作原理。那么,让我们从概念上深入了解。
如果输出没有足够的上下文信息来表示编码后的源句子,那么模型将无法给我们提供良好的翻译结果。在这种情况下,我们需要提供未来词汇的信息,这样编码器的输出就会由左侧和右侧的词汇决定。作为人类,我们使用这种完整的上下文来理解句子的意思。在机器层面,通过包含双向编码器来实现这一点,使其包含两个循环神经网络(RNN)。一个从前往后遍历句子,另一个从后往前遍历。因此,对于每个单词,它将连接产生双侧上下文向量的输出。参见图 9.53:

图 9.53:用于机器翻译的双向 RNN 架构(图片来源:http://img.yantuwan.com/weiyuehao.php?http://mmbiz.qpic.cn/mmbiz_png/KmXPKA19gW81aVM2Gdgrxfsa0vR4YVib2wCIHNabCic1Hr144r4PAuSDMLNMHGgWz12GtibYdgF1jTvHtuniauHYSw/0?wx_fmt=png)
谷歌在模型中加入了大量的层次结构,以及包含一个双向 RNN 层和七个单向层的编码器。解码器有八个单向 RNN 层。如果你增加更多的层次,训练时间会相应增加。在这里,我们仅使用一个双向层。如果所有层都是双向的,那么整个层次必须在其他层的计算开始之前完成其计算。借助单向层,我们可以并行执行计算。
这完全是关于机器翻译的内容。我们已经生成了机器翻译的输出,但仍然有改进的空间。通过使用深度学习(DL),我们将构建一个通用的单一机器翻译系统。
现在让我们开始基于自然语言生成(NLG)的下一部分编程。
深度学习技术与自然语言生成(NLG)
在这一部分,我们将构建一个非常简单但直观的自然语言生成(NLG)应用程序。我们将从简短的文章中生成一句话的摘要。在这一部分,我们将详细探讨摘要生成的所有细节。
这个应用程序需要大量的训练时间,因此你可以将模型放到 CPU 上训练,同时可以做一些其他任务。如果你没有其他任务,那让我给你分配一个任务。
练习
尝试想一想,如何仅通过提供一些起始字符序列就能生成一篇维基百科文章。别误会我!我很认真!你真的需要认真思考这个问题。这是你可以使用的数据集:einstein.ai/research/the-wikitext-long-term-dependency-language-modeling-dataset。跳转到下载部分并下载名为“Download WikiText-103 word level (181 MB)”的该数据集。
(提示:参见此链接,github.com/kumikokashii/lstm-text-generator。)
不用担心;理解了摘要的概念后,你就可以尝试这个了。让我们开始摘要之旅吧!
菜谱摘要器和标题生成
在进入代码之前,我想先给你一些关于摘要的简要背景。架构和其他技术部分将在编码过程中逐步理解。
语义在 NLP 中至关重要。随着数据密度的增加,信息量也在增加。如今,周围的人确实希望你能在短时间内高效地说出最重要的事情。
文本摘要起源于 90 年代。加拿大政府开发了一个名为预测生成器(FoG)的系统,利用天气预报数据生成摘要。那时采用的是基于模板的方法,机器只需要填充某些特定的值。让我给你举个例子,周六将是晴天,降雨概率为 10%。其中的晴天和10%其实是 FoG 生成的。
其他领域包括金融、医疗等。在当今世界,医生发现总结患者的病史非常有用,他们可以高效、有效地进行诊断。
以下是两种类型的摘要:
-
抽取式
-
抽象性
过去的大多数摘要工具都是抽取式的;它们从文章中选择现有的词语集合来创建摘要。而我们人类做的更多;也就是说,当我们总结时,我们会构建一个内部的语义表示,这个表示基于我们所阅读的内容。利用这个内部的语义表示,我们可以总结文本。这种摘要方式被称为抽象摘要。
那么,让我们使用 Keras 构建一个抽象摘要工具。
Keras 是一个高层封装的 TensorFlow 和 Theano 工具。这个示例需要多个 GPU 支持,训练时间超过 12 小时。如果你想在本地重现结果,那将需要大量的计算资源。
以下是编码部分的步骤。这里,我们首次使用 Python 3:
-
克隆 GitHub 仓库:
-
已初始化子模块:
git submodule update --init -recursive
- 进入文件夹:
python src/config.p
- 安装依赖:
pip install -r requirements.txt
- 设置目录:
python src/config.py
- 从网页抓取菜谱或使用这个链接中的现有菜谱:
wget -P recipe-box/data https://storage.googleapis.com/recipe- box/recipes_raw.zip; unzip recipe-box/data/recipes_raw.zip -d recipe-box/data
- 对数据进行分词:
python src/tokenize_recipes.py
-
使用
GloVe 向量初始化词嵌入:- 获取 GloVe 向量训练模型:
wget -P data http://nlp.stanford.edu/data/glove.6B.zip;
unzip data/glove.6B.zip -d data
-
- 初始化嵌入:
python src/vocabulary-embedding.py
- 训练模型:
python src/train_seq2seq.py
- 进行预测:
use src/predict.ipynb
这里,我们使用 GloVe 进行向量化,因为我们希望获得全球级别的词语表示用于摘要,并且我们使用的是序列到序列模型(Seq2Seq 模型)来训练数据。Seq2Seq 就是我们在机器翻译部分讨论的模型。查看图 9.54、图 9.55和图 9.56中的代码片段,训练完成后,你可以在图 9.57中看到输出:

图 9.54:分词代码片段
请参考以下图示以使用 GloVe 进行词汇构建:

图 9.55:使用 GloVe 进行词汇构建
请参考以下图示以训练模型:

图 9.56:模型训练
以下图示给出了示例:

图 9.57:模型的预测结果
我知道,摘要示例将需要大量的计算能力,可能会出现您的本地机器内存(RAM)不足以运行此代码的情况。在这种情况下,不用担心;有多种云服务选项可供使用。您可以使用 Google Cloud、Amazon Web Services(AWS)或任何其他服务。
现在,您已经对自然语言理解(NLU)和自然语言生成(NLG)应用有了足够的了解。我还在此 GitHub 链接中放置了一个与 NLG 领域相关的应用:
github.com/tensorflow/models/tree/master/im2txt
该应用程序为图像生成标题;这是一种计算机视觉与 NLG 结合的应用。相关的详细信息在 GitHub 上,所以也可以查看这个示例。
在下一节中,我们将讨论基于梯度下降的优化策略。TensorFlow 为我们提供了一些梯度下降算法的变种。一旦我们了解这些变种的工作原理以及它们各自的优缺点,那么选择最适合优化我们深度学习算法的选项将变得很容易。因此,让我们来理解基于梯度下降的优化。
基于梯度下降的优化
在本节中,我们将讨论 TensorFlow 提供的基于梯度下降的优化选项。最初,可能不清楚应该使用哪个优化选项,但随着您了解深度学习算法的实际逻辑,您会变得更加清晰。
我们使用基于梯度下降法的方法来开发智能系统。通过这种算法,机器可以学习如何从数据中识别模式。在这里,我们的最终目标是获得局部最小值,而目标函数是机器将做出的最终预测或机器生成的结果。在梯度下降算法中,我们并不专注于在第一步就实现目标函数的最佳最终目标,而是通过迭代或反复采取小步骤,选择中间的最佳选项,以帮助我们实现最终的最佳选项,也就是局部最小值。这种有根据的猜测与检验方法有助于获得局部最小值。当深度学习算法获得局部最小值时,算法可以生成最佳结果。我们已经看过了基础的梯度下降算法。如果你遇到过拟合或欠拟合的情况,你可以通过不同类型的梯度下降来优化算法。梯度下降有多种变体,可以帮助我们生成理想的局部最小值,控制算法的方差,更新我们的参数,并最终使我们的机器学习或深度学习算法收敛。让我们举个例子。如果你有函数 Y = X²,那么该函数的偏导数是 2X。当我们随机猜测起始值,并以 X = 3 开始时,Y = 2(3) = 6,为了获得局部最小值,我们需要朝负方向移动——所以 Y = -6。在第一次迭代后,如果你猜测 X = 2.3,那么 Y = 2(2.3) = 4.6,我们需要再次朝负方向移动——Y = -4.6——因为我们得到的是一个正值。如果得到负值,则需要朝正方向移动。经过若干次迭代后,Y 的值会接近零,这就是我们的局部最小值。现在让我们从基础的梯度下降法开始,探索梯度下降法的各种变体。
基础梯度下降法
在基础梯度下降法中,我们计算整个训练数据集中的参数相对于损失函数的梯度,并且为了执行一次更新,我们需要计算整个数据集的梯度。为了进行一次更新,我们必须考虑整个训练数据集以及所有参数,所以这个过程非常慢。你可以在图 9.58中看到这个方程:

图 9.58:梯度下降的方程(图片来源:http://sebastianruder.com/optimizing-gradient-descent/index.html#challenges)
你可以在图 9.59中找到理解此方法的示例逻辑代码:

图 9.59:梯度下降的示例代码(图片来源:http://sebastianruder.com/optimizing-gradient-descent/index.html#challenges)
由于这种方法较慢,我们将引入一种新的技术,叫做随机梯度下降法。
随机梯度下降法
在这个技术中,我们会为每个训练样本和标签更新参数,因此我们只需要为训练数据集添加一个循环,并且这种方法相比基础梯度下降法更新参数更快。你可以在图 9.60中看到公式:

图 9.60:随机梯度下降公式(图片来源:http://sebastianruder.com/optimizing-gradient-descent/index.html#challenges)
你可以在图 9.61中找到用于理解的示例逻辑代码:

图 9.61:随机梯度下降示例代码(图片来源:http://sebastianruder.com/optimizing-gradient-descent/index.html#challenges)
这种方法也有一些问题。该方法使得收敛变得复杂,有时更新参数的速度过快。算法可能会超过局部最小值并继续运行。为避免这个问题,引入了另一种方法,叫做小批量梯度下降。
小批量梯度下降
在这种方法中,我们将结合基础梯度下降法和随机梯度下降法的优点。我们将从训练数据集中取出一个子集作为小批量,并从中更新参数。这种类型的梯度下降法适用于基本的人工神经网络(ANN)。
你可以在图 9.62中看到公式:

图 9.62:小批量梯度下降公式(图片来源:http://sebastianruder.com/optimizing-gradient-descent/index.html#challenges)
你可以在图 9.63中找到用于理解的示例逻辑代码:

图 9.63:小批量梯度下降示例代码(图片来源:http://sebastianruder.com/optimizing-gradient-descent/index.html#challenges)
如果我们有一个高维数据集,那么我们可以使用一些其他的梯度下降方法;让我们从动量法开始。
动量
如果所有可能参数的值表面曲线在一个维度上比在另一个维度上陡峭得多,那么这种情况在局部最优附近非常常见。在这些场景中,SGD 会在坡度之间震荡。为了解决这个震荡问题,我们将使用动量法。你可以在图 9.64中看到公式:

图 9.64:动量公式(图片来源:http://sebastianruder.com/optimizing-gradient-descent/index.html#challenges)
如果你查看方程,我们将上一时间步的梯度方向的一部分加到当前步骤中,并将参数更新放大到正确的方向,这加速了我们的收敛,并减少了振荡。所以在这里,动量的概念与物理学中的动量概念相似。当达到局部最小值时,这种变种并不会减慢,因为此时动量较大。在这种情况下,我们的算法可能完全错过局部最小值,而这个问题可以通过 Nesterov 加速梯度来解决。
Nesterov 加速梯度
该方法由 Yurii Nesterov 发明。他试图解决动量技术中出现的问题。他已发布了一篇论文,你可以通过此链接查看:
你可以在图 9.65中看到该方程:

图 9.65:Nesterov 加速梯度的方程(图片来源:http://sebastianruder.com/optimizing-gradient-descent/index.html#challenges)
如你所见,我们做的是与动量法相同的计算,只是改变了计算顺序。在动量法中,我们计算梯度并按动量放大该方向的跳跃,而在 Nesterov 加速梯度法中,我们首先基于之前的动量进行跳跃,然后计算梯度,接着我们做一个修正,最终生成参数的更新值。这帮助我们更加动态地提供参数值。
Adagrad
Adagrad 代表自适应梯度。这种方法允许学习率基于参数进行自适应调整。该算法为不常出现的参数提供较大的更新,为频繁出现的参数提供较小的更新。你可以在图 9.66中看到该方程:

图 9.66:Adagrad 方程(图片来源:http://sebastianruder.com/optimizing-gradient-descent/index.html#challenges)
该方法为每个参数提供一个基于该参数过去梯度计算的不同学习率。在这里,我们无需手动调整学习率,尽管它有一个局限性。根据方程,学习率始终下降,因为分母中平方梯度的累积值始终为正,随着分母的增大,整个项会减少。有时,学习率变得非常小,导致机器学习模型停止学习。为了解决这个问题,Adadelta 方法应运而生。
Adadelta
Adadelta 是 Adagrad 的扩展。在 Adagrad 中,我们不断将平方根加入求和,从而导致学习率逐渐减小。与其将所有过去的平方根相加,我们将累积的过去梯度窗口限制为一个固定大小。
你可以在图 9.67中看到该方程:

图 9.67:Adadelta 的方程式(图片来源:sebastianruder.com/optimizing-gradient-descent/index.html#challenges)
正如你在方程式中看到的,我们将使用梯度的总和作为所有过去的平方梯度的衰减平均值。这里,给定时间戳的运行平均值 E[g²][t] 依赖于前一个平均值和当前梯度。
经过对所有优化技术的了解,你现在知道我们如何计算每个参数的个别学习率,如何计算动量,以及如何防止学习率衰减。不过,通过应用一些自适应动量,依然有改进的空间,这就引出了我们最后的优化方法——Adam。
Adam
Adam 代表自适应动量估计。在计算每个参数的学习率时,我们还可以单独存储每个参数的动量变化。
你可以在图 9.68中看到方程式:

图 9.68:Adam 的均值和方差(图片来源:sebastianruder.com/optimizing-gradient-descent/index.html#challenges)
首先,我们将计算梯度的均值,然后计算梯度的非中心方差,并使用这些值来更新参数。这就像 Adadelta 一样。你可以在图 9.69中看到 Adam 的方程式:

图 9.69:Adam 的方程式(图片来源:sebastianruder.com/optimizing-gradient-descent/index.html#challenges)
那么现在你可能想知道我们应该使用哪种方法;依我看,Adam 是最好的整体选择,因为它在性能上超越了其他方法。你也可以使用 Adadelta 和 Adagrad。如果你的数据是稀疏的,那么你应该避免使用 SGD、动量方法或 Nesterov。
人工智能与人类智能
在过去的一年中,你可能听到过这样的问题。在人工智能的世界里,这类问题已经变得非常常见。人们制造了一种炒作,认为人工智能将使人类消失,机器将夺走我们的一切权力。现在让我告诉你,这并不是真相。这类威胁听起来像是科幻故事。依我看,人工智能正处于高速发展的阶段,但其目的是补充人类并使人类生活更轻松。我们仍在探索宇宙中一些复杂且未知的真理,这些真理能帮助我们提供更多关于如何构建人工智能系统的洞见。所以,人工智能纯粹是为了帮助我们。人工智能肯定会让我们的生活充满惊喜,但它不会很快饱和。享受这个人工智能阶段,并以积极的方式为人工智能生态系统做出贡献。
有人担心人工智能会夺走我们的工作。它不会夺走你的工作,它会让你的工作变得更轻松。如果你是医生,想要对某份癌症报告给出最终意见,人工智能会帮助你。在信息技术(IT)行业,有人担心人工智能会取代程序员。如果你相信研究人员和科技公司很快就会建造出比人类更强大的机器,认为人工智能的转变很快会发生,机器会夺走我们的工作,那么你最好掌握机器学习(ML)、深度学习(DL)和人工智能相关的技能,以确保你还有工作做,也许你是这个星球上最后一个还有工作的人!我们假设人工智能会夺走一些工作,但这个人工智能生态系统也会创造出许多新的工作机会。所以不要担心!这场讨论可以继续下去,但我真的想给你们一点时间思考这个问题。
总结
恭喜大家!我们已经到达最后一章了!我非常感谢你们的努力。在这一章中,你们学到了很多东西,比如人工智能的各个方面,帮助你们理解为什么深度学习如今成为热门词汇。我们已经了解了人工神经网络(ANNs)的概念,学习了梯度下降、各种激活函数和损失函数等概念。我们还看到了深度神经网络(DNN)的架构和深度学习生命周期。我们还涉及了序列到序列模型的基础,并开发了机器翻译、标题生成和摘要等应用。我们还学习了基于梯度下降的优化技术。
接下来的部分是附录 A 到 C,这些内容将为你提供关于框架(如 hadoop、spark 等)的概述。你还可以看到这些框架以及其他工具和库的安装指南。除此之外,你还可以找到很多 Python 库的备忘单,如果你是 Python 新手,这些备忘单会非常有用。如果你真的想提升你的数据科学和自然语言处理(NLP)技能,我还提供了一些我的个人建议。我还在附录中提供了 Gitter 链接,你可以通过这些链接联系我,若有任何问题可以随时向我请教。
第十章:高级工具
本附录主要讨论各种框架如何在 NLP 应用程序中使用。我们将概述这些框架,并简要介绍它们的基本功能及其作用。我们不会详细查看每个框架的架构。此处的目的是让你了解可以一起使用的不同工具和框架,以便构建各种 NLP 应用程序。我们还将了解一些可帮助你开发仪表盘的可视化库。
Apache Hadoop 作为一个存储框架
Apache Hadoop 是一个广泛使用的框架。Hadoop 允许使用简单的编程模型在商品计算机集群上分布式处理大数据集。Hadoop 使用了 MapReduce 的概念。MapReduce 将输入查询划分为小部分,并对存储在 Hadoop 分布式文件系统 (HDFS) 上的数据进行并行处理。
Hadoop 具有以下特点:
-
它具有可扩展性
-
它具有成本效益
-
它提供了一个强大的生态系统
-
它提供更快的数据处理
Hadoop 可以作为 NLP 应用程序的存储框架。如果你想存储大量数据,那么可以使用一个多节点 Hadoop 集群,并将数据存储在 HDFS 上。因此,许多 NLP 应用程序会使用 HDFS 来存储它们的历史数据。Hadoop 将程序发送到数据上,数据会在本地处理。这个特性使得 Hadoop 具有较高的速度。需要注意的是,Hadoop 在磁盘级别执行操作,这比较慢,但我们通过并行执行操作,所以数据处理速度很快。现在,你可能会认为磁盘操作相比内存操作较慢,但我们有大量数据,这些数据一次是无法全部放入内存的。因此,采用在本地处理数据并通过多节点集群并行执行操作的方式,能给我们提供良好的吞吐量。
Hadoop 作为其核心架构的一部分,具有以下组件:
-
HDFS
-
MapReduce
-
YARN
-
Hadoop 常用工具
你可以在 图 01 中看到 Hadoop 的架构:

图 01:Hadoop 2.x YARN 架构(图片来源:github.com/zubayr/big_config/blob/master/hbase/hbase_tuning.md)
你可以在 图 02 中看到 Hadoop 生态系统:

图 02:Hadoop 生态系统(图片来源:s3.amazonaws.com/files.dezyre.com/images/blog/Big+Data+and+Hadoop+Training+Hadoop+Components+and+Architecture_1.png)
对于实时数据处理,Hadoop 稍显缓慢且效率较低。别担心!我们还有另一个框架可以帮助我们进行实时数据处理。
许多 NLP 应用程序使用 Hadoop 来存储数据,因为它能很好地处理数据。就个人而言,我曾使用 Hadoop 将我的语料库存储在 HDFS 上。然后,我使用 Spark MLlib 来开发 机器学习 (ML) 算法。对于实时数据处理,我使用了 Apache Flink。
为了实验目的,我为你提供了设置单节点 Hadoop 集群的步骤。相关的 GitHub 链接是:github.com/jalajthanaki/NLPython/blob/master/Appendix3/Installationdocs/App3_3_Hadoop_installation.md。
你可以在此文档中找到一些 Hadoop 的命令:
Apache Spark 作为处理框架
Apache Spark 是一个大规模数据处理框架。它是一个快速且通用的引擎。它是最快的处理框架之一。Spark 可以进行内存数据处理,也可以进行磁盘数据处理。
Spark 的重要功能如下:
-
速度:Apache Spark 可以比 Hadoop MapReduce 在内存中运行快 100 倍,或者在磁盘上运行快 10 倍。
-
易用性:提供了多种 API,适用于 Scala、Java、Spark 和 R,用于开发应用程序。
-
通用性:Spark 提供了结合 SQL、流式处理和复杂分析的功能。
-
随处运行:Spark 可以在 Hadoop、Mesos、独立模式或云端运行。你可以通过包括 HDFS、Cassandra、HBase 和 S3 来访问多种数据源。
我使用 Spark 通过 MLlib 训练我的模型。我使用了 Spark Java 和 PySpark API。结果是,你可以将数据重定向到 HDFS。我将训练好的模型保存到 HDFS,并根据需要加载它们。Spark 确实加速了你的处理时间。我亲身体验过这一点。其背后的原因是其内存中处理架构。Spark 的架构如图 03所示:

图 03:Spark 运行架构(图片来源:https://www.slideshare.net/datamantra/spark-architecture)
你可以在图 04中看到 Spark 生态系统:

图 04:Spark 生态系统(图片来源:http://jorditorres.org/spark-ecosystem/)
你可以在这个 GitHub 链接查看安装步骤:
github.com/jalajthanaki/NLPython/blob/master/Appendix3/Installationdocs/App3_4_Spark_installation.md
你可以通过以下链接获取更多信息:
Apache Flink 作为实时处理框架
Apache Flink 用于实时流处理和批处理。我之前提到过我们不需要担心实时框架,因为我们有 Flink 框架来处理这个问题。
Flink 是一个开源流处理框架,适用于分布式、高性能、始终可用且精准的数据流应用。你可以在flink.apache.org/查看更多关于 Flink 的信息。
Flink 必定会提供一个非常美好的未来。你可以在图 05中看到:

图 05:Flink 的特性(图片来源:https://flink.apache.org/)
Flink 是一个相对较新的框架。如果你想执行实时情感分析或制作一个推荐引擎,那么 Flink 非常有用。你可以参考以下视频,了解 HDFS、Flink、Kappa 和 Lambda 架构是如何被使用的。这是一个必看的视频:
www.youtube.com/watch?v=mYGF4BUwtaw
这段视频帮助你理解各种框架如何融合在一起开发一个好的实时应用程序。
Python 中的可视化库
可视化是跟踪某些过程和应用程序结果的重要活动之一。在第六章《高级特征工程与 NLP 算法》中,我们使用了matplotlib,其他章节也有使用。
除了matplotlib,我们还可以使用各种可视化库:
-
matplotlib:简单易用且非常实用 -
bokeh:提供自定义主题和图表 -
pygal:你可以用它制作酷炫的图表和图形
你可以通过以下链接参考每个库。所有库都有详细的文档,你可以查阅并开始制作自己的图表。
你可以在matplotlib.org/找到更多关于matplotlib的信息。
你可以在bokeh.pydata.org/en/latest/docs/gallery.html找到更多关于Bokeh的信息。
你可以在pygal.org/en/stable/documentation/index.html上找到关于pygal的文档。
总结
如果你想了解关于这些框架和库的详细信息,可以使用 Gitter 聊天室与我联系,因为框架的深入细节超出了本书的范围。
这个框架概述将帮助您了解各种框架在自然语言处理(NLP)应用中的应用方式。Hadoop 用于存储。Spark MLlib 用于开发机器学习模型,并将训练好的模型存储在 HDFS 上。我们可以通过加载模型,根据需要运行训练好的模型。Flink 在实时分析和数据处理方面为我们带来了便利。实时情感分析、文档分类、用户推荐引擎等是您可以使用 Flink 构建的一些实时应用。matplotlib 用于开发机器学习模型。pygal 和 bokeh 用于为我们的最终用户制作漂亮的仪表盘。
第十一章:如何提高你的 NLP 技能
本附录将为你提供更多关于如何提升 NLP 技能的信息。这也将帮助你更新知识。
开始一段新的 NLP 职业旅程
如果你是一个编码员并希望在 NLP 领域发展职业生涯,请记住以下几点:
-
NLP 以及人工智能的快速发展使得仅仅掌握技能是不够的,你还需要不断更新它们。
-
Kaggle 是所有 NLP 和数据科学从业者最好的学习平台之一。如果你第一次听说这个黑客松平台,我敢肯定,点击这个链接你不会失望的:
www.kaggle.com/。 -
参加 Kaggle 竞赛。学习、讨论并实践。
-
如果你来自编程背景,GitHub 就是你的新简历。所以,尝试创建新的项目并将它们放到你的 GitHub 上。
-
为开源社区项目做贡献。这不仅能帮助你提升思维能力,还能提高你的编码技能。
-
参加各种会议。尝试探索新的想法和技术。
-
阅读研究论文和文章。
-
成为社区和讨论的一部分。
-
提问。简而言之,解锁自己。
-
思考产品架构。
-
整合你所有的学习,尝试看到你开发产品的全局视角。
-
如果你认为自己学得足够多了,那么再想一想。有时,你所尝试的用例、应用等非常相似,以至于你觉得它们并不那么有趣。回顾你所有的学习和经验,为你的思维指引一个更好的新方向(就像牛顿或爱因斯坦一样,他们停止了学习,开始思考,并给我们带来了伟大的科学理论)。尝试做一些有用的事情,积极地影响他人的生活。
Cheat sheets
我提供了针对库和框架的 Cheat sheets,链接如下:
github.com/jalajthanaki/NLPython/tree/master/Appendix2/Cheatsheets
Cheat sheets 涵盖了以下库、工具和框架。这些 Cheat sheets 不是我开发的,我想对以下主题的 Cheat sheets 的独立作者表示充分的感谢:
-
初学者的 Linux 书籍
-
Python
-
NumPy
-
SciPy
-
pandas
-
Fask
-
scikit-learn
-
TensorFlow API 可访问
www.tensorflow.org/api_docs/python/ -
TensorFlow cheat sheet 在
github.com/crscardellino/cheatsheets/blob/master/tensorflow.md,由 Cristian Cardellino 制作。 -
Keras
-
PySpark
-
数学
-
Git
-
Linux
选择你的领域
在阅读完所有章节后,你可能已经足够了解自己喜欢什么。你想建立核心的机器学习内容吗?你喜欢在 Hadoop、Spark 等框架上工作吗?你热衷于设计架构吗?你想为可视化做贡献吗?思考并选择吧。
你可以选择数据科学中的任何领域,或者参与整个数据科学产品开发生命周期。我想分享我的例子。我曾与中型企业和初创公司合作。到目前为止,我有机会探索与数据科学相关的各个领域,比如提出数据科学产品并发布产品。我通常会在分析商业机会后提出新的产品。我总是通过思考,如果我们做出这个产品,最终用户会使用它,并且反过来,我所在的公司也会从中获得积极的影响,来验证我的产品提案。然后,我会开始设计部分的工作,提出很多问题,比如我们需要什么样的数据,使用哪些数据资源,哪些是我们需要收集的关键数据点,产品的架构是什么样的,我们会使用哪些机器学习模型,如何与现有产品集成,我们何时发布它等等。如果你像我一样思考,并且想在所有领域和数据科学产品的每个组件上工作,那么这是件非常好的事情。只要用心和热情去做工作,并且看到更大的图景。
敏捷工作方式以实现成功
自然语言处理(NLP)或任何与数据科学相关的项目需要经过多次迭代才能获得最佳结果。你需要理解问题陈述。之后,为了达到最佳效果,你需要从数据分析开始。分析完数据后,制作一个基本原型。然后验证你的模型。如果它给出了最佳结果,那么你就完成了;如果没有,那么尝试实现不同的算法,进行超参数调优,或者更改或改进你的特征集。在工作过程中,你需要保持敏捷。尝试识别你的问题或错误,然后进行智能迭代。在 Stack Overflow 上提问,尝试寻找答案。这会对你有很大帮助。保持自己对所有技术和工具的更新。有些库可以解决你的问题。寻找任何可用的付费第三方工具,并尝试了解它的工作原理。使用这些工具后,你可能会成为你的产品大师,并使你的产品对最终用户更有价值。
有用的 NLP 和数据科学博客
以下是一些重要的 NLP 和数据科学博客:
获取公共数据集
以下是可用数据集的列表:
-
Kaggle 数据集:
www.kaggle.com/datasets -
UCI 机器学习:
archive.ics.uci.edu/ml/ -
Reddit:
www.reddit.com/r/datasets/ -
一个非常棒的 GitHub 仓库,包含了公共数据集的列表:
github.com/caesar0301/awesome-public-datasets -
Google 高级搜索在您搜索数据集时也非常方便:
www.google.co.in/advanced_search
数据科学所需的数学
如果您来自非技术背景,且想学习对您的 NLP 或任何其他数据科学项目有帮助的数学,您可以从这个网站开始学习:
如果您想访问参考链接、研究论文和书籍,您可以点击以下 GitHub 链接:
github.com/jalajthanaki/NLPython/tree/master/Appendix2
如果您想向此页面添加内容,随时可以添加。让我们继续这段旅程。
总结
本节内容主要是与您讨论并分享我的看法。这些看法可能对您有所帮助。我给出了一些建议、流程和您可以检查并尝试吸收的领域。如果您愿意,我列出了一些有用博客的链接以及其他有关公开数据集的重要链接。我还提供了不同 Python 库的备忘单。
第十二章:安装指南
本附录提供了与安装 Python 库、单节点 Hadoop 集群和单节点 Spark 集群相关的信息。如果你有与本书或任何自然语言处理(NLP)相关的问题,可以加入我在 Gitter 上的讨论。
安装 Python、pip 和 NLTK
Pip 代表 pip 安装包或 pip 安装 Python。
你可以在这个链接看到如何安装pip和nltk:
Python 已在 Linux 和 macOS 上预装。我建议你使用 Linux 操作系统或 macOS,因为它们更容易设置与机器学习(ML)相关的框架。
安装 PyCharm IDE
你可以通过点击这个链接来安装适用于 Linux 操作系统的 PyCharm 社区版 IDE:
安装依赖项
你可以运行以下命令来安装 NLP 项目所需的各种 Python 依赖项:
sudo pip install -r pip-requirements.txt
这是依赖文件的链接:
github.com/jalajthanaki/NLPython/blob/master/pip-requirements.txt
框架安装指南
Hadoop 独立集群安装指南:
Spark 独立集群安装指南:
github.com/jalajthanaki/NLPython/blob/master/Appendix3/Installationdocs/App3_4_Spark_installation.md。
TensorFlow 安装指南:
提出你的疑问
如果你有任何与安装相关的问题,可以通过这个链接加入我在 Gitter 上的讨论:
如果你有与编程相关的问题或其他与本书相关的问题,可以在 Gitter 房间里向我提问:
gitter.im/NLPython/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link。
概要
本节的重点是向你提供有关各种库安装的详细信息。我还提供了各种 IDE 的安装步骤。为了让你的生活更轻松,我开发了一个 pip 需求文档。通过运行之前给出的命令,你将能够安装 Python 依赖包。我还提供了在你的机器上安装 Hadoop 和 Spark 的步骤,并在单节点集群中运行它们。如果你有任何关于安装的问题,也可以联系我。



浙公网安备 33010602011771号