机器人的人工视觉和语言处理(一)
原文:
annas-archive.org/md5/f77f73a89c331afcaea234799b391074译者:飞龙
第一章:前言
关于
本节简要介绍了作者、本书的内容、你开始时所需的技术技能,以及完成所有包括的活动和练习所需的硬件和软件要求。
本书介绍
机器人学中的人工视觉与语言处理 书籍首先讨论了机器人的理论。你将比较不同的机器人工作方法,探索计算机视觉、其算法和局限性。然后你将学习如何通过自然语言处理命令来控制机器人。在这本书的过程中,你将学习 Word2Vec 和 GloVe 嵌入技术、非数值数据以及循环神经网络(RNN)及其高级模型。你将使用 Keras 创建一个简单的 Word2Vec 模型,构建一个卷积神经网络(CNN),并通过数据增强和迁移学习来改进它。你将学习 ROS 并构建一个对话代理来管理你的机器人。你还将把你的代理与 ROS 集成,并将图像转换为文本,文本转换为语音。你将学习如何借助视频剪辑构建一个物体识别系统。
本书结束时,你将具备构建一个功能性应用程序的技能,该应用程序可以与 ROS 集成,从你的环境中提取有用的信息。
作者介绍
Álvaro Morena Alberola 是一名计算机工程师,热爱机器人技术和人工智能。目前,他正在担任软件开发人员。他对 AI 的核心部分——人工视觉非常感兴趣。Álvaro 喜欢使用新技术并学习如何利用先进工具。他认为机器人技术是改善人类生活的一种方式,是帮助人们完成他们自己无法完成的任务的一种手段。
Gonzalo Molina Gallego 是一名计算机科学毕业生,专注于人工智能和自然语言处理。他有丰富的文本对话系统工作经验,创建过对话代理,并提供过良好的方法论建议。目前,他正在研究混合领域对话系统的新技术。Gonzalo 认为对话用户界面是未来的发展趋势。
Unai Garay Maestre 是一名计算机科学毕业生,专注于人工智能和计算机视觉领域。他曾成功地为 2018 年 CIARP 会议贡献了一篇论文,提出了使用变分自编码器的新数据增强方法。他还从事机器学习开发工作,使用深度神经网络处理图像。
目标
-
探索 ROS 并构建一个基本的机器人系统
-
使用 NLP 技术识别对话意图
-
学习并使用 Word2Vec 和 GloVe 的词嵌入
-
使用深度学习实现人工智能(AI)和物体识别
-
开发一个简单的物体识别系统,使用 CNN
-
将 AI 与 ROS 集成,使你的机器人能够识别物体
读者群体
人工视觉和语言处理技术在机器人学中的应用 针对想要学习如何集成计算机视觉和深度学习技术以创建完整机器人系统的机器人工程师。如果你具备 Python 的工作知识和深度学习背景,那将会很有帮助。对 ROS 的了解是一个加分项。
方法
人工视觉和语言处理技术在机器人学中的应用 采用实用方法,为你提供了创建集成计算机视觉和自然语言处理控制机器人系统的工具。本书分为三个部分:自然语言处理、计算机视觉和机器人学。它在详细介绍基础知识后引入高级主题。书中还包含多个活动,供你在高度相关的背景下练习和应用你的新技能。
最低硬件要求
为了最佳的学生体验,我们建议以下硬件配置:
-
处理器:2GHz 双核处理器或更好
-
内存:8 GB RAM
-
存储空间:5 GB 可用硬盘空间
-
良好的互联网连接
为了训练神经网络,我们建议使用 Google Colab。但如果你想用自己的计算机训练这些网络,你需要:
- NVIDIA GPU
软件要求
不推荐在本书中使用 Ubuntu 16.04,因为它与 ROS Kinetic 存在兼容性问题。但如果你想使用 Ubuntu 18.04,有一个 ROS 支持的版本,名为 Melodic。在项目进行过程中,你需要安装几个库以完成所有练习,如 NLTK (<= 3.4)、spaCy (<=2.0.18)、gensim (<=3.7.0)、NumPy (<=1.15.4)、sklearn (<=0.20.1)、Matplotlib (<=3.0.2)、OpenCV (<=4.0.0.21)、Keras (<=2.2.4) 和 Tensorflow (<=1.5, >=2.0)。每个库的安装过程在练习中有详细说明。
要在 Ubuntu 系统中使用 YOLO,你需要安装你的 GPU 的 NVIDIA 驱动程序和 NVIDIA CUDA 工具包。
约定
文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟的 URL、用户输入和 Twitter 句柄显示如下:"使用 TfidfVectorizer 方法,我们可以将语料库中的文档集合转换为 TF-IDF 特征矩阵"
代码块如下所示:
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus)
新术语和重要词汇以粗体显示。在屏幕上看到的词汇,例如菜单或对话框中的内容,会在文本中这样显示:"形态分析:专注于分析句子中的词和其形态素"
安装和设置
在你开始本书之前,你需要安装以下软件。你可以在这里找到安装步骤:
安装 Git LFS
为了从本书的 GitHub 下载所有资源并能够使用图像来训练你的神经网络模型,你需要安装 Git LFS(Git Large File Storage)。它用 Git 内部的文本指针替换大文件,如音频样本、视频、数据集和图形。
如果你还没有克隆存储库:
-
安装 Git LFS
-
克隆 Git 仓库
-
从仓库文件夹执行
gitlfs pull -
完成
如果仓库已经克隆:
-
安装 Git LFS
-
从仓库文件夹执行:
gitlfs pull -
完成
安装 Git LFS:github.com/git-lfs/git-lfs/wiki/Installation
[推荐] Google Colaboratory
如果可能,使用 Google Colaboratory。它是一个免费的 Jupyter notebook 环境,无需设置,完全运行在云端。你还可以利用 GPU 来运行它。
使用它的步骤如下:
-
将整个 GitHub 上传到你的 Google Drive 账户中,这样你就可以使用存储在仓库中的文件。确保你先使用 Git LFS 加载了所有文件。
-
前往你想打开新 Google Colab Notebook 的文件夹,点击“新建”>“更多”>“Colaboratory”。现在,你有一个已打开并保存在相应文件夹中的 Google Colab Notebook,你可以开始使用 Python、Keras 或任何已安装的库。
-
如果你想安装特定的库,你可以使用“pip”包安装或其他命令行安装,并在开头加上“!”。例如,“!pip install sklearn”将安装 scikit-learn。
-
如果你想从 Google Drive 加载文件,你需要在 Google Colab 单元格中执行以下两行代码:
from google.colab import drive drive.mount(‘drive’) -
然后,打开输出中出现的链接,并使用你创建 Google Colab Notebook 时所用的 Google 账户登录。
-
现在,你可以使用
ls列出当前目录中的文件,并使用cd导航到特定的文件夹,以便找到上传的文件位置。 -
现在,Google Colab Notebook 可以像在该文件夹中打开的 Jupyter notebook 一样加载任何文件并执行任何任务。
安装 ROS Kinetic
这是你必须遵循的步骤,以在你的 Ubuntu 系统上安装该框架:
-
准备 Ubuntu 接受 ROS 软件:
sudosh -c ‘echo “deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main” > /etc/apt/sources.list.d/ros-latest.list’ -
配置下载密钥:
sudo apt-key adv --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-key 421C365BD9FF1F717815A3895523BAEEB01FA116 -
确保系统已更新:
sudo apt-get update -
安装完整框架,以免遗漏功能:
sudo apt-get install ros-kinetic-desktop-full -
初始化并更新
rosdep:sudo rosdep init rosdep update -
如果你不想每次工作时都声明环境变量,可以将它们添加到
bashrc文件中:echo “source /opt/ros/kinetic/setup.bash” >> ~/.bashrcsource ~/.bashrc注意
完成此过程后,可能需要重启计算机以使系统应用新的配置。
-
通过启动框架检查它是否正常工作:
roscore
配置 TurtleBot
注意
可能会出现 TurtleBot 与你的 ROS 发行版不兼容的情况(我们使用的是 Kinetic Kame),但不用担心,Gazebo 中有很多机器人可以模拟。你可以查找不同的机器人并尝试在你的 ROS 发行版中使用它们。
这是 TurtleBot 的配置过程:
-
安装其依赖项:
sudo apt-get install ros-kinetic-turtlebotros-kinetic-turtlebot-apps ros-kinetic-turtlebot-interactions ros-kinetic-turtlebot-simulator ros-kinetic-kobuki-ftdiros-kinetic-ar-track-alvar-msgs -
在你的
catkin工作空间中下载 TurtleBot 模拟器包:cd ~/catkin_ws/src git clone https://github.com/turtlebot/turtlebot_simulator -
之后,您应该能够在 Gazebo 中使用 TurtleBot。
如果在 Gazebo 中尝试可视化 TurtleBot 时遇到错误,请从我们的 GitHub 下载
turtlebot_simulator文件夹并替换它。启动 ROS 服务:
roscore启动 TurtleBot World:
cd ~/catkin_ws catkin_make sourcedevel/setup.bash roslaunchturtlebot_gazeboturtlebot_world.launch
Darknet 基本安装
按照以下步骤安装 Darknet:
-
下载框架:
git clone https://github.com/pjreddie/darknet -
切换到下载的文件夹并运行编译命令:
cd darknet make如果编译过程正确完成,您应该看到类似以下的输出:
Darknet 编译输出
Darknet 高级安装
这是您必须完成的安装过程,以实现章节目标。这将允许您使用 GPU 计算实时检测和识别物体。在执行此安装之前,您必须在 Ubuntu 系统上安装一些依赖项,如下所示:
-
NVIDIA 驱动程序:这些驱动程序将使您的系统能够正确地与 GPU 配合工作。正如您所知,它必须是 NVIDIA 型号。
-
CUDA:这是一个 NVIDIA 工具包,为构建需要 GPU 使用的应用程序提供开发环境。
-
OpenCV:这是一个免费的人工视觉库,非常适合处理图像。
注意
需要注意的是,所有这些依赖项都有多个版本。您必须找到与您的特定 GPU 和系统兼容的每个工具的版本。
一旦系统准备就绪,您可以执行高级安装:
-
如果您还没有完成基本安装,请下载框架:
git clone https://github.com/pjreddie/darknet -
修改 Makefile 文件的前几行以启用 OpenCV 和 CUDA,内容应该如下:
GPU=1 CUDNN=0 OPENCV=1 OPENMP=0 DEBUG=0 -
保存 Makefile 修改,切换到
darknet目录并运行编译命令:cd darknet make现在,您应该看到类似以下的输出:
Darknet 编译带有 CUDA 和 OpenCV
安装 YOLO
在执行此安装之前,您必须在 Ubuntu 系统上安装一些依赖项,如 Darknet 的高级安装 部分所述。
注意
需要考虑的是,所有这些依赖项都有多个版本。您必须找到与您的特定 GPU 和系统兼容的每个工具的版本。
一旦系统准备好,您可以执行高级安装:
-
下载框架:
git clone https://github.com/pjreddie/darknet -
修改 Makefile 文件的前几行以启用 OpenCV 和 CUDA,内容应该如下:
GPU=1 CUDNN=0 OPENCV=1 OPENMP=0 DEBUG=0 -
保存 Makefile 修改,切换到 darknet 目录并运行编译命令:
cd darknet Make
额外资源
本书的代码包也托管在 GitHub 上,地址为:github.com/PacktPublishing/Artificial-Vision-and-Language-Processing-for-Robotics。
我们还有其他代码包,来自我们丰富的书籍和视频目录,可以在github.com/PacktPublishing/找到。快来看看吧!
文档链接:
ROS Kinetic - wiki.ros.org/kinetic/Installation
Git 大文件存储 - git-lfs.github.com/
第二章:第一章
机器人学基础
学习目标
在本章结束时,你将能够:
-
描述机器人学历史中的重要事件
-
解释使用人工智能、人工视觉和自然语言处理的重要性
-
根据目标或功能对机器人进行分类
-
识别机器人的各个部分
-
使用里程计估算机器人的位置
本章概述了机器人学的简短历史,分类了不同类型的机器人及其硬件,并解释了如何使用里程计来找到机器人的位置。
介绍
机器人学领域代表着人类的现在和未来。目前,工业部门、研究实验室、大学,甚至我们的家庭中都存在机器人。机器人学学科正在不断发展,这也是它值得学习的原因之一。每个机器人都需要有人为其编程。即使是基于人工智能和自我学习的机器人,也需要设置初始目标。发生故障的机器人需要技术人员进行维护,基于人工智能的系统也需要持续的数据输入和监控才能有效运作。
在本书中,你将学习和实践许多有趣的技术,重点是人工计算机视觉、自然语言处理,以及与机器人和模拟器的互动。这将为你在机器人学的一些前沿领域打下坚实的基础。
机器人学的历史
机器人学源于创造智能机器以执行人类难以完成的任务的需求。但最初并没有被称为“机器人学”。“机器人”这一术语由捷克作家卡雷尔·恰佩克在他的作品R.U.R.(罗索姆的万能机器人)中创造。它源自捷克语单词robota,意为奴役,指的是强迫劳动。
Čapek 的作品在全球广为人知,"机器人"一词也因此变得家喻户晓,以至于著名的教师和作家艾萨克·阿西莫夫在他的作品中也使用了这个术语;他将机器人学定义为研究机器人及其特征的科学。
这里你可以看到塑造机器人历史的重要事件的时间线:
图 1.1:机器人历史
图 1.2:机器人历史(续)
图 1.1 和 1.2 提供了一个有用的时间线,展示了机器人学的起源和发展。
人工智能
人工智能指的是一系列旨在赋予机器与人类相同能力的算法。它使机器人能够做出自己的决策、与人类互动,并识别物体。这种智能不仅存在于机器人中,还广泛应用于许多其他领域和系统中(尽管人们可能并未察觉到它)。
已经有许多现实世界中的产品正在使用这种技术。这里列出了一些示例,展示了你可以构建的有趣应用程序:
-
Siri:这是由苹果公司创建的语音助手,内置于他们的手机和平板电脑中。Siri 非常实用,因为它连接到互联网,可以即时查找数据、发送信息、查看天气等,功能非常丰富。
-
Netflix:Netflix 是一个在线视频影视服务。它基于用户观看历史,通过 AI 开发的非常准确的推荐系统向用户推荐电影。例如,如果用户通常观看浪漫电影,系统将推荐浪漫剧集和电影。
-
Spotify:Spotify 是一个类似于 Netflix 的在线音乐服务。它使用推荐系统,根据用户的听歌历史和库中添加的音乐类型,向用户精准推荐歌曲。
-
特斯拉的自动驾驶汽车:这些汽车使用 AI 技术,能够检测障碍物、人类以及交通信号,以确保乘客安全出行。
-
Pacman:像几乎所有其他视频游戏一样,Pacman 的敌人是通过 AI 编程的。它们使用一种特定的技术,不断计算碰撞距离,考虑到墙壁边界,并试图困住 Pacman。由于这是一个非常简单的游戏,算法并不复杂,但它很好地展示了 AI 在娱乐中的重要性。
自然语言处理
自然语言处理(NLP)是人工智能的一个专业领域,涉及研究使人类与机器之间实现沟通的各种方式。它是唯一能够使机器人理解和再现人类语言的技术。
如果用户使用的是一款本应具备交流能力的应用程序,用户便会期望该应用能进行类人对话。如果类人机器人使用语句不规范或没有给出与问题相关的答案,用户体验将不佳,机器人也不会成为吸引人的购买选项。因此,理解并充分利用自然语言处理(NLP)在机器人学中的重要性不言而喻。
让我们来看看一些使用自然语言处理的现实应用:
-
Siri:苹果的语音助手 Siri 使用自然语言处理来理解用户的语音,并给出有意义的回应。
-
Cortana:这是微软创建的另一个语音助手,集成在 Windows 10 操作系统中。它的工作方式与 Siri 相似。
-
Bixby:Bixby 是三星公司的一部分,集成在最新的三星手机中,用户体验与使用 Siri 或 Cortana 类似。
注意事项
你可能会问这三者中哪个最好;然而,这取决于每个用户的喜好和需求。
-
电话客服:如今,客户服务电话通常由自动语音应答系统接听。这些系统大多数是通过接收关键词输入的电话运营商。现代的电话运营商大多使用自然语言处理技术,能够与客户进行更真实的电话对话。
-
Google Home:Google 的虚拟家居助手使用自然语言处理技术来回答用户的问题并执行指定的任务。
计算机视觉
计算机视觉是机器人技术中常用的一种技术,它可以使用不同的摄像头模拟人眼的生物力学三维运动。可以定义为一套用于获取、分析和处理图像的方法,并将其转化为计算机可以处理的有价值信息。这意味着收集到的信息会转化为数字数据,以便计算机可以进行处理。这将在接下来的章节中介绍。
这里列出了一些使用计算机视觉的现实世界例子:
-
自动驾驶汽车:自动驾驶汽车使用计算机视觉来获取交通和环境信息,并根据这些信息决定接下来的行动。例如,如果汽车的摄像头捕捉到行人穿过,它就会停车。
-
手机相机应用:许多手机上的相机应用程序包括能修改拍摄照片的效果。例如,Instagram允许用户在实时中使用滤镜,通过将用户的面部与滤镜映射来修改图像。
-
网球鹰眼:这是一种基于计算机的视觉系统,用于网球比赛中追踪球的轨迹,并在场地上显示其最可能的路径。它被用来检查球是否在场地边界内弹跳。
机器人的类型
讨论人工智能和自然语言处理时,了解现实世界中的机器人非常重要,因为这些机器人可以给你一个关于现有模型发展和改进的清晰印象。但首先,让我们来讨论一下我们能找到的不同类型的机器人。通常,它们可以分为工业机器人和服务机器人,我们将在接下来的章节中进行讨论。
工业机器人
工业机器人用于制造过程,通常没有人形。一般来说,它们看起来很像其他机器。这是因为它们的设计目的是执行特定的工业任务。
服务机器人
服务机器人以部分或完全自主的方式工作,执行对人类有用的任务。这些机器人还可以进一步分为两类:
-
个人机器人:这些机器人通常用于繁琐的家务清洁任务,或娱乐行业。这是人们在讨论机器人时常常想象的那种机器,它们通常被想象成具有类人特征的机器人。
-
田间机器人:这些机器人负责军事和探索任务。它们使用耐用材料建造,因为它们必须承受严酷的阳光和其他外部天气因素。
在这里你可以看到一些现实世界中个人机器人的例子:
-
Sophia:这是由汉森机器人公司创造的人形机器人。它的设计目的是与人类共同生活并向他们学习。
-
Roomba:这是由 iRobot 公司生产的清洁机器人。它由一个带轮子的圆形底座组成,可以在房子里移动,同时计算出最有效的方式来覆盖整个区域。
-
Pepper:Pepper 是由 SoftBank Robotics 设计的社交机器人。虽然它具有人形,但并不像人类那样双足行走。它还配备有一个轮式底座,提供良好的移动性。
机器人硬件和软件
就像任何其他计算机系统一样,机器人由硬件和软件组成。机器人所使用的硬件和软件将取决于机器人的用途和设计它的开发者。然而,有一些硬件组件是多个机器人中较为常见的,我们将在本章中讨论这些组件。
首先,让我们来看一下每个机器人都有的三种组件:
-
控制系统:控制系统是机器人的核心组件,负责连接所有需要控制的其他组件。它通常是一个微控制器或微处理器,其性能取决于机器人本身。
-
执行器:执行器是机器人一部分,使其能够改变外部环境,例如用于移动整个机器人或机器人某部分的电机,或者用于发出声音的扬声器。
-
传感器:这些组件负责获取信息,使机器人能够根据这些信息产生期望的输出。这些信息可以与机器人的内部状态或外部环境有关。基于此,传感器分为以下几种类型:
-
内部传感器:其中大多数用于测量机器人的位置,因此你通常会在这些机器人身体内部找到它们。以下是一些机器人可以使用的内部传感器:
光电传感器:这些是能够检测任何穿越传感器内部凹槽物体的传感器。
编码器:编码器是一种可以将微小运动转化为电信号的传感器。这个信号随后被控制系统用来执行多种操作。例如,电梯中的编码器可以通知控制系统电梯是否到达正确楼层。通过计数编码器自转的次数,可以知道编码器提供的功率。这是将运动转化为一定能量的过程。
信标和 GPS 系统:信标和 GPS 系统是用于估算物体位置的传感器。GPS 系统能够成功完成此任务,得益于它们从卫星获得的信息。
-
外部传感器:这些传感器用于获取机器人周围环境的数据。它们包括接近传感器、接触传感器、光线传感器、颜色传感器、反射传感器和红外传感器。
以下图示展示了机器人内部结构的图示:
图 1.3:机器人部件示意图
为了更好地理解前面的示意图,我们将看到每个组件在模拟情况下是如何工作的。假设一个机器人接到命令,从 A 点移动到 B 点:
图 1.4:机器人从 A 点开始移动
机器人使用内部传感器GPS,不断检查自身的位置,并检查是否已到达目标点。GPS 计算坐标并将其发送到控制系统,控制系统将处理这些数据。如果机器人尚未到达 B 点,控制系统会指示执行器继续前进。这个过程在下图中表示:
图 1.5:机器人正在完成从 A 到 B 的路径
另一方面,如果 GPS 发送到控制系统的坐标与 B 点匹配,控制系统将指示执行器完成过程,之后机器人将停止移动:
图 1.6:路径结束!机器人到达 B 点
机器人定位
通过使用前面一节提到的内部传感器之一,我们可以计算机器人在一定位移后的位置。这种计算叫做里程计,可以通过编码器及其提供的信息来完成。讨论这一技术时,需要记住主要的优点和缺点:
-
优点:它可以在没有外部传感器的情况下计算机器人的位置,这样可以使机器人的设计成本大大降低。
-
缺点:最终的位置计算并不完全准确,因为它依赖于地面和轮子的状态。
现在,让我们一步一步地看一下如何进行这种计算。假设我们有一台两轮移动的机器人,我们将按如下步骤进行:
-
首先,我们需要计算轮子行驶的距离,这可以通过使用从发动机的编码器中提取的信息来完成。在一个两轮机器人中,一个简单的示意图可能是这样的:
图 1.7:两轮机器人运动的示意图
左轮行驶的距离是图 1.6 中标记为 DL 的虚线,而 DR 代表右轮。
-
为了计算轮轴中心点的线性位移,我们需要第一步中计算的信息。使用相同的简单示意图,Dc 将是距离:
注意
如果你在使用多轴轮时,首先应该研究轴的分布,然后计算每个轴的行驶距离。
图 1.8:两轮机器人运动示意图 (2)
-
要计算机器人的旋转角度,我们需要在第一步中获得的最终计算结果。这里我们所指的角度是 α:
图 1.9:两轮机器人运动示意图 (3)
如图所示,在这种情况下,α 将是 90º,这意味着机器人已经旋转了一定的角度。
-
一旦你获得了所有信息,就可以进行一系列的计算(将在下一节中讨论)来得到最终位置的坐标。
练习 1:计算机器人的位置
在这个练习中,我们使用之前的过程来计算两轮机器人在运动一定时间后的位置信息。首先,让我们考虑以下数据:
-
轮径 = 10 cm
-
机器人底座长度 = 80 cm
-
每圈编码器计数 = 76
-
每 5 秒钟左编码器的计数 = 600
-
每 5 秒钟右编码器的计数 = 900
-
初始位置 = (0, 0, 0)
-
移动时间 = 5 秒
注意
每圈编码器计数是我们用来计算编码器每圈在其轴上完成一次旋转所产生的能量的单位。例如,在上面提供的信息中,左编码器在 5 秒内完成 600 次计数。我们也知道,编码器完成一圈需要 76 次计数。因此,我们可以推断,在 5 秒内,编码器将完成 7 圈(600/76)。这样,如果我们知道 1 圈所产生的能量,就可以计算出 5 秒内所产生的能量。
对于初始位置,第一个和第二个数字表示 X 和 Y 坐标,最后一个数字表示机器人的旋转角度。这些数据是相对的,你需要想象坐标轴的起点。
现在,让我们按照以下步骤进行:
-
让我们计算每个车轮的完成距离。我们首先计算每个编码器在运动过程中执行的计数次数。这可以通过将总运动距离除以给定的编码器时间,并乘以每个编码器的计数次数来轻松计算:
(移动时间 / 编码器时间) * 左编码器计数:
(5 / 5) * 600 = 600 次计数
(移动时间 / 编码器时间) * 右编码器计数:
(5 / 5) * 900 = 900 次计数
计算出这个值后,我们可以利用这些数据来获取总距离。由于车轮是圆形的,我们可以通过以下方式计算每个车轮的已完成距离:
[2πr / 每圈编码器计数] * 总左编码器计数:
(10π/76) * 600 = 248.02 cm
[2πr / 每圈编码器计数] * 总右编码器计数:
(10π/76) * 900 = 372.03 cm
-
现在计算轮轴中心点的线性位移。这可以通过一个简单的计算来完成:
(左轮距离 + 右轮距离) / 2:
(248.02 + 372.03) / 2 = 310.03 cm
-
计算机器人的旋转角度。为此,你可以计算每个轮子完成的距离差,并将其除以底盘长度:
(右轮距离 - 左轮距离)/ 底盘长度:
(372.03 - 248.02) / 80 = 1.55 弧度
-
最后,我们可以通过分别计算每个组件来得出最终位置。以下是计算每个组件所需使用的公式:
最终 x 位置 = 初始 x 位置 + (轮子轴位移 * 旋转角度余弦):
0 + (310.03 * cos (1.55)) = 6.45
最终 y 位置 = 初始 y 位置 + (轮子轴位移 * 旋转角度余弦):
0 + (310.03 * sin (1.55)) = 309.96
机器人最终旋转 = 初始机器人旋转 + 机器人旋转角度:
0 + 1.55 = 1.55
所以,在这个过程之后,我们可以得出结论,机器人从 (0, 0, 0) 移动到了 (6.45, 309.96, 1.55)。
如何与机器人合作
就像任何其他软件开发一样,为机器人实现应用程序和程序的过程有许多不同的方法。
在接下来的章节中,我们将使用一些框架和技术,使我们能够抽象出具体问题,并开发出一个容易适应各种机器人和设备的解决方案。在本书中,我们将使用机器人操作系统(ROS)来实现这一目标。
在我们开始与机器人合作之前,另一个需要考虑的问题是使用哪种编程语言。你肯定知道并且已经使用过一些编程语言,那么哪种语言最合适呢?这个问题的真实答案是没有特定的语言;它总是取决于眼前的问题。但是在我们的书中,由于我们将要进行的活动类型,我们将使用 Python。正如你可能知道的,它是一种解释型、高级、通用的编程语言,广泛用于 AI 和机器人技术。
通过使用 Python,就像其他编程语言一样,你可以开发你希望机器人具备的任何功能。例如,你可以给机器人编程,让它在检测到人时进行简单的问候。你也可以编程一个更复杂的功能,比如当机器人“听到”音乐时跳舞。
现在我们将通过一些练习和活动,向你介绍 Python 在机器人中的应用,如果你之前没有使用过它的话。
练习 2:使用 Python 计算轮子行驶的距离
在本练习中,我们将实现一个简单的 Python 函数,用于计算轮子行驶的距离,使用我们在练习 1中执行的相同过程,计算机器人的位置。以下是需要遵循的步骤:
-
导入所需资源。在本例中,我们将使用 π(圆周率):
from math import pi -
创建带有参数的函数。为了计算这个距离,我们需要以下内容:
轮子的直径(以厘米为单位)
每圈的编码器计数
用来测量编码器计数的秒数
给定时间内的轮子编码器计数
总的移动时间
这是函数定义:
def wheel_distance(diameter, encoder, encoder_time, wheel, movement_time): -
从函数的实现开始。首先,计算编码器测量的距离:
time = movement_time / encoder_time wheel_encoder = wheel * time -
将上面获得的距离转换为我们预期的距离,即轮子行驶的距离:
wheel_distance = (wheel_encoder * diameter * pi) / encoder -
返回最终值:
return wheel_distance -
最后,你可以通过向函数传递值并进行相应的手动计算来检查函数是否正确实现:
wheel_distance(10, 76, 5, 400, 5)这个函数调用应该返回
165.34698176788385。
你的笔记本中的输出应该像这样:
图 1.10:轮子最终覆盖的距离
练习 3:使用 Python 计算最终位置
在本练习中,我们使用 Python 来计算机器人最终位置,给定机器人的初始位置、轴完成的距离和旋转角度。你可以按照以下过程来实现:
-
导入正弦和余弦函数:
from math import cos, sin -
使用所需的参数定义该函数:
机器人初始位置(坐标)
机器人中央轴完成的距离
从初始点的角度变化:
def final_position(initial_pos, wheel_axis, angle):通过编写 练习 1:计算机器人的位置 中使用的公式来设置一个函数。
它们可以像这样编码:
final_x = initial_pos[0] + (wheel_axis * cos(angle)) final_y = initial_pos[1] + (wheel_axis * sin(angle)) final_angle = initial_pos[2] + angle注意
return(final_x, final_y, final_angle) -
再次,你可以通过传入所有参数并手动计算结果来测试该函数:
final_position((0,0,0), 125, 1)上面的代码返回以下结果:
(67.53778823351747, 105.18387310098706, 1)在这里,你可以看到整个实现过程以及一个函数调用的示例:
图 1.11:计算出的机器人最终位置
活动 1:使用 Python 进行机器人定位(里程计法)
你正在创建一个系统,检测机器人在移动一定时间后的位置。开发一个 Python 函数,给定以下数据后返回机器人最终位置:
-
轮子直径(厘米) = 10 厘米
-
机器人底盘长度 = 80 厘米
-
每圈的编码器计数 = 76
-
用于测量编码器计数的秒数 = 600
-
给定秒数内的左右编码器计数 = 900
-
初始位置 = (0, 0, 0)
-
移动持续时间(秒) = 5 秒
注意
之前练习中实现的函数可以帮助你完成本活动。你可以按照以下步骤继续进行此活动。
按照以下步骤将帮助你完成练习:
-
首先,你需要计算每个轮子完成的距离。
-
要继续,你需要计算轴所完成的距离。
-
现在计算机器人的旋转角度。
-
然后计算机器人的最终位置。
输出应该像这样:
图 1.11:通过活动的 Python 函数计算的机器人最终位置
注意:
本活动的解决方案可以在第 300 页找到。
总结
在本章中,你已了解了机器人技术的世界。你学习了如自然语言处理(NLP)和计算机视觉等先进技术,并将其与机器人技术结合使用。在本章中,你还使用了 Python,而在接下来的章节中你将继续使用它。
此外,你已经利用了里程计来计算机器人在没有外部传感器的情况下的位置。如你所见,如果所需数据可用,计算机器人的位置并不难。请注意,尽管里程计是一项不错的技术,但在未来的章节中,我们将使用其他方法,这些方法将使我们能够使用传感器,并且在结果的准确性上可能更优。
在接下来的章节中,我们将探讨计算机视觉,并处理一些更实际的主题。例如,你将学习机器学习、决策树和人工神经网络,并以应用它们于计算机视觉为目标。在接下来的章节中,你将会使用到这些技术,并且你肯定会有机会将它们应用于个人或职业用途。
第三章:第二章
计算机视觉简介
学习目标
到本章结束时,你将能够:
-
解释人工智能和计算机视觉的影响
-
部署一些基本的计算机视觉算法
-
开发一些基本的机器学习算法
-
构建你的第一个神经网络
本章介绍了计算机视觉的基本概念,接着介绍了一些重要的计算机视觉和机器学习基本算法。
介绍
人工智能(AI)正在改变一切。它试图模拟人类智能,以完成各种任务。
处理图像的人工智能领域称为计算机视觉。计算机视觉是一个跨学科的科学领域,旨在模拟人类眼睛。它不仅从图像中提取像素并进行解读,还通过执行自动化任务和使用算法,从特定图像中获得更高层次的理解。
其中一些算法在物体识别、人脸识别、图像分类、图像编辑,甚至图像生成方面表现更好。
本章将从计算机视觉的介绍开始,首先讲解一些最基本的算法,并通过练习将它们付诸实践。接着,会介绍机器学习的基本算法到神经网络的概念,并通过多个练习来巩固所学的知识。
计算机视觉中的基本算法
在本节中,我们将讨论图像是如何形成的。我们将介绍一个非常有用的库,用于执行计算机视觉任务,并了解一些任务和算法的工作原理以及如何编码它们。
图像术语
要理解计算机视觉,我们首先需要了解图像是如何工作的以及计算机是如何解释它们的。
计算机将图像理解为一组数字的集合。更具体地说,图像被视为一个二维数组,一个包含从 0 到 255(在灰度图像中,0 代表黑色,255 代表白色)值的矩阵,表示图像的像素值(像素值),如下例所示:
图 2.1:没有像素值和有像素值的图像表示
在左侧的图像中,数字 3 以低分辨率显示。在右侧,显示了相同的图像,并附有每个像素的值。随着像素值的增加,颜色会变亮,而值减小时,颜色会变暗。
这张图像是灰度图像,这意味着它只是一个从 0 到 255 的二维数值数组,但彩色图像呢?彩色图像(或红/绿/蓝(RGB)图像)有三层二维数组堆叠在一起。每一层代表一种颜色,将它们组合在一起就形成了彩色图像。
上述图像的矩阵大小为 14x14 像素。在灰度模式下,它表示为 14x14x1,因为只有一个矩阵和一个通道。而对于 RGB 格式,它表示为 14x14x3,因为有三个通道。从中计算机只需理解这些图像是由这些像素构成的。
OpenCV
OpenCV 是一个开源的计算机视觉库,支持 C++、Python 和 Java 接口,并且支持 Windows、Linux、macOS、iOS 和 Android。
在本章中提到的所有算法,我们将使用 OpenCV。OpenCV 帮助我们通过 Python 实现这些算法。如果你想实践这些算法,建议使用 Google Colab。你需要安装 Python 3.5 或更高版本、OpenCV 和 NumPy,以便继续本章的内容。为了在屏幕上显示结果,我们将使用 Matplotlib。这两个库都是人工智能领域的优秀工具。
基本图像处理算法
为了让计算机理解图像,首先必须对图像进行处理。处理图像的算法有很多种,输出的结果取决于具体任务的要求。
一些最基本的算法包括:
-
阈值化
-
形态学变换
-
模糊
阈值化
阈值化通常用于简化计算机和用户对图像的可视化方式,以便更容易进行分析。它基于用户设置的一个值,每个像素的值根据是否高于或低于设定值,转换为白色或黑色。如果图像是灰度图,输出图像将是黑白图像,但如果你选择保持 RGB 格式,阈值将应用于每个通道,这意味着图像仍然是彩色的。
有多种方法可以进行阈值化,以下是一些常用的阈值方法:
-
**简单阈值化:**如果像素值低于用户设定的阈值,则该像素将被赋值为 0(黑色)或 255(白色)。简单阈值化中也有不同的阈值化方式:
阈值二进制
阈值二进制反转
截断
阈值设为零
阈值设为零反转
不同类型的阈值如图 2.2 所示
图 2.2:不同类型的阈值
阈值二进制反转与二进制类似,但原本为黑色的像素变为白色,反之亦然。全局阈值化是简单阈值化下的另一种名称。
截断显示阈值以上的像素值和实际像素值。
阈值设为零时,如果像素值高于阈值,它将输出该像素的实际值,否则输出黑色图像,而阈值设为零反转则正好相反。
注意
阈值值可以根据图像或用户的需求进行调整。
-
自适应阈值法:简单阈值使用全局值作为阈值。如果图像某些部分的光照条件不同,算法的表现会比较差。在这种情况下,自适应阈值法会自动为图像的不同区域猜测不同的阈值,从而在不同光照条件下得到更好的整体效果。
自适应阈值有两种类型:
自适应均值阈值
自适应高斯阈值
自适应阈值与简单阈值的区别如图 2.3 所示
图 2.3:自适应阈值与简单阈值的区别
在自适应均值阈值法中,阈值值是邻域区域的均值,而在自适应高斯阈值法中,阈值值是邻域值的加权和,其中权重是一个高斯窗口。
-
大津二值化法:在全局阈值法中,我们使用一个任意值作为阈值值。考虑一张双峰图像(像素分布在两个主要区域的图像)。你如何选择正确的阈值?大津二值化法会自动根据图像的直方图计算出适合双峰图像的阈值。图像直方图是一种直方图,它作为图形表示显示了色调在数字图像中的分布:
图 2.4:大津阈值法
练习 4:将不同的阈值应用于图像
注意
由于我们在 Google Colab 上训练人工神经网络,我们应该使用 Google Colab 提供的 GPU。为此,我们需要进入 runtime > Change runtime type > Hardware accelerator: GPU > Save。
所有的练习和活动将主要在 Google Colab 中开发。除非另有指示,否则建议为不同的作业保持单独的文件夹。
Dataset 文件夹可以在 GitHub 的 Lesson02 | Activity02 文件夹中找到。
在这个练习中,我们将加载一张地铁图像,并应用阈值处理:
-
打开你的 Google Colab 界面。
-
创建一个书籍文件夹,下载 GitHub 上的
Dataset文件夹,并将其上传到该文件夹中。 -
按如下方式导入驱动器并挂载:
from google.colab import drive drive.mount('/content/drive')注意
每次使用新协作者时,都需要将驱动器挂载到所需文件夹中。
一旦你第一次挂载了驱动器,你将需要输入授权码,这个授权码可以通过点击 Google 提供的 URL 并按下键盘上的 Enter 键获得:
图 2.5:显示 Google Colab 授权步骤的图像
-
现在你已经挂载了驱动器,需要设置目录的路径:
cd /content/drive/My Drive/C13550/Lesson02/Exercise04/注意
第 5 步中提到的路径可能会根据你在 Google Drive 上的文件夹设置发生变化。路径总是以
cd /content/drive/My Drive/开头。Dataset文件夹必须出现在你设置的路径中。 -
现在你需要导入相应的依赖:OpenCV
cv2和 Matplotlib:import cv2 from matplotlib import pyplot as plt -
现在输入代码加载
subway.jpg图像,我们将使用 OpenCV 对其进行灰度处理并使用 Matplotlib 显示:注意
img = cv2.imread('subway.jpg',0) plt.imshow(img,cmap='gray') plt.xticks([]),plt.yticks([]) plt.show()图 2.6:绘制加载的地铁图像结果
-
让我们通过使用 OpenCV 方法应用简单的阈值化处理。
在 OpenCV 中执行此操作的方法称为 cv2.threshold,它需要三个参数:image(灰度图像)、threshold value(用于分类像素值的阈值),以及 maxVal,它表示当像素值大于(有时小于)阈值时所给出的值:
_,thresh1 = cv2.threshold(img,107,255,cv2.THRESH_BINARY) _,thresh2 = cv2.threshold(img,107,255,cv2.THRESH_BINARY_INV) _,thresh3 = cv2.threshold(img,107,255,cv2.THRESH_TRUNC) _,thresh4 = cv2.threshold(img,107,255,cv2.THRESH_TOZERO) _,thresh5 = cv2.threshold(img,107,255,cv2.THRESH_TOZERO_INV) titles = ['Original Image','BINARY', 'BINARY_INV', 'TRUNC','TOZERO','TOZERO_INV'] images = [img, thresh1, thresh2, thresh3, thresh4, thresh5] for i in range(6): plt.subplot(2,3,i+1),plt.imshow(images[i],'gray') plt.title(titles[i]) plt.xticks([]),plt.yticks([]) plt.show()图 2.7:使用 OpenCV 进行简单阈值化
-
我们将对自适应阈值化做同样的操作。
执行此操作的方法是 cv2.adaptiveThreshold,它有三个特殊的输入参数和一个输出参数。输入参数为自适应方法、块大小(邻域区域的大小)和 C(从计算得到的均值或加权均值中减去的常数),而输出参数只有阈值化后的图像。这与全局阈值化不同,后者有两个输出:
th2=cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,71,7) th3=cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,71,7) titles = ['Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding'] images = [th2, th3] for i in range(2): plt.subplot(1,2,i+1),plt.imshow(images[i],'gray') plt.title(titles[i]) plt.xticks([]),plt.yticks([]) plt.show()图 2.8:使用 OpenCV 进行自适应阈值化
-
最后,让我们将 Otsu 二值化付诸实践。
-
该方法与简单的阈值化相同,cv2.threshold,只是多了一个额外的标志,cv2.THRESH_OTU:
ret2,th=cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU) titles = ['Otsu\'s Thresholding'] images = [th] for i in range(1): plt.subplot(1,1,i+1),plt.imshow(images[i],'gray') plt.title(titles[i]) plt.xticks([]),plt.yticks([]) plt.show()
图 2.9:使用 OpenCV 进行 Otsu 二值化
现在你可以对任何图像应用不同的阈值化变换。
形态学变换
形态学变换由一组基于图像形状的简单图像操作组成,通常用于二值图像。它们通常用于区分文本与背景或其他形状。它们需要两个输入,一个是原始图像,另一个称为结构元素或核,它决定了操作的性质。核通常是一个矩阵,它在图像上滑动,将其值与图像像素的值相乘。两个基本的形态学操作是腐蚀和膨胀。它们的变体形式是开运算和闭运算。应该使用哪种操作取决于具体任务:
- 腐蚀:对于给定的二值图像,它会将图像的厚度在内部和外部各收缩一个像素,这些像素由白色像素表示。此方法可以多次应用。根据你想要实现的目标,该方法可以用于不同的目的,但通常它与膨胀一起使用(如图 2.10 所示),用于去除孔洞或噪声。这里展示的是腐蚀的示例,数字是 3:
图 2.10:腐蚀示例
- 膨胀:该方法与腐蚀相反。它通过在二值图像的内部和外部各增加一个像素来增加物体的厚度。该方法也可以对图像多次应用。根据你想要实现的目标,这种方法可以用于不同的目的,但通常它与腐蚀结合使用,以去除图像中的孔洞或噪声。下面是膨胀的示例(我们已对图像应用了多次膨胀操作):
图 2.11:膨胀示例
-
开运算:该方法首先进行腐蚀,然后进行膨胀,通常用于去除图像中的噪声。
-
闭运算:该算法与开运算相反,首先进行膨胀再进行腐蚀。它通常用于去除物体中的孔洞:
图 2.12:开运算和闭运算示例
如你所见,开运算方法可以去除图像中的随机噪声,而闭运算方法则能有效修复图像中的小随机孔洞。为了去除开运算输出图像中的孔洞,可以应用闭运算方法。
还有更多的二值操作,但这些是基本操作。
练习 5:将各种形态学变换应用于图像
在本练习中,我们将加载一个数字图像,并对其应用我们刚刚学到的形态学变换:
-
打开你的 Google Colab 界面。
-
设置目录路径:
cd /content/drive/My Drive/C13550/Lesson02/Exercise05/注意
步骤 2 中提到的路径可能会发生变化,具体取决于你在 Google Drive 上的文件夹设置。
-
导入 OpenCV、Matplotlib 和 NumPy 库。NumPy 是 Python 科学计算的基础包,将帮助我们创建应用的卷积核:
import cv2 import numpy as np from matplotlib import pyplot as plt -
现在输入代码,加载我们将使用 OpenCV 处理并通过 Matplotlib 显示的
Dataset/three.png图像:注意
img = cv2.imread('Dataset/three.png',0) plt.imshow(img,cmap='gray') plt.xticks([]),plt.yticks([]) plt.savefig('ex2_1.jpg', bbox_inches='tight') plt.show()图 2.13:加载图像的绘制结果
-
让我们使用 OpenCV 方法应用腐蚀操作。
这里使用的方法是cv2.erode,它有三个参数:图像、在图像上滑动的卷积核和迭代次数,表示执行的次数:
kernel = np.ones((2,2),np.uint8) erosion = cv2.erode(img,kernel,iterations = 1) plt.imshow(erosion,cmap='gray') plt.xticks([]),plt.yticks([]) plt.savefig('ex2_2.jpg', bbox_inches='tight') plt.show()图 2.14:使用 OpenCV 的腐蚀方法的输出结果
如我们所见,图形的厚度减少了。
-
我们将对膨胀进行相同的操作。
这里使用的方法是cv2.dilate,它有三个参数:图像、内核和迭代次数:
kernel = np.ones((2,2),np.uint8) dilation = cv2.dilate(img,kernel,iterations = 1) plt.imshow(dilation,cmap='gray') plt.xticks([]),plt.yticks([]) plt.savefig('ex2_3.jpg', bbox_inches='tight') plt.show()图 2.15:使用 OpenCV 的膨胀方法的输出结果
如我们所见,图形的厚度增加了。
-
最后,让我们把开运算和闭运算应用到实践中。
这里使用的方法是cv2.morphologyEx,它有三个参数:图像、应用的方法和内核:
import random random.seed(42) def sp_noise(image,prob): ''' Add salt and pepper noise to image prob: Probability of the noise ''' output = np.zeros(image.shape,np.uint8) thres = 1 - prob for i in range(image.shape[0]): for j in range(image.shape[1]): rdn = random.random() if rdn < prob: output[i][j] = 0 elif rdn > thres: output[i][j] = 255 else: output[i][j] = image[i][j] return output def sp_noise_on_figure(image,prob): ''' Add salt and pepper noise to image prob: Probability of the noise ''' output = np.zeros(image.shape,np.uint8) thres = 1 - prob for i in range(image.shape[0]): for j in range(image.shape[1]): rdn = random.random() if rdn < prob: if image[i][j] > 100: output[i][j] = 0 else: output[i][j] = image[i][j] return output kernel = np.ones((2,2),np.uint8) # Create thicker figure to work with dilation = cv2.dilate(img, kernel, iterations = 1) # Create noisy image noise_img = sp_noise(dilation,0.05) # Create image with noise in the figure noise_img_on_image = sp_noise_on_figure(dilation,0.15) # Apply Opening to image with normal noise opening = cv2.morphologyEx(noise_img, cv2.MORPH_OPEN, kernel) # Apply Closing to image with noise in the figure closing = cv2.morphologyEx(noise_img_on_image, cv2.MORPH_CLOSE, kernel) images = [noise_img,opening,noise_img_on_image,closing] for i in range(4): plt.subplot(1,4,i+1),plt.imshow(images[i],'gray') plt.xticks([]),plt.yticks([]) plt.savefig('ex2_4.jpg', bbox_inches='tight') plt.show()
图 2.16:使用 OpenCV 的开运算方法(左)和闭运算方法(右)的输出结果
注意
整个代码文件可以在 GitHub 的 Lesson02 | Exercise05 文件夹中找到。
模糊(平滑)
图像模糊通过滤波器内核在图像上执行卷积,简而言之,就是在图像的每一部分上乘以特定值的矩阵,以平滑图像。它有助于去除噪声和边缘:
-
均值滤波:在这种方法中,我们考虑一个盒子滤波器或内核,它计算内核区域内像素的平均值,通过卷积将中央元素替换为整个图像的平均值。
-
高斯模糊:这里应用的内核是高斯内核,而不是盒子滤波器。它用于去除图像中的高斯噪声。
-
中值模糊:类似于均值滤波,但它用内核像素的中位数值替代中央元素。它对去除椒盐噪声(即图像中可见的黑白斑点)有很好的效果。
在图 2.17 中,我们应用了上述方法:
图 2.17:不同模糊方法对比的结果
还有许多其他算法可以应用,但这些是最重要的。
练习 6:将各种模糊方法应用于图像
在这个练习中,我们将加载一张地铁图像,并对其应用模糊方法:
-
打开你的 Google Colab 界面。
-
设置目录的路径:
cd /content/drive/My Drive/C13550/Lesson02/Exercise06/注意
第 2 步中提到的路径可能会根据你在 Google Drive 上的文件夹设置有所不同。
-
导入 OpenCV、Matplotlib 和 NumPy 库:
import cv2 from matplotlib import pyplot as plt import numpy as np -
输入代码以加载我们将要处理的
Dataset/subway.png图像,使用 OpenCV 将其转换为灰度图像,并用 Matplotlib 显示:注意
img = cv2.imread('Dataset/subway.jpg') #Method to convert the image to RGB img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) plt.imshow(img) plt.savefig('ex3_1.jpg', bbox_inches='tight') plt.xticks([]),plt.yticks([]) plt.show()图 2.18:以 RGB 格式绘制加载的地铁图像的结果
-
让我们应用所有的模糊方法:
应用的方法有cv2.blur、cv2.GaussianBlur和cv2.medianBlur。它们都以图像作为第一个参数。第一种方法只接受一个参数,即内核。第二种方法需要内核和标准差(sigmaX 和 sigmaY),如果这两个参数都为零,则根据内核大小计算。最后提到的方法只需再加一个参数,即内核大小:
blur = cv2.blur(img,(51,51)) # Apply normal Blurring blurG = cv2.GaussianBlur(img,(51,51),0) # Gaussian Blurring median = cv2.medianBlur(img,51) # Median Blurring titles = ['Original Image','Averaging', 'Gaussian Blurring', 'Median Blurring'] images = [img, blur, blurG, median] for i in range(4): plt.subplot(2,2,i+1),plt.imshow(images[i]) plt.title(titles[i]) plt.xticks([]),plt.yticks([]) plt.savefig('ex3_2.jpg', bbox_inches='tight') plt.show()
图 2.19:使用 OpenCV 的模糊方法
现在你已经知道如何将几种模糊技术应用于任何图像。
练习 7:加载图像并应用已学方法
在这个练习中,我们将加载一张数字图像,并应用我们到目前为止学到的方法。
注意
整个代码可以在 GitHub 的 Lesson02 | Exercise07-09 文件夹中找到。
-
打开一个新的 Google Colab 界面,并按照本章练习 4中提到的方法,挂载你的 Google Drive。
-
设置目录的路径:
cd /content/drive/My Drive/C13550/Lesson02/Exercise07/注意
第 2 步中提到的路径可能根据你在 Google Drive 上的文件夹设置有所不同。
-
导入相应的依赖项:NumPy、OpenCV 和 Matplotlib:
import numpy as np #Numpy import cv2 #OpenCV from matplotlib import pyplot as plt #Matplotlib count = 0 -
输入代码加载
Dataset/number.jpg图像,我们将使用 OpenCV 将其处理为灰度图像,并使用 Matplotlib 显示:注意
img = cv2.imread('Dataset/number.jpg',0) plt.imshow(img,cmap='gray') plt.xticks([]),plt.yticks([]) plt.show()图 2.20:加载带数字的图像结果
-
如果你想使用机器学习或任何其他算法来识别这些数字,你需要简化它们的可视化。使用阈值处理似乎是进行此操作的第一步。我们已经学习了一些阈值处理方法,但最常用的就是大津二值化法,因为它能够自动计算阈值,而不需要用户手动提供细节。
对灰度图像应用大津二值化,并使用 Matplotlib 显示:
_,th1=cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU th1 = (255-th1) # This step changes the black with white and vice versa in order to have white figures plt.imshow(th1,cmap='gray') plt.xticks([]),plt.yticks([]) plt.show()图 2.21:在图像上使用大津二值化阈值处理
-
为了去除背景中的线条,我们需要进行一些形态学变换。首先,从应用闭操作方法开始:
open1 = cv2.morphologyEx(th1, cv2.MORPH_OPEN, np.ones((4, 4),np.uint8)) plt.imshow(open1,cmap='gray') plt.xticks([]),plt.yticks([]) plt.show()图 2.22:应用闭操作方法
注意
背景中的线条已经完全去除,现在数字的预测会更加容易。
-
为了填补这些数字中可见的空洞,我们需要应用开操作方法。对前面的图像应用开操作方法:
close1 = cv2.morphologyEx(open1, cv2.MORPH_CLOSE, np.ones((8, 8), np.uint8)) plt.imshow(close1,cmap='gray') plt.xticks([]),plt.yticks([]) plt.show()图 2.23:应用开操作方法
-
数字周围仍然有一些杂质和不完美的地方。为了去除这些,使用更大内核的闭操作方法会是最佳选择。现在应用相应的方法:
open2 = cv2.morphologyEx(close1, cv2.MORPH_OPEN,np.ones((7,12),np.uint8)) plt.imshow(open2,cmap='gray') plt.xticks([]),plt.yticks([]) plt.show()图 2.24: 使用更大大小的核应用闭运算方法
根据你用于预测数字的分类器或给定图像的条件,可能会应用其他算法。
-
如果你想预测数字,你需要逐一进行预测。因此,你应该将数字分解为更小的数字。
幸运的是,OpenCV 有一个方法可以实现这一点,它叫做cv2.findContours。为了找到轮廓,我们需要将黑色反转为白色。这个代码块较大,但只有在你想要逐个字符进行预测时才需要使用:
_, contours, _ = cv2.findContours(open2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) #Find contours cntsSorted = sorted(contours, key=lambda x: cv2.contourArea(x), reverse=True) #Sort the contours cntsLength = len(cntsSorted) images = [] for idx in range(cntsLength): #Iterate over the contours x, y, w, h = cv2.boundingRect(contour_no) #Get its position and size ... # Rest of the code in Github images.append([x,sample_no]) #Add the image to the list of images and the X position images = sorted(images, key=lambda x: x[0]) #Sort the list of images using the X position {…}注意
带有注释的完整代码可在 GitHub 上的 Lesson02 | Exercise07-09 文件夹中找到。
图 2.25: 提取的数字作为输出
在代码的第一部分,我们正在寻找图像的轮廓(连接所有边界上连续点的曲线,这些点的颜色或强度相同),以找到每个数字,之后我们根据每个轮廓(每个数字)的区域进行排序。
接下来,我们遍历轮廓,使用给定的轮廓裁剪原始图像,最终将每个数字裁切成不同的图像。
接下来,我们需要让所有的图像具有相同的形状,因此我们使用 NumPy 将图像调整为给定的形状,并将图像与 X 位置一起添加到图像列表中。
最后,我们根据 X 位置对图像列表进行排序(从左到右,这样它们就保持顺序),并绘制结果。我们还将每个数字保存为单独的图像,以便之后可以单独使用每个数字进行任何任务。
恭喜!你已经成功处理了一张包含文本的图像,提取出了文本并且分离了每个字符,现在机器学习的魔法可以开始了。
机器学习简介
机器学习(ML)是让计算机从数据中学习而不需要定义规则的科学。机器学习主要基于通过大量数据训练的模型,例如数字图像或不同物体的特征,并与它们相应的标签一起使用,如数字的数量或物体的类型。这被称为有监督学习。还有其他类型的学习,例如无监督学习和强化学习,但我们将重点关注有监督学习。监督学习和无监督学习的主要区别在于,模型从数据中学习聚类(具体的聚类数量取决于你指定的聚类数),这些聚类会被转化为类别。而强化学习则关注软件代理如何在环境中采取行动,以增加奖励,奖励在代理执行正确操作时为正,反之为负。
在本章的这一部分,我们将理解机器学习并检查各种模型和算法,从最基本的模型到解释人工神经网络。
决策树和提升算法
在本节中,我们将解释决策树和提升算法作为最基本的机器学习算法之一。
装袋(决策树和随机森林)和提升(AdaBoost)将在本主题中进行解释。
装袋:
决策树或许是最基本的机器学习算法,用于分类和回归,但基本上主要用于教学和进行测试。
在决策树中,每个节点表示正在训练的数据的属性(是否为真),每个分支(节点之间的线)表示一个决策(如果某事为真,则选择这个方向;否则,选择另一个方向),每个叶子表示最终的结果(如果所有条件满足,则是一朵向日葵或雏菊)。
现在我们将使用鸢尾花数据集。该数据集考虑萼片宽度和长度以及花瓣宽度和长度,以便将鸢尾花分类为山鸢尾、变色鸢尾或维吉尼亚鸢尾。
注
可以使用 Python 从 scikit-learn 下载鸢尾花数据集:
scikit-learn.org/stable/modules/generated/sklearn.datasets.load_iris.html
Scikit-learn 是一个提供数据挖掘和数据分析有用工具的库。
下面的流程图显示了在这个数据集上训练的决策树的学习表示。X 代表数据集中的特征,X0 代表萼片长度,X1 代表萼片宽度,X2 代表花瓣长度,X3 代表花瓣宽度。'value'标签表示每个类别的样本落入每个节点的数量。我们可以看到,在第一步中,决策树仅通过考虑 X2 特征,花瓣长度,就能区分 setosa 与其他两个类别:
图 2.26:鸢尾花数据集的决策树图
由于 scikit-learn,可以只用几行代码在 Python 中实现决策树:
from sklearn.tree import DecisionTreeClassifier
dtree=DecisionTreeClassifier()
dtree.fit(x,y)
x和y分别是训练集的特征和标签。
x,除了仅代表这些长度和宽度的数据列,还可以是图像的每个像素。在机器学习中,当输入数据是图像时,每个像素被视为一个特征。
决策树是针对一个特定任务或数据集进行训练的,不能被转移到另一个类似的问题上。尽管如此,可以将多个决策树组合起来以创建更大的模型,并学习如何泛化。这些被称为随机森林。
"森林"这个名字指的是多种决策树算法的集合,遵循袋装法,即多个算法的组合能够取得最佳的整体结果。出现“随机”一词是因为该算法在选择特征来分割节点时具有随机性。
再次感谢 scikit-learn,我们可以通过几行代码实现随机森林算法,代码与前面非常相似:
from sklearn.ensemble import RandomForestClassifier
rndForest=RandomForestClassifier(n_estimators=10)
rndForest.fit(x,y)
n_estimators表示底层决策树的数量。如果你使用这个方法测试结果,结果一定会有所提高。
还有其他一些方法也遵循提升法的方法论。提升法包含了所谓的弱学习器,这些学习器被组合成加权和,从而生成一个强学习器,并给出输出。这些弱学习器是顺序训练的,也就是说,每个学习器都会尝试解决前一个学习器所犯的错误。
有许多算法使用这种方法,最著名的有 AdaBoost、梯度提升和 XGBoost。我们这里只看 AdaBoost,因为它是最著名且最容易理解的。
提升法
AdaBoost将多个弱学习器组合在一起,形成一个强学习器。AdaBoost 的名字代表自适应提升,意味着该策略在每个时刻的权重是不同的。在一次迭代中被错误分类的例子,会在下一次迭代中得到更高的权重,反之亦然。
该方法的代码如下:
from sklearn.ensemble import AdaBoostClassifier
adaboost=AdaBoostClassifier(n_estimators=100)
adaboost.fit(x_train, y_train)
n_estimators是提升完成后的最大估算器数量。
这个方法的初始化是基于决策树的,因此其性能可能不如随机森林。但为了构建一个更好的分类器,应该使用随机森林算法:
AdaBoostClassifier(RandomForestClassifier(n_jobs=-1,n_estimators=500,max_features='auto'),n_estimators=100)
练习 8:使用决策树、随机森林和 AdaBoost 算法预测数字
在这个练习中,我们将使用上一练习中获得的数字和我们在本主题中学习到的模型来正确预测每个数字。为此,我们将从Dataset/numbers文件夹中的一些样本中提取几个数字,并结合 MNIST 数据集以获得足够的数据,从而使模型能够正确学习。MNIST 数据集由手写数字组成,数字范围从 0 到 9,形状为 28 x 28 x 3,主要供研究人员测试他们的方法或进行实验。然而,即使这些数字不完全相同,它也能帮助预测某些数字。你可以在yann.lecun.com/exdb/mnist/查看这个数据集。
由于安装 Keras 需要 TensorFlow,我们建议使用 Google Colab,它类似于 Jupyter Notebook,不同之处在于,它不会占用你的本地系统资源,而是使用远程虚拟机,并且所有机器学习和 Python 相关的库已经预安装好了。
让我们开始这个练习:
注意
我们将在本笔记本中继续从练习 7 的代码。
-
前往 Google Colab 界面,在那里你执行了练习 7,加载图像并应用已学方法。
-
导入库:
import numpy as np import random from sklearn import metrics from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier from sklearn.tree import DecisionTreeClassifier from sklearn.utils import shuffle from matplotlib import pyplot as plt import cv2 import os import re random.seed(42)注意
我们将随机方法的种子设为 42,以保证可重复性:所有随机步骤具有相同的随机性,始终给出相同的输出。它可以设定为任何不变的数字。
-
现在我们将导入 MNIST 数据集:
from keras.datasets import mnist (x_train, y_train), (x_test, y_test) = mnist.load_data()在代码的最后一行,我们加载了数据到
x_train,即训练集(60,000 个数字示例),y_train,即这些数字的标签,x_test,即测试集,和y_test,即相应的标签。这些数据是 NumPy 格式的。 -
我们使用 Matplotlib 来展示其中一些数字:
for idx in range(5): rnd_index = random.randint(0, 59999) plt.subplot(1,5,idx+1),plt.imshow(x_train[idx],'gray') plt.xticks([]),plt.yticks([]) plt.show()图 2.27: MNIST 数据集
注意
这些数字看起来和我们在上一个练习中提取的数字不同。为了使模型能够正确预测第一练习中处理过的图像中的数字,我们需要将一些这些数字添加到数据集中。
这是添加新数字的过程,这些数字看起来像我们想要预测的数字:
添加一个包含从 0 到 9 编号的子文件夹的 Dataset 文件夹(已完成)。
获取前一个练习中的代码。
使用代码提取存储在
'Dataset/numbers/'中的所有数字(已完成)。将生成的数字粘贴到相应的文件夹中,文件夹名称与生成的数字对应(已完成)。
将这些图像添加到原始数据集中(此练习中的步骤 5)。
-
要将这些图像添加到训练集中,应该声明以下两个方法:
# --------------------------------------------------------- def list_files(directory, ext=None): return [os.path.join(directory, f) for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f)) and ( ext==None or re.match('([\w_-]+\.(?:' + ext + '))', f) )] # ------------------------------------------------------- def load_images(path,label): X = [] Y = [] label = str(label) for fname in list_files( path, ext='jpg' ): img = cv2.imread(fname,0) img = cv2.resize(img, (28, 28)) X.append(img) Y.append(label) if maximum != -1 : X = X[:maximum] Y = Y[:maximum] X = np.asarray(X) Y = np.asarray(Y) return X, Y第一个方法,
list_files(),列出文件夹中所有具有指定扩展名的文件,在本例中是jpg。在主方法
load_images()中,我们从这些文件夹中加载图像,这些图像来自数字文件夹,并附带相应的标签。如果最大值与-1 不同,我们会设定一个加载每个数字的数量的限制。这样做是因为每个数字应有相似的样本。最后,我们将列表转换为 NumPy 数组。 -
现在我们需要将这些数组添加到训练集中,以便我们的模型可以学习如何识别提取的数字:
print(x_train.shape) print(x_test.shape) X, Y = load_images('Dataset/%d'%(0),0,9) for digit in range(1,10): X_aux, Y_aux = load_images('Dataset/%d'%(digit),digit,9) print(X_aux.shape) X = np.concatenate((X, X_aux), axis=0) Y = np.concatenate((Y, Y_aux), axis=0)使用前面代码中声明的方法添加这些数字后,我们将这些数组与前面创建的集合连接:
from sklearn.model_selection import train_test_split x_tr, x_te, y_tr, y_te = train_test_split(X, Y, test_size=0.2)之后,使用
sklearn中的train_test_split方法将这些数字分开—20%用于测试,其余部分用于训练:x_train = np.concatenate((x_train, x_tr), axis=0) y_train = np.concatenate((y_train, y_tr), axis=0) x_test = np.concatenate((x_test, x_te), axis=0) y_test = np.concatenate((y_test, y_te), axis=0) print(x_train.shape) print(x_test.shape)完成后,我们将这些数据与原始的训练集和测试集进行合并。我们在合并之前和之后打印了
x_train和x_test的形状,因此可以看到那额外的 60 个数字。形状从(60,000, 28, 28)和(10,000, 28, 28)变为(60,072, 28, 28)和(10,018, 28, 28)。 -
对于我们将在本练习中使用的从 sklearn 导入的模型,我们需要将数组格式化为形状(n 个样本和数组),目前我们有的是(n 个样本,数组高度和数组宽度):
x_train = x_train.reshape(x_train.shape[0],x_train.shape[1]*x_train.shape[2]) x_test = x_test.reshape(x_test.shape[0],x_test.shape[1]*x_test.shape[2]) print(x_train.shape) print(x_test.shape)我们将数组的高度和宽度相乘,以得到数组的总长度,但只在一个维度中: (28*28) = (784)。
-
现在我们准备将数据输入到模型中。我们将开始训练一个决策树:
print ("Applying Decision Tree...") dtc = DecisionTreeClassifier() dtc.fit(x_train, y_train)为了查看该模型的表现,我们使用准确率作为度量指标。这表示已被预测的来自
x_test的样本数,我们已经从metrics模块和 sklearn 导入了该模块。现在,我们将使用该模块中的accuracy_score()来计算模型的准确率。我们需要使用模型中的predict()函数预测来自x_test的结果,并查看输出是否与y_test标签匹配:y_pred = dtc.predict(x_test) accuracy = metrics.accuracy_score(y_test, y_pred) print(accuracy*100)之后,计算并打印准确率。得到的准确率为87.92%,对于决策树来说,这并不是一个坏的结果,但它还是可以改进的。
-
让我们尝试随机森林算法:
print ("Applying RandomForest...") rfc = RandomForestClassifier(n_estimators=100) rfc.fit(x_train, y_train)使用相同的计算准确率的方法,得到的准确率是94.75%,这比之前的结果好多了,应该可以归类为一个好的模型。
-
现在,我们将尝试使用初始化为随机森林的 AdaBoost:
print ("Applying Adaboost...") adaboost = AdaBoostClassifier(rfc,n_estimators=10) adaboost.fit(x_train, y_train)使用 AdaBoost 获得的准确率为95.67%。这个算法比之前的算法花费更多的时间,但得到了更好的结果。
-
现在我们将对上一个练习中获得的数字应用随机森林。我们选择这个算法是因为它比 AdaBoost 花费的时间要少得多,并且能提供更好的结果。在检查以下代码之前,你需要运行第一个练习中的代码,图像存储在
Dataset/number.jpg文件夹中,这个图像是第一个练习使用的,还有从Dataset/testing/文件夹中提取的另外两张测试图像。完成这些后,你应该在你的目录中有五张数字图像,每张图像都可以加载。下面是代码:for number in range(5): imgLoaded = cv2.imread('number%d.jpg'%(number),0) img = cv2.resize(imgLoaded, (28, 28)) img = img.flatten() img = img.reshape(1,-1) plt.subplot(1,5,number+1), plt.imshow(imgLoaded,'gray') plt.title(rfc.predict(img)[0]) plt.xticks([]),plt.yticks([]) plt.show()
图 2.28:随机森林对数字 1、6、2、1 和 6 的预测
在这里,我们应用了随机森林模型的predict()函数,将每个图像传递给它。随机森林似乎表现相当好,因为它正确预测了所有数字。让我们尝试另一个未使用过的数字(在Dataset文件夹内有一个文件夹包含一些测试图像):
图 2.29:随机森林对数字 1、5、8、3 和 4 的预测
它在其余数字上依然表现不错。让我们再尝试一个数字:
图 2.30:随机森林对数字 1、9、4、7 和 9 的预测
数字 7 似乎存在问题。这可能是因为我们没有引入足够的样本,并且模型的简单性也导致了问题。
注释
本次练习的完整代码可以在 GitHub 的 Lesson02 | Exercise07-09 文件夹中找到。
现在,在下一个主题中,我们将探索人工神经网络的世界,这些网络在完成这些任务时更为强大。
人工神经网络(ANNs)
人工神经网络(ANNs)是模仿人脑并受其启发的信息处理系统,它们通过学习如何识别数据中的模式来模拟人脑。它们通过具有良好结构的架构来完成任务。该架构由多个小的处理单元(即神经元)组成,这些神经元通过相互连接来解决主要问题。
人工神经网络(ANNs)通过处理数据集中足够的示例来进行学习,足够的示例意味着成千上万,甚至是数百万个示例。这里的数据量可能成为一个劣势,因为如果你没有这些数据,你将不得不自己创建,这意味着你可能需要大量资金来收集足够的数据。
这些算法的另一个缺点是它们需要在特定的硬件和软件上进行训练。它们在高性能的 GPU 上训练效果最佳,而这些 GPU 价格昂贵。你仍然可以使用价格较低的 GPU 做某些事情,但数据训练的时间会更长。你还需要特定的软件,如TensorFlow、Keras、PyTorch 或 Fast.AI。对于本书,我们将使用 TensorFlow 和 Keras,它们运行在 TensorFlow 之上。
这些算法通过将所有数据作为输入来工作,其中第一层神经元作为输入层。之后,每个输入都会传递到下一层神经元,在那里它们会与某些值相乘,并通过激活函数进行处理,该函数做出“决策”并将这些值传递给下一层。网络中间的层被称为隐藏层。这个过程一直持续到最后一层,在那里输出结果。当将 MNIST 图像作为输入引入神经网络时,网络的最后一层应该有 10 个神经元,每个神经元代表一个数字,如果神经网络猜测某个图像是特定的数字,那么对应的神经元将被激活。人工神经网络检查其决策是否成功,如果没有,它会执行一个叫做反向传播的修正过程,在该过程中每次通过网络时都会被检查和修正,调整神经元的权重。图 2.31 展示了反向传播过程:
图 2.31:反向传播过程
这是一个人工神经网络的图形表示:
图 2.32:ANN 架构
在前面的图中,我们可以看到神经元,它们是所有处理发生的地方,以及它们之间的连接,它们是网络的权重。
我们将了解如何创建这些神经网络,但首先,我们需要查看我们所拥有的数据。
在前面的练习中,我们使用了形状为(60,072 和 784)以及(10,018 和 784)的整数类型,并且像素值为 0 到 255,分别用于训练和测试。人工神经网络(ANN)在使用归一化数据时表现得更好,速度也更快,但这到底是什么意思呢?
拥有归一化数据意味着将 0-255 范围的值转换为 0-1 的范围。这些值必须适应在 0 和 1 之间,这意味着它们将是浮动数字,因为没有其他方法可以将更大的数字范围压缩到较小的范围内。因此,首先我们需要将数据转换为浮动类型,然后进行归一化。以下是执行此操作的代码:
x_train = (x_train.astype(np.float32))/255.0 #Converts to float and then normalize
x_test = (x_test.astype(np.float32))/255.0 #Same for the test set
x_train = x_train.reshape(x_train.shape[0], 28, 28, 1)
x_test = x_test.reshape(x_test.shape[0], 28, 28, 1)
对于标签,我们也需要将格式转换为独热编码。
为此,我们需要使用 Keras 中 utils 包(现已更名为 np_utils)中的一个函数 to_categorical(),该函数将每个标签的数字转换为独热编码。以下是代码:
y_train = np_utils.to_categorical(y_train, 10)
y_test = np_utils.to_categorical(y_test, 10)
如果我们打印 y_train 的第一个标签 5,然后打印转换后的 y_train 的第一个值,它将输出 [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]。这种格式将在一个包含 10 个位置的数组的第六个位置放置 1(因为有 10 个数字),对应数字 5(第六个位置是为了 0,而不是 1)。现在我们可以继续进行神经网络的架构设计了。
对于一个基础的神经网络,使用了密集层(或全连接层)。这些神经网络也被称为全连接神经网络。它们包含一系列神经元,代表人类大脑的神经元。它们需要指定一个激活函数。激活函数是一个对输入进行加权求和、加上偏置并决定是否激活的函数(分别输出 1 或 0)。
最常用的激活函数是 Sigmoid 和 ReLU,但 ReLU 在整体上表现更好。它们在下图中表示:
图 2.33:Sigmoid 和 ReLU 函数
Sigmoid 和 ReLU 函数计算加权和并添加偏置。然后它们根据该计算的值输出一个值。Sigmoid 函数会根据计算结果的值给出不同的值,范围从 0 到 1。而 ReLU 函数则对于负值输出 0,对于正值输出计算结果的值。
在神经网络的最后,通常会使用 softmax 激活函数,它将为每个类别输出一个非概率数值,该数值对于最可能与输入图像匹配的类别来说会更高。还有其他激活函数,但对于多分类问题,softmax 是最适合的输出函数。
在Keras中,神经网络的代码如下:
model = Sequential()
model.add(Dense(16, input_shape=input_shape))
model.add(Activation('relu'))
model.add(Dense(8))
model.add(Activation('relu'))
model.add(Flatten())
model.add(Dense(10, activation="softmax"))
模型创建为 Sequential(),因为层是按顺序创建的。首先,我们添加一个包含 16 个神经元的密集层,并传递输入的形状,以便神经网络知道输入的形状。接着,应用 ReLU 激活函数。我们使用这个函数是因为它通常能给出很好的结果。然后,我们叠加另一个具有 8 个神经元且使用相同激活函数的层。
最后,我们使用 Flatten 函数将数组转换为一维,然后叠加最后一个密集层,其中类别数应表示神经元的数量(在这种情况下,MNIST 数据集有 10 个类别)。应用 softmax 函数,以便获得一热编码的结果,正如我们之前提到的。
现在我们需要编译模型。为此,我们使用如下的 compile 方法:
model.compile(loss='categorical_crossentropy', optimizer=Adadelta(), metrics=['accuracy'])
我们传入损失函数,用于计算反向传播过程中的误差。对于这个问题,我们将使用分类交叉熵作为损失函数,因为这是一个分类问题。使用的优化器是Adadelta,它在大多数情况下表现很好。我们将准确率作为模型的主要评价指标。
我们将使用在 Keras 中的回调函数。这些函数在每个 epoch 训练过程中都会被调用。我们将使用 Checkpoint 函数,以便在每个 epoch 上保存我们具有最佳验证结果的模型:
ckpt = ModelCheckpoint('model.h5', save_best_only=True,monitor='val_loss', mode='min', save_weights_only=False)
用于训练这个模型的函数叫做 fit(),其实现如下:
model.fit(x_train, y_train, batch_size=64, epochs=10, verbose=1, validation_data=(x_test, y_test),callbacks=[ckpt])
我们传入训练集及其标签,并设置批次大小为 64(这些是每个 epoch 步骤中传递的图像),我们选择设置 10 次训练 epoch(每个 epoch 都会处理数据)。还传入验证集,以便查看模型在未见数据上的表现,最后,我们设置之前创建的回调函数。
所有这些参数必须根据我们面临的问题进行调整。为了将这一切付诸实践,我们将进行一个练习——这是我们在决策树中做过的相同练习,但这次使用的是神经网络。
练习 9:构建你的第一个神经网络
注意
我们将继续在这里编写练习 8 中的代码。
本练习的完整代码可以在 GitHub 的 Lesson02 | Exercise07-09 文件夹中找到。
-
前往你在 Google Colab 上执行 练习 8,使用决策树、随机森林和 AdaBoost 算法预测数字 的界面。
-
现在从 Keras 库导入所需的包:
from keras.callbacks import ModelCheckpoint from keras.layers import Dense, Flatten, Activation, BatchNormalization, Dropout from keras.models import Sequential from keras.optimizers import Adadelta from keras import utils as np_utils -
我们按照本章中解释的方法对数据进行归一化处理。我们还声明了将传递给神经网络的
input_shape实例,并打印出来:x_train = (x_train.astype(np.float32))/255.0 x_test = (x_test.astype(np.float32))/255.0 x_train = x_train.reshape(x_train.shape[0], 28, 28, 1) x_test = x_test.reshape(x_test.shape[0], 28, 28, 1) y_train = np_utils.to_categorical(y_train, 10) y_test = np_utils.to_categorical(y_test, 10) input_shape = x_train.shape[1:] print(input_shape) print(x_train.shape)输出结果如下:
图 2.34:通过神经网络归一化处理后的数据输出
-
现在,我们将声明模型。我们之前构建的模型在这个问题上表现并不理想,所以我们创建了一个更深的模型,增加了更多神经元,并加入了一些新的方法:
def DenseNN(input_shape): model = Sequential() model.add(Dense(512, input_shape=input_shape)) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(Dropout(0.2)) model.add(Dense(512)) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(Dropout(0.2)) model.add(Dense(256)) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(Dropout(0.2)) model.add(Flatten()) model.add(Dense(256)) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(Dropout(0.2)) model.add(Dense(10, activation="softmax"))我们添加了一个
BatchNormalization()方法,它帮助网络更快地收敛,并可能整体上获得更好的结果。我们还添加了
Dropout()方法,它帮助网络避免过拟合(训练集的准确率远高于验证集的准确率)。它通过在训练过程中断开一些神经元(0.2 -> 20%的神经元),从而实现更好的问题泛化(更好地分类未见过的数据)。此外,神经元的数量大幅增加。层数也有所增加。随着层数和神经元的增加,理解会更深,学习到的特征也更复杂。
-
现在我们使用分类交叉熵来编译模型,因为有多个类别,并且使用 Adadelta,它在这些任务中表现非常好。同时,我们将准确率作为主要度量标准:
model.compile(loss='categorical_crossentropy', optimizer=Adadelta(), metrics=['accuracy']) -
让我们创建
Checkpoint回调函数,其中模型将存储在Models文件夹中,文件名为model.h5。我们将使用验证损失作为主要的追踪方法,模型会被完整保存:ckpt = ModelCheckpoint('Models/model.h5', save_best_only=True,monitor='val_loss', mode='min', save_weights_only=False) -
开始使用
fit()函数训练网络,就像我们之前解释的那样。我们使用 64 作为批次大小,10 个 epochs(足够了,因为每个 epoch 会持续很长时间,而且每个 epoch 之间的改善不会太大),并引入 Checkpoint 回调函数:model.fit(x_train, y_train, batch_size=64, epochs=10, verbose=1, validation_data=(x_test, y_test), callbacks=[ckpt])这将花费一些时间。
输出应该是这样的:
图 2.35:神经网络输出
模型的最终准确率对应于最后的
val_acc,为97.83%。这个结果比我们使用 AdaBoost 或随机森林时获得的结果更好。 -
现在,让我们进行一些预测:
for number in range(5): imgLoaded = cv2.imread('number%d.jpg'%(number),0) img = cv2.resize(imgLoaded, (28, 28)) img = (img.astype(np.float32))/255.0 img = img.reshape(1, 28, 28, 1) plt.subplot(1,5,number+1),plt.imshow(imgLoaded,'gray') plt.title(np.argmax(model.predict(img)[0])) plt.xticks([]),plt.yticks([]) plt.show()代码与上一练习中使用的代码相似,但有一些细微的不同。其中之一是,由于我们更改了输入格式,我们也需要更改输入图像的格式(浮动和归一化)。另一个是预测采用了 one-hot 编码,因此我们使用
argmax()的 NumPy 函数来获取 one-hot 输出向量中最大值的位置,这将是预测的数字。让我们看看我们之前使用随机森林时尝试的最后一个数字的输出:
图 2.36:使用神经网络预测数字
预测已成功——即使是随机森林模型困难的 7 也分类成功。
注意
完整代码可以在 GitHub 的 Lesson02 | Exercise07-09 文件夹中找到。
如果你尝试其他数字,它都会很好地分类——它已经学会了如何分类。
恭喜!你已经构建了你的第一个神经网络,并将其应用于现实世界的问题!现在你可以继续进行本章的活动了。
活动 2:从 Fashion-MNIST 数据库中分类 10 种衣物类型
现在你将面临一个与之前类似的问题,但这次涉及的是衣物类型的分类。这个数据库与原始 MNIST 非常相似,包含 60,000 张 28x28 的灰度图像用于训练,10,000 张用于测试。你需要按照第一个练习中的步骤进行,因为这个活动并不聚焦于现实世界。你将需要通过构建神经网络来实践在上一个练习中学到的能力。为此,你需要打开一个 Google Colab 笔记本。以下步骤将引导你朝着正确的方向前进:
-
从 Keras 加载数据集:
from keras.datasets import fashion_mnist (x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()注意
数据已像 MNIST 一样预处理,因此接下来的步骤应该类似于练习 5,对图像应用各种形态学变换。
-
导入
random并设置种子为 42。导入matplotlib并绘制数据集中的五个随机样本,方法与上一个练习相同。 -
现在对数据进行归一化,并重新调整其形状,以便适配神经网络,并将标签转换为 one-hot 编码。
-
开始构建神经网络的架构,使用全连接层。你需要在一个方法中构建它,该方法将返回模型。
注意
我们建议从构建一个非常小且简单的架构开始,通过在给定数据集上进行测试来不断改进它。
-
使用合适的参数编译模型并开始训练神经网络。
-
一旦训练完成,我们应该进行一些预测以测试模型。我们已经将一些图像上传到上一个练习的
Dataset文件夹中的testing文件夹。使用这些图像进行预测,方法与上一个练习中相同。注意
你需要考虑到输入神经网络的图像背景是黑色的,而衣物是白色的,因此你应该做相应的调整,以使图像看起来像这些图像。如果需要,应该将白色和黑色反转。NumPy 有一个方法可以做到这一点:
image = np.invert(image)。 -
查看结果:
图 2.37:预测的输出是该列表中位置的索引
注意
此活动的解决方案可以在第 302 页找到。
总结
计算机视觉是人工智能中的一个重要领域。通过理解这个领域,你可以实现一些目标,例如从图像中提取信息,或生成看起来与现实生活中一模一样的图像。本章介绍了使用 OpenCV 库进行图像预处理和特征提取的方法,借此可以轻松地训练和预测机器学习模型。还介绍了一些基础的机器学习模型,如决策树和提升算法。这些内容作为机器学习的入门,主要用于实验和玩耍。最后,介绍了神经网络,并使用 Keras 和 TensorFlow 作为后端进行编码。讲解了归一化并进行了实践操作,还涉及了全连接层,尽管卷积层比全连接层更适合处理图像,卷积层将在书的后续章节中讲解。
还介绍了避免过拟合的概念,最后我们使用该模型进行了预测,并通过真实世界的图像进行了实践操作。
在下一章,将介绍自然语言处理(NLP)的基本概念,并展示一些最广泛使用的技术,这些技术用于从语料库中提取信息,以便创建语言预测的基本模型。
第四章:第三章
自然语言处理基础
学习目标
本章结束时,你将能够:
-
分类自然语言处理的不同领域
-
分析 Python 中的基本自然语言处理库
-
预测一组文本中的主题
-
开发一个简单的语言模型
本章涵盖了自然语言处理的不同基础知识和领域,并介绍了 Python 中的相关库。
介绍
自然语言处理(NLP)是人工智能(AI)的一个领域,旨在使计算机能够理解并处理人类语言,以执行有用的任务。在这个领域中,有两个部分:自然语言理解(NLU)和自然语言生成(NLG)。
近年来,人工智能改变了机器与人类的互动方式。人工智能通过执行一些任务帮助人们解决复杂的方程式,比如根据你的口味推荐电影(推荐系统)。得益于 GPU 的高性能和海量数据的可用性,已经可以创建能够学习并表现得像人类一样的智能系统。
有许多库旨在帮助创建这些系统。在本章中,我们将回顾一些最著名的 Python 库,用于从原始文本中提取和清理信息。你可能会认为这项任务很复杂,但语言的完整理解和解释本身就是一项艰巨的任务。例如,“Cristiano Ronaldo 进了三个球”这句话对于机器来说很难理解,因为它不知道 Cristiano Ronaldo 是谁,也不知道“进了三个球”是什么意思。
自然语言处理(NLP)中最受欢迎的主题之一是问答系统(QA)。这个领域还包含信息检索(IR)。这些系统通过查询数据库中的知识或信息来构建答案,但它们也能够从一组自然语言文档中提取答案。这就是像谷歌这样的搜索引擎的工作原理。
目前在行业中,自然语言处理正变得越来越流行。最新的 NLP 趋势包括在线广告匹配、情感分析、自动翻译和聊天机器人。
对话代理,通常被称为聊天机器人,是自然语言处理的下一个挑战。它们能够进行真实的对话,许多公司使用它们来获取产品反馈或创建新的广告活动,通过分析客户在聊天机器人中的行为和意见。虚拟助手是自然语言处理的一个极好例子,它们已经进入市场。最著名的包括 Siri、亚马逊的 Alexa 和 Google Home。在本书中,我们将创建一个聊天机器人来控制一个虚拟机器人,使其能够理解我们希望机器人做什么。
自然语言处理
如前所述,NLP 是一个涉及理解和处理人类语言的人工智能领域。NLP 位于人工智能、计算机科学和语言学的交汇点。这个领域的主要目标是让计算机理解用人类语言写成的陈述或词语:
图 3.1:人工智能、语言学和计算机科学中的自然语言处理表示
语言学科学专注于研究人类语言,试图描述和解释语言的不同方法。
语言可以被定义为一组规则和符号的集合。符号通过规则组合使用,以传达信息。人类语言是特别的。我们不能简单地把它看作是自然形成的符号和规则;根据上下文,词汇的意义可能会发生变化。
自然语言处理正变得越来越流行,并且可以解决许多困难的问题。可用的文本数据量非常大,人类无法处理所有这些数据。在维基百科中,每天平均有 547 篇新文章,总共有超过 500 万篇文章。可以想象,人类无法阅读所有这些信息。
NLP 面临三个挑战。第一个挑战是收集所有数据,第二个是对数据进行分类,最后一个是提取相关信息。
自然语言处理解决了许多繁琐的任务,如电子邮件中的垃圾邮件检测、词性(POS)标注和命名实体识别。通过深度学习,NLP 还可以解决语音转文本问题。尽管 NLP 展示了很强的能力,但也存在一些情况,例如在人机对话中没有得到良好的解决方案、问答系统的摘要和机器翻译等。
自然语言处理的部分
如前所述,自然语言处理(NLP)可以分为两个部分:自然语言理解(NLU)和自然语言生成(NLG)。
自然语言理解
本节的自然语言处理涉及对人类语言的理解和分析。它侧重于对文本数据的理解,并处理这些数据以提取相关信息。NLU 提供直接的人机交互,并执行与语言理解相关的任务。
NLU 涉及人工智能最具挑战性的任务之一,那就是文本的理解。NLU 的主要挑战是理解对话。
注意
自然语言处理使用一套方法来生成、处理和理解语言。自然语言理解通过功能来理解文本的意义。
以前,对话被表示为一棵树,但这种方法无法涵盖许多对话情况。为了覆盖更多的情况,需要更多的树,每个对话上下文对应一棵树,这就导致了许多句子的重复:
图 3.2:使用树表示对话
这种方法已经过时且低效,因为它基于固定的规则;本质上是一个 if-else 结构。但现在,NLU(自然语言理解)贡献了另一种方法。对话可以表示为一个维恩图,其中每个集合代表对话的一个上下文:
图 3.3:使用维恩图表示对话
如你在之前的图中所见,NLU 方法通过改进对话理解的结构,提升了效果,因为它不是一个包含 if-else 条件的固定结构。NLU 的主要目标是解释人类语言的意义,处理对话的上下文,解决歧义并管理数据。
自然语言生成
NLG 是生成具有意义和结构的短语、句子和段落的过程。它是 NLP 的一个领域,不涉及理解文本。
要生成自然语言,NLG 方法需要相关数据。
NLG 有三个组成部分:
-
生成器:负责将文本包含在意图中,使其与情境的上下文相关联。
-
组成部分和表示层次:为生成的文本提供结构
-
应用:保存对话中的相关数据,以跟随逻辑脉络
生成的文本必须是人类可读的格式。NLG 的优点在于你可以使数据变得易于访问,并且可以迅速创建报告摘要。
自然语言处理的层次
人类语言有不同的表示层次。每个表示层次都比前一个层次更复杂。随着层次的上升,理解语言变得越来越困难。
前两个层次依赖于数据类型(音频或文本),具体分为以下几类:
-
语音学分析:如果数据是语音,首先我们需要分析音频以获得句子。
-
OCR/分词:如果我们有文本,需要通过计算机视觉(OCR)识别字符并构成单词。如果没有,我们需要对文本进行分词(即将句子拆分成文本单元)。
注意
OCR 过程是识别图像中的字符。一旦它生成了单词,这些单词就会作为原始文本进行处理。
-
形态学分析:专注于句子的单词,并分析其语素。
-
句法分析:这一层次专注于句子的语法结构。即理解句子中的不同部分,比如主语或谓语。
-
语义表示:程序并不理解单个词汇;它可以通过知道单词在句子中的使用方式来理解词汇的意义。例如,“猫”和“狗”对于算法来说可能意味着相同,因为它们可以以相同的方式使用。通过这种方式理解句子被称为词汇级别的意义。
-
话语处理:分析和识别文本中连接的句子及其关系。通过这样做,算法可以理解文本的主题是什么。
自然语言处理(NLP)在今天的行业中展现出巨大的潜力,但也存在一些例外。通过使用深度学习的概念,我们可以处理这些例外问题,从而获得更好的结果。这些问题将在第四章,神经网络与 NLP中进行回顾。文本处理技术的优势和递归神经网络的改进是 NLP 变得越来越重要的原因。
Python 中的 NLP
近年来,Python 变得非常流行,它将通用编程语言的强大功能与特定领域语言的使用相结合,如 MATLAB 和 R(用于数学和统计)。它有不同的库用于数据加载、可视化、NLP、图像处理、统计等。Python 拥有最强大的文本处理和机器学习算法库。
自然语言工具包(NLTK)
NLTK 是 Python 中用于处理人类语言数据的最常见工具包。它包括一套用于处理自然语言和统计数据的库和程序。NLTK 通常作为学习工具和进行研究时使用。
该库提供了超过 50 个语料库和词汇资源的接口和方法。NLTK 能够对文本进行分类并执行其他功能,如分词、词干提取(提取单词的词干)、标注(识别单词的标签,如人名、城市等)和句法分析。
练习 10:NLTK 入门
在本练习中,我们将回顾关于 NLTK 库的最基本概念。正如我们之前所说,这个库是自然语言处理(NLP)领域中最广泛使用的工具之一。它可以用来分析和研究文本,忽略无关的信息。这些技术可以应用于任何文本数据,例如,从一组推文中提取最重要的关键词,或分析一篇报纸文章:
注意
本章中的所有练习将在 Google Colab 中执行。
-
打开你的 Google Colab 界面。
-
为书籍创建一个文件夹。
-
在这里,我们将使用 NLTK 库的基本方法处理一个句子。首先,让我们导入必要的方法(
stopwords、word_tokenize和sent_tokenize):from nltk.corpus import stopwords from nltk.tokenize import word_tokenize from nltk.tokenize import sent_tokenize import nltk nltk.download('punkt') -
现在我们创建一个句子并应用这些方法:
example_sentence = "This course is great. I'm going to learn deep learning; Artificial Intelligence is amazing and I love robotics..." sent_tokenize(example_sentence) # Divide the text into sentences图 3.4:将句子划分为子句
word_tokenize(example_sentence)图 3.5:将句子分解成单词
注意
Sent_tokenize返回一个包含不同句子的列表。NLTK 的一个缺点是sent_tokenize并没有分析整个文本的语义结构;它只是根据句号将文本分割。 -
通过单词分词后的句子,我们来去除停用词。停用词是一组没有关于文本相关信息的词语。在使用
停用词之前,我们需要下载它:nltk.download('stopwords') -
现在,我们将
停用词的语言设置为英语:stop_words = set(stopwords.words("english")) print(stop_words)输出如下:
图 3.6: 停用词设置为英语
-
处理句子,删除
停用词:print(word_tokenize(example_sentence)) print([w for w in word_tokenize(example_sentence.lower()) if w not in stop_words])输出如下:
图 3.7: 去除停用词后的句子
-
现在,我们可以修改
停用词的集合并检查输出:stop_words = stop_words - set(('this', 'i', 'and')) print([w for w in word_tokenize(example_sentence.lower()) if w not in stop_words])图 3.8: 设置停用词
-
词干提取器去除单词的形态学词缀。让我们定义一个词干提取器并处理我们的句子。
Porter 词干提取器是一种执行此任务的算法:from nltk.stem.porter import * # importing a stemmer stemmer = PorterStemmer() # importing a stemmer print([stemmer.stem(w) for w in word_tokenize(example_sentence)])输出如下:
图 3.9: 设置停用词
-
最后,让我们按类型对每个单词进行分类。为此,我们将使用一个词性标注器:
nltk.download('averaged_perceptron_tagger') t = nltk.pos_tag(word_tokenize(example_sentence)) #words with each tag t输出如下:
图 3.10: 词性标注器
注意
平均感知机标注器是一种算法,用于预测单词的类别。
正如你在这次练习中可能已经注意到的,NLTK 能够轻松处理一个句子。它还可以分析大量文本文档,毫无问题。它支持多种语言,且分词过程比类似的库要快,并且每个 NLP 问题都有许多方法可供使用。
spaCy
spaCy 是 Python 中的另一个自然语言处理库。它看起来与 NLTK 相似,但你会发现它的工作方式有所不同。
spaCy 由 Matt Honnibal 开发,旨在帮助数据科学家轻松清理和标准化文本。它是准备机器学习模型文本数据的最快库。它包含内置的词向量和一些方法,用于比较两个或多个文本之间的相似性(这些方法是通过神经网络训练的)。
它的 API 易于使用,比 NLTK 更直观。通常,在自然语言处理(NLP)中,spaCy 与 NumPy 进行比较。它提供了执行分词、词形还原、词性标注、命名实体识别(NER)、依赖解析、句子和文档相似性分析、文本分类等任务的方法和功能。
它不仅具有语言学特征,还拥有统计模型。这意味着你可以预测一些语言注释,例如判断一个词是动词还是名词。根据你希望进行预测的语言,你需要更改一个模块。在这一部分中有 Word2Vec 模型,我们将在第四章中讨论,神经网络与自然语言处理。
正如我们之前所说,spaCy 有许多优点,但也有一些缺点;例如,它仅支持 8 种语言(NLTK 支持 17 种语言),分词过程较慢(这个耗时的过程在长文本中可能会很关键),而且总的来说,它不够灵活(也就是说,它只提供了 API 方法,无法修改任何参数)。
在开始练习之前,我们先回顾一下 spaCy 的架构。spaCy 最重要的数据结构是 Doc 和 Vocab。
Doc 结构是你正在加载的文本;它不是一个字符串。它由一系列标记及其注释组成。Vocab 结构是一组查找表,那么什么是查找表,它为什么重要呢?查找表是计算中的一个数组索引操作,它替代了运行时操作。spaCy 将跨文档可用的信息集中化。这意味着它更加高效,因为这样节省了内存。没有这些结构,spaCy 的计算速度将会更慢。
然而,Doc 的结构与 Vocab 不同,因为 Doc 是数据的容器。一个 Doc 对象拥有数据,并由一系列标记或跨度组成。还有一些词素(lexemes),它们与 Vocab 结构有关,因为它们没有上下文(与标记容器不同)。
注意
词素是没有屈折词尾的词汇意义单位。研究这一领域的是形态学分析。
图 3.11 显示了 spaCy 架构。
图 3.11:spaCy 架构
根据你加载的语言模型不同,你将拥有不同的处理流程和 Vocab。
练习 11:spaCy 简介
在这个练习中,我们将进行与练习 10、NLTK 简介中相同的转换,使用 spaCy API 对该练习中的同一个句子进行操作。这个练习将帮助你理解和学习这些库之间的差异:
-
打开你的 Google Colab 界面。
-
为这本书创建一个文件夹。
-
然后,导入包以使用它的所有功能:
import spacy -
现在我们将初始化我们的
nlp对象。这个对象是 spaCy 方法的一部分。通过执行这行代码,我们正在加载括号内的模型:import en_core_web_sm nlp = spacy.load('en') -
我们使用与练习 10、NLTK 简介中相同的句子,并创建 Doc 容器:
example_sentence = "This course is great. I'm going to learn deep learning; Artificial Intelligence is amazing and I love robotics..." doc1 = nlp(example_sentence) -
现在,打印
doc1、它的格式,第 5 个和第 11 个标记,以及第 5 个和第 11 个标记之间的跨度。你将看到如下结果:print("Doc structure: {}".format(doc1)) print("Type of doc1:{}".format(type(doc1))) print("5th and 10th Token of the Doc: {}, {}".format(doc1[5], doc1[11])) print("Span between the 5th token and the 10th: {}".format(doc1[5:11]))输出结果如下:
图 3.12:spaCy 文档输出
-
正如我们在图 3.5 中看到的,文档由标记(tokens)和跨度(spans)组成。首先,我们将看到
doc1的跨度,然后是它的标记。打印跨度:
for s in doc1.sents: print(s)输出结果如下:
图 3.13:打印 doc1 的跨度
打印令牌:
for i in doc1: print(i)输出结果如下:
图 3.14:打印 doc1 的令牌
-
一旦我们将文档划分为令牌,停用词就可以被去除。
首先,我们需要导入它们:
from spacy.lang.en.stop_words import STOP_WORDS print("Some stopwords of spaCy: {}".format(list(STOP_WORDS)[:10])) type(STOP_WORDS)输出结果如下:
图 3.15:spaCy 中的 10 个停用词
但令牌容器有
is_stop属性:for i in doc1[0:5]: print("Token: {} | Stop word: {}".format(i, i.is_stop)输出结果如下:
](tos-cn-i-73owjymdk6/f4e285a2059347adb9d22acc29f59922)
图 3.16:令牌的
is_stop属性 -
要添加新的停用词,我们必须修改
vocab容器:nlp.vocab["This"].is_stop = True doc1[0].is_stop这里的输出结果如下:
真
-
要执行词性标注,我们初始化令牌容器:
for i in doc1[0:5]: print("Token: {} | Tag: {}".format(i.text, i.pos_))输出结果如下:
图 3.17:令牌的
.pos_属性 -
文档容器具有
ents属性,包含令牌的实体。为了在文档中包含更多实体,我们可以声明一个新的实体:doc2 = nlp("I live in Madrid and I am working in Google from 10th of December.") for i in doc2.ents: print("Word: {} | Entity: {}".format(i.text, i.label_))输出结果如下:
图 3.18:令牌的 .label_ 属性
注意
如你在本练习中所见,spaCy 比 NLTK 更易于使用,但 NLTK 提供了更多方法来执行不同的文本操作。spaCy 非常适合用于生产环境。这意味着,在最短的时间内,你就能对文本进行基本处理。
练习已结束!现在你可以使用 NLTK 或 spaCy 对文本进行预处理。根据你要执行的任务,你将能够选择其中一个库来清理数据。
主题建模
在自然语言理解(NLU)中,作为自然语言处理(NLP)的一部分,许多任务之一是提取句子、段落或整个文档的含义。理解文档的一种方法是通过其主题。例如,如果一组文档来自一份报纸,那么这些主题可能是政治或体育。通过主题建模技术,我们可以获得一组代表不同主题的词语。根据你的文档集,你将拥有由不同词语代表的不同主题。这些技术的目标是了解语料库中不同类型的文档。
术语频率 – 逆文档频率(TF-IDF)
TF-IDF 是一种常用的 NLP 模型,用于从文档中提取最重要的词汇。为了进行这种分类,算法会为每个单词分配一个权重。这种方法的思想是忽略那些与全球概念(即文本的整体主题)无关的词语,因此这些词语的权重会被降低(意味着它们将被忽略)。降低它们的权重将帮助我们找到该文档的关键字(即权重最大的词语)。
数学上,算法用来查找文档中术语权重的方法如下:
图 3.19:TF-IDF 公式
-
Wi,j:术语 i 在文档 j 中的权重
-
tf,j: i 在 j 中的出现次数
-
df,j: 包含 i 的文档数量
-
N: 文档的总数
结果是术语在该文档中出现的次数,乘以总文档数的对数,再除以包含该术语的文档数量。
潜在语义分析(LSA)
LSA 是主题建模的基础技术之一。它分析文档集与其术语之间的关系,并生成与之相关的一组概念。
与 TF-IDF 相比,LSA 更具前瞻性。在大规模文档集中,TF-IDF 矩阵包含大量噪声信息和冗余维度,因此 LSA 算法执行了降维处理。
这一降维处理通过奇异值分解(SVD)进行。SVD 将矩阵 M 分解为三个独立矩阵的乘积:
图 3.20:奇异值分解
-
A: 这是输入数据矩阵。
-
m: 这是文档的数量。
-
n: 这是术语的数量。
-
U: 左奇异向量。我们的文档-主题矩阵。
-
S: 奇异值。表示每个概念的强度。这是一个对角矩阵。
-
V: 右奇异向量。表示术语在主题中的向量。
注意
该方法在大规模文档集上更为高效,但还有更好的算法可以执行此任务,比如 LDA 或 PLSA。
练习 12:Python 中的主题建模
在本练习中,将使用特定的库在 Python 中编写 TF-IDF 和 LSA 代码。完成本练习后,你将能够执行这些技术来提取文档中术语的权重:
-
打开你的 Google Colab 界面。
-
为书籍创建一个文件夹。
-
为了生成 TF-IDF 矩阵,我们可以编写图 3.19 中的公式,但我们将使用 Python 中最著名的机器学习库之一——scikit-learn:
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.decomposition import TruncatedSVD -
我们将在本练习中使用的语料库非常简单,只有四个句子:
corpus = [ 'My cat is white', 'I am the major of this city', 'I love eating toasted cheese', 'The lazy cat is sleeping', ] -
使用
TfidfVectorizer方法,我们可以将语料库中的文档集合转换为 TF-IDF 特征矩阵:vectorizer = TfidfVectorizer() X = vectorizer.fit_transform(corpus) -
get_feature_names()方法显示提取的特征。注意
vectorizer.get_feature_names()输出结果如下:
图 3.21:语料库的特征名称
-
X 是一个稀疏矩阵。要查看其内容,我们可以使用
todense()函数:X.todense()输出结果如下:
图 3.22:语料库的 TF-IDF 矩阵
-
现在让我们使用 LSA 进行降维。
TruncatedSVD方法使用 SVD 对输入矩阵进行变换。在本练习中,我们将使用n_components=10。从现在开始,你需要使用n_components=100(它在较大的语料库中有更好的效果):lsa = TruncatedSVD(n_components=10,algorithm='randomized',n_iter=10,random_state=0) lsa.fit_transform(X)输出结果如下:
图 23:使用 LSA 进行降维
-
attribute .components_显示每个vectorizer.get_feature_names()的权重。注意,LSA 矩阵的范围为 4x16,我们的语料库中有 4 个文档(概念),而矢量化器有 16 个特征(术语):lsa.components_输出如下:
](tos-cn-i-73owjymdk6/2df529843cb8493b8b18d98aaddc8384)
图 3.24:期望的 TF-IDF 矩阵输出
练习已经成功结束!这是活动 3的预备练习,处理语料库。请务必查看练习的第七步——它将为你提供完成后续活动的关键。我鼓励你阅读 scikit-learn 文档,学习如何发现这两种方法的潜力。现在你已经知道如何创建 TF-IDF 矩阵。这个矩阵可能会非常庞大,因此,为了更好地管理数据,LSA 算法对文档中每个术语的权重进行了降维处理。
活动 3:处理语料库
在本活动中,我们将处理一个非常小的语料库,通过 LSA 清理数据并提取关键词和概念。
想象一下这个场景:你所在城镇的报摊举办了一场比赛。比赛的内容是预测一篇文章的类别。该报纸没有结构化数据库,这意味着它只有原始数据。他们提供了一小组文档,需要知道这篇文章是政治类、科学类还是体育类:
注意
你可以在 spaCy 和 NLTK 库之间选择进行活动。如果在 LSA 算法结束时,关键词相关性得以保留,那么两种解决方案都有效。
-
加载语料库文档并将其存储在列表中。
注意
语料库文档可以在 GitHub 上找到,
github.com/PacktPublishing/Artificial-Vision-and-Language-Processing-for-Robotics/tree/master/Lesson03/Activity03/dataset -
使用 spaCy 或 NLTK 预处理文本。
-
应用 LSA 算法。
-
显示与每个概念相关的前五个关键词:
关键词:moon, apollo, earth, space, nasa
关键词:yard, touchdown, cowboys, prescott, left
关键词:facebook, privacy, tech, consumer, data
注意
输出的关键词可能与你的不一样。如果你的关键词不相关,请检查解决方案。
输出如下:
图 3.25:概念中最相关词语的输出示例(f1)
注意
该活动的解决方案可以在第 306 页找到。
语言建模
到目前为止,我们已经回顾了文本数据预处理的最基本技术。现在,我们将深入探讨自然语言的结构——语言模型。我们可以将此话题视为自然语言处理(NLP)中机器学习的入门。
语言模型简介
一个统计语言模型(LM)是一个单词序列的概率分布,这意味着,它为一个特定的句子分配一个概率。例如,语言模型可以用来计算句子中即将到来的单词的概率。这涉及到对语言模型结构以及如何形成它做出一些假设。一个语言模型的输出从来不是完全正确的,但使用它通常是必要的。
语言模型(LM)在许多 NLP 任务中都有应用。例如,在机器翻译中,了解下一句前面的句子非常重要。语言模型还用于语音识别,以避免歧义,拼写纠错以及摘要生成等。
让我们看看语言模型是如何在数学上表示的:
- P(W) = P(w1, w2, w3, w4, … wn)
P(W) 是我们的语言模型(LM),wi 是包含在 W 中的单词,正如我们之前提到的,我们可以用它来计算即将到来的单词的概率,方式如下:
- P(w5|w1, w2, w3, w4)
这个(w1, w2, w3, w4)表示在给定的单词序列中,w5(即将到来的单词)的概率可能是多少。
看这个例子,P(w5|w1, w2, w3, w4),我们可以做出这样的假设:
- P(实际单词 | 前一个单词)
根据我们查看的前几个单词的数量来获取实际单词的概率,我们可以使用不同的模型。那么,现在我们将介绍一些关于这些模型的重要概念。
Bigram 模型
bigram 模型是由两个连续的单词组成的序列。例如,在句子“我的猫是白色的”中,有以下这些 bigram:
我的猫
猫是
是白色的
从数学上讲,bigram 模型有以下形式:
- Bigram 模型:P(wi|wi-1)
N-gram 模型
如果我们改变前一个单词的长度,就得到了 N-gram 模型。它的工作原理与 bigram 模型相似,但考虑的单词比前一个集合更多。
使用之前的例子“我的猫是白色的”,我们可以得到以下结果:
-
Trigram
我的猫是
猫是白色的
-
4-gram
-
我的猫是白色的
N-Gram 问题
此时,你可能会认为 n-gram 模型比 bigram 模型更准确,因为 n-gram 模型可以访问更多的“先前知识”。然而,由于长距离依赖,n-gram 模型也存在一定的局限性。一个例子是,“经过深思熟虑,我买了一台电视”,我们将其计算为:
- P(电视 | 经过深思熟虑,我买了一台)
句子“经过深思熟虑,我买了一台电视”可能是我们语料库中唯一具有这种结构的单词序列。如果我们将“电视”这个词换成另一个词,例如“电脑”,句子“经过深思熟虑,我买了一台电脑”也是有效的,但在我们的模型中,以下情况将会发生:
- P(电脑 | 经过深思熟虑,我买了一台) = 0
这个句子是有效的,但我们的模型不够准确,所以我们在使用 n-gram 模型时需要小心。
计算概率
Unigram 概率
单语是计算概率的最简单情况。它计算一个词在一组文档中出现的次数。它的公式如下:
图 3.27:单语概率估计
-
c(wi) 是出现次数
-
wi 在整个语料库中出现。语料库的大小就是它包含的词项数量。
双语概率
为了估计双语概率,我们将使用最大似然估计:
图 3.27:双语概率估计
为了更好地理解这个公式,我们来看一个例子。
假设我们的语料库由这三句话组成:
我的名字是查尔斯。
查尔斯是我的名字。
我的狗在玩球。
语料库的大小是 14 个词,现在我们要估计 "my name" 这一序列的概率。
图 3.28:双语估计的例子
链式法则
现在我们了解了双语和 n-gram 的概念,我们需要了解如何获得这些概率。
如果你有基本的统计学知识,你可能会认为最好的选择是应用链式法则,将每个概率连接起来。例如,在句子 "My cat is white" 中,概率如下:
- P(my cat is white) = p(white|my cat is) p(is|my cat) p(cat|my) p(my)
这似乎在这个句子中是可行的,但如果我们有一个更长的句子,长距离依赖问题就会出现,n-gram 模型的结果可能会不正确。
平滑
到目前为止,我们有了一个概率模型,如果我们想估计模型的参数,可以使用最大似然估计法。
语言模型(LM)面临的一个大问题是数据不足。我们的数据是有限的,因此会有许多未知事件。这意味着什么?这意味着我们最终得到的语言模型会对未见过的词汇给出 0 的概率。
为了解决这个问题,我们将使用平滑方法。通过这个平滑方法,每个概率估计结果都会大于零。我们将使用的方法是加一平滑:
图 3.29:双语估计中的加一平滑
V 是我们语料库中不同词项的数量。
注意
还有更多表现更好的平滑方法;这只是最基本的方法。
马尔可夫假设
马尔可夫假设对于估计长句子的概率非常有用。通过这种方法,我们可以解决长距离依赖问题。马尔可夫假设简化了链式法则,用于估计长序列的词汇。每次估计只依赖于前一步:
图 3.30:马尔可夫假设
我们也可以使用二阶马尔可夫假设,它依赖于前两个词项,但我们将使用一阶马尔可夫假设:
图 3.31:马尔科夫示例
如果我们将其应用于整个句子,结果如下:
图 3.32:整个句子的马尔科夫示例
按照上述方式分解单词序列将更加准确地输出概率。
练习 13:创建二元模型
在这个练习中,我们将创建一个简单的语言模型(LM),使用 unigram 和 bigram。同样,我们将比较在没有加一平滑和加一平滑的情况下创建语言模型的结果。n-gram 的一个应用示例是键盘应用。它们可以预测你下一个单词。这个预测可以通过一个二元模型来实现:
-
打开你的 Google Colab 界面。
-
创建书籍文件夹。
-
声明一个小的、易于训练的语料库:
import numpy as np corpus = [ 'My cat is white', 'I am the major of this city', 'I love eating toasted cheese', 'The lazy cat is sleeping', ] -
导入所需的库并加载模型:
import spacy import en_core_web_sm from spacy.lang.en.stop_words import STOP_WORDS nlp = en_core_web_sm.load() -
使用 spaCy 对其进行分词。为了加快平滑处理和二元模型的速度,我们将创建三个列表:
Tokens:语料库中的所有标记Tokens_doc:包含每个语料库标记的列表的列表Distinc_tokens:去重后的所有标记:tokens = [] tokens_doc = [] distinc_tokens = []我们先创建一个循环,遍历语料库中的句子。
doc变量将包含句子的标记序列:for c in corpus: doc = nlp(c) tokens_aux = []现在,我们将创建第二个循环,遍历标记并将其推入相应的列表中。
t变量将是句子的每个标记:for t in doc: tokens_aux.append(t.text) if t.text not in tokens: distinc_tokens.append(t.text) # without duplicates tokens.append(t.text) tokens_doc.append(tokens_aux) tokens_aux = [] print(tokens) print(distinc_tokens) print(tokens_doc) -
创建 unigram 模型并进行测试:
def unigram_model(word): return tokens.count(word)/len(tokens) unigram_model("cat")结果 = 0.1388888888888889
-
添加平滑并使用相同的单词进行测试:
def unigram_model_smoothing(word): return (tokens.count(word) + 1)/(len(tokens) + len(distinc_tokens)) unigram_model_smoothing("cat")结果 = 0.1111111111111111
注意
这种平滑方法的问题在于每个未见过的单词都有相同的概率。
-
创建二元模型(bigram):
def bigram_model(word1, word2): hit = 0 -
我们需要遍历文档中的所有标记,尝试找到
word1和word2一起出现的次数:for d in tokens_doc: for t,i in zip(d, range(len(d))): # i is the length of d if i <= len(d)-2: if word1 == d[i] and word2 == d[i+1]: hit += 1 print("Hits: ",hit) return hit/tokens.count(word1) bigram_model("I","am")输出如下:
图 3.33:输出显示 word1 和 word2 在文档中一起出现的次数
-
为二元模型添加平滑:
def bigram_model_smoothing(word1, word2): hit = 0 for d in tokens_doc: for t,i in zip(d, range(len(d))): if i <= len(d)-2: if word1 == d[i] and word2 == d[i+1]: hit += 1 return (hit+1)/(tokens.count(word1)+len(distinc_tokens)) bigram_model("I","am")输出如下:
图 3.34:为模型添加平滑后输出结果
恭喜!你已经完成了本章的最后一个练习。在下一章,你将看到这种语言模型(LM)方法是一种基础的深度自然语言处理(NLP)方法。现在你可以利用庞大的语料库,创建属于你自己的语言模型(LM)。
注意
应用马尔科夫假设,最终的概率将四舍五入为 0。我建议使用 log()并逐个添加各个组件。此外,检查代码的精度位数(float16 < float32 < float64)。
总结
自然语言处理(NLP)在人工智能中变得越来越重要。各个行业分析大量未经结构化的原始文本数据。为了理解这些数据,我们使用许多库进行处理。NLP 分为两大类方法和功能:NLG 用于生成自然语言,NLU 用于理解自然语言。
首先,清理文本数据非常重要,因为其中会有很多无用的、不相关的信息。一旦数据准备好进行处理,通过诸如 TF-IDF 或 LSA 等数学算法,就能理解大量文档。像 NLTK 和 spaCy 这样的库在完成这项任务时非常有用,它们提供了去除数据噪音的方法。文档可以被表示为一个矩阵。首先,TF-IDF 能够给出文档的全局表示,但当语料库较大时,更好的选择是通过 LSA 和 SVD 进行降维处理。scikit-learn 提供了处理文档的算法,但如果文档没有经过预处理,结果将不准确。最后,可能需要使用语言模型,但它们需要由有效的训练集文档构成。如果文档集质量良好,语言模型应该能够生成语言。
在下一章中,我们将介绍递归神经网络(RNNs)。我们将探讨这些 RNN 的一些高级模型,并因此在构建我们的机器人时走在前列。