原文:
annas-archive.org/md5/ba166785943c99247a0ed26eb9d45859
译者:飞龙
前言
人工智能(AI)是各行各业、各个领域中最新的、颠覆性的技术。本书展示了在 Python 中实现的 AI 项目,涵盖了构成 AI 世界的现代技术。
本书从使用流行的 Python 库 scikit-learn 构建第一个预测模型开始。你将理解如何使用有效的机器学习技术(如随机森林和决策树)构建分类器。通过关于鸟类物种预测、学生表现数据分析、歌曲流派识别和垃圾邮件检测的精彩项目,你将学习到智能应用开发的基础知识以及各种算法和技术。你还将通过使用 Keras 库的项目理解深度学习和神经网络的机制。
在本书结束时,你将能够自信地使用 Python 构建自己的 AI 项目,并准备好迎接更高级的内容。
本书适合人群
本书适用于那些希望通过易于跟随的项目迈出人工智能领域第一步的 Python 开发者。要求具备基本的 Python 编程知识,以便能灵活操作代码。
本书内容
第一章,构建你自己的预测模型,介绍了分类和评估技术,接着解释了决策树,并通过一个编码项目构建了一个用于学生表现预测的模型。
第二章,使用随机森林进行预测,讲解了随机森林,并通过一个编码项目使用它来对鸟类物种进行分类。
第三章,评论分类应用,介绍了文本处理和词袋模型技术。接着,展示了如何使用这一技术构建 YouTube 评论的垃圾邮件检测器。随后,你将学习更复杂的 Word2Vec 模型,并通过一个编码项目实践它,检测产品、餐厅和电影评论中的正面和负面情感。
第四章,神经网络,简要介绍了神经网络,接着讲解了前馈神经网络,并展示了一个使用神经网络识别歌曲流派的程序。最后,你将修改早期的垃圾邮件检测器,使其能够使用神经网络。
第五章,深度学习,讨论了深度学习和卷积神经网络(CNN)。你将通过两个项目实践卷积神经网络和深度学习。首先,你将构建一个可以识别手写数学符号的系统,然后回顾鸟类物种识别项目,并将实现方式修改为使用深度卷积神经网络,使其准确度大大提升。
为了充分利用本书
-
您需要具备 Python 及其科学计算库的基础知识。
-
安装 Jupyter Notebook,最好通过 Anaconda 安装。
下载示例代码文件
您可以通过您的帐户在 www.packtpub.com 下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问 www.packtpub.com/support 并注册以直接将文件通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
在 www.packtpub.com 上登录或注册。
-
选择“支持”标签。
-
点击“代码下载和勘误”。
-
在搜索框中输入书名,并按照屏幕上的指示操作。
一旦文件下载完成,请确保使用最新版本的工具解压或提取文件夹:
-
Windows 上使用 WinRAR/7-Zip
-
Mac 上使用 Zipeg/iZip/UnRarX
-
Linux 上使用 7-Zip/PeaZip
本书的代码包也托管在 GitHub 上,地址为 github.com/PacktPublishing/Python-Artificial-Intelligence-Projects-for-Beginners
。如果代码有任何更新,它将更新到现有的 GitHub 仓库中。
我们还提供了来自我们丰富书籍和视频目录的其他代码包,您可以在 github.com/PacktPublishing/
上查看它们!
下载彩色图像
我们还提供了一个 PDF 文件,其中包含书中使用的截图/图表的彩色图像。您可以在此下载:www.packtpub.com/sites/default/files/downloads/PythonArtificialIntelligenceProjectsforBeginners_ColorImages.pdf
。
使用的约定
本书中使用了一些文本约定。
CodeInText
:表示文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 账号名。例如:“classes.txt
文件展示了带有鸟类物种名称的类 ID。”
粗体:表示新术语、重要词汇或在屏幕上看到的文字。例如,菜单或对话框中的词汇会像这样出现在文本中。
警告或重要提示以此方式出现。
小贴士和技巧以此方式显示。
联系我们
我们欢迎读者的反馈。
一般反馈:发送电子邮件至 feedback@packtpub.com
,并在邮件主题中提及书名。如果您对本书的任何内容有疑问,请通过电子邮件联系我们:questions@packtpub.com
。
勘误:尽管我们已尽一切努力确保内容的准确性,错误偶尔也会发生。如果您在本书中发现错误,请向我们报告,我们将不胜感激。请访问 www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入详细信息。
盗版:如果您在互联网上发现我们作品的任何形式的非法副本,我们将不胜感激您提供地址或网站名称。请通过copyright@packtpub.com
与我们联系,并提供材料链接。
如果您有意成为作者:如果您在某个专业领域有专长,并且有意撰写或贡献一本书籍,请访问 authors.packtpub.com。
评价
请留下您的评价。在阅读和使用本书后,为什么不在购买它的网站上留下您的评价呢?潜在读者可以看到并使用您公正的意见来做出购买决策,我们在 Packt 可以了解您对我们产品的看法,我们的作者也可以看到您对他们书籍的反馈。谢谢!
欲了解更多有关 Packt 的信息,请访问 packtpub.com。
第一章:创建您自己的预测模型
我们的社会比以往任何时候都更具技术先进性。人工智能(AI)技术已经在全球范围内传播,正在复制人类。创造能够模拟人类智能各个方面(如推理、学习和解决问题)的机器的初衷,促成了 AI 技术的发展。AI 真正与人类本性相抗衡。换句话说,AI 使机器像人类一样思考和行动。最能展示这种技术威力的例子就是 Facebook 的标签建议或人脸识别功能。考虑到这种技术对当今世界的巨大影响,AI 无疑将在未来几年成为最伟大的技术之一。
我们将通过一个基于 AI 技术的项目进行实验,探索使用机器学习算法进行分类,并结合 Python 编程语言。我们还将通过一些示例来帮助更好地理解。
在本章中,我们将探讨以下几个有趣的主题:
-
分类技术概述
-
Python scikit 库
分类概述和评估技术
AI 为我们提供了各种分类技术,但机器学习分类是最适合入门的技术,因为它是最常见且最容易理解的分类方法。在我们的日常生活中,眼睛捕捉到成千上万的图像:无论是在书本中,还是在某个屏幕上,或者是你周围环境中看到的东西。这些被我们眼睛捕捉到的图像帮助我们识别和分类物体。我们的应用程序基于相同的逻辑。
在这里,我们正在创建一个应用程序,通过机器学习算法来识别图像。假设我们有苹果和橙子的图像,通过观察这些图像,我们的应用程序将帮助识别图像是苹果还是橙子。这种类型的分类可以称为二元分类,意味着将给定集合中的物体分类为两个组,但也存在多类别分类的技术。我们需要大量的苹果和橙子图像,并且需要设置一个机器学习算法,使得应用程序能够分类这两种图像。换句话说,我们让这些算法学习两者之间的区别,从而帮助正确分类所有示例。这被称为监督学习。
现在让我们比较监督学习与无监督学习。假设我们不知道实际的数据标签(即我们不知道这些图片是苹果还是橙子的例子)。在这种情况下,分类方法可能帮助不大。聚类方法总能缓解这种情况。结果会是一个可以部署在应用中的模型,并且它会像下面的图所示那样工作。该应用会记住苹果和橙子之间的区别,并使用机器学习算法识别实际的图像。如果我们输入一个新的数据,模型会告诉我们它的决策,判断该输入是苹果还是橙子。在这个例子中,我们创建的应用能够以 75%的置信度识别苹果图像:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00005.jpeg
有时候,我们希望知道置信度的程度,其他时候我们只需要最终的答案,也就是模型最有信心的选择。
评估
我们可以通过衡量模型的准确性来评估其性能。准确性定义为分类正确的案例所占的百分比。我们可以使用混淆矩阵分析模型的错误,或者说分析它的混淆程度。混淆矩阵是指模型中的混淆情况,但当混淆矩阵变得非常大时,理解起来可能会有些困难。让我们来看一下下面的二分类例子,它展示了模型在正确预测对象时的次数:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00006.jpeg
在前面的表格中,真正的苹果(True apple)和真正的橙子(True orange)所在的行表示该对象实际上是苹果或橙子的情况。列则表示模型做出的预测。我们看到,在这个例子中,有 20 个苹果被正确预测,而有 5 个苹果被错误地识别为橙子。
理想情况下,混淆矩阵应该只有对角线上的数字非零。这里我们可以通过将对角线上的数字相加来计算准确率,这些数字代表所有正确分类的例子,然后将这个和除以矩阵中所有数字的总和:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00007.jpeg
在这里,我们得到了 84%的准确率。为了更好地了解混淆矩阵,让我们通过另一个例子来学习,这个例子涉及三个类别,如下图所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00008.jpeg
来源:scikit-learn 文档
有三种不同种类的鸢尾花。矩阵给出了正确和错误预测的原始记录。因此,setosa被正确预测了 13 次,所有 setosa 图像的样本中。另一方面,versicolor被正确预测了 10 次,并且有 6 次 versicolor 被预测为virginica。现在,让我们对混淆矩阵进行标准化,并显示预测正确或错误的图像所占的百分比。在我们的例子中,我们看到 setosa 种类的图像被始终正确预测:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00009.jpeg
来源:scikit-learn 文档
在评估混淆矩阵时,我们还注意到系统在两个物种之间产生了混淆:versicolor 和 virginica。这也让我们得出结论,系统并不总能识别 virginica 种类的物种。
对于进一步的实例,我们需要更注意,因为系统将在同一数据上进行训练和测试,所以不能期望非常高的准确性。这将导致记忆训练集并使模型过度拟合。因此,我们应该尝试将数据拆分为训练集和测试集,首先按照 90/10%或 80/20%的比例进行拆分。然后,我们使用训练集开发模型,使用测试集执行和计算混淆矩阵的准确性。
我们需要小心,不要选择一个非常好的测试集或非常差的测试集来获取准确性。因此,为了确保这一点,我们使用一种验证方法,称为K 折交叉验证。为了更好地理解它,想象一下 5 折交叉验证,其中我们将测试集移动 20 个数据点,因为总共有 5 个数据集。然后,我们将剩余的集合作为数据集,计算所有折叠的平均值:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00010.jpeg
这有点让人困惑,对吧?但是 scikit-learn 内置了交叉验证的支持。这个功能是确保我们不会过度拟合模型,且不会在不合格的测试集上运行模型的好方法。
决策树
在本节中,我们将使用决策树和学生表现数据来预测一个孩子是否能够在学校表现良好。我们将结合之前的技术和一些 scikit-learn 代码来实现预测。在开始预测之前,我们先了解一下什么是决策树。
决策树是最简单的分类技术之一。它们可以与20 个问题的游戏相比较,其中树中的每个节点要么是叶节点,要么是问题节点。以泰坦尼克号生还率为例,这个数据集包含了泰坦尼克号每位乘客的生还结果。
将我们的第一个节点视为一个问题:乘客是男性吗? 如果不是,那么乘客很可能存活。否则,我们将继续对男性乘客提出另一个问题:男性是否超过 9.5 岁?(其中 9.5 岁是决策树学习过程选择的理想分裂点)。如果答案是是,那么该乘客很可能没有存活。如果答案是否,则会提出另一个问题:该乘客是否有兄弟姐妹? 下图将为您提供简要说明:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00011.jpeg
理解决策树并不要求你成为决策树学习过程的专家。如前面的图示所见,这个过程使得理解数据变得非常简单。并不是所有机器学习模型像决策树一样容易理解。
现在让我们更深入了解决策树,通过了解决策树的学习过程。考虑我们之前使用的泰坦尼克号数据集,我们将根据信息增益来找到最佳的分裂属性,信息增益也称为熵:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00012.jpeg
只有在知道某一列的值后,结果更可预测时,信息增益才最高。换句话说,如果我们知道乘客是男性还是女性,我们就能知道他或她是否存活,因此性别这一列的信息增益最高。我们不会将年龄列作为第一次分裂的最佳属性,因为我们对乘客的年龄知之甚少,这也不是最佳的第一次分裂,因为如果我们只知道乘客的年龄,我们对结果的了解将会更少。
根据信息增益对性别这一列进行分裂后,我们现在得到了女性和男性两个子集,如下图所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00013.jpeg
在分裂之后,我们有一个分支节点和一个问题节点,如前面的截图所示,且根据问题的答案,可以选择两条路径。现在,我们需要再次在这两个子集内找到最佳属性进行分裂。左边的子集,其中所有乘客都是女性,并没有一个很好的属性来进行分裂,因为很多乘客都存活了。因此,左边的子集最终会变成一个叶节点,预测存活情况。在右边,age
属性被选为最佳分裂点,考虑到9.5岁的年龄作为分裂点。我们得到了两个新的子集:年龄大于9.5岁和年龄小于9.5岁:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00014.jpeg
重复这个过程,不断将数据分裂为两个新子集,直到没有好的分裂点,或者没有剩余的属性,最终形成叶节点而不是问题节点。在我们开始建立预测模型之前,让我们先了解一下 scikit-learn 包。
scikit-learn 分类器的常用 API
在本节中,我们将学习如何使用 scikit-learn 包创建代码,以构建和测试决策树。Scikit-learn 包含许多简单的函数集。实际上,除了下面截图中第二行代码,这一行代码特别与决策树有关外,我们还将使用相同的函数来处理其他分类器,例如随机森林:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00015.jpeg
在我们深入技术部分之前,让我们先理解这些代码行的含义。前两行代码用于设置决策树,但我们可以认为树尚未构建,因为我们还没有指向任何已训练的数据集。第三行通过 fit
函数构建树。接下来,我们对一系列示例进行评分并获得准确性数值。这两行代码将用于构建决策树。之后,我们用一个示例进行预测,这意味着我们将用一行数据来训练模型,并根据“生还”列预测输出。最后,我们执行交叉验证,将数据分割并为每个训练集构建一个条目,评估每个测试集的决策树。在运行这些代码时,得到的结果是评分,随后我们会对这些评分进行平均。
你可能会有一个问题:我们什么时候应该使用决策树? 这个问题的答案其实很简单,因为决策树简单且易于解释,并且需要较少的数据准备,尽管你不能把它们视为最准确的技术。你可以将决策树的结果展示给任何领域专家,比如一位泰坦尼克号历史学家(以我们的例子为例)。即使是对机器学习知之甚少的专家,通常也能够理解决策树的提问并判断树的准确性。
当数据特征较少时,决策树的表现可能更好,但当数据具有较多特征时,它们的表现可能较差。这是因为树可能会变得过大,难以理解,并且可能会通过引入过于特定于训练数据的分支来轻易地过拟合训练数据,而这些分支与创建的测试数据并无太大关系,从而降低了获得准确结果的机会。现在,既然你已经了解了决策树的基本原理,我们准备好实现使用学生表现数据创建预测模型的目标了。
涉及决策树和学生表现数据的预测
在这一部分中,我们将使用决策树来预测学生表现,基于学生的历史表现数据。我们将使用 UC Irvine 机器学习库中的学生表现数据集,archive.ics.uci.edu/ml/datasets/student+performance
。我们的最终目标是预测学生是否通过。数据集包含约 649 名学生的数据,每个学生有 30 个属性。这些属性是混合型的——包括词汇和短语类型的分类属性,以及数值型属性。这些混合属性造成了一个小问题,需要进行修正。我们将需要将这些词汇和短语类型的属性转换成数字。
以下截图显示了数据中的前半部分属性:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00016.jpeg
你一定注意到,有些属性是分类的,比如学校名称;性别;母亲职业(Mjob);父亲职业(Fjob);原因;以及监护人。其他的,如年龄(age)和通学时间(traveltime),是数值型的。以下截图显示了数据中的后半部分属性:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00017.jpeg
很明显,某些属性是更好的预测因子,比如缺勤和过去的失败次数,而其他属性可能预测性较差,比如学生是否有恋爱关系,或者学生的监护人是母亲、父亲还是其他人。决策树将尝试通过提供的信息增益来识别最重要或最具预测性的属性。我们可以通过查看生成的树来识别最具预测性的属性,因为最具预测性的属性会出现在最前面的提问中。
原始数据集中有三项考试成绩:G1
、G2
和G3
。其中,G1
是第一学期成绩,G2
是第二学期成绩,G3
是最终成绩。我们将通过只提供通过或失败来简化问题。这可以通过将三项成绩相加,检查其和是否足够大(即 35)来完成。这样,我们大约能得到 50%的学生通过,50%的学生失败,从而得到一个平衡的数据集。现在我们来看一下代码:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00018.gif
我们导入了数据集(student-por.csv
),该数据集使用分号而不是逗号作为分隔符,因此我们将分隔符指定为分号。为了进行交叉验证,我们将找到数据集中的行数。使用长度变量,我们可以看到数据集有649
行。
接下来,我们添加了“通过”和“失败”列。这些列中的数据将包含 1 或 0,其中 1 表示通过,0 表示失败。我们将通过计算每一行的考试成绩之和来实现这一点。如果三项成绩之和大于或等于 35,学生将被标记为 1,否则标记为 0。
我们需要对数据集的每一行应用这个规则,这将通过apply
函数来实现,这是 Pandas 的一个功能。这里的axis=1
表示按行使用 apply,axis=0
则表示按列应用。下一行表示需要删除一个变量:G1
、G2
或 G3
。以下的代码截图将帮助你理解我们刚才学到的内容:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00019.gif
以下截图显示了数据集的前 5 行和 31 列。之所以有 31 列,是因为我们有所有的属性列,以及我们的"通过"和"未通过"列:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00020.gif
如前所述,这些列中的一些是单词或短语,例如Mjob、Fjob、internet 和 romantic。这些列需要转换为数字,可以使用get_dummies
函数来实现,这是 Pandas 的一个功能,我们需要指定要转换为数字形式的列。
以Mjob为例,函数会查看该列中所有可能的答案或值,并为每个值赋予一个列名。这些列将被命名为Mjob at_home、Mjob health 或 Mjob。例如,Mjob at_home列的值将为1,其余列的值为0。这意味着生成的新列中只有一个列的值为 1。
这被称为独热编码。之所以给这个方法起这个名字,是因为例如,想象有一些电线进入电路。假设电路中有五根电线,而你想使用独热编码方法,你需要激活其中一根电线,同时保持其余电线关闭。
在对数据集执行get_dummies
函数后,你可以注意到例如activities_no和activities_yes列。原先关联的列中,表示"no"的在activities_no列下的值为 1,后面是 0。对于activities_yes列,如果值为"yes",则该列为 0,其他列为 1。这导致创建了许多新的列,总数约为 57 个,但这使我们的数据集充满了数字数据。以下截图显示了activities_yes和activities_no列:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00021.jpeg
这里我们需要打乱行并生成一个包含前 500 行的训练集,剩余的 149 行作为测试集,然后我们只需要从训练集中获取属性,也就是说我们将去掉"通过"列,并将"通过"列单独保存。对于测试集,也进行相同操作。我们将对整个数据集应用这些属性,并将"通过"列单独保存。
现在我们将计算整个数据集中有多少人通过和未通过。这可以通过计算通过和未通过的百分比来完成,结果是 649 个数据中有 328 个是通过的。这表示通过率大约是 50%,也就是一个均衡的数据集。
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00022.jpeg
接下来,我们开始使用来自 scikit-learn 包的 DecisionTreeClassifier
函数构建决策树,这是一个能够对数据集进行多类分类的类。在这里,我们将使用熵或信息增益度量来决定何时进行拆分。我们将在深度为五个问题时进行拆分,初始树深度设置为 max_depth=5
,以便我们了解树是如何适应数据的:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00023.gif
为了获得数据集的概览,我们需要创建树的可视化表示。这可以通过使用 scikit-learn 包的另一个函数:expoert_graphviz
来实现。以下截图展示了在 Jupyter Notebook 中树的表示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00024.jpeg
这只是一个表示,可以通过在 Jupyter 输出中滚动查看更多内容
从之前的表示可以很容易理解,数据集被分为两部分。让我们尝试从顶部解读树。在这种情况下,如果失败大于或等于 0.5,这意味着它为真,并且它被放置在树的左侧。考虑到树总是在左侧为真,右侧为假,这意味着没有先前的失败。在表示中,我们可以看到树的左侧大多数是蓝色,这意味着它预测通过,即使与最多 5 个问题的失败相比,问题的数量较少。树的右侧如果失败小于 0.5,这将导致学生失败,这意味着第一个问题是错误的。如果预测为失败,它会以橙色显示,但随着问题数量的增加,由于我们使用了 max_depth = 5
,预测会进一步调整。
以下代码块展示了一种方法,用于导出可视化表示,您可以通过点击导出并保存为 PDF 或其他格式,便于后续查看:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00025.gif
接下来,我们使用之前创建的测试集检查树的评分:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00026.gif
我们得到的结果大约是 60%。现在让我们交叉验证一下结果,以确保数据集已经被完美地训练过:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00027.jpeg
对整个数据集执行交叉验证,它会按照 20/80 的比例拆分数据,其中 20% 用作测试集,80% 用作训练集。平均结果为 67%。这表明我们有一个良好平衡的数据集。在这里,我们有多个关于 max_depth
的选择:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00028.gif
我们使用从 1 到 20 的各种 max_depth
值。考虑到我们构建的是一个深度为 20 的树,其中每个问题都具有 20 的深度值,这样我们将会得到超过 20 个问题,也就是说你需要向下走 20 步才能到达叶节点。在这里,我们再次执行交叉验证,并保存并打印我们的答案。这将给出不同的准确性和计算结果。通过分析发现,当深度为 2 和 3 时,准确性最佳,且与之前得到的平均准确性进行了比较。
以下截图展示了我们将用于创建图表的数据:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00029.gif
以下截图中的误差条表示分数的标准差,结果表明,对于这个数据集,深度为 2 或 3 是理想的,而我们假设的深度为 5 是不正确的:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00030.jpeg
更深的层次并不会带来更多的能力,仅仅问一个问题,你之前失败过吗?,并不能提供与两个或三个问题相同的信息量。
我们的模型表明,增加深度并不一定有帮助,单纯询问一个问题,你之前失败过吗?,也无法提供与两个或三个问题相同的信息量。
总结
本章我们学习了分类和评估技术,并深入了解了决策树。我们还创建了一个模型来预测学生的表现。
在下一章中,我们将深入了解随机森林,并利用机器学习和随机森林来预测鸟类物种。
第二章:使用随机森林进行预测
在本章中,我们将讨论使用随机森林的分类技术。我们将像上一章那样使用 scikit-learn。我们将展示如何使用描述性属性预测鸟类物种,并在其上应用混淆矩阵。
以下是详细的主题列表:
-
分类与评估技术
-
使用随机森林预测鸟类物种
-
混淆矩阵
随机森林
随机森林是决策树的扩展,是一种集成方法。
集成方法通过构建多个分类器并独立运行每个分类器来实现高准确性。当一个分类器做出决策时,可以利用最常见的决策和平均决策。如果我们使用最常见的方法,这称为投票。
这是一个描述集成方法的图示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00031.jpeg
你可以把每个分类器看作是专门针对数据的独特视角。每个分类器可能是不同类型的。例如,你可以将决策树、逻辑回归和神经网络结合起来,或者分类器可能是相同类型的,但训练于不同的训练数据部分或子集。
随机森林是决策树的集合或集成。每棵树都在属性的随机子集上进行训练,如下图所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00032.jpeg
这些决策树是典型的决策树,但它们有很多棵。与单棵决策树相比,特别是在随机森林中,区别在于每棵树仅允许查看一些属性,通常这些属性的数量相对于可用的总属性数量较少。每棵树都专门针对这些属性。这些专门化的树被收集起来,每棵树都会为其预测投票。获得最多投票的结果就是最终的随机森林预测结果。
随机森林的使用
当属性足够多以构建树且准确性至关重要时,我们应该考虑使用随机森林。当树的数量较少时,与单一决策树相比,模型的可解释性较差。如果可解释性很重要,应该避免使用随机森林,因为如果树的数量过多,模型会非常庞大,并且在训练和预测过程中可能需要大量内存。因此,资源有限的环境可能无法使用随机森林。接下来的部分将解释如何使用随机森林预测鸟类物种。
使用随机森林预测鸟类物种
在这里,我们将使用随机森林来预测鸟类的物种。我们将使用加州理工学院和加州大学圣地亚哥分校的数据集(www.vision.caltech.edu/visipedia/CUB-200-2011.html
),其中包含来自 200 种不同鸟类的大约 12,000 张照片。在这里,我们不会查看图片,因为那需要用到卷积神经网络(CNN),这将在后续章节中讲解。CNN 可以比随机森林更好地处理图片。相反,我们将使用鸟类的属性,如大小、形状和颜色。
这里是数据集中的一些物种:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00033.jpeg
一些鸟类,例如美洲乌鸦和鱼乌鸦,几乎无法区分,至少从视觉上看是这样的。每张照片的属性,如颜色和大小,实际上是由人类标注的。加州理工学院和加州大学圣地亚哥分校使用了亚马逊机械土耳其平台的人工劳动力来标注这个数据集。研究人员通常使用机械土耳其平台,这是一个网站服务,用户每标注一张照片就能获得少量报酬,用以通过人类的直觉而非机器预测来改善数据集。
如果你有自己的数据集,需要大量人工标注,或许可以考虑花钱通过机械土耳其平台来完成这个任务。
这是单张照片及其标签的示例:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00034.jpeg
我们可以看到,夏季山雀被标记为拥有红色喉部、固态腹部花纹、栖息形态等特征。数据集还包括每个人决定标签所花的时间以及他们对标签决定的信心,但我们暂时不打算使用这些信息。
数据被分割成多个文件。在进入代码之前,我们将讨论这些文件:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00035.jpeg
classes.txt
文件显示了带有鸟类物种名称的类 ID。images.txt
文件显示了图像 ID 和文件名。每张照片的物种信息存储在image_class_labels.txt
文件中,该文件将类 ID 与图像 ID 关联。
attributes.txt
文件列出了每个属性的名称,这对于我们来说最终并不是很重要。我们只需要属性的 ID:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00036.jpeg
最重要的文件是image_attribute_labels.txt
:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00037.jpeg
它通过二进制值将每个图像与其属性连接起来,该值表示该属性是否存在。每行数据由机械土耳其平台的用户生成。
现在,让我们看一下代码:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00038.gif
我们将首先加载包含所有图像属性标签的 CSV 文件。
需要注意以下几点:
-
所有值都以空格分隔
-
没有标题列或行
-
忽略诸如
error_bad_lines= False
和warn_bad_lines= False
的消息或警告。 -
使用列
0
、1
和2
,它们分别是图像 ID、属性 ID,以及存在或不存在的值。
你不需要担心选择属性的过程和所花费的时间。
在数据集的顶部是这样的:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00039.gif
图像 ID 为 1 的图像没有属性 1、2、3 或 4,但它有属性 5。
形状将告诉我们有多少行和列:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00040.gif
它有 370 万行和三列。这不是你想要的实际公式。你希望属性是列,而不是行。
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00041.gif
因此,我们必须使用透视操作,就像 Excel 中的透视方法一样:
-
对图像 ID 进行透视操作,为每个图像 ID 创建一行。图像 1 将只有一行数据。
-
将属性转化为独立的列,值将是 1 或 2。
现在我们可以看到每个图像 ID 只有一行,每个属性都有一个单独的列,值为 1 和 2:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00042.gif
让我们将这些数据输入到随机森林中。在之前的示例中,我们有 312 列和 312 个属性,最终大约有 12,000 张图像或 12,000 个不同的鸟类示例:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00043.gif
现在,我们需要加载答案,比如它是否是鸟类,属于哪个物种。由于这是一个图像分类标签文件,分隔符是空格。没有标题行,两个列分别是imgid
和label
。我们将使用set_index('imgid')
,以便产生与imgatt2.head()
相同的结果,其中行由图像 ID 标识:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00044.gif
它的样子如下:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00045.gif
imgid
列的值为1
、2
、3
、4
和5
,它们都标记为1
。它们都位于文件的顶部。如所见,大约有 12,000 行数据,这样的规模非常合适:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00046.gif
这与属性数据的数量相同。我们将使用连接操作。
在连接过程中,我们将使用图像 ID 的索引来连接两个数据框。实际上,我们将获得的是标签被附加为最后一列。
我们现在将进行洗牌,然后分离出属性。换句话说,我们想从标签中去除标签。所以,这里是属性,前 312 列和最后一列是标签:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00047.gif
洗牌后,第一行是图像 527,第二行是图像 1532,以此类推。标签数据中的属性一致。在第一行,它是图像 527,对应的是编号 10。你不知道它是哪种鸟,但它属于某个类别,并且有这些属性。最终,它已经是正确的格式。我们需要进行训练和测试集划分。
总共有 12,000 行数据,所以我们取前 8,000 行作为训练集,剩下的 4,000 行作为测试集。我们将使用RandomForestClassifier
来获取答案:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00048.gif
最大特征显示每棵树可以查看的不同列的数量。
比如说,看两个属性,这可能不足以真正搞清楚是哪只鸟。有些鸟类是独特的,因此可能需要更多的属性。稍后如果我们设置max_features=50
,而估算器的数量表示创建的树的数量。实际的拟合过程会建立它。
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00049.gif
让我们预测几个案例。我们将使用训练集前五行的属性,预测物种编号 10、28、156、10 和 43。经过测试,我们得到了 44%的准确率:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00050.gif
即使是 44%的准确率也不是最好的结果。毕竟有 200 个物种,因此 0.5%的准确率比随便猜测要好得多。
为数据制作混淆矩阵
让我们制作一个混淆矩阵,看看数据集混淆了哪些鸟类。来自 scikit-learn 的confusion_matrix
函数将生成矩阵,但它是一个相当大的矩阵:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00051.gif
200 乘 200 这种数字形式不太容易理解。
这里有一些来自 scikit-learn 文档的代码,它可以帮助我们绘制矩阵并在矩阵中加上颜色:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00052.jpeg
我们需要矩阵中鸟类的实际名称,以便知道哪些物种彼此之间产生了混淆。因此,让我们加载类别文件:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00053.gif
绘制矩阵。这是该数据集的混淆矩阵:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00054.gif
输出如下所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00055.jpeg
输出是不可读的,因为有 200 行和 200 列。但如果我们单独打开它并开始放大,在y轴上你会看到实际的鸟类,在x轴上,你会看到预测的鸟类:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00056.jpeg
例如,常见的黄喉是正确的。从下图可以看出,常见黄喉与黑脚信天翁被混淆。当我们放大时,可以看到这种混淆:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00057.jpeg
这就像是常见的黄喉和黑脚信天翁之间的混淆方块。有些特征是海鸥,例如北极海鸥、黑海鸥、里海海鸥和普通海鸥。海鸥显然很容易混淆,因为它们看起来相似。
这个集数据也有些混淆:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00058.jpeg
这是关于麻雀的数据集。混淆矩阵告诉我们我们预期的结果,即,看起来相似的鸟类会互相混淆。正如之前的截图所见,这里有一些混淆的小方块。
在大多数情况下,你不希望把信天翁和常见黄喉混淆,因为这意味着数据集根本不知道自己在做什么。
由于鸟类的名称已经排序,混淆的方块就较少。我们可以将其与简单的决策树进行比较:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00059.gif
这里的准确率是 27%,低于之前 44%的准确率。因此,决策树效果较差。如果我们使用支持向量机(SVM),也就是神经网络方法,输出为 29%:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00060.gif
随机森林仍然更好。
我们进行交叉验证,以确保我们以不同的方式划分训练集和测试集。输出结果仍然是随机森林为 44%,决策树为 25%,SVM 为 27%,如下图所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00061.jpeg
随机森林表现最优,因为我们在随机森林中有一些选项和问题。
例如,每棵树能问多少个不同的问题?它查看多少个属性,又有多少棵树?嗯,有很多参数需要查看,所以我们不妨做个循环,把它们都试一遍:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00062.jpeg
这些是所有的准确率,但最好通过图表来可视化,如下所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00063.jpeg
我们可以看到,增加树的数量可以得到更好的结果。此外,如果能够查看更多的特征,增加特征数量也能带来更好的结果,但最终,如果特征数量大约在 20 到 30 之间,树的数量在 75 到 100 棵之间,那时准确率大约能达到 45%,这是你能得到的最佳结果。
总结
在本章中,我们学习了随机森林并对鸟类物种进行了分类。接着,我们讨论了混淆矩阵和不同的图表,这些图表基于随机树、决策树和 SVM 给出了结果。
在下一章中,我们将探讨使用词袋模型和 Word2Vec 模型进行评论分类。
第三章:评论分类的应用
在这一章中,我们将概述文本分类的词袋模型。我们将研究如何使用词袋模型和随机森林技术预测 YouTube 评论是否为垃圾邮件。然后我们将研究 Word2Vec 模型,以及通过 Word2Vec 方法和 k-近邻分类器预测正面和负面评论。
在这一章中,我们将特别关注文本和单词,分类互联网评论为垃圾邮件或非垃圾邮件,或将互联网评论识别为正面或负面。我们还将概述文本分类的词袋模型,并使用词袋模型和随机森林技术预测 YouTube 评论是否为垃圾邮件。我们还将研究 Word2Vec 模型和 k-近邻分类器。
但是,在我们开始之前,我们将回答以下问题:是什么让文本分类成为一个有趣的问题?
文本分类
为了回答我们的问题,我们将以著名的鸢尾花数据集作为示例数据集。下图是紫花鸢尾物种的图片。为了识别物种,我们需要一些除物种图像之外的其他信息,例如花朵的花瓣长度、花瓣宽度、花萼长度和花萼宽度,这些将帮助我们更好地识别图像:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00064.jpeg
该数据集不仅包含了紫花鸢尾的例子,还包括了 Setosa 和 Virginica 的例子。数据集中的每个示例都包含这四个测量值。数据集包含大约 150 个例子,每个物种包含 50 个例子。我们可以使用决策树或其他模型来预测一朵新花的物种,只要给出相同的四个测量值。正如我们所知,同一物种的花朵测量值几乎相似。由于相似性有不同的定义,但在这里我们将相似性视为在图上的接近度,假设每个点代表一朵花。下图是花萼宽度与花瓣宽度的比较:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00065.jpeg
如果我们没有办法衡量相似性,比如说每朵花都有不同的测量值,那么就无法使用机器学习构建分类器了。
正如我们所知道的,同一物种的花朵有相同的测量值,这帮助我们区分不同的物种。想象一下,如果每朵花的测量值都不同,那么使用机器学习构建分类器来识别物种的图像就没有意义了。
机器学习技术
在考虑图像之前,我们现在来考虑文本。例如,考虑以下句子,并尝试找出第一对短语与第二对短语的相似之处:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00066.jpeg
我希望你能回答那个问题,否则我们将无法构建决策树、随机森林或其他任何预测模型。为了回答这个问题,注意到前一对短语很相似,因为它们包含一些共同的单词,如subscribe和channel,而第二对句子包含的共同单词较少,如to和the。考虑每个短语代表一个数字向量,这样前一对短语在数值上与第二对短语相似。只有这样,我们才能使用随机森林或其他分类技术,在这种情况下,检测 YouTube 评论垃圾邮件。为了实现这一点,我们需要使用词袋模型。
词袋模型
词袋模型正是我们所需要的,它将短语或句子转换,并计算相似单词出现的次数。在计算机科学中,词袋(bag)指的是一种数据结构,类似于数组或列表,它跟踪对象,但在这种情况下,顺序不重要,如果一个对象出现多次,我们只需要跟踪其出现次数,而不是重复记录它们。
例如,考虑前面图表中的第一个短语,它有一个词袋,包含像channel这样的单词,出现一次,plz,出现一次,subscribe,出现两次,等等。然后,我们会将所有这些计数收集到一个向量中,每个短语、句子或文档对应一个向量,具体取决于你正在处理的内容。再次强调,原始单词的出现顺序不重要。
我们创建的向量也可以用于按字母顺序排序数据,但必须对所有不同的短语一致地进行此操作。然而,我们仍然面临相同的问题。每个短语都有一个包含不同列的向量,因为每个短语包含不同的单词和不同数量的列,如下表所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00067.jpeg
如果我们通过所有短语中的唯一单词创建一个更大的向量,我们会得到一个合适的矩阵表示。每一行代表一个不同的短语,注意使用 0 来表示某个短语中没有某个单词:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00068.jpeg
如果你想拥有一个包含大量短语、文档等的词袋,我们需要收集所有示例中出现的唯一单词,并创建一个巨大的矩阵,N x M,其中N是示例的数量,M是出现次数的数量。我们可能会轻松拥有成千上万个维度,在用于鸢尾花数据集的四维模型中进行比较。词袋矩阵可能是稀疏的,意味着大部分是零,因为大多数短语中并没有包含大部分单词。
在开始构建我们的词袋模型之前,我们需要处理一些事情,例如以下内容:
-
将每个单词转换为小写
-
去除标点符号
-
去除常见词(停用词)
-
去除复数形式(例如,bunnies => bunny)
-
进行词形还原(例如,reader => read,reading = read)
-
使用 n-gram(如二元组(两词组合)或三元组)
-
仅保留频繁出现的词汇(例如,必须在 >10 个示例中出现)
-
仅保留最频繁的 M 词汇(例如,仅保留 1,000 个)
-
记录二进制计数(1 = 存在,0 = 不存在)而不是实际计数
还有许多其他最佳实践的组合,找到最适合特定数据的组合需要一些研究。
我们在处理长文档时面临的问题是,长文档通常会有更高的词汇计数,但即使词汇计数差异显著,我们仍然可能希望将关于某个主题的长文档视为与关于同一主题的短文档相似。
此外,如果我们仍然想要减少非常常见的词汇并突出显示稀有的词汇,我们需要做的是记录每个词汇的相对重要性,而不是其原始计数。这被称为词频逆文档频率(TF-IDF),它衡量一个词汇或术语在文档中的常见程度。
我们使用对数函数来确保包含大量词汇的长文档与包含相似词汇的短文档非常相似。TF-IDF 由两个相乘的部分组成,当 TF 值较高时,结果也较高,但 IDF 衡量的是词汇在所有文档中的常见程度,这将影响常见词汇的分数。因此,出现在其他文档中的常见词汇会得到较低的分数,无论它出现了多少次。
如果一个文档的得分较低,意味着该词汇出现得较少;如果得分较高,意味着该词汇在文档中出现频繁。但是,如果该词汇在所有文档中都很常见,那么在该文档中的得分就变得不相关。无论如何,它的得分都会被认为是低的。这表明,TF-IDF 公式以我们希望模型展现的方式运行。以下图表解释了我们的理论:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00069.jpeg
我们将使用词袋方法来检测 YouTube 评论是否为垃圾邮件。
检测 YouTube 评论垃圾邮件
在这一部分,我们将介绍一种使用词袋和随机森林检测 YouTube 评论垃圾邮件的技术。数据集非常直观。我们将使用一个包含约 2,000 条来自热门 YouTube 视频评论的数据集(archive.ics.uci.edu/ml/datasets/YouTube+Spam+Collection
)。数据集的格式是每一行包含一个评论,后面跟着一个标记为 1 或 0 的值,表示该评论是否为垃圾邮件。
首先,我们将导入一个数据集。这个数据集实际上分成了四个不同的文件。我们的评论集来自 PSY 的《江南 Style》视频:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00070.jpeg
然后我们将打印出以下几条评论:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00071.jpeg
在这里,我们可以看到有超过两列,但我们只需要内容列和分类列。内容列包含评论,分类列包含值 1 或 0,表示是否为垃圾评论。例如,注意到前两条评论被标记为非垃圾评论,但接下来的评论subscribe to me for call of duty vids是垃圾评论,hi guys please my android photo editor download yada yada也是垃圾评论。在开始排序评论之前,我们先看一下数据集中垃圾评论和非垃圾评论的数量。我们得到的结果是 175 条垃圾评论和 175 条非垃圾评论,总共有 350 行数据:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00072.gif
在 scikit-learn 中,词袋技术实际上被称为CountVectorizer
,即统计每个单词出现的次数并将其放入向量中。为了创建一个向量,我们需要为CountVectorizer
创建一个对象,然后同时执行 fit 和 transform 操作:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00073.jpeg
这分为两个不同的步骤进行。首先是 fit 步骤,它会发现数据集中有哪些单词,其次是 transform 步骤,它会为这些短语生成词袋矩阵。得到的矩阵结果是 350 行,1,418 列:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00074.jpeg
总共有 350 行,这意味着我们有 350 条不同的评论和 1,418 个单词。显然,1,418 个单词是出现在所有这些短语中的单词。
现在让我们打印一个单独的评论,然后对该评论进行分析,以便查看短语是如何被拆解的。如下面的截图所示,评论首先被打印出来,然后我们在下方进行分析,这样我们可以看到它是如何拆解成单词的:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00075.jpeg
我们可以使用向量化器功能来找出数据集在向量化后找到的单词。以下是向量化后的结果,其中开始是数字,结束是常规单词:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00076.gif
执行以下命令来打乱数据集,设置分数为 100%,即添加frac=1
:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00077.gif
现在我们将把数据集拆分为训练集和测试集。假设前 300 条用于训练,后 50 条用于测试:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00078.jpeg
在上面的代码中,vectorizer.fit_transform(d_train['CONTENT'])
是一个重要步骤。在这一阶段,你有一个训练集,你希望对其进行拟合转换,这意味着它会学习单词并生成矩阵。然而,对于测试集,我们不再执行拟合转换,因为我们不希望模型为测试数据学习不同的单词。我们将使用它在训练集上学到的相同单词。假设测试集有不同的单词,其中一些可能是测试集中独有的,可能从未出现在训练集中。这是完全可以接受的,反正我们将忽略它。因为我们使用训练集来构建随机森林或决策树,或者无论是什么模型,我们必须使用一组特定的单词,而这些单词在测试集上也必须是相同的。我们不能向测试集引入新单词,因为随机森林或任何其他模型都无法评估新单词。
现在我们对数据集进行转换,稍后将使用这些答案进行训练和测试。训练集现在有 300 行和 1,287 个不同的单词或列,而测试集有 50 行,但我们仍然有相同的 1,287 列:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00079.gif
即使测试集有不同的单词,我们也需要确保它与训练集以相同的方式进行转换,并保持相同的列。现在我们将开始构建随机森林分类器。我们将把这个数据集转化成 80 棵不同的树,并拟合训练集,以便在测试集上评估其表现:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00080.jpeg
我们获得的分数输出是 98%;这真的很不错。在这里,似乎它在垃圾邮件和非垃圾邮件之间产生了混淆。我们需要确保准确率很高;为此,我们将进行五次不同拆分的交叉验证。为了执行交叉验证,我们将使用所有训练数据并将其分成四个不同的组:20%、80% 和 20% 将作为测试数据,80% 将作为训练数据:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00081.jpeg
我们现在将对刚才获得的分数进行平均,结果大约是 95%的准确率。接下来,我们将打印出所有数据,如下图所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00082.gif
整个数据集包含五个不同的视频评论,这意味着我们一共有大约 2,000 行。在检查所有评论时,我们注意到有1005
条垃圾评论和951
条非垃圾评论,它们非常接近,足够均匀地分成两部分:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00083.gif
这里我们将打乱整个数据集并分离评论和答案:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00084.gif
我们需要在这里执行几个步骤,使用CountVectorizer
,然后是随机森林。为此,我们将使用 scikit-learn 中的一个特性叫做Pipeline。Pipeline 非常方便,它可以将两个或更多步骤组合在一起,这样所有步骤就能作为一个整体来处理。因此,我们将构建一个包含词袋的管道,然后使用countVectorizer
,接着是随机森林分类器。然后我们会打印出管道,它包含了所需的步骤:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00085.jpeg
我们可以通过将CountVectorizer
添加到我们的RandomForestClassifier
中,让管道自动为每个步骤命名,并且它会将它们命名为CountVectorizer
和RandomForestClassifier
:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00086.jpeg
一旦管道创建完成,你只需调用 fit,它会执行接下来的步骤:首先执行 fit,然后使用CountVectorizer
进行转换,接着使用RandomForest
分类器进行拟合。这就是使用管道的好处:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00087.jpeg
现在你可以调用 score,这样它就知道在评分时会先通过词袋countVectorizer
,然后用RandomForestClassifier
进行预测:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00088.jpeg
这个过程将产生大约 94 的得分。我们只能用管道预测一个示例。例如,假设我们在数据集训练之后有一个新的评论,我们想知道用户输入的这条评论是否为垃圾评论:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00089.jpeg
如所见,它检测得很准确;但对于以下评论呢:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00090.jpeg
为了克服这个问题并将此分类器部署到一个环境中,在有人输入新评论时预测它是否为spm
。我们将使用我们的管道来计算交叉验证的准确性。在这种情况下,我们发现平均准确率约为 94:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00091.jpeg
这相当不错。现在让我们在模型中加入 TF-IDF,以使其更精确:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00092.jpeg
这将放置在countVectorizer
之后。在生成计数后,我们可以为这些计数计算 TF-IDF 得分。现在我们将在管道中加入这个,并用相同的准确度执行另一次交叉验证检查:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00093.jpeg
这展示了管道所需的步骤:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00094.jpeg
以下输出显示了我们得到了CountVectorizer
、一个 TF-IDF 转换器和RandomForestClassifier
。注意,countVectorizer
可以是小写或大写,这取决于数据集;我们可以决定要使用多少个单词。我们可以使用单词,也可以使用二元组(即词对)或三元组(即词三元组)。我们还可以移除停用词,这些停用词是非常常见的英语单词,如and、or和the。在使用 TF-IDF 时,你可以关闭idf
部分,只保留tf
部分,这样它就只是计数的对数。你也可以使用idf
。使用随机森林时,你可以选择使用多少棵树,这就是估算器的数量。
scikit-learn 还有另一个特性可供我们搜索所有这些参数。通过这个特性,我们可以找到最佳的参数:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00095.jpeg
我们可以创建一个小字典,列出管道步骤的名称,然后说明参数名称,这样我们就有了选择的选项。为了演示,我们将尝试最大单词数,或者仅仅是 1,000 或 2,000 个单词。
使用 ngrams
,我们可以只提及单个单词或单词对,这些单词是停用词,使用英语停用词字典,或者不使用停用词,这意味着在第一种情况下,我们需要去除常用词,在第二种情况下,我们不去除常用词。使用 TF-IDF,我们使用 idf
来说明是 yes 还是 no。我们创建的随机森林使用了 20、50 或 100 棵树。利用这些,我们可以执行网格搜索,遍历所有参数组合,并找出最佳组合。所以,让我们给我们的第二个管道编号,其中包含 TF-IDF。我们将使用 fit
执行搜索,结果可以在以下截图中看到:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00096.jpeg
由于单词数目很大,需要一点时间,大约 40 秒钟,最终找到最佳参数。我们可以从网格搜索中获取最佳参数并打印出来,看看分数如何:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00097.gif
因此,我们达到了近 96% 的准确率。我们使用了大约 1,000 个单词,仅使用了单词,使用了 yes 来去除停用词,在随机森林中使用了 100 棵树,同时使用了 yes、IDF 和 TF-IDF 计算。在这里,我们不仅演示了词袋模型、TF-IDF 和随机森林,还演示了管道特性和网格搜索特性。
Word2Vec 模型
在本节中,我们将学习关于 Word2Vec 的知识,这是一种现代和流行的处理文本的技术。通常情况下,Word2Vec 的表现优于简单的词袋模型。词袋模型只计算每个文档中每个单词出现的次数。给定两个这样的词袋向量,我们可以比较文档,看它们的相似程度。这与比较文档中使用的单词是一样的。换句话说,如果两个文档有许多相似的单词,它们出现的次数也相似,那么它们将被认为是相似的。
但是词袋模型没有关于单词相似性的信息。因此,如果两个文档没有使用完全相同的单词,而是使用同义词,比如 please 和 plz,它们在词袋模型中并不被视为相似。Word2Vec 可以发现某些单词彼此相似,我们可以利用这一点在进行文本机器学习时获得更好的性能。
在 Word2Vec 中,每个单词本身就是一个向量,可能有 300 维。例如,在一个预训练的 Google Word2Vec 模型中,它检查了数百万或数十亿页的文本,我们可以看到 cat、dog 和 spatula 是 300 维向量:
-
猫 = <0.012, 0.204, …, -0.275, 0.056>(300 维)
-
狗 = <0.051, -0.022, …, -0.355, 0.227>
-
锅铲 = <-0.191, -0.043, …, -0.348, 0.398>
-
猫和狗的相似度(距离)—0.761
-
猫和锅铲的相似度—0.124
如果我们比较狗和猫的向量相似度,我们将得到 0.761,或者说 76%的相似度。如果我们用猫和锅铲做相同的比较,我们得到 0.124。很明显,Word2Vec 学到了狗和猫是相似的词,而猫和锅铲则不是。Word2Vec 利用神经网络来学习这些词向量。从高层次来看,神经网络类似于随机森林或决策树以及其他机器学习技术,因为它们接收一组输入和一组输出,并学习如何根据输入预测输出。
对于 Word2Vec,输入是单个词,即我们想要学习其向量的词,输出是该词周围的词。Word2Vec 也支持这种输入输出配置的反向操作。因此,Word2Vec 通过记住上下文词来学习词向量。所以,狗和猫会有相似的词向量,因为这两个词在类似的场景中使用,例如 她抚摸了狗 和 她抚摸了猫。通过 Word2Vec 进行神经网络训练可以采取两种形式,因为 Word2Vec 支持两种不同的训练技术。
第一种技术被称为连续词袋模型,在这种模型中,上下文词作为输入,忽略中间词,正在学习向量的词(即中心词)作为输出。在以下图示中,你可以看到channel这个词前后各有三个词:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00098.jpeg
这些是上下文词。连续词袋模型会在整个句子中滑动,每个词依次充当中心词。神经网络学习每个词的 300 维向量,以便在给定上下文词的情况下预测中心词。换句话说,它能够根据输入预测输出。
在第二种技术中,我们将会反转这一过程。这被称为skip-gram,其中中心词是输入,上下文词是输出:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00099.jpeg
在这种技术中,中心词向量被用来预测给定中心词的上下文词。
这两种技术在大多数情况下表现良好。它们各自有一些小优缺点,但对于我们的使用场景来说并不重要。
Doc2Vec
我们将使用 Word2Vec 来检测正面和负面的产品、餐厅和电影评论。我们将使用一种略微不同形式的 Word2Vec,称为Doc2Vec。在这种情况下,输入是文档名,例如文件名,输出是文档中的词汇滑动窗口。这一次,我们将没有中心词:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00100.jpeg
在这种情况下,作为一个帮助我们预测单词的向量,通过了解文件名。事实上,输入并不非常重要,在本例中就是文件名。我们只需要跟踪右侧的单词,确保它们都来自同一文档。因此,所有这些单词都会与该文件名连接起来,但文件名的实际内容并不重要。因为我们可以根据文件名预测文档的单词,所以我们可以有效地拥有一个模型,知道文档中哪些单词是会一起出现的。换句话说,文档通常只谈论一件事,例如,学习到很多不同的正面词汇会出现在正面评论中,而负面评论中会出现很多负面词汇。
文档向量
训练后,我们得到一个新的文档,想要找到它的文档向量。我们将利用在训练过程中学到的单词相似性来构建一个向量,预测新文档中的单词。我们将使用一个虚拟的文件名,因为实际名称并不重要。重要的是它只是一个名称。所以,所有这些单词都将在该一个名称下连接在一起:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00101.jpeg
一旦我们获得了新的文档向量,就可以将其与其他文档向量进行比较,并找到最相似的过去已知文档,如下所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00102.jpeg
因此,我们可以使用Doc2Vec
来找出哪些文档彼此最相似。这将帮助我们检测正面和负面评论,因为理想情况下,正面评论将具有相似的文档向量,负面评论也是如此。我们期望Doc2Vec
的表现优于词袋模型,因为Doc2Vec
学习的是在同一文档中一起使用的单词,所以那些类似于词袋模型的单词并没有实际学到关于单词相似性的信息。
检测用户评论中的正面或负面情感
在这一部分,我们将研究如何检测用户评论中的正面和负面情感。换句话说,我们将检测用户是对产品或服务发表正面评论还是负面评论。我们将专门使用Word2Vec
和Doc2Vec
,以及gensim
Python 库来实现这些服务。我们有两个类别,正面和负面评论,而且我们有超过 3,000 条不同的评论供我们分析。这些评论来自 Yelp、IMDb 和 Amazon。让我们通过导入gensim
库开始代码,gensim
提供了Word2Vec
和Doc2Vec
,并且用于记录消息的状态:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00103.jpeg
首先,我们将看到如何加载一个由谷歌提供的预训练的Word2Vec
模型,该模型已在数十亿页文本上进行训练,并最终为所有不同的单词生成了 300 维的向量。加载模型后,我们将查看cat
的向量。这表明模型是一个 300 维的向量,如同cat
所表示的:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00104.gif
下图显示了单词dog
的 300 维向量:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00105.gif
下图显示了单词spatula
的 300 维向量:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00106.jpeg
我们在计算狗和猫的相似度时得到了 76%的结果,如下所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00107.jpeg
猫和铲子的相似度是 12%;它有点低,这也是应该的:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00108.jpeg
这里我们使用以下代码训练我们的Word2Vec
和Doc2Vec
模型:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00109.jpeg
我们使用Doc2Vec
是因为我们希望为每个文档确定一个向量,而不是每个单词的向量,因为我们的文档是评论,我们希望查看这些评论是正面的还是负面的,这意味着它类似于正面评论或类似于负面评论。Doc2Vec
由gensim
提供,库中有一个叫做TaggedDocument
的类,它允许我们使用“这些是文档中的单词,Doc2Vec 是模型
”。
现在我们创建一个工具函数,它将接收一个句子或整个段落,并将其转为小写,移除所有的 HTML 标签、撇号、标点符号、空格和重复的空格,最终将其按单词拆分:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00110.jpeg
现在是时候准备我们的训练集了。我们不会使用 3,000 条 Yelp、IMDb 和 Amazon 的评论,因为这些数据量不足以训练一个良好的Doc2Vec
模型。如果我们有数百万条评论,那我们可以拿出一部分进行训练,其余的用作测试,但仅有 3,000 条评论是不够的。因此,我从 IMDb 和其他地方收集了评论,包括 Rotten Tomato。这些足以训练一个Doc2Vec
模型,但这些评论并不来自我们最终预测所使用的数据集。这些评论仅仅是评论。它们是正面的;它们是负面的。我不知道是哪种,因为我没有记录是哪种。重要的是我们有足够的文本来学习单词在这些评论中的使用方式。没有任何记录说明评论是正面的还是负面的。
因此,Doc2Vec
和Word2Vec
实际上是用于无监督训练。这意味着我们没有任何答案。我们只是学习单词是如何一起使用的。记住单词的上下文,以及单词如何根据附近的单词来使用:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00111.jpeg
所以,在每个案例中,在每个文件中,我们简单地使用该文档或评论中的单词加上一个标签,标签就是文件名。这一点很重要,因为它可以让模型学习到这些单词属于同一文档,并且这些单词之间是有某种关系的。加载后,我们从不同的文档中得到了 175,000 个训练示例:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00112.gif
现在让我们看一下下图中的前 10 个句子:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00113.gif
我们打乱这些文档,然后将它们输入到 Doc2Vec
训练器中,使用 Doc2Vec(permuter, dm=0, hs=1, size=50)
,在这里我们最终训练了 Doc2Vec
模型,并让它学习所有不同文档的文档向量。dm=0
和 hs=1
只是表示如何进行训练的参数。这些是我发现最准确的设置。dm=0
是我们在上一节中展示的模型,意味着它接收一个文件名并预测单词:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00114.gif
这里的 size=50
表示我们发现每个文档的 50 维向量是最好的,而 300 维的向量则最优,因为我们没有足够的训练样本。由于我们没有数百万或数十亿的数据,这是一个很好的 300 维向量,50 维似乎效果更好。运行这段代码会使用处理器和所有核心,所以执行时会花费一些时间。你会看到它显示了完成的百分比。最终,在我的案例中,它花了 300 秒来获取这些信息,肯定算是不错的。速度挺快的,但如果你有数百万或数十亿的训练文档,那可能需要几天时间。
一旦训练完成,我们可以删除一些内容以释放内存:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00115.jpeg
我们确实需要保留推断数据,这足以为新文档绑定一个新的文档向量,但我们不需要保留所有关于不同单词的数据。
你可以保存模型,然后用 model = Doc2Vec.Load('reviews.d2v')
命令在之后加载它,如果你想把它放到产品中部署,或者放到服务器上:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00116.jpeg
模型训练完成后,你可以推断出一个向量,表示该新文档的文档向量。那么,让我们使用工具函数提取单词。这里我们使用的是一个在评论中找到的示例短语。这是它为该短语学习到的 50 维向量:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00117.jpeg
现在出现的问题是,负面短语呢?还有其他负面短语。它们被认为相似吗?嗯,它们被认为有 48%的相似度,如下截图所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00118.jpeg
那不同的短语怎么样呢?Highly recommended
和 Service sucks
。它们不太相似:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00119.jpeg
模型学习了单词在同一评论中如何一起使用,并了解到这些单词以一种方式组合,而其他单词则以另一种方式组合。
最后,我们准备加载我们的真实数据集进行预测:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00120.jpeg
总结一下,我们使用了 Yelp、Amazon 和 IMDb 的评论。我们加载了不同的文件,每个文件中的每一行都包含一条评论。最终,我们从行中提取了单词,找出了该文档的向量。我们将其放入一个列表中,打乱顺序,最终构建了一个分类器。在这种情况下,我们将使用 k 近邻算法,这是一种非常简单的技术。
它只是一个技术,告诉你找到所有相似的文档,在这里就是找到与我们正在查看的文档最相似的九个文档,并进行投票计数:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00121.jpeg
本例中我们将使用九条评论,如果大多数是正面评论,那么我们也会认为这是一条正面评论。如果大多数是负面评论,那么我们也认为它是负面的。我们不希望评论出现平局,这就是为什么我们选择了九条评论而不是八条的原因。
现在我们将与随机森林的结果进行比较:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00122.jpeg
现在我们需要对 9 个最近邻进行交叉验证;我们使用Doc2Vec
检测正面/负面评论的准确率为 76%。为了实验目的,如果我们使用随机森林而不特意选择树的数量,我们只能得到 70%的准确率:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00123.jpeg
在这种情况下,k 近邻算法既简单又准确。最终,这一切都值得吗?那么,让我们将其与词袋模型进行比较。我们来做一个小管道,使用CountVectorizer
、TF-IDF 和随机森林,最后在相同的数据上进行交叉验证,这里指的是评论数据。我们得到的结果是 74%,如下图所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00124.jpeg
在执行模型构建后,我们发现Doc2Vec
的效果更好。如果我们添加很多与测试集风格相同的训练示例,Doc2Vec
比词袋模型要准确得多。因此,在我们的案例中,测试集几乎都是 Yelp、Amazon 和 IMDb 上的评论,都是一句话或一行文本,内容比较简短。然而,我们找到的训练集来自不同地方的不同评论,约有 175,000 个示例。这些评论通常是段落形式,或者是用不同的方式书写的。
理想情况下,我们将在与将来预测内容相似的示例上训练一个Doc2Vec
或Word2Vec
模型,但找到足够的示例可能会很困难,正如这里所面临的情况一样,因此我们尽力而为。即便如此,结果仍然比词袋模型要好。
总结
本章中,我们介绍了文本处理和词袋模型技术。接着,我们使用该技术为 YouTube 评论构建了一个垃圾评论检测器。随后,我们了解了复杂的 Word2Vec 模型,并通过一个编码项目来应用它,该项目可以检测正面和负面的产品、餐馆和电影评论。这就是本章关于文本处理的全部内容。
在下一章,我们将讨论深度学习,这是一种在神经网络中广泛应用的技术。
第四章:神经网络
在本章中,我们将对神经网络进行概述。我们将了解什么是简单的浅层神经网络,并对它们的工作原理有一些了解。我们将通过尝试使用浅层神经网络识别歌曲的风格来进行实践。同时,我们还将回顾之前使用神经网络进行垃圾邮件检测器工作的经验。之后,我们将研究更大的神经网络,称为深度学习,并应用卷积神经网络来识别手写的数学符号。最后,我们将重新审视之前讨论的鸟类物种识别器,并使用深度学习来生成一个更加准确的识别器。
本章将涵盖的主题如下:
-
理解神经网络
-
使用神经网络识别歌曲的风格
-
回顾我们在使用神经网络进行垃圾邮件检测器的工作
理解神经网络
神经网络,最初称为人工神经网络,灵感来自于动物大脑及其他神经系统部分中的实际神经元。神经元彼此连接,接收并发送冲动信号,通过动物的身体或计算机中的网络传递。
以下图示展示了单个神经元的组成部分:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00125.jpeg
单个神经元的组成部分
以下图示展示了神经元是如何发放信号的:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00126.jpeg
神经元是如何发放信号的
它是“全或无”的意思,也就是说,当神经元从其邻居那里获得足够的输入时,它会迅速发放信号,并将信号沿着轴突传送给每个前馈连接的神经元。
在这里,我们可以看到大脑中的实际神经元:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00127.jpeg
大脑中的实际神经元
人类大脑总共有大约 1000 亿个神经元,并且约有 100 万亿个连接。值得注意的是,我们在软件中创建的神经网络的复杂度至少低于 1 百万倍。
前馈神经网络
我们设计的大多数神经网络都是前馈的,并且是完全连接的。这意味着每个神经元都会连接到下一层的每个神经元。第一层接收输入,最后一层给出输出。网络的结构,包括神经元数量及其连接,是预先决定的,在训练过程中无法更改。此外,每个输入必须具有相同数量的值。这意味着图像等内容可能需要调整大小,以匹配输入神经元的数量。每一层神经元的数量就是该层的形状:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00128.jpeg
前馈神经网络设计
每个神经元会将它从前一层接收到的值累加起来。每个神经元之间的连接都有一个权重。在加和输入时,输入会与相应的权重相乘。每个神经元还有一个额外的输入,称为偏置,它与其他神经元没有连接。一旦加权输入值被累加,激活函数会应用于该和。
有几种常见的激活函数,例如双曲正切,其形状如下所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00129.jpeg
双曲正切
每个神经元的输出是激活函数的结果。
网络中的连接权重开始时是随机的,并在训练过程中进行调整。训练的目的是通过检查成百上千甚至更多的示例案例,调整网络的权重,直到网络足够准确。
训练结束后,我们得到一个已经定义好的网络结构,并且所有在训练中学到的权重。因此,以下内容是正确的:
训练过的神经网络 = 结构 + 学到的权重
如下所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00130.jpeg
训练后带有权重的网络结构
现在,网络已经准备好应用于训练集之外的新数据。
训练是按批次进行的,这意味着将多个训练样本送入网络,收集输出结果,称为预测。然后,计算每个批次的损失,损失是总体误差的衡量标准:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00131.jpeg
训练过程:评估一个批次,调整权重,然后重复
然后,网络中的每个权重都会根据它对总体损失的贡献程度进行调整。通过非常渐进的调整,应该能确保当这个批次的示例再次被访问时,预测会更准确。
网络通常会在多个周期(epoch)中进行训练。一个周期意味着所有训练数据都已处理一次。因此,10 个周期意味着对相同的训练数据查看 10 次。我们通常会将大约 20%的训练数据分离出来作为验证集。这部分数据在训练过程中不会使用,只会在每个周期后用于评估模型。
理想情况下,我们希望网络变得更加准确,这意味着我们希望减少损失,这对于训练集和验证集都应适用。
以下一组图表显示了这种理想行为:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00132.jpeg
理想行为
注意过拟合的迹象,即训练损失下降,但验证损失上升。
如果网络设计不正确,例如网络层数过多,可能会发生过拟合,这意味着网络在训练集上表现很好,但在验证集上表现差。这是一个问题,因为最终我们希望将神经网络应用于来自真实世界的新数据,这些数据可能与训练集略有不同,因此我们使用验证集来查看网络在未见过的训练数据上的表现。
使用神经网络识别歌曲的类型
在这一部分,我们将构建一个神经网络来识别歌曲的流派。我们将使用 GTZAN 流派集(marsyasweb.appspot.com/download/data_sets/.GTZAN Genre Collection
)。它包含来自 10 多种不同流派的 1,000 首歌曲。每种流派有 100 首歌曲,每首歌大约 30 秒长。
我们将使用 Python 库librosa
来从歌曲中提取特征。我们将使用Mel 频率倒谱系数(MFCC)。MFCC 值模仿人类听觉,通常用于语音识别应用程序以及音乐流派检测。这些 MFCC 值将直接输入到神经网络中。
为了帮助我们理解 MFCC,让我们使用两个示例。下载 Stereo Surgeon 的《Kick Loop 5》。你可以通过访问freesound.org/people/Stereo%20Surgeon/sounds/266093/
来下载它,另外下载 cmagar 的《Whistling》可以通过访问freesound.org/people/grrlrighter/sounds/98195/
来获取。一个是低音鼓,另一个是高频口哨声。它们显然不同,我们将看看它们在 MFCC 值上有何区别。
让我们进入代码部分。首先,我们需要导入librosa
库。我们还需要导入glob
,因为我们将列出不同类型目录中的文件。另外,像往常一样导入numpy
。我们将导入matplotlib
来绘制 MFCC 图表。然后,从 Keras 导入 Sequential 模型。这是一个典型的前馈神经网络。最后,我们将导入全连接神经网络层,它只是一个包含许多神经元的层:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00133.jpeg
与卷积不同,它将有二维表示。我们将导入激活函数,它允许我们为每个神经元层指定激活函数,还将导入to_categorical
,它允许我们将类名转换为诸如摇滚、迪斯科等内容,这就是所谓的独热编码(one-hot encoding)。
我们已经正式开发了一个辅助函数来显示 MFCC 值:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00134.jpeg
首先,我们将加载歌曲并从中提取 MFCC 值。接着,我们将使用specshow
,这是librosa
库中的一个频谱图显示工具。
这是低音鼓:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00135.jpeg
我们可以看到,在低频时,低音非常明显,其他频率几乎没有表现出来。
然而,如果我们看一下口哨声,很明显可以看到高频成分的表现:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00136.jpeg
颜色越深,或者越接近红色,表示在该时间点该频率范围内的能量越大。所以,你甚至可以看到口哨声时频率的变化。
现在,这是迪斯科歌曲的频率:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00137.jpeg
这是频率输出:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00138.jpeg
你可以在前面的输出中大致看到节拍,但它们只有 30 秒长,所以很难看清单独的节拍。
将此与古典音乐进行比较,古典音乐中并不像嘻哈那样有明显的节拍,而是连续的低音线,例如大提琴所发出的那种低音线:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00139.jpeg
这是嘻哈歌曲的频率:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00140.jpeghttps://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00141.jpeg
它看起来有点像迪斯科,但如果要求我们能够用肉眼区分这些,我们就不需要神经网络了,因为这可能是一个相对简单的问题。所以,无法用肉眼区分这些不是我们的问题,而是神经网络的问题。
我们还有一个辅助函数,这次它只加载 MFCC 值,但这次我们是为神经网络做准备:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00142.jpeg
我们已经加载了这首歌的 MFCC 值,但由于这些值的范围可能在-250 到+150 之间,因此它们对神经网络没有帮助。我们不想输入这些大值和小值。我们希望输入接近-1 和+1 或从 0 到 1 之间的值。因此,我们将计算每首歌的最大值,即每个歌的绝对值,然后将所有值除以该最大值。此外,由于歌曲的长度略有不同,我们只想选择 25,000 个 MFCC 值。我们必须确保输入神经网络的内容始终具有相同的大小,因为神经网络的输入神经元数量是有限的,一旦建立了网络,我们就无法改变它。
接下来,我们有一个名为generate_features_and_labels
的函数,它将遍历所有不同的音乐流派,逐一处理数据集中的所有歌曲,并生成这些 MFCC 值和类别名称:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00143.jpeg
如上图所示,我们将为所有特征和标签准备一个列表。遍历 10 个流派。对于每个流派,我们将查看该文件夹中的文件。'generes/'+genre+'/*.au'
文件夹展示了数据集的组织方式。当我们处理该文件夹时,每个文件将有 100 首歌,我们将提取特征并将这些特征放入all_features.append(features)
列表中。这首歌的流派名称也需要放入一个列表中。因此,最终,所有特征将包含 1,000 个条目,所有标签也将包含 1,000 个条目。对于所有特征,每个 1,000 个条目将包含 25,000 个条目。这将是一个 1,000 x 25,000 的矩阵。
目前,对于所有标签,存在一个包含 1,000 个条目的列表,列表中包含诸如blues
、classical
、country
、disco
、hiphop
、jazz
、metal
、pop
、reggae
和rock
等词语。现在,这会成为一个问题,因为神经网络不会预测单个单词或字母。我们需要为其提供一个独热编码,这意味着这里的每个词将被表示为十个二进制数。例如,在blues
情况下,它将是一个 1,后面跟着九个 0;在classical
情况下,它将是一个 0,后面跟着一个 1,再后面跟着九个 0,以此类推。首先,我们必须通过使用np.unique(all_labels, return_inverse=True)
命令来找出所有独特的标签,并将其转换为整数。然后,我们需要使用to_categorical
,它将这些整数转换为独热编码。返回的结果是 1000 x 10 维的数组。1,000 是因为有 1,000 首歌,每首歌都有十个二进制数来表示独热编码。然后,通过命令return np.stack(all_features), onehot_labels
将所有特征堆叠到一个矩阵中,返回独热编码矩阵。因此,我们将调用该函数并保存特征和标签:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00144.jpeg
为了确保无误,我们将打印特征和标签的形状,如下截图所示。特征的形状是 1,000 x 25,000,标签的形状是 1,000 x 10。现在,我们将把数据集拆分为训练集和测试集。我们将 80%的数据作为训练集,定义为training_split= 0.8
来进行拆分:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00145.jpeg
在此之前,我们需要先打乱数据,在打乱之前,我们需要将标签和特征配对,以确保它们不会以不同的顺序被打乱。我们将调用np.random.shuffle(alldata)
来进行打乱,然后使用splitidx = int(len(alldata)*training_split)
来拆分数据集,最终得到训练集和测试集,如前面的截图所示。查看训练集和测试集的形状,训练集有 800 条数据,即 1,000 的 80%。每条数据有 25,010 个特征,但这些并不是真正的所有特征。实际上,它是 25,000 个特征加上 10 个用于独热编码的特征,因为记住,在打乱之前我们已经将它们堆叠在一起了。因此,我们需要将这部分数据去除。我们可以通过train_input = train[:,:-10]
来完成。对于训练输入和测试输入,我们取所有列除了最后 10 列,对于标签,我们只取最后 10 列。然后,我们可以查看训练输入和训练标签的形状。现在,我们得到了正确的 800 x 25,000 和 800 x 10 的形状。
接下来,我们将构建神经网络:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00146.jpeg
我们将使用一个顺序神经网络。第一层将是 100 个神经元的密集层。现在,仅在第一层中,输入的维度或输入形状是非常重要的,在我们的例子中,它是 25000。这表示每个样本输入多少个值。这 25000 将会连接到第一层中的 100 个神经元。第一层会对输入进行加权求和,权重和偏置项,然后我们将运行relu
激活函数。如果你还记得,relu
表示任何小于 0 的值都会变成 0,而任何大于 0 的值都将保持不变。这 100 个神经元将连接到另外 10 个神经元,这将是输出层。输出层将是 10,因为我们已经进行了独热编码,并且在该编码中有 10 个二进制数字。
代码中使用的激活函数softmax
表示将 10 个输出进行归一化,使它们的和为 1。这样,它们就会变成概率,且在这 10 个概率中,得分最高的那个(即概率最大)将作为预测结果,并直接对应到得分最高的数字所在的位置。例如,如果最高的得分出现在第 4 个位置,那就表示是迪斯科(见代码中的说明)。
接下来,我们将编译模型,选择一个优化器,比如 Adam,并定义loss
损失函数。每当我们有多个输出(比如这里的 10 个输出)时,我们通常会选择分类交叉熵(categorical cross-entropy)和精度(accuracy)作为评估指标,以便在训练过程中和评估时查看精度,此外损失函数会始终显示:不过,精度对于我们来说更有意义。接着,我们可以打印model.summary
,这会告诉我们关于各层的详细信息。
它的执行结果大致如下:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00147.jpeg
第一个 100 个神经元层的输出形状肯定是 100 个值,因为有 100 个神经元,而第二层密集层的输出是 10,因为有 10 个神经元。那么,为什么第一层有 250 万个参数或权重呢?那是因为我们有 25000 个输入。实际上,我们有 25000 个输入,每一个输入都连接到这 100 个密集神经元中的每一个。所以总数是 250 万个,再加上 100,因为这 100 个神经元每个都有一个偏置项,自己的偏置权重,而这些也需要被学习。
总的来说,我们有大约 250 万个参数或权重。接下来,我们运行 fit 方法。它会接受训练输入和训练标签,并指定我们想要的训练轮数。我们需要 10 轮,所以是对训练输入进行 10 次重复训练;它还会接受一个批量大小,表示每次更新权重之前要处理多少个样本,在我们的例子中是若干首歌曲;并且validation_split
为 0.2,这表示从训练输入中取出 20%,不在此上进行训练,而是用它来评估每轮训练后的效果。验证集永远不会用于训练,但它可以让我们在训练过程中查看模型的进展。
最后,因为我们提前分离了训练集和测试集,所以我们将对测试数据进行评估,并打印出该数据集的损失和准确率。这里是训练结果:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00148.jpeg
它在运行时会持续打印输出。它总是打印损失和准确率。这是针对训练集本身,而不是验证集,因此准确率应该接近 1.0。实际上,你可能不希望它接近 1.0,因为那可能意味着过拟合,但如果你让它训练足够长的时间,通常会在训练集上达到 1.0 的准确率,因为它在记住训练集。我们真正关心的是验证准确率,因为它让我们使用测试集。测试集是模型从未见过的数据,至少在训练时没有见过,事实上,验证准确率与最终准确率非常接近。这个最终准确率是基于我们提前分离出的测试数据。现在我们的准确率大约为 53%。这个结果看起来相对较低,直到我们意识到有 10 种不同的类别。随机猜测的准确率是 10%,所以这个结果比随机猜测要好得多。
修改垃圾邮件检测器以使用神经网络
在这一部分,我们将更新之前的垃圾邮件检测器,改为使用神经网络。回想一下,使用的数据集来自 YouTube,共有约 2,000 条评论,其中一半是垃圾邮件,另一半不是。这些评论来自五个不同的视频。
在上一个版本中,我们使用了词袋模型和随机森林。我们进行了参数搜索,以找到最适合词袋模型的参数,这些参数是包含 1,000 个不同单词的 CountVectorizer。这些 1,000 个单词是使用频率最高的单词。我们使用了单一词项(unigrams)而非二元组(bigrams)或三元组(trigrams)。最好从英语语言中去除常见词和停用词。最好的方法是使用 TF-IDF。我们还发现,使用 100 棵树对随机森林来说效果最好。现在,我们将继续使用词袋模型,但将随机森林替换为浅层神经网络。另请记住,在上一个版本中,我们的准确率达到了 95%或 96%。
看一下代码:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00149.jpeg
我们从导入开始。我们再次使用 pandas 来加载数据集。这次,我们将使用 Keras Tokenizer。使用 Tokenizer 没有特别的理由,只是为了展示一种替代技术。我们将导入 NumPy,然后继续导入神经网络的顺序模型,这是典型的前馈网络。接着,我们添加典型的密集层,即神经元层。我们还将添加 dropout 功能,帮助防止过拟合,并决定每一层的激活函数。我们将使用来自 Keras 的np_utils
库中的to_categorical
方法来生成独热编码,并引入StratifiedKFold
来执行交叉验证。
首先,我们加载数据集:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00150.jpeg
有五个不同的 CSV 文件。我们将把它们堆叠在一起,以便形成一个大的数据集。然后我们通过运行一个随机抽取行的样本来对其进行洗牌。我们将设置为保留 100%的数据,从而有效地洗牌所有数据。
现在,StratifiedKFold
技术会取一个拆分数,比如五个,并生成原始数据集在这些拆分中的索引:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00151.jpeg
我们将得到一个 80%/20%的训练和测试集拆分。这个 20%的测试集在每次拆分时都会不同。它是一个迭代器,因此我们可以使用for
循环查看所有不同的拆分。我们将打印测试位置,以确保它们在每次拆分中都没有重叠:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00152.jpeg
这是第一个拆分:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00153.jpeg
这是第二个拆分:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00154.jpeg
这是第三个:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00155.jpeg
这是第四个:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00156.jpeg
最后,看看第五个:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00157.gif
现在显然可以看出它们没有重叠。
接下来,我们定义一个函数,接收这些不同拆分的索引,并执行词袋模型,构建神经网络,训练并评估它。然后返回该拆分的得分。我们从获取训练集和测试集的位置开始,并提取评论:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00158.jpeg
然后我们继续构建我们的 Tokenizer。在这个阶段,我们可以指定 Tokenizer 支持的词汇数量。一项研究得出结论,使用 2,000 个词比使用 1,000 个词更好。对于随机森林来说,使用 1,000 个词更好,并且通过进行所有参数的 GridSearch 验证了这一点。没有特别的理由认为,由于词袋模型在 1,000 个词上表现最佳,而随机森林也适用,因此它一定是神经网络的最佳选择。所以,在这个案例中,我们将使用 2,000 个词。这只是一个构造函数,词袋模型实际上还没有发生什么。接下来我们需要做的是学习这些词汇,这将通过使用fit_on_texts
方法来实现。
现在,fit_on_texts
应该只用于训练集。我们只希望学习训练集中的单词。这帮助我们模拟真实世界的情境:在训练模型时,只在某一数据集上进行训练,然后真实世界可能会呈现出一些全新的数据,我们从未见过。为了实现这一点,我们采用了训练和测试的拆分方法。我们只想学习训练集中的单词。如果测试集中的某些单词在训练集中从未出现过,它们将被忽略。这是好的,因为它正是现实世界中会发生的情况。
我们将在训练集上学习单词,然后将训练集和测试集的评论转换成词袋模型。texts_to_matrix
用于此目的。它生成一个矩阵,可以直接输入到神经网络中。我们给它train_content
(评论)和test_content
。然后,我们可以决定是否使用tfidf
分数、二进制分数或频率计数。我们这次将使用tfidf
。tfidf
是一个介于 0 和任何随机整数之间的数,可能是一个很大的数字,在大多数情况下,不建议给神经网络中的神经元输入过大的数字或过小的数字(即负数)。在这里,我们希望将这些数字缩放到 0 和 1 之间,或者-1 和 1 之间。为了将其缩放到 0 和 1 之间,我们可以除以最大值。因此,我们必须查看所有训练示例中的所有 TF-IDF 训练数字,并将每个数字除以其中的最大值。测试集也需要做同样的处理。现在,训练输入和测试输入是已经重新缩放到 0 到 1 之间的tfidf
分数。
我们还通过从每个分数中减去平均值,将其在-1 和 1 之间进行转换。现在,对于输出,尽管我们可以使用二进制,但我们在此案例中将使用分类输出,没什么特别的原因,仅仅是为了展示。我们将把所有的期望输出(即类别),如垃圾邮件和非垃圾邮件,转换成 1,0 和 0,1 的编码。
现在,我们可以构建我们的网络了。我们将在每次训练/测试拆分时重新构建网络,这样它就会从随机状态开始。我们将构建一个顺序网络,这是一种典型的前馈网络。我们将有一个由 512 个神经元组成的第一层。它们将接收 2,000 个不同的输入。之所以是 2,000,是因为词袋模型的大小是 2,000。
然后我们使用 ReLU 激活函数。我们也可以使用 Tanh。ReLU 在今天的神经网络中很常见,且它的运算速度较快,精度也较高。这里有 512 个神经元的层,接着是一个 2 个神经元的层。2 个神经元是很特定的,因为这是输出层。我们使用独热编码(one-hot encoding),所以是 1,0,0,1,这样就是两个神经元。它必须与我们所需的输出数量相匹配。每个神经元都与之前的 512 个神经元相连接。这是很多边缘连接,将第一层和第二层相连。
为了防止过拟合,我们加入了一个丢弃层。50%的丢弃率意味着每次更新权重时,它会随机拒绝更新一半的权重。然后,我们会计算它们输入的加权和。
我们将那个加权和输入到 softmax 函数。Softmax 会将这些不同的输出转化为概率,使得其中一个概率最大,且所有的概率值都介于 0 和 1 之间。接着,我们编译模型,计算损失函数为categorical_crossentropy
。这是通常在使用独热编码时使用的损失函数。接着,我们使用 Adamax 优化器。Keras 提供了不同的优化器,你可以查看 Keras 文档:keras.io/
。
准确率是训练网络时需要关注的一个重要指标,我们也希望在最后计算准确率,以查看模型的表现。
然后,我们在训练集上运行 fit 函数。d_train_inputs
是训练输入数据,d_train_inputs
是矩阵词袋模型,训练输出数据以及独热编码。我们设定 10 个 epoch,这意味着它会遍历整个训练集 10 次,批量大小为 16,这意味着它会遍历 16 行数据,计算平均损失后再更新权重。
在模型拟合完成后,间接地意味着它已经经过训练,我们会进行测试评估。直到这时,它才会查看测试集。输出的分数包括损失和其他我们设定的度量标准,在这个例子中是准确率。因此,我们将显示准确率乘以 100,得到百分比,然后返回分数。
现在,我们重新构建那个拆分,即使用五个不同折叠的 k 折交叉验证:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00159.jpeg
我们收集分数。对于每个拆分,我们将运行train_and_test
函数并保存分数。在这里,它会在每个拆分上运行。如果你向下滚动,你会看到每个 epoch 的进展。我们可以看到训练输入的准确率在每个 epoch 上都在增加。如果准确率非常高,可能会开始担心过拟合,但在经过 10 个 epoch 后,我们使用从未见过的测试集进行测试。这帮助我们获得测试集的准确率。然后,我们会对下一个拆分进行相同操作,并得到一个不同的准确率。我们会再做几次,直到我们得到五个不同的数字,每个拆分一个。
平均值的计算方法如下:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00160.jpeg
在这里,我们得到了 95%的准确率,这与我们使用随机森林得到的结果非常接近。我们并没有使用这个神经网络示例来证明我们能得到 100%的准确率,而是用它来展示一种替代的垃圾邮件检测方法,替代了随机森林的方法。
总结
本章介绍了神经网络的简要概念,讲解了前馈神经网络,并展示了一个使用神经网络识别歌曲类型的程序。最后,我们修订了之前的垃圾邮件检测器,使其能够与神经网络一起工作。
在下一章中,我们将深入学习深度学习,并了解卷积神经网络。
第五章:深度学习
在本章中,我们将介绍一些深度学习的基础知识。深度学习是指拥有多层的神经网络。这虽然是一个流行词,但其背后的技术是真实且相当复杂的。
这个术语的流行程度正在上升,与机器学习和人工智能一起,如下图所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00161.jpeg
正如一些深度学习方法的发明者所言,深度学习的主要优势在于,添加更多数据和更多计算能力通常能产生更准确的结果,而无需进行大量的工程工作。
本章中,我们将探讨以下内容:
-
深度学习方法
-
使用 CNN 识别手写数学符号
-
重新审视鸟类物种识别器以使用图像
深度学习方法
深度学习是指在特定应用中可以使用的几种方法。这些方法包括卷积层和池化层。更简单、更快速的激活函数,例如 ReLU,当神经元的加权和为正时返回其加权和,为负时返回零。正则化技术,如丢弃法(dropout),在权重更新过程中随机忽略部分权重,以防止过拟合。GPU 被用于加速训练,其速度比传统方法快 50 倍。这是因为 GPU 针对矩阵计算进行了优化,而矩阵计算在神经网络和语音识别等应用中被广泛使用。
过去五年,多个因素推动了深度学习的快速发展。大量公共数据集,如包含数百万张标注图像的 ImageNet 和包含语音样本的 Mozilla Common Voice Project,现已可用。这些数据集满足了深度学习的基本要求——大量的训练数据。GPU 已经转向深度学习和集群,同时也专注于游戏领域。这有助于实现大规模的深度学习。
先进的软件框架已经开源并且在快速改进,人人都可以使用。这些框架包括 TensorFlow、Keras、Torch 和 Caffe。像 Inception-v3 这样的深度架构在 ImageNet 数据集上取得了最先进的成果。这个网络大约有 2400 万个参数,并且有一个庞大的研究人员和软件工程师社区,他们迅速将研究原型转化为开源软件,任何人都可以下载、评估和扩展。
卷积和池化
本节将深入探讨两项基本的深度学习技术,即卷积和池化。在这一部分中,我们将使用图像来理解这些概念。尽管如此,我们所学习的内容同样可以应用于其他数据,如音频信号。让我们来看一下以下的照片,并开始通过放大观察像素:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00162.jpeg
卷积是按通道进行的。输入图像通常由三个通道组成:红色、绿色和蓝色。接下来的步骤是将这三种颜色分离开。下图展示了这一过程:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00163.jpeg
卷积是一个核。在这张图中,我们应用了一个 3 x 3 的卷积核。每个卷积核包含一定数量的权重。卷积核在图像上滑动,并计算卷积核上像素的加权和,每个像素与其对应的卷积核权重相乘:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00164.gif
还添加了一个偏置项。每个卷积核滑动到的位置会产生一个单一的数字,即加权和。卷积核的权重最初是随机值,并在训练阶段发生变化。下图展示了三种不同权重的卷积核示例:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00165.jpeg
你可以看到图像如何根据权重不同而有所不同。最右边的图像强调了边缘,这通常对识别物体很有帮助。步幅帮助我们理解卷积核如何在图像上滑动。下图是一个 1 x 1 步幅的例子:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00166.jpeg
卷积核向右移动一个像素,然后向下移动。在这个过程中,卷积核的中心会接触到图像的每个像素,并与其他卷积核发生重叠。也可以观察到,有些像素被卷积核的中心遗漏。下图展示了一个 2 x 2 步幅的例子:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00167.jpeg
在某些情况下,观察到没有发生重叠。为了证明这一点,以下图包含了一个 3 x 3 步幅的例子:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00168.jpeg
在这种情况下,由于卷积核与步幅的大小相同,因此不会发生重叠。
然而,图像的边缘需要不同的处理方式。为了处理这一点,我们可以使用填充。填充有助于防止卷积核跨越图像边界。填充由额外的像素组成,这些像素的值始终为零,不参与加权和的计算。填充使得卷积核的权重能够覆盖图像的每个区域,同时仍然假设卷积核的步幅为 1。卷积核在覆盖的每个区域上会生成一个输出。因此,如果步幅大于 1,我们将得到的输出会比原始像素少。换句话说,卷积有助于减少图像的尺寸。下面的公式展示了卷积输出的尺寸:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00169.jpeg
通常的做法是使用方形图像。为了简化,卷积核和步幅通常是方形的。这有助于我们仅关注一个维度,并且宽度和高度是相同的。下图展示了一个 3 x 3 卷积核与(3, 3)步幅的例子:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00170.jpeg
上述计算结果为 85 宽度和 85 高度。图像的宽度和高度有效地从原始 256 缩小了三倍。我们不会使用较大的步幅,而是选择使用步幅为 1 的卷积,以便卷积能够遍历每个像素。这将帮助我们得到更实际的结果。我们还需要确保有足够的填充。然而,在网络中移动时减少图像尺寸是有益的,因为这样可以让网络更快地训练,减少参数数量。较少的参数意味着更小的过拟合风险。
我们通常在卷积维度之间使用最大池化或平均池化,而不是改变步幅长度。池化操作查看一个区域,假设它是 2 x 2,并且只保留最大值或平均值。以下图像展示了一个 2 x 2 矩阵,表示池化操作:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00171.gif
池化区域的步幅大小始终与池的大小相同。这有助于避免重叠。
池化操作不使用任何权重,这意味着没有需要训练的参数。
这是一个相对较浅的卷积神经网络(CNN)表示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00172.jpeg
来源:cs231.github.io,MIT 许可证
我们观察到,输入图像经过多个卷积和池化层,并且每层之间有 ReLU 激活,最终到达一个传统的全连接网络。尽管图中没有展示全连接网络,但它最终会预测类别。在这个例子中,与大多数 CNN 一样,我们会在每个层中进行多次卷积。这里我们观察到的是 10 个卷积,它们以行的形式展示。每个卷积都有自己的核在每一列中,这样可以在每个分辨率上学习不同的卷积。右侧的全连接层将决定哪些卷积最能识别汽车、卡车等。
使用 CNN 识别手写数学符号
本节内容涉及构建一个卷积神经网络(CNN)来识别手写数学符号。我们将使用HASYv2
数据集。该数据集包含来自 369 个不同类别的 168,000 张图像,每个类别代表一个不同的符号。与流行的 MNIST 数据集(包含手写数字)相比,这个数据集是一个更为复杂的类比。
以下图示展示了该数据集中可用的图像类型:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00173.jpeg
在这里,我们可以看到一张图表,展示了有多少符号拥有不同数量的图像:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00174.jpeg
我们观察到许多符号的图像较少,而有些符号则有大量图像。导入任何图像的代码如下:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00175.jpeg
我们首先从IPython
库导入Image
类,这样我们就可以在 Jupyter Notebook 中显示图像。以下是数据集中的一张图像:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00176.jpeg
这是一张字母A的图像。每张图像的尺寸为 30 x 30 像素。尽管它实际上不需要是 RGB 格式,这张图像还是采用了 RGB 格式。不同的通道主要是黑白或灰度的。我们将使用这三个通道。接着,我们导入 CSV 文件,这样就可以加载数据集了:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00177.jpeg
该 CSV 文件列出了所有不同的文件名和类名。我们从pil
库导入图像类,它允许我们加载图像。我们还导入了preprocessing.image
,它使我们能够将图像转换为numpy
数组。然后,我们会遍历数据文件,逐一查看每个文件名并加载它,同时记录它属于哪个类:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00178.jpeg
下一步是保存图像和类,并使用 CSV 读取器。我们需要设置一个计数器,确保跳过 CSV 文件的第一行(表头)。在这之后,我们继续打开图像,它位于每行的第一列。然后将其转换为数组。最终结果的维度将为 30 x 30 x 3,即 30 宽度、30 高度和 3 个通道(RGB)。
这三个通道的值将在 0 到 255 之间。这些是典型的像素值,但对于神经网络来说并不好。我们需要将值转换到 0 到 1 或-1 到 1 之间。为此,我们将每个像素值除以 255。为了简化操作,我们将收集文件名、类名和图像矩阵,并将它们放入我们的图像列表中。我们还会记录类的名称。以下代码片段将帮助我们更深入地理解这一概念:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00179.jpeg
该文件名为hasy-data/v2-00000.png
。A
是类的名称,后面跟着数组。该数组的维度为 30 x 30 x 3。最内层和最后一个维度是 3。每个 1.0 表示白色。这是因为我们像前面提到的那样将所有值除以 255。
我们在HASYv2
数据集中有 168,000 张图像:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00180.jpeg
然后我们继续对数据进行打乱,并按照 80%的训练集和 20%的测试集比例进行拆分。如下代码块所示,我们首先进行打乱,然后再拆分图像:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00181.jpeg
因为我们使用了包含三个不同值的元组,所以最终我们需要将它们收集成一个矩阵:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00182.jpeg
我们需要收集图像和标签。为了收集图像,我们遍历每一行并提取每个第三个元素。这个元素就是图像矩阵。我们将它们全部合并成一个numpy
数组。同样的操作适用于训练集和测试集。
对于输出,我们需要选择第二个值。这些值仍然是字符串,如a
和=
。我们需要将第二个值转换为独热编码,以便它能用于神经网络。
我们接着使用 scikit-learn 的预处理标签编码器和独热编码器:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00183.jpeg
我们将创建一个LabelEncoder
对象,并且对类进行拟合和转换:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00184.jpeg
fit
函数学习现存的类别。它学会了有 369 个不同的类别名称。transform
函数将这些类别转化为整数。这是通过对类别进行排序并为每个类别分配一个整数 ID 来实现的。integer_encoded
帮助我们以整数 ID 的形式重现类别列表。一热编码器(one-hot encoder)将这些整数进行拟合,并学习有多少个不同的整数。就像LabelEncoder
学习类别名称一样,onehot_encoder
将学习到有 369 个不同的整数。
代码接着使用了LabelEncoder
,它将train_output
转化为整数。这些整数随后被转化为一热编码(one-hot encoding)。一热编码返回一个 369 维的向量,第一维有 369 个值,向量中所有值都是零,除了一个值是 1。这个 1 的位置取决于该类别是哪一类。test_output
也经过同样的处理。当输入和输出的训练数据准备好后,我们继续构建神经网络。
为了做到这一点,我们将再次使用Sequential
:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00185.jpeg
Sequential 是一个前馈网络。尽管有卷积层,但它们仍然是前馈的,并且没有循环结构。网络的最后使用了全连接层(dense layers)。我们还使用了Dropout
来尽量避免过拟合。当我们从卷积层切换到全连接层时,我们需要使用flatten
命令,因为卷积层是二维的,而全连接层不是。我们还需要使用Conv2D
和MaxPooling2D
。
以下代码块是我们的网络设计:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00186.jpeg
这个模型借鉴了 MNIST 设计,它处理手写数字。我们首先创建一个顺序模型。我们需要添加一个具有 32 个不同卷积层的卷积层。卷积核大小为 3 x 3,激活函数为 ReLU。由于这是第一层,我们需要指定输入形状。如果你还记得,输入的维度是 30 x 30 x 3。
我们使用 3 x 3 的卷积核大小,并将步幅设置为 1,因为这是默认值。步幅为 1 时需要进行填充。这样会产生一个 30 x 30 x 32 的形状,因为有 32 个卷积层。30 x 30 的维度保持不变。我们现在可以观察到,仅通过进行卷积操作,我们并没有真正地减少维度。
MaxPooling
用于将维度缩减一半。这是可能的,因为它的池化大小为 2 x 2。然后,我们进行另一个卷积层,这是另一次维度缩减。
在所有卷积操作完成后,我们将所有内容展平。这将二维表示转化为一维表示。然后将其输入到一个拥有超过 1000 个神经元的全连接层。
这个密集层将会有一个tanh
激活函数。然后,这个结果会被送入另一个神经元的密集层。这个密集层有 369 个神经元,用于类别输出。这是onehot_encoding
输出。除了 softmax 激活函数外,我们不会使用其他激活函数。因此,原始值将被重新缩放到 0 和 1 之间。这意味着所有 369 个神经元的输出值之和为 1.0。Softmax 基本上将输出转化为概率。
重新编译categorical _crossentropy
有助于我们预测多个类别中的一个。你希望在adam
优化器上执行此操作,并观察其准确性。以下是模型的总结:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00187.jpeg
我们观察到卷积层不会改变维度,但池化会改变维度。由于奇数维度大小,即 15,它会将尺寸减半。下一个层的输出是 13,同样会被减半。conv2d_1 (Conv2D)
的参数用于学习卷积。dense_1 (Dense)
的参数用于学习与前一层连接的权重。类似地,dense_2 (Dense)
的参数用于学习前一层的权重。最终,我们有大约 160 万个参数。
我们将使用 TensorBoard 可视化性能的准确性和验证准确性。我们将把所有结果保存到名为mnist-style
的目录中,因为这就是我们之前构建的网络的风格。以下是一个回调:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00188.jpeg
Keras 支持各种类型的回调。回调函数在fit
方法中使用,因此每完成一个周期后,它会调用回调函数。回调函数会接收信息,比如验证损失和训练损失。我们使用 10 个周期和 32 的批量大小,且验证分割为 0.2(20%)。
这是训练的结果:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00189.jpeg
现在,有很多选择,但最终我们需要检查它们。我们得到了大约 76%的验证准确率,当我们在测试集上进行测试时,得到了相同的 76%准确率。现在,设计过程中有很多决策,包括卷积层的数量、每个卷积层的大小、使用什么类型的核,核的大小、步幅、卷积的激活函数、最大池化是否出现、池化的大小、密集层的数量及出现时机、激活函数等等。很多决策。选择这些不同设计的方式相当困难。这些实际上叫做超参数。
在拟合过程中可以学习的权重叫做参数,而关于如何设计网络、激活函数等的决策,我们称之为超参数,因为它们不能通过网络学习。为了尝试不同的参数,我们可以通过一些循环来实现:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00190.jpeg
我们将计时每个训练过程所需的时间。我们将收集结果,也就是准确率数字。然后,我们将尝试一个 2D 卷积层,可能会有一两个这样的层。我们将尝试一个包含 128 个神经元的密集层。我们将尝试一个 dropout,如for dropout in [0.0, 0.25, 0.50, 0.7]
,这意味着会有 0-25%、50%、75% 的可能性进行丢弃。所以,对于这些组合,我们根据卷积层的数量来创建模型,卷积层可能是一个或两个。我们将添加一个卷积层。
如果是第一层,我们需要输入输入形状,否则我们只需添加该层。然后,在添加卷积层后,我们将对最大池化做相同的操作。接着,我们将展平并添加一个密集层,其大小由for dense_size in [128, 256, 512, 1024, 2048]: loop
决定。不过,它总是使用tanh
激活函数。
如果使用了Dropout
,我们将添加一个 dropout 层。这个 dropout 的意思是,假设它是 50%,每次在每个批次更新权重时,每个权重有 50% 的概率不会被更新,但我们将这个层放在两个密集层之间,以防止过拟合。最后一层将始终是类别数量,因为必须如此,并且我们将使用 softmax。编译方法是相同的。
为 TensorBoard 设置一个不同的日志目录,以便我们能够区分不同的配置。启动计时器并运行 fit。进行评估并获取分数,停止计时器,并打印结果。所以,下面是它在所有这些不同配置下的运行情况:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00191.jpeg
0.74 是实际测试集的准确率。所以,你可以看到准确率有很多不同的数字。从低到高准确率大约在 0.7 到 0.8 之间,并且训练时间因网络中的参数数量而不同。我们可以可视化这些结果,因为我们正在使用回调函数。
这是来自训练集的准确率和损失:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00192.jpeg
这是验证准确率和验证损失:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00193.jpeg
稍微缩小视图,这样我们就可以看到旁边的配置,然后我们可以将它们全部关闭。再打开mnist-style
。这是我们尝试的第一个配置:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00194.jpeg
你可以看到,准确率在上升,损失在下降。这是非常正常的。验证准确率上升,损失下降,并且大体保持一致。我们不希望看到的是,尽管准确率在大幅上升,但验证损失在一段时间后急剧飙升。这几乎可以定义为过拟合。模型在训练集上的表现非常好,但在未见过的样本上表现大幅下滑。我们真的不希望发生这种情况。所以,让我们比较一些东西。首先,我们将比较不同的 dropout。我们来看conv2d_1
-dense_128
,但是使用不同的 dropout。
至于损失函数:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00195.jpeg
我们可以看到,当丢弃率非常低时,比如 0 或 0.25,损失值被最小化了。这是因为,如果你真的想学习训练集,就不要拒绝更新权重。相反,要一直更新所有权重。在相同的实验中,通过观察深蓝色的线,我们可以看到,经过两次训练后,模型确实出现了过拟合,因为验证损失(即未见过的样本)开始变得更差了。所以,过拟合从这里开始。很明显,丢弃率能减少过拟合。看看 0.75 的丢弃率,验证损失在此时变得越来越低,这意味着损失越来越小。
然而,这并没有使其成为最准确的模型,因为我们可以看到,无论是训练集还是验证集,准确率并不一定是最好的。
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00196.jpeg
实际上,大约 0.5 的丢弃率对于验证集来说效果相当好。现在,让我们确保其他层也符合这个规律。再次地,当没有丢弃率(0.0)时,我们得到最低的训练损失,但最高的验证损失。同样,0.75 的丢弃率会带来最低的验证损失,但不一定是最好的训练表现。
现在,让我们比较一下它们有多少个密集层。我们将保持丢弃率为 0.5,所以我们将使用conv2d_1
。因此,我们有一个卷积层,dense_*
,并且丢弃率为 0.50:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00197.jpeg
所以这里的选择是,密集层应该有 128、256、512、1024 还是 2048 个神经元?在前面的图中,我们可以看到一些明显的过拟合现象。几乎除了 128,其他的密集层数都会开始遭遇过拟合。因此,128 个神经元的密集层可能是最好的选择。现在,我们来比较一下一个卷积层和两个卷积层的表现:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00198.jpeg
实际上并没有太大的差别。为了验证,我们使用了两个卷积层,并获得了最低的损失值,这通常也意味着最高的准确率。这表明我们已经缩小了范围。这叫做模型选择,主要是找出最佳模型以及最佳超参数。我们已经将其缩小到二维卷积,两个卷积层,第一层密集层有 128 个神经元,并使用 50%的丢弃率。基于这些条件,接下来让我们在所有数据上重新训练,以确保我们拥有最优的训练模型:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00199.jpeg
我们得到了两个卷积层,做了 128 个全连接层和 0.5 的 dropout,在这个案例中,我们将我们所有的数据、整个数据集的训练和测试结果都结合在一起。现在,我们不能真正评估这个模型,因为我们丢失了测试集,所以我们接下来要做的就是使用这个模型来预测其他图像。实际上,我们将在拟合模型后保存它,并展示如何加载它。假如你要在另一个文件中加载它,你还需要知道那些标签的名称,因为我们所知道的只是 one-hot 编码。通过 one-hot 编码,我们可以得到整数,但那仍然不同于符号的实际名称。所以,我们必须保存LabelEncoder
中的类别,我们将使用一个numpy
文件来保存它。
让我们训练模型:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00200.jpeg
其实这些内容可以放在一个单独的文件中。你可以再次加载所有内容:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00201.jpeg
导入keras.models
,然后你可以使用load_model
功能。那里保存的模型文件实际上保存了结构和权重。恢复网络时,你只需要做这些。你可以再次打印出总结信息。对于LabelEncoder
,我们需要重新调用构造函数,并传入我们提前保存的类别。
现在,我们可以创建一个名为predict
的函数来接受图像。我们做一点预处理,将图像转换为数组,除以 255,然后进行预测。如果你有一整套图像,就不需要进行这种 reshape,但由于我们只有一张,我们可以将它放入一个只有一行的数组中。我们将从中获取预测结果,并使用LabelEncoder
将预测结果反转为实际的类别名称、符号名称,那么哪个是预测呢?它是 one-hot 编码,因此你可以找出最大数字的位置。这会处理所有神经元输出,即 369 个结果,找出最大置信度的数字,然后认为这是预测的结果。因此,one-hot 编码会告诉你这个特定的符号,然后我们可以打印它:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00202.jpeg
这是我们如何使用该函数的示例:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00203.jpeg
实际上,我们正在使用训练图像来进行此操作,而不是制作新的图像,但你明白了这个思路。你拿到一张图像,假设它是一个A
,而我对此的置信度是 87%。对于π的预测,我们的置信度是 58%,而对于 alpha 的预测,置信度是 88%。接下来,我们将看看之前使用过的鸟类物种示例,而不是使用人类创建的所有属性,我们将直接使用图像本身。
重新回顾使用图像的鸟类物种识别器
在本节中,我们将重新审视之前的鸟类物种识别器。这一次,我们将更新它以使用神经网络和深度学习。你还记得鸟类数据集吗?它包含了 200 种不同的鸟类,共有 12000 张图像。与上次不同,我们这次不会使用人工标注的属性,而是直接使用原始图像,且不进行任何预处理。在第一次尝试中,我们将构建一个自定义的卷积神经网络,就像我们为数学符号分类器所做的那样。
让我们来看一下代码。我们将从典型的导入开始:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00204.jpeg
我们会创建一些便捷变量,包括图像的行数和列数,宽度和高度,以及通道数(RGB),尽管每张鸟类图像将是相同的。尽管它们的大小不一定相同,我们将把它们调整为这个尺寸,以便它们保持一致:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00205.jpeg
现在,这个项目介绍了一个 Keras 中的有趣功能,叫做图像数据生成器:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00206.jpeg
数据生成器可以从现有的训练集生成新的图像,这些新图像可能会有各种差异;例如,它们可以旋转,可以水平或垂直翻转,等等。然后,我们就可以生成比最初更多的示例。当你的训练示例较少时,这是一个很好的方法。在我们的案例中,我们大约有 6000 个训练集。这个数量在深度学习中相对较小,因此我们希望能够生成更多;数据生成器会持续生成,直到我们要求它停止。对于训练图像,我们还希望生成水平翻转的版本。我们不想进行垂直翻转,因为我不期望任何鸟类图像会被倒置。我们还希望支持最多 45 度的旋转,并且希望将所有像素值重新缩放为除以 255。实际上,ImageDataGenerator
只是调用构造函数,因此实际上还没有发生任何事情。接下来你需要做的是使用flow_from_directory
,这样你的图像就可以组织到目录或子目录中。
我们有一个train
目录,里面会有每个鸟类类别的文件夹。所以,在 train 中有 200 个不同的文件夹,而每个文件夹里包含了该鸟类的图像。我们希望所有图像都被调整为 256 x 256 的尺寸,并且我们可以指定不使用二进制,而是使用类别类,这意味着我们将有许多不同的类别(在这个例子中是 200 个)。我们也会使用数据生成器来处理测试集,因为flow_from_directory
是一个非常方便的函数。不过,我们不希望做任何翻转或旋转。我们只希望直接使用测试集,以便能够与其他人的结果进行比较。flow_from_directory
的另一个非常方便的特点是,它会自动生成一个包含图像数据的numpy
矩阵,同时也会提供类别值的独热编码。
所以,之前需要几个步骤的事情,现在可以一次完成。
现在,我其实不需要进行重置,但由于这些技术上是迭代器,如果你一直在修正模型并尝试重新训练,那么你可能想要重置一下,这样你可以确保每次生成相同顺序的图像。无论如何,它是一个迭代器,所以你可以调用next
、reset
等:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00207.jpeg
现在,我们将构建一个顺序模型,这是一个卷积模型。我们有一个 3 x 3 的卷积核,数量为 64。我们还使用了一个relu
激活函数和另一个由relu
激活函数构建的卷积层,接着可以进行最大池化。通过实验,我发现这种结构效果相对较好:3 x 3 之后是 3 x 3,每层有 64 个卷积核。通过使用比较剧烈的 4 x 4 最大池化,我们重复这个过程,然后进行展平。我们设置 50%的 Dropout 以减少过拟合,接着是一个 400 个神经元的全连接层,再加一个 Dropout,最后输出层有 200 个神经元,因为有 200 个不同的类别,并且由于是独热编码,我们希望使用 softmax 激活函数,这样只有那 200 个类别中一个类别的值会是最大的。同时,我们也要确保它们的总和为 1.0。
这是模型的概述。最终,我们有大约 500 万个参数:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00208.jpeghttps://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00209.jpeg
我做过的不同变体,比如说 100 百万个参数的情况,效果更差,因为参数实在是太多了。要么是参数太多,这意味着训练它学习任何东西都非常困难,因为显然所有参数一开始都是随机的,所以很难让这些参数趋向正确的值,要么是参数太少,这样也学不到任何东西。你需要找到一个平衡点,我认为 500 万差不多是在这个平衡点附近。
现在,如果你使用生成器,你就不需要提前准备好所有训练数据;它会随着训练的进行动态生成这些图像:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00210.jpeg
这实际上相当节省内存。你不需要提前加载整个数据集。它只会按需加载,但你必须调用fit_generator
,而不是仅仅使用 fit。你提供的不是训练输入和输出,而是生成器。生成器知道如何生成图像矩阵,也知道如何生成一热编码(one-hot encoding)。所以,当你有图像数据时,这非常方便。还有其他类型的生成器,可以查阅 Keras 文档了解更多。steps_per_epoch
显示每个训练周期需要生成多少张图片,或者说需要生成多少批次。默认情况下,生成器每次生成 32 张图片。关于训练周期数量,如果你想在 TensorBoard 上查看一些统计信息,可以设置一个回调函数,并设置 verbose 为 2,这样我们就可以看到一些输出了。
总共有 10 个训练周期(epochs):
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00211.jpeg
我们可以看到,训练准确度是在训练图片上进行的。它并不是很准确,无法预测测试集上的准确度,所以我们需要分开处理。测试图片也在生成器中。你不只是进行评估——你需要使用evaluate_generator
,然后你会说,你想评估多少张图片? 我们只评估 1,000 张,结果得到了 22%的准确率。
这还算不错。随机猜测的准确率大约是 0.5%,所以 22%是相当不错的,而这仅仅是从一个从零开始的手工制作的模型,学习了所有的鸟类图片后得到的结果。我之所以这么说,是因为接下来我们要做的就是扩展一个预训练模型,以获得更高的准确度提升。
这个模型是手工构建的,但要是扩展像Inceptionv3
这样的模型会更好,如图所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00212.jpeg
这个模型相当深,它有很多卷积层,和大多数 CNN 一样,它以一个全连接层结束,或者可能是多个全连接层。Inceptionv3
模型是为 ImageNet 设计的。嗯,这是一个数据集,并且它有一些相关的竞赛,里面有数百万张图片,包含 1,000 个不同类别,比如昆虫、房屋、汽车等等。Inceptionv3
模型是最先进的,或者至少曾经是。它是 ImageNet 的竞赛模型,用来与其他数据库竞争。我们将使用这个网络的大部分部分,一直到全连接层。我们不需要最后的全连接或密集层,因为这些是为 ImageNet 设计的。具体来说,ImageNet 有 1,000 个输出,而这对我们来说并不合适。我们不需要识别 ImageNet 中的图像,但我们确实需要识别我们的鸟类图像,而且只有 200 个不同的类别。
所以,我们只是去掉前面的部分,并用我们自己的全连接层或多个层替换它。我们将使用它学习到的所有卷积和卷积核,这些都是基于 ImageNet 图像学习得到的。接下来,我们来看看代码。为了实现这一点,从 Keras 的 applications 模块导入Inceptionv3
。Keras 还提供了其他可以选择的模型:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00213.jpeg
我们将像之前一样使用数据生成器。
这时开始有所不同:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00214.jpeg
首先,加载InceptionV3
模型,并使用 ImageNet 的权重。include_top=False
意味着去掉顶部的密集全连接层。这就是所谓的顶部,它最终输出 1000 个不同的结果。我们不需要这个,我们只需要卷积层。这将被称为base_model
。调用x
,这是基础模型的输出,添加一个GlobalAveragePooling
,它会计算整个卷积的平均值,然后加入一些全连接层,包含 1024 个神经元和另一个 200 个神经元的层。当然,200 个神经元是因为我们有 200 个不同的鸟类物种,1024 个神经元则是为了学习卷积如何匹配鸟类物种,然后生成包含这些层的模型。模型的输入是Inceptionv3
的输入,输出是out_layer = Dense(200, activation='softmax')(x)
。
此时,你可以调用常规的模型函数,比如compile
,但在编译之前,我们希望将所有基础模型的层和所有卷积层标记为不可训练。
我们将执行两个步骤。在我们添加了新的两层全连接层,即 1024 和 200 个神经元时,这些层的权重是随机的,所以它们现在几乎没有用。卷积层已经在 ImageNet 上进行了学习,所以它们已经很好了。我们不希望在训练我们的鸟类图片时更改这些卷积层,直到我们将那对新的全连接层排列正确。所以,我们首先将 Inception 模型中的那些层标记为不可训练;只保留那些卷积层的权重不变——我们只训练我们新添加的那两层:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00215.jpeg
接下来的操作是对生成器进行拟合,就像之前一样。
我们将开始训练 100 个 epoch:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00216.jpeg
然后我们将进行评估:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00217.jpeg
现在,我们的准确率达到了 44%。通过使用 Inception v3 的权重和结构,或者 ImageNet,但将顶部的两层替换为我们自己的全连接网络,我们比之前自定义卷积神经网络的结果提高了 20%。但我们可以做得更好。
我们可以使用我们刚刚得到的模型,这样模型就能训练顶部的两层并将所有层标记为可训练:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00218.jpeg
所以,现在顶层的两层已经调整成合理的形式,准确率为 44%,我们将让整个网络更新我们所有的鸟类图像。我们将非常慢地使用随机梯度下降法(SGD),采用非常低的学习率和高动量。经过 100 轮训练后,我们的准确率达到了 64%:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00219.jpeg
所以,我们基本上每次都提升了 20%。使用自定义的 CNN,我们从头开始就获得了 22%的准确率。当然,这个网络没有Inception
模型那么大,但它展示了从头开始时会发生什么。然后,我们从Inception
开始,使用所有的卷积核,但在其上加了我们自己的两个随机层,随机权重,训练这些权重但不改变卷积核,最终得到了 44%的准确率。最后,我们更新了所有的权重、卷积核和顶层,并获得了 64%的准确率。
所以,这比随机猜测要好得多,随机猜测的准确率是 0.5%,而且每次我们改进模型时,准确率都有所提高。你可以保存结果,然后将其加载到一个单独的文件中,也许通过加载模型来实现:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00220.jpeg
如果你想将鸟的名字打印给用户,你还需要知道类别名称:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00221.jpeg
在这种情况下,我们可以按排序的形式列出子目录,因为这将与独热编码匹配,我们可以定义一个名为predict
的函数,给它一个包含图像的文件名,它会加载该图像。确保它会调整图像大小并转换为数组,将其除以 255,然后运行预测器。所有这些之前都通过图像生成器为我们做过了:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00222.jpeg
但是现在,由于我们是一次处理一个,我们将改为手动操作。运行预测,找出最佳得分和位置,获取类别名称并打印出来,再加上置信度。这里有几个例子是我在互联网上找到的鸟类:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/py-ai-proj-bg/img/00223.jpeg
我不能保证这些图片不是训练集的一部分,但谁知道呢。在蜂鸟的情况下,它预测正确了。房屋鹪鹩也被正确预测了。然而,大雁并没有被正确预测。这是一个允许用户输入文件名的例子。如果你有自己的图片,且这些图片与摄影类型的图像相似,你应该考虑使用像InceptionV3
这样的预训练模型,以获得更大的准确率提升。
概要
在这一章中,我们讨论了深度学习和卷积神经网络(CNN)。我们通过两个项目练习了卷积神经网络和深度学习。首先,我们构建了一个可以读取手写数学符号的系统,然后重新审视了鸟类物种识别器,并将实现方式更改为使用深度卷积神经网络,这样的准确性显著提高。这也标志着 Python 人工智能初学者项目的结束。