dl-nlp-merge-0

53 阅读1小时+

自然语言处理的深度学习(一)

原文:annas-archive.org/md5/79edb699aa9642b682a3d7113b128c41

译者:飞龙

协议:CC BY-NC-SA 4.0

第一章:前言

关于

本节简要介绍了作者、本书的覆盖范围、入门所需的技术技能,以及完成书中所有活动和练习所需的硬件和软件要求。

关于本书

将深度学习方法应用于各种 NLP 任务,可以使你的计算算法在速度和准确性方面达到全新的水平。《自然语言处理中的深度学习》一书首先介绍了自然语言处理领域的基本构建块。接着,本书介绍了你可以用最先进的神经网络模型解决的各种问题。深入探讨不同的神经网络架构及其具体应用领域,将帮助你理解如何选择最适合你需求的模型。在你逐步深入本书的过程中,你将学习卷积神经网络、循环神经网络和递归神经网络,同时还将涉及长短期记忆网络(LSTM)。在后续章节中,你将能够使用 NLP 技术(如注意力模型和束搜索)开发应用程序。

在本书结束时,你不仅将掌握自然语言处理的基本知识,还能选择最佳的文本预处理和神经网络模型,解决一系列 NLP 问题。

关于作者

Karthiek Reddy Bokka 是一位语音和音频机器学习工程师,毕业于南加州大学,目前在波特兰的 Bi-amp Systems 工作。他的兴趣包括深度学习、数字信号和音频处理、自然语言处理和计算机视觉。他有设计、构建和部署人工智能应用程序的经验,致力于利用人工智能解决现实世界中与各种实用数据相关的问题,包括图像、语音、音乐、非结构化原始数据等。

Shubhangi Hora 是一位 Python 开发者、人工智能爱好者和作家。她拥有计算机科学和心理学的背景,尤其对与心理健康相关的 AI 感兴趣。她目前生活在印度浦那,热衷于通过机器学习和深度学习推动自然语言处理的发展。除此之外,她还喜欢表演艺术,并是一名受过训练的音乐家。

Tanuj Jain 是一位数据科学家,目前在一家总部位于德国的公司工作。他一直在开发深度学习模型,并将其投入到商业生产中。自然语言处理是他特别感兴趣的领域,他已将自己的专业知识应用于分类和情感评分任务。他拥有电气工程硕士学位,专注于统计模式识别。

Monicah Wambugu 是一家金融科技公司的首席数据科学家,该公司通过利用数据、机器学习和分析技术执行替代性信用评分,提供小额贷款。她是加州大学伯克利分校信息学院信息管理与系统硕士项目的研究生。Monicah 特别感兴趣的是数据科学和机器学习如何被用来设计能够响应目标受众行为和社会经济需求的产品和应用。

描述

本书将从自然语言处理领域的基本构建模块开始。它将介绍可以通过最先进的神经网络模型解决的问题。它将深入讨论文本处理任务中所需的必要预处理。本书将涵盖 NLP 领域的一些热门话题,包括卷积神经网络、递归神经网络和长短期记忆网络。读者将理解文本预处理和超参数调优的重要性。

学习目标

  • 学习自然语言处理的基本原理。

  • 理解深度学习问题的各种预处理技术。

  • 使用 word2vec 和 Glove 开发文本的向量表示。

  • 理解命名实体识别。

  • 使用机器学习进行词性标注。

  • 训练和部署可扩展的模型。

  • 理解几种神经网络架构。

读者对象

有志成为数据科学家和工程师的人,希望在自然语言处理领域入门深度学习。

读者将从自然语言处理概念的基础开始,逐渐深入理解神经网络的概念及其在文本处理问题中的应用。他们将学习不同的神经网络架构及其应用领域。要求具备扎实的 Python 和线性代数知识。

方法

面向自然语言处理的深度学习将从自然语言处理的基本概念开始。一旦基本概念介绍完毕,听众将逐步了解 NLP 技术在现实世界中可应用的领域和问题。一旦用户理解了问题领域,解决方案的开发方法将会被介绍。作为基于解决方案的方法的一部分,讨论神经网络的基本构建模块。最终,将详细阐述各种神经网络的现代架构及其对应的应用领域,并给出实例。

硬件要求

为了获得最佳体验,我们推荐以下硬件配置:

  • 处理器:Intel Core i5 或同等处理器

  • 内存:4 GB RAM

  • 存储:5 GB 可用空间

软件要求

我们还建议您提前安装以下软件:

规范

文本中的代码字、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄如下所示:

代码块设置如下:

from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline

新术语和重要单词以粗体显示。屏幕上看到的词语,例如菜单或对话框中的内容,文本中会像这样显示:“接下来,点击生成文件,然后点击立即下载,并将下载的文件命名为model.h5。”

安装与设置

每一段伟大的旅程都始于谦逊的第一步,我们即将展开的数据处理之旅也不例外。在我们用数据做出令人惊叹的事情之前,我们需要准备好最具生产力的环境。在这篇简短的说明中,我们将展示如何做到这一点。

在 Windows 上安装 Python

  1. 在官方安装页面www.python.org/downloads/windows/找到所需的 Python 版本。

  2. 请确保安装正确的“-bit”版本,取决于您的计算机系统,是 32 位还是 64 位。您可以在操作系统的系统属性窗口中找到此信息。

    下载安装程序后,只需双击文件并按照屏幕上的用户友好提示进行操作。

在 Linux 上安装 Python

在 Linux 上安装 Python,请执行以下操作:

  1. 打开命令提示符,并通过运行 python3 --version 来确认是否已安装 Python 3。

  2. 要安装 Python 3,请运行以下命令:

    sudo apt-get update
    sudo apt-get install python3.6
    
  3. 如果遇到问题,网上有许多资源可以帮助您排查并解决问题。

在 macOS X 上安装 Python

要在 macOS X 上安装 Python,请执行以下操作:

  1. 通过按住命令和空格键(CMD + Space),在打开的搜索框中输入 terminal,按下回车键打开终端。

  2. 通过运行命令 xcode-select --install,在命令行中安装 Xcode。

  3. 安装 Python 3 的最简单方法是使用 homebrew,可以通过命令行运行 ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 安装。

  4. 将 homebrew 添加到你的 PATH 环境变量中。通过运行sudo nano ~/.profile打开命令行中的配置文件,并在底部插入export PATH="/usr/local/opt/python/libexec/bin:$PATH"

  5. 最后一步是安装 Python。在命令行中运行brew install python

  6. 请注意,如果你安装了 Anaconda,最新版本的 Python 将会自动安装。

安装 Keras

安装 Keras,请执行以下步骤:

  1. 由于Keras需要另一个深度学习框架作为后端,因此你需要先下载另一个框架,推荐使用TensorFlow

    要为你的平台安装TensorFlow,请点击www.tensorflow.org/install/

  2. 一旦后端安装完成,你可以运行sudo pip install keras进行安装。

    另外,你也可以从 GitHub 源代码安装,使用以下命令克隆Keras

    git clone https://github.com/keras-team/keras.git
    
  3. 安装cd keras sudo python setup.py install

    现在需要配置后端。有关更多信息,请参考以下链接:(keras.io/backend/)

额外资源

本书的代码包也托管在 GitHub 上,网址为:github.com/TrainingByPackt/Deep-Learning-for-Natural-Language-Processing。我们还有来自丰富书籍和视频目录的其他代码包,地址是github.com/PacktPublishing/。快来看看吧!

你可以从这里下载本书的图形包:

www.packtpub.com/sites/default/files/downloads/9781838558024_ColorImages.pdf

第二章:第一章

自然语言处理简介

学习目标

本章结束时,你将能够:

  • 描述自然语言处理及其应用

  • 解释不同的文本预处理技术

  • 对文本语料进行文本预处理

  • 解释 Word2Vec 和 GloVe 词向量的工作原理

  • 使用 Word2Vec 和 GloVe 生成词向量

  • 使用 NLTK、Gensim 和 Glove-Python 库进行文本预处理和生成词向量

本章旨在让你掌握自然语言处理的基础知识,并体验在深度学习中使用的各种文本预处理技术。

介绍

欢迎来到自然语言处理的深度学习之旅。本书将引导你理解并优化深度学习技术,用于自然语言处理,推动通用人工智能的现实化。你将深入了解自然语言处理的概念——它的应用和实现——并学习深度神经网络的工作方式,同时利用它们使机器理解自然语言。

自然语言处理基础

为了理解什么是自然语言处理,我们将这个术语分解成两个部分:

  • 自然语言是一种书面和口头沟通形式,它是自然和有机发展的。

  • 处理是指用计算机分析和理解输入数据。

图 1.1:自然语言处理

图 1.1:自然语言处理

因此,自然语言处理是基于机器的人类沟通处理。它旨在教会机器如何处理和理解人类的语言,从而建立起人与机器之间的轻松沟通渠道。

例如,我们手机和智能音响中的个人语音助手,如 Alexa 和 Siri,就是自然语言处理的产物。它们的设计使得它们不仅能理解我们对它们说的话,还能根据我们的指示做出反应并提供反馈。自然语言处理算法帮助这些技术与人类进行交流。

在自然语言处理的上述定义中,需要考虑的关键点是沟通必须发生在人类的自然语言中。几十年来,我们通过编写程序来与机器沟通,执行特定的任务。然而,这些程序使用的语言并非自然语言,因为它们不是口语交流形式,也没有自然或有机发展。这些语言,如 Java、Python、C 和 C++,是为机器设计的,且始终以“机器能够理解和轻松处理什么”为出发点。

虽然 Python 是一种更易于使用的语言,因此对于人类来说更容易学习并编写代码,但基本的观点保持不变——为了与机器沟通,人类必须学习一种机器能够理解的语言。

图 1.2:自然语言处理的维恩图

图 1.2:自然语言处理的维恩图

自然语言处理的目的正好与此相反。与其让人类适应机器的方式,学习如何与机器有效沟通,不如让机器适应人类并学习人类的沟通方式。这更加合理,因为技术的目的是让我们的生活更轻松。

为了通过一个例子来说明这一点,你第一次编写的程序可能是一段要求计算机打印“hello world”的代码。这是你在遵循机器的规则,并要求它执行你所理解的语言中的任务。当你对语音助手发出命令说“hello world”,并且它返回“hello world”时,这是自然语言处理的应用示例,因为你正在使用自然语言(在此情况下是英语)与计算机进行交流。计算机则遵循你的交流方式,理解你说的话,处理你要求它执行的任务,然后执行该任务。

自然语言处理的重要性

下图展示了人工智能领域的各个部分:

图 1.3:人工智能及其部分子领域

图 1.3:人工智能及其部分子领域

自然语言处理与机器学习和深度学习一起,是人工智能的一个子领域,并且由于它处理的是自然语言,实际上它处于人工智能和语言学的交汇点。

如前所述,自然语言处理使得机器能够理解人类的语言,从而在两者之间建立一个高效的沟通渠道。然而,自然语言处理之所以必要,还有另一个原因,那就是,像机器一样,机器学习和深度学习模型在处理数值数据时效果最佳。数值数据对于人类来说很难自然产生;想象一下我们用数字而不是单词交流。因此,自然语言处理处理文本数据并将其转换为数值数据,使得机器学习和深度学习模型能够在其上进行训练。因此,它的存在是为了弥合人类与机器之间的沟通鸿沟,将人类的口语和书面语言转化为机器能够理解的数据。多亏了自然语言处理,机器能够理解、回答基于数据的问题、使用数据解决问题,并用自然语言进行沟通等。

自然语言处理的能力

自然语言处理在现实生活中有许多有益的应用,这些应用属于自然语言处理的三大主要能力:

  • 语音识别

    机器能够识别口语形式的自然语言并将其转化为文本形式。一个例子是智能手机上的语音输入——你可以启用语音输入,向手机说话,手机会将你说的内容转化为文本。

  • 自然语言理解

    机器能够理解自然语言的书面和口语形式。如果给出命令,机器能够理解并执行。一个例子是对着 iPhone 上的 Siri 说“嘿,Siri,打电话回家”,Siri 会自动为你拨打“家”的电话。

  • 自然语言生成

    机器能够自主生成自然语言。一个例子是对 iPhone 上的 Siri 说“现在几点了?”,Siri 会回答时间——“现在是下午 2:08”。

这三种能力被用来完成和自动化许多任务。让我们来看看自然语言处理为哪些方面做出了贡献,以及如何做到的。

文本数据被称为语料(复数),单个语料称为语料库。

自然语言处理的应用

下图展示了自然语言处理的一般应用领域:

图 1.4:自然语言处理的应用领域

图 1.4:自然语言处理的应用领域
  • 自动文本摘要

    这涉及处理语料以提供总结。

  • 翻译

    这包括翻译工具,能够将文本从一种语言翻译成另一种语言,例如谷歌翻译。

  • 情感分析

    这也被称为情感人工智能或意见挖掘,是从书面和口头的语料中识别、提取和量化情感与情绪状态的过程。情感分析工具用于处理如客户评论和社交媒体帖子等内容,旨在理解人们对特定事物(如新餐厅的食品质量)的情绪反应和意见。

  • 信息提取

    这是从语料中识别和提取重要术语的过程,称为实体。命名实体识别属于这一类别,这一过程将在下一章中进行解释。

  • 关系提取

    关系抽取涉及从语料库中提取语义关系。语义关系存在于两个或更多实体(如人、组织和事物)之间,并且属于多种语义类别中的一种。例如,如果一个关系抽取工具收到一段关于 Sundar Pichai 及其作为谷歌首席执行官的描述,该工具能够输出“Sundar Pichai 为谷歌工作”,其中 Sundar Pichai 和谷歌是两个实体,而“为……工作”则是定义它们关系的语义类别。

  • 聊天机器人

    聊天机器人是一种人工智能形式,旨在通过语音和文本与人类对话。它们大多数模仿人类,使你感觉像是在与另一个人交谈。聊天机器人在健康行业中被用于帮助那些遭受抑郁和焦虑的人。

  • 社交媒体分析

    像 Twitter 和 Facebook 这样的社交媒体应用有话题标签和趋势,这些话题和趋势通过自然语言处理技术被跟踪和监控,以便了解全球范围内正在讨论的内容。此外,自然语言处理还协助内容审查过程,通过过滤负面、冒犯性和不当的评论和帖子,帮助维护社交平台的健康环境。

  • 个人语音助手

    Siri、Alexa、Google Assistant 和 Cortana 都是个人语音助手,利用自然语言处理技术来理解并回应我们说的话。

  • 语法检查

    语法检查软件会自动检查和修正你的语法、标点符号和打字错误。

文本预处理

在回答理解性阅读题时,问题是针对文章的不同部分的,因此,某些单词和句子对你来说很重要,而其他的则无关紧要。技巧在于从问题中识别关键词,并将其与文章进行匹配,从而找到正确的答案。

文本预处理的工作方式类似——机器不需要语料库中的无关部分,它只需要执行任务所需的重要单词和短语。因此,文本预处理技术涉及为语料库准备适当的分析,并为机器学习和深度学习模型做好准备。文本预处理基本上是告诉机器它需要考虑什么,以及可以忽略什么。

每个语料库根据需要执行的任务不同,要求采用不同的文本预处理技术,一旦你掌握了不同的预处理技术,你就会理解在何时、为何使用特定的技术。技术解释的顺序通常也是它们执行的顺序。

在接下来的练习中,我们将使用NLTK Python 库,但在进行活动时也可以自由使用其他库。NLTK代表自然语言工具包,它是最简单且最流行的 Python 自然语言处理库之一,这也是我们使用它来理解自然语言处理基本概念的原因。

注意

如需了解更多关于 NLTK 的信息,请访问 www.nltk.org/。

文本预处理技术

以下是自然语言处理中的最流行的文本预处理技术:

  • 小写/大写

  • 噪声去除

  • 文本标准化

  • 词干提取

  • 词形还原

  • 分词

  • 去除停用词

让我们逐一查看每种技术。

小写/大写

这是最简单且最有效的预处理技术之一,但人们常常忘记使用它。它可以将所有现有的大写字母转换为小写字母,使整个语料库都是小写的,或者将语料库中所有的小写字母转换为大写字母,使整个语料库都是大写的。

这种方法尤其适用于当语料库的大小不太大且任务涉及识别由于字符大小写不同而可能被识别为不同的术语或输出时,因为机器本身会将大写字母和小写字母视为独立的实体——'A'与'a'是不同的。输入中的这种大小写变体可能导致输出不正确或完全没有输出。

一个例子是,一个语料库中包含了'India'和'india'。如果没有进行小写转换,机器会将它们识别为两个不同的术语,而实际上它们只是同一个单词的不同形式,表示的是同一个国家。经过小写转换后,语料库中只会存在一个"India"的实例,即'india',这简化了查找语料库中所有提到印度的地方的任务。

注意

所有练习和活动将主要在 Jupyter Notebook 上开发。你需要在系统上安装 Python 3.6 和 NLTK。

练习 1 – 6 可以在同一个 Jupyter 笔记本中完成。

练习 1:对句子进行小写转换

在本次练习中,我们将输入一个包含大写和小写字母的句子,并将其全部转换为小写字母。以下步骤将帮助你完成该解决方案:

  1. 打开cmd或根据你的操作系统打开其他终端。

  2. 导航到所需路径并使用以下命令启动Jupyter笔记本:

    jupyter notebook
    
  3. 将输入句子存储在's = "The cities I like most in India are Mumbai, Bangalore, Dharamsala and Allahabad."

  4. 应用lower()函数将大写字母转换为小写字母,然后打印出新的字符串,如下所示:

    s = s.lower()
    print(s)
    

    预期输出:

    图 1.5:包含混合大小写的句子的小写处理输出
  5. 创建一个包含大写字符的单词数组,如下所示:

    words = ['indiA', 'India', 'india', 'iNDia']
    
  6. 使用列表推导,对words数组中的每个元素应用lower()函数,然后打印新数组,如下所示:

    words = [word.lower() for word in words]
    print(words)
    

    预期输出:

图 1.6:带混合大小写单词的小写化输出

图 1.6:带混合大小写单词的小写化输出

噪声移除

噪声是一个非常通用的术语,在不同的语料库和任务中可能意味着不同的东西。对于一个任务来说被视为噪声的内容,可能在另一个任务中被认为是重要的,因此这是一个非常领域特定的预处理技术。例如,在分析推文时,标签可能对识别趋势和理解全球讨论的内容非常重要,但在分析新闻文章时,标签可能并不重要,因此在后者的情况下,标签就被视为噪声。

噪声不仅仅包括单词,还可以包括符号、标点符号、HTML 标记(<,>,*, ?,.)、数字、空格、停用词、特定术语、特定的正则表达式、非 ASCII 字符(\W|\d+)以及解析术语。

移除噪声至关重要,这样只有语料库中重要的部分才会被输入到模型中,从而确保准确的结果。它还通过将单词转化为根形态或标准形式来帮助分析。考虑以下示例:

图 1.7:噪声移除输出

图 1.7:噪声移除输出

删除所有符号和标点符号后,所有“sleepy”的实例都对应于单一的单词形式,从而提高了语料库的预测和分析效率。

练习 2:移除单词中的噪声

在这个练习中,我们将输入一个包含噪声的单词数组(如标点符号和 HTML 标记),并将这些单词转换为清洁、无噪声的形式。为此,我们需要使用 Python 的正则表达式库。该库提供了多个函数,允许我们筛选输入数据并移除不必要的部分,这正是噪声移除过程的目标。

注意

若要了解更多关于‘re’的信息,请点击 docs.python.org/3/library/r…

  1. 在相同的Jupyter笔记本中,导入正则表达式库,如下所示:

    import re
    
  2. 创建一个名为'clean_words'的函数,该函数包含移除不同类型噪声的方法,如下所示:

    def clean_words(text):
    
      #remove html markup
      text = re.sub("(<.*?>)","",text)
      #remove non-ascii and digits
      text=re.sub("(\W|\d+)"," ",text)
      #remove whitespace
      text=text.strip()
      return text 
    
  3. 创建一个包含噪声的原始单词数组,如下所示:

    raw = ['..sleepy', 'sleepy!!', '#sleepy', '>>>>>sleepy>>>>', '<a>sleepy</a>']
    
  4. 对原始数组中的单词应用clean_words()函数,然后打印清理后的单词数组,如下所示:

    clean = [clean_words(r) for r in raw]
    print(clean)
    

    预期输出:

图 1.8:噪声移除输出

文本归一化

这是将原始语料库转换为规范和标准形式的过程,基本上是确保文本输入在分析、处理和操作之前保持一致。

文本标准化的示例包括将缩写映射到其完整形式,将同一单词的不同拼写转换为单一拼写形式,等等。

以下是一些不正确拼写和缩写的标准形式示例:

图 1.9:不正确拼写的标准形式

图 1.9:不正确拼写的标准形式

图 1.10:缩写的标准形式

图 1.10:缩写的标准形式

由于标准化的方法非常依赖于语料库和具体任务,因此没有统一的标准方式。最常见的做法是使用字典映射法,即手动创建一个字典,将一个单词的各种形式映射到该单词的标准形式,然后将这些单词替换为该单词的标准形式。

词干提取

词干提取是在语料库上进行的,以将单词简化为它们的词干或词根形式。之所以说“词干或词根形式”,是因为词干提取的过程并不总是将单词还原为词根形式,有时只是简化为它的标准形式。

经历词干提取的单词称为屈折词。这些单词与词根形式不同,表示某种属性,例如数目或性别。例如,“journalists”是“journalist”的复数形式。因此,词干提取会去掉“s”,将“journalists”还原为它的词根形式:

图 1.11:词干提取的输出

图 1.11:词干提取的输出

词干提取在构建搜索应用时非常有用,因为在搜索特定内容时,您可能还希望找到拼写不同的该内容的实例。例如,如果您在搜索本书中的练习,您可能还希望搜索到“Exercise”。

然而,词干提取并不总是提供所需的词干,因为它是通过去掉单词末尾的部分来工作的。词干提取器有可能将“troubling”还原为“troubl”而不是“trouble”,这对解决问题没有太大帮助,因此词干提取并不是一种常用的方法。当使用时,Porter 的词干提取算法是最常见的算法。

练习 3:对单词进行词干提取

在本练习中,我们将处理一个包含同一单词不同形式的输入数组,并将这些单词转换为它们的词干形式。

  1. 在同一个Jupyter笔记本中,导入nltkpandas库以及Porter Stemmer,如示例所示:

    import nltk
    import pandas as pd
    from nltk.stem import PorterStemmer as ps
    
  2. 创建一个stemmer实例,如下所示:

    stemmer = ps()
    
  3. 创建一个包含同一单词不同形式的数组,如下所示:

    words=['annoying', 'annoys', 'annoyed', 'annoy']
    
  4. 将词干提取器应用于words数组中的每个单词,并将它们存储在一个新数组中,如所示:

    stems =[stemmer.stem(word = word) for word in words]
    
  5. 将原始单词及其词干以数据框的形式打印,如下所示:

    sdf = pd.DataFrame({'raw word': words,'stem': stems})
    sdf
    

    预期输出:

图 1.12:词干提取的输出

图 1.12:词干提取的输出

词形还原

词形还原是一个类似于词干提取的过程——它的目的是将单词还原为其根本形式。与词干提取不同的是,词形还原不仅仅是通过剪切单词的结尾来获得根本形式,而是遵循一个过程,遵循规则,并且通常使用 WordNet 进行映射,将单词还原为其根本形式。(WordNet 是一个英语语言数据库,包含单词及其定义,此外还包含同义词和反义词。它被认为是词典和同义词词典的融合。)例如,词形还原能够将单词“better”转换为其根本形式“good”,因为“better”只是“good”的比较级形式。

尽管词形还原的这一特性使得它在与词干提取相比时更具吸引力和效率,但缺点是,由于词形还原遵循如此有序的程序,因此它比词干提取需要更多时间。因此,当你处理大规模语料库时,不推荐使用词形还原。

练习 4:对单词进行词形还原

在本练习中,我们将接收一个包含单词不同形式的输入数组,并将这些单词转换为其根本形式。

  1. 在与前一个练习相同的Jupyter笔记本中,导入WordNetLemmatizer并下载WordNet,如所示:

    from nltk.stem import WordNetLemmatizer as wnl
    nltk.download('wordnet')
    
  2. 创建一个lemmatizer实例,如下所示:

    lemmatizer = wnl()
    
  3. 创建一个包含同一单词不同形式的数组,如示范所示:

    words = ['troubling', 'troubled', 'troubles', 'trouble']
    
  4. words数组中的每个单词应用lemmatizer,并将结果存储在一个新数组中,如下所示。word参数向词形还原函数提供它应还原的单词。pos参数是你希望词形还原为的词性。'v'代表动词,因此词形还原器会将单词还原为其最接近的动词形式:

    # v denotes verb in "pos"
    lemmatized = [lemmatizer.lemmatize(word = word, pos = 'v') for word in words]
    
  5. 打印原始单词及其根本形式,结果以数据框形式展示,如所示:

    ldf = pd.DataFrame({'raw word': words,'lemmatized': lemmatized})
    ldf = ldf[['raw word','lemmatized']]
    ldf
    

    预期输出:

图 1.13:词形还原的输出

图 1.13:词形还原的输出

分词

分词是将语料库拆解为单个词元的过程。词元是最常用的单词——因此,这个过程将语料库拆解为单个单词——但也可以包括标点符号和空格等其他元素。

这一技术是最重要的技术之一,因为它是很多自然语言处理应用的前提,我们将在下一章学习这些应用,比如词性标注PoS)。这些算法将词元作为输入,不能使用字符串或段落文本作为输入。

可以通过分词将文本分解成单个单词或单个句子作为词元。让我们在接下来的练习中尝试这两种方法。

练习 5:词元化单词

在本练习中,我们将接收一个输入句子,并从中生成单个单词作为词元。

  1. 在同一个Jupyter笔记本中,导入nltk

    import nltk
    
  2. nltk导入word_tokenizepunkt,如所示:

    nltk.download('punkt')
    from nltk import word_tokenize
    
  3. 将单词存储在一个变量中,并对其应用 word_tokenize(),然后打印结果,如下所示:

    s = "hi! my name is john."
    tokens = word_tokenize(s)
    tokens
    

    预期输出:

图 1.14:单词分词的输出

图 1.14:单词分词的输出

如你所见,甚至标点符号也被分词并视为独立的标记。

现在让我们看看如何分词句子。

练习 6:句子分词

在本练习中,我们将从输入句子中生成单独的单词作为标记。

  1. 在同一个 Jupyter 笔记本中,按如下方式导入 sent_tokenize

    from nltk import sent_tokenize
    
  2. 将两个句子存储在一个变量中(我们之前的句子实际上是两个句子,所以可以使用同一个句子来查看单词分词和句子分词之间的区别),然后对其应用 sent_tokenize(),接着打印结果,如下所示:

    s = "hi! my name is shubhangi."
    tokens = sent_tokenize(s)
    tokens
    

    预期输出:

图 1.15:句子分词的输出

图 1.15:句子分词的输出

如你所见,两个句子已经形成了两个独立的标记。

其他技术

有多种方式可以进行文本预处理,包括使用各种 Python 库,如 BeautifulSoup 来剥离 HTML 标记。前面的练习是为了向你介绍一些技术。根据手头的任务,你可能只需要使用其中的一两个,或者全部使用它们,包括对它们所做的修改。例如,在噪声移除阶段,你可能觉得有必要移除像 'the'、'and'、'this' 和 'it' 这样的词。因此,你需要创建一个包含这些词的数组,并通过 for 循环将语料库中的词汇传递,只保留那些不在该数组中的词,去除噪声词。另一种方法将在本章后面介绍,且是在完成分词后执行的。

练习 7:移除停用词

在本练习中,我们将从输入句子中移除停用词。

  1. 打开一个 Jupyter 笔记本,并使用以下代码行下载 'stopwords':

    nltk.download('stopwords')
    
  2. 将一个句子存储在一个变量中,如下所示:

    s = "the weather is really hot and i want to go for a swim"
    
  3. 导入 stopwords 并创建一组英语停用词,如下所示:

    from nltk.corpus import stopwords
    stop_words = set(stopwords.words('english'))
    
  4. 使用 word_tokenize 对句子进行分词,然后将那些在 stop_words 中没有出现的标记存储在一个数组中。接着,打印该数组:

    tokens = word_tokenize(s)
    tokens = [word for word in tokens if not word in stop_words]
    print(tokens)
    

    预期输出:

图 1.16:移除停用词后的输出

图 1.16:移除停用词后的输出

此外,你可能需要将数字转换为其词语形式。这也是可以添加到噪声移除功能中的一种方法。此外,你可能需要使用缩写词库,它的作用是扩展文本中已有的缩写词。例如,缩写词库会将 'you're' 转换为 'you are',如果这对你的任务是必要的,那么建议安装并使用该库。

文本预处理技术超出了本章所讨论的范围,可以包括任务或语料库中任何所需的内容和技术。在某些情况下,一些词语可能很重要,而在其他情况下则不重要。

词嵌入

如本章前面的部分所提到的,自然语言处理为机器学习和深度学习模型准备文本数据。当提供数值数据作为输入时,模型表现最为高效,因此自然语言处理的一个关键角色是将预处理后的文本数据转化为数值数据,这是文本数据的数值表示。

这就是词嵌入:它们是文本的数值表示,采用实值向量的形式。具有相似含义的词映射到相似的向量,因此具有相似的表示形式。这帮助机器学习不同单词的意义和上下文。由于词嵌入是映射到单个单词的向量,词嵌入只能在对语料库进行分词之后生成。

图 1.17:词嵌入示例

图 1.17:词嵌入示例

词嵌入包括用于创建学习到的数值表示的各种技术,是表示文档词汇最流行的方式。词嵌入的优点在于,它们能够捕捉上下文、语义和句法的相似性,以及一个单词与其他单词的关系,从而有效地训练机器理解自然语言。这就是词嵌入的主要目的——形成类似向量的簇,这些簇对应于具有相似意义的词。

使用词嵌入的原因是为了让机器像我们一样理解同义词。以在线餐厅评论为例——这些评论由形容词描述食物、环境和整体体验。它们要么是积极的,要么是消极的,理解哪些评论属于这两类中的哪一类非常重要。自动分类这些评论可以为餐厅提供快速的见解,了解需要改进的地方,人们喜欢餐厅的哪些方面,等等。

存在多种可以归类为积极的形容词,消极形容词也是如此。因此,机器不仅需要能够区分负面和正面,还需要学习和理解多个单词可以归属于同一类别,因为它们最终意味着相同的东西。这就是词嵌入发挥作用的地方。

以餐饮服务应用上的餐厅评论为例。以下两句话来自两条不同的餐厅评论:

  • 句子 A – 这里的食物很好。

  • 句子 B – 这里的食物很好。

机器需要能够理解这两条评论都是积极的,并且它们意味着类似的内容,尽管这两句话中的形容词不同。这是通过创建词嵌入来实现的,因为两个词“good”和“great”分别映射到两个不同但相似的实际值向量,因此可以将它们聚集在一起。

词嵌入的生成

我们已经了解了什么是词嵌入以及它们的重要性;现在我们需要理解它们是如何生成的。将词语转换为其实际值向量的过程被称为向量化,这是通过词嵌入技术完成的。市面上有许多词嵌入技术,但在本章中,我们将讨论两种主要技术——Word2Vec 和 GloVe。一旦词嵌入(向量)被创建,它们会结合成一个向量空间,这是一个代数模型,由遵循向量加法和标量乘法规则的向量组成。如果你不记得线性代数的内容,现在可能是快速复习的好时机。

Word2Vec

如前所述,Word2Vec 是一种词嵌入技术,用于从词语中生成向量——这从其名字上就能大致理解。

Word2Vec 是一个浅层神经网络——它只有两层——因此不算是深度学习模型。输入是一个文本语料库,机器使用它来生成向量作为输出。这些向量被称为输入语料库中单词的特征向量。它将语料库转换为深度神经网络能够理解的数值数据。

Word2Vec 的目标是理解两个或多个词语共同出现的概率,从而将具有相似意义的词语聚集在一起,形成向量空间中的一个簇。像其他机器学习或深度学习模型一样,Word2Vec 通过从过去的数据和单词的出现中学习,变得越来越高效。因此,如果提供足够的数据和上下文,它可以根据过去的出现和上下文准确地猜测一个词的含义,类似于我们如何理解语言。

例如,一旦我们听说并阅读过“boy”和“man”,“girl”和“woman”这些词,并理解它们的含义,我们就能建立起这些词之间的联系。同样,Word2Vec 也可以建立这种联系,并为这些词生成向量,这些向量彼此靠近,位于同一个簇中,以确保机器能够意识到这些词意味着相似的东西。

一旦 Word2Vec 接收到一个语料库,它会生成一个词汇表,其中每个词都有一个与之关联的向量,这被称为其神经词嵌入,简单来说,这个神经词嵌入就是用数字表示的词语。

Word2Vec 的工作原理

Word2Vec 训练一个词与其在输入语料库中邻近的词进行对比,有两种方法可以实现这一点:

  • 连续词袋模型(CBOW)

    该方法根据上下文预测当前单词。因此,它将单词的周围单词作为输入,输出该单词,并根据该单词是否确实是句子的一部分来选择该单词。

    例如,如果算法提供了“the food was”这些单词并需要预测后面的形容词,它最有可能输出“good”而不是“delightful”,因为“good”出现的次数更多,因此它学到“good”的出现概率高于“delightful”。CBOW 被认为比跳字法更快,并且在频繁出现的单词上准确性更高。

图 1.18: CBOW 算法

图 1.18: CBOW 算法
  • 跳字法

    该方法通过将一个单词作为输入,理解该单词的含义并将其与上下文关联,来预测周围的单词。例如,如果算法给定单词“delightful”,它需要理解该词的含义,并从过去的上下文中学习,预测出周围的单词是“the food was”的概率最大。跳字法通常认为在小型语料库中表现最好。

图 1.19: 跳字法算法

图 1.19: 跳字法算法

虽然这两种方法看起来是相反的方式,但它们本质上是根据局部(附近)单词的上下文来预测单词;它们使用一个上下文窗口来预测接下来会出现哪个单词。这个窗口是一个可配置的参数。

选择使用哪种算法的决定取决于手头的语料库。CBOW 基于概率工作,因此会选择在特定上下文中出现概率最高的单词。这意味着它通常只会预测常见和频繁出现的单词,因为这些单词的概率最高,而罕见和不常见的单词则永远不会由 CBOW 生成。另一方面,跳字法预测上下文,因此,当给定一个单词时,它会将其视为一个新的观察,而不是将其与具有相似意义的现有单词进行比较。因此,罕见单词不会被跳过或忽视。然而,这也意味着跳字法需要大量的训练数据才能高效地工作。因此,根据训练数据和语料库的不同,应该决定使用哪种算法。

本质上,这两种算法以及整个模型,都需要一个强度很高的学习阶段,在这个阶段它们会经过数千、数百万个单词的训练,以便更好地理解上下文和含义。基于此,它们能够为单词分配向量,从而帮助机器学习和预测自然语言。为了更好地理解 Word2Vec,让我们通过 Gensim 的 Word2Vec 模型做一个练习。

Gensim 是一个开源库,用于使用统计机器学习进行无监督主题建模和自然语言处理。Gensim 的 Word2Vec 算法接收由单独的单词(令牌)组成的句子序列作为输入。

此外,我们还可以使用min_count参数。它的作用是问你一个单词在语料库中应该出现多少次才对你有意义,然后在生成词嵌入时考虑这一点。在实际场景中,当处理数百万个单词时,只出现一次或两次的单词可能完全不重要,因此可以忽略它们。然而,现在我们仅在三句话上训练模型,每句只有 5 到 6 个单词。因此,min_count设置为 1,因为即使一个单词只出现一次,对我们来说它也是重要的。

练习 8:使用 Word2Vec 生成词嵌入

在本练习中,我们将使用 Gensim 的 Word2Vec 算法,在分词后生成词嵌入。

注意

你需要在系统上安装gensim才能进行以下练习。如果尚未安装,你可以使用以下命令进行安装:

pip install --upgrade gensim

如需更多信息,请点击 radimrehurek.com/gensim/mode…

以下步骤将帮助你完成该解答:

  1. 打开一个新的Jupyter笔记本。

  2. gensim导入 Word2Vec 模型,并从nltk导入word_tokenize,如所示:

    from gensim.models import Word2Vec as wtv
    from nltk import word_tokenize
    
  3. 将三个包含常见单词的字符串存入三个独立的变量中,然后对每个句子进行分词,并将所有令牌存储在一个数组中,如所示:

    s1 = "Ariana Grande is a singer"
    s2 = "She has been a singer for many years"
    s3 = "Ariana is a great singer"
    sentences = [word_tokenize(s1), word_tokenize(s2), word_tokenize(s3)]
    

    你可以打印句子数组来查看令牌。

  4. 如下所示,训练模型:

    model = wtv(sentences, min_count = 1)
    

    Word2Vec 的min_count默认值为 5\。

  5. 如所示,总结模型:

    print('this is the summary of the model: ')
    print(model)
    

    你的输出将会是如下所示:

    图 1.20:模型摘要的输出

    图 1.20:模型摘要的输出

    Vocab = 12 表示输入模型的句子中有 12 个不同的单词。

  6. 让我们通过总结模型来找出词汇表中存在哪些单词,如下所示:

    words = list(model.wv.vocab)
    print('this is the vocabulary for our corpus: ')
    print(words)
    

    你的输出将会是如下所示:

图 1.21:语料库词汇表的输出

图 1.21:语料库词汇表的输出

让我们来看一下单词‘singer’的向量(词嵌入):

print("the vector for the word singer: ")
print(model['singer'])

期望输出:

图 1.22:单词‘singer’的向量

图 1.22:单词‘singer’的向量

我们的 Word2Vec 模型已经在这三句话上进行了训练,因此它的词汇表仅包含这些句子中出现的单词。如果我们试图从 Word2Vec 模型中找到与某个特定输入单词相似的单词,由于词汇表非常小,我们不会得到任何实际有意义的单词。考虑以下例子:

#lookup top 6 similar words to great
w1 = ["great"]
model.wv.most_similar (positive=w1, topn=6)

“positive”指的是在输出中仅展示正向的向量值。

与‘great’最相似的六个单词是:

图 1.23:与“great”类似的词向量

图 1.23:与“great”类似的词向量

类似地,对于“singer”这个词,它可能是如下所示:

#lookup top 6 similar words to singer
w1 = ["singer"]
model.wv.most_similar (positive=w1, topn=6)

图 1.24:与“singer”类似的词向量

图 1.24:与“singer”类似的词向量

我们知道这些词与我们的输入词在意义上并不相似,这也体现在它们旁边的相关性值中。然而,它们会出现在这里,因为它们是我们词汇中唯一存在的词。

Gensim Word2Vec 模型的另一个重要参数是大小参数。它的默认值是 100,表示用于训练模型的神经网络层的大小。这对应于训练算法的自由度。较大的大小需要更多的数据,但也会提高准确性。

注意

有关 Gensim Word2Vec 模型的更多信息,请点击

rare-technologies.com/word2vec-tu….

GloVe

GloVe,"global vectors" 的缩写,是一种由斯坦福大学开发的词嵌入技术。它是一种无监督学习算法,基于 Word2Vec。虽然 Word2Vec 在生成词嵌入方面非常成功,但它的问题在于它有一个较小的窗口,通过这个窗口它集中关注局部词汇和局部上下文来预测词汇。这意味着它无法从全局词频中学习,也就是说,无法从整个语料库中学习。正如其名称所示,GloVe 会查看语料库中出现的所有词汇。

虽然 Word2Vec 是一种预测模型,它通过学习向量来提高预测能力,但 GloVe 是一种基于计数的模型。这意味着 GloVe 通过对共现计数矩阵进行降维来学习向量。GloVe 能够建立的连接如下所示:

king – man + woman = queen

这意味着它能够理解“king”和“queen”之间的关系与“man”和“woman”之间的关系相似。

这些是复杂的术语,所以我们一个个来理解。所有这些概念来自于统计学和线性代数,如果你已经了解这些内容,可以跳过活动部分!

在处理语料库时,存在根据词频构建矩阵的算法。基本上,这些矩阵包含文档中出现的词作为行,列则是段落或单独的文档。矩阵的元素表示词在文档中出现的频率。自然地,对于一个大型语料库,这个矩阵会非常庞大。处理这样一个大型矩阵将需要大量的时间和内存,因此我们进行降维处理。这是减少矩阵大小的过程,使得可以对其进行进一步操作。

对于 GloVe 来说,矩阵被称为共现计数矩阵,包含单词在语料库中特定上下文中出现的次数信息。行是单词,列是上下文。然后,这个矩阵会被分解以减少维度,新的矩阵为每个单词提供了一个向量表示。

GloVe 还具有预训练的单词,并附有向量,可以在语义匹配语料库和任务时使用。以下活动将引导您完成在 Python 中实现 GloVe 的过程,不过代码不会直接给出,所以您需要动脑筋,甚至可能需要一些谷歌搜索。试试看!

练习 9:使用 GloVe 生成词嵌入

在本练习中,我们将使用Glove-Python生成词嵌入。

注意

要在您的平台上安装 Glove-Python,请访问 pypi.org/project/glo…

mattmahoney.net/dc/text8.zi… 下载 Text8Corpus。

提取文件并将其与您的 Jupyter 笔记本一起存储。

  1. 导入itertools

    import itertools
    
  2. 我们需要一个语料库来生成词嵌入,而幸运的是,gensim.models.word2vec库中有一个名为Text8Corpus的语料库。导入它和Glove-Python库中的两个模块:

    from gensim.models.word2vec import Text8Corpus
    from glove import Corpus, Glove
    
  3. 使用itertools将语料库转换为列表形式的句子:

    sentences = list(itertools.islice(Text8Corpus('text8'),None))
    
  4. 启动Corpus()模型并将其拟合到句子上:

    corpus = Corpus()
    corpus.fit(sentences, window=10)
    

    window参数控制考虑多少个相邻单词。

  5. 现在我们已经准备好了语料库,需要训练嵌入。启动Glove()模型:

    glove = Glove(no_components=100, learning_rate=0.05)
    
  6. 基于语料库生成共现矩阵,并将glove模型拟合到此矩阵上:

    glove.fit(corpus.matrix, epochs=30, no_threads=4, verbose=True)
    

    模型已训练完成!

  7. 添加语料库的字典:

    glove.add_dictionary(corpus.dictionary)
    
  8. 使用以下命令查看根据生成的词嵌入,哪些单词与您选择的单词相似:

    glove.most_similar('man')
    

    预期输出:

图 1.25:'man'的词嵌入输出

图 1.25:'man'的词嵌入输出

您可以尝试对多个不同的单词进行此操作,看看哪些单词与它们相邻并且最相似:

glove.most_similar('queen', number = 10)

预期输出:

图 1.26:'queen'的词嵌入输出

图 1.26:'queen'的词嵌入输出
注意

若要了解更多关于 GloVe 的信息,请访问 nlp.stanford.edu/projects/gl…

活动 1:使用 Word2Vec 从语料库生成词嵌入。

您的任务是基于特定语料库(此处为 Text8Corpus)训练 Word2Vec 模型,确定哪些单词彼此相似。以下步骤将帮助您解决问题。

注意

您可以在 mattmahoney.net/dc/text8.zi… 找到文本语料库文件。

  1. 从先前给出的链接上传文本语料库。

  2. gensim模型中导入word2vec

  3. 将语料库存储在一个变量中。

  4. 在语料库上拟合 word2vec 模型。

  5. 找到与“man”最相似的单词。

  6. 'Father' 对 'girl','x' 对 'boy'。 找到 x 的前 3 个单词。

    注意

    活动的解决方案可以在第 296 页找到。

    预期输出:

图 1.27:相似词嵌入的输出

图 1.27:相似词嵌入的输出

'x'的前三个词可能是:

图 1.28:'x'的前三个词的输出

图 1.28:'x'的前三个词的输出

摘要

在本章中,我们了解了自然语言处理如何使人类和机器能够使用自然语言进行交流。自然语言处理有三大应用领域,分别是语音识别、自然语言理解和自然语言生成。

语言是复杂的,因此文本需要经过多个阶段,才能让机器理解。这个过滤过程称为文本预处理,包含了多种技术,服务于不同的目的。它们都是任务和语料库依赖的,并为将文本输入到机器学习和深度学习模型中做好准备。

由于机器学习和深度学习模型最适合处理数值数据,因此有必要将预处理过的语料库转换为数值形式。此时,词嵌入便登场了;它们是单词的实值向量表示,帮助模型预测和理解单词。生成词嵌入的两种主要算法是 Word2Vec 和 GloVe。

在下一章中,我们将基于自然语言处理算法进行深入探讨。我们将介绍和解释词性标注和命名实体识别的过程。

第三章:第二章

自然语言处理的应用

学习目标

在本章结束时,你将能够:

  • 描述词性标注及其应用

  • 区分基于规则的词性标注器和基于随机的词性标注器

  • 对文本数据执行词性标注、词块分析和分块

  • 执行命名实体识别进行信息提取

  • 开发并训练你自己的词性标注器和命名实体识别器

  • 使用 NLTK 和 spaCy 执行词性标注(POS tagging)、词块分析(chunking)、分块(chinking)和命名实体识别(Named Entity Recognition)

本章旨在向你介绍自然语言处理的众多应用及其涉及的各种技术。

引言

本章从快速回顾自然语言处理是什么以及它可以提供哪些服务开始。接着,讨论自然语言处理的两个应用:词性标注(POS tagging)命名实体识别(Named Entity Recognition)。解释了这两种算法的功能、必要性和目的。此外,还有一些练习和活动,用于执行词性标注和命名实体识别,并构建和开发这些算法。

自然语言处理旨在帮助机器理解人类的自然语言,以便有效地与人类沟通并自动化大量任务。上一章讨论了自然语言处理的应用以及这些技术如何简化人类生活的实际案例。本章将特别探讨其中的两种算法及其现实应用。

自然语言处理的各个方面都可以类比为语言教学。在上一章中,我们看到机器需要被告知关注语料库的哪些部分,哪些部分是无关紧要的。它们需要被训练去去除停用词和噪声元素,专注于关键词,将同一词的不同形式归约为词根,这样就能更容易地进行搜索和解读。以类似的方式,本章讨论的两种算法也教会机器一些关于语言的特定知识,正如我们人类在学习语言时所经历的。

词性标注

在我们深入了解算法之前,先来了解什么是词性。词性是我们在学习英语的早期阶段被教授的内容。它们是根据单词的句法或语法功能对单词进行分类的方式。这些功能是不同单词之间存在的功能性关系。

词性

英语有九大主要词性:

  • 名词:事物或人

  • 示例:table(桌子)、dog(狗)、piano(钢琴)、London(伦敦)、towel(毛巾)

  • 代词:替代名词的词

  • 示例:I(我)、you(你)、he(他)、she(她)、it(它)

  • 动词:表示动作的词

  • 示例:to be(是)、to have(有)、to study(学习)、to learn(学习)、to play(玩)

  • 形容词:描述名词的词

  • 示例:intelligent(聪明的)、small(小的)、silly(傻的)、intriguing(有趣的)、blue(蓝色的)

  • 限定词:限制名词的词

  • 示例:a few(一些)、many(许多)、some(一些)、three(三个)

    注意

    想了解更多限定词的例子,请访问 www.ef.com/in/english-resources/english-grammar/determiners/

  • 副词:描述动词、形容词或副词本身的词

  • 示例:quickly(快速地)、shortly(很快)、very(非常)、really(真的)、drastically(急剧地)

  • 介词:将名词与其他词连接的词

  • 示例:to(到)、on(在……上)、in(在……里面)、under(在……下)、beside(在……旁边)

  • 连词:连接两个句子或词语的词

  • 示例:and(和)、but(但是)、yet(然而)

  • 感叹词:表示惊叹的词语

  • 示例:ouch!(哎呀!)、Ow!(啊!)、Wow!(哇!)

如你所见,每个词都被分配了一个特定的词性标签,这帮助我们理解词的意义和用途,使我们更好地理解其使用的上下文。

词性标注器

词性标注是为一个词分配标签的过程。这是通过一种叫做词性标注器的算法完成的。该算法的目标其实就是这么简单。

大多数词性标注器是监督式学习算法。如果你不记得什么是监督式学习算法,它是基于先前标注的数据学习执行任务的机器学习算法。算法将数据行作为输入。这些数据包含特征列——用于预测某些事物的数据——通常还包括一个标签列——需要预测的事物。模型在这些输入数据上进行训练,学习和理解哪些特征对应于哪些标签,从而学会如何执行预测标签的任务。最终,它们会接收到未标注的数据(仅包含特征列的数据),并根据这些数据预测标签。

以下图示是监督式学习模型的一般示意图:

图 2.1:监督式学习

图 2.1:监督式学习
注意

想了解更多关于监督式学习的信息,请访问 www.packtpub.com/big-data-and-business-intelligence/applied-supervised-learning-python

因此,词性标注器通过学习先前标注过的数据集来提升其预测能力。在这种情况下,数据集可以包含各种特征,比如词本身(显然)、词的定义、词与其前后及其他相关词语(在同一句话、短语或段落中出现的)之间的关系。这些特征共同帮助标注器预测应该为一个词分配什么词性标签。用于训练监督式词性标注器的语料库被称为预标注语料库。这样的语料库作为构建系统的基础,帮助词性标注器为未标注的词进行标注。这些系统/类型的词性标注器将在下一节中讨论。

然而,预标注的语料库并不总是现成可用的,而且为了准确训练标注器,语料库必须足够大。因此,最近有一些词性标注器的迭代版本可以被视为无监督学习算法。这些算法以仅包含特征的数据为输入。这些特征不与标签相关联,因此,算法不是预测标签,而是根据输入数据形成分组或聚类。

在词性标注的过程中,模型使用计算方法自动生成一组词性标签。而预标注语料库则在监督式词性标注器的情况下帮助创建标注器的系统,而在无监督词性标注器的情况下,这些计算方法则作为创建此类系统的基础。无监督学习方法的缺点在于,自动生成的词性标签集可能并不像用于训练监督方法的预标注语料库中的标签那么准确。

总结来说,监督学习和无监督学习方法的关键区别如下:

  • 监督式词性标注器以预标注的语料库作为输入进行训练,而无监督词性标注器则以未标注的语料库作为输入,创建一组词性标签。

  • 监督式词性标注器根据标注语料库创建包含相应词性标签的单词词典,而无监督式词性标注器则使用自创的词性标签集生成这些词典。

几个 Python 库(如 NLTK 和 spaCy)都有自己训练的词性标注器。你将在接下来的章节中学习如何使用其中一个,但现在先让我们通过一个示例来理解词性标注器的输入和输出。需要记住的一个重要点是,由于词性标注器会为给定语料库中的每个单词分配一个词性标签,因此输入需要以单词标记的形式提供。因此,在进行词性标注之前,需要对语料库进行标记化处理。假设我们给训练过的词性标注器输入以下标记:

['I', 'enjoy', 'playing', 'the', 'piano']

在进行词性标注后,输出将类似于以下内容:

['I_PRO', 'enjoy_V', 'playing_V', 'the_DT', piano_N']

这里,PRO = 代词,V = 动词,DT = 限定词,N = 名词。

训练过的监督式和无监督式词性标注器的输入和输出是相同的:分别是标记和带词性标签的标记。

注意

这不是输出的确切语法;你将在进行练习时看到正确的输出。这里仅仅是为了让你了解词性标注器的作用。

上述的词性只是非常基础的标签,为了简化自然语言理解的过程,词性算法创建了更为复杂的标签,这些标签是这些基础标签的变体。以下是完整的带描述的词性标签列表:

图 2.2:带描述的词性标签

图 2.2:带描述的词性标签

这些标签来自宾夕法尼亚树库标签集(www.ling.upenn.edu/courses/Fall_2003/ling001/penn_treebank_pos.html),它是最流行的标签集之一。大多数英语语言的预训练标注器都使用这个标签集进行训练,包括 NLTK 的词性标注器。

词性标注的应用

就像文本预处理技术通过促使机器专注于重要细节,帮助机器更好地理解自然语言一样,词性标注帮助机器解读文本的上下文,从而理解它。虽然文本预处理更像是清理阶段,但词性标注实际上是机器开始独立输出关于语料库有价值信息的部分。

理解哪些词对应哪些词性,对于机器处理自然语言有多种好处:

  • 词性标注对于区分同形异义词非常有用——这些词虽然拼写相同,但意思却不同。例如,单词“play”可以作为动词,表示参与某项活动,也可以作为名词,表示一种在舞台上表演的戏剧作品。词性标注器可以帮助机器通过确定“play”的词性标签,理解该词在上下文中的使用情况。

  • 词性标注建立在句子和单词分割的需求之上——这是自然语言处理的基本任务之一。

  • 词性标签被用于其他算法执行更高层次的任务,其中之一就是我们将在本章中讨论的命名实体识别。

  • 词性标签也有助于情感分析和问答的过程。例如,在句子“Tim Cook 是这家科技公司的 CEO”中,你希望机器能够将“这家科技公司”替换为公司的名称。词性标注可以帮助机器识别出“这家科技公司”是一个限定词((this)+名词短语(technology company))。它可以利用这些信息,例如,搜索网上的文章并检查“Tim Cook 是 Apple 的 CEO”出现的次数,进而判断 Apple 是否是正确答案。

因此,词性标注是理解自然语言过程中的一个重要步骤,因为它有助于其他任务的完成。

词性标注器的类型

正如我们在前一节中所看到的,词性标注器可以是监督学习型或无监督学习型。这一差异在很大程度上影响了标注器的训练方式。还有另一个区分点影响着标注器如何为未标注的词分配标签,这就是用于训练标注器的方法。

词性标注器有两种类型:基于规则的和随机的。我们来看看这两者。

基于规则的词性标注器

这些词性标注器的工作方式几乎完全如其名称所示——通过规则。为标注器提供规则集的目的是确保它们在大多数情况下准确标注歧义/未知单词,因此大多数规则仅在标注器遇到歧义/未知单词时应用。

这些规则通常被称为上下文框架规则,向标注器提供上下文信息,以帮助它们理解给歧义单词分配哪个标签。一个规则的示例是:如果一个歧义/未知单词 x 前面跟着一个限定词,后面跟着一个名词,则为其分配形容词标签。一个例子是“one small girl”,其中“one”是限定词,“girl”是名词,因此标注器会给“small”分配形容词标签。

规则取决于你对语法的理论。此外,规则通常还包括诸如大写和标点符号的规则。这可以帮助你识别代词,并将其与句子开头(跟随句号)出现的单词区分开来。

大多数基于规则的词性标注器是监督学习算法,旨在学习正确的规则并将其应用于正确标注歧义词。然而,最近也有实验尝试使用无监督的方式训练这些标注器。未标记的文本提供给标注器进行标注,然后人类检查输出的标签,纠正任何不准确的标签。这些正确标注的文本随后会提供给标注器,以便它可以在两个不同的标签集之间发展出修正规则,并学习如何准确标注单词。

这种基于修正规则的词性标注器的一个例子是布里尔的标注器,它遵循之前提到的过程。它的功能可以与绘画艺术进行比较——在给房子上色时,首先涂上房子的背景色(例如,一个棕色的方形),然后在背景上使用更细的画笔绘制细节,如门和窗户。类似地,布里尔的基于规则的词性标注器的目标是首先一般性地标注一个未标注的语料库,即使某些标签可能是错误的,然后重新检查这些标签,理解哪些标签是错误的并从中学习。

注意

练习 10-16 可以在同一个 Jupyter Notebook 中进行。

练习 10:执行基于规则的词性标注

NLTK 有一个基于规则的词性标注器。在本练习中,我们将使用 NLTK 的词性标注器进行词性标注。以下步骤将帮助你解决这个问题:

  1. 打开 cmd 或终端,具体取决于你的操作系统。

  2. 导航到所需路径,并使用以下命令启动Jupyter Notebook:

    jupyter notebook
    
  3. 导入nltkpunkt,如所示:

    import nltk
    nltk.download('punkt')
    nltk.download('averaged_perceptron_tagger')
    nltk.download('tagsets')
    
  4. 将输入字符串存储在一个名为s的变量中,如下所示:

    s = 'i enjoy playing the piano'
    
  5. 如示例所示,对句子进行分词:

    tokens = nltk.word_tokenize(s)
    
  6. 对词元应用词性标注器,然后打印标签集,如下所示:

    tags = nltk.pos_tag(tokens)
    tags
    

    你的输出将如下所示:

    图 2.3:标注输出

    图 2.3:标注输出
  7. 要理解 "NN" 词性标签的含义,你可以使用以下代码行:

    nltk.help.upenn_tagset("NN")
    

    输出结果将如下所示:

    图 2.4:名词详情

    图 2.4:名词详情

    你可以通过将“NN”替换为每个词性标签来对每个词性标签进行处理。

    让我们通过一个包含同义词的句子来尝试一下。

  8. 将包含同义词的输入字符串存储在一个名为 sent 的变量中:

    sent = 'and so i said im going to play the piano for the play tonight'
    
  9. 对这个句子进行分词处理,并像示例中一样应用词性标注器:

    tagset = nltk.pos_tag(nltk.word_tokenize(sent))
    tagset
    

    预期输出:

图 2.5:标注输出

图 2.5:标注输出

如你所见,第一个“play”实例被标注为“VB”,代表动词,基本形式,第二个“play”实例被标注为“NN”,代表名词。因此,词性标注器能够区分同义词和同一单词的不同实例。这有助于机器更好地理解自然语言。

随机词性标注器

随机词性标注器是使用除基于规则的方法之外的任何方法为单词分配标签的标注器。因此,有很多方法属于随机类别。所有在为单词确定词性标签时,采用统计方法(如概率和频率)模型的标注器都是随机模型。

我们将讨论三种模型:

  • 单字法或词频方法

  • n-gram 方法

  • 隐马尔可夫模型

单字法或词频方法

最简单的随机词性标注器仅根据一个词与标签出现的概率来为模糊的单词分配词性标签。这基本上意味着,标注器在训练集中最常与某个单词关联的标签,就是它为该模糊单词实例分配的标签。例如,假设训练集中,“beautiful”一词大多数情况下被标注为形容词。当词性标注器遇到“beaut”时,它不能直接标注该词,因为它不是一个有效的单词。这个词将是一个模糊词,因此它将计算它可能属于每个词性标签的概率,基于该词在不同实例中被标注为每个词性标签的频率。“beaut”可以看作是“beautiful”的模糊形式,并且由于“beautiful”大多数时候被标注为形容词,词性标注器也会将“beaut”标注为形容词。这被称为词频方法,因为标注器正在检查与单词关联的词性标签的频率。

n-gram 方法

这一方法建立在之前的基础上。名称中的 n 代表在确定一个单词属于某一词性标签的概率时考虑的单词数量。在单字法标注器中,n = 1,因此只考虑单词本身。增加 n 的值会导致标注器计算特定的 n 个词性标签序列共同出现的概率,并根据该概率为单词分配标签。

在为单词分配标签时,这些 POS 标注器通过考虑它的类型和前面 n 个单词的 POS 标签来创建单词的上下文。根据上下文,标注器选择最可能与前面单词标签序列一致的标签,并将其分配给所讨论的单词。最流行的 n-gram 标注器称为维特比算法。

隐马尔可夫模型

隐马尔可夫模型结合了词频方法和 n-gram 方法。马尔可夫模型描述了事件或状态的序列。每个状态发生的概率仅依赖于前一个事件的状态。这些事件基于观察。隐马尔可夫模型的“隐藏”方面在于,事件可能是一组隐藏状态。

在 POS 标注的情况下,观察结果是单词标记,而隐藏的状态集合是 POS 标签。工作方式是模型根据前一个词的标签计算当前词具有特定标签的概率。例如,P(V | NN)是当前单词是动词的概率,假设前一个单词是名词。

注意

这是对隐马尔可夫模型的非常基本的解释。要了解更多,请访问 medium.freecodecamp.org/an-introduc…

要了解更多有关随机模型的信息,请访问 ccl.pku.edu.cn/doubtfire/N…

之前提到的三种方法按顺序解释了每个模型是如何在前一个模型的基础上构建并提高精度的。然而,每个建立在前一个模型基础上的模型涉及更多的概率计算,因此将根据训练语料库的大小来进行更多的计算时间。因此,选择使用哪种方法取决于语料库的大小。

练习 11:执行随机 POS 标注

spaCy 的 POS 标注器是一种随机标注器。在本练习中,我们将使用 spaCy 的 POS 标注器对一些句子进行标记,以查看基于规则和随机标注之间的结果差异。以下步骤将帮助您解决问题:

注意

要安装 spaCy,请点击以下链接并按照说明操作:spacy.io/usage

  1. 导入spaCy

    import spacy
    
  2. 加载 spaCy 的'en_core_web_sm'模型:

    nlp = spacy.load('en_core_web_sm')
    

    spaCy 有针对不同语言的特定模型。'en_core_web_sm'模型是英语语言模型,已经在书面网络文本(如博客和新闻文章)上进行了训练,包括词汇、句法和实体。

    注意

    要了解更多有关 spaCy 模型的信息,请访问 spacy.io/models。

  3. 在您要为其分配 POS 标签的句子上拟合模型。让我们使用我们给 NLTK 的 POS 标注器的句子:

    doc = nlp(u"and so i said i'm going to play the piano for the play tonight")
    
  4. 现在,让我们对这个句子进行标记化,分配 POS 标签,并打印它们:

    for token in doc:
        print(token.text, token.pos_, token.tag_)
    

    预期输出:

图 2.6:词性标注输出

图 2.6:词性标注输出

若要理解词性标注的含义,可以使用以下代码:

spacy.explain("VBZ")

用你想了解的词性标注替换"VBZ"。在这种情况下,你的输出将是:

'verb, 3rd person singular present'

正如你所见,结果与 NLTK 词性标注器的输出基本相同。这是因为我们的输入非常简单。

分块处理

词性标注器处理单独的词元进行标注。然而,单独对每个词进行标注并不是理解语料库的最佳方式。例如,'United'和'Kingdom'分开时意义不大,但'United Kingdom'作为一个整体告诉机器这代表一个国家,从而提供更多的上下文和信息。正是在这个过程中,分块处理发挥了作用。

分块处理是一种算法,它以单词及其词性标注作为输入。它处理这些单独的词元及其标注,查看它们是否可以组合。一个或多个单独的词元的组合被称为一个分块,分配给该分块的词性标注称为分块标记。

分块标记是基本词性标记的组合。通过这些标记更容易定义短语,而且比简单的词性标记更高效。这些短语即为分块。有时单个词也会被视为一个分块并分配分块标记。常见的五种主要分块标记如下:

  • 名词短语 (NP):这些是以名词为核心词的短语。它们作为动词或动词短语的主语或宾语。

  • 动词短语 (VP):这些是以动词为核心词的短语。

  • 形容词短语 (ADJP):这些是以形容词为核心词的短语。形容词短语的主要功能是描述和修饰名词或代词。它们通常位于名词或代词之前或之后。

  • 副词短语 (ADVP): 这些是以副词为核心词的短语。它们通过提供描述和修饰名词或动词的细节,用来作为这些词的修饰语。

  • 介词短语 (PP):这些是以介词为核心词的短语。它们用来定位动作或实体在时间或空间中的位置。

例如,在句子“the yellow bird is slow and is flying into the brown house”中,以下短语将被分配相应的分块标记:

'the yellow bird' – NP

'is' – VP

'slow' – ADJP

'is flying' – VP

'into' – PP

'the brown house' – NP

因此,分块处理是在对语料库应用词性标注(POS tagging)之后进行的。这使得文本能够被分解为最简单的形式(单词的词元),对其结构进行分析,然后再重新组合成有意义的更高级别的分块。分块处理还有助于命名实体识别的过程。我们将在接下来的部分看到具体如何进行。

NLTK 库内的块解析器是基于规则的,因此需要提供一个正则表达式作为规则来输出具有其块标签的块。spaCy可以在没有规则的情况下执行分块。让我们看看这两种方法。

练习 12:使用 NLTK 执行分块

在这个练习中,我们将生成块和块标签。nltk有一个正则表达式解析器。这需要一个短语的正则表达式和相应的块标签作为输入。然后它在语料库中搜索这个表达式并分配标签。

由于分块与词性标记一起工作,我们可以在词性标记练习的基础上扩展代码。我们在 'tagset' 中保存了具有各自词性标记的标记。让我们使用它。以下步骤将帮助您解决问题:

  1. 创建一个正则表达式,将搜索一个名词短语,如下所示:

    rule = r"""Noun Phrase: {<DT>?<JJ>*<NN>}"""
    

    此正则表达式搜索一个限定词(可选)、一个或多个形容词,然后是一个名词。这将形成一个称为名词短语的块。

    注意

    如果您不知道如何编写正则表达式,请查看这些快速教程:www.w3schools.com/python/pyth… pythonprogramming.net/regular-exp…

  2. 创建一个RegexpParser的实例,并将规则传递给它:

    chunkParser = nltk.RegexpParser(rule)
    
  3. tagset 包含具有各自词性标记的标记传递给 chunkParser,以便它可以执行分块,然后绘制块:

    chunked = chunkParser.parse(tagset)
    chunked.draw()
    
    注意

    对于 .draw() 函数能正常工作,matplotlib 需要在您的机器上安装。

    您的输出将类似于这样:

    图 2.7:解析树。

    图 2.7:解析树。

    这是一个解析树。如您所见,分块过程已识别出名词短语并加标签,剩余的标记显示它们的词性标签。

  4. 让我们用另一句话试试同样的事情。将输入句子存储在另一个变量中:

    a = "the beautiful butterfly flew away into the night sky"
    
  5. 使用 NLTK 的 POS 标记器对句子进行标记化和词性标注:

    tagged = nltk.pos_tag(nltk.word_tokenize(a))
    
  6. 重复第 3 步:

    chunked2 = chunkParser.parse(tagged)
    chunked2.draw()              
    

    期望的输出:

图 2.8:分块的输出。

图 2.8:分块的输出。

练习 13:使用 spaCy 执行分块

在这个练习中,我们将使用 spaCy 实现分块。spaCy不需要我们制定规则来识别块;它自动识别块并告诉我们头词是什么,从而告诉我们块标签是什么。让我们使用与练习 12 相同的句子识别一些名词块。以下步骤将帮助您解决问题:

  1. 在这个句子上,使用 spaCy 的英文模型进行适配:

    doc = nlp(u"the beautiful butterfly flew away into the night sky")
    
  2. 在这个模型上应用 noun_chunks,对于每个块,打印块的文本、块的根词和连接根词与其头部的依赖关系:

    for chunk in doc.noun_chunks:
        print(chunk.text, chunk.root.text, chunk.root.dep_)
    

    期望的输出:

图 2.9:使用 spaCy 进行分块的输出

图 2.9:使用 spaCy 进行分块的输出

如您所见,与 NLTK 相比,spaCy 的分块要简单得多。

Chinking

切除操作是分块的扩展,正如你从它的名字中可能已经猜到的那样。它不是处理自然语言中的必选步骤,但它可以是有益的。

切除操作发生在分块之后。分块后,你会得到带有分块标签的块,以及带有词性标签的单个单词。通常,这些多余的单词是没有必要的。它们对最终结果或理解自然语言的整个过程没有贡献,因此会造成干扰。切除操作帮助我们通过提取块及其块标签来处理这个问题,形成标记语料库,从而去除不必要的部分。这些有用的块被称为切除块,一旦它们从标记语料库中提取出来。

例如,如果你只需要从语料库中提取名词或名词短语来回答诸如“这个语料库在讲什么?”这样的问题,你应该应用切除操作,因为它只会提取你需要的内容,并将其呈现在你眼前。让我们通过一个练习来验证这一点。

练习 14:执行切除操作

切除操作基本上是在改变你在语料库中寻找的东西。因此,应用切除操作涉及改变提供给 chinkParser 的规则(正则表达式)。以下步骤将帮助你完成解决方案:

  1. 创建一个规则,将整个语料库进行分块,并只从标记为名词或名词短语的单词或短语中创建切除块:

    rule = r"""Chink: {<.*>+}
                        }<VB.?|CC|RB|JJ|IN|DT|TO>+{"""
    

    这个规则的形式是正则表达式。基本上,这个正则表达式告诉机器忽略所有不是名词或名词短语的单词。当遇到名词或名词短语时,这个规则将确保它被提取为一个切除块。

  2. 创建一个 RegexpParser 实例并传入规则:

    chinkParser = nltk.RegexpParser(rule)
    
  3. chinkParser 提供包含带有相应 POS 标签的标记的 tagset,以便它可以执行切除操作,然后绘制切除块:

    chinked = chinkParser.parse(tagset)
    chinked.draw()
    

    预期输出:

图 2.10:切除操作的输出

图 2.10:切除操作的输出

如你所见,切除块已被高亮显示,并且只包含名词。

活动 2:构建和训练你自己的 POS 标注器

我们已经看过了如何使用现有的和预训练的 POS 标注器对单词进行词性标注。在这个活动中,我们将训练我们自己的 POS 标注器。这就像训练任何其他机器学习算法一样。以下步骤将帮助你完成解决方案:

  1. 选择一个语料库来训练标注器。你可以使用 nltk treebank 来进行操作。以下代码应该能帮助你导入 treebank 语料库:

    nltk.download('treebank')
    tagged = nltk.corpus.treebank.tagged_sents()
    
  2. 确定标注器在为单词分配标签时将考虑的特征。

  3. 创建一个函数,去除带标签的单词的标签,以便我们可以将它们传递给我们的标注器。

  4. 构建数据集并将数据分为训练集和测试集。将特征赋给 'X',并将 POS 标签附加到 'Y'。在训练集上应用这个函数。

  5. 使用决策树分类器训练标注器。

  6. 导入分类器,初始化它,在训练数据上拟合模型,并打印准确性分数。

    注意

    输出中的准确性分数可能会有所不同,这取决于使用的语料库。

    期望输出:

图 2.11:期望的准确性分数。

图 2.11:期望的准确性分数。
注意

该活动的解决方案可以在第 297 页找到。

命名实体识别

这是信息提取过程中的第一步。信息提取是机器从非结构化或半结构化文本中提取结构化信息的任务。这有助于机器理解自然语言。

在文本预处理和词性标注后,我们的语料库变得半结构化且机器可读。因此,信息提取是在我们准备好语料库后进行的。

以下图示为命名实体识别的示例:

图 2.12:命名实体识别示例

图 2.12:命名实体识别示例

命名实体

命名实体是可以归类为不同类别的现实世界对象,如人、地点和物品。基本上,它们是可以通过专有名称表示的词汇。命名实体还可以包括数量、组织、货币金额等。

一些命名实体及其所属类别示例如下:

  • 唐纳德·特朗普,人物

  • 意大利,地点

  • 瓶子,物体

  • 500 美元,货币

命名实体可以视为实体的实例。在之前的示例中,类别基本上是实体本身,命名实体是这些实体的实例。例如,伦敦是“城市”的一个实例,而“城市”是一个实体。

最常见的命名实体类别如下所示:

  • 组织(ORGANIZATION)

  • 人物(PERSON)

  • 地点(LOCATION)

  • 日期(DATE)

  • 时间(TIME)

  • 货币(MONEY)

  • 百分比(PERCENT)

  • 设施(FACILITY)

  • GPE(地理政治实体)

命名实体识别器

命名实体识别器是识别和提取语料库中的命名实体并为其分配类别的算法。提供给经过训练的命名实体识别器的输入是标记化的词语及其相应的词性标注。命名实体识别的输出是命名实体及其类别,并与其他标记化的词语及其词性标注一起给出。

命名实体识别问题分为两个阶段:

  1. 识别和识别命名实体(例如,“伦敦”)

  2. 对这些命名实体进行分类(例如,“伦敦”是一个“地点”)

第一阶段的命名实体识别与分块过程相似,因为其目标是识别由专有名词表示的事物。命名实体识别器需要关注连续的词元序列,以便正确地识别命名实体。例如,“美国银行”应该被识别为一个单一的命名实体,尽管该短语包含了“美国”这个词,而“美国”本身就是一个命名实体。

与词性标注器类似,大多数命名实体识别器都是监督学习算法。它们在包含命名实体及其所属类别的输入数据上进行训练,从而使算法能够学习如何在未来对未知命名实体进行分类。

这种包含命名实体及其相应类别的输入数据通常被称为知识库。一旦命名实体识别器经过训练并面对未识别的语料库,它会参考这个知识库,寻找最准确的分类,以分配给命名实体。

然而,由于监督学习需要大量标注数据,未监督学习版本的命名实体识别器也正在进行研究。这些模型在未标注的语料库上进行训练——这些文本中没有被分类的命名实体。与词性标注器类似,命名实体识别器会对命名实体进行分类,然后不正确的分类会由人工进行修正。修正后的数据会反馈给命名实体识别器,从而使它们能够从错误中学习。

命名实体识别的应用

如前所述,命名实体识别是信息提取的第一步,因此在使机器理解自然语言并执行各种基于此的任务中起着重要作用。命名实体识别可以并且已经在多个行业和场景中被使用,以简化和自动化过程。让我们来看几个应用案例:

  • 在线内容,包括文章、报告和博客文章,通常会被打上标签,以便用户更轻松地进行搜索,并快速了解内容的主要信息。命名实体识别器可以用来扫描这些内容并提取命名实体,以自动生成这些标签。这些标签也有助于将文章归类到预定义的层级中。

  • 搜索算法同样也受益于这些标签。如果用户向搜索算法输入一个关键词,算法不需要遍历每篇文章中的所有单词(这会耗费大量时间),它只需要参考命名实体识别所生成的标签,就可以快速提取出包含或与输入关键词相关的文章。这大大减少了计算时间和操作量。

  • 这些标签的另一个用途是创建高效的推荐系统。如果你阅读了一篇关于印度当前政治局势的文章,文章可能被标记为“印度政治”(这只是一个例子),那么新闻网站可以利用这个标签来推荐具有相同或相似标签的不同文章。在视觉娱乐领域,如电影和电视节目也是如此。在线视频平台会使用分配给内容的标签(例如“动作”、“冒险”、“惊悚”等类型),以更好地了解你的口味,从而向你推荐相似的内容。

  • 客户反馈 对于任何提供服务或产品的公司都至关重要。通过命名实体识别器处理客户投诉和评价,可以生成标签,帮助基于地点、产品类型和反馈类型(正面或负面)对它们进行分类。这些评价和投诉随后可以发送给负责该产品或该领域的人,并根据反馈是正面还是负面进行处理。对推文、Instagram 标题、Facebook 帖子等也可以进行类似操作。

如你所见,命名实体识别有许多应用。因此,理解它是如何工作的,以及如何实现它,非常重要。

命名实体识别器的类型

与 POS 标注器一样,设计命名实体识别器有两种主要方法:通过定义规则来识别实体的语言学方法,或者使用统计模型的随机方法来准确确定命名实体属于哪一类别。

基于规则的 NER

基于规则的 NER 的工作方式与基于规则的 POS 标注器相同。

随机 NER

这些包括所有使用统计学命名和识别实体的模型。对于随机命名实体识别,有几种方法。让我们来看一下其中的两种:

  • 最大熵分类

    这是一个机器学习分类模型。它仅根据提供给它的信息(语料库)计算命名实体落入特定类别的概率。

    注意

    如需了解更多关于最大熵分类的信息,请访问 blog.datumbox.com/machine-lea…

  • 隐马尔可夫模型

    这种方法与 POS 标注部分中解释的方法相同,但不同的是,隐藏状态集不再是 POS 标签,而是命名实体的类别。

    注意

    如需了解更多关于随机命名实体识别及何时使用哪种方法的信息,请访问 www.datacommunitydc.org/blog/2013/0…

练习 15:使用 NLTK 执行命名实体识别

在本练习中,我们将使用 NLTKne_chunk 算法对一个句子进行命名实体识别。与前几次练习中使用的句子不同,创建一个包含可以分类的专有名词的新句子,这样你就能实际看到结果:

  1. 将输入句子存储在一个变量中,如下所示:

    ex = "Shubhangi visited the Taj Mahal after taking a SpiceJet flight from Pune."
    
  2. 对句子进行分词,并为标记分配 POS 标签

    tags = nltk.pos_tag(nltk.word_tokenize(ex))
    
  3. 对标记过的词语应用 ne_chunk() 算法,并打印或绘制结果:

    ne = nltk.ne_chunk(tags, binary = True)
    ne.draw()
    

    True 的值赋给 binary 参数,告诉算法仅识别命名实体,而不对其进行分类。因此,你的结果将类似于以下内容:

    图 2.13:带有 POS 标签的命名实体识别输出

    图 2.13:带有词性标注的命名实体识别输出

    正如你所看到的,命名实体被标记为 'NE'。

  4. 要知道算法为这些命名实体分配了哪些类别,只需将 'binary' 参数的值设置为 'False':

    ner = nltk.ne_chunk(tags, binary = False)
    ner.draw()
    

    预期输出:

图 2.14:带有命名实体的输出

图 2.14:带有命名实体的输出

该算法准确地将 'Shubhangi' 和 'SpiceJet' 分类。'Taj Mahal' 这一项不应该是组织(ORGANIZATION),它应该是设施(FACILITY)。因此,NLTK 的 ne_chunk() 算法并不是最佳选择。

练习 16:使用 spaCy 进行命名实体识别

在这个练习中,我们将实现 spaCy 的命名实体识别器,处理前一个练习中的句子并比较结果。spaCy 有多个命名实体识别模型,这些模型在不同的语料库上进行训练。每个模型有不同的类别集;以下是 spaCy 可以识别的所有类别的列表:

图 2.15:spaCy 的类别

图 2.15:spaCy 的类别

以下步骤将帮助你解决问题:

  1. 在前一个练习中使用的句子上适配 spaCy 的英语模型:

    doc = nlp(u"Shubhangi visited the Taj Mahal after taking a SpiceJet flight from Pune.")
    
  2. 对于该句中的每个实体,打印实体的文本和标签:

    for ent in doc.ents:
        print(ent.text, ent.label_)
    

    你的输出将像这样:

    图 2.16:命名实体输出

    图 2.16:命名实体输出

    它只识别了 'SpiceJet' 和 'Pune' 作为命名实体,而没有识别 'Shubhangi' 和 'Taj Mahal'。让我们试着给 'Shubhangi' 加上一个姓氏,看看是否有所不同。

  3. 在新句子上适配模型:

    doc1 = nlp(u"Shubhangi Hora visited the Taj Mahal after taking a SpiceJet flight from Pune.")
    
  4. 重复步骤 2:

    for ent in doc1.ents:
        print(ent.text, ent.label_)
    

    预期输出:

图 2.17:使用 spaCy 进行命名实体识别的输出。

图 2.17:使用 spaCy 进行命名实体识别的输出。

现在我们已经加上了姓氏,“Shubhangi Hora” 被识别为一个 PERSON,“Taj Mahal” 被识别为 WORK_OF_ART。后者是不正确的,因为如果你查看类别表,WORK_OF_ART 用来描述歌曲和书籍。

因此,命名实体的识别和分类强烈依赖于识别器所训练的数据。这一点在实现命名实体识别时需要记住;通常来说,为特定的使用案例训练和开发自己的识别器会更好。

活动 3:在标注语料库上执行 NER

现在我们已经看到了如何在句子上执行命名实体识别,在这个活动中,我们将在一个经过词性标注的语料库上执行命名实体识别。假设你有一个语料库,已经为其标注了词性标签,现在你的任务是从中提取实体,以便你能够提供一个关于该语料库讨论内容的总体总结。以下步骤将帮助你解决问题:

  1. 导入 NLTK 和其他必要的包。

  2. 打印 nltk.corpus.treebank.tagged_sents() 来查看你需要提取命名实体的标注语料库。

  3. 将标注句子的第一句存储到一个变量中。

  4. 使用nltk.ne_chunk对句子进行命名实体识别(NER)。将binary设置为True并打印命名实体。

  5. 对任意数量的句子重复步骤 3 和步骤 4,查看语料库中存在的不同实体。将binary参数设置为False,查看命名实体的分类。

    预期输出:

图 2.18:对标注语料进行 NER 的预期输出

图 2.18:对标注语料进行 NER 的预期输出
注意

该活动的解决方案可以在第 300 页找到。

总结

自然语言处理使机器能够理解人类的语言,就像我们学习如何理解和处理语言一样,机器也在被教导。两种能让机器更好理解语言并为现实世界做出贡献的方法是词性标注和命名实体识别。

前者是将词性标签(POS)分配给单个单词,以便机器能够学习上下文,后者则是识别并分类命名实体,从语料库中提取有价值的信息。

这些过程的执行方式有所不同:算法可以是有监督的或无监督的,方法可以是基于规则的或随机的。无论哪种方式,目标都是一样的,即理解并与人类进行自然语言交流。

在下一章中,我们将讨论神经网络,它们如何工作,以及如何在自然语言处理中使用它们。

第四章:第三章

神经网络简介

学习目标

到本章结束时,你将能够:

  • 描述深度学习及其应用

  • 区分深度学习和机器学习

  • 探索神经网络及其应用

  • 了解神经网络的训练与工作原理

  • 使用 Keras 创建神经网络

本章旨在向你介绍神经网络、它们在深度学习中的应用及其普遍的缺点。

介绍

在前两章中,你了解了自然语言处理的基础知识、它的重要性、准备文本进行处理的步骤以及帮助机器理解并执行基于自然语言任务的两种算法。然而,为了应对更高层次、更复杂的自然语言处理问题,如创建类似SiriAlexa的个人语音助手,还需要额外的技术。深度学习系统,如神经网络,常用于自然语言处理,因此我们将在本章中讨论它们。在接下来的章节中,你将学习如何使用神经网络进行自然语言处理。

本章首先解释深度学习及其与机器学习的不同之处。然后,讨论神经网络,它是深度学习技术的核心部分,以及它们的基本功能和实际应用。此外,本章还介绍了Keras,一个 Python 深度学习库。

深度学习简介

人工智能是指拥有类似人类自然智能的智能体。这种自然智能包括计划、理解人类语言、学习、做决策、解决问题以及识别单词、图像和物体的能力。在构建这些智能体时,这种智能被称为人工智能,因为它是人为制造的。这些智能体并不指代物理对象。实际上,它们是指能展示人工智能的软件。

人工智能有两种类型——窄域人工智能和广域人工智能。窄域人工智能是我们目前所接触到的人工智能类型;它是任何拥有自然智能若干能力之一的单一智能体。本书第一章中你了解的自然语言处理应用领域就是窄域人工智能的例子,因为它们是能够执行单一任务的智能体,例如,机器能够自动总结文章。确实存在能够执行多项任务的技术,如自动驾驶汽车,但这些技术仍被认为是多个窄域人工智能的组合。

广义人工智能是指在一个智能体中拥有所有人类能力及更多能力,而不是一个智能体中仅有一到两个能力。AI 专家声称,一旦人工智能超越了广义人工智能的目标,在所有领域中比人类更聪明、更熟练,它将成为超级人工智能。

如前几章所述,自然语言处理是一种实现人工智能的方法,通过使机器能够理解并与人类以自然语言进行沟通。自然语言处理准备文本数据并将其转换为机器能够处理的形式——即数值形式。这就是深度学习的应用领域。

像自然语言处理和机器学习一样,深度学习也是一种技术和算法类别。它是机器学习的一个子领域,因为这两种方法共享相同的主要原理——无论是机器学习还是深度学习算法,都从输入中获取信息并使用它来预测输出。

图 3.1:深度学习作为机器学习的子领域

图 3.1:深度学习作为机器学习的一个子领域

当在训练数据集上训练时,两种类型的算法(机器学习和深度学习)都旨在最小化实际结果和预测结果之间的差异。这帮助它们在输入和输出之间建立关联,从而提高准确性。

比较机器学习和深度学习

虽然这两种方法都基于相同的原理——从输入预测输出——但它们通过不同的方式实现这一点,这也是深度学习被归类为一种独立方法的原因。此外,深度学习出现的一个主要原因是这些模型在预测过程中的准确性得到了提高。

虽然机器学习模型相当自足,但它们仍然需要人工干预来判断预测是否错误,因此需要在执行特定任务时变得更好。而深度学习模型则能够自己判断预测是否错误。因此,深度学习模型是自足的;它们能够在没有人工干预的情况下做出决策并提高效率。

为了更好地理解这一点,让我们以一个可以通过语音命令控制温度设置的空调为例。假设当空调听到“热”这个词时,它会降低温度,而当它听到“冷”这个词时,它会升高温度。如果这是一个机器学习模型,那么空调会随着时间的推移学会在不同的句子中识别这两个词。然而,如果这是一个深度学习模型,它可以根据与“热”和“冷”类似的词语和句子(如“有点热”或“我快冻死了!”等)来学习调整温度。

这是一个直接与自然语言处理相关的例子,因为该模型能够理解人类的自然语言,并根据其理解做出反应。在本书中,我们将专注于使用深度学习模型进行自然语言处理,尽管实际上它们几乎可以应用于每个领域。目前,它们在自动化驾驶任务中也有所应用,使得车辆能够识别停车标志、读取交通信号,并在行人面前停车。医疗领域也在利用深度学习方法检测早期的疾病——如癌细胞。然而,由于本书的重点是让机器理解人类的自然语言,我们还是回到这个主题。

深度学习技术通常用于监督学习方式,即它们会接受标记数据进行学习。然而,机器学习方法与深度学习方法之间的关键区别在于,后者需要极为庞大的数据量,这是之前不存在的。因此,深度学习直到最近才变得具有优势。它还需要相当大的计算能力,因为它需要在如此庞大的数据集上进行训练。

然而,主要的区别在于算法本身。如果你以前学习过机器学习,那么你应该知道解决分类和回归问题的各种算法,以及无监督学习的问题。深度学习系统与这些算法的不同之处在于,它们使用的是人工神经网络。

神经网络

神经网络和深度学习通常是互换使用的术语。它们并不意味着相同的东西,因此让我们来了解它们之间的区别。

如前所述,深度学习是一种遵循与机器学习相同原则的方法,但它具备更高的准确性和效率。深度学习系统利用人工神经网络,这些神经网络本身就是计算模型。因此,基本上,神经网络是深度学习方法的一部分,但并不是深度学习方法的全部。它们是被深度学习方法所整合的框架。

图 3.2: 神经网络作为深度学习方法的一部分

图 3.2: 神经网络作为深度学习方法的一部分

人工神经网络基于一个受人脑中生物神经网络启发的框架。这些神经网络由节点组成,使得网络能够从图像、文本、现实物体等中学习,从而能够执行任务并进行准确预测。

神经网络由多个层组成,我们将在接下来的部分中深入了解。这些层的数量可以从三层到数百层不等。由三层或四层构成的神经网络称为浅层神经网络,而层数更多的网络则被称为深度神经网络。因此,深度学习方法使用的神经网络是深度神经网络,它们包含多个层。由于这一点,深度学习模型非常适合处理复杂任务,如人脸识别、文本翻译等。

这些层将输入分解为多个抽象级别。因此,深度学习模型能够更好地从输入中学习并理解,无论是图像、文本还是其他形式的输入,这有助于它做出决策并像人类大脑一样进行预测。

让我们通过一个例子来理解这些层。假设你正在卧室里做些工作,突然注意到自己在出汗。这就是你的输入数据——你感到很热,于是脑海中浮现出一个声音:“我觉得很热!”接着,你可能会想为什么自己会感到这么热:“为什么我会这么热?”这是一个思考。然后你会尝试解决这个问题,或许通过洗个澡来缓解:“让我快速洗个澡。”这是你做出的决策。但随后你记得自己很快就要出门上班:“但是,我得很快离开家。”这是一个记忆。你可能会尝试说服自己:“其实,还是有足够时间快速洗个澡吧?”这是推理的过程。最后,你可能会根据自己的想法做出行动,或者心里想着:“我要去洗澡了”,或者“没时间洗澡了,算了。”这就是决策过程,如果你真的去洗了澡,那就是一种行动。

深度神经网络中的多层结构使得模型能够像大脑一样经历不同的处理层级,从而建立起生物神经网络的原理。这些层正是深度学习模型能够高精度完成任务和预测输出的原因。

神经网络架构

神经网络架构指的是构成神经网络的基本元素。尽管有多种不同类型的神经网络,但基本架构和基础结构保持不变。该架构包括:

  • 节点

  • 边缘

  • 偏差

  • 激活函数

如前所述,神经网络由多个层组成。虽然这些层的数量因模型而异,并且依赖于当前的任务,但只有三种类型的层。每一层由多个节点组成,节点的数量取决于该层以及整个神经网络的需求。一个节点可以看作是一个神经元。

神经网络中的层如下所示:

  • 输入层

    顾名思义,这一层由进入神经网络的输入数据组成。它是一个必需的层,因为每个神经网络都需要输入数据进行学习和执行操作,从而生成输出。此层在神经网络中只能出现一次。每个输入节点与后续层中的每个节点相连。

    输入数据的变量或特征被称为特征。目标输出依赖于这些特征。例如,以鸢尾花数据集为例。(鸢尾花数据集是机器学习初学者中最流行的数据集之一。它包含三种不同类型花卉的数据。每个实例有四个特征和一个目标类别。)花卉的分类标签取决于四个特征——花瓣的长度和宽度,以及萼片的长度和宽度。特征,因此输入层,被表示为X,每个单独的特征被表示为X1X2、...、Xn

  • 隐藏层

    这是进行实际计算的层。它位于输入层之后,因为它作用于由输入层提供的输入,并且位于输出层之前,因为它生成由输出层提供的输出。

    隐藏层由称为“激活节点”的节点组成。每个节点拥有一个激活函数,这是一个对激活节点接收到的输入执行的数学函数,用于生成输出。本章后续会讨论激活函数。

    这是唯一可以多次出现的层,因此在深度神经网络中,可能存在最多上百个隐藏层。隐藏层的数量取决于具体任务。

    一个隐藏层的节点生成的输出将作为输入传递到下一个隐藏层。每个隐藏层的激活节点生成的输出被发送到下一层的每个激活节点。

  • 输出层

    这是神经网络的最后一层,包含提供所有处理和计算结果的节点。这也是一个必需的层,因为神经网络必须根据输入数据生成输出。

    以鸢尾花数据集为例,某一花卉实例的输出将是该花卉的类别——鸢尾花 Setosa、鸢尾花 Virginica 或鸢尾花 Versicolor。

    输出,通常称为目标,表示为y

图 3.3:具有 2 个隐藏层的神经网络

图 3.3:具有 2 个隐藏层的神经网络

节点

每个激活节点或神经元具有以下组件:

  • 激活

    这是节点的当前状态——它是否处于激活状态。

  • 阈值(可选)

    如果存在,该值决定了一个神经元是否被激活,具体取决于加权和是否高于或低于此阈值。

  • 激活函数

    这是根据输入和加权和计算激活节点的新激活值的函数。

  • 输出函数

    这会根据激活函数生成特定激活节点的输出。

    输入神经元没有像这样的组件,因为它们不进行计算,也没有前置神经元。类似地,输出神经元也没有这些组件,因为它们不进行计算,也没有后续神经元。

边缘

 图 3.4:神经网络的加权连接

图 3.4:神经网络的加权连接

在前面的图示中,每一条箭头代表两个不同层的节点之间的连接。这样的连接被称为边缘。每个指向激活节点的边缘都有自己的权重,可以视为一个节点对另一个节点的影响程度。权重可以是正的,也可以是负的。

看看之前的图示。在值到达激活函数之前,它们的值会先与分配给各自连接的权重相乘。然后这些乘积的值会相加,得到一个加权和。这个加权和本质上是衡量该节点对输出的影响程度。如果值较低,意味着它对输出的影响不大,因此也不那么重要。如果值较高,则意味着它与目标输出有强烈的相关性,因此在确定输出时起着重要作用。

偏置

偏置是一个节点,神经网络的每一层都有自己的偏置节点,输出层除外。因此,每一层都有自己的偏置节点。偏置节点保存一个值,称为偏置。这个值会在计算加权和的过程中被加入,因此它在确定节点生成的输出中也起到了作用。

偏置是神经网络中的一个重要方面,因为它允许激活函数向右或向左平移。这有助于模型更好地拟合数据,从而生成准确的输出。

激活函数

激活函数是神经网络隐含层中激活节点的一部分。它们的作用是为神经网络引入非线性,这是非常重要的,因为没有它们,神经网络将只有线性函数,这样就和线性回归模型没有区别了。这就违背了神经网络的初衷,因为没有非线性,神经网络就无法学习数据中的复杂函数关系。激活函数还需要是可微的,以便进行反向传播。这个内容将在本章的后续部分讨论。

基本上,一个激活节点计算它接收到的输入的加权和,添加偏置值,然后对这个值应用激活函数。这会为该特定激活节点生成一个输出,该输出随后作为输入传递给下一层。这个输出被称为激活值。因此,下一层的激活节点将接收到来自前一层激活节点的多个激活值,并计算一个新的加权和。它会对这个值应用激活函数,生成它自己的激活值。这就是数据在神经网络中流动的方式。因此,激活函数帮助将输入信号转换为输出信号。

计算加权和、应用激活函数并产生激活值的过程称为前向传播。

有几种激活函数(如 Logistic、TanH、ReLU 等)。Sigmoid 函数是其中最流行且最简单的激活函数之一。当用数学形式表示时,这个函数看起来像

图 3.5:Sigmoid 函数的表达式

图 3.5:Sigmoid 函数的表达式

如你所见,这个函数是非线性的。

训练神经网络

到目前为止,我们知道,一旦输入提供给神经网络,它会进入输入层,这是一个用于将输入传递到下一层的接口。如果存在隐藏层,则输入会通过加权连接发送到隐藏层的激活节点。激活节点接收到的所有输入的加权和是通过将输入与各自的权重相乘,然后将这些值加上偏置值来计算的。激活函数从加权和中生成激活值,并将其传递到下一层的节点。如果下一层是另一个隐藏层,则它将使用来自前一隐藏层的激活值作为输入,并重复激活过程。然而,如果下一层是输出层,则神经网络会提供输出。

从这些信息中,我们可以得出结论,深度学习模型中有三个部分会影响模型生成的输出——输入、连接权重和偏置、以及激活函数。

图 3.6:影响输出的深度学习模型方面

图 3.6:影响输出的深度学习模型方面

虽然输入来自数据集,但前两个部分不是。那么,接下来就会有两个问题:谁或什么决定连接的权重是多少?我们怎么知道该使用哪些激活函数?让我们逐一解决这些问题。

计算权重

权重在多层神经网络中起着非常重要的作用,因为改变单一连接的权重会完全改变分配给进一步连接的权重,从而影响后续层生成的输出。因此,拥有最优的权重对于创建一个准确的深度学习模型是必要的。听起来好像压力很大,但幸运的是,深度学习模型能够自主找到最优的权重。为了更好地理解这一点,让我们以线性回归为例。

线性回归是一种监督式机器学习算法,顾名思义,它适用于解决回归问题(输出为连续数值的数据集,例如房屋的售价)。该算法假设输入(特征)与输出(目标)之间存在线性关系。基本上,它认为存在一条最佳拟合线,可以准确描述输入和输出变量之间的关系。它使用这个关系来预测未来的数值。在只有一个输入特征的情况下,这条线的方程式为:

图 3.7:线性回归的表达式

图 3.7:线性回归的表达式

其中,

y 是目标输出

c 是 y 轴截距

m 是模型系数

x 是输入特征

类似于神经网络中的连接,输入特征也附带了数值——它们被称为模型系数。在某种程度上,这些模型系数决定了特征在确定输出中的重要性,这类似于神经网络中的权重作用。确保这些模型系数的值正确是非常重要的,以便获得准确的预测。

假设我们想预测房屋的售价,依据是它有多少个卧室。所以,房屋的售价是我们的目标输出,卧室的数量是我们的输入特征。由于这是一个监督学习方法,我们的模型将被提供一个数据集,其中包含输入特征与正确的目标输出的匹配实例。

图 3.8:线性回归的样本数据集

图 3.8:线性回归的样本数据集

现在,我们的线性回归模型需要找到一个模型系数,用来描述卧室数量对房屋售价的影响。它通过使用两种算法——损失函数和梯度下降算法——来实现这一目标。

损失函数

损失函数有时也被称为成本函数。

对于分类问题,损失函数计算特定类别的预测概率与该类别本身之间的差异。例如,假设你有一个二分类问题,需要预测一座房子是否会售出。只有两个输出——“是”和“否”。在拟合这个数据的分类模型时,模型会预测数据实例属于“是”类别或“否”类别的概率。假设“是”类别的值为 1,“否”类别的值为 0。因此,如果输出概率更接近 1,则它会落入“是”类别。该模型的损失函数将衡量这种差异。

对于回归问题,损失函数计算实际值与预测值之间的误差。上一节中的房价例子是一个回归问题,因此损失函数计算的是房子的实际价格与模型预测的价格之间的误差。因此,从某种意义上说,损失函数帮助模型自我评估其性能。显然,模型的目标是预测一个与实际价格完全相同,或者至少最接近的价格。为了做到这一点,它需要尽可能地最小化损失函数。

唯一直接影响模型预测价格的因素是模型系数。为了得到最适合当前问题的模型系数,模型需要不断改进模型系数的值。我们将每个不同的值称为模型系数的更新。因此,随着每次模型系数的更新,模型必须计算实际价格与使用该模型系数更新后的预测价格之间的误差。

一旦该函数达到了最小值,模型系数在此最小点的值被选为最终的模型系数。该值被存储,并在上述线性回归算法的线性方程中使用。从此之后,每当模型接收到房子的卧室数量等输入数据而没有目标输出时,它会使用带有适当模型系数的线性方程来计算并预测这座房子将以多少价格售出。

有许多不同种类的损失函数——例如 MSE(用于回归问题)和 Log Loss(用于分类问题)。让我们来看看它们是如何工作的。

均方误差函数计算实际值与预测值之间的差异,将其平方后,再对整个数据集取平均。该函数用数学表达式表示如下:

图 3.9:均方误差函数的表达式

图 3.9:均方误差函数的表达式

其中,

n 是数据点的总数

yi 是第 i 个实际值

xi 是输入

f() 是对输入执行的函数,用来生成输出,因此

f(xi) 是预测值

对数损失用于输出为 0 到 1 之间概率值的分类模型。预测概率与实际类别之间的差异越大,对数损失越高。对数损失函数的数学表示为:

图 3.10:对数损失函数的表达式

图 3.10:对数损失函数的表达式

其中,

N 是数据点的总数

yi 是第 i 个实际标签

p 是预测概率

梯度下降算法

通过损失函数评估模型性能的过程是由模型独立执行的,更新并最终选择模型系数的过程也是如此。

假设你在一座山上,想要下山并到达真正的底部。天空多云,山峰众多,你无法确切知道底部在哪儿,也不知道应该朝哪个方向走,你只知道你需要到达那里。你从海拔 5000 米的地方开始,决定迈大步。你迈出一步,然后检查手机,看看自己距离海平面有多少米。手机显示你距离海平面 5003 米,说明你走错了方向。现在,你朝另一个方向迈大步,手机显示你距离海平面 4998 米。这意味着你离底部更近了,但你怎么知道这一步是下降最快的那一步呢?如果你朝另一个方向走,发现自己降到了 4996 米呢?因此,你会检查每个可能方向上的位置,选择那个最接近底部的方向。

你不断重复这个过程,直到你的手机显示你位于海拔 100 米的地方。当你再迈出一步时,手机的读数仍然保持不变——海拔 100 米。最终,你到达了一个看起来像是底部的地方,因为从这个点出发的任何方向,都会导致你依然处于海拔 100 米的位置。

图 3.11:更新参数

图 3.11:更新参数

这就是梯度下降算法的工作原理。该算法沿着损失函数与模型系数和截距的可能值的图形下降,就像你在下山一样。它从给定的模型系数值开始——这就像你站在海平面上方 5000 米的某个点。它会计算该点处图形的梯度。这个梯度告诉模型应该朝哪个方向移动,以更新系数,进而接近全局最小值,这也是最终目标。因此,它采取一步,来到了一个新点,拥有了新的模型系数。它重复计算梯度、获取移动方向、更新系数并采取一步的过程。它检查是否这一步提供了最陡的下降。每次它迈出一步,都到达一个新的模型系数,并计算该点的梯度。这个过程会重复,直到梯度的值在多次试验中不再变化。这意味着算法已经达到了全局最小值并且收敛。此时的模型系数会作为线性方程中的最终模型系数。

在神经网络中,梯度下降算法和损失函数共同作用,找到分配给连接的权重和偏置的值。这些值通过最小化损失函数来更新,使用的是梯度下降算法,就像线性回归模型中一样。此外,在线性回归的情况下,由于损失函数是碗形的,因此总是只有一个最小值。这使得梯度下降算法很容易找到它,并且可以确定这是最低点。然而,在神经网络的情况下,情况并不如此简单。神经网络使用的激活函数目的在于引入非线性因素。

因此,神经网络的损失函数图像并不是碗形曲线,并且它不只有一个最小点。相反,它有多个最小值,其中只有一个是全局最小值,其余的被称为局部最小值。这听起来像是一个重大问题,但实际上,梯度下降算法能够达到一个局部最小值并选择该点的权重值是没问题的,因为大多数局部最小值通常离全局最小值非常近。为了设计神经网络,也有一些修改版的梯度下降算法被使用。随机梯度下降和批量梯度下降就是其中的两种。

假设我们的损失函数是均方误差(MSE),并且我们需要梯度下降算法更新一个权重(w)和一个偏置(b)。

图 3.12:损失函数梯度的表达式

图 3.12:损失函数梯度的表达式

梯度是损失函数对权重和偏置的偏导数。其数学表示如下:

图 3.13:损失函数偏导数的梯度表示

图 3.13:损失函数偏导数的梯度表示

这样得到的结果是当前点的损失函数的梯度。这也告诉我们应该朝哪个方向移动,以继续更新权重和偏置。

每一步的步长大小是由一个称为学习率的参数来调整的,它是梯度下降算法中一个非常敏感的参数。它被称为 alpha,并用符号 α 表示。如果学习率太小,算法会采取过多的微小步骤,从而需要很长时间才能达到最小值。然而,如果学习率过大,算法可能会完全错过最小值。因此,调整并测试不同的学习率以确保选择正确的学习率非常重要。

学习率与每一步计算出的梯度相乘,以修改步长的大小,因此每一步的步长不一定相同。数学上,这可以表示为:

图 3.14:学习率与梯度相乘的表达式

图 3.14:学习率与梯度相乘的表达式

以及,

图 3.15:每一步学习率与梯度相乘的表达式

图 3.15:每一步学习率与梯度相乘的表达式

这些值是从之前的权重和偏置值中减去的,因为偏导数指向的是最陡上升的方向,而我们的目标是下降。

图 3.16:学习率

图 3.16:学习率

反向传播

线性回归本质上是一个神经网络,只不过没有隐藏层,并且激活函数是恒等函数(即线性函数,因此是线性的)。因此,学习过程与前面几节描述的相同——损失函数的目标是通过让梯度下降算法不断更新权重,直到达到全局最小值,从而最小化误差。

然而,在处理更大、更复杂的非线性神经网络时,计算出的损失会通过网络反向传播到每一层,然后开始更新权重的过程。损失被反向传播,因此这一过程被称为反向传播(Backpropagation)。

反向传播是通过使用损失函数的偏导数来执行的。它涉及到通过在神经网络中反向传播来计算每个层中每个节点的损失。了解每个节点的损失可以让网络理解哪些权重对输出和损失产生了剧烈的负面影响。因此,梯度下降算法可以减少这些连接的权重,这些连接的错误率较高,从而减少该节点对网络输出的影响。

当处理神经网络中的多层时,许多激活函数作用于输入。这个过程可以表示为如下:

图 3.17:反向传播函数的表达式

图 3.17:反向传播函数的表达式

这里XYZ是激活函数。正如我们所看到的,**f(x)**是一个复合函数,因此,反向传播可以视为链式法则的应用。链式法则是用于计算复合函数偏导数的公式,这正是我们在反向传播过程中所做的。通过将链式法则应用于前述的函数(通常称为前向传播函数,因为数值朝着前向方向流动以生成输出),并计算相对于每个权重的偏导数,我们将能够精确地确定每个节点对最终输出的影响程度。

输出层中最后一个节点的损失是整个神经网络的总损失,因为它位于输出层,因此所有前面节点的损失都会累积到这一层。输入层中的输入节点没有损失,因为它们对神经网络没有影响。输入层仅仅是一个接口,将输入发送到隐藏层中的激活节点。

因此,反向传播过程就是通过梯度下降算法和损失函数来更新权重的过程。

注意

如需了解更多关于反向传播的数学原理,请点击此链接:ml-cheatsheet.readthedocs.io/en/latest/b…

设计神经网络及其应用

训练和设计神经网络时,通常使用一些常见的机器学习技术。神经网络可以被分类为:

  • 有监督神经网络

  • 无监督神经网络

有监督神经网络

这些就像前一节中使用的例子(根据房间数量预测房屋价格)。有监督神经网络是在由样本输入和其对应输出组成的数据集上进行训练的。这些方法适用于噪声分类和预测任务。

有两种类型的监督学习方法:

  • 分类

    这是针对那些目标输出为离散类别或类的问题,例如鸢尾花数据集。神经网络从样本输入和输出中学习如何正确分类新数据。

  • 回归

    这是针对那些目标输出为一系列连续数值的问题,比如房价的例子。神经网络描述了输入与输出之间的因果关系。

无监督神经网络

这些神经网络是在没有任何目标输出的数据上进行训练的,因此能够识别并提取数据中的模式和推断。这使得它们非常适合执行如识别类别关系和发现数据中自然分布等任务。

  • 聚类

聚类分析是将相似的输入分组在一起。这些神经网络可以用于基因序列分析和物体识别等任务。

能够进行模式识别的神经网络可以通过监督学习或无监督学习方法进行训练。它们在文本分类和语音识别中发挥着关键作用。

练习 17:创建神经网络

在这个练习中,我们将实现一个简单的经典神经网络,通过遵循之前概述的工作流程,来预测评论是正面还是负面。

这是一个自然语言处理问题,因为神经网络将接收一行行的句子,这些句子实际上是评论。每个评论在训练集中都有一个标签——0 表示负面,1 表示正面。这个标签依赖于评论中出现的单词,因此我们的神经网络需要理解评论的含义并据此进行标注。最终,我们的神经网络需要能够预测评论是正面还是负面。

注意

从链接下载数据集:

github.com/TrainingByP…

以下步骤将帮助你解决这个问题。

  1. 在你想要编写代码的目录中,输入以下命令来打开一个新的 Jupyter 笔记本:

    jupyter notebook
    
  2. 接下来,导入pandas,以便你可以将数据存储在数据框中:

    import pandas as pd
    df = pd.read_csv('train_comment_small_50.csv', sep=',')
    
  3. 导入正则表达式包

    import re
    
  4. 创建一个函数来预处理评论,去除HTML标签、转义引号和普通引号:

    def clean_comment(text):
        # Strip HTML tags
        text = re.sub('<[^<]+?>', ' ', text)
    
        # Strip escaped quotes
        text = text.replace('\\"', '')
    
        # Strip quotes
        text = text.replace('"', '')
    
        return text
    
  5. 将这个函数应用于当前存储在数据框中的评论:

    df['cleaned_comment'] = df['comment_text'].apply(clean_comment)
    
  6. scikit-learn中导入train_test_split,以便将这些数据分为训练集和验证集:

    from sklearn.model_selection import train_test_split
    X_train, X_test, y_train, y_test = train_test_split(df['cleaned_comment'], df['toxic'], test_size=0.2)
    
  7. nltk库中导入nltkstopwords

    import nltk
    nltk.download('stopwords')
    
  8. 现在,机器学习和深度学习模型要求输入数据为数值型数据,而我们当前的数据是文本形式。因此,我们将使用一种名为 CountVectorizer 的算法,将评论中的单词转换为词频向量。

    from sklearn.feature_extraction.text import CountVectorizer
    from nltk.corpus import stopwords
    
    vectorizer = CountVectorizer(binary=True, stop_words = stopwords.words('english'), lowercase=True, min_df=3, max_df=0.9, max_features=5000)
    X_train_onehot = vectorizer.fit_transform(X_train)
    

    我们的数据现在已经清理并准备好了!

  9. 我们将创建一个两层的神经网络。在定义神经网络时,层数不包括输入层,因为输入层是默认存在的,而且输入层不参与计算过程。因此,一个两层的神经网络包括一个输入层,一个隐藏层和一个输出层。

  10. 从 Keras 导入模型和层:

    from keras.models import Sequential
    from keras.layers import Dense
    
  11. 初始化神经网络:

    nn = Sequential()
    
  12. 添加隐藏层。指定该层的节点数量、节点的激活函数以及该层的输入:

    nn.add(Dense(units=500, activation='relu', input_dim=len(vectorizer.get_feature_names())))
    
  13. 添加输出层。同样,指定节点数量和激活函数。由于这是一个二分类问题(预测评论是正面还是负面),我们将在这里使用sigmoid函数。我们只会有一个输出节点,因为输出只是一个值——要么是 1,要么是 0\。

    nn.add(Dense(units=1, activation='sigmoid'))
    
  14. 现在我们要编译神经网络,并决定使用哪个损失函数、优化算法和性能指标。由于这是一个二分类问题,我们将使用binary_crossentropy作为我们的loss函数。优化算法基本上是梯度下降算法。梯度下降有不同的版本和变体。在这种情况下,我们将使用Adam算法,这是随机梯度下降的扩展:

    nn.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
    
  15. 现在,让我们总结一下我们的模型,看看发生了什么:

    nn.summary()
    

    你将得到的输出将类似于这样:

    图 3.18: 模型摘要

    图 3.18: 模型摘要
  16. 现在,是时候训练模型了。使用我们之前划分的X_trainy_train数据来拟合神经网络:

    nn.fit(X_train_onehot[:-20], y_train[:-20], 
              epochs=5, batch_size=128, verbose=1, 
              validation_data=(X_train_onehot[-100:], y_train[-20:]))
    

    就这样!我们的神经网络现在准备好进行测试了。

  17. 将输入验证数据转换为词频向量并评估神经网络。打印准确率分数,看看网络的表现如何:

    scores = nn.evaluate(vectorizer.transform(X_test), y_test, verbose=1)
    print("Accuracy:", scores[1])
    

    你的分数可能会稍有不同,但应该接近 0.875\。

    这是一个相当不错的分数。所以,就是这样。你刚刚创建了你的第一个神经网络,训练了它,并验证了它。

    预期输出:

    图 3.19: 预期准确率分数

    ](tos-cn-i-73owjymdk6/1a025d43a1564c128c13759146755cf4)

    图 3.19: 预期准确率分数
  18. 保存你的模型:

    model.save('nn.hd5')
    

部署模型作为服务的基础

部署模型作为服务的目的是让其他人能够轻松查看和访问它,而不仅仅是通过查看你在 GitHub 上的代码。根据你创建模型的初衷,模型的部署方式有不同类型。可以说有三种类型——流式模型(一个不断学习的模型,随着不断输入数据并做出预测),分析即服务模型(AaaS——一个供任何人互动的模型)和在线模型(一个只允许公司内部人员访问的模型)。

展示你工作的最常见方式是通过 Web 应用程序。有多个部署平台可以帮助你通过它们部署你的模型,如 Deep Cognition、MLflow 等。

Flask 是最容易使用的微型 Web 框架,可用于在不使用现有平台的情况下部署你自己的模型。它是用 Python 编写的。使用这个框架,你可以为你的模型构建一个 Python API,该 API 将轻松生成预测并为你显示结果。

流程如下:

  1. 为 API 创建一个目录

  2. 将你的预训练神经网络模型复制到这个目录中。

  3. 编写一个程序,加载这个模型,预处理输入数据,使其与模型的训练输入匹配,使用该模型进行预测并准备、发送、显示这个预测结果。

测试和运行 API 时,你只需键入应用程序名称,并加上**.run()**。

在我们创建的神经网络的情况下,我们会保存该模型,并将其加载到一个新的 Jupyter 笔记本中。我们会将输入数据(清洗后的评论)转换为词频向量,以确保 API 的输入数据与训练数据相同。然后,我们会使用模型生成预测并显示它们。

活动 4:评论情感分析

在这个活动中,我们将审查一个数据集中的评论,并将其分类为正面或负面。以下步骤将帮助你完成解决方案。

注意

你可以在以下链接找到数据集:

github.com/TrainingByP…

  1. 打开一个新的Jupyter笔记本。导入数据集。

  2. 导入必要的 Python 包和类。将数据集加载到数据框中。

  3. 导入必要的库来清洗和准备数据。创建一个数组来存储清洗后的文本。使用for循环,遍历每个实例(每条评论)。

  4. 导入 CountVectorizer 并将单词转换为词频向量。创建一个数组来存储每个独特单词作为单独的列,从而使它们成为独立变量。

  5. 导入必要的标签编码实体。

  6. 将数据集划分为训练集和测试集。

  7. 创建神经网络模型。

  8. 训练模型并验证它。

  9. 评估神经网络并打印准确率评分,查看它的表现如何。

    预期输出:

图 3.20:准确率评分

图 3.20:准确率评分
注意

活动的解决方案可以在第 302 页找到。

总结

在这一章中,我们介绍了机器学习的一个子集——深度学习。你了解了这两种技术类别之间的异同,并理解了深度学习的需求及其应用。

神经网络是对人脑中生物神经网络的人工表示。人工神经网络是深度学习模型中所采用的框架,已经证明它们在效率和准确性上不断提高。它们被应用于多个领域,从训练自动驾驶汽车到在非常早期阶段检测癌细胞。

我们研究了神经网络的不同组件,并了解了在损失函数、梯度下降算法和反向传播的帮助下,网络如何进行自我训练和修正。你还学会了如何对文本输入进行情感分析!此外,你还学习了将模型部署为服务的基本知识。

在接下来的章节中,你将了解更多关于神经网络及其不同类型的内容,并学习在不同情况下使用哪种神经网络。

第五章:第四章

卷积神经网络的基础

学习目标

到本章结束时,你将能够:

  • 描述 CNN 在神经科学中的灵感来源

  • 描述卷积操作

  • 描述一个基本的 CNN 架构用于分类任务

  • 实现一个简单的 CNN 用于图像和文本分类任务

  • 实现一个用于文本情感分析的 CNN

本章中,我们旨在涵盖卷积神经网络(CNN)的架构,并通过其在图像数据上的应用来获得对 CNN 的直觉,随后再深入探讨它们在自然语言处理中的应用。

引言

神经网络作为一个广泛的领域,从生物系统,特别是大脑中汲取了很多灵感。神经科学的进展直接影响了神经网络的研究。

CNN 的灵感来源于两位神经科学家的研究,D.H. Hubel 和 T.N. Wiesel。他们的研究集中在哺乳动物的视觉皮层,这是大脑中负责视觉的部分。在上世纪六十年代的研究中,他们发现视觉皮层由多层神经元组成。此外,这些层次是以一种层级结构排列的。这个层级从简单的神经元到超复杂的神经元都有。他们还提出了“感受野”的概念,即某些刺激能够激活或触发一个神经元的空间范围,具有一定的空间不变性。空间或位移不变性使得动物能够识别物体,无论它们是旋转、缩放、变换,还是部分遮挡。

图 4.1:空间变化的示例

图 4.1:空间变化的示例

受到动物视觉神经概念的启发,计算机视觉科学家们构建了遵循局部性、层次性和空间不变性相同原则的神经网络。我们将在下一节深入探讨 CNN 的架构。

CNN 是神经网络的一个子集,包含一个或多个“卷积”层。典型的神经网络是全连接的,意味着每个神经元都与下一层中的每个神经元连接。当处理高维数据(如图像、声音等)时,典型的神经网络运行较慢,并且容易过拟合,因为学习的权重太多。卷积层通过将神经元与低层输入的一个区域连接来解决这个问题。我们将在下一节中更详细地讨论卷积层。

为了理解 CNN 的一般架构,我们将首先将其应用于图像分类任务,然后再应用于自然语言处理。首先,我们将做一个小练习来理解计算机是如何看待图像的。

练习 18:了解计算机如何看待图像

图像和文本有一个重要的相似性。图像中一个像素的位置,或文本中的一个单词位置,都很重要。这种空间上的意义使得卷积神经网络可以同时应用于文本和图像。

在本练习中,我们希望确定计算机如何解读图像。我们将使用 MNIST 数据集,它包含手写数字,非常适合演示 CNN。

注意

MNIST 是一个内置的 Keras 数据集。

你需要安装 Python 和 Keras。为了更方便地可视化,你可以在 Jupyter notebook 中运行代码:

  1. 首先导入必要的类:

    %matplotlib inline
    import keras
    import matplotlib.pyplot as plt
    
  2. 由于我们将在整个章节中使用该数据集,因此我们将按如下方式导入训练集和测试集:

    (X_train, y_train), (X_test, y_test) = keras.datasets.mnist.load_data()
    
  3. 可视化数据集中的第一张图像:

    sample_image = X_train[0]
    plt.imshow(sample_image)
    

    运行前面的代码应该会显示出图像,如下所示:

    图 4.2: 图像的可视化

    图 4.2: 图像的可视化

    这些图像为 28x28 像素,每个像素的值在 0 到 255 之间。尝试修改不同的索引来显示它们的值,如下所示。你可以通过将任意数字在 0255 之间作为 xy 来实现:

    print(sample_image[x][y]) 
    
  4. 当你运行以下打印代码时,应该会看到 0 到 255 之间的数字:

    print(sample_image[22][11])
    print(sample_image[6][12])
    print(sample_image[5][23])
    print(sample_image[10][11])
    

    预期输出:

图 4.3: 图像的数字表示

图 4.3: 图像的数字表示

本练习旨在帮助你理解图像数据如何被处理,其中每个像素作为一个在 0255 之间的数字。这一理解至关重要,因为我们将在下一部分将这些图像输入到 CNN 中作为输入。

理解 CNN 的架构

假设我们有一个任务,将每个 MNIST 图像分类为 0 到 9 之间的数字。前面示例中的输入是一个图像矩阵。对于彩色图像,每个像素是一个包含三个值的数组,分别对应 RGB 颜色模式。对于灰度图像,每个像素仅是一个数字,就像我们之前看到的那样。

要理解 CNN 的架构,最好将其分为两个部分,如下图所示。

CNN 的前向传播涉及在两个部分中进行一系列操作。

图 4.4: 卷积和 ReLU 操作的应用

图 4.4: 卷积和 ReLU 操作的应用

该图在以下部分中解释:

  • 特征提取

  • 神经网络

特征提取

CNN 的第一部分是特征提取。从概念上讲,可以理解为模型尝试学习哪些特征可以区分不同的类别。在图像分类任务中,这些特征可能包括独特的形状和颜色。

CNN 学习这些特征的层次结构。CNN 的低层抽象特征如边缘,而高层则学习更明确的特征,如形状。

特征学习通过重复一系列三个操作进行,如下所示:

  1. 卷积

  2. 激活函数(应用 ReLU 激活函数以实现非线性)

  3. 池化

卷积

卷积是将 CNN(卷积神经网络)与其他神经网络区分开来的操作。卷积操作不仅仅是机器学习中的特有操作,它还广泛应用于其他领域,如电气工程和信号处理。

卷积可以理解为通过一个小窗口查看,当我们将窗口向右和向下移动时。卷积在这个上下文中意味着反复滑动一个“滤波器”跨越图像,同时在移动时应用点积操作。

这个窗口被称为“滤波器”或“卷积核”。在实际操作中,滤波器或卷积核是一个矩阵,通常比输入的尺寸小。为了更好地理解滤波器如何应用于图像,考虑以下示例。在计算滤波器覆盖区域的点积后,我们向右移动一步,再次计算点积:

图 4.5:滤波器应用于图像

图 4.5:滤波器应用于图像

卷积的结果称为特征图或激活图。

滤波器的大小需要定义为超参数。这个大小也可以看作是神经元能够“看到”输入的区域。这个区域被称为神经元的感受野。此外,我们需要定义步幅大小,即在应用滤波器之前需要进行的步数。位于中心的像素相比位于边缘的像素,滤波器会经过多次。为了避免在角落处丢失信息,建议添加一层零填充。

ReLU 激活函数

激活函数在整个机器学习中都被广泛使用。它们有助于引入非线性,使得模型能够学习非线性函数。在这个特定的上下文中,我们应用了修正线性单元ReLU)。它的基本原理是将所有负值替换为零。

以下图像展示了应用 ReLU 后图像的变化。

图 4.6:应用 ReLU 函数后的图像

图 4.6:应用 ReLU 函数后的图像

练习 19:可视化 ReLU

在本练习中,我们将可视化修正线性单元(ReLU)函数。ReLU 函数将在 X-Y 坐标轴上绘制,其中 X 轴是从 -15 到 15 范围内的数字,Y 轴是应用 ReLU 函数后的输出值。此练习的目标是可视化 ReLU。

  1. 导入所需的 Python 包:

    from matplotlib import pyplot
    
  2. 定义 ReLU 函数:

    def relu(x):
        return max(0.0, x)
    
  3. 指定输入和输出参考:

    inputs = [x for x in range(-15, 15)]
    outputs = [relu(x) for x in inputs]
    
  4. 绘制输入与输出的关系:

    pyplot.plot(inputs, outputs) #Plot the input against the output
    pyplot.show()
    

    预期输出:

图 4.7:ReLU 的图形绘制

图 4.7:ReLU 的图形绘制

池化

池化是一个降采样过程,它涉及将数据从更高维度空间减少到更低维度空间。在机器学习中,池化通常用于减少层的空间复杂性。这可以让我们学习更少的权重,从而加快训练速度。

历史上,曾使用不同的技术来执行池化操作,比如平均池化和 L2 范数池化。最常用的池化技术是最大池化。最大池化涉及在定义的窗口大小内取最大的元素。下面是一个对矩阵进行最大池化的例子:

图 4.8:最大池化

](tos-cn-i-73owjymdk6/5fdfc57043b145c281dabfbbe9d21ffe)

图 4.8:最大池化

如果我们对前面的例子应用最大池化,那么包含 2、6、3 和 7 的区域将被缩减为 7。同样,包含 1、0、9 和 2 的区域将被缩减为 9。通过最大池化,我们选择一个区域中的最大值。

Dropout

机器学习中一个常见的问题是过拟合。过拟合发生在模型“记住”了训练数据,并且在测试时面对不同的示例时无法进行泛化。避免过拟合有几种方法,特别是通过正则化:

图 4.9:正则化

图 4.9:正则化

正则化是约束系数趋近于零的过程。正则化可以总结为一种技术,用于惩罚已学习的系数,使它们趋向于零。Dropout 是一种常见的正则化技术,在前向和反向传播过程中,通过随机“丢弃”一些神经元来实现。为了实现 dropout,我们将神经元被丢弃的概率指定为一个参数。通过随机丢弃神经元,我们确保模型能够更好地泛化,因此更加灵活。

卷积神经网络中的分类

CNN 的第二部分更具任务特定性。对于分类任务,这一部分基本上是一个全连接的神经网络。当神经网络中的每个神经元都与下一层的所有神经元连接时,神经网络被认为是全连接的。全连接层的输入是展平后的向量,这个向量是第一部分的输出。展平操作将矩阵转换为 1D 向量。

全连接层中隐藏层的数量是一个超参数,可以优化和微调。

练习 20:创建一个简单的 CNN 架构

在这个练习中,你将使用 Keras 构建一个简单的 CNN 模型。这个练习将包括创建一个包含到目前为止讨论的层的模型。在模型的第一部分,我们将有两个卷积层,使用 ReLU 激活函数,一个池化层和一个 dropout 层。在第二部分,我们将有一个展平层和一个全连接层。

  1. 首先,我们导入必要的类:

    from keras.models import Sequential #For stacking layers
    from keras.layers import Dense, Conv2D, Flatten, MaxPooling2D, Dropout
    from keras.utils import plot_model
    
  2. 接下来,定义所使用的变量:

    num_classes = 10
    
  3. 现在我们来定义模型。Keras 的 Sequential 模型允许你按顺序堆叠层:

    model = Sequential()
    
  4. 现在我们可以添加第一节的层。卷积层和 ReLU 层一起定义。我们有两个卷积层。每个层的卷积核大小都定义为 3。模型的第一层接收输入。我们需要定义它期望输入的结构方式。在我们的案例中,输入是 28x28 的图像形式。我们还需要指定每一层的神经元数量。在我们的案例中,我们为第一层定义了 64 个神经元,为第二层定义了 32 个神经元。请注意,这些是可以优化的超参数:

    model.add(Conv2D(64, kernel_size=3, activation='relu', input_shape=(28,28,1)))
    model.add(Conv2D(32, kernel_size=3, activation='relu'))
    
  5. 然后我们添加一个池化层,接着是一个丢弃层,丢弃层以 25%的概率“丢弃”神经元:

    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.25))
    

    第一节的层已经完成。请注意,层的数量也是一个可以优化的超参数。

  6. 对于第二节,我们首先将输入展平。然后我们添加一个完全连接层或密集层。使用 softmax 激活函数,我们可以计算 10 个类别的每个类别的概率:

    model.add(Flatten())
    model.add(Dense(num_classes, activation='softmax'))
    
  7. 为了可视化到目前为止的模型架构,我们可以按照以下方式打印出模型:

    model.summary()
    

    预期输出

    图 4.10:模型摘要

    图 4.10:模型摘要
  8. 你也可以运行以下代码将图像导出到文件:

    plot_model(model, to_file='model.png')
    

图 4.11:简单 CNN 的可视化架构

图 4.11:简单 CNN 的可视化架构

在前面的练习中,我们创建了一个简单的卷积神经网络(CNN),包含两个卷积层,用于分类任务。在前面的输出图像中,你会注意到这些层是如何堆叠的——从输入层开始,然后是两个卷积层、池化层、丢弃层和展平层,最后是完全连接层。

训练 CNN

在训练 CNN 时,模型尝试学习特征提取中的滤波器权重以及神经网络中完全连接层的权重。为了理解模型是如何训练的,我们将讨论如何计算每个输出类别的概率,如何计算误差或损失,最后,如何优化或最小化该损失,并在更新权重时进行调整:

  1. 概率

    回想一下,在神经网络的最后一层,我们使用了 softmax 函数来计算每个输出类别的概率。这个概率是通过将该类别分数的指数除以所有分数的指数总和来计算的:

    图 4.12:计算概率的表达式

    图 4.12:计算概率的表达式
  2. 损失

    我们需要能够量化计算出的概率如何预测实际类别。这是通过计算损失来实现的,在分类概率的情况下,最好通过类别交叉熵损失函数来完成。类别交叉熵损失函数接受两个向量,预测的类别(我们称之为 y')和实际的类别(称之为 y),并输出整体损失。交叉熵损失是类别概率的负对数似然之和。它可以用 H 函数表示:

    图 4.13:计算损失的表达式
  3. 优化

    考虑以下交叉熵损失的示意图。通过最小化损失,我们可以以更高的概率预测正确的类别:

图 4.14:交叉熵损失与预测概率

图 4.14:交叉熵损失与预测概率

梯度下降是一种优化算法,用于寻找函数的最小值,例如前面描述的损失函数。虽然计算了整体误差,但我们需要回过头来计算每个节点对损失的贡献。因此,我们可以更新权重,以最小化整体误差。反向传播应用了微积分中的链式法则来计算每个权重的更新。这是通过求误差或损失相对于权重的偏导数来完成的。

为了更好地可视化这些步骤,考虑以下图示,概括了这三个步骤。在分类任务中,第一步涉及计算每个输出类别的概率。然后,我们应用损失函数来量化概率预测实际类别的效果。为了在未来做出更好的预测,我们通过梯度下降进行反向传播,更新权重:

图 4.15:分类任务的步骤

图 4.15:分类任务的步骤

练习 21:训练 CNN

在本练习中,我们将训练在练习 20 中创建的模型。以下步骤将帮助您解决这个问题。请记住,这适用于整个分类任务。

  1. 我们首先定义训练的轮数。一个轮次是深度神经网络中的常见超参数。一个轮次表示整个数据集经过完整的前向传播和反向传播。当训练数据量很大时,数据可以分成多个批次:

    epochs=12
    
  2. 回想一下,我们通过运行以下命令导入了 MNIST 数据集:

    (X_train, y_train), (X_test, y_test) = keras.datasets.mnist.load_data()
    
  3. 我们首先重新调整数据以适应模型:

    X_train = X_train.reshape(60000,28,28,1) #60,000 is the number of training examples
    X_test = X_test.reshape(10000,28,28,1)
    
  4. to_categorical 函数将整数向量转换为一热编码的矩阵。给定以下示例,函数返回如下数组:

    #Demonstrating the to_categorical method
    Import numpy as np
    from keras.utils import to_categorical
    example = [1,0,3,2]
    to_categorical(example)
    

    数组将如下所示:

    图 4.16:数组输出

    图 4.16:数组输出
  5. 我们将其应用于目标列,如下所示:

    from keras.utils import to_categorical
    y_train = to_categorical(y_train)
    y_test = to_categorical(y_test)
    
  6. 我们随后将损失函数定义为分类交叉熵损失函数。此外,我们定义优化器和度量标准。Adam(自适应矩估计)优化器是一种经常用于替代随机梯度下降的优化算法。它为模型的每个参数定义了自适应学习率:

    model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
    
  7. 要训练模型,请运行.fit方法:

    model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=epochs)
    

    输出应如下所示:

    图 4.17:训练模型

    图 4.17:训练模型
  8. 要评估模型的性能,您可以运行以下命令:

    score = model.evaluate(X_test, y_test, verbose=0)
    print('Test loss:', score[0])
    print('Test accuracy:', score[1])
    
  9. 对于这个任务,我们预计在若干个周期后会有相当高的准确率:

图 4.18:准确率和损失输出

图 4.18:准确率和损失输出

应用 CNN 到文本

现在我们已经对 CNN 如何在图像中使用有了一般直觉,让我们看看它们如何应用在自然语言处理中。与图像类似,文本具有使其在 CNN 使用中成为理想选择的空间特性。但是,当我们处理文本时,架构上有一个主要变化。文本不再具有二维卷积层,而是一维的,如下所示。

图 4.19:一维卷积

图 4.19:一维卷积

需要注意的是,前述输入序列可以是字符序列或单词序列。在字符级别上应用 CNNs 到文本的应用可以如下图所示。CNN 具有 6 个卷积层和 3 个全连接层,如下所示。

图 4.20:CNN 具有 6 个卷积和 3 个全连接层

图 4.20:CNN 具有 6 个卷积和 3 个全连接层

当应用于大型嘈杂数据时,字符级 CNN 表现良好。它们与单词级应用相比较简单,因为它们不需要预处理(如词干处理),并且字符被表示为一热编码表示。

在以下示例中,我们将演示如何将 CNN 应用于单词级别的文本。因此,在将数据输入 CNN 架构之前,我们需要执行一些向量化和填充操作。

练习 22:将简单的 CNN 应用于 Reuters 新闻主题分类

在这个练习中,我们将应用 CNN 模型到内置的 Keras Reuters 数据集中。

注意

如果您使用 Google Colab,需要通过运行以下命令将您的numpy版本降级到 1.16.2:

!pip install numpy==1.16.1

import numpy as np

由于此版本的numpyallow_pickle的默认值设置为True,因此需要进行此降级。

  1. 首先导入必要的类:

    import keras
    from keras.datasets import reuters
    from keras.preprocessing.text import Tokenizer
    from keras.models import Sequential
    from keras import layers
    
  2. 定义变量:

    batch_size = 32
    epochs = 12
    maxlen = 10000
    batch_size = 32
    embedding_dim = 128
    num_filters = 64
    kernel_size = 5
    
  3. 加载 Reuters 数据集:

    (x_train, y_train), (x_test, y_test) = reuters.load_data(num_words=None, test_split=0.2)
    
  4. 准备数据:

    word_index = reuters.get_word_index(path="reuters_word_index.json")
    num_classes = max(y_train) + 1 
    index_to_word = {}
    for key, value in word_index.items():
        index_to_word[value] = key
    
  5. 对输入数据进行标记化:

    tokenizer = Tokenizer(num_words=maxlen)
    x_train = tokenizer.sequences_to_matrix(x_train, mode='binary')
    x_test = tokenizer.sequences_to_matrix(x_test, mode='binary')
    y_train = keras.utils.to_categorical(y_train, num_classes)
    y_test = keras.utils.to_categorical(y_test, num_classes)
    
  6. 定义模型:

    model = Sequential()
    model.add(layers.Embedding(512, embedding_dim, input_length=maxlen))
    model.add(layers.Conv1D(num_filters, kernel_size, activation='relu'))
    model.add(layers.GlobalMaxPooling1D())
    model.add(layers.Dense(10, activation='relu'))
    model.add(layers.Dense(num_classes, activation='softmax'))
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
    
  7. 训练和评估模型。打印准确率分数:

    history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_split=0.1)
    score = model.evaluate(x_test, y_test, batch_size=batch_size, verbose=1)
    print('Test loss:', score[0])
    print('Test accuracy:', score[1])
    

    预期输出

图 4.21:准确率分数

图 4.21:准确率分数

因此,我们已经创建了一个模型,并在数据集上进行了训练。

CNN 的应用领域

现在我们已经了解了 CNN 的架构,让我们来看看一些应用。通常,CNN 非常适合具有空间结构的数据。具有空间结构的数据类型示例包括声音、图像、视频和文本。

在自然语言处理领域,CNNs 被用于各种任务,如句子分类。一个例子是情感分类任务,其中一个句子被分类为属于预定的类别之一。

如前所述,CNNs 被应用于字符级别的分类任务,如情感分类,尤其是在社交媒体帖子等嘈杂数据集上。

CNNs 更常应用于计算机视觉。以下是该领域的一些应用:

  • 面部识别

    大多数社交网络网站使用 CNN 来检测面部并随后执行诸如标签标注等任务。

图 4.22:面部识别

图 4.22:面部识别
  • 物体检测

    类似地,CNN 能够在图像中检测物体。有几种基于 CNN 的架构用于检测物体,其中最流行的之一是 R-CNN。(R-CNN 代表区域卷积神经网络。)R-CNN 的工作原理是应用选择性搜索来生成区域,然后使用 CNN 进行分类,每次处理一个区域。

图 4.23:物体检测

](tos-cn-i-73owjymdk6/c8df08f5f55948858d69ff0293e0249a)

图 4.23:物体检测
  • 图像标注

    该任务涉及为图像创建文本描述。执行图像标注的一种方式是将第二部分的全连接层替换为递归神经网络(RNN)。

图 4.24:图像标注

](tos-cn-i-73owjymdk6/656f485a0fb8477c95ef49433df1e837)

图 4.24:图像标注
  • 语义分割

    语义分割是将图像划分为更有意义部分的任务。图像中的每个像素都会被分类为属于某个类别。

图 4.25:语义分割

图 4.25:语义分割

一种可以用于执行语义分割的架构是全卷积网络FCN)。FCN 的架构与之前的架构在两方面略有不同:它没有全连接层,并且具有上采样。上采样是将输出图像放大到与输入图像大小相同的过程。

这是一个示例架构:

图 4.26:语义分割的示例架构

图 4.26:语义分割的示例架构
注意

想了解更多关于 FCN 的信息,请参考 Jonathan Long、Evan Shelhamer 和 Trevor Darrell 的论文《Fully Convolutional Networks for Semantic Segmentation》。

活动 5:在实际数据集上进行情感分析

假设你被要求创建一个模型来分类数据集中的评论。在本活动中,我们将构建一个执行情感分析二分类任务的 CNN。我们将使用来自 UCI 数据集库的实际数据集。

注意

该数据集可从 archive.ics.uci.edu/ml/datasets… 下载

从组标签到个体标签,基于深度特征,Kotziaa 等,KDD 2015 UCI 机器学习库 [archive.ics.uci.edu.ml]。加利福尼亚州欧文市:加利福尼亚大学信息与计算机科学学院

你也可以通过我们的 GitHub 仓库链接下载它:

github.com/TrainingByP…

以下步骤将帮助你解决问题。

  1. 下载 Sentiment Labelled Sentences 数据集。

  2. 在你的工作目录中创建一个名为 'data' 的文件夹,并将下载的文件夹解压到该目录中。

  3. 在 Jupyter Notebook 上创建并运行你的工作脚本(例如,sentiment.ipynb)。

  4. 使用 pandas 的 read_csv 方法导入数据。可以随意使用数据集中的一个或所有文件。

  5. 使用 scikit-learn 的 train_test_split 将数据拆分为训练集和测试集。

  6. 使用 Keras 的 tokenizer 进行分词。

  7. 使用 texts_to_sequences 方法将文本转换为序列。

  8. 通过填充确保所有序列具有相同的长度。你可以使用 Keras 的 pad_sequences 函数。

  9. 定义模型,至少包含一层卷积层和一层全连接层。由于这是二分类问题,我们使用 sigmoid 激活函数,并通过二元交叉熵损失函数计算损失。

  10. 训练和测试模型。

    注意

    该活动的解决方案可以在第 305 页找到。

    预期输出:

图 4.27:准确度分数

图 4.27:准确度分数

摘要

在本章中,我们学习了卷积神经网络(CNN)的架构和应用。CNN 不仅应用于文本和图像,还应用于具有某种空间结构的数据集。在接下来的章节中,你将探索如何将其他形式的神经网络应用于各种自然语言任务。