面向 OpenCV 的机器学习(一)

原文:Machine Learning for OpenCV

协议:CC BY-NC-SA 4.0

零、前言

随着世界的变化和人类制造更智能更好的机器,对机器学习和计算机视觉专家的需求也在增加。机器学习,顾名思义,就是给定某一组参数作为输入,进行预测的机器学习过程。另一方面,计算机视觉给出了机器视觉;也就是说,它使机器意识到视觉信息。当你结合这些技术时,你会得到一台可以使用视觉数据进行预测的机器,这让机器离拥有人类能力又近了一步。当你加入深度学习,机器甚至可以在预测方面超越人类的能力。这看似牵强,但随着人工智能系统取代基于决策的系统,这实际上已经成为现实。你有人工智能摄像机、人工智能显示器、人工智能音响系统、人工智能处理器等等。我们不能向您承诺,在阅读本书后,您将能够构建一个人工智能相机,但我们确实打算为您提供这样做所需的工具。我们将要介绍的最强大的工具是 OpenCV 库,它是世界上最大的计算机视觉库。尽管它在机器学习中的使用并不常见,但我们已经提供了一些关于如何将其用于机器学习的示例和概念。在本书中,我们采用了动手的方法,我们建议您尝试本书中的每一段代码来构建一个展示您的知识的应用程序。世界在变化,这本书是我们帮助年轻人变得更好的方法。

这本书是给谁的

我们试图从头开始解释所有的概念,使这本书既适合初学者,也适合高级读者。我们建议读者具备一些 Python 编程的基础知识,但这不是强制性的。每当你遇到一些你无法理解的 Python 语法时,一定要在网上查找。帮助总是提供给寻找帮助的人。

充分利用这本书

如果您是 Python 初学者,我们建议您阅读任何好的 Python 编程书籍或在线教程或视频。也可以看看 DataCamp(www.datacamp.com)用互动课学习 Python。

我们还建议您学习 Python 中 Matplotlib 库的一些基本概念。你可以试试这个教程:https://www . datacamp . com/community/tutories/matplotlib-tutory-python。

在开始阅读这本书之前,你不需要在你的系统上安装任何东西。我们将在第一章中介绍所有的安装步骤。

下载示例代码文件

你可以从你在www.packt.com的账户下载这本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册将文件直接通过电子邮件发送给您。

您可以按照以下步骤下载代码文件:

  1. 登录或注册www.packt.com
  2. 选择“支持”选项卡。
  3. 点击代码下载。
  4. 在搜索框中输入图书的名称,并按照屏幕指示进行操作。

下载文件后,请确保使用最新版本的解压缩文件夹:

  • 视窗系统的 WinRAR/7-Zip
  • zipeg/izp/un ARX for MAC
  • 适用于 Linux 的 7-Zip/PeaZip

这本书的代码包也在 GitHub 上发布,网址为 https://GitHub . com/packt publishing/Machine-Learning for-OpenCV-第二版…

下载彩色图像

我们还提供了一个 PDF 文件,其中包含本书中使用的截图/图表的彩色图像。可以在这里下载:www . packtpub . com/sites/default/files/downloads/9781789536300 _ color images . pdf

使用的约定

本书通篇使用了许多文本约定。

CodeInText:表示文本中的码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和推特句柄。这里有一个例子:“我们可以设置max_samples<1.0max_features<1.0来实现随机补丁方法。”

代码块设置如下:

In [1]: from sklearn.ensemble import BaggingClassifier... from sklearn.neighbors import KNeighborsClassifier... bag_knn = BaggingClassifier(KNeighborsClassifier(),... n_estimators=10)

任何命令行输入或输出都编写如下:

$ conda install package_name

粗体:表示一个新的术语、一个重要的单词或者你在屏幕上看到的单词。

Warnings …

取得联系

我们随时欢迎读者的反馈。

一般反馈:如果你对这本书的任何方面有疑问,在你的信息主题中提到书名,发邮件给我们customercare@packtpub.com

勘误表:虽然我们已经尽了最大的努力来保证内容的准确性,但是错误还是会发生。如果你在这本书里发现了一个错误,如果你能向我们报告,我们将不胜感激。请访问www.packtpub.com/support/errata,选择您的图书,点击勘误表提交链接,并输入详细信息。

盗版:如果您在互联网上遇到任何形式的我们作品的非法拷贝,如果您能提供我们的位置地址或网站名称,我们将不胜感激。请通过copyright@packt.com联系我们,并提供材料链接。

如果你有兴趣成为一名作者:如果有一个你有专长的话题,你有兴趣写或者投稿一本书,请访问authors.packtpub.com

复习

请留下评论。一旦你阅读并使用了这本书,为什么不在你购买它的网站上留下评论呢?然后,潜在的读者可以看到并使用您不带偏见的意见来做出购买决定,我们在 Packt 可以了解您对我们产品的看法,我们的作者可以看到您对他们的书的反馈。谢谢大家!

更多关于 Packt 的信息,请访问packt.com

一、机器学习的风格

所以,你已经决定进入机器学习领域。太好了!

如今,机器学习无处不在——从保护我们的电子邮件,到在图片中自动标记我们的朋友,再到预测我们喜欢什么电影。机器学习作为人工智能的一种形式,使计算机能够通过经验进行学习;利用从过去收集的数据来预测未来。最重要的是,计算机视觉是当今机器学习最令人兴奋的应用领域之一,深度学习和卷积神经网络驱动着自动驾驶汽车和谷歌的 DeepMind 等创新系统。

然而,不要烦恼;您的应用程序不需要如此大规模或改变世界…

技术要求

可以通过以下链接查阅本章代码:github . com/PacktPublishing/Machine-Learning-for-OpenCV-Second-Edition/tree/master/chapter 01

以下是软件和硬件要求的简短总结:

  • OpenCV 版本 4.1.x (4.1.0 或 4.1.1 都可以正常工作)。
  • Python 3.6 版本(任何 Python 3 . x 版本都可以)。
  • Anaconda Python 3,用于安装 Python 和所需的模块。
  • 这本书可以使用任何操作系统——苹果操作系统、视窗操作系统和基于 Linux 的操作系统。我们建议您的系统中至少有 4 GB 内存。
  • 运行本书提供的代码不需要 GPU。

机器学习入门

机器学习已经存在至少 60 年了。出于对人工智能的追求,早期的机器学习系统推断出if...else语句的手动编码规则来处理数据和做出决策。设想一个垃圾邮件过滤器,其工作是解析传入的电子邮件,并将不需要的邮件移动到垃圾邮件文件夹中,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以想出一个单词黑名单,当它们出现在邮件中时,会将电子邮件标记为垃圾邮件。这是一个手工编码专家系统的简单例子。(我们将在第七章中构建一个更智能的实施垃圾邮件…

机器学习可以解决的问题

大多数机器学习问题属于以下三个主要类别之一:

  • 在监督学习中,我们有一个数据点的标签。现在,这可以是图像中捕获的对象的类别、面部周围的边界框、图像中出现的数字或任何其他东西。把它想象成一个老师,他不仅教书,还告诉你问题的正确答案是什么。现在,学生可以尝试设计一个模型或方程,考虑所有的问题及其正确答案,并找出一个有(或没有)正确答案的问题的答案。进入模型学习的数据称为训练数据,测试过程/模型的数据称为测试数据。这些预测有两种风格,例如用正确的动物识别新照片(称为分类 问题)或为其他二手车分配准确的销售价格(称为回归 问题)。不要担心这是否有点超出你的理解范围——我们会用整本书来确定细节。

  • 在无监督学习中,数据点没有关联的标签(第八章,发现隐藏结构无监督学习)。把它想象成一堂课,老师给你一个混乱的谜题,让你自己去想该怎么做。在这里,最常见的结果是 c 光泽,其中包含具有相似特征的对象。它还会导致以不同的方式查看高维数据(复杂数据),从而使其看起来更简单。

  • 强化学习是关于在一个问题中获得最大回报。所以,如果老师每答对一个问题就给你一颗糖,每答错一个问题就惩罚你,那他/她就是在强化概念,让你增加收到糖果的次数,而不是让你受到惩罚的次数。

下图说明了这三个主要类别:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

既然我们已经介绍了主要的机器学习类别,那么让我们回顾一下 Python 中的一些概念,这些概念在本书的旅程中将会非常有用。

Python 入门

Python 已经成为许多数据科学和机器学习应用程序的通用语言,这要归功于它为数据加载、数据可视化、统计、图像处理和自然语言处理等过程提供的大量开源库。使用 Python 的主要优势之一是能够直接与代码交互,使用终端或其他工具,如 Jupyter Notebook ,我们将在稍后介绍。

如果你大部分时间都在结合 C++使用 OpenCV,我强烈建议你改用 Python,至少是为了学习这本书。这个决定不是出于恶意做出的!恰恰相反:我已经完成了相当一部分 C/C++编程——尤其是…

OpenCV 入门

作为 OpenCV 的狂热用户,我相信你是,我可能不需要说服你 OpenCV 的力量。

OpenCV 旨在为计算机视觉应用程序提供通用基础设施,现已成为一套全面的经典和最先进的计算机视觉和机器学习算法。根据他们自己的文档,OpenCV 拥有超过 47,000 人的用户社区,下载量超过 700 万次。太令人印象深刻了!作为一个开源项目,研究人员、企业和政府机构很容易利用和修改已经可用的代码。

也就是说,作为最近机器学习热潮的一部分,出现了许多开源机器学习库,它们提供的功能比 OpenCV 多得多。一个突出的例子是 scikit-learn ,它提供了许多最先进的机器学习算法以及大量的在线教程和代码片段。由于 OpenCV 的开发主要是为了提供计算机视觉算法,其机器学习功能被限制在一个名为ml的模块中。正如我们将在本书中看到的,OpenCV 仍然提供了许多最先进的算法,但有时在功能上有点欠缺。在这些罕见的情况下,我们将简单地使用 scikit-learn 来达到我们的目的,而不是重新发明轮子。

最后但并非最不重要的一点是,使用 Python Anaconda 发行版安装 OpenCV 本质上是一个单行程序,我们将在下面的章节中看到。

If you are a more advanced user who wants to build real-time applications, OpenCV’s algorithms are well-optimized for this task, and Python provides several ways to speed up computations where it is necessary (using, for example, Cython or parallel processing libraries such as joblib or dask).

装置

在开始之前,让我们确保已经安装了创建一个全面运行的数据科学环境所必需的所有工具和库。从 GitHub 下载本书的最新代码后,我们将安装以下软件:

  • Python 的 Anaconda 发行版,基于 Python 3.6 或更高版本
  • OpenCV 4.1
  • 一些支持包

Don’t feel like installing stuff? You can also visit mybinder.org/v2/gh/PacktPublishing/Machine-Learning-for-OpenCV-Second-Edition/master, where you will find all the code for this book in an interactive, executable environment and 100% free and open source, thanks to the Binder project.

获取这本书的最新代码

你可以从 GitHub 获得这本书的最新代码:GitHub . com/PacktPublishing/机器学习 for OpenCV-第二版。您可以下载一个.zip包(初学者)或者使用 Git(中间用户)克隆存储库。

Git is a version control system that allows you to track changes in files and collaborate with others on your code. In addition, the web platform GitHub makes it easy for people to share their code with you on a public server. As I make improvements to the code, you can easily update your local copy, file bug reports, or suggest code changes.

如果选择用 git 的话,第一步就是要确定安装了(git-scm.com/downloads)。

然后,打开一个终端(或命令提示符,在窗口中称为):

  • 在 Windows 10 上,右键单击开始菜单按钮,然后选择命令提示符。
  • 在 macOS X 上,按 Cmd + Space 打开聚光灯搜索,然后输入terminal,点击进入
  • 在 Ubuntu、Linux/Unix 和朋友上,按 Ctrl + Alt + T 。在红帽上,右键单击桌面并从菜单中选择打开终端。

导航到要下载代码的目录:

cd Desktop

然后,您可以通过键入以下内容来获取最新代码的本地副本:

git clone https://github.com/PacktPublishing/Machine-Learning-for-OpenCV-Second-Edition.git

这将在名为OpenCV-ML的文件夹中下载最新的代码。

过一段时间,代码可能会在线更改。在这种情况下,您可以通过在OpenCV-ML目录中运行以下命令来更新您的本地副本:

git pull origin master

掌握 Python 的 Anaconda 发行版

Anaconda 是 Continuum Analytics 为科学计算开发的免费 Python 发行版。它可以跨 Windows、Linux 和 macOS X 平台工作,并且是免费的,甚至可以用于商业用途。然而,它最好的一点是它附带了许多预安装的软件包,这些软件包对于数据科学、数学和工程来说是必不可少的。这些包包括以下内容:

  • NumPy:Python 中科学计算的基础包,为多维数组、高级数学函数和伪随机数生成器提供功能
  • SciPy:Python 中科学计算的函数集合,提供高级线性代数例程,…

在 conda 环境中安装 OpenCV

我们将执行以下步骤来安装 OpenCV:

  1. 在终端中,导航到下载以下代码的目录:
$ cd Desktop/OpenCV-ML
  1. 然后,运行以下命令创建一个基于 Python 3.6 的 conda 环境,该环境还将一次性安装environment.yml文件(可在 GitHub 存储库中获得)中列出的所有必要的包:
$ conda create env -f environment.yml
  1. 你也可以看看下面的environment.yml文件:
name: OpenCV-ML
channels:
  - conda-forge
dependencies:
  - python==3.6
  - numpy==1.15.4
  - scipy==1.1.0
  - scikit-learn==0.20.1
  - matplotlib
  - jupyter==1.0
  - notebook==5.7.4
  - pandas==0.23.4
  - theano
  - keras==2.2.4
  - mkl-service==1.1.2
  - pip
  - pip:
    - opencv-contrib-python==4.1.0.25

Notice that the environment’s name will be OpenCV-ML. This code will use the conda-forge channel to download all the conda based dependencies and use pip to install OpenCV 4.0 (along with opencv_contrib).

  1. 要激活环境,请根据您的平台键入以下内容之一:
$ source activate OpenCV-ML  # on Linux / Mac OS X
$ activate OpenCV-ML         # on Windows
  1. 当我们关闭终端时,会话将被停用,因此下次我们打开新的终端时,必须再次运行最后一个命令。我们还可以手动关闭环境:
$ source deactivate  # on Linux / Mac OS X
$ deactivate         # on Windows

完成了!让我们验证所有这些安装是否成功。

验证安装

仔细检查我们的安装是个好主意。当我们的终端仍然打开时,我们启动 IPython,这是一个运行 Python 命令的交互式外壳:

$ ipython

接下来,确保您正在运行(至少)Python 3.6,而不是 Python 2.7。您可能会在 IPython 的欢迎消息中看到版本号。如果没有,您可以运行以下命令:

In [1]: import sys...     print(sys.version)        3.6.0 | packaged by conda-forge | (default, Feb 9 2017, 14:36:55) [GCC 4.8.2 20140120 (Red Hat 4.8.2-15)]

现在尝试导入 OpenCV,如下所示:

In [2]: import cv2

您应该不会收到错误消息。然后,尝试找出版本号,如下所示:

In [3]: cv2.__version__Out[3]: '4.0.0'

确保 OpenCV 的版本…

了解 OpenCV 的 ml 模块

从 OpenCV 3.1 开始,OpenCV 中所有与机器学习相关的功能都被归入ml模块。对于 C++ API 来说,这种情况已经持续了很长时间。通过显示ml模块中的所有功能,您可以一窥接下来会发生什么:

In [4]: dir(cv2.ml)
Out[4]: ['ANN_MLP_ANNEAL',
 'ANN_MLP_BACKPROP',
 'ANN_MLP_GAUSSIAN',
 'ANN_MLP_IDENTITY',
 'ANN_MLP_LEAKYRELU',
 'ANN_MLP_NO_INPUT_SCALE',
 'ANN_MLP_NO_OUTPUT_SCALE',
 ...
 '__spec__']

If you have installed an older version of OpenCV, the ml module might not be present. For example, the k-nearest neighbor algorithm (which we will talk about in Chapter 3, First Steps in Supervised Learning) used to be called cv2.KNearest() but is now called cv2.ml.KNearest_create(). In order to avoid confusion throughout the book, I recommend using OpenCV 4.0.

这一切都很好,但你现在会想为什么你甚至应该学习机器学习,以及它的应用是什么?让我们在下一节回答这个问题。

机器学习的应用

机器学习、人工智能、深度学习和数据科学是我认为将改变我们一直以来看待事物的方式的四个术语。让我们看看我是否能说服你我为什么这么相信。

从让计算机学习如何玩围棋并击败同一个游戏的世界冠军,到使用同一个分支仅仅通过观察一个人的大脑的电脑断层扫描来检测他是否有肿瘤,机器学习在每个领域都留下了印记。我参与的项目之一是利用机器学习来确定火电厂锅炉水冷壁管的剩余寿命周期。所提出的解决方案通过使用…

OpenCV 4.0 有什么新功能?

所以,我们来到了第一章的最后一节。我将保持简短和中肯,因为你作为一个读者可以安全地跳过它。我们讨论的话题是 OpenCV 4.0

OpenCV 4.0 是 OpenCV 经过三年半的努力和 bug 修复的结果,最终在 2018 年 11 月发布。在本节中,我们将了解 OpenCV 4.0 中的一些主要变化和新功能:

  • 随着 OpenCV 4.0 的发布,OpenCV 正式成为 C++11 库。这意味着当您试图编译 OpenCV 4.0 时,您必须确保系统中存在符合 C++11 的编译器。
  • 延续上一点,删除了很多 C 语言的 API。受到影响的一些模块包括视频输入输出模块(videoio)、物体检测模块(objdetect)等。XML、YAML 和 JSON 的文件 IO 也删除了 C API。
  • OpenCV 4.0 在 DNN 模块(深度学习模块)中也有很多改进。增加了 ONNX 支持。英特尔 OpenVINO 也标志着其在全新 OpenCV 版本中的出现。我们将在后面的章节中更详细地探讨这个问题。
  • OpenCL 加速已经在 AMD 和 NVIDIA GPUs 上修复。
  • 还增加了 OpenCV Graph API,这是一个高效的图像处理和其他操作的引擎。
  • 正如在每一个 OpenCV 版本中一样,为了提高性能,有很多变化。还增加了一些新功能,如二维码检测和解码。

简而言之,OpenCV 4.0 有很多变化,它们有自己的用途。例如,ONNX 支持有助于模型跨各种语言和框架的可移植性,OpenCL 减少了计算机视觉应用程序的运行时间,Graph API 有助于提高应用程序的效率,OpenVINO 工具包使用英特尔的处理器和模型动物园来提供高效的深度学习模型。在后面的章节中,我们将主要关注 OpenVINO 工具包和 DLDT 以及加速计算机视觉应用。但是,我也应该在这里指出,OpenCV 3.4.4 和 OpenCV 4.0.0 都在高速修改以修复 bug。因此,如果您打算在任何应用程序中使用它们中的任何一个,请准备好修改您的代码和安装,以纳入所做的更改。同样,OpenCV 4.0.1 和 OpenCV 3.4.5 也是在前几个月内推出的。

摘要

在这一章中,我们在高抽象层次上讨论了机器学习:它是什么,为什么它很重要,以及它能解决什么样的问题。我们了解到机器学习问题有三种形式:监督学习、无监督学习和强化学习。我们谈到了监督学习的突出性,认为这个领域可以进一步分为两个子领域:分类和回归。分类模型允许我们将对象分类为已知的类别(例如将动物分类为猫和狗),而回归分析可用于预测目标变量的连续结果(例如二手车的销售价格)。

我们还学习了如何使用建立数据科学环境…

二、在 OpenCV 中处理数据

既然我们已经激起了对机器学习的兴趣,现在是时候深入研究构成典型机器学习系统的不同部分了。

太多时候,你会听到有人抛出这样一句话, J 必须将机器学习应用到你的数据中!,好像那会瞬间解决你所有的问题。你可以想象这种情况的现实要复杂得多,尽管,我会承认,如今,仅仅通过从互联网上剪切和粘贴几行代码,就可以非常容易地构建自己的机器学习系统。然而,要建立一个真正强大和有效的系统,必须牢牢掌握基本概念,并深入了解每种方法的优缺点。所以,如果你还不认为自己是机器学习专家,不要担心。好事需要时间。

早些时候,我将机器学习描述为人工智能的一个子领域。这可能是真的——主要是出于历史原因——但大多数情况下,机器学习只是为了理解数据。因此,将机器学习视为数据科学的一个分支可能更合适,在这个分支中,我们构建数学模型来帮助我们理解数据。

因此,这一章是关于数据的。我们想学习数据如何与机器学习相适应,以及如何使用我们选择的工具来处理数据:OpenCV 和 Python。

在本章中,我们将涵盖以下主题:

  • 了解机器学习工作流程
  • 了解培训数据和测试数据
  • 学习如何使用 OpenCV 和 Python 加载、存储、编辑和可视化数据

技术要求

可以从以下链接查阅本章代码:github . com/PacktPublishing/Machine-Learning-for-OpenCV-Second-Edition/tree/master/chapter 02

以下是软件和硬件要求的总结:

  • 您将需要 OpenCV 版本 4.1.x (4.1.0 或 4.1.1 都可以)。
  • 您将需要 Python 3.6 版本(任何 Python 3 . x 版本都可以)。
  • 您将需要 Anaconda Python 3 来安装 Python 和所需的模块。
  • 除了这本书,你可以使用任何操作系统——苹果操作系统、视窗操作系统和基于 Linux 的操作系统。我们建议您的系统中至少有 4 GB 内存。
  • 你不需要一个图形处理器来运行本书提供的代码。

了解机器学习工作流程

如前所述,机器学习就是建立数学模型来理解数据。当我们赋予机器学习模型调整其内部参数的能力时,学习方面就进入了这个过程;我们可以调整这些参数,以便模型更好地解释数据。从某种意义上说,这可以理解为模型从数据中学习。一旦模型学到了足够的东西——无论这意味着什么——我们就可以要求它解释新观察到的数据。

下图说明了一个典型的分类过程:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们一步一步地分解它。

首先要注意的是,机器学习问题总是被分成(至少)两个不同的阶段:

  • 一个训练阶段,在此期间我们的目标是在一组我们称之为训练数据集的数据上训练一个机器学习模型
  • 测试阶段,在此期间,我们在一组从未见过的新数据上评估所学习的(或最终确定的)机器学习模型,我们称之为测试数据集

将我们的数据分成训练集和测试集的重要性不可低估。我们总是在一个独立的测试集上评估我们的模型,因为我们有兴趣知道我们的模型对新数据的概括程度。最后,这不就是学习的全部吗——无论是机器学习还是人类学习?回想一下你自己还是学生时的学校:作为家庭作业的一部分,你必须解决的问题永远不会在期末考试中以完全相同的形式出现。同样的审查应该适用于机器学习模型;我们对我们的模型能多好地记住一组数据点(比如一个家庭作业问题)不太感兴趣,但我们想知道我们的模型将如何利用他们所学的知识来解决新问题(比如期末考试中出现的问题)并解释新的数据点。

The workflow of an advanced machine learning problem will typically include a third set of data termed a validation dataset. For now, this distinction is not important. A validation set is typically formed by further partitioning the training dataset. It is used in advanced concepts such as model selection, which we will talk about in Chapter 11, Selecting the Right Model with Hyperparameter Tuning, when we have become proficient in building machine learning systems.

接下来要注意的是,机器学习实际上是关于数据的。数据以原始形式进入前面描述的工作流图——无论这意味着什么——并在培训和测试阶段使用。数据可以是任何东西,从图像和电影到文本文档和音频文件。因此,在其原始形式中,数据可能由像素、字母、单词组成,甚至更糟:纯比特。很容易看出,这种原始形式的数据可能不太方便处理。相反,我们必须找到方法预处理数据,使其成为易于解析或使用数据的形式。

数据预处理分为两个阶段:

  • 特征选择:这是识别数据中重要属性(或特征)的过程。图像的可能特征可能是边缘、角或脊的位置。您可能已经熟悉 OpenCV 提供的一些更高级的特征描述符,例如加速健壮特征 ( SURF )或方向梯度直方图 ( HOG )。尽管这些特性可以应用于任何图像,但对于我们的特定任务来说,它们可能并不那么重要(或工作得那么好)。例如,如果我们的任务是区分干净的水和脏的水,最重要的特征可能是水的颜色,SURF 或 HOG 特征的使用可能对我们没有太大帮助。
  • 特征提取:这是将原始数据转化为期望的特征空间的实际过程。一个例子是哈里斯算子,它允许我们提取图像中的角点(也就是一个选定的特征)。

一个更高级的话题是发明信息特征的过程,这被称为特征工程。毕竟,在人们能够从流行的功能中进行选择之前,必须有人先发明它们。这对于我们算法的成功往往比算法本身的选择更重要。我们将在第四章、中详细讨论特征工程,代表数据和工程特征

Don’t let naming conventions confuse you! Sometimes, feature selection and feature extraction are hard to distinguish, mainly because of how things are named. For example, SURF stands for both the feature extractor as well as the actual name of the features. The same is true for the S****cale-Invariant Feature Transform (SIFT), which is a feature extractor that yields what is known as SIFT features. Unfortunately, both the algorithms are patented and cannot be used for commercial purposes. We won’t be sharing any code about either algorithms.

最后要说明的一点是,在监督学习中,每个数据点都必须有一个标签。标签标识属于某一类事物(如猫或狗)或具有某一价值(如房价)的数据点。说到底,有监督的机器学习系统的目标是预测测试集中所有数据点的标签(如上图所示)。我们通过学习训练数据中的规律性,使用随之而来的标签,然后在测试集上测试我们的性能来做到这一点。

因此,要构建一个运行良好的机器学习系统,我们首先必须涵盖如何加载、存储和操作数据。用 Python 在 OpenCV 中你是怎么做到的?

用 OpenCV 和 Python 处理数据

数据世界充满了各种各样的数据类型。这有时会使用户很难区分用于特定值的数据类型。在这里,我们将尝试通过将除标量值之外的所有内容都视为数组来保持简单,标量值将保留其标准数据类型。因此,图像将成为 2D 阵列,因为它们有宽度和高度。1D 阵列可以是强度随时间变化的声音片段。

如果你大部分时间都在使用 OpenCV 的 C++ 应用程序编程接口 ( API )并计划继续这样做,你可能会发现用 C++处理数据会有点痛苦。不仅要处理…

开始新的 IPython 或 Jupyter 会话

在我们拿到 NumPy 之前,我们需要打开一个 IPython 外壳或启动一个 Jupyter 笔记本:

  1. 像我们在上一章中所做的那样打开一个终端,并导航到OpenCV-ML目录:
 $ cd Desktop/OpenCV-ML
  1. 激活我们在上一章中创建的conda环境:
 $ source activate OpenCV-ML  # Mac OS X / Linux
 $ activate OpenCV-ML         # Windows
  1. 开始新的 IPython 或 Jupyter 会话:
 $ ipython           # for an IPython session
      $ jupyter notebook  # for a Jupyter session

如果您选择启动 IPython 会话,程序应该会向您发送如下欢迎消息:

$ ipython
Python 3.6.0 | packaged by conda-forge | (default, Feb 9 2017, 14:36:55) 
Type 'copyright', 'credits' or 'license' for more information
IPython 7.2.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: 

In [1]开始的一行是您键入常规 Python 命令的地方。此外,您还可以在键入变量和函数的名称时使用选项卡键,让 IPython 自动完成它们。

A limited number of Unix and macOS system shell commands work too—such as ls and pwd. You can run any shell command by prefixing it with !, such as !ping www.github.com. For more information, check out the official IPython reference at ipython.org/ipython-doc/3/interactive/tutorial.html.

如果您选择启动 Jupyter 会话,您的网络浏览器中应该会打开一个指向http://localhost:8888的新窗口。要创建新笔记本,请单击右上角的新建,然后选择笔记本(Python 3):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这将打开一个新窗口,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

标有In [ ]的单元格(看起来像前面的文本框)与 IPython 会话中的命令行相同。现在你可以开始输入你的 Python 代码了!

使用 Python 的 NumPy 包处理数据

如果您安装了 Anaconda,我假设您的虚拟环境中已经安装了 NumPy。如果您使用了 Python 的标准发行版或任何其他发行版,您可以前往www.numpy.org并按照那里提供的安装说明进行操作。

如前所述,如果你还不是 Python 专家,也没关系。谁知道呢,也许你刚刚从 OpenCV 的 C++ API 切换过来。这一切都很好。我想给大家简单介绍一下如何开始使用 NumPy。如果您是更高级的 Python 用户,您可以跳过这一部分。

一旦您熟悉了 NumPy,您会发现 Python 世界中的大多数科学计算工具都是围绕它构建的…

正在导入 NumPy

启动新的 IPython 或 Jupyter 会话后,您可以导入 NumPy 模块并验证其版本,如下所示:

In [1]: import numpy
In [2]: numpy.__version__
Out[2]: '1.15.4'

Recall that in the Jupyter Notebook, you can hit Ctrl + Enter to execute a cell once you have typed the command. Alternatively, Shift + Enter executes the cell and automatically inserts or selects the cell below it. Check out all of the keyboard shortcuts by clicking on Help | Keyboard Shortcut or take a quick tour by clicking on Help | User Interface Tour.

对于这里讨论的软件包部分,我建议使用 NumPy 版或更高版本。按照惯例,你会发现科学 Python 世界的大多数人都会使用np作为别名导入 NumPy:

In [3]: import numpy as np
In [4]: np.__version__
Out[4]: '1.15.4'

在本章和本书的其余部分,我们将坚持同样的惯例。

理解 NumPy 数组

你可能已经知道 Python 是一种弱类型语言。这意味着无论何时创建新变量,都不必指定数据类型。例如,以下内容将自动表示为整数:

In [5]: a = 5

您可以通过键入以下内容来再次检查:

In [6]: type(a)Out[6]: int

As the standard Python implementation is written in C, every Python object is basically a C structure in disguise. This is true even for integers in Python, which are actually pointers to compound C structures that contain more than just the raw integer value. Therefore, the default C data type used to represent Python integers will depend on your system architecture (that is, whether it is a 32-bit …

通过索引访问单个数组元素

如果您以前使用过 Python 的标准列表索引,那么您将不会在 NumPy 中发现许多索引问题。在 1D 数组中,可以通过在方括号中指定所需的索引来访问第I值(从零开始计算),就像 Python 列表一样:

In [13]: int_arr
Out[13]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [14]: int_arr[0]
Out[14]: 0
In [15]: int_arr[3]
Out[15]: 3

要从数组末尾开始索引,可以使用负索引:

In [16]: int_arr[-1]
Out[16]: 9
In [17]: int_arr[-2]
Out[17]: 8

切片数组还有其他一些很酷的技巧,如下所示:

In [18]: int_arr[2:5]  # from index 2 up to index 5 - 1
Out[18]: array([2, 3, 4])
In [19]: int_arr[:5]    # from the beginning up to index 5 - 1
Out[19]: array([0, 1, 2, 3, 4])
In [20]: int_arr[5:]    # from index 5 up to the end of the array
Out[20]: array([5, 6, 7, 8, 9])
In [21]: int_arr[::2]   # every other element
Out[21]: array([0, 2, 4, 6, 8])
In [22]: int_arr[::-1]  # the entire array in reverse order
Out[22]: array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])

我鼓励你自己玩这些阵列!

The general form of slicing arrays in NumPy is the same as it is for standard Python lists. To access a slice of an array, x, use x[start:stop:step]. If any of these are unspecified, they default to the start=0stop=size of dimensionstep=1 values.

创建多维数组

数组不必局限于列表。事实上,它们可以有任意数量的维度。在机器学习中,我们通常至少处理 2D 数组,其中列索引代表特定特征的值,行包含实际的特征值。

有了 NumPy,从头开始创建多维数组就很容易了。假设我们要创建一个三行五列的数组,所有元素都初始化为零。如果我们不指定数据类型,NumPy 将默认使用浮点:

In [23]: arr_2d = np.zeros((3, 5))...      arr_2dOut[23]: array([[0., 0., 0., 0., 0.],                [0., 0., 0., 0., 0.],                [0., 0., 0., 0., 0.]])

你可能从你的 OpenCV 时代就知道这一点…

在 Python 中加载外部数据集

感谢 SciPy 社区,有很多资源可以让我们获得一些数据。

一个特别有用的资源是 scikit-learnsklearn.datasets包。这个包预装了一些小数据集,不需要我们从外部网站下载任何文件。这些数据集包括以下内容:

  • load_boston:波士顿数据集包含波士顿不同郊区的房价,以及几个有趣的特征,如城镇人均犯罪率、住宅用地比例和非零售商业数量
  • load_iris:鸢尾数据集包含三种不同类型的鸢尾花(濑户鸢尾、云芝和弗吉尼亚鸢尾),以及描述萼片和花瓣的宽度和长度的四个特征
  • load_diabetes:糖尿病数据集让我们可以根据患者年龄、性别、体重指数、平均血压和六项血清指标等特征,将患者分为糖尿病患者和非糖尿病患者
  • load_digits:数字数据集包含数字 0-9 的 8×8 像素图像
  • load_linnerud:Linnerud 数据集包含 3 个生理变量和 3 个运动变量,在健身俱乐部对 20 名中年男性进行了测量

此外,scikit-learn 允许我们直接从外部存储库下载数据集,例如:

  • fetch_olivetti_faces:Olivetti 人脸数据集包含 10 幅不同的图像,每幅图像包含 40 个不同的对象
  • fetch_20newsgroups:20 个新闻组数据集包含大约 18,000 个新闻组帖子,涉及 20 个主题

更好的是,可以在openml.org直接从机器学习数据库下载数据集。例如,要下载鸢尾花数据集,只需键入以下内容:

In [1]: from sklearn import datasets
In [2]: iris = datasets.fetch_openml('iris', version=1)
In [3]: iris_data = iris['data']
In [4]: iris_target = iris['target']

鸢尾花数据库包含总共具有4特征的150样品——萼片长度、萼片宽度、花瓣长度和花瓣宽度。数据分为三类——濑户鸢尾、彩叶鸢尾和北美鸢尾。数据和标签在两个独立的容器中交付,我们可以按如下方式进行检查:

In [5]: iris_data.shape 
Out[5]: (150, 4)
In [6]: iris_target.shape 
Out[6]: (150,)

在这里,我们可以看到iris_data包含150个样本,每个样本都有4特征(这就是为什么数字 4 在形状中)。标签储存在iris_target中,每个样品只有一个标签。

我们可以进一步检查所有目标的值,但我们不想只打印它们。相反,我们有兴趣看到所有不同的目标值,这在 NumPy 中很容易做到:

In [7]: import numpy as np
In [8]: np.unique(iris_target) # Find all unique elements in array
Out[8]: array(['Iris-setosa', 'Iris-versicolor', 'Iris-virginica'], dtype=object)

Another Python library for data analysis that you should have heard about is pandas (pandas.pydata.org). pandas implements several powerful data operations for both databases and spreadsheets. However great the library, at this point, pandas is a bit too advanced for our purposes.

使用 Matplotlib 可视化数据

如果我们不知道如何查看数据,那么知道如何加载数据的用处有限。谢天谢地,有 Matplotlib

Matplotlib 是一个建立在 NumPy 数组上的多平台数据可视化库——看,我向你保证 NumPy 会再次出现。它是由约翰·亨特在 2002 年构思的,最初是作为 IPython 的补丁设计的,以便能够从命令行进行交互式 MATLAB 风格的绘图。近年来,更新、更闪亮的工具不断涌现,最终取代了 Matplotlib(如 R 语言中的ggplotggvis),但 Matplotlib 作为一个久经考验的跨平台图形引擎仍然至关重要。

正在导入 Matplotlib

你可能又走运了:如果你遵循上一章中概述的建议,安装了 Python Anaconda 堆栈,那么你已经安装了 Matplotlib,并准备好了。否则,您可能需要访问matplotlib.org了解安装说明。

就像我们使用 NumPy 的np简写一样,我们将使用 Matplotlib 导入的一些标准简写:

In [1]: import matplotlib as mpl
In [2]: import matplotlib.pyplot as plt

plt界面是我们最常使用的,我们将在本书中看到。

制作一个简单的情节

不用多说,让我们创建我们的第一个情节。

假设我们想要产生正弦函数的简单线图sin(x)。我们希望在 x 轴上的所有点对函数进行评估,其中0 < x < 10。我们将使用 NumPy 的linspace功能在 x 轴上创建一个线性间距,从x010,总共有100个采样点:

In [3]: import numpy as npIn [4]: x = np.linspace(0, 10, 100)

我们可以使用 NumPy 的sin函数在所有点x评估sin函数,并通过调用pltplot函数来可视化结果:

In [5]: plt.plot(x, np.sin(x))

你自己试过吗?发生了什么事?有什么发现吗?

问题是,根据您运行该脚本的位置,您可能不会…

可视化外部数据集中的数据

作为本章的最后一个测试,让我们可视化一些来自外部数据集的数据,例如 scikit-learn 的digits数据集。

具体来说,我们需要三种可视化工具:

  • sci kit-了解实际数据
  • 用于数据处理的数字
  • Matplotlib

所以,让我们从导入所有这些开始:

In [1]: import numpy as np
...     from sklearn import datasets
...     import matplotlib.pyplot as plt
...     %matplotlib inline

第一步是实际加载数据:

In [2]: digits = datasets.load_digits()

如果我们没记错的话,digits应该有两个不同的字段:包含实际图像数据的data字段和包含图像标签的target字段。与其相信我们的记忆,我们应该简单地调查digits对象。我们通过键入它的名称,添加一个句点,然后点击选项卡键:digits.<TAB>来实现。这将揭示digits对象还包含一些其他字段,例如名为images的字段。imagesdata这两个字段似乎只是形状不同:

In [3]: print(digits.data.shape)
... print(digits.images.shape)
Out[3]: (1797, 64)
 (1797, 8, 8)

在这两种情况下,第一维对应于数据集中的图像数量。然而,data将所有像素排列在一个大向量中,而images保留了每个图像的 8×8 空间排列。

因此,如果我们想要绘制单个图像,则images字段会更合适。首先,我们使用 NumPy 的数组切片从数据集中获取单个图像:

In [4]: img = digits.images[0, :, :]

这里,我们说我们想要抓取 1,797 项长数组中的第一行和所有对应的 8 x 8 = 64 像素。然后,我们可以使用pltimshow功能绘制图像:

In [5]: plt.imshow(img, cmap='gray') 
...     plt.savefig('figures/02.04-digit0.png') 
Out[5]: <matplotlib.image.AxesImage at 0x7efcd27f30f0>

前面的命令给出了以下输出。请注意,图像是模糊的,因为我们已经将它调整到更大的尺寸。原始图像的大小仅为 8 x 8:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此外,我还用cmap参数指定了一个颜色图。默认情况下,Matplotlib 使用 MATLAB 的默认颜色图 jet 。然而,在灰度图像的情况下,灰色彩色图更有意义。

最后,我们可以使用pltsubplot功能绘制整数个数字样本。subplot函数与 MATLAB 中的相同,我们指定行数、列数和当前子图索引(从1开始计数)。我们将使用for循环迭代数据集中的前 10 个图像,每个图像都被分配了自己的子情节:

In [6]: plt.figure(figsize=(14,4))
...
...     for image_index in range(10):
...         # images are 0-indexed, but subplots are 1-indexed
...         subplot_index = image_index + 1
...         plt.subplot(2, 5, subplot_index)
...         plt.imshow(digits.images[image_index, :, :], cmap='gray')

这将导致以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Another great resource for all sorts of datasets is the machine learning repository of my alma mater, the University of California, Irvine: archive.ics.uci.edu/ml/index.php.

用 C++中 OpenCV 的 TrainData 容器处理数据

为了完整起见,对于坚持使用 OpenCV 的 C++ API 的人来说,让我们在 OpenCV 的TrainData容器上做一个快速的迂回,它允许我们从.csv文件加载数值数据。

除此之外,在 C++中,ml模块包含一个名为TrainData的类,它提供了一个在 C++中处理数据的容器。其功能仅限于从.csv文件(包含逗号分隔值)中读取(最好是)数字数据。因此,如果您想要处理的数据是以一个组织整齐的.csv文件的形式出现的,那么这个类将为您节省大量时间。如果你的数据来自不同的来源,恐怕你最好的选择可能是手工创建一个.csv文件,使用…

摘要

在本章中,我们讨论了处理机器学习问题的典型工作流:如何从原始数据中提取信息特征,如何使用数据和标签来训练机器学习模型,以及如何使用最终确定的模型来预测新的数据标签。我们了解到,将数据拆分为训练集和测试集是非常重要的,因为这是了解模型对新数据点的泛化能力的唯一方法。

在软件方面,我们显著提高了 Python 技能。我们学习了如何使用 NumPy 数组存储和操作数据,以及如何使用 Matplotlib 进行数据可视化。我们讨论了 scikit-learn 及其许多有用的数据资源。最后,我们还讨论了 OpenCV 自己的TrainData容器,它为 OpenCV 的 C++ API 的用户提供了一些缓解。

有了这些工具,我们现在准备实现我们的第一个真正的机器学习模型!在下一章中,我们将重点讨论监督学习及其两个主要问题类别,分类和回归。

三、监督学习的第一步

这是你一直在等待的时刻,不是吗?

我们已经覆盖了所有的基础——我们有一个运行良好的 Python 环境,我们安装了 OpenCV,我们知道如何用 Python 处理数据。现在,是时候构建我们的第一个机器学习系统了!还有什么比专注于最常见和最成功的机器学习类型之一:监督学习更好的开始方式呢?

从上一章中,我们已经知道监督学习是通过使用附带的标签来学习训练数据中的规则,以便我们可以预测一些新的、从未见过的测试数据的标签。在这一章中,我们想深入一点,学习如何将我们的理论知识…

技术要求

可以通过以下链接查阅本章代码:github . com/PacktPublishing/Machine-Learning-for-OpenCV-Second-Edition/tree/master/chapter 03

以下是软件和硬件要求的全球总结:

  • 您将需要 OpenCV 版本 4.1.x (4.1.0 或 4.1.1 都可以)。
  • 您将需要 Python 3.x 版本(任何 Python 3.x 版本都可以)。
  • 您将需要 Anaconda Python 3 来安装 Python 和所需的模块。
  • 这本书可以使用任何操作系统——苹果操作系统、视窗操作系统和基于 Linux 的操作系统。我们建议您的系统中至少有 4 GB 内存。
  • 运行本书提供的代码不需要 GPU。

理解监督学习

我们之前已经确定,监督学习的目标总是预测数据的标签(或目标值)。然而,根据这些标签的性质,监督学习可以有两种不同的形式:

  • 分类:每当我们使用数据预测类别时,监督学习就被称为分类。这方面的一个很好的例子是,当我们试图预测一个图像是否包含一只猫或一只狗。在这里,数据的标签是分类的,不是一个就是另一个,但绝不是类别的混合。例如,一张图片包含一只猫或一只狗,从不包含 50%的猫和 50%的狗(在你问之前,不,这里我们不考虑卡通人物 CatDog 的图片),以及我们的工作…

看看 OpenCV 中的监督学习

如果我们不能将其付诸实践,仅仅知道监督学习是如何工作的是没有任何用处的。值得庆幸的是,OpenCV 为其所有的统计学习模型提供了一个相当简单的界面,其中包括所有的监督学习模型。

在 OpenCV 中,每一个机器学习模型都源自cv::ml::StatModel基类。这是一个花哨的说法,说如果我们想在 OpenCV 中使用机器学习模型,我们必须提供StatModel告诉我们的所有功能。这包括训练模型的方法(称为train)和测量模型性能的方法(称为calcError)。

In Object-Oriented Programming (OOP), we deal primarily with objects or classes. An object consists of several functions, called methods, as well as variables, called members or attributes. You can learn more about OOP in Python at docs.python.org/3/tutorial/classes.html.

由于软件的这种组织,在 OpenCV 中建立机器学习模型总是遵循相同的逻辑,我们将在后面看到:

  • 初始化:我们通过名称来调用模型,创建一个模型的空实例。
  • 设置参数:如果模型需要一些参数,我们可以通过 setter 方法进行设置,每个模型可以不同。例如,要使 k-NN 算法工作,我们需要指定其开放参数 k (我们将在后面找到)。
  • 训练模型:每个模型都必须提供一个叫做train的方法,用来将模型拟合到一些数据。
  • 预测新标签:每个模型都必须提供一个叫做predict的方法,用来预测新数据的标签。
  • 给模型打分:每个模型都必须提供一个叫做calcError的方法,用来衡量性能。这种计算可能对每个模型都不同。

Because OpenCV is a vast and community-driven project, not every algorithm follows these rules to the extent that we as users might expect. For example, the k-NN algorithm does most of its work in a findNearest method, although predict still works. We will make sure to point out these discrepancies as we work through different examples.

由于我们会偶尔使用 scikit-learn 来实现一些 OpenCV 没有提供的机器学习算法,因此值得指出的是,scikit-learn 中的学习算法遵循几乎相同的逻辑。最显著的区别是 scikit-learn 在初始化步骤中设置了所有必需的模型参数。另外,它调用训练函数fit,而不是train,调用评分函数score,而不是calcError

用评分函数测量模型性能

构建机器学习系统最重要的部分之一是找到一种方法来衡量模型预测的质量。在现实生活中,一个模型很少会把所有事情都做好。从前面的章节中,我们知道我们应该使用测试集中的数据来评估我们的模型。但是这到底是怎么回事呢?

简短但不太有用的答案是,这取决于模型。人们提出了各种各样的评分函数,可以用来评估所有可能场景中的训练模型。好消息是,它们中的许多实际上是 scikit-learn 的metrics模块的一部分。

让我们快速了解一些最重要的评分功能。…

使用准确度、精确度和召回率对分类器进行评分

在只有两个不同类别标签的二进制分类任务中,有几种不同的方法来衡量分类性能。一些常见的指标如下:

  • accuracy_score:准确度计算测试集中已被正确预测的数据点的数量,并将该数量作为测试集大小的一部分返回。坚持图片分类为猫或狗的例子,准确性表示图片被正确分类为包含猫或狗的部分。这是分类器最基本的评分功能。
  • precision_score : Precision 描述分类器不将包含狗的图片标记为猫的能力。换句话说,在测试集中分类器认为包含猫的所有图片中,精度是实际包含猫的图片的分数。
  • recall_score:回忆(或敏感度)描述分类器检索包含猫的所有图片的能力。换句话说,在测试集中的所有猫的图片中,回忆是被正确识别为猫的图片的比例。

假设我们有一些ground truth(根据我们拥有的数据集正确)类标签不是 0 就是 1。我们可以使用 NumPy 的随机数生成器随机生成它们。显然,这意味着,每当我们重新运行代码时,都会随机生成新的数据点。然而,对于这本书的目的来说,这不是很有帮助,因为我希望您能够运行代码,并总是得到与我相同的结果。一个很好的技巧是固定随机数发生器的种子。这将确保生成器在每次运行脚本时以相同的方式初始化:

  1. 我们可以使用以下代码修复随机数生成器的种子:
In [1]: import numpy as np
In [2]: np.random.seed(42)
  1. 然后,我们可以通过在范围(0,2)中选择随机整数来生成五个随机标签,它们要么是 0,要么是 1:
In [3]: y_true = np.random.randint(0, 2, size=5)
...     y_true
Out[3]: array([0, 1, 0, 0, 0])

In the literature, these two classes are sometimes also called positives (all data points with the class label, 1) and negatives (all other data points).

让我们假设我们有一个分类器,试图预测前面提到的类标签。为了论证,假设分类器不是很聪明,总是预测标签,1。我们可以通过硬编码预测标签来模拟这种行为:

In [4]: y_pred = np.ones(5, dtype=np.int32)
...     y_pred
Out[4]: array([1, 1, 1, 1, 1], dtype=int32)

我们预测的准确性如何?

如前所述,准确性计算测试集中已被正确预测的数据点的数量,并将该数量作为测试集大小的一部分返回。我们只正确预测了第二个数据点(这里真正的标签是1)。在所有其他情况下,真正的标签是0,然而我们预测1。因此,我们的精度应该是 1/5 或 0.2。

准确性度量的简单实现可能会汇总预测的类标签与真实的类标签匹配的所有情况:

In [5]: test_set_size = len(y_true)
In [6]: predict_correct = np.sum(y_true == y_pred)
In [7]: predict_correct / test_set_size
Out[7]: 0.2

scikit-learn 的metrics模块提供了更智能、更方便的实现:

In [8]: from sklearn import metrics
In [9]: metrics.accuracy_score(y_true, y_pred)
Out[9]: 0.2

这并不太难,是吗?然而,要理解精度和召回率,我们需要对ⅰ型和ⅱ型错误有一个大致的了解。我们回想一下,带有类标签1的数据点通常被称为阳性,带有类标签0(或-1)的数据点通常被称为阴性。然后,对特定数据点进行分类可以有四种可能的结果之一,如下面的混淆矩阵所示:

| | 是真正的正 | 是真正的负 |
| 预测阳性 | 正确肯定 | 假阳性 |
| 预测负值 | 假阴性 | 正确否定 |

让我们把它分解一下。如果一个数据点真的是正的,并且我们预测是正的,那么我们就做对了!在这种情况下,结果被称为真阳性。如果我们认为数据点是正的,但实际上是负的,我们就错误地预测了正的(因此有了这个术语,假阳性)。类似地,如果我们认为数据点是负的,但它确实是正的,我们就错误地预测了负的(假负的)。最后,如果我们预测了一个负数,而数据点确实是负数,我们就找到了一个真正的负数。

In statistical hypothesis testing, false positives are also known as type I errors and false negatives are also known as type II errors.

让我们根据模型数据快速计算这四个指标。我们有一个真正的正,其中真正的标签是1,我们预测1:

In [10]: truly_a_positive = (y_true == 1)
In [11]: predicted_a_positive = (y_pred == 1)
In [12]: true_positive = np.sum(predicted_a_positive * truly_a_positive )
...      true_positive
Out[12]: 1

同样,假阳性是我们预测的1,但ground truth实际上是0:

In [13]: false_positive = np.sum((y_pred == 1) * (y_true == 0))
...      false_positive
Out[13]: 4

我相信现在你已经掌握了窍门。但是我们需要做数学才能知道预测的否定吗?我们不太聪明的分类器从来没有预测过0,所以(y_pred == 0)永远不会是真的:

In [14]: false_negative = np.sum((y_pred == 0) * (y_true == 1))
...      false_negative
Out[14]: 0
In [15]: true_negative = np.sum((y_pred == 0) * (y_true == 0))
...      true_negative
Out[15]: 0

让我们也画出混淆矩阵:

| | 是真正的正 | 是真正的负 |
| 预测阳性 | one | four |
| 预测负值 | Zero | Zero |

为了确保我们做对了所有的事情,让我们再计算一次精确度。准确性应该是真阳性的数量加上真阴性的数量(也就是我们得到的所有正确的东西)除以数据点的总数:

In [16]: accuracy = (true_positive + true_negative) / test_set_size
...      accuracy
Out[16]: 0.2

成功!然后给出精度,即真阳性数除以所有真预测数:

In [17]: precision = true_positive / (true_positive + false_positive)
...      precision
Out[17]: 0.2

事实证明,在我们的情况下,精确度并不比准确度好。让我们用 scikit-learn 来检查我们的数学:

In [18]: metrics.precision_score(y_true, y_pred)
Out[18]: 0.2

最后,recall作为我们正确分类为阳性的所有阳性的分数给出:

In [19]: recall = true_positive / (true_positive + false_negative)
...      recall
Out[19]: 1.0
In [20]: metrics.recall_score(y_true, y_pred)
Out[20]: 1.0

完美回忆!但是,回到我们的模型数据,应该很清楚,这个出色的回忆得分只是运气。由于在我们的模型数据集中只有一个1标签,并且我们碰巧正确地对它进行了分类,所以我们得到了一个完美的召回分数。这是否意味着我们的分类器是完美的?不是真的!但是,我们发现了三个有用的指标,似乎可以衡量我们分类性能的互补方面。

使用均方误差、解释方差和 R 平方对回归进行评分

就回归模型而言,我们的指标,如前所示,不再起作用了。毕竟,我们现在预测的是连续的输出值,而不是截然不同的分类标签。幸运的是,scikit-learn 提供了一些其他有用的评分功能:

  • mean_squared_error:回归问题最常用的误差度量是测量训练集中每个数据点的预测值和真实目标值之间的平方误差,所有数据点的平均值。
  • explained_variance_score:一个更复杂的度量是衡量一个模型能在多大程度上解释测试数据的变化或分散。通常,解释的数量…

使用分类模型预测类别标签

有了这些工具,我们现在可以进行第一个真正的分类示例。

以兰多姆维尔小镇为例,那里的人们对他们的两支运动队——兰多姆维尔红军和兰多姆维尔蓝军——非常着迷。红军已经存在很长时间了,人们喜欢他们。但是后来,一些外地的百万富翁出现了,买下了红军的最佳射手,并组建了一支新的球队,蓝军。令大多数红魔球迷不满的是,这位最佳射手将随蓝军赢得冠军。几年后,他将回到红军,尽管球迷们对他之前的职业选择有些反感,但他们永远不会原谅他。但无论如何,你可以看到为什么红魔的球迷不一定能和蓝军的球迷相处融洽。事实上,这两个粉丝群是如此的分裂,以至于他们甚至从来没有住在一起。我甚至听过这样的故事:一旦蓝军球迷搬进隔壁,红球迷就故意搬走。真实故事!

不管怎样,我们是新来的,正试着挨家挨户地向人们推销一些蓝调商品。然而,我们时不时会遇到一个心碎的红军球迷,他会因为我们卖蓝军的东西而对我们大喊大叫,并把我们赶出他们的草坪。不好听!完全避开这些房子,转而去拜访蓝军球迷,压力会小得多,也能更好地利用我们的时间。

自信我们可以学会预测红军球迷住在哪里,我们开始记录我们的遭遇。如果我们路过一个红军球迷的家,我们会在手边的城镇地图上画一个红色三角形;否则,我们画一个蓝色的正方形。过了一会儿,我们对每个人的居住地有了一个很好的了解:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然而,现在,我们接近了前面地图中标记为绿色圆圈的房子。我们应该敲他们的门吗?我们试图找到一些他们更喜欢哪支球队的线索(也许是挂在后门廊的球队旗帜),但我们看不到任何线索。我们怎么知道敲他们的门是否安全?

这个愚蠢的例子说明的正是监督学习算法可以解决的那种问题。我们有一堆观察数据(房子、它们的位置和颜色)组成了我们的训练数据。我们可以利用这些数据从经验中学习,这样,当我们面临预测新房子颜色的任务时,我们就可以做出明智的估计。

正如我们前面提到的,红魔的球迷对他们的球队真的很有激情,所以他们永远不会靠近蓝军球迷。难道我们不能利用这些信息,看看所有邻近的房子,找出什么样的风扇住在新房子里?

这正是 k-NN 算法要做的。

理解 k-NN 算法

k-NN 算法可以说是最简单的机器学习算法之一。这样做的原因是我们基本上只需要存储训练数据集。然后,为了预测一个新的数据点,我们只需要在训练数据集中找到最近的数据点:它的最近邻居。

简而言之,k-NN 算法认为一个数据点可能与其邻居属于同一类。想想看:如果我们的邻居是红魔球迷,我们可能也是红魔球迷;否则,我们早就搬走了。蓝军也是如此。

当然,有些街区可能更复杂一点。在这种情况下,我们不仅要考虑我们最近的邻居(其中 k=1 ,而是…

在 OpenCV 中实现 k-NN

使用 OpenCV,我们可以通过cv2.ml.KNearest_create()函数轻松创建 k-NN 模型。然后,构建模型包括以下步骤:

  1. 生成一些训练数据。

  2. 为给定的数字 k 创建一个 k-NN 对象。

  3. 找到我们要分类的新数据点的 k 最近邻。

  4. 通过多数票分配新数据点的类别标签。

  5. 画出结果。

我们首先导入所有必要的模块:用于 k-NN 算法的 OpenCV、用于数据处理的 NumPy 和用于绘图的 Matplotlib。如果你在 Jupyter 笔记本上工作,别忘了召唤%matplotlib inline魔法:

In [1]: import numpy as np
...     import cv2
...     import matplotlib.pyplot as plt
...     %matplotlib inline
In [2]: plt.style.use('ggplot')

生成训练数据

第一步是生成一些训练数据。为此,我们将使用 NumPy 的随机数生成器。如前一节所述,我们将修复随机数生成器的种子,这样重新运行脚本将始终生成相同的值:

In [3]: np.random.seed(42)

好了,现在我们开始吧。我们的训练数据到底应该是什么样的?

在前面的示例中,每个数据点都是城镇地图上的一所房子。每个数据点都有两个特征(即其在城镇地图上的位置的 xy 坐标)和一个类别标签(即,如果蓝调粉丝住在那里,则为蓝色正方形,如果红调粉丝住在那里,则为红色三角形)。

因此,可以表示单个数据点的特征…

训练分类器

与所有其他机器学习功能一样,k-NN 分类器是 OpenCV 3.1 ml模块的一部分。我们可以使用以下命令创建一个新的分类器:

In [15]: knn = cv2.ml.KNearest_create()

In older versions of OpenCV, this function might be called cv2.KNearest() instead.

然后,我们将训练数据传递给train方法:

In [16]: knn.train(train_data, cv2.ml.ROW_SAMPLE, labels)
Out[16]: True

在这里,我们必须告诉knn我们的数据是一个N×2数组(也就是说,每一行都是一个数据点)。成功后,功能返回True

预测新数据点的标签

knn提供的另一个真正有用的方法叫做findNearest。它可用于根据最近邻预测新数据点的标签。

得益于我们的generate_data功能,生成一个新的数据点其实真的很容易!我们可以把一个新的数据点想象成一个大小为1的数据集:

In [17]: newcomer, _ = generate_data(1)...      newcomerOut[17]: array([[91., 59.]], dtype=float32)

我们的函数也返回一个随机标签,但是我们对此不感兴趣。相反,我们想用我们训练好的分类器来预测它!我们可以告诉 Python 忽略带有下划线(_)的输出值。

让我们再看看我们的城镇地图。我们将像前面一样绘制训练集,但是…

使用回归模型预测连续结果

现在,让我们把注意力转向一个回归问题。我相信你现在可以在睡梦中背诵,回归是关于预测连续的结果,而不是预测离散的类别标签。

理解线性回归

最简单的回归模型叫做线性回归。线性回归背后的思想是用特征的线性组合来描述一个目标变量(比如波士顿房价——回想一下我们在第一章、机器学习的味道中研究的各种数据集)。

为了简单起见,我们只关注两个特性。假设我们想使用两个特征来预测明天的股价:今天的股价和昨天的股价。我们将今天的股价表示为第一特征, f 1 ,昨天的股价表示为 f 2 。那么,线性回归的目标就是学习两个权重系数, w 1w 2 ,这样我们就可以预测明天的股价如下:

这是

OpenCV 中的线性回归

在现实数据集上尝试线性回归之前,让我们了解如何使用cv2.fitLine函数将直线拟合到 2D 或三维点集:

  1. 让我们从生成一些点开始。我们将通过给位于线上的点添加噪声来生成它们外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传:
In [1]: import cv2
...     import numpy as np
...     import matplotlib.pyplot as plt
...     from sklearn import linear_model
...     from sklearn.model_selection import train_test_split
...     plt.style.use('ggplot')
...     %matplotlib inline
In [2]: x = np.linspace(0,10,100)
...     y_hat = x*5+5
...     np.random.seed(42)
...     y = x*5 + 20*(np.random.rand(x.size) - 0.5)+5
  1. 我们还可以使用以下代码来可视化这些点:
In [3]: plt.figure(figsize=(10, 6))
...     plt.plot(x, y_hat, linewidth=4)
...     plt.plot(x,y,'x')
...     plt.xlabel('x')
...     plt.ylabel('y')

这给了我们下图,红线是真实函数:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 接下来,我们将把这些点分成训练集和测试集。在这里,我们将数据分成 70:30 的比例,这意味着 70%的分数将用于培训,30%用于测试:
In [4]: x_train, x_test, y_train, y_test = train_test_split(x,y,test_size=0.3,random_state=42)
  1. 现在,让我们使用cv2.fitLine将一条线拟合到这个 2D 点集。此函数接受以下参数:
    • points:这是一条线必须拟合的一组点。
    • distType:这是 M 估计器使用的距离。
    • param:这是数值参数©,用于某些类型的距离。我们将它保持在 0,以便可以选择一个最佳值。
    • reps:这是原点到直线距离的精度。0.01对于reps来说是一个不错的默认值。
    • aeps:这是角度的精度。0.01对于aeps来说是一个不错的默认值。

For more information, have a look at the documentation.

  1. 让我们看看使用不同的距离类型选项会得到什么样的结果:
In [5]: distTypeOptions = [cv2.DIST_L2,\
...                 cv2.DIST_L1,\
...                 cv2.DIST_L12,\
...                 cv2.DIST_FAIR,\
...                 cv2.DIST_WELSCH,\
...                 cv2.DIST_HUBER]

In [6]: distTypeLabels = ['DIST_L2',\
...                 'DIST_L1',\
...                 'DIST_L12',\
...                 'DIST_FAIR',\
...                 'DIST_WELSCH',\
...                 'DIST_HUBER']

In [7]: colors = ['g','c','m','y','k','b']
In [8]: points = np.array([(xi,yi) for xi,yi in zip(x_train,y_train)])
  1. 我们还将使用 scikit-learn 的LinearRegression来拟合训练点,然后使用predict功能来预测它们的 y 值:
In [9]: linreg = linear_model.LinearRegression()
In [10]: linreg.fit(x_train.reshape(-1,1),y_train.reshape(-1,1))
Out[10]:LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None,normalize=False)
In [11]: y_sklearn = linreg.predict(x.reshape(-1,1))
In [12]: y_sklearn = list(y_sklearn.reshape(1,-1)[0])
  1. 我们使用reshape(-1,1)reshape(1,-1)将 NumPy 数组转换成列向量,然后再转换回行向量:
In [13]: plt.figure(figsize=(10, 6))
...      plt.plot(x, y_hat,linewidth=2,label='Ideal')
...      plt.plot(x,y,'x',label='Data')

...      for i in range(len(colors)):
...          distType = distTypeOptions[i]
...          distTypeLabel = distTypeLabels[i]
...          c = colors[i]

...          [vxl, vyl, xl, yl] = cv2.fitLine(np.array(points, dtype=np.int32), distType, 0, 0.01, 0.01)
...          y_cv = [vyl[0]/vxl[0] * (xi - xl[0]) + yl[0] for xi in x]
...          plt.plot(x,y_cv,c=c,linewidth=2,label=distTypeLabel)

...      plt.plot(x,list(y_sklearn),c='0.5',\
linewidth=2,label='Scikit-Learn API')
...      plt.xlabel('x')
...      plt.ylabel('y')
...      plt.legend(loc='upper left')

前面这段(也是很长的)代码的唯一目的是创建一个图表,用于比较使用不同距离测量获得的结果。

我们来看看剧情:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以清楚地看到,scikit-learn 的LinearRegression模型比 OpenCV 的fitLine功能表现要好很多。现在,让我们用 scikit-learn 的 API 来预测波士顿的房价。

用线性回归预测波士顿房价

为了更好地理解线性回归,我们希望构建一个简单的模型,该模型可以应用于最著名的机器学习数据集之一:波士顿房价数据集。在这里,目标是预测 20 世纪 70 年代波士顿几个街区的房屋价值,使用的信息包括犯罪率、财产税税率、到就业中心的距离和高速公路通达性。

正在加载数据集

我们可以再次感谢 scikit-learn 轻松访问数据集。我们首先导入所有必要的模块,就像之前一样:

In [14]: from sklearn import datasets
...      from sklearn import metrics

然后加载数据集是一个单行过程:

In [15]: boston = datasets.load_boston()

boston对象的结构与iris对象相同,如前面的命令所述。我们可以在'DESCR'获取更多数据集信息,并在'data'中找到所有数据,'feature_names'中找到所有要素名称,'filename'中找到波士顿 CSV 数据集的物理位置,'target'中找到所有目标值:

In [16]: dir(boston)
Out[16]: ['DESCR', 'data', 'feature_names', 'filename', 'target']

数据集总共包含506个数据点,每个数据点都有13特征:

In [17]: boston.data.shape
Out[17]: (506, 13)

当然,我们只有一个目标值,那就是房价:

In [18]: boston.target.shape
Out[18]: (506,)

训练模型

现在让我们创建一个LinearRegression模型,然后在训练集上进行训练:

In [19]: linreg = linear_model.LinearRegression()

在前面的命令中,我们希望将数据分成训练集和测试集。我们可以按照我们认为合适的方式进行拆分,但通常情况下,保留 10%到 30%用于测试是一个好主意。这里,我们选择 10%,使用test_size参数:

In [20]: X_train, X_test, y_train, y_test = train_test_split(...            boston.data, boston.target, test_size=0.1,...            random_state=42...      )

在 scikit-learn 中,train函数被称为fit,但在其他方面的表现与 OpenCV 中完全相同:

In [21]: linreg.fit(X_train, y_train)Out[21]: LinearRegression(copy_X=True, fit_intercept=True, ...

测试模型

为了测试模型的泛化性能,我们计算测试数据的均方误差:

In [24]: y_pred = linreg.predict(X_test)
In [25]: metrics.mean_squared_error(y_test, y_pred)
Out[25]: 14.995852876582541

我们注意到测试集的均方误差比训练集稍低。这是个好消息,因为我们最关心的是测试错误。然而,从这些数字中,真的很难理解这个模型到底有多好。也许绘制数据会更好:

In [26]: plt.figure(figsize=(10, 6))
...      plt.plot(y_test, linewidth=3, label='ground truth')
...      plt.plot(y_pred, linewidth=3, label='predicted')
...      plt.legend(loc='best')
...      plt.xlabel('test data points')
...      plt.ylabel('target value')
Out[26]: <matplotlib.text.Text at 0x7ff46783c7b8>

这产生了以下图表:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这更有道理!这里,我们看到所有测试样本的ground truth房价为红色,我们预测的房价为蓝色。如果你问我的话,这很近。不过,有趣的是,对于真正高或真正低的房价,该模型往往偏离得最多,例如数据点 121842 的峰值。我们可以通过计算 R 的平方来形式化我们能够解释的数据中的方差量:

In [27]: plt.figure(figsize=(10, 6))
...      plt.plot(y_test, y_pred, 'o')
...      plt.plot([-10, 60], [-10, 60], 'k--')
...      plt.axis([-10, 60, -10, 60])
...      plt.xlabel('ground truth')
...      plt.ylabel('predicted')

这将在 x 轴上绘制ground truth价格y_test,在 y 轴上绘制y_pred预测。我们还绘制了一条对角线作为参考(使用黑色虚线,'k--'),我们很快就会看到。但是我们也想在文本框中显示 R 2 分数和均方误差:

...      scorestr = r'R$²$ = %.3f' % linreg.score(X_test, y_test)
...      errstr = 'MSE = %.3f' % metrics.mean_squared_error(y_test, y_pred)
...      plt.text(-5, 50, scorestr, fontsize=12)
...      plt.text(-5, 45, errstr, fontsize=12)
Out[27]: <matplotlib.text.Text at 0x7ff4642d0400>

这将生成下图,并且是绘制模型拟合的专业方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果我们的模型是完美的,那么所有的数据点将位于虚线对角线上,因为y_pred将总是等于y_true。与对角线的偏差表明模型犯了一些错误,或者模型无法解释的数据中存在一些差异。实际上,外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传表明我们能够解释数据中 76%的分散,均方误差为 14.996。这些是我们可以用来比较线性回归模型和一些更复杂的模型的一些性能度量。

应用套索和岭回归

机器学习中的一个常见问题是,一个算法可能在训练集上运行得很好,但是,当应用于看不见的数据时,它会犯很多错误。你可以看到这是多么的有问题,因为,通常,我们最感兴趣的是模型如何推广到新的数据。一些算法(如决策树)比其他算法更容易受到这种现象的影响,但甚至线性回归也会受到影响。

This phenomenon is also known as overfitting, and we will talk about it extensively in Chapter 5, Using Decision Trees to Make a Medical Diagnosis, and Chapter 11, Selecting the Right Model with Hyperparameter Tuning.

减少过拟合的常用技术称为正则化,包括…

利用逻辑回归对鸢尾属植物进行分类

机器学习领域另一个著名的数据集叫做 Iris 数据集。鸢尾数据集包含来自三个不同物种的 150 朵鸢尾花的测量值:濑户鸢尾、云芝和绿鸢尾。这些尺寸包括花瓣的长度和宽度以及萼片的长度和宽度,都以厘米为单位。

我们的目标是建立一个机器学习模型,可以学习这些鸢尾花的测量值,它们的种类是已知的,这样我们就可以预测新鸢尾花的种类。

理解逻辑回归

在我们开始这一部分之前,让我发出警告——逻辑回归,不管它的名字是什么,实际上是一个分类模型,特别是当你有两个类的时候。它的名称来源于逻辑函数(或 sigmoid ),该函数用于将任何实值输入 x 转换为预测输出值 ŷ ,其取值介于 01 之间,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ŷ 舍入到最接近的整数有效地将输入分类为属于类别 01

当然,大多数情况下,我们的问题有不止一个输入或特征值, x 。例如,Iris 数据集提供了一个总数…

加载训练数据

Iris 数据集包含在 scikit-learn 中。我们首先加载所有必要的模块,正如我们在前面的例子中所做的:

In [1]: import numpy as np
...     import cv2
...     from sklearn import datasets
...     from sklearn import model_selection
...     from sklearn import metrics
...     import matplotlib.pyplot as plt
...     %matplotlib inline
In [2]: plt.style.use('ggplot')

然后加载数据集是一个单行过程:

In [3]: iris = datasets.load_iris()

这个函数返回一个我们称之为iris的字典,它包含一堆不同的字段:

In [4]: dir(iris)
Out[4]: ['DESCR', 'data', 'feature_names', 'filename', 'target', 'target_names']

这里所有的数据点都包含在'data'中。有150个数据点,每个数据点都有4特征值:

In [5]: iris.data.shape
Out[5]: (150, 4)

这四个特征对应于前面提到的萼片和花瓣尺寸:

In [6]: iris.feature_names
Out[6]: ['sepal length (cm)',
         'sepal width (cm)',
         'petal length (cm)',
         'petal width (cm)']

对于每个数据点,我们都有一个存储在target中的类标签:

In [7]: iris.target.shape
Out[7]: (150,)

我们还可以检查类别标签,发现总共有三个类别:

In [8]: np.unique(iris.target)
Out[8]: array([0, 1, 2])

使它成为一个二元分类问题

为了简单起见,我们现在想集中讨论一个二元分类问题,这里我们只有两个类。最简单的方法是通过选择所有不属于类2的行来丢弃属于某个类的所有数据点,如类标签 2:

In [9]: idx = iris.target != 2...     data = iris.data[idx].astype(np.float32)...     target = iris.target[idx].astype(np.float32)

接下来,让我们检查数据。

检查数据

在开始建立模型之前,看一下数据总是一个好主意。我们之前在城镇地图的例子中做过,所以我们也在这里重复一下。使用 Matplotlib,我们创建一个散点图,其中每个数据点的颜色对应于类别标签:

In [10]: plt.scatter(data[:, 0], data[:, 1], c=target,  
                     cmap=plt.cm.Paired, s=100)
...      plt.xlabel(iris.feature_names[0])
...      plt.ylabel(iris.feature_names[1])
Out[10]: <matplotlib.text.Text at 0x23bb5e03eb8>

为了使绘图更容易,我们将自己限制在前两个特征上(iris.feature_names[0]是萼片长度,iris.feature_names[1]是萼片宽度)。我们可以在下图中看到很好的类分离:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上图显示了虹膜数据集的前两个特征。

将数据分成训练集和测试集

在上一章中,我们了解到将训练数据和测试数据分开是非常重要的。我们可以使用 scikit-learn 的许多辅助函数之一轻松拆分数据:

In [11]: X_train, X_test, y_train, y_test = model_selection.train_test_split(...            data, target, test_size=0.1, random_state=42...      )

这里我们要把数据拆分成 90%的训练数据和 10%的测试数据,我们用test_size=0.1指定。通过检查返回参数,我们注意到我们最终得到了精确的90训练数据点和10测试数据点:

In [12]: X_train.shape, y_train.shapeOut[12]: ((90, 4), (90,))In [13]: X_test.shape, y_test.shapeOut[13]: ((10, 4), (10,))

训练分类器

创建逻辑回归分类器的步骤与设置 k-NN 的步骤基本相同:

In [14]: lr = cv2.ml.LogisticRegression_create()

然后我们必须指定期望的训练方法。这里我们可以选择cv2.ml.LogisticRegression_BATCH或者cv2.ml.LogisticRegression_MINI_BATCH。目前,我们需要知道的是,我们希望在每个数据点之后更新模型,这可以通过以下代码来实现:

In [15]: lr.setTrainMethod(cv2.ml.LogisticRegression_MINI_BATCH)
...      lr.setMiniBatchSize(1)

我们还希望指定算法在终止之前应该运行的迭代次数:

In [16]: lr.setIterations(100)

然后,我们可以调用对象的train方法(与我们之前的方法完全相同),该方法将在成功时返回True:

In [17]: lr.train(X_train, cv2.ml.ROW_SAMPLE, y_train)
Out[17]: True

正如我们刚刚看到的,训练阶段的目标是找到一组权重,最好地将特征值转换为输出标签。单个数据点由其四个特征值给出(f0??、f1??、f2??、f3)。既然我们有四个特征,那么我们也应该得到四个权重,这样x = w0f0+w1f1+w2f2+w3f3ŷ=σ(x) 。然而,如前所述,该算法增加了一个额外的权重,作为偏移或偏差,从而使x = w0f0+w1f1+w2f2+w3f3+w4】。我们可以按如下方式检索这些权重:****

In [18]: lr.get_learnt_thetas()
Out[18]: array([[-0.04090132, -0.01910266, -0.16340332, 0.28743777, 0.11909772]], dtype=float32)

这意味着逻辑函数的输入为x =-0.0409 f0-0.0191 f1-0.163 f2+0.287 f3+0.119。然后,当我们馈入属于类 1 的新数据点( f 0f 1f 2f 3 )时,输出 ŷ=σ(x) 应该接近 1。但这实际上有多有效呢?

测试分类器

让我们通过计算训练集上的准确度分数来亲眼看看:

In [19]: ret, y_pred = lr.predict(X_train)In [20]: metrics.accuracy_score(y_train, y_pred)Out[20]: 1.0

满分!然而,这仅仅意味着模型能够完美地记忆训练数据集。这并不意味着模型能够对一个新的、看不见的数据点进行分类。为此,我们需要检查测试数据集:

In [21]: ret, y_pred = lr.predict(X_test)...      metrics.accuracy_score(y_test, y_pred)Out[21]: 1.0

幸运的是,我们又得了一个满分!现在我们可以肯定,我们建立的模型真的很棒。

摘要

在这一章里,我们谈了很多,不是吗?

简而言之,我们学习了很多不同的监督学习算法,如何将它们应用于真实数据集,以及如何在 OpenCV 中实现一切。我们介绍了分类算法,如 k-NN 和逻辑回归,并讨论了它们如何用于预测两个或多个离散类别的标签。我们介绍了线性回归的各种变体(如套索回归和岭回归),并讨论了它们如何用于预测连续变量。最后但同样重要的是,我们熟悉了 Iris 和 Boston 数据集,这是机器学习历史上的两个经典。

在接下来的章节中,我们将深入探讨这些主题,并探索一些更有趣的例子来说明这些概念的用处。

但是首先,我们需要谈谈机器学习中的另一个基本话题,特征工程。通常,数据不会出现在格式良好的数据集中,以有意义的方式表示数据是我们的责任。因此,下一章将讨论表示特征和工程数据。

四、表示数据和工程特征

在最后一章中,我们构建了第一个监督学习模型,并将其应用于一些经典数据集,如 IrisBoston 数据集。然而,在现实世界中,数据很少出现在一个整洁的<n_samples x n_features> 特征矩阵中,该矩阵是预打包数据库的一部分。相反,我们有责任找到一种以有意义的方式表示数据的方法。寻找表示我们数据的最佳方式的过程被称为特征工程,它是数据科学家和机器学习从业者试图解决现实世界问题的主要任务之一。

我知道你宁愿直接跳到最后,建立人类有史以来最深的神经网络。…

技术要求

可以从以下链接查阅本章代码:github . com/PacktPublishing/Machine-Learning-for-OpenCV-Second-Edition/tree/master/chapter 04

以下是软件和硬件要求的总结:

  • 您将需要 OpenCV 版本 4.1.x (4.1.0 或 4.1.1 都可以)。
  • 您将需要 Python 3.6 版本(任何 Python 3 . x 版本都可以)。
  • 您将需要 Anaconda Python 3 来安装 Python 和所需的模块。
  • 除了这本书,你可以使用任何操作系统——苹果操作系统、视窗操作系统和基于 Linux 的操作系统。我们建议您的系统中至少有 4 GB 内存。
  • 你不需要一个图形处理器来运行本书提供的代码。

理解特征工程

信不信由你,一个机器学习系统学习的好坏主要取决于训练数据的质量。尽管每种学习算法都有其优缺点,但性能的差异往往归结于数据的准备或表示方式。因此,特征工程可以理解为数据表示的工具。机器学习算法试图从样本数据中学习一个问题的解决方案,而特征工程会问:样本数据的最佳表示是什么,用来学习问题的解决方案?

记住,前几章,我们讨论了整个机器学习管道。在这里,我们已经提到了特征提取,但是我们…

预处理数据

我们在处理数据时越有纪律性,最终可能会取得更好的结果。这个过程的第一步被称为数据预处理,它有(至少)三种不同的味道:

  • 数据格式化:数据可能不是适合我们工作的格式;例如,数据可能以专有文件格式提供,而我们最喜欢的机器学习算法不理解这一点。

  • 数据清理:数据可能包含无效或缺失的条目,需要清理或删除。

  • 数据采样:数据对于我们的特定目的来说可能太大了,迫使我们对数据进行智能采样。

一旦对数据进行了预处理,我们就为实际的特征工程做好了准备:对预处理后的数据进行转换,以适合我们特定的机器学习算法。该步骤通常涉及三个可能程序中的一个或多个:

  • 缩放:某些机器学习算法往往要求数据在一个共同的范围内,比如均值和单位方差为零。缩放是将所有要素(可能具有不同的物理单位)纳入一个共同的值范围的过程。
  • 分解:数据集的特征通常比我们可能处理的要多得多。特征分解是将数据压缩成少量信息量大的数据成分的过程。
  • 聚合:有时候,可以将多个特征组合成一个单一的、更有意义的特征。例如,数据库可能包含登录到基于网络的系统的每个用户的日期和时间。根据任务的不同,简单地计算每个用户的登录次数可以更好地表示这些数据。

让我们更详细地看看其中的一些过程。

标准化特征

标准化是指对数据进行缩放,使其均值和单位方差为零的过程。这是对各种机器学习算法的共同要求,如果单个特征不能满足这一要求,机器学习算法可能会表现不佳。我们可以通过从每个数据点减去所有数据的平均值( μ )并除以数据的方差( σ )来手动标准化我们的数据;也就是说,对于每个特征 x ,我们会计算 (x - μ) / σ

或者,scikit-learn 在其preprocessing模块中提供了这一过程的简单实现。

让我们考虑一个 3×3 的数据矩阵,X,代表三个数据点(行),每个数据点(行)有三个任意选择的特征值(列):…

标准化特征

与标准化类似,标准化是缩放单个样本以获得单位范数的过程。我相信你知道范数代表向量的长度,可以用不同的方式定义。我们在前一章讨论了其中的两个:L1 范数(或曼哈顿距离)和 L2 范数(或欧几里德距离)。

在 scikit-learn 中,我们的数据矩阵X可以使用normalize函数进行归一化,而l1范数由norm关键字指定:

In [5]: X_normalized_l1 = preprocessing.normalize(X, norm='l1')
...     X_normalized_l1
Out[5]: array([[ 0.2, -0.4, 0.4],
               [ 1\. , 0\. , 0\. ],
               [ 0\. , 0.5, -0.5]])

类似地,可以通过指定norm='l2'来计算 L2 范数:

In [6]: X_normalized_l2 = preprocessing.normalize(X, norm='l2')
...     X_normalized_l2
Out[6]: array([[ 0.33333333, -0.66666667, 0.66666667],
               [ 1\. , 0\. , 0\. ],
               [ 0\. , 0.70710678, -0.70710678]])

将要素缩放到一个范围

将要素缩放至零均值和单位方差的替代方法是让要素位于给定的最小值和最大值之间。通常,这些值是 0 和 1,因此每个特征的最大绝对值都按单位大小进行缩放。在 scikit-learn 中,这可以通过使用MinMaxScaler来实现:

In [7]: min_max_scaler = preprocessing.MinMaxScaler()...     X_min_max = min_max_scaler.fit_transform(X)...     X_min_maxOut[7]: array([[ 0.33333333, 0\. , 1\. ],               [ 1\. , 0.66666667, 0.33333333],               [ 0\. , 1\. , 0\. ]])

默认情况下,数据将被缩放到 0 和 1 之间。我们可以通过向MinMaxScaler构造函数传递关键字参数feature_range来指定不同的范围:

In [8]: min_max_scaler = preprocessing.MinMaxScaler(feature_range ...

二值化特征

最后,我们可能会发现自己不太关心数据的确切特征值。相反,我们可能只想知道某个特性是存在还是不存在。二值化数据可以通过阈值化特征值来实现。让我们快速提醒自己我们的特征矩阵,X:

In [9]: X
Out[9]: array([[ 1., -2., 2.],
               [ 3., 0., 0.],
               [ 0., 1., -1.]])

让我们假设这些数字代表我们银行账户中的数千美元。如果账户中有超过 0.5 千美元,我们认为这个人富有,我们用 1 表示。否则,我们放一个 0。这类似于用threshold=0.5设定数据的阈值:

In [10]: binarizer = preprocessing.Binarizer(threshold=0.5)
...      X_binarized = binarizer.transform(X)
...      X_binarized
Out[10]: array([[ 1., 0., 1.],
                [ 1., 0., 0.],
                [ 0., 1., 0.]])

结果是一个完全由 1 和 0 组成的矩阵。

处理丢失的数据

特征工程中的另一个常见需求是缺失数据的处理。例如,我们可能有一个如下所示的数据集:

In [11]: from numpy import nan...      X = np.array([[ nan, 0,   3 ],...                    [ 2,   9,  -8 ],...                    [ 1,   nan, 1 ],...                    [ 5,   2,   4 ],...                    [ 7,   6,  -3 ]])

大多数机器学习算法无法处理非数字 ( NAN )值(【Python 中的 )。相反,我们首先必须用一些适当的填充值替换所有的nan值。这被称为缺失值的插补

scikit-learn 提供了三种不同的估算缺失值的策略:

  • mean:用沿矩阵指定轴的平均值替换所有nan值(默认:轴= 0 )
  • median:替换全部…

理解降维

数据集通常具有比我们可能处理的更多的特征。例如,假设我们的工作是预测一个国家的贫困率。我们可能会从匹配一个国家的名字和它的贫困率开始,但这不会帮助我们预测一个新国家的贫困率。所以,我们开始思考贫困的可能原因。但是贫困的可能原因有多少呢?因素可能包括一个国家的经济、缺乏教育、高离婚率、人口过剩等等。如果这些原因中的每一个都是用来帮助预测贫困率的特征,那么我们最终会得到无数个特征。如果你是数学家,你可能会把这些特征看作是高维空间中的轴,然后每个国家的贫困率就是这个高维空间中的一个点。

如果你不是数学家,从小处着手可能会有帮助。假设,我们首先只看两个特征:一个国家的国内生产总值 ( GDP )和公民数量。在 2D 的空间里,我们把 GDP 解释为 x 轴,把公民人数解释为 y 轴。然后,我们看第一个国家。它的国内生产总值很小,公民人数也很平均。我们在 x-y 平面上画一个点,代表这个国家。我们添加了第二、第三和第四个国家。第四个国家恰好既有很高的 GDP,又有大量的公民。因此,我们的四个数据点可能分布在 x-y 平面上,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然而,如果我们开始在我们的分析中增加第三个特征,比如该国的离婚率,会发生什么?这将为我们的图添加第三个轴( z 轴)。突然,我们发现数据不再很好地在 x-y-z 立方体中传播,因为立方体的大部分仍然是空的。而在二维中,我们似乎覆盖了大部分的 x-y 方块,在三维中,我们需要更多的数据点来填充数据点 1 到 3 和右上角的孤立数据点 4 之间的空白。

This problem is also known as the curse of dimensionality: the number of data points needed to fill the available space grows exponentially with the number of dimensions (or plot axes). If a classifier is not fed with data points that span the entire feature space (such as shown in the preceding cube example), the classifier will not know what to do once a new data point is presented that lies far away from all of the previously encountered data points.

维数灾难意味着,在一定数量的特征(或维度)之后,分类器的性能将开始下降。让我们试着理解这一点。更多的特征本质上意味着可以考虑更多的数据集变化。但是,如果考虑的特征超过了要求的特征,分类器甚至会考虑任何异常值,或者过度填充数据集。因此,分类器的性能将开始下降,而不是提高:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但是,我们如何为数据集找到这个看似最优的维数呢?

这就是降维发挥作用的地方。这些是一系列技术,允许我们找到高维数据的紧凑表示,而不会丢失太多信息。

在 OpenCV 中实现主成分分析

最常见的降维技术之一叫做主成分分析

类似于前面显示的 2D 和 3D 例子,我们可以将图像视为高维空间中的一个点。如果我们通过堆叠所有的列来展平高度 m 和宽度 n 的 2D 灰度图像,我们得到长度 m x n x 1 的(特征)向量。该向量中第I元素的值是图像中第I像素的灰度值。现在,假设我们想要用这些精确的维度来表示每一个可能的 2D 灰度图像。这会给出多少图像?

由于灰度像素通常取 0 到 255 之间的值,因此总共有 256 个提升到 m x n 图像的幂。机会…

实现独立分量分析

scikit-learn 提供了与主成分分析密切相关的其他有用的降维技术,但没有 OpenCV。为了完整起见,我们在这里提到它们。独立分量分析执行与主成分分析相同的数学步骤,但是它选择分解的分量尽可能彼此独立,而不是像主成分分析那样按照预测器。

在 scikit-learn 中,ICA 可从decomposition模块获得:

In [9]:  from sklearn import decomposition
In [10]: ica = decomposition.FastICA(tol=0.005)

Why do we use tol=0.005? Because we want the FastICA to converge to some particular value. There are two methods to do that—increase the number of iterations (the default value is 200) or decrease the tolerance (the default value is 0.0001). I tried to increase the iterations but, unfortunately, it didn’t work, so I went ahead with the other option. Can you figure out why it didn’t converge?

如前所述,数据转换发生在fit_transform函数中:

In [11]: X2 = ica.fit_transform(X)

在我们的例子中,绘制旋转的数据导致了与先前使用主成分分析获得的结果相似的结果,这可以在这个代码块后面的图表中得到验证。

In [12]: plt.figure(figsize=(10, 6))
...      plt.plot(X2[:, 0], X2[:, 1], 'o')
...      plt.xlabel('first independent component')
...      plt.ylabel('second independent component')
...      plt.axis([-0.2, 0.2, -0.2, 0.2])
Out[12]: [-0.2, 0.2, -0.2, 0.2]

这可以在下图中看到:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现非负矩阵分解(NMF)

另一种有用的降维技术叫做 NMF 。它再次实现了与主成分分析和独立分量分析相同的基本数学运算,但它有一个额外的限制,即它只对非负数据进行运算。换句话说,如果我们想使用 NMF,我们的特征矩阵中不能有负值;分解的结果部分也将具有非负值。

在 scikit-learn 中,NMF 的工作方式与 ICA 完全一样:

In [13]: nmf = decomposition.NMF()In [14]: X2 = nmf.fit_transform(X)In [15]: plt.plot(X2[:, 0], X2[:, 1], 'o')...      plt.xlabel('first non-negative component')...      plt.ylabel('second non-negative component')...      plt.axis([-5, 20, -5, 10])Out[15]: [-5, 20, ...

用 t 分布随机邻域嵌入(t-SNE)可视化降维

t-SNE 是一种降维技术,最适合于高维数据的可视化。

在本节中,我们将看到一个如何使用 t-SNE 可视化高维数据集的示例。让我们在这种情况下使用数字数据集,它有从 0 到 9 的数字手写图像。这是一个公开的数据集,通常被称为 MNIST 数据集。我们将看到如何使用 t-SNE 可视化这个数据集的降维:

  1. 首先,让我们加载数据集:
In [1]: import numpy as np
In [2]: from sklearn.datasets import load_digits
In [3]: digits = load_digits()
In [4]: X, y = digits.data/255.0, digits.target
In [5]: print(X.shape, y.shape)
Out[5]: (1797, 64) (1797,)
  1. 您应该首先应用一种降维技术(如 PCA)将大量的维度减少到一个较低的数量,然后使用一种技术(如 t-SNE)来可视化数据。但是,在这种情况下,让我们使用所有的维度,直接使用 t-SNE:
In [6]: from sklearn.manifold import TSNE
In [7]: tsne = TSNE(n_components=2, verbose=1, perplexity=40, n_iter=300)
In [8]: tsne_results = tsne.fit_transform(df.loc[:,features].values)
Out[8]: [t-SNE] Computing 121 nearest neighbors...
... [t-SNE] Indexed 1797 samples in 0.009s...
... [t-SNE] Computed neighbors for 1797 samples in 0.395s...
... [t-SNE] Computed conditional probabilities for sample 1000 / 1797
... [t-SNE] Computed conditional probabilities for sample 1797 / 1797
... [t-SNE] Mean sigma: 0.048776
... [t-SNE] KL divergence after 250 iterations with early exaggeration: 61.094833
... [t-SNE] KL divergence after 300 iterations: 0.926492
  1. 最后,让我们在散点图的帮助下,可视化我们使用 t-SNE 提取的两个维度:
In [9]: import matplotlib.pyplot as plt
In [10]: plt.scatter(tsne_results[:,0],tsne_results[:,1],c=y/10.0)
...      plt.xlabel('x-tsne')
...      plt.ylabel('y-tsne')
...      plt.title('t-SNE')
In [11]: plt.show()

我们得到如下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在,让我们在下一节讨论如何表示分类变量。

表示分类变量

在构建机器学习系统时,我们可能会遇到的最常见的数据类型之一是分类特征(也称为离散特征),例如水果的颜色或公司的名称。分类特征的挑战是它们不会以连续的方式变化,这使得很难用数字来表示它们。

例如,香蕉不是绿色就是黄色,但不是两者都有。一个产品要么属于服装部门,要么属于图书部门,但很少同时属于这两个部门,以此类推。

你会如何表现这些特征?

例如,让我们假设我们正在尝试编码一个数据集,该数据集由机器学习和人工智能的先辈列表组成:…

表示文本特征

与分类特征类似,scikit-learn 提供了一种对另一种常见特征类型(文本特征)进行编码的简单方法。使用文本功能时,将单个单词或短语编码为数值通常很方便。

让我们考虑一个包含少量文本短语的数据集:

In [1]: sample = [
...        'feature engineering',
...        'feature selection',
...        'feature extraction'
...     ]

编码这种数据最简单的方法之一是通过字数统计;对于每个短语,我们简单地计算每个单词在其中的出现次数。在 scikit-learn 中,这很容易使用CountVectorizer完成,其功能类似于DictVectorizer:

In [2]: from sklearn.feature_extraction.text import CountVectorizer
...     vec = CountVectorizer()
...     X = vec.fit_transform(sample)
...     X
Out[2]: <3x4 sparse matrix of type '<class 'numpy.int64'>'
                with 6 stored elements in Compressed Sparse Row format>

默认情况下,这将把我们的特征矩阵X存储为稀疏矩阵。如果我们想手动检查它,我们需要将它转换成一个常规数组:

In [3]: X.toarray()
Out[3]: array([[1, 0, 1, 0],
               [0, 0, 1, 1],
               [0, 1, 1, 0]], dtype=int64)

为了理解这些数字的含义,我们必须查看功能名称:

In [4]: vec.get_feature_names()
Out[4]: ['engineering', 'extraction', 'feature', 'selection']

现在,很清楚X中的整数是什么意思了。如果我们看一下在X的顶行中表示的短语,我们看到它包含单词engineering的一个出现和单词feature的一个出现。另一方面,它不包含extractionselection字样。这有意义吗?快速浏览一下我们的原始数据sample就会发现这个短语确实是feature engineering

只看X阵(不作弊!),你能猜出sample中的最后一句话是什么吗?

这种方法的一个可能的缺点是,我们可能过于重视那些频繁出现的单词。解决这个问题的一种方法是术语频率-反向文档频率 ( TF-IDF )。TF-IDF 所做的事情可能比它的名字更容易理解,它基本上是通过衡量字数在整个数据集中出现的频率来衡量字数。

TF-IDF 的语法与前面的命令非常相似:

In [5]: from sklearn.feature_extraction.text import TfidfVectorizer
...     vec = TfidfVectorizer()
...     X = vec.fit_transform(sample)
...     X.toarray()
Out[5]: array([[ 0.861037 , 0\. , 0.50854232, 0\. ],
               [ 0\. , 0\. , 0.50854232, 0.861037 ],
               [ 0\. , 0.861037 , 0.50854232, 0\. ]])

我们注意到现在的数字比以前少了,第三列受到的冲击最大。这是有道理的,因为第三列对应于所有三个短语中最频繁出现的单词,feature:

In [6]: vec.get_feature_names()
Out[6]: ['engineering', 'extraction', 'feature', 'selection']

If you’re interested in the math behind TF-IDF, you can start with this paper: citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.121.1424&rep=rep1&type=pdf. For more information about its specific implementation in scikit-learn, have a look at the API documentation at scikit-learn.org/stable/modules/feature_extraction.html#tfidf-term-weighting.

在第七章中,用贝叶斯学习实现垃圾邮件过滤器将变得非常重要。

表示图像

计算机视觉最常见和最重要的数据类型之一当然是图像。表示图像最直接的方法可能是使用图像中每个像素的灰度值。通常,灰度值不能很好地表示它们所描述的数据。例如,如果我们看到一个像素的灰度值为 128,我们能知道这个像素属于哪个对象吗?可能不会。因此,灰度值不是非常有效的图像特征。

使用颜色空间

或者,我们可能会发现颜色包含一些原始灰度值无法捕获的信息。大多数情况下,图像出现在传统的 RGB 颜色空间中,其中图像中的每个像素都获得其表观红色(R)绿色 ( G )和蓝色 ( B )的强度值。但是,OpenCV 提供了一整套其他的色彩空间,比如色相饱和度值 ( HSV )、色相饱和度明度 ( HSL )以及 Lab 色彩空间。让我们快速看一下它们。

在 RGB 空间中编码图像

我相信你已经熟悉了 RGB 色彩空间,它使用红色、绿色和蓝色的不同色调的添加混合来产生不同的合成颜色。RGB 色彩空间在日常生活中非常有用,因为它覆盖了人眼可以看到的很大一部分色彩空间。这就是为什么彩色电视机或彩色电脑显示器只需要关心产生红、绿、蓝光的混合物。

在 OpenCV 中,支持开箱即用的 RGB 图像。你只需要知道,或者需要提醒的是,彩色图像实际上是作为 BGR 图像存储在 OpenCV 中的;也就是说,颜色通道的顺序是蓝-绿-红,而不是红-绿-蓝。其原因是…

在 HSV 和 HLS 空间中编码图像

然而,自从 RGB 颜色空间被创建以来,人们已经意识到它实际上是人类视觉的相当差的表示。因此,研究人员开发了许多替代表示。其中一个称为HSV(?? 色相、饱和度和值的缩写),另一个称为 HLS ( 色相、明度和饱和度)。您可能在颜色选择器和常见的图像编辑软件中见过这些颜色空间。在这些颜色空间中,颜色的色调由单一色调通道捕捉,色彩由饱和度通道捕捉,亮度或明度由明度或值通道捕捉。

在 OpenCV 中,使用cv2.cvtColor可以很容易地将一幅 RGB 图像转换为 HSV 颜色空间:

In [5]: img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)

HLS 颜色空间也是如此。事实上,OpenCV 提供了一系列额外的色彩空间,可通过cv2.cvtColor获得。我们需要做的就是用以下内容之一替换颜色标志:

  • HLS 使用cv2.COLOR_BGR2HLS
  • 使用cv2.COLOR_BGR2LAB的 LAB(亮度、绿-红和蓝-黄)
  • 使用cv2.COLOR_BGR2YUV的 YUV(整体亮度、蓝色亮度和红色亮度)

检测图像中的角点

在图像中最容易找到的传统特征之一可能是角(几条边相交的位置)。OpenCV 提供了至少两种不同的算法来查找图像中的角点:

  • 哈里斯·唐纳探测:哈里斯和斯蒂芬斯知道边缘是所有方向都有高强度变化的区域,他们想出了一个快速找到这些位置的方法。该算法在 OpenCV 中实现为cv2.cornerHarris
  • Shi-Tomasi 角点检测 : Shi 和 Tomasi 对什么构成好的特征进行跟踪有自己的想法,通常通过找到 N 最强的角点,比 Harris 角点检测做得更好。该算法在 OpenCV 中实现为cv2.goodFeaturesToTrack

哈里斯…

使用恒星探测器和简要描述符

然而,当图像的比例改变时,角点检测是不够的。已经发表了多篇论文,描述了用于特征检测和描述的不同算法。我们将查看加速鲁棒特征 ( SURF )检测器(更多信息,请参见en.wikipedia.org/wiki/Speeded_up_robust_features)和二进制鲁棒独立基本特征 ( 简报)描述符的组合。特征检测器识别图像中的关键点,特征描述符计算所有关键点的实际特征值。

这些算法的细节超出了本书的范围。高级用户可以参考详细描述这些算法的论文。

有关更多详细信息,您可以参考以下链接:

整个过程从读取图像开始,将其转换为灰度,使用恒星特征检测器找到感兴趣的点,最后使用简要描述符计算特征值。

  1. 让我们首先读取图像并将其转换为灰度:
In [23]: img = cv2.imread('data/rubic-cube.png')
In [24]: gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  1. 现在,我们将创建特征检测器和描述符:
In [25]: star = cv2.xfeatures2d.StarDetector_create()
In [26]: brief = cv2.xfeatures2d.BriefDescriptorExtractor_create()
  1. 接下来,是时候使用恒星探测器来获取关键点并将它们传递给简要描述符了:
In [27]: keyPoints = star.detect(gray, None)
In [28]: keyPoints, descriptors = brief.compute(img, keyPoints)

这里有个陷阱。在写这本书的时候,OpenCV 版本还没有cv2.drawKeypoints功能的解析版本。所以,我写了一个类似的函数,我们可以用来画关键点。您不需要担心函数中涉及的步骤,这只是供您参考。如果已经安装了本书指定的 OpenCV 版本(OpenCV 4.1.0 或 OpenCV 4.1.1),可以直接使用cv2.drawKeypoints功能:

In [29]: def drawKeypoint (img, keypoint, color):
...          draw_shift_bits = 4
...          draw_multiplier = 1 << draw_shift_bits

...          center = (int(round(keypoint.pt[0])),int(round(keypoint.pt[1])))

...          radius = int(round(keypoint.size/2.0))

...          # draw the circles around keypoints with the keypoints size
...          cv2.circle(img, center, radius, color, 1, cv2.LINE_AA)

...          # draw orientation of the keypoint, if it is applicable
...          if keypoint.angle != -1:

...              srcAngleRad = keypoint.angle * np.pi/180.0

...              orient = (int(round(np.cos(srcAngleRad)*radius)), \
                 int(round(np.sin(srcAngleRad)*radius)))

...              cv2.line(img, center, (center[0]+orient[0],\
                               center[1]+orient[1]),\
                 color, 1, cv2.LINE_AA)
...          else:
...              # draw center with R=1
...              radius = 1 * draw_multiplier
...              cv2.circle(img, center, radius,\
                  color, 1, cv2.LINE_AA)

...          return img
In [30]: from random import randint
...      def drawKeypoints(image, keypoints):
...          for keypoint in keypoints:
...              color = (randint(0,256),randint(0,256),randint(0,256))
...              image = drawKeypoint(image, keypoint, color)
...          return image
  1. 现在让我们使用这个函数来绘制检测到的关键点:
In [31]: result = drawKeypoints(img, keyPoints)
In [32]: print("Number of keypoints = {}".format(len(keyPoints)))
Out[32]: Number of keypoints = 453
In [33]: plt.figure(figsize=(18,9))
...      plt.imshow(result)

我们得到如下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

很棒,对吧?

虽然简单快捷,但它不适用于图像的旋转。您可以通过旋转图像(更多信息请访问www . pyimagesearch . com/2017/01/02/rotate-images-with-opencv-and-python/)然后运行 LISTER 来尝试。让我们看看 ORB 如何帮助我们解决这个问题。

使用定向快速旋转简报

个人而言,我是 ORB 的超级粉丝。它是免费的,也是 SIFT 和 SURF 的一个很好的替代品,它们都受到专利法的保护。ORB 实际上比 SURF 好用。有趣的是,加里·布拉德斯基是题为ORB:SIFT 和 SURF 的有效替代品的论文作者之一。你能理解为什么这很有趣吗?谷歌加里·布拉德斯基和 OpenCV,你会得到你的答案。

整个过程或多或少会保持不变,所以让我们快速浏览一下代码:

In [34]: img = cv2.imread('data/rubic-cube.png')...      gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)In [35]: orb = cv2.ORB_create()In [36]: keyPoints = orb.detect(gray,None)In [37]: keyPoints, descriptors ...

摘要

在这一章中,我们深入到兔子洞,研究了几种常见的特征工程技术,重点是特征选择和特征提取。我们成功地对数据进行了格式化、清理和转换,以便普通的机器学习算法能够理解它。我们了解了维数灾难,并通过在 OpenCV 中实现 PCA 对降维做了一点尝试。最后,我们简单介绍了 OpenCV 为图像数据提供的常见特征提取技术。

有了这些技能,我们现在可以接受任何数据,无论是数字数据、分类数据、文本数据还是图像数据。当我们遇到丢失的数据时,我们知道该做什么,我们知道如何传输我们的数据,使其适合我们首选的机器学习算法。

在下一章中,我们将进行下一步,并讨论一个具体的用例,即如何使用我们新获得的知识,使用决策树进行医学诊断。

五、利用决策树的医学诊断

既然我们知道如何处理各种形状和形式的数据,无论是数字数据、分类数据、文本数据还是图像数据,现在是时候好好利用我们新获得的知识了。

在本章中,我们将学习如何构建一个可以进行医学诊断的机器学习系统。我们并不都是医生,但在我们一生中的某个时刻,我们可能都去过一次。通常,医生会获得尽可能多的关于患者病史和症状的信息,以做出明智的诊断。我们将借助所谓的决策树来模拟医生的决策过程。我们还将涵盖基尼系数、信息增益和方差减少,以及过度拟合和修剪。

决策树是一种简单而强大的监督学习算法,类似于流程图;我们将在一分钟内详细讨论这个问题。除了在医学领域,决策树通常用于天文学(例如,从哈勃太空望远镜图像中过滤噪声或对恒星-星系团进行分类)、制造和生产(例如,由波音公司发现制造过程中的缺陷)以及物体识别(例如,识别 3D 物体)等领域。

具体来说,我们希望在本章中了解以下内容:

  • 从数据中构建简单的决策树,并将其用于分类或回归
  • 使用基尼系数、信息增益和方差缩减来决定下一步做什么决定
  • 规划决策树及其好处

但首先,让我们谈谈决策树实际上是什么。

技术要求

可以从以下链接查阅本章代码:github . com/PacktPublishing/Machine-Learning-for-OpenCV-Second-Edition/tree/master/chapter 05

以下是软件和硬件要求的总结:

  • 您将需要 OpenCV 版本 4.1.x (4.1.0 或 4.1.1 都可以)。
  • 您将需要 Python 3.6 版本(任何 Python 3 . x 版本都可以)。
  • 您将需要 Anaconda Python 3 来安装 Python 和所需的模块。
  • 除了这本书,你可以使用任何操作系统——苹果操作系统、视窗操作系统和基于 Linux 的操作系统。我们建议您的系统中至少有 4 GB 内存。
  • 你不需要一个图形处理器来运行本书提供的代码。

理解决策树

决策树是一个简单而强大的有监督学习问题的模型。顾名思义,我们可以把它想象成一棵树,其中信息沿着不同的分支流动——从树干开始,一直到单个的叶子,决定在每个连接点采取哪个分支。

这基本上是一个决策树!下面是决策树的一个简单示例:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

决策树由关于数据(也称为决策节点)及其可能后果的问题或测试的层次结构组成。

构建决策树的真正困难之一是如何从数据中提取合适的特征。为了说明这一点,让我们用一个具体的例子。假设我们有一个由一封电子邮件组成的数据集:

In [1]: data = [
...       'I am Mohammed Abacha, the son of the late Nigerian Head of '
...       'State who died on the 8th of June 1998\. Since i have been '
...       'unsuccessful in locating the relatives for over 2 years now '
...       'I seek your consent to present you as the next of kin so '
...       'that the proceeds of this account valued at US$15.5 Million '
...       'Dollars can be paid to you. If you are capable and willing '
...       'to assist, contact me at once via email with following '
...       'details: 1\. Your full name, address, and telephone number. '
...       '2\. Your Bank Name, Address. 3.Your Bank Account Number and '
...       'Beneficiary Name - You must be the signatory.'
...     ]

这封电子邮件可以像我们在上一章中所做的那样,使用 scikit-learn 的CountVectorizer进行矢量化:

In [2]: from sklearn.feature_extraction.text import CountVectorizer
... vec = CountVectorizer()
... X = vec.fit_transform(data)

从上一章中,我们知道可以使用以下功能查看X中的特征名称:

In [3]: function:vec.get_feature_names()[:5]
Out[3]: ['15', '1998', '8th', 'abacha', 'account']

为了清楚起见,我们只关注前五个单词,它们按字母顺序排序。然后,相应的出现次数可以如下所示:

In [4]: X.toarray()[0, :5]
Out[4]: array([1, 1, 1, 1, 2], dtype=int64)

这告诉我们,五个单词中有四个只在邮件中出现一次,但单词account(最后一个列在Out[3]中)实际上出现了两次。在最后一章中,我们键入X.toarray()将稀疏数组X转换为人类可读的数组。结果是一个 2D 数组,其中行对应于数据样本,列对应于前面命令中描述的要素名称。由于数据集中只有一个样本,我们将自己限制在数组的第 0 行(即第一个数据样本)和数组的前五列(即前五个字)。

那么,我们如何检查电子邮件是否来自尼日利亚王子?

一种方法是查看电子邮件是否同时包含单词nigerianprince:

In [5]: 'nigerian' in vec.get_feature_names()
Out[5]: True
In [6]: 'prince' in vec.get_feature_names()
Out[6]: False

让我们惊讶的是,我们发现了什么?prince这个词在邮件中没有出现。

这是否意味着信息是合法的?

不,当然不是。这封电子邮件没有标上'prince',而是标上了head of state,有效地绕过了我们过于简单的垃圾邮件检测器。

同样,我们甚至如何开始模拟树中的第二个决定:*想让我给他寄钱?*文本中没有直截了当的特征来回答这个问题。因此,这是一个特征工程的问题,即以允许我们回答这个问题的方式组合消息中实际出现的单词。当然,一个好的迹象是寻找像US$money这样的字符串,但是我们仍然不知道这些单词被提到的上下文。据我们所知,也许它们是句子的一部分:别担心,我不想你给我寄钱。

更糟糕的是,事实证明,我们问这些问题的顺序实际上会影响最终结果。比如,如果我们先问最后一个问题:我真的认识一个尼日利亚王子吗?假设我们有一个尼日利亚王子作为叔叔,那么在电子邮件中找到尼日利亚王子可能就不再可疑了。

如你所见,这个看似简单的例子很快就失控了。

幸运的是,决策树背后的理论框架帮助我们找到正确的决策规则以及下一步要处理的决策。

然而,要理解这些概念,我们还得再深入一点。

构建我们的第一棵决策树

我认为我们已经为一个更复杂的例子做好了准备。如前所述,现在让我们进入医疗领域。

让我们考虑一个例子,几个病人患有同样的疾病,例如一种罕见的底栖病。让我们进一步假设这种疾病的真正原因至今仍不为人所知,并且我们所能获得的所有信息都由一系列生理测量组成。例如,我们可能可以访问以下信息:

  • 患者的血压(BP)
  • 患者的胆固醇水平(cholesterol)
  • 患者的性别(sex)
  • 患者年龄(age)
  • 一个病人的血钠浓度(Na)
  • 一个病人的血钾浓度(K)

基于所有的…

生成新数据

在继续下一步之前,让我们快速了解每个机器学习工程师的一个非常关键的步骤——数据生成。我们知道,所有的机器学习和深度学习技术都需要大量的数据——简单来说:越大越好。但是如果你没有足够的数据呢?你可能会得到一个不够精确的模型。常用的技术(如果您不能生成任何新数据)是使用大部分数据进行训练。这样做的主要缺点是,你的模型不是一般化的,或者换句话说,存在过度拟合的问题。

解决上述问题的一个办法是生成新数据,或者通常所说的合成数据。这里需要注意的关键点是,合成数据应该具有与真实数据相似的特征。它们与真实数据越相似,对作为 ML 工程师的你越有利。这种技术被称为数据增强,在这里我们使用各种技术,如旋转和镜像来生成基于现有数据的新数据。

由于我们在这里处理的是一个假设的情况,我们可以编写简单的 Python 代码来生成随机数据——因为这里没有为我们设置的特性。在现实世界中,您将使用数据扩充来生成看起来真实的新数据样本。让我们看看如何处理我们的案例。

在这里,数据集实际上是一个字典列表,其中每个字典构成一个数据点,包含患者的血液工作、年龄和性别,以及处方药物。因此,我们知道我们想要创建新的字典,我们知道在这本字典中使用的键。接下来要关注的是字典中值的数据类型。

我们从age开始,?? 是一个整数,然后是性别,不是M就是F。同样,对于其他值,我们可以推断数据类型,在某些情况下,使用常识,我们甚至可以推断要使用的值的范围。

It is very important to note that common sense and deep learning don’t go well together most of the time. This is because you want your model to understand when something is an outlier. For example, we know that it’s highly unlikely for someone to have an age of 130 but a generalized model should understand that this value is an outlier and should not be taken into account. This is why you should always have a small portion of data with such illogical values.

让我们看看如何为我们的案例生成一些合成数据:

import random

def generateBasorexiaData(num_entries):
    # We will save our new entries in this list 
    list_entries = []
    for entry_count in range(num_entries):
        new_entry = {}
        new_entry['age'] = random.randint(20,100)
        new_entry['sex'] = random.choice(['M','F'])
        new_entry['BP'] = random.choice(['low','high','normal'])
        new_entry['cholestrol'] = random.choice(['low','high','normal'])
        new_entry['Na'] = random.random()
        new_entry['K'] = random.random()
        new_entry['drug'] = random.choice(['A','B','C','D'])
        list_entries.append(new_entry)
    return list_entries

如果我们想生成五个新条目,我们可以使用entries = generateBasorexiaData (5)调用前面的函数。

既然我们知道了如何生成数据,让我们看看我们可以用这些数据做些什么。我们能想出医生开药的理由ABCD吗?我们能看到病人的血液值和医生开的药之间的关系吗?

很有可能,这个问题对你来说和对我来说一样难回答。尽管数据集乍一看可能是随机的,但事实上,我已经在患者的血液值和处方药物之间建立了一些明确的关系。让我们看看决策树是否能揭示这些隐藏的关系。

通过理解数据来理解任务

解决一个新的机器学习问题的第一步总是什么?

你完全正确:获得数据的感觉。我们越了解数据,就越了解我们试图解决的问题。在我们未来的努力中,这也将帮助我们选择合适的机器学习算法。

首先要意识到的是drug列实际上并不像所有其他列一样是一个特征值。由于我们的目标是根据患者的血液值预测将开出哪种药物,因此drug栏实际上成为了目标标签。换句话说,机器学习算法的输入将是 a 的血液值、年龄和性别…

预处理数据

为了让决策树算法理解我们的数据,我们需要将所有分类特征(sexBPcholesterol)转换为数字特征。最好的方法是什么?

没错:我们使用 scikit-learn 的DictVectorizer。就像我们在上一章中所做的那样,我们将想要转换的数据集输入到fit_transform方法中:

In [10]: from sklearn.feature_extraction import DictVectorizer
...      vec = DictVectorizer(sparse=False)
...      data_pre = vec.fit_transform(data)

然后,data_pre包含预处理后的数据。如果要看第一个数据点(即data_pre的第一行),我们将要素名称与对应的特征值进行匹配:

In [12]: vec.get_feature_names()
Out[12]: ['BP=high', 'BP=low', 'BP=normal', 'K', 'Na', 'age',
...       'cholesterol=high', 'cholesterol=normal',
...       'sex=F', 'sex=M']
In [13]: data_pre[0]
Out[13]: array([ 1\. , 0\. , 0\. , 0.06, 0.66, 33\. , 1\. , 0\. ,
                 1\. , 0\. ])

由此,我们可以看到,三个分类变量——血压(BP)、胆固醇水平(cholesterol)和性别(sex)——已经使用一热编码进行了编码。

为了确保我们的数据变量与 OpenCV 兼容,我们需要将所有内容转换为浮点值:

In [14]: import numpy as np
...      data_pre = np.array(data_pre, dtype=np.float32)
...      target = np.array(target, dtype=np.float32)

然后,剩下要做的就是将数据分成训练集和测试集,就像我们在第三章、监督学习的第一步中所做的那样。请记住,我们总是希望将训练集和测试集分开。由于在这个例子中我们只有 20 个数据点要处理,我们可能应该保留 10%以上的数据用于测试。15-5 的分成在这里似乎是合适的。我们可以明确地命令split函数产生正好五个测试样本:

In [15]: import sklearn.model_selection as ms
...      X_train, X_test, y_train, y_test =
...      ms.train_test_split(data_pre, target, test_size=5,
...      random_state=42)

构建树

使用 OpenCV 构建决策树的工作方式与第三章、监督学习的第一步非常相似。回想一下,所有的机器学习功能都存在于 OpenCV 3.1 的ml模块中:

  1. 我们可以使用以下代码创建一个空决策树:
In [16]: import cv2...      dtree = cv2.ml.dtree_create()
  1. 为了在训练数据上训练决策树,我们使用train方法。这就是我们之前将数据转换为浮点的原因——这样我们就可以在train方法中使用它:
In [17]: dtree.train(X_train, cv2.ml.ROW_SAMPLE, y_train)

这里,我们必须指定X_train中的数据样本是占据行(使用cv2.ml.ROW_SAMPLE)还是列(cv2.ml.COL_SAMPLE)。

  1. 然后,我们可以预测…

可视化经过训练的决策树

如果你刚刚起步,不太关心引擎盖下发生的事情,OpenCV 对决策树的实现已经足够好了。然而,在接下来的部分中,我们将切换到 scikit-learn。它的实现允许我们定制算法,并使研究树的内部工作变得更加容易。它的用法也有更好的记录。

在 scikit-learn 中,决策树可以用于分类和回归。它们位于tree模块中:

  1. 我们先从sklearn导入tree模块:
In [21]: from sklearn import tree
  1. 类似于 OpenCV,然后我们使用DecisionTreeClassifier构造函数创建一个空决策树:
In [22]: dtc = tree.DecisionTreeClassifier()
  1. 然后可以使用fit方法训练树:
In [23]: dtc.fit(X_train, y_train)
Out[23]: DecisionTreeClassifier(class_weight=None, criterion='gini',
            max_depth=None, max_features=None, max_leaf_nodes=None,
            min_impurity_split=1e-07, min_samples_leaf=1,
            min_samples_split=2, min_weight_fraction_leaf=0.0,
            presort=False, random_state=None, splitter='best')
  1. 然后,我们可以使用score方法计算训练集和测试集的准确度分数:
In [24]: dtc.score(X_train, y_train)
Out[24]: 1.0
In [25]: dtc.score(X_test, y_test)
Out[25]: 0.40000000000000002

现在,有一件很酷的事情:如果你想知道树的样子,你可以使用 GraphViz 从树结构中创建一个 PDF 文件(或者任何其他支持的文件类型)。为此,您需要首先安装 GraphViz。不要担心,因为它已经存在于我们在本书开头创造的环境中。

  1. 然后,回到 Python 中,您可以使用 scikit-learn 的export_graphviz导出器将 GraphViz 格式的树导出到文件tree.dot:
In [26]: with open("tree.dot", 'w') as f:
... tree.export_graphviz(clf, out_file=f)
  1. 然后,回到命令行,您可以使用 GraphViz 将tree.dot转换为(例如)一个 PNG 文件:
$ dot -Tpng tree.dot -o tree.png

或者,您也可以指定-Tpdf或任何其他支持的图像格式。前面的树的结果如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这一切意味着什么?让我们一步一步地分解这个图表。

调查决策树的内部工作方式

我们之前已经确定决策树基本上是一个流程图,它对数据做出一系列决策。这个过程从根节点(位于最顶端的节点)开始,在这里,我们根据某种决策规则将数据分成两组(仅适用于二叉树)。然后,重复该过程,直到所有剩余的样本具有相同的目标标签,此时我们已经到达叶节点。

在前面的垃圾邮件过滤器示例中,通过询问真/假问题来做出决定。例如,我们问一封电子邮件是否包含某个单词。如果是的话,我们沿着标记为真的边问下一个问题。然而,这不仅适用于分类特征,…

评定功能的重要性

我还没有告诉你的是你如何选择分割数据的特征。前面的根节点按照 *Na < = 0.72,*拆分数据,但是谁让树先关注钠呢?还有,0.72 这个数字到底是从哪里来的?

显然,有些功能可能比其他功能更重要。事实上,scikit-learn 提供了一个对特征重要性进行评级的功能,这是一个介于每个特征的 01 之间的数字,其中 0 表示根本不用于任何决策1 表示完美地预测了目标。特征重要性被标准化,因此它们的总和为 1:

In [27]: dtc.feature_importances_
Out[27]: array([ 0\.        , 0\.   , 0\.        , 0.13554217, 0.29718876,
                 0.24096386, 0\.   , 0.32630522, 0\.        , 0\. ])

如果我们提醒自己功能名称,就会清楚哪个功能似乎是最重要的。一个情节可能最能提供信息:

In [28]: plt.barh(range(10), dtc.feature_importances_, align='center',
...      tick_label=vec.get_feature_names())

这将导致以下条形图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在,很明显,知道给患者服用哪种药物的最重要特征实际上是患者的胆固醇水平是否正常。年龄、钠水平和钾水平也很重要。另一方面,性别和血压似乎没有任何区别。然而,这并不意味着性别或血压没有信息。这仅仅意味着决策树没有选择这些特征,可能是因为另一个特征会导致同样的分裂。

但是,坚持住。如果胆固醇水平如此重要,为什么它没有被选为树的第一个特征(即根节点)?为什么会选择先在钠水平上进行拆分?这就是我需要告诉你图中那个不祥的gini标签的地方。

Feature importances tell us which features are important for classification, but not which class label they are indicative of. For example, we only know that the cholesterol level is important, but we don’t know how that led to different drugs being prescribed. In fact, there might not be a simple relationship between features and classes.

理解决策规则

为了构建完美的树,您可能希望在信息量最大的特征处拆分树,从而得到最纯的子节点。然而,这个简单的想法带来了一些实际的挑战:

  • 实际上并不清楚什么是最有信息量的。我们需要一个具体的值,一个分数函数,或者一个数学方程来描述一个特征的信息量。
  • 为了找到最佳分割,我们必须在每个决策节点搜索所有的可能性。

幸运的是,决策树算法实际上为您完成了这两个步骤。scikit-learn 支持的两个最常用的标准如下:

  • criterion='gini':基尼不纯是一种错误分类的度量,目的是…

控制决策树的复杂性

如果你继续种植一棵树,直到所有的叶子都是纯净的,你通常会得到一棵太复杂而无法解释的树。纯叶的存在意味着树在训练数据上是 100%正确的,就像我们前面展示的树一样。因此,该树在测试数据集上的表现很可能很差,就像我们前面展示的树一样。我们说树超过了训练数据。

有两种常见的方法可以避免过度拟合:

  • 预修剪:这是提前停止树的创建的过程。
  • 后期修剪 (或只是修剪):这是先构建树,然后移除或折叠只包含少量信息的节点的过程。

有几种方法可以预先修剪一棵树,所有这些方法都可以通过向DecisionTreeClassifier构造函数传递可选参数来实现:

  • 通过max_depth参数限制树的最大深度
  • 通过max_leaf_nodes限制叶节点的最大数量
  • 通过min_samples_split需要节点中的最小点数来保持分裂

通常预修剪足以控制过度拟合。

试试我们的玩具数据集吧!你能让考试的分数有所提高吗?当您开始使用早期参数时,树布局如何变化?

In more complicated real-world scenarios, pre-pruning is no longer sufficient to control overfitting. In such cases, we want to combine multiple decision trees into what is known as a random forest. We will talk about this in Chapter 10, Ensemble Methods for Classification.

利用决策树诊断乳腺癌

现在我们已经构建了第一个决策树,是时候将我们的注意力转向一个真实的数据集了:乳腺癌威斯康星数据集(archive . ics . UCI . edu/ml/datasets/乳腺癌+癌症+威斯康星+(Diagnostic) )。

该数据集是医学影像研究的直接结果,被认为是当今的经典。数据集是从健康(良性)和癌性(恶性)组织的数字化图像创建的。不幸的是,我没能从最初的研究中找到任何公共领域的例子,但是图像看起来类似于下面的截图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这项研究的目的是对组织进行分类…

正在加载数据集

完整数据集是 scikit-learn 示例数据集的一部分。我们可以使用以下命令导入它:

  1. 首先,让我们使用load_breast_cancer函数加载数据集:
In [1]: from sklearn import datasets
...     data = datasets.load_breast_cancer()
  1. 与前面的示例一样,所有数据都包含在 2D 特征矩阵data.data中,其中行代表数据样本,列是特征值:
In [2]: data.data.shape
Out[2]: (569, 30)
  1. 通过查看提供的功能名称,我们认识到前面提到的一些功能:
In [3]: data.feature_names
Out[3]: array(['mean radius', 'mean texture', 'mean perimeter',
               'mean area', 'mean smoothness', 'mean compactness',
               'mean concavity', 'mean concave points',
               'mean symmetry', 'mean fractal dimension',
               'radius error', 'texture error', 'perimeter error',
               'area error', 'smoothness error',
               'compactness error', 'concavity error',
               'concave points error', 'symmetry error',
               'fractal dimension error', 'worst radius',
               'worst texture', 'worst perimeter', 'worst area',
               'worst smoothness', 'worst compactness',
               'worst concavity', 'worst concave points',
               'worst symmetry', 'worst fractal dimension'], 
              dtype='<U23')
  1. 由于这是一个二进制分类任务,我们希望找到两个目标名称:
In [4]: data.target_names
Out[4]: array(['malignant', 'benign'], dtype='<U9')
  1. 让我们保留大约 20%的数据样本用于测试:
In [5]: import sklearn.model_selection as ms
...     X_train, X_test, y_train, y_test =
...     ms.train_test_split(data_pre, target, test_size=0.2,
...     random_state=42)
  1. 你当然可以选择不同的比例,但大多数人通常使用 70-30、80-20 或 90-10 这样的比例。这完全取决于数据集的大小,但最终应该不会有太大的不同。将数据拆分为 80-20 应该会产生以下集合大小:
In [6]: X_train.shape, X_test.shape
Out[6]: ((455, 30), (114, 30))

构建决策树

如前所示,我们可以使用 scikit-learn 的tree模块创建决策树。现在,我们不要指定任何可选参数:

  1. 我们将从创建决策树开始:
In [5]: from sklearn import tree...     dtc = tree.DecisionTreeClassifier()
  1. 你还记得如何训练决策树吗?我们将为此使用fit函数:
In [6]: dtc.fit(X_train, y_train)Out[6]: DecisionTreeClassifier(class_weight=None, criterion='gini',                               max_depth=None, max_features=None,                               max_leaf_nodes=None,                               min_impurity_split=1e-07,                               min_samples_leaf=1,                               min_samples_split=2,                               min_weight_fraction_leaf=0.0,                               presort=False, random_state=None,                               splitter='best')
  1. 因为我们没有指定任何预修剪参数,所以我们希望这样…

使用决策树进行回归

虽然到目前为止,我们一直专注于在分类任务中使用决策树,但是您也可以将它们用于回归。但是您需要再次使用 scikit-learn,因为 OpenCV 不提供这种灵活性。因此,我们在此仅简要回顾其功能:

  1. 假设我们想用决策树来拟合一个正弦波。为了让事情变得有趣,我们还将使用 NumPy 的随机数生成器向数据点添加一些噪声:
In [1]: import numpy as np
...     rng = np.random.RandomState(42)
  1. 然后我们创建 100 个随机间隔的 x 值,介于 0 和 5 之间,并计算相应的 sin 值:
In [2]: X = np.sort(5 * rng.rand(100, 1), axis=0)
...     y = np.sin(X).ravel()
  1. 然后,我们在y(使用y[::2])中的每隔一个数据点添加噪声,由0.5缩放,因此我们不会引入太多抖动:
In [3]: y[::2] += 0.5 * (0.5 - rng.rand(50))
  1. 然后,您可以像以前创建任何其他树一样创建一个回归树。

一个小的区别是ginientropy的分割标准不适用于回归任务。相反,scikit-learn 提供了两种不同的拆分标准:

  1. 使用最小均方误差准则,我们将建立两棵树。让我们首先构建一个深度为 2 的树:
In [4]: from sklearn import tree
In [5]: regr1 = tree.DecisionTreeRegressor(max_depth=2,
...     random_state=42)
...     regr1.fit(X, y)
Out[5]: DecisionTreeRegressor(criterion='mse', max_depth=2,
                              max_features=None, max_leaf_nodes=None,
                              min_impurity_split=1e-07,
                              min_samples_leaf=1, min_samples_split=2,
                              min_weight_fraction_leaf=0.0,
                              presort=False, random_state=42,
                              splitter='best')
  1. 接下来,我们将构建一个最大深度为 5:
In [6]: regr2 = tree.DecisionTreeRegressor(max_depth=5,
...     random_state=42)
...     regr2.fit(X, y)
Out[6]: DecisionTreeRegressor(criterion='mse', max_depth=5,
                              max_features=None, max_leaf_nodes=None,
                              min_impurity_split=1e-07,
                              min_samples_leaf=1, min_samples_split=2,
                              min_weight_fraction_leaf=0.0,
                              presort=False, random_state=42,
                              splitter='best')

然后我们可以像线性回归器一样使用决策树,从第三章、监督学习的第一步

  1. 为此,我们创建了一个测试集,其中 x 值在 0 到 5 的整个范围内密集采样:
In [7]: X_test = np.arange(0.0, 5.0, 0.01)[:, np.newaxis]
  1. 预测的 y 值可以通过predict方法获得:
In [8]: y_1 = regr1.predict(X_test)
...     y_2 = regr2.predict(X_test)
  1. 如果我们将所有这些绘制在一起,我们可以看到决策树是如何不同的:
In [9]: import matplotlib.pyplot as plt
... %matplotlib inline
... plt.style.use('ggplot')

... plt.scatter(X, y, c='k', s=50, label='data')
... plt.plot(X_test, y_1, label="max_depth=2", linewidth=5)
... plt.plot(X_test, y_2, label="max_depth=5", linewidth=3)
... plt.xlabel("data")
... plt.ylabel("target")
... plt.legend()
Out[9]: <matplotlib.legend.Legend at 0x12d2ee345f8>

这将产生以下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里,粗红线代表深度为 2 的回归树。您可以看到树是如何使用这些粗略的步骤来近似数据的。较细的蓝线属于深度为 5 的回归树;增加的深度允许树进行许多更精细的近似。因此,该树可以更好地逼近数据。然而,由于这种额外的力量,树也更容易拟合噪声值,这尤其可以从图右侧的尖峰中看出。

摘要

在本章中,我们学习了决策树的所有知识,以及如何将它们应用于分类和回归任务。我们讨论了数据生成、过拟合以及通过调整修剪前和修剪后设置来避免这种现象的方法。我们还学习了如何使用基尼系数和信息增益等指标来评估节点分裂的质量。最后,我们将决策树应用于医学数据来检测癌组织。我们将在本书的最后回到决策树,届时我们将把多棵树组合成一个随机森林。但是现在,让我们进入一个新的话题。

在下一章中,我们将介绍机器学习领域的另一个主要内容:支持向量机…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值