开发者的机器学习-全-
开发者的机器学习(全)
原文:
annas-archive.org/md5/8a94ca38f52581df16e4dcd003cd91fa译者:飞龙
前言
机器学习是目前最耀眼的学科之一。媒体赞誉其为工作的未来,据说它是近几个月来任何重大技术投资的组成部分,在一个一切由数据和自动化驱动的世界中。它在许多领域得到广泛应用,如图像理解、机器人技术、搜索引擎、自动驾驶汽车等,应用领域几乎每天都在增加。在这本书中,我们将使用代码和图表作为主要的概念载体,研究机器学习的动机和当前技术,省略了基本数学结果。
我们将开始讨论基本的机器学习概念、其分支和问题类型。然后,将有一个关于掌握即将到来的技术所需的基本数学概念的解释章节。随着我们通过章节的进展,将解释越来越复杂和精细的模型,从线性回归开始,然后是逻辑回归、神经网络及其更近期的变体(CNNs、RNNs),最后以对更高级机器学习技术(如 GANs 和强化学习)的综合介绍结束。
这本书旨在帮助开发者最终理解机器学习炒作的实质,并了解主要的基本概念,采用算法视角,以及更正式的数学定义。本书在 Python 中实现代码概念,考虑到其界面的简洁性,以及 Python 提供了一套无与伦比的工具,可以从书中的代码中继续学习。因此,熟悉 Python 编程对于玩转代码肯定有帮助,但对于在其他语言中经验丰富的程序员来说应该是可管理的。
你将学习如何就所需算法的类型做出明智的决定,以解决你自己的机器学习问题,并了解这些算法如何工作以获得最佳结果。如果你想在日常中以编码友好的语言理解机器学习,并拥有刚好足够的信息能够跳入这个领域,这本书肯定会是你的救星!
本书涵盖的内容
第一章,引言 - 机器学习与统计学,涵盖了机器学习中的各种入门概念。它讲述了历史、分支和一般学科概念。还介绍了理解随后开发的大多数技术所需的基数学概念。
第二章,学习过程,涵盖了机器学习过程工作流程中的所有步骤,并展示了所有这些阶段的有用工具和概念定义。
第三章,聚类,涵盖了无监督学习中的几种技术,特别是 K-Means 和 K-NN 聚类。
第四章,线性与逻辑回归,涵盖了两种相当不同的监督学习算法,它们具有相似的名字:线性回归(我们将用它来进行时间序列预测),和逻辑回归(我们将用它来进行分类)。
第五章,神经网络,涵盖了现代机器学习应用的基本构建块之一,并以实际步骤逐步构建神经网络结束。
第六章,卷积神经网络,涵盖了这种强大的神经网络变体,并以对一个非常著名的 CNN 架构 VGG16 的内部结构的实际考察结束,该架构在实用应用中得到了应用。
第七章,循环神经网络,概述了 RNN 概念,并完整地描述了最常用的架构 LSTM 的所有阶段。最后,分享了一个关于时间序列预测的实际练习。
第八章,近期模型与发展,涵盖了两个在领域内引起巨大兴趣的即将到来的技术:生成对抗网络,以及整个强化学习领域。
第九章,软件安装与配置,涵盖了在三个操作系统(Linux、macOS 和 Windows)上安装所有必要的软件包。
阅读这本书所需的条件
本书专注于机器学习概念,并以 Python 语言(版本 3)作为计算工具。我们使用了 Python 3 和 Jupyter Notebook 来构建我们的工作簿,您可以通过编辑和操作它们来更好地理解概念。我们专注于如何以最佳方式利用各种 Python 库来构建实际应用。本着这种精神,我们尽量使所有代码都尽可能友好和易于阅读。我们相信这将使我们的读者能够轻松理解代码,并在不同场景中方便地使用它。
这本书面向的对象
这本书是为那些希望通过计算密集型方法理解机器学习基本概念的开发者/技术爱好者而写的。这本书适合任何脚本语言编程的人阅读,但熟悉 Python 将有助于您与代码互动。它对当前的数据科学家来说也很有用,可以帮助他们回归基本概念,并使用新颖的动手方法来理解这些概念。
习惯用法
在这本书中,您将找到许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称如下所示:“让我们使用np生成大量伯努利分布的事件,并绘制这个分布的趋势。”
代码块设置如下:
def mean(sampleset): #Definition header for the mean function
total=0
for element in sampleset:
total=total+element
return total/len(sampleset)
新术语和重要词汇以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:“为了下载新模块,我们将转到文件 | 设置 | 项目名称 | 项目解释器。”
警告或重要提示如下所示。
小贴士和技巧看起来像这样。
读者反馈
我们始终欢迎读者的反馈。告诉我们您对这本书的看法——您喜欢什么或不喜欢什么。读者反馈对我们很重要,因为它帮助我们开发出您真正能从中获得最大价值的标题。要发送一般反馈,请简单地发送电子邮件至feedback@packtpub.com,并在邮件主题中提及书名。如果您在某个主题领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在您是 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助您充分利用您的购买。
下载示例代码
您可以从您的账户下载这本书的示例代码文件。www.packtpub.com。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。您可以通过以下步骤下载代码文件:
-
使用您的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标指针悬停在顶部的“支持”标签上。
-
点击“代码下载与勘误”。
-
在搜索框中输入书名。
-
选择您想要下载代码文件的书籍。
-
从下拉菜单中选择您购买此书的来源。
-
点击“代码下载”。
文件下载完成后,请确保您使用最新版本的软件解压缩或提取文件夹:
-
Windows 上的 WinRAR / 7-Zip
-
Mac 上的 Zipeg / iZip / UnRarX
-
Linux 上的 7-Zip / PeaZip
书籍的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Machine**-**Learning-for-Developers。我们还有其他来自我们丰富图书和视频目录的代码包可供在github.com/PacktPublishing/找到。查看它们吧!
勘误
尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这个问题,我们将不胜感激。通过这样做,您可以避免其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。要查看之前提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分下。
盗版
互联网上对版权材料的盗版是一个持续存在的问题,跨越所有媒体。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现任何形式的我们作品的非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。请通过发送链接到疑似盗版材料给我们发送邮件到copyright@packtpub.com。我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。
询问
如果您在这本书的任何方面遇到问题,您可以给我们发送邮件到questions@packtpub.com,我们将尽力解决问题。
第一章:引言 - 机器学习与统计学
机器学习无疑是近年来讨论最多的领域之一,而且有充分的理由。每天都有新的应用和模型被发现,世界各地的研究人员每天都在宣布在结果质量方面的令人印象深刻的进展。
每天都有许多新的从业者决定参加课程并寻找入门材料,以便他们可以使用这些新可用的技术来改进他们的应用。但在许多情况下,机器学习的整个体系,如文献中通常所解释的,需要良好的数学概念理解作为先决条件,从而为程序员设置了很高的门槛,而程序员通常具有良好的算法技能,但对高等数学概念不太熟悉。
这第一章将是对该领域的总体介绍,涵盖机器学习的主要研究领域,并会概述基本的统计学、概率论和微积分,同时通过源代码示例,以允许你实验所提供的公式和参数的方式呈现。
在这一章中,你将学习以下主题:
-
什么是机器学习?
-
机器学习领域
-
统计学和概率论的基本要素
-
微积分的基本要素
我们周围的世界提供了大量的数据。在基本层面上,我们不断地从周围的文本、图像、声音和其他类型的信息中获取和吸收知识。因此,数据的可用性是获取执行任务技能的过程中的第一步。
世界各地有成千上万的计算设备收集和存储了大量的基于图像、视频和文本的信息。因此,学习的原材料显然是丰富的,并且以计算机可以处理的形式提供。
这本书所讨论的学科崛起的起点:研究允许计算机从数据中学习而不需要明确编程的技术和方法。
机器学习的更正式定义,来自汤姆·米切尔,如下所示:
“如果一个计算机程序在任务 T 和性能度量 P 方面,通过经验 E 学习,那么它的性能随着经验 E 的提高而提高。”
这个定义是完整的,并重新确立了在每一个机器学习项目中扮演角色的要素:要执行的任务、连续的实验以及清晰和适当的性能度量。用更简单的话说,我们有一个程序,它根据经验并在一定标准的指导下改进其完成任务的方式。
机器学习在大图景中的位置
机器学习作为一个学科,不是一个孤立的领域——它被包含在一个更广泛的领域内,即人工智能(AI)。但正如你可以猜到的,机器学习并不是凭空出现的。作为一个学科,它有其前辈,并且它已经按照越来越复杂的阶段在以下四个明显区分的步骤中不断发展:
-
机器学习的第一个模型涉及基于规则的决策和基于数据算法的简单级别,它本身包括所有可能的后果和决策规则,作为先决条件,意味着所有可能的选项都将由该领域的专家事先硬编码到模型中。这种结构在 1950 年第一代编程语言出现以来的大多数应用中得到了实施。这类算法处理的主要数据类型和函数是布尔类型,因为它专门处理是或否的决策。
-
在统计推理的第二发展阶段,我们开始让数据的概率特性发挥作用,除了之前预先设定的选择。这更好地反映了现实世界问题的模糊性,在这些问题中,异常值很常见,而且比固定的提问方式更重要的是考虑数据的非确定性倾向。这个学科向数学工具的混合体中增加了贝叶斯概率理论的元素。属于这一类的方法包括曲线拟合(通常是线性或多项式),它具有与数值数据一起工作的共同特性。
-
机器学习阶段是我们将在整本书中工作的领域,它涉及比上一阶段最简单的贝叶斯元素更复杂的任务。
机器学习算法最突出的特点是它们可以从数据中泛化模型,但模型能够生成自己的特征选择器,这些选择器不受刚性目标函数的限制,因为它们是在训练过程中生成和定义的。这类模型的另一个不同之处在于,它们可以接受大量不同类型的数据作为输入,例如语音、图像、视频、文本以及其他可以表示为向量的数据。
-
人工智能是抽象能力规模中的最后一步,在某种程度上包括所有之前的算法类型,但有一个关键的区别:人工智能算法能够将学习到的知识应用于解决训练期间从未考虑过的任务。与此算法一起工作的数据类型甚至比机器学习支持的数据类型更通用,并且根据定义,它们应该能够将解决问题的能力从一个数据类型转移到另一个数据类型,而无需对模型进行完全重新训练。这样,我们就可以开发一个用于黑白图像目标检测的算法,并且模型可以将知识抽象出来,将模型应用于彩色图像。
在以下图表中,我们表示了这些四个向真实人工智能应用发展的阶段:

机器学习的类型
让我们尝试剖析不同类型的机器学习项目,从实施者的角度出发,从先前知识等级开始。项目可以是以下类型:
-
监督学习:在这种学习类型中,我们被提供一组真实数据的样本集,以及模型应用后应给出的结果。在统计术语中,我们有所有训练集实验的结果。
-
无监督学习:这种学习类型仅提供问题域中的样本数据,但将相似数据分组并应用类别的任务没有先前信息可以从中推断。
-
强化学习:这种学习类型没有标记的样本集,并且参与元素的数量不同,包括代理、环境和学习最优策略或步骤集,通过使用奖励或惩罚(每次尝试的结果)来最大化以目标为导向的方法。
看一下以下图表:

机器学习的主要领域
监督等级
学习过程支持监督领域的逐步步骤:
-
无监督学习没有关于任何样本的类别或值的先验知识,它应该自动推断。
-
半监督学习需要已知样本的种子,模型从该种子推断剩余样本的类别或值。
-
监督学习:这种方法通常包括一组已知样本,称为训练集,另一组用于验证模型的泛化能力,第三组,称为测试集,在训练过程之后使用,以获得训练集之外的独立样本数量,并保证测试的独立性。
在以下图表中,描述了提到的方法:

无监督学习、半监督学习和监督学习的训练技术的图形表示
监督学习策略 - 回归与分类
这种学习类型有两个主要问题类型需要解决:
-
回归问题:这类问题接受问题域中的样本,并在训练模型后,通过比较输出与真实答案来最小化误差,这允许在给定一个新未知样本时预测正确答案
-
分类问题:这类问题使用领域中的样本为新未知样本分配标签或组
无监督问题解决——聚类
大多数无监督问题解决都是通过观察项的相似性或共享特征的价值来对项进行分组,因为没有关于先验类别的确切信息。这种技术被称为聚类。
在这些主要问题类型之外,还存在两者的混合,这被称为半监督问题解决,其中我们可以在训练时训练一组标记的元素,并使用推理将信息分配给未标记数据。为了将数据分配给未知实体,使用了三个主要标准——平滑性(彼此靠近的点属于同一类)、聚类(数据倾向于形成簇,这是平滑性的特殊情况)和流形(数据属于比原始领域维度低得多的流形)。
交易工具——编程语言和库
由于本书的目标读者是开发者,我们认为使用真实代码解释数学概念的方法是自然而然的选择。
在选择代码示例的编程语言时,最初的方法是使用多种技术,包括一些前沿的库。在咨询社区后,很明显,在解释概念时,简单语言会更受欢迎。
在选项中,理想的候选者将是一种易于理解的语言,具有现实世界的机器学习采用,并且也相关。
对于这项任务,最明显的候选者是 Python,它满足了所有这些条件,尤其是在过去几年中,它已经成为了机器学习的首选语言,无论是对于新手还是专业从业者。
在下面的图表中,我们比较了机器学习编程语言领域的先前明星 R,我们可以清楚地得出使用 Python 的巨大、有利趋势。这意味着在这本书中学到的技能现在和可预见的未来都将相关:

机器学习领域的 R 和 Python 的兴趣图。
除了 Python 代码外,我们还将得到 Python 生态系统中一些最著名的数值、统计和图形库的帮助,即 pandas、NumPy 和 matplotlib。对于深度神经网络示例,我们将使用 Keras 库,以 TensorFlow 作为后端。
Python 语言
Python 是一种通用脚本语言,由荷兰程序员 Guido Van Rossum 于 1989 年创建。它具有非常简单的语法和极大的可扩展性,这得益于其众多的扩展库,使其非常适合原型设计和通用编码。由于其本地的 C 绑定,它也可以成为生产部署的候选者。
这种语言实际上被用于各种领域,从网络开发到科学计算,以及作为通用脚本工具的使用。
NumPy 库
如果我们必须选择本书中必须使用的库,以及用 Python 编写的非平凡数学应用,那它必须是 NumPy。这个库将帮助我们使用以下组件实现统计和线性代数应用:
-
一种多功能的 N 维数组对象,性能出色
-
许多可以无缝应用于这些数组的数学函数
-
线性代数基本操作
-
随机数分布和强大的统计软件包
-
与所有主要的机器学习软件包兼容
本书将广泛使用 NumPy 库,利用其许多基本操作来简化代码中的概念解释。
matplotlib 库
数据绘图是数据科学的一个基本组成部分,通常是分析师执行的第一步,以了解提供的数据集中发生了什么。
因此,我们需要一个非常强大的库来能够绘制输入数据,并且也要能够表示结果输出。在本书中,我们将使用 Python 的 matplotlib 库来描述概念和模型的结果。
那么,什么是 matplotlib?
Matplotlib 是一个广泛使用的绘图库,特别设计用于 2D 图表。从这个库中,我们将专注于使用pyplot模块,它是 matplotlib API 的一部分,具有 MATLAB 类似的方法,并直接支持 NumPy。对于那些不熟悉 MATLAB 的人来说,它已经数十年来是科学和工程领域的默认数学笔记本环境。
描述的方法将被用来展示涉及的大多数概念,实际上,读者只需使用这两个库和提供的代码,就能生成本书中的许多示例。
Pandas
Pandas通过一种特殊的结构,称为DataFrame,补充了之前提到的库,并添加了许多统计和数据操作方法,例如 I/O,用于多种不同格式,如切片、子集、处理缺失数据、合并和重塑等。
DataFrame 对象是整个库中最有用的特性之一,它提供了一个特殊的二维数据结构,列可以是不同的数据类型。其结构非常类似于数据库表,但沉浸在灵活的编程运行时和生态系统中,如 SciPy。这些数据结构也与 NumPy 矩阵兼容,因此我们可以以最小的努力对数据进行高性能操作。
SciPy
SciPy 是一组非常实用的科学 Python 库,包括 NumPy、pandas、matplotlib 等,但它也是生态系统中的核心库,我们可以通过它执行许多额外的基本数学运算,例如积分、优化、插值、信号处理、线性代数、统计学和文件 I/O。
Jupyter 笔记本
Jupyter 是一个成功的基于 Python 的项目例子,它也是我们将要使用的最强大的工具之一,通过代码探索和理解数据。
Jupyter 笔记本是由代码、图形或格式化文本交织的单元格组成的文档,从而形成一个非常灵活和强大的研究环境。所有这些元素都被一个方便的 Web 界面所包裹,该界面与 IPython 交互式解释器交互。
一旦加载了 Jupyter 笔记本,整个环境和所有变量都存储在内存中,可以更改和重新定义,从而允许研究和实验,如下面的截图所示:

Jupyter 笔记本
这个工具将是本书教学过程中的一个重要部分,因为大多数 Python 示例都将以这种格式提供。在本书的最后一章,你可以找到完整的安装说明。
安装完成后,你可以进入你的笔记本所在的目录,然后通过输入 jupyter notebook 来调用 Jupyter。
基本数学概念
正如我们在前面的章节中看到的,本书的主要目标受众是希望理解机器学习算法的开发者。但为了真正掌握其背后的动机和原因,有必要回顾和构建所有基本推理,这包括统计学、概率论和微积分。
我们将首先从统计学的一些基础知识开始。
统计学 - 模型不确定性的基本支柱
统计学可以被定义为一种学科,它使用数据样本来提取和支撑对更大数据样本的结论。鉴于机器学习包括数据属性研究和数据赋值的大部分,我们将使用许多统计概念来定义和证明不同的方法。
描述性统计学 - 主要操作
在接下来的章节中,我们将开始定义统计学领域的根本操作和度量,以便能够从基本概念出发。
平均值
这是统计学中最直观且最常用的概念之一。给定一组数字,该组数字的均值是所有元素的总和除以该组中元素的数量。
表示均值的公式如下:

尽管这是一个非常简单的概念,但我们将编写一个 Python 代码示例,我们将创建一个样本集,将其表示为线形图,并将整个集的均值标记为一条线,这条线应该位于样本的加权中心。这将作为 Python 语法的介绍,也是实验 Jupyter 笔记本的一种方式:
import matplotlib.pyplot as plt #Import the plot library
def mean(sampleset): #Definition header for the mean function
total=0
for element in sampleset:
total=total+element
return total/len(sampleset)
myset=[2.,10.,3.,6.,4.,6.,10.] #We create the data set
mymean=mean(myset) #Call the mean funcion
plt.plot(myset) #Plot the dataset
plt.plot([mymean] * 7) #Plot a line of 7 points located on the mean
该程序将输出数据集元素的时序,然后绘制一条均值高度的线。
如下所示,均值是描述样本集趋势的一种简洁(一个值)方式:

在这个第一个例子中,我们处理的是一个非常均匀的样本集,因此均值对其值非常有信息量。但让我们尝试使用一个非常分散的样本集进行相同的操作(也鼓励您尝试调整这些值):

方差
正如我们在第一个例子中看到的,均值不足以描述非均匀或非常分散的样本。
为了添加一个描述样本集值分散程度的唯一值,我们需要查看方差的概念,这需要从样本集的均值开始,然后平均样本与提供的均值的距离。方差越大,样本集越分散。
方差的经典定义如下:

让我们编写以下示例代码片段来阐述这一概念,采用之前使用的库。为了清晰起见,我们重复声明mean函数:
import math #This library is needed for the power operation
def mean(sampleset): #Definition header for the mean function
total=0
for element in sampleset:
total=total+element
return total/len(sampleset)
def variance(sampleset): #Definition header for the mean function
total=0
setmean=mean(sampleset)
for element in sampleset:
total=total+(math.pow(element-setmean,2))
return total/len(sampleset)
myset1=[2.,10.,3.,6.,4.,6.,10.] #We create the data set
myset2=[1.,-100.,15.,-100.,21.]
print "Variance of first set:" + str(variance(myset1))
print "Variance of second set:" + str(variance(myset2))
上述代码将生成以下输出:
Variance of first set:8.69387755102
Variance of second set:3070.64
如您所见,第二个集合的方差由于实际分散的值而要高得多。我们计算平方距离的均值有助于真正突出差异,因为它是一个二次运算。
标准差
标准差简单来说是一种使均值平方的平方性质正规化的方法,有效地线性化这一项。这种度量对于其他更复杂的操作可能是有用的。
这是标准差的官方形式:

概率和随机变量
我们现在将要研究理解本书所有概念所需的最重要学科。
概率是一门数学学科,其主要任务是研究随机事件。在更实用的定义中,概率通常试图量化与事件相关联的确定性(或相反,不确定性)的水平,从可能发生的事件的总体中。
事件
为了理解概率,我们首先需要定义事件。事件是在一个实验中,我们执行一个具有不同可能结果的确定动作,该实验的所有可能结果的一个子集。
事件的一些例子是特定骰子的数字出现,以及在装配线上出现特定类型的产品缺陷。
概率
根据前面的定义,概率是事件发生的可能性。概率被量化为一个介于0和1之间的实数,并且当事件发生的可能性增加时,分配的概率P趋向于1。
事件发生的概率的数学表达式是P(E)。
随机变量和分布
在分配事件概率时,我们也可以尝试覆盖整个样本,并为样本域的每个可能结果分配一个概率值。
这个过程确实具有函数的所有特征,因此我们将有一个随机变量,它将为每个可能的事件结果都有一个值。我们将把这个函数称为随机函数。
这些变量可以是以下两种类型之一:
-
离散:如果结果的数量是有限的,或者可数的无限。
-
连续:如果结果集属于一个连续区间。
这个概率函数也被称为概率分布。
有用的概率分布
在多种可能的概率分布中,有许多函数因其特殊的性质或它们代表的流行问题而被研究和分析。
我们将描述那些对机器学习发展有特殊影响的最常见的分布。
伯努利分布
让我们从一种简单的分布开始:一种具有二元结果,并且非常类似于抛掷一枚(公平的)硬币的分布。
这个分布代表一个事件,该事件以概率p(我们称之为正面)取值为1,以概率1-p(我们称之为反面)取值为0。
为了可视化这一点,让我们使用np生成大量伯努利分布的事件,并绘制这个分布的趋势,以下只有两种可能的结果:
plt.figure()
distro = np.random.binomial(1, .6, 10000)/0.5
plt.hist(distro, 2 , normed=1)
下面的图表通过直方图显示了二项分布,展示了结果概率的互补性质:

二项分布
因此,在这里我们看到可能结果的补集概率的非常明显的趋势。现在让我们用更多的可能结果来补充模型。当它们的数量大于 2 时,我们谈论的是多项分布:
plt.figure()
distro = np.random.binomial(100, .6, 10000)/0.01
plt.hist(distro, 100 , normed=1)
plt.show()
看一下下面的图表:

有 100 个可能结果的多项分布
均匀分布
这个非常常见的分布是我们将看到的第一个连续分布。正如其名所示,它在域的任何区间上都有恒定的概率值。
为了积分到 1,a和b是函数的极值,这个概率的值为1/(b-a)。
让我们使用以下代码生成一个具有非常规则直方图的样本均匀分布的图表:
plt.figure()
uniform_low=0.25
uniform_high=0.8
plt.hist(uniform, 50, normed=1)
plt.show()
看一下下面的图表:

均匀分布
正态分布
这个非常常见的连续随机函数,也称为高斯函数,可以用均值和方差这样的简单度量来定义,尽管形式上有些复杂。
这是函数的规范形式:

看一下下面的代码片段:
import matplotlib.pyplot as plt #Import the plot library
import numpy as np
mu=0\.
sigma=2\.
distro = np.random.normal(mu, sigma, 10000)
plt.hist(distro, 100, normed=True)
plt.show()
下面的图表显示了生成的分布的直方图:

正态分布
逻辑分布
这个分布与正态分布相似,但形态上的差异是尾部更细长。这个分布的主要重要性在于它的累积分布函数(CDF),我们将在接下来的章节中使用,并且肯定会看起来很熟悉。
让我们首先使用以下代码片段来表示基本分布:
import matplotlib.pyplot as plt #Import the plot library
import numpy as np
mu=0.5
sigma=0.5
distro2 = np.random.logistic(mu, sigma, 10000)
plt.hist(distro2, 50, normed=True)
distro = np.random.normal(mu, sigma, 10000)
plt.hist(distro, 50, normed=True)
plt.show()
看一下下面的图表:

逻辑分布(红色)与正态分布(蓝色)
然后,如前所述,让我们计算逻辑分布的累积分布函数(CDF),这样你将看到一个非常熟悉的图形,S 型曲线,我们将在回顾神经网络激活函数时再次看到:
plt.figure()
logistic_cumulative = np.random.logistic(mu, sigma, 10000)/0.02
plt.hist(logistic_cumulative, 50, normed=1, cumulative=True)
plt.show()
看一下下面的图表:

逻辑分布的倒数
概率函数的统计量
在本节中,我们将看到可以应用于概率的最常见的统计量。第一组度量是均值和方差,它们与我们看到的统计学介绍中的定义没有区别。
偏度
这个度量表示横向偏差,或者更一般地说,是从中心偏离,或者概率分布的对称性(或缺乏对称性)。一般来说,如果偏度为负,则表示向右偏离,如果为正,则表示向左偏离:

看一下下面的图表,它描述了偏度的统计分布:

分布形状如何影响偏度的描述。
峰度
峰度给我们一个关于分布集中趋势的概念,定义了中心区域有多尖锐,或者相反——函数的尾部分布有多分散。
峰度的公式如下:

在下面的图表中,我们可以清楚地看到我们正在学习的新的度量如何直观地理解:

分布形状如何影响峰度的描述。
微分学元素
为了涵盖机器学习的基本知识,特别是如梯度下降这样的学习算法,我们将向您介绍微分学中涉及的概念。
基础知识
要涵盖达到梯度下降理论所需的微积分术语,需要许多章节,因此我们假设您已经理解了最著名连续函数的性质(如线性、二次、对数和指数)以及极限的概念。
为了清晰起见,我们将发展一元函数的概念,然后简要扩展以涵盖多元函数。
寻找变化——导数
在上一节中,我们建立了函数的概念。除了在整个定义域中定义的常数函数外,所有函数都有某种价值动态。这意味着对于某些确定的 x 值,f(x1)与f(x2)是不同的。
微分学的目的是衡量变化。对于这个具体任务,17 世纪许多数学家(莱布尼茨和牛顿是最杰出的代表)努力寻找一个简单的模型来衡量和预测一个符号定义的函数随时间的变化。
这项研究引导该领域到一个美妙的概念——一个符号结果,在特定条件下,告诉你函数在某个点的变化程度和方向。这就是导数的概念。
滑动在斜率上
如果我们要衡量函数随时间的变化,第一步直观的步骤就是取函数的值,然后在随后的点进行测量。从第二个值中减去第一个值将给我们一个关于函数随时间变化多少的概念:
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
def quadratic(var):
return 2* pow(var,2)
x=np.arange(0,.5,.1)
plt.plot(x,quadratic(x))
plt.plot([1,4], [quadratic(1), quadratic(4)], linewidth=2.0)
plt.plot([1,4], [quadratic(1), quadratic(1)], linewidth=3.0,
label="Change in x")
plt.plot([4,4], [quadratic(1), quadratic(4)], linewidth=3.0,
label="Change in y")
plt.legend()
plt.plot (x, 10*x -8 )
plt.plot()
在前面的代码示例中,我们首先定义了一个样本二次方程(2*x²)*,然后定义了我们将在其中使用arange函数的域的部分(从0到0.5,以0.1为步长)。
然后,我们定义一个区间,在这个区间内我们测量 y 随 x 的变化,并画出表示这种测量的线条,如下面的图表所示:

实施微分的初始设置图示
在这种情况下,我们在 x=1 和 x=4 处测量函数,并定义这个区间的变化率为以下:

应用公式,样本的结果是 (36-0)/3= 12。
这种初始方法可以作为一种近似测量这种动态的方式,但它太依赖于我们进行测量的点,并且需要在每一个我们需要的区间进行测量。
为了更好地理解函数的动态,我们需要能够在函数定义域的每一个点上定义和测量瞬时变化率。
这种瞬时变化的概念让我们需要减小定义域的 x 值之间的距离,在这一点上它们之间的距离非常短。我们将用初始值 x 和随后的值, x + Δx 来制定这种方法:

在下面的代码中,我们近似差值,逐步减小 Δx:
initial_delta = .1
x1 = 1
for power in range (1,6):
delta = pow (initial_delta, power)
derivative_aprox= (quadratic(x1+delta) - quadratic (x1) )/
((x1+delta) - x1 )
print "del ta: " + str(delta) + ", estimated derivative: " +
str(derivative_aprox)
在前面的代码中,我们首先定义了一个初始的 delta,这带来了一个初始近似。然后,我们应用差分函数,随着 delta 值的减小,这要归功于对 0.1 进行递增幂运算。我们得到的结果如下:
delta: 0.1, estimated derivative: 4.2
delta: 0.01, estimated derivative: 4.02
delta: 0.001, estimated derivative: 4.002
delta: 0.0001, estimated derivative: 4.0002
delta: 1e-05, estimated derivative: 4.00002
随着分离的减小,很明显变化率将围绕 4 上下波动。但这个过程何时停止呢?实际上,我们可以说这个过程可以无限进行下去,至少在数值意义上是这样。
这时,极限的概念直观地出现了。然后我们将定义这个过程,即让 Δ 无限减小,并将其称为 f(x) 或 f'(x) 的导数:

这就是导数的正式定义。
但是数学家并没有止步于这些繁琐的计算,进行了大量的数值运算(在 17 世纪这些运算大多是通过手工完成的),并希望进一步简化这些运算。
如果我们再进行一步,可以象征性地定义一个函数的导数会怎样呢?
这就需要构建一个函数,通过替换 x 变量的值,就能得到相应函数的导数。这一巨大的步骤在 17 世纪就已经实现,对于不同的函数族,从抛物线 (y=x²+b) 开始,然后是更复杂的函数:

链式法则
函数导数的符号确定的一个非常重要的结果是链式法则。这个公式最早在 1676 年莱布尼茨的一篇论文中提到,使得解决复合函数的导数变得非常简单和优雅,简化了非常复杂函数的求解。
为了定义链式法则,如果我们假设一个函数 f,它被定义为另一个函数 g 的函数,即 F 中的 f(g(x)),其导数可以定义为以下:

链式法则的公式使我们能够对输入值依赖于另一个函数的公式进行求导。这等同于搜索与先前函数相关联的函数变化率。链式法则是神经网络训练阶段使用的主要理论概念之一,因为在那些分层结构中,第一层神经元的输出将是后续层的输入,从而产生一个复合函数,大多数情况下,这个函数是多层嵌套的。
偏导数
到目前为止,我们一直在处理单变量函数,但我们将主要从现在开始处理的是多元函数,因为数据集将包含的列不止一列,每一列都将代表一个不同的变量。
在许多情况下,我们需要知道函数在仅与一个维度相关的关系中的变化情况,这将涉及到查看数据集的一列如何对函数总变化数做出贡献。
偏导数的计算包括将已知的导数规则应用于多元函数,考虑到变量不是作为常数进行求导。
看一下以下幂规则:
f(x,y) = 2x³y
当对这个函数关于 x 求导,将 y 视为常数时,我们可以将其重写为 3.2y x²,并将导数应用于变量 x,这样我们就可以得到以下导数:
d/dx (f(x,y)) = 6yx²*
使用这些技术,我们可以处理更复杂的多元函数,这些函数将是我们特征集的一部分,通常包含的变量不止两个。
摘要
在本章中,我们探讨了多个不同的概念元素,包括一些基本数学概念的概述,这些概念是机器学习概念的基础。
当我们正式解释不同建模方法的机制时,这些概念将非常有用,我们鼓励你在阅读章节之前和阅读过程中尽可能多地提高对这些概念的理解,以便更好地掌握算法的工作原理。
在下一章中,我们将快速概述机器学习项目的完整工作流程,这将帮助我们理解涉及的各种元素,从数据收集到结果评估。
第二章:学习过程
在第一章中,我们看到了机器学习领域的数学概念、历史和领域的概述。
由于本书旨在提供一种既实用又形式正确的学习方法,现在是时候探索任何机器学习过程的一般思想过程了。这些概念将贯穿整个章节,并帮助我们定义该领域最佳实践的共同框架。
本章我们将涵盖以下主题:
-
理解问题和定义
-
数据集检索、预处理和特征工程
-
模型定义、训练和评估
-
理解结果和指标
每个机器学习问题都有其特定的特点。然而,随着学科随着时间的推移而发展,出现了机器学习过程应包括哪些步骤以及最佳实践的模式。以下各节将列出这些步骤,包括适用于这些情况的代码示例。
理解问题
在解决机器学习问题时,花时间分析数据和可能的工作量是很重要的。这个初步步骤比列表中随后的所有步骤都要灵活和非正式。
从机器学习的定义来看,我们知道我们的最终目标是让计算机从一组样本数据中学习或泛化某种行为或模型。因此,我们首先应该做的是理解我们想要学习的新能力。
在企业领域,现在是时候进行更多实际讨论和头脑风暴了。在这个阶段,我们可以提出的主要问题可能如下:
-
我们真正试图解决的问题是什么?
-
当前信息管道是什么?
-
我该如何简化数据获取?
-
进来的数据是否完整,或者是否存在缺失?
-
我们可以合并哪些额外的数据源,以便有更多的变量可供使用?
-
数据发布是否定期,或者能否实时获取?
-
对于这个特定问题,最小的时间代表性单位应该是什么?
-
我试图描述的行为在本质上是否发生变化,或者其基本原理在时间上是否相对稳定?
理解问题需要从业务知识方面入手,审视所有可能影响模型的有价值的信息来源。一旦确定,接下来的任务将生成一组有组织和结构的值,这些值将成为我们模型的输入。
让我们来看一个初始问题定义的例子,以及初始分析的思想过程。
假设公司 A 是一家零售连锁店,希望能够预测特定日期某种产品的需求。这可能是一项具有挑战性的任务,因为它涉及到人类行为,其中包含一些非确定性因素。
建立这样的模型需要什么样的数据输入?当然,我们希望得到该类商品的交易清单。但如果商品是商品呢?如果商品依赖于大豆或面粉的价格,当前和过去的收获量可以丰富模型。如果产品是中档商品,当前的通货膨胀和工资变化也可能与当前的收益相关。
理解问题需要一些业务知识,并寻找所有可能影响模型的有价值的信息来源。在某种程度上,这更像是一种艺术形式,但这并没有减少它的重要性。
假设我们已经分析了问题的基本要素,并且对输入数据和期望输出的行为和特征有了更清晰的了解。接下来的任务将生成一组有组织和结构化的值,这些值将成为我们模型的输入。经过清理和调整后,这组数据将被称为我们的数据集。
数据集定义和检索
一旦我们确定了数据源,下一个任务就是收集所有元组或记录作为一个同质集合。格式可以是表格排列,一系列实值(如音频或天气变量),以及 N 维矩阵(一组图像或云点)等。
ETL 过程
大数据处理领域的前几个阶段在几十年的时间里以数据挖掘的名义发展,然后采用了流行的名称大数据。
这些学科中最好的成果之一是提取、转换、加载(ETL)过程的规范。
这个过程从来自商业系统的多个数据源混合开始,然后过渡到一个将数据转换成可读状态的系统,最后通过生成具有非常结构化和文档化的数据类型的数据库结束。
为了应用这个概念,我们将这个过程的各种元素与结构化数据集的最终结果混合,其最终形式包括一个额外的标签列(在监督学习问题的情况下)。
这个过程在以下图中表示:

从原始数据到有用数据集的 ETL 过程描述
图表说明了数据管道的第一阶段,从整个组织的所有数据开始,无论是商业交易、物联网设备原始值还是其他有价值的数据源的信息元素,这些元素通常具有非常不同类型和组成。ETL 过程负责使用不同的软件过滤器从它们中收集原始信息,应用必要的转换以以有用的方式安排数据,最后以表格格式呈现数据(我们可以将其视为具有最后一个特征或结果列的单个数据库表,或者是一个包含合并数据的巨大 CSV 文件)。最终结果可以方便地被后续过程使用,而无需实际考虑数据格式的许多怪癖,因为它们已经被标准化为非常清晰的表格结构。
使用 SciPy 和 pandas 加载数据集并进行探索性分析
为了获得一些数据集格式类型的实际概述,我们将使用之前介绍的 Python 库(SciPy 和 pandas)来演示这个例子,因为它们几乎被普遍使用。
让我们从导入并执行几个数据集输入格式的简单统计分析开始。
样本数据文件将位于每个章节代码目录中的 data 目录内。
使用 IPython 进行交互式工作
在本节中,我们将介绍Python 交互式控制台,或IPython,这是一个命令行外壳,允许我们以交互式的方式探索概念和方法。
要运行 IPython,你可以在命令行中调用它:

在这里,我们看到 IPython 正在执行,然后是初始的快速帮助。最有趣的部分是最后一行——它将允许你导入库并执行命令,并显示结果对象。IPython 的一个额外且方便的特性是,你可以即时重新定义变量以查看不同输入下的结果差异。
在当前示例中,我们使用的是撰写时的标准 Python 版本,这是当时最支持的 Linux 发行版(Ubuntu 16.04)。这些示例对于 Python 3 应该是等效的。
首先,让我们导入 pandas 并加载一个样本.csv文件(一种非常常见的格式,每行一个记录)。它包含一个非常著名的用于分类问题的数据集,包含 150 个鸢尾花实例的属性维度,以及一个表示类别的数值列(1、2 或 3):
In [1]: import pandas as pd #Import the pandas library with pd alias
在这一行中,我们以通常的方式导入 pandas,使其方法可以通过import语句使用。as修饰符允许我们使用简短的名字来引用库中的所有对象和方法:
In [2]: df = pd.read_csv ("data/iris.csv") #import iris data as dataframe
在这一行中,我们使用read_csv方法,允许 pandas 猜测.csv文件的可能的项分隔符,并将其存储在dataframe对象中。
让我们对数据集进行一些简单的探索:
In [3]: df.columns
Out[3]:
Index([u'Sepal.Length', u'Sepal.Width', u'Petal.Length', u'Petal.Width',
u'Species'],
dtype='object')
In [4]: df.head(3)
Out[4]:
5.1 3.5 1.4 0.2 setosa
0 4.9 3.0 1.4 0.2 setosa
1 4.7 3.2 1.3 0.2 setosa
2 4.6 3.1 1.5 0.2 setosa
现在,我们能够看到数据集的列名并探索其前n个实例。查看前几个记录,您可以看到setosa鸢尾花类的不同度量。
现在,让我们访问特定列的子集并显示前三个元素:
In [19]: df[u'Sepal.Length'].head(3)
Out[19]:
0 5.1
1 4.9
2 4.7
Name: Sepal.Length, dtype: float64
Pandas 包括许多用于导入表格数据格式的相关方法,例如 HDF5(read_hdf)、JSON(read_json)和 Excel(read_excel)。有关完整格式的列表,请访问pandas.pydata.org/pandas-docs/stable/io.html.
除了这些简单的探索方法之外,我们现在将使用 pandas 获取我们已看到的所有描述性统计概念,以便表征Sepal.Length列的分布:
#Describe the sepal length column
print "Mean: " + str (df[u'Sepal.Length'].mean())
print "Standard deviation: " + str(df[u'Sepal.Length'].std())
print "Kurtosis: " + str(df[u'Sepal.Length'].kurtosis())
print "Skewness: " + str(df[u'Sepal.Length'].skew())
这里是这个分布的主要指标:
Mean: 5.84333333333
Standard deviation: 0.828066127978
Kurtosis: -0.552064041316
Skewness: 0.314910956637
现在,我们将通过查看该分布的直方图来图形化评估这些指标的准确性,这次使用内置的plot.hist方法:
#Plot the data histogram to illustrate the measures
import matplotlib.pyplot as plt
%matplotlib inline
df[u'Sepal.Length'].plot.hist()

鸢尾花萼长度的直方图
如指标所示,分布是右偏斜的,因为偏度是正的,并且它是明显分布类型(具有比 1 大得多的分布范围),正如峰度指标所指示的。
处理 2D 数据
在这里,我们停止表格数据的讨论,转向 2D 数据结构。由于图像是流行机器学习问题中最常用的数据类型,我们将向您展示 SciPy 堆栈中包含的一些有用方法。
以下代码已优化,可在带有内联图形的 Jupyter 笔记本上运行。您可以在源文件Dataset_IO.pynb中找到源代码:
import scipy.misc
from matplotlib import pyplot as plt
%matplotlib inline
testimg = scipy.misc.imread("data/blue_jay.jpg")
plt.imshow( testimg)
导入单个图像基本上包括导入相应的模块,使用imread方法将指定的图像读入矩阵,并使用 matplotlib 显示。以%开始的行对应于参数修改,表示以下matplotlib图形应在笔记本中内联显示,以下为结果(轴对应于像素数):

初始 RGB 图像已加载
测试变量将包含一个高度宽度通道数数组,包含每个图像像素的所有红色、绿色和蓝色值。让我们获取这些信息:
testimg.shape
解释器将显示以下内容:
(1416, 1920, 3)
我们还可以尝试将通道分离并分别用红色、绿色和蓝色尺度表示,以了解图像中的颜色模式:
plt.subplot(131)
plt.imshow( testimg[:,:,0], cmap="Reds")
plt.title("Red channel")
plt.subplot(132)
plt.imshow( testimg[:,:,1], cmap="Greens")
plt.title("Green channel")
plt.subplot(133)
plt.imshow( testimg[:,:,2], cmap="Blues")
plt.title("Blue channel")
在前面的例子中,我们使用三位代码创建三个子图,以指示结构和位置。第一个表示行号,第二个表示列号,最后一个表示在该结构上的绘图位置。cmap参数表示分配给每个图形的颜色映射。
输出将如下所示:

样本图像分离通道的描述。
注意,红色和绿色通道具有相似的图案,而蓝色调在这只鸟的图像中占主导地位。这种通道分离可能是检测这种鸟类在其栖息地中的极其基础的初步方法。
本节是对加载数据集的不同方法的简化介绍。在接下来的章节中,我们将看到获取数据集的不同高级方法,包括加载和训练不同批次的样本集。
特征工程。
特征工程在某种程度上是机器学习过程中最被低估的部分之一,尽管许多社区知名人士认为它是学习过程的基础。
这个过程的目的是什么?简而言之,它从数据库、传感器、档案等来源提取原始数据,并以一种使模型易于泛化的方式对其进行转换。这个学科从许多来源获取标准,包括常识。这确实更像是一门艺术,而不是一门僵化的科学。它是一个手动过程,即使其中的一些部分可以通过特征提取领域的某些技术实现自动化。
作为这个过程的一部分,我们还有许多强大的数学工具和降维技术,如主成分分析(PCA)和自编码器,这些工具允许数据科学家跳过那些不能以有用方式丰富数据表示的特征。
缺失数据的插补。
当处理不太完美或不完整的数据集时,缺失的记录本身可能不会为模型增加价值,但行中的其他元素可能对模型有用。这在模型有很高比例的不完整值时尤其如此,因此不能丢弃任何行。
在这个过程中,主要问题是“你如何解释缺失值?”有很多方法,通常取决于问题本身。
一种非常简单的方法可能是将值设为零,假设数据分布的平均值为 0。一个改进的步骤是将缺失数据与周围内容相关联,分配整个列的平均值,或者相同列的n个元素的平均值。另一个选择是使用列的中位数或最频繁的值。
此外,还有一些更高级的技术,如鲁棒方法甚至 k-最近邻,我们在这本书中不会涉及。
单热编码。
数值或分类信息可以很容易地用整数表示,每个选项或离散结果一个。但在某些情况下,bins表示当前选项更受欢迎。这种数据表示形式称为单热编码。这种编码简单地将某个输入转换为一个只包含零的二元数组,除了由变量的值指示的值,该值将为 1。
在简单的整数情况下,这将是列表[1, 3, 2, 4]在单热编码中的表示:
[[0 1 0 0 0]
[0 0 0 1 0]
[0 0 1 0 0]
[0 0 0 0 1]]
让我们实现一个整数数组单热编码器的简单示例,以便更好地理解这个概念:
import numpy as np
def get_one_hot(input_vector):
result=[]
for i in input_vector:
newval=np.zeros(max(input_vector))
newval.itemset(i-1,1)
result.append(newval)
return result
在这个例子中,我们首先定义get_one_hot函数,它接受一个数组作为输入并返回一个数组。
我们所做的是逐个取数组的元素,对于数组中的每个元素,我们生成一个长度等于数组最大值的零数组,以便为所有可能的值留出空间。然后我们在由当前值指示的索引位置插入1(我们减去1是因为我们从基于 1 的索引转换为基于 0 的索引)。
让我们尝试我们刚刚编写的函数:
get_one_hot([1,5,2,4,3])
#Out:
[array([ 1., 0., 0., 0., 0.]),
array([ 0., 0., 0., 0., 1.]),
array([ 0., 1., 0., 0., 0.]),
array([ 0., 0., 0., 1., 0.]),
array([ 0., 0., 1., 0., 0.])]
数据集预处理
当我们第一次涉足数据科学时,一个常见的错误是期望所有数据从一开始就非常精致,具有良好的特征。唉,在许多情况下,这并不是事实,原因有很多,比如空数据、传感器错误导致异常值和 NAN、故障寄存器、仪器引入的偏差以及所有导致模型拟合不良并必须根除的各种缺陷。
这个阶段两个关键的过程是数据归一化和特征缩放。这个过程包括应用简单的变换称为仿射,它将当前不平衡的数据映射到一个更易于管理的形状,保持其完整性但提供更好的随机属性,并改善未来应用的模型。标准化技术的共同目标是使数据分布更接近正态分布,以下技术:
归一化和特征缩放
数据集预处理中的一个非常重要的步骤是归一化和特征缩放。数据归一化允许我们的优化技术,特别是迭代技术,更好地收敛,并使数据更易于管理。
归一化或标准化
这种技术旨在使数据集具有正态分布的性质,即均值为 0,标准差为 1。
获得这些性质的方法是计算所谓的Z分数,基于数据集样本,以下公式:

让我们借助 scikit-learn 可视化并练习这个新概念,读取MPG数据集的一个文件,该数据集包含每加仑英里数(miles per gallon)的城市循环燃油消耗,基于以下特征:mpg、cylinders、displacement、horsepower、weight、acceleration、model year、origin和car name。
from sklearn import preprocessing
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
df=pd.read_csv("data/mpg.csv")
plt.figure(figsize=(10,8))
print df.columns
partialcolumns = df[['acceleration', 'mpg']]
std_scale = preprocessing.StandardScaler().fit(partialcolumns)
df_std = std_scale.transform(partialcolumns)
plt.scatter(partialcolumns['acceleration'], partialcolumns['mpg'], color="grey", marker='^')
plt.scatter(df_std[:,0], df_std[:,1])
下面的图片使我们能够比较非归一化和归一化的数据表示:

原始数据集的描述和其归一化对应物的描述。
在评估时对结果数据进行去规范化非常重要,这样你才不会失去数据的代表性,尤其是如果模型应用于回归时,未缩放的数据将不再有用。
模型定义
如果我们只想用一个词来总结机器学习过程,那一定是模型。这是因为我们用机器学习构建的是抽象或模型,它们代表并简化现实,使我们能够根据我们在其上训练的模型解决现实生活中的问题。
随着几乎每天都有新模型出现,选择使用哪个模型的任务变得越来越困难,但你可以通过按你想要执行的任务类型和输入数据的类型对方法进行分组,从而将问题简化为更小的一组选项。
提出正确的问题
有过度概括的风险,让我们尝试总结一个用于模型的示例决策问题:
-
我们是否试图通过仅根据其特征对信息进行分组来表征数据,而不需要任何或仅有少量先前的提示?这是聚类技术的领域。
-
第一个也是最基本的问题:我们是在尝试预测变量的即时结果,还是将数据标记或分类到不同的组中?如果是前者,我们正在解决回归问题。如果是后者,这属于分类问题的领域。
-
在解决前面的问题并选择第 2 点中的任何选项后,我们应该问自己:数据是否是序列的,或者我们是否应该考虑序列?循环神经网络应该是首选之一。
-
继续探讨非聚类技术:我们要发现的数据或模式是否在空间上有位置?卷积神经网络是这类问题的一个常见起点。
-
在最常见的情况下(没有特定排列的数据),如果函数可以用一个单变量或多变量函数表示,我们可以应用线性、多项式或逻辑回归,如果我们想提升模型,多层神经网络将提供更复杂非线性解决方案的支持。
-
我们正在处理多少维度和变量?我们只是想提取最有用的特征(以及数据维度),排除不那么有趣的那些吗?这是降维技术的领域。
-
我们是否想要学习一组具有有限步骤的策略,以实现目标?这属于强化学习的领域。如果这些经典方法都不适合我们的研究,那么会出现大量利基技术,应该进行额外的分析。
在接下来的章节中,你将找到关于如何根据更严格的准则做出决策的更多信息,并最终将模型应用于你的数据。此外,如果你发现你的答案与这一节中解释的简单准则关系不大,你可以查看第八章,最近模型和发展,以了解更高级的模型。
损失函数定义
这个机器学习过程步骤也非常重要,因为它提供了衡量你模型质量的一个独特指标,如果选择不当,可能会破坏模型的准确性或其收敛速度的效率。
简单来说,损失函数是一个衡量模型估计值与真实期望值之间距离的函数。
我们必须考虑的一个重要事实是,几乎所有模型的目的是最小化误差函数,为此,我们需要它可导,并且误差函数的导数应该尽可能简单。
另一个事实是,当模型变得越来越复杂时,误差的导数也会变得更加复杂,因此我们需要使用迭代方法来近似导数的解,其中之一就是众所周知的梯度下降法。
模型拟合和评估
在这个机器学习过程的这部分,我们已经准备好了模型和数据,然后我们继续训练和验证我们的模型。
数据集划分
在训练模型时,我们通常将提供的数据分成三组:训练集,它实际上将被用来调整模型的参数;验证集,它将被用来比较应用于该数据的替代模型(如果我们只有一个模型和架构在心中,则可以忽略);以及测试集,它将被用来衡量所选模型的准确性。这些划分的比例通常是 70/20/10。
常见的训练术语 – 迭代、批次和 epoch
在训练模型时,有一些常见的术语表示迭代优化的不同部分:
-
迭代定义了一次计算误差梯度和调整模型参数的实例。当数据被分成样本组时,每个这样的组被称为批次。
-
批次可以包括整个数据集(传统批次),或者只包括一个非常小的子集,直到整个数据集被前馈,这被称为小批量。每个批次中的样本数量称为批次大小。
-
整个数据集的每次遍历称为一个epoch。
训练类型 – 在线处理和批量处理
训练过程提供了许多遍历数据集和根据输入数据和误差最小化结果调整模型参数的方法。
当然,在训练阶段,数据集将被多次以多种方式评估。
参数初始化
为了确保良好的拟合开始,模型权重必须初始化为最有效的值。通常具有tanh激活函数的神经网络对范围[-1,1]或[0,1]非常敏感;因此,对数据进行归一化以及参数也应处于该范围内非常重要。
模型参数应该有有用的初始值,以便模型收敛。训练开始时的重要决策之一是模型参数的初始化值(通常称为权重)。一个经典的初始规则不是将变量初始化为 0,因为这会阻止模型优化,因为它们没有合适的函数斜率乘数来调整。一个常见的合理标准是使用正态随机分布来为所有值。
使用 NumPy,你通常会使用以下代码初始化一个系数向量:
mu, sigma = 0, 1
dist = np.random.normal(mu, sigma, 1000)
>>> dist = np.random.normal(mu, sigma, 10)
>>> print dist
[ 0.32416595 1.48067723 0.23039378 -0.59140674 1.65827372 -0.8241832
0.86016434 -0.05996878 2.2855467 -0.19759244]
在这个阶段,一个特定的问题来源是将模型的所有参数都设置为 0。因为许多优化技术通常通过乘以一个确定的系数来近似最小值,乘以 0 将阻止模型发生任何变化,除了偏差项。
模型实现和结果解释
如果模型不能在训练和测试集之外使用,那么它就不实用。这就是模型部署到生产中的时候。
在这个阶段,我们通常会加载所有模型的操作和训练好的权重,等待新的未知数据,当它到达时,我们通过模型的全部链式函数将其输入,通过 Web 服务通知输出层或操作的输出结果,打印到标准输出,等等。
然后,我们将有一个最终任务——在现实世界中解释模型的输出结果,以不断检查它是否在当前条件下工作。在生成模型的情况下,预测的适用性更容易理解,因为目标通常是表示一个先前已知的实体。
回归指标
对于回归指标,计算了多个指标,以给出回归模型拟合度的简明概念。以下是主要指标列表。
均方绝对误差
mean_absolute_error函数计算平均绝对误差,这是一个与绝对误差损失期望值相对应的风险指标,或称为l1-norm损失。
如果ŷ[i]是第i个样本的预测值,而y[i]是对应的真实值,那么在n个样本上估计的平均绝对误差(MAE)定义如下:

中位数绝对误差
中位数绝对误差特别有趣,因为它对异常值具有鲁棒性。损失是通过计算目标与预测之间的所有绝对差异的中位数来计算的。
如果 ŷ 是第 i 个样本的预测值,而 y[i] 是相应的真实值,那么在 n 个样本上估计的绝对误差中位数定义如下:

均方误差
均方误差(MSE)是一个风险指标,等于平方(二次)误差损失的期望值。
如果 ŷ[i] 是第 i 个样本的预测值,而 y[i] 是相应的真实值,那么在 n 个样本上估计的 MSE 定义如下:

分类指标
分类任务意味着在估计误差时遵循不同的规则。我们所拥有的优势是输出的数量是离散的,因此可以以二进制方式精确地确定预测是否失败。这使我们转向主要指标。
准确度
准确度计算模型正确预测的分数或计数。
在多标签分类中,该函数返回子集的准确率。
如果一个样本的所有预测标签严格匹配真实标签集,那么子集的准确率是 1.0;否则,它是 0.0。
如果 ŷ[i] 是第 i 个样本的预测值,而 y[i] 是相应的真实值,那么在 n 个样本中正确预测的比例定义如下:

精确度得分、召回率和 F 度量
精确度得分如下:

在这里,t[p] 是真正例的数量,而 f[p] 是假正例的数量。精确度是分类器不将负样本标记为正样本的能力。最佳值是 1,最差值是 0。
召回率如下:

在这里,t[p] 是真正例的数量,而 f[n] 是假阴性数量。召回率可以描述为分类器找到所有正样本的能力。其值从 1(最佳)到 0。
F 度量(F[β] 和 F[1] 度量)可以解释为精确度和召回率的特殊类型的平均值(加权调和平均值)。F[β] 度量的最佳值是 1,其最差得分是 0。当 β = 1 时,F[β] 和 F[1] 是等价的,召回率和精确度同等重要:

混淆矩阵
每个分类任务都旨在为新未知数据预测一个标签或标记。展示分类准确率的一种非常有效的方式是通过混淆矩阵,其中我们展示了 [分类样本,真实值] 对,以及预测表现的具体视图。
预期输出应该是矩阵的主对角线,得分为 1.0;也就是说,所有预期值都应该与实际值相匹配。
在以下代码示例中,我们将进行预测值和真实值的合成样本,并生成最终数据的混淆矩阵:
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import numpy as np
y_true = [8,5,6,8,5,3,1,6,4,2,5,3,1,4]
y_pred = [8,5,6,8,5,2,3,4,4,5,5,7,2,6]
y = confusion_matrix(y_true, y_pred)
print y
plt.imshow(confusion_matrix(y_true, y_pred), interpolation='nearest', cmap='plasma')
plt.xticks(np.arange(0,8), np.arange(1,9))
plt.yticks(np.arange(0,8), np.arange(1,9))
plt.show()
结果将是以下内容:
[[0 1 1 0 0 0 0 0]
[0 0 0 0 1 0 0 0]
[0 1 0 0 0 0 1 0]
[0 0 0 1 0 1 0 0]
[0 0 0 0 3 0 0 0]
[0 0 0 1 0 1 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 2]]
这些值的最终混淆矩阵图形表示如下:

混淆矩阵
在图像中,我们看到在(5,5)对角线上的高准确度值有三个正确预测,以及(8,8)值有两个。正如我们所看到的,通过分析图表可以直观地提取准确度的分布。
聚类质量测量
无监督学习技术,理解为没有真实标签的数据标注,使得为模型实现显著的度量指标变得有些困难。尽管如此,为此类技术实现了一系列的度量。在本节中,我们列出了最著名的几种。
轮廓系数
轮廓系数是一个不需要知道数据集标签的度量。它给出了簇之间分离的概念。
它由两个不同的元素组成:
-
样本与其同一类中所有其他点的平均距离(a)
-
样本与其最近簇中所有其他点的平均距离(b)
该系数s的公式定义如下:

轮廓系数仅在类别的数量至少为两个时才定义,整个样本集的系数是所有样本系数的平均值。
纯度、完整性和 V 度量
纯度、完整性和 V 度量是聚类操作质量的关键相关指标。在以下公式中,我们将使用K表示簇的数量,C表示类别的数量,N表示样本总数,a[ck]表示在簇k中类c的元素数量。
纯度是单个簇中属于单个类别的样本比率的度量。一个簇中包含的不同类别越少,越好。下限应为 0.0,上限应为 1.0(越高越好),其公式如下所示:

完整性衡量的是分配到同一簇的给定类成员的比率:

V 度量是纯度和完整性的调和平均值,其公式如下:

摘要
在本章中,我们回顾了机器学习过程中涉及的所有主要步骤。我们将间接地在整本书中使用它们,并希望它们能帮助您构建未来的工作。
在下一章中,我们将回顾我们将使用来解决所有机器学习问题并熟练掌握它们的编程语言和框架。
参考文献
-
Lichman, M. (2013). UCI 机器学习仓库 (
archive.ics.uci.edu/ml). 加利福尼亚州欧文,加州大学信息与计算机科学学院。 -
Quinlan, R. (1993). 结合实例学习和基于模型的学习。在第十届国际机器学习会议论文集,236-243 页,麻省大学阿默斯特分校。摩根考夫曼出版社。
-
Townsend, James T. 字母混淆矩阵的理论分析。注意,感知与心理学 9.1 (1971): 40-50。
-
Peter J. Rousseeuw (1987). 轮廓:聚类分析的图形辅助解释和验证。计算与应用数学 20: 53-65。
-
Kent, Allen 等人,机器文献检索 VIII. 设计信息检索系统的操作标准。信息科学和技术协会杂志 6.2 (1955): 93-101。
-
Rosenberg, Andrew 和 Julia Hirschberg,V-Measure:基于条件熵的外部聚类评估度量。EMNLP-CoNLL。第 7 卷。2007 年。
第三章:聚类
恭喜!你已经完成了这本书的引言部分,其中你探索了许多主题,如果你能够跟上,你就准备好开始了解许多机器学习模型的内部运作之旅了。
在本章中,我们将探讨一些有效且简单的方法来自动发现有趣的数据聚集体,并开始研究数据自然分组的原因。
本章将涵盖以下主题:
-
K-means 算法的一个逐行实现的示例,包括对数据结构和例程的解释
-
对k-最近邻(K-NN)算法的详细解释,使用代码示例来解释整个过程
-
确定代表一组样本的最佳分组数量的额外方法
分组作为一种人类活动
人类通常倾向于将日常元素聚集到具有相似特征的组中。人类思维的这个特性也可以通过算法来复制。相反,可以最初应用于任何未标记数据集的最简单操作之一是将元素分组到共同特征周围。
正如我们所描述的,在这个学科发展的阶段,聚类被教授为一个入门主题,应用于元素集合的最简单类别。
但作为一个作者,我建议研究这个领域,因为社区暗示当前模型的表现将都会达到一个平台期,在追求人工智能任务的全局泛化之前。那么,哪些方法将是跨越人工智能前沿下一阶段的主要候选者?形式上非常复杂的方法,正如这里所解释的方法。
但现在我们不要偏离主题,让我们从最简单的分组标准开始,即到共同中心的距离,这被称为K-means。
自动化聚类过程
用于聚类的信息分组遵循所有技术的一个共同模式。基本上,我们有一个初始化阶段,随后是迭代插入新元素,之后更新新的分组关系。这个过程一直持续到满足停止标准,此时分组特征化完成。以下流程图说明了这个过程:

聚类算法的一般方案
在我们对整体过程有了清晰的认识之后,让我们开始处理一些应用了这种方案的案例,从K-means开始。
寻找共同中心 - K-means
我们开始了!经过一些必要的准备复习后,我们最终将开始从数据中学习;在这种情况下,我们希望对现实生活中观察到的数据进行标记。
在这个例子中,我们有以下元素:
-
一组 N 维数值类型的元素
-
预先确定的小组数量(这有点棘手,因为我们不得不做出一个有根据的猜测)
-
每个组的一组常见代表点(称为质心)
这种方法的主要目标是把数据集分成任意数量的簇,每个簇都可以用提到的质心表示。
“质心”这个词来自数学领域,已经被翻译到微积分和物理学中。在这里,我们找到了三角形质心分析计算的经典表示:

三角形质心寻找方案的图形描述
在 R^n 空间中,有限个点集的质心,x[1 ,]x[2], ..., x[k],如下所示:

质心的解析定义
因此,现在我们已经定义了这个中心度量,让我们提出问题,“这与数据元素的分组有什么关系?”
要回答这个问题,我们首先必须理解到质心的距离的概念。距离有许多定义,可以是线性的、二次的,以及其他形式。
因此,让我们回顾一下主要的距离类型,然后我们将提到通常使用哪一种:
在这篇综述中,当我们定义度量类型时,我们将使用二维变量,作为一种简化的手段。
让我们看看以下几种距离类型:
- 欧几里得距离:这种距离度量计算两点之间的直线距离,或者具有以下公式:

- 切比雪夫距离:这种距离等于沿任何轴的最大距离。它也被称为棋盘距离,因为它给出了国王从初始点到最终点所需的最小移动次数。其定义为以下公式:

- 曼哈顿距离:这种距离相当于在具有单位方格的城市中从一个点到另一个点的距离。这种 L1 型距离总结了水平前进的单位数和垂直的单位数。其公式如下:

以下图表进一步解释了不同类型距离的公式:

一些最著名的距离类型的图形表示
K-means 选择的距离度量是欧几里得距离,它易于计算,并且很好地扩展到多个维度。
现在我们已经拥有了所有元素,是时候定义我们将用来定义我们将分配给任何给定样本的标签的标准了。让我们用以下陈述总结学习规则:
“样本将被分配到由最近的质心表示的组。”
这种方法的目的是最小化从簇成员到包含样本的所有簇的实际质心的平方距离之和。这也被称为惯性最小化。
在以下图中,我们可以看到将典型的 K-means 算法应用于类似 blob 的样本群体的结果,预设的簇数为 3:

使用 3 个质心种子进行 K-means 聚类的典型聚类过程结果
K-means 是一种简单而有效的算法,可以用来快速了解数据集是如何组织的。其主要区别在于属于同一类的对象将共享一个共同的距离中心,这个中心将随着每个新样本的添加而逐步升级。
K-means 的优缺点
这种方法的优点如下:
-
它具有良好的可扩展性(大多数计算可以并行运行)
-
它已经被应用于非常广泛的领域
但简单也有其代价(没有银弹规则适用):
-
它需要先验知识(簇的可能数量应该事先知道)
-
异常值可能会扭曲质心的值,因为它们与任何其他样本具有相同的权重
-
由于我们假设该图是凸的和各向同性的,因此它与非 blob 相似的簇不太适用。
K-means 算法分解
K-means 算法的机制可以用以下流程图来概括:

K-means 过程流程图
让我们更详细地描述这个过程:
我们从未分类的样本开始,并取 K 个元素作为起始质心。为了简洁起见,也有可能简化这个算法,取元素列表中的第一个元素。
我们然后计算样本与第一个选择的样本之间的距离,从而得到第一次计算出的质心(或其他代表性值)。您可以在插图中的移动质心中看到向更直观(并且数学上正确)的中心位置的偏移。
在质心改变后,它们的位移将导致单个距离的变化,因此簇成员资格可能会改变。
这是我们重新计算质心并重复第一步的时候,以防停止条件没有满足。
停止条件可以是各种类型:
-
在 n 次迭代之后。可能的情况是,我们选择了一个非常大的数字,这将导致不必要的计算轮次,或者它可能收敛得很慢,如果质心没有非常稳定的均值,我们将得到非常不可信的结果。
-
对于迭代的收敛性,一个可能的更好的标准是查看质心的变化,无论是总的位移还是总的簇元素交换。通常使用后者,因此一旦没有更多的元素从当前簇转移到另一个簇,我们将停止这个过程。
N 次迭代条件也可以作为最后的手段,因为它可能导致非常长的过程,在大量迭代中观察不到任何可观察的变化。
让我们尝试通过几个步骤直观地总结 K-NN 聚类的过程,观察聚类随时间的变化:

聚类重构循环的图形示例
在子图 1 中,我们开始在随机位置种下可能的质心,并将最接近的数据元素分配给它们;然后在子图 2 中,我们将质心重新配置为新聚类的中心,这反过来又重新配置了聚类(子图 3),直到我们达到稳定状态。元素聚合也可以逐个进行,这将触发更柔和的重配置。这将是本章实际部分的实现策略。
K-means 实现
在本节中,我们将通过实际样本从基本概念开始回顾 K-means 的概念。
首先,我们将导入所需的库。为了提高对算法的理解,我们将使用numpy库。然后我们将使用著名的matplotlib库来图形化表示算法:
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
这将包含多个二维元素,然后生成候选中心,这些中心将包含四个二维元素。
为了生成数据集,通常使用随机数生成器,但在此情况下,我们希望将样本设置为预定的数字,以便于操作,并允许您手动重复这些过程:
samples=np.array([[1,2],[12,2],[0,1],[10,0],[9,1],[8,2],[0,10],[1,8],[2,9],[9,9],[10,8],[8,9] ], dtype=np.float)
centers=np.array([[3,2], [2,6], [9,3], [7,6]], dtype=np.float)
N=len(samples)
让我们表示样本的中心。首先,我们将初始化一个新的matplotlib图形,以及相应的坐标轴。fig对象将允许我们更改所有图形的参数。
plt和ax变量名称是引用图形的一般方式和引用图形的一个轴的标准方式。
因此,让我们尝试了解样本的外观。这将通过matplotlib库的scatter绘图类型来完成。它接受参数为x坐标、y坐标、大小(以点平方为单位)、标记类型和颜色。
有许多标记可供选择,例如点(.)、圆圈(o)、方块(s)。要查看完整列表,请访问matplotlib.org/api/markers_api.html.
让我们看一下以下代码片段:
fig, ax = plt.subplots()
ax.scatter(samples.transpose()[0], samples.transpose()[1], marker =
'o', s = 100 )
ax.scatter(centers.transpose()[0], centers.transpose()[1], marker =
's', s = 100, color='black')
plt.plot()
现在让我们看一下以下图表:

初始聚类状态,中心以黑色方块表示
让我们定义一个函数,给定一个新样本,将返回一个包含到所有当前质心的距离的列表,以便将此新样本分配给其中一个,然后重新计算质心:
def distance (sample, centroids):
distances=np.zeros(len(centroids))
for i in range(0,len(centroids)):
dist=np.sqrt(sum(pow(np.subtract(sample,centroids[i]),2)))
distances[i]=dist
return distances
然后,我们需要一个函数,该函数将逐步构建我们应用程序的图形。
它期望最多有 12 个子图,plotnumber参数将确定在 6 x 2 矩阵中的位置(620将是左上角的子图,621 是右侧的下一个,依此类推)。
之后,对于每个图像,我们将对聚类样本进行散点图绘制,然后绘制当前质心的位置:
def showcurrentstatus (samples, centers, clusters, plotnumber):
plt.subplot(620+plotnumber)
plt.scatter(samples.transpose()[0], samples.transpose()[1], marker =
'o', s = 150 , c=clusters)
plt.scatter(centers.transpose()[0], centers.transpose()[1], marker =
's', s = 100, color='black')
plt.plot()
以下名为kmeans的函数将使用之前的距离函数来存储样本分配到的质心(它将是一个从1到K的数字)。
主循环将从样本0到N,对于每一个样本,它将寻找最近的质心,将质心编号分配给聚类数组的索引n,并将样本坐标加到当前分配的质心上。
然后,为了获取样本,我们使用bincount方法计算每个质心的样本数量,并通过构建divisor数组,我们将一个类别的元素总和除以之前的divisor数组,从而得到新的质心:
def kmeans(centroids, samples, K, plotresults):
plt.figure(figsize=(20,20))
distances=np.zeros((N,K))
new_centroids=np.zeros((K, 2))
final_centroids=np.zeros((K, 2))
clusters=np.zeros(len(samples), np.int)
for i in range(0,len(samples)):
distances[i] = distance(samples[i], centroids)
clusters[i] = np.argmin(distances[i])
new_centroids[clusters[i]] += samples[i]
divisor = np.bincount(clusters).astype(np.float)
divisor.resize([K])
for j in range(0,K):
final_centroids[j] = np.nan_to_num(np.divide(new_centroids[j] ,
divisor[j]))
if (i>3 and plotresults==True):
showcurrentstatus(samples[:i], final_centroids,
clusters[:i], i-3)
return final_centroids
现在是时候启动 K-means 过程了,使用我们最初设置的初始样本和中心。当前算法将展示聚类如何从少数元素开始,发展到最终状态:
finalcenters=kmeans (centers, samples, 4, True)
让我们看一下以下屏幕截图:

聚类过程的描述,其中质心以黑色方块表示
最近邻
K-NN 是另一种经典的聚类方法。它构建样本组,假设每个新样本将与它的邻居具有相同的类别,而不寻找全局代表性的中心样本。相反,它观察环境,寻找每个新样本环境中最频繁的类别。
K-NN 的原理
K-NN 可以在许多配置中实现,但在这章中,我们将使用半监督方法,从一定数量的已分配样本开始,然后使用主要标准猜测聚类成员资格。
在以下图中,我们对算法进行了分解。它可以总结为以下步骤:

K-NN 聚类过程的流程图
让我们以简化的形式回顾以下所有涉及步骤:
-
我们将先前已知的样本放置在数据结构中。
-
然后,我们读取下一个待分类的样本,并计算新样本与训练集中每个样本的欧几里得距离。
-
我们通过选择最近样本的类别来确定新元素的类别,通过欧几里得距离。K-NN 方法需要 K 个最近样本的投票。
-
我们重复此过程,直到没有更多剩余样本。
这张图片将给我们一个关于新样本如何被添加的思路。在这种情况下,我们使用K为1,以简化:

K-NN 循环的示例应用
K-NN 可以使用我们所学到的多种配置之一来实现,但在这章中,我们将使用半监督方法;我们将从一个已经分配了一定数量的样本开始,然后根据训练集的特征来猜测簇的成员资格。
K-NN 的优缺点
这种方法的优点如下:
-
简单性:无需调整参数
-
无需正式训练:我们只需要更多的训练示例来改进模型
不利之处如下:
计算成本高 - 在原始方法中,除了实现缓存时,必须计算所有点与新样本之间的距离。
K-NN 示例实现
对于 K-NN 方法的简单实现,我们将使用 NumPy 和 Matplotlib 库。此外,为了更好地理解,我们将使用 scikit-learn 的 make_blobs 方法生成合成数据集,这将生成定义良好且分离的信息组,以便我们有可靠的实现参考。
导入所需的库:
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from sklearn.datasets.samples_generator import make_blobs
%matplotlib inline
因此,现在是生成这个示例数据样本的时候了。make_blobs 函数的参数包括样本数量、特征或维度的数量、中心或组数、样本是否需要打乱,以及簇的标准差,以控制组样本的分散程度:
data, features = make_blobs(n_samples=100, n_features = 2, centers=4,
shuffle=True, cluster_std=0.8)
fig, ax = plt.subplots()
ax.scatter(data.transpose()[0], data.transpose()[1], c=features,marker =
'o', s = 100 )
pl
这里是生成的样本聚类的表示:

首先,让我们定义我们的 distance 函数,它将用于找到所有新元素的邻居。我们基本上提供一个样本,并返回提供的新元素与所有对应元素之间的距离:
def distance (sample, data):
distances=np.zeros(len(data))
for i in range(0,len(data)):
dist=np.sqrt(sum(pow(np.subtract(sample,data[i]),2)))
distances[i]=dist
return distances
add_sample 函数将接收一个新的二维样本、当前数据集和一个标记相应样本组(在这种情况下为 0 到 3)的数组。在这种情况下,我们使用 argpartition 来获取新样本最近三个邻居的索引,然后使用它们从 features 数组中提取一个子集。然后,bincount 将返回该三个元素子集上任何不同类别的计数,然后通过 argmax,我们将选择该两个元素集中元素最多的组的索引(在这种情况下,类号):
def add_sample(newsample, data, features):
distances=np.zeros((len(data),len(data[0])))
#calculate the distance of the new sample and the current data
distances = distance(newsample, data)
closestneighbors = np.argpartition(distances, 3)[:3]
closestgroups=features[closestneighbors]
return np.argmax(np.bincount(closestgroups))
然后我们定义我们的主要 knn 函数,它接受要添加的新数据,并使用由 data 和 features 参数表示的原始分类数据来决定新元素的类别:
def knn (newdata, data, features):
for i in newdata:
test=add_sample (i, data, features);
features=np.append(features, [test],axis=0)
data=np.append(data, [i],axis=0)
return data,features
最后,是时候启动这个过程了。因此,我们在 -10 到 10 的范围内定义了一组新的样本,在 x 和 y 维度上,然后我们将使用它调用我们的 knn 例程:
newsamples=np.random.rand(20,2)*20-8.
> finaldata, finalfeatures=knn (newsamples, data, features)
现在是时候表示最终结果了。首先,我们将表示初始样本,它们比我们的随机值形成得更好,然后是我们的最终值,用空方框表示(c='none'),这样它们将作为这些样本的标记:
fig, ax = plt.subplots()
ax.scatter(finaldata.transpose()[0], finaldata.transpose()[1],
c=finalfeatures,marker = 'o', s = 100 )
ax.scatter(newsamples.transpose()[0], newsamples.transpose()
[1],c='none',marker =
's', s = 100 )
plt.plot()
让我们看看下面的图表:

最终聚类状态(新分类的项目用方块标记)
在前面的图表中,我们可以看到我们的简单模型如何随着过程的推进很好地对分组进行资格认证和改革。如图所示,新的分组不一定呈圆形;它们根据 incoming data 的进展方式而变化。
超越基础
现在我们已经完成了对两种主要聚类技术示例的审查,让我们探索一些更高级的指标和技术,这样我们就可以将它们放入我们的工具箱中。
拐点法
在实现 K-means 时可能出现的疑问之一可能是“我怎么知道目标簇数量是最佳或对数据集最具代表性的?”
对于这个任务,我们有拐点法。它由一个独特的统计量组成,用于衡量分组中的总组分散。它通过重复 K-means 过程,使用不断增加的初始簇数量,并计算所有组的总簇内内部距离来实现。
通常情况下,该方法将以一个非常高的值开始(除非我们以正确的质心数量开始),然后我们会观察到总簇内距离会迅速下降,直到达到一个点,它不会显著变化。恭喜,我们找到了所谓的“拐点”,在下面的图表中表现为一个拐点:

随着簇数量的增加,错误演变的图形表示,以及拐点。
关于这个指标的准确性,正如你所看到的,拐点法是一个启发式方法,而不是数学上确定的,但如果你想要快速估计正确的簇数量,它可能是有用的,尤其是在曲线在某些点突然变化时。
摘要
在本章中,我们以极其实用的方式介绍了最简单但仍非常实用的机器学习模型,以便我们在复杂性尺度上开始。
在接下来的章节中,我们将介绍几种回归技术,那时我们将去解决一种我们尚未处理过的新类型的问题,即使使用聚类方法(回归)通过新的数学工具来近似未知值也是可能的。在它里面,我们将使用数学函数来建模过去的数据,并尝试基于这些建模函数来建模新的输出。
参考文献
-
Thorndike, Robert L, Who belongs in the family?, Psychometrika 18.4 (1953): 267-276.
-
Steinhaus, H,关于物质分割成部分的问题。波兰科学院学报 1 (1956): 801–804。
-
MacQueen, James,一些用于多元观测分类和分析的方法。第五次伯克利数学统计与概率研讨会论文集。第 1 卷。第 14 号。1967 年。
-
Cover, Thomas, 和 Peter Hart,最近邻模式分类。IEEE 信息系统传输 13.1 (1967): 21-27。
第四章:线性和逻辑回归
通过使用共同特征对相似信息进行分组后,我们获得了洞察力,现在是时候稍微更数学化,开始寻找一种方法来使用一个独特的函数来描述数据,该函数将压缩大量信息,并允许我们预测未来的结果,假设数据样本保持其先前的属性。
在本章中,我们将涵盖以下主题:
-
线性回归的逐步实现
-
多项式回归
-
逻辑回归及其实现
-
Softmax 回归
回归分析
本章将从解释一般原则开始。那么,让我们提出一个基本问题:什么是回归?*
在所有考虑因素之前,回归基本上是一个统计过程。正如我们在介绍部分所看到的,回归将涉及一组具有特定概率分布的数据。总之,我们有一组需要特征化的数据。
在回归的情况下,我们特别寻找哪些元素?我们希望确定自变量和因变量之间的关系,该关系能够最佳地调整到提供的数据。当我们找到描述变量之间的这种函数时,它将被称为回归函数。
有大量函数类型可供我们帮助建模当前数据,最常见的例子是线性、多项式和对数。
这些技术旨在确定一个目标函数,在我们的情况下,它将输出函数的有限个未知最优参数,称为参数回归技术。
回归的应用
回归通常用于预测未来的变量值,并且它是数据分析项目中初始数据建模的一个非常常用的技术,但它也可以用于优化流程,找到相关但分散的数据之间的共同点。
在这里,我们将列出回归分析的一些可能的应用:
-
在社会科学中,预测各种指标的未来的值,例如失业率和人口
-
在经济学中,预测未来的通货膨胀率、利率和类似指标
-
在地球科学中,为了预测未来的现象,例如臭氧层的厚度
-
帮助正常企业仪表盘的所有元素,添加生产吞吐量、收入、支出等的概率估计
-
证明两种现象之间的依赖性和相关性
-
在反应实验中找到成分的最优组合
-
最小化风险投资组合
-
理解一家公司的销售额对广告支出变化的敏感度
-
看看股票价格如何受到利率变化的影响
定量变量与定性变量
在日常数据工作中,我们遇到的所有元素并不都是相同的,因此它们需要根据其特性进行特殊处理。我们可以通过以下标准将数据类型划分为定量数据和定性数据变量,以识别问题变量是否适当:
-
定量变量:在物理变量或测量的领域中,我们通常处理实数或定性变量,因为最重要的是我们测量的数量。在这个组中,我们有有序变量,即当我们在一个活动中处理顺序和排名时。这两种变量类型都属于定量变量类别。
-
定性变量:另一方面,我们有显示样本属于哪个类别的测量。这不能以数量的方式表达;它通常被分配一个标签、标记或分类值,代表样本所属的组。我们称这些变量为定性变量。

针对定量和定性分析差异的参考表
现在我们来探讨哪些类型的变量适合应用于回归问题。
明确的答案是定量变量,因为数据分布的建模只能通过我们用来检测这些变量之间规律对应关系的函数来完成,而不是基于类别或元素类型。回归需要一个连续的输出变量,而这只有定量指标才能满足。
对于定性变量,我们将数据分配给分类问题,因为它的定义本身就是寻找非数字标签或标记来分配给样本。这是分类的任务,我们将在下一章中看到。
线性回归
因此,现在是时候开始使用我们数据中最简单但仍然非常有用的抽象——线性回归函数了。
在线性回归中,我们试图找到一个线性方程,它最小化了数据点与模型线之间的距离。模型函数具有以下形式:
y[i] = ßx[i] + α + ε[i]
在这里,α 是模型的截距,ß 是模型线的斜率。变量 x 通常被称为自变量,而 y 被称为因变量,但它们也可以被称为回归变量和响应变量。
ε[i] 变量是一个非常有趣的元素,它是样本 i 到回归线的误差或距离。

回归线组成部分的描述,包括原始元素、估计的元素(红色)和误差(ε)
所有这些距离的集合,以称为 成本 函数的形式计算,作为求解过程的结果,将给出未知参数的值,这些值最小化了成本。让我们开始工作吧。
成本函数的确定
与所有机器学习技术一样,学习过程依赖于一个最小化损失函数,它告诉我们当我们预测一个结果时,我们处于学习的哪个阶段,我们的预测是正确还是错误。
线性回归中最常用的成本函数被称为 最小二乘法。
让我们定义这个成本函数,为了简化,我们考虑一个二维回归,其中我们有一个数字元组的列表 (x[0], y[0]), (x[1], y[1]) ... (x[n], y[n]) 以及要找到的值,即 β[0] 和 β[1]。在这种情况下,最小二乘成本函数可以定义为以下:

线性方程的最小二乘函数,使用标准变量β[0]和β[1],这些将在下一节中使用
对每个元素的求和给出一个唯一的全局数,这给出了所有值(y[i])与我们的理想回归线(β[0] + β1x[i])中相应点的总差异的全局概念。
这个操作的原理非常清晰:
-
求和给我们一个唯一的全局数
-
模型-实际点之间的差异给出了距离或 L1 误差
-
将其平方后,我们得到一个正数,这也会以非线性的方式惩罚距离,超过一个误差限制,因此我们犯的错误越多,我们越愿意增加我们的惩罚率
另一种表述方式是,这个过程最小化了平方残差的和,残差是我们从数据集中得到的价值与模型计算出的预期价值之间的差异,对于相同的输入值。
最小化误差的多种方法
最小二乘误差函数有几种得到解的方法:
-
解析方法
-
使用协方差和相关性值
-
对于机器学习方法家族来说,最熟悉的方式——梯度下降法
解析方法
解析方法采用几种线性代数技术,以便得到精确的解。
我们以非常简洁的方式介绍这项技术,因为它与我们在这本书中回顾的机器学习技术没有直接关系。我们介绍它是为了完整性。
首先,我们以矩阵形式表示误差:

线性回归方程的矩阵形式的规范形式
在这里,J 是成本函数,具有以下解析解:

线性回归矩阵形式的解析解
解析方法的优缺点
考虑到我们可以给出一个简单且确定性的表示,使用线性代数技术来计算最小误差解法是一个更简单的方法,因此在进行操作后不需要额外的猜测。
但这种方法可能存在一些问题:
-
首先,矩阵求逆和乘法是非常计算密集的操作。它们通常有大约 O(n²) 到 O(n³) 的下限,因此当样本数量增加时,问题可能变得难以处理。
-
此外,根据实现方式,这种直接方法也可能具有有限的精度,因为我们通常可以使用当前硬件浮点容量的限制。
协方差/相关方法
现在是时候介绍一种新的方法来估计回归线的系数了,在这个过程中,我们将学习额外的统计度量,如协方差和相关性,这些也将帮助我们第一次分析数据集并得出初步结论。
协方差
协方差是一个统计术语,可以规范地定义为以下内容:
一对随机变量之间系统关系的度量,其中一个变量的变化由另一个变量等效的变化所抵消。
协方差可以取从 -∞ 到 +∞ 的任何值,其中负值表示负相关关系,而正值表示正相关关系。它还确定了变量之间的线性关系。
因此,当值为零时,表示没有直接的线性关系,值倾向于形成类似团块状的分布。
协方差不受度量单位的影响,也就是说,当改变单位时,两个变量之间关系的强度不会改变。然而,协方差值会改变。它有以下公式,需要每个轴的平均值作为先决条件:

相关系数
记得我们描述变量标准化过程时吗?我们通过减去平均值并使用数据集的标准差进行缩放来使变量居中,以下公式:

数据标准化操作的解析形式
这将是我们的分析起点,我们将沿着每个轴扩展,使用相关值。
相关系值决定了两个或更多随机变量协同移动的程度。在研究两个变量时,如果观察到其中一个变量的移动与另一个变量等效的移动一致,则称这些变量是相关的,确切的相关值由以下公式给出:

相关性的规范定义
作为基于真实值的度量标准,它可以是两种类型,正或负。当两个变量朝同一方向移动时,变量是正相关的或直接相关的。当两个变量朝相反方向移动时,相关性是负的或逆的。
相关系数的值介于-1到+1之间,其中接近+1的值表示强烈的正相关,而接近-1的值是强烈负相关的指标:

样本分布如何影响相关值的图形描述
有其他测量相关性的方法。在这本书中,我们将主要讨论线性相关性。还有研究非线性相关性的其他方法,这些方法不会在本书中介绍。

线性相关性和非线性相关性度量之间的差异描述
在本章的实践练习中,你将找到线性协方差和相关的实现。
使用协方差和相关性搜索斜率和截距
正如我们一开始就知道的,我们需要找到以下形式的表示底层数据的直线方程:

线性方程的近似定义
由于我们知道这条线穿过所有点的平均值,我们可以估计截距,唯一未知的是估计的斜率:

截距的导出定义
斜率表示因变量变化与自变量变化的比值。在这种情况下,我们处理的是数据的变化,而不是坐标之间的绝对差异。
由于数据是非均匀的,我们将斜率定义为与因变量协变的自变量方差的比率:

估计斜率系数
恰好,如果我们绘制的数据看起来像圆形云,我们的斜率将变为零,这表明x 和 y的变化之间没有因果关系,以下形式表示:

估计斜率系数的展开形式
应用我们之前展示的公式,我们最终可以将估计回归线的斜率表达式简化为以下表达式:

斜率系数的最终形式
在这里,S[y]是y的标准差,而S[x]是x的标准差。
在方程中剩余元素的帮助下,我们可以简单地根据直线将到达均值数据点的知识推导出截距:

近似截距系数的最终形式
因此,我们已经完成了两种初步回归形式的非常简化的表达,这也为我们留下了许多分析元素。现在,是时候介绍当前机器学习技术的明星了,你作为从业者肯定会在许多项目中使用,称为梯度下降。
梯度下降
是时候讨论将带我们进入现代机器学习核心的方法了。这里解释的方法将被用于许多更复杂的模型,以类似的方式使用,难度增加,但原理相同。
一些直观的背景
为了介绍梯度下降,我们首先来看我们的目标——将线函数拟合到一组提供的数据。那么我们有哪些元素呢?
-
一个模型函数
-
一个误差函数
我们还可以得到的一个元素是所有可能误差的表示,对于任何参数组合。那会很好,对吧?但看看这样一个函数在只有一个简单直线作为解的问题中的样子。这个曲线代表的是 z= x² + y²,它遵循最小二乘误差函数的形式:

在两个变量的情况下,最小二乘误差曲面。在线性回归的情况下,它们是斜率和交点
如你所见,计算每个线参数的所有可能结果会消耗太多的 CPU 时间。但我们有一个优势:我们知道这种曲线的表面是凸的(这超出了本书的范围),所以它大致看起来像一个碗,并且有一个唯一的最低值(如前一个文件夹中所示)。这将避免我们定位看似最小但实际上只是表面上的波峰的问题。
梯度下降循环
因此,是时候寻找一种方法来收敛到函数的最小值,只知道我在曲面上的位置,以及可能是我站立在曲面上的点的梯度:
-
从一个随机位置开始(记住,我们对曲面一无所知)
-
寻找最大变化的方向(因为函数是凸的,我们知道它会引导我们到最小值)
-
沿着误差表面朝那个方向前进,与误差量成比例
-
将下一步的起点调整到我们着陆的曲面上的新点,并重复这个过程
这种方法允许我们以迭代的方式,在有限的时间内发现我们值最小化的路径,与暴力方法相比。
对于两个参数和最小二乘函数的过程是这样的:

梯度下降算法的描述,从起始的高误差点开始,沿着最大变化的方向下降
这让我们对在正常设置下,当我们使用和选择良好且适当的初始参数时,过程是如何工作的有一个概念。
在接下来的章节中,我们将更详细地介绍梯度下降的过程,包括选择不同的元素(我们将称之为超参数)如何改变过程的行为。
正式化我们的概念
现在,让我们回顾一下我们过程的数学方面,这样我们就有了一个参考,在将它们用于实践之前,了解所有涉及的各个部分。
我们方程的元素如下:
-
线性函数变量,β[0] 和 β[1]
-
样本集中的样本数量,m
-
样本集中的不同元素,x^((i)) 和 y^((i))
让我们从我们的误差函数 J 开始,它在前面几节中被称为最小二乘函数。为了实用性,我们将在方程的开始处添加 1/2m 项,如下所示:

最小二乘误差函数
让我们引入一个新的运算符,这是所有后续工作的基础,即梯度。
为了构建这个概念,我们有以下内容:
-
一个或多个独立变量的函数
-
对于所有独立变量的函数的偏导数
如我们目前所知,如何计算偏导数,因此我们只需说梯度是一个包含所有已提到的偏导数的向量;在我们的情况下,它将如下所示:

误差函数的梯度
这样的运算符的目的是什么?如果我们能够计算它,它将给出整个函数在单一点上变化的方向。
首先,我们计算偏导数。你可以尝试推导它;基本上,它使用求导平方表达式的链式法则,然后乘以原始表达式。
在方程的第二部分,我们通过模型函数 h[a] 的名称简化线性函数:

对于 β[1] 变量的误差函数的偏导数
在 β[1] 的情况下,我们得到 x^((i)) 元素的附加因子,因为 β[1]x^((i) 的导数是 x^((i)):

对于 β[1] 变量的误差函数的偏导数
现在我们引入递归表达式,它将在迭代且条件满足的情况下提供一组参数,以收敛的方式减少总误差。
这里,我们引入一个非常重要的元素:步长,命名为 α。它的目的是什么?它将允许我们调整我们在每一步中前进的距离。我们将发现,如果没有选择正确数量的幂,可能会导致灾难性的后果,包括误差发散到无穷大。
注意,第二个公式只有微小的差异,就是乘以当前的 x 值:

模型函数的递归方程
因此,我们准备出发了!现在我们只需添加一点数学调味料,以便产生算法的更紧凑表示。现在让我们以向量形式表示未知数,这样所有的表达式都将作为一个整体来表示:

β 的向量表示
使用这个新的表达式,我们的递归步骤可以用这个简单且易于记忆的表达式来表示:

梯度下降递归的向量表示
将递归表示为过程
寻找最小误差的整个方法可以交替地用流程图表示,这样我们就可以将所有元素放在同一个地方,并理解如果我们暂时不考虑相对复杂的分析机制,它看起来是多么简单:

梯度下降方法的流程图。注意其简单的构建模块,没有考虑到它所涉及的微妙数学。
因此,有了这个关于梯度下降过程的最后程序性视角,我们准备继续本章更实用的部分。我们希望您喜欢这段寻找答案的旅程:我们如何以简单的方式表示我们的数据?请放心,在接下来的章节中,我们将使用更强大的工具。
实用性 - 新方法的新工具
在本节中,我们将介绍一个新库,它将帮助我们处理协方差和相关性,尤其是在数据可视化领域。
Seaborn 是什么?
Seaborn 是一个用于在 Python 中创建吸引人且信息丰富的统计图形的库。此外,它还提供了非常有用的多元分析原语,这将帮助您决定是否以及如何将确定回归分析应用于您的数据。
Seaborn 提供的一些功能如下:
-
几种非常高质量的内置主题
-
选择调色板以制作美丽图表的工具,这些图表可以揭示数据中的模式
-
用于可视化单变量和双变量分布或比较数据子集之间分布的重要函数
-
适合并可视化不同类型独立和依赖变量的线性回归模型的可视化工具
-
当用最小参数集调用时尝试做些有用的事情的绘图函数;它们通过额外的参数暴露出许多可定制的选项
一个重要的附加功能是,由于 Seaborn 使用 matplotlib,可以使用这些工具进一步调整图形,并使用 matplotlib 的任何后端进行渲染。
现在让我们探索 Seaborn 将带来的最有用的实用工具。
用于变量探索的有用图表 - 对数图
在数据探索阶段,我们可以拥有的最有用的度量之一是数据集中所有特征的交互关系的图形表示,并以直观的方式发现联合变化:

Iris 数据集中变量的对图
相关性图
相关性图允许我们以更简洁的方式总结变量之间的依赖关系,因为它使用颜色板显示了变量对之间的直接相关性。对角线值当然是 1,因为所有变量都与自身具有最大相关性:

旧金山住房数据集的相关性图
数据探索和线性回归实践
在本节中,我们将开始使用最著名的玩具数据集之一,对其进行探索,并选择一个维度来学习如何为其值构建线性回归模型。
让我们先导入所有库(scikit-learn、seaborn和matplotlib);Seaborn 的一个优秀特性是它能够定义非常专业的样式设置。在这种情况下,我们将使用whitegrid样式:
import numpy as np from sklearn import datasets import seaborn.apionly as sns %matplotlib inline import matplotlib.pyplot as plt sns.set(style='whitegrid', context='notebook')
Iris 数据集
是时候加载Iris数据集了。这是最著名的传统数据集之一。你会在许多书籍和出版物中找到它。鉴于数据的好性质,它对分类和回归示例非常有用。Iris 数据集(archive.ics.uci.edu/ml/datasets/Iris)包含三种鸢尾花类型各 50 条记录,总共有 150 行,分布在五个字段中。每一行都是以下测量值:
-
花萼长度(单位:厘米)
-
花萼宽度(单位:厘米)
-
花瓣长度(单位:厘米)
-
花瓣宽度(单位:厘米)
最后一个字段是花的类型(setosa、versicolor或virginica)。让我们使用load_dataset方法从数据集中创建一个值矩阵:
iris2 = sns.load_dataset('iris')
为了理解变量之间的依赖关系,我们将实现协方差操作。它将接收两个数组作为参数,并返回covariance(x,y)值:
def covariance (X, Y):
xhat=np.mean(X)
yhat=np.mean(Y)
epsilon=0
for x,y in zip (X,Y):
epsilon=epsilon+(x-xhat)*(y-yhat)
return epsilon/(len(X)-1)
让我们尝试一下实现的功能,并将其与 NumPy 函数进行比较。请注意,我们计算了cov(a,b),而 NumPy 生成了一个包含所有组合cov(a,a)、cov(a,b)的矩阵,因此我们的结果应该等于该矩阵的(1,0)和(0,1)值:
print (covariance ([1,3,4], [1,0,2]))
print (np.cov([1,3,4], [1,0,2]))
0.5
[[ 2.33333333 0.5 ]
[ 0.5 1\. ]]
在对之前定义的关联函数进行最小测试后,接收两个数组,例如covariance,并使用它们来获取最终值:
def correlation (X, Y):
return (covariance(X,Y)/(np.std(X, ddof=1)*np.std(Y, ddof=1))) ##We have to indicate ddof=1 the unbiased std
让我们用两个样本数组测试这个函数,并将其与 NumPy 的相关矩阵的(0,1)和(1,0)值进行比较:
print (correlation ([1,1,4,3], [1,0,2,2]))
print (np.corrcoef ([1,1,4,3], [1,0,2,2]))
0.870388279778
[[ 1\. 0.87038828]
[ 0.87038828 1\. ]]
使用 Seaborn 对对图获得直观想法
在开始解决问题时,获取所有可能变量组合的图形表示是一个非常不错的想法。
Seaborn 的pairplot函数提供了所有变量对的一个完整的图形摘要,以散点图的形式表示,并在矩阵对角线上表示单变量分布。
让我们看看这种图表类型如何显示所有变量的依赖关系,并尝试寻找线性关系作为测试回归方法的基础:
sns.pairplot(iris2, size=3.0)
<seaborn.axisgrid.PairGrid at 0x7f8a2a30e828>

数据集中所有变量的对数图。
让我们选择两个变量,从我们的初步分析来看,它们具有线性依赖的性质。它们是petal_width和petal_length:
X=iris2['petal_width']
Y=iris2['petal_length']
现在我们来看看这个变量组合,它显示出明显的线性趋势:
plt.scatter(X,Y)
这是所选变量的表示,在一个散点图类型中:

这是我们将尝试用我们的线性预测函数建模的当前数据分布。
创建预测函数
首先,让我们定义一个函数,它将以线性函数的形式抽象地表示建模数据,形式为y=betax+alpha*:
def predict(alpha, beta, x_i):
return beta * x_i + alpha
定义误差函数
现在,是时候定义一个函数,它将显示在训练过程中预测值与预期输出之间的差异。正如我们将在下一章中深入解释的,我们有两个主要的选择:测量值之间的绝对差异(或 L1),或者测量差异平方的变体(或 L2)。让我们定义两种版本,包括第一个公式在第二个公式中:
def error(alpha, beta, x_i, y_i): #L1
return y_i - predict(alpha, beta, x_i)
def sum_sq_e(alpha, beta, x, y): #L2
return sum(error(alpha, beta, x_i, y_i) ** 2
for x_i, y_i in zip(x, y))
相关性拟合
现在,我们将定义一个函数来实现相关性方法,以找到回归的参数:
def correlation_fit(x, y):
beta = correlation(x, y) * np.std(y, ddof=1) / np.std(x,ddof=1)
alpha = np.mean(y) - beta * np.mean(x)
return alpha, beta
然后运行拟合函数并打印猜测的参数:
alpha, beta = correlation_fit(X, Y)
print(alpha)
print(beta)
1.08355803285
2.22994049512
现在我们将绘制回归线与数据,以直观地显示解决方案的适当性:
plt.scatter(X,Y)
xr=np.arange(0,3.5)
plt.plot(xr,(xr*beta)+alpha)
这是我们将使用最近计算出的斜率和截距得到的最终图:

最终回归线。
多项式回归以及欠拟合和过拟合的介绍
在寻找模型时,我们寻找的主要特征之一是使用简单的函数表达式进行泛化的能力。当我们增加模型的复杂性时,我们可能会构建一个对训练数据很好的模型,但对于那个特定数据子集来说可能过于优化。
另一方面,欠拟合适用于模型过于简单的情况,例如这个案例,可以用一个简单的线性模型很好地表示。
在下面的例子中,我们将使用 scikit-learn 库来搜索更高阶的多项式,以使用越来越复杂的度数来拟合传入的数据。
超出二次函数的正常阈值,我们将看到函数如何拟合数据的每一个细节,但当我们将数据外推时,超出正常范围之外的值显然超出了范围:
from sklearn.linear_model import Ridge
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
ix=iris2['petal_width']
iy=iris2['petal_length']
# generate points used to represent the fitted function
x_plot = np.linspace(0, 2.6, 100)
# create matrix versions of these arrays
X = ix[:, np.newaxis]
X_plot = x_plot[:, np.newaxis]
plt.scatter(ix, iy, s=30, marker='o', label="training points")
for count, degree in enumerate([3, 6, 20]):
model = make_pipeline(PolynomialFeatures(degree), Ridge())
model.fit(X, iy)
y_plot = model.predict(X_plot)
plt.plot(x_plot, y_plot, label="degree %d" % degree)
plt.legend(loc='upper left')
plt.show()
合并图显示了不同的多项式系数如何以不同的方式描述数据总体。20 次方的多项式清楚地显示了它如何完美地调整训练数据集,在已知值之后,它几乎以惊人的方式发散,违背了为未来数据泛化的目标。

初始数据集的曲线拟合,使用递增值的多项式。
实践中的线性回归和梯度下降
现在,我们第一次在实践中使用梯度下降技术!我们现在正在练习的概念将在本书的其余部分为我们提供良好的服务。让我们首先导入必备的库,就像往常一样。我们将使用 NumPy 进行数值处理,以及 Seaborn 和 matplotlib 进行表示:
import numpy as np
import seaborn as sns
%matplotlib inline
import matplotlib.pyplot as plt
sns.set(style='whitegrid', context='notebook')
损失函数将是我们了解我们做得如何的指南。正如我们在理论部分所看到的,将使用最小二乘法。
你可以在前面的章节中回顾J或损失函数的定义和属性。
因此,这个least_squares函数将接收当前回归线的参数,即b[0]和b[1],以及数据元素来衡量我们对现实表示的好坏:
def least_squares(b0, b1, points):
totalError = 0
N=float(len(points))
for x,y in points:
totalError += (y - (b1 * x + b0)) ** 2
return totalError / 2.*N
在这里,我们将定义递归的每一步。作为参数,我们将接收当前的b[0]和b[1],用于训练模型的点,以及学习率。在step_gradient函数的第 5 行,我们看到计算了两个梯度,然后我们创建了new_b0和new_b1变量,通过学习率按误差方向更新它们的值。在最后一行,我们在所有点都用于梯度后返回更新的值和当前误差水平:
def step_gradient(b0_current, b1_current, points, learningRate):
b0_gradient = 0
b1_gradient = 0
N = float(len(points))
for x,y in points:
b0_gradient += (1/N) * (y - ((b1_current * x) + b0_current))
b1_gradient += (1/N) * x * (y - ((b1_current * x) + b0_current))
new_b0 = b0_current + (learningRate * b0_gradient)
new_b1 = b1_current + (learningRate * b1_gradient)
return [new_b0, new_b1, least_squares(new_b0, new_b1, points)]
然后,我们定义一个函数,将在模型外部运行完整的训练,这样我们就可以在一个地方检查所有参数的组合。这个函数将初始化参数,并将重复梯度步骤固定次数:
def run_gradient_descent(points, starting_b0, starting_b1, learning_rate, num_iterations):
b0 = starting_b0
b1 = starting_b1
slope=[]
intersect=[]
error=[]
for i in range(num_iterations):
b0, b1 , e= step_gradient(b0, b1, np.array(points), learning_rate)
slope.append(b1)
intersect.append(b0)
error.append(e)
return [b0, b1, e, slope, intersect,error]
当收敛速度很高时,这个过程可能会证明效率低下,浪费宝贵的 CPU 迭代次数。一个更聪明的停止条件将包括添加一个可接受的误差值,这将停止迭代。
好吧,是时候尝试我们的模型了!让我们再次加载 Iris 数据集,作为参考,并检查我们结果的正误。我们将使用petal_width和petal_length参数,我们已经看到并决定它们是线性回归的良好候选者。NumPy 的dstack命令允许我们将两列合并,我们将它们转换为列表以删除列标题。唯一的缺点是生成的列表有一个未使用的额外维度,我们使用[0]索引选择器来丢弃它:
iris = sns.load_dataset('iris')
X=iris['petal_width'].tolist()
Y=iris['petal_length'].tolist()
points=np.dstack((X,Y))[0]
因此,让我们尝试我们的模型,使用看起来很好的初始参数,0.0001的学习率,初始参数在0,以及1000次迭代;看看它的表现如何:
learning_rate = 0.0001
initial_b0 = 0
initial_b1 = 0
num_iterations = 1000
[b0, b1, e, slope, intersect, error] = run_gradient_descent(points, initial_b0, initial_b1, learning_rate, num_iterations)
plt.figure(figsize=(7,5))
plt.scatter(X,Y)
xr=np.arange(0,3.5)
plt.plot(xr,(xr*b1)+b0);
plt.title('Regression, alpha=0.001, initial values=(0,0), it=1000');

嗯,这不好;显然,我们还没有达到那里。让我们看看训练过程中的错误发生了什么:
plt.figure(figsize=(7,5))
xr=np.arange(0,1000)
plt.plot(xr,np.array(error).transpose());
plt.title('Error for 1000 iterations');

这个过程看起来似乎在起作用,但有点慢。也许我们可以尝试将步长增加 10 倍,看看它是否快速收敛?让我们检查一下:
learning_rate = 0.001 #Last one was 0.0001
initial_b0 = 0
initial_b1 = 0
num_iterations = 1000
[b0, b1, e, slope, intersect, error] = run_gradient_descent(points, initial_b0, initial_b1, learning_rate, num_iterations)
plt.figure(figsize=(7,5))
xr=np.arange(0,1000)
plt.plot(xr,np.array(error).transpose());
plt.title('Error for 1000 iterations, increased step by tenfold');

这比之前好多了!过程收敛得更快。让我们看看现在回归线的样子:
plt.figure(figsize=(7,5))
plt.scatter(X,Y)
xr=np.arange(0,3.5)
plt.plot(xr,(xr*b1)+b0);
plt.title('Regression, alpha=0.01, initial values=(0,0), it=1000');

是的!看起来好多了。我们可能会认为我们已经完成了,但开发者总是希望更快。让我们看看如果我们想要更快,比如用巨大的步长2,会发生什么:
learning_rate = 0.85 #LAst one was 0.0001
initial_b0 = 0
initial_b1 = 0
num_iterations = 1000
[b0, b1, e, slope, intersect, error] = run_gradient_descent(points, initial_b0, initial_b1, learning_rate, num_iterations)
plt.figure(figsize=(7,5))
xr=np.arange(0,1000)
plt.plot(xr,np.array(error).transpose());
plt.title('Error for 1000 iterations, big step');

这是一个糟糕的举动;正如你所见,错误最终变成了无穷大!这里发生了什么?简单来说,我们采取的步骤太过激进,以至于我们并没有像之前描述的那样切割那个想象中的碗,而是在表面跳跃,随着迭代的进行,我们开始无法控制地累积错误。另一个可以采取的措施是改进我们的种子值,正如你所见,它从0开始。这在一般情况下对这个技术来说是一个非常糟糕的想法,尤其是在你处理未归一化的数据时。这里有更多原因,你可以在更高级的文献中找到。所以,让我们尝试在伪随机位置初始化参数,以便让代码示例中的图形保持一致,看看会发生什么:
learning_rate = 0.001 #Same as last time
initial_b0 = 0.8 #pseudo random value
initial_b1 = 1.5 #pseudo random value
num_iterations = 1000
[b0, b1, e, slope, intersect, error] = run_gradient_descent(points, initial_b0, initial_b1, learning_rate, num_iterations)
plt.figure(figsize=(7,5))
xr=np.arange(0,1000)
plt.plot(xr,np.array(error).transpose());
plt.title('Error for 1000 iterations, step 0.001, random initial parameter values');

正如你所见,即使你有相同的粗略错误率,初始错误值也减少了十倍(从 2e5 到 2e4)。现在让我们尝试一种最终的技术来改进基于输入值归一化的参数收敛。正如你在第二章,“学习过程”中所学到的,它包括对数据进行中心化和缩放。这个操作对数据有什么影响?使用图形图像,当数据未归一化时,错误表面往往很浅,值波动很大。归一化将那些数据转换成一个更深层次的表面,有更明确的梯度指向中心:
learning_rate = 0.001 #Same as last time
initial_b0 = 0.8 #pseudo random value
initial_b1 = 1.5 #pseudo random value
num_iterations = 1000
x_mean =np.mean(points[:,0])
y_mean = np.mean(points[:,1])
x_std = np.std(points[:,0])
y_std = np.std(points[:,1])
X_normalized = (points[:,0] - x_mean)/x_std
Y_normalized = (points[:,1] - y_mean)/y_std
plt.figure(figsize=(7,5))
plt.scatter(X_normalized,Y_normalized)
<matplotlib.collections.PathCollection at 0x7f9cad8f4240>

现在我们有了这组干净整洁的数据,让我们再次尝试使用最后的慢收敛参数,看看错误最小化速度会发生什么:
points=np.dstack((X_normalized,Y_normalized))[0]
learning_rate = 0.001 #Same as last time
initial_b0 = 0.8 #pseudo random value
initial_b1 = 1.5 #pseudo random value
num_iterations = 1000
[b0, b1, e, slope, intersect, error] = run_gradient_descent(points, initial_b0, initial_b1, learning_rate, num_iterations)
plt.figure(figsize=(7,5))
xr=np.arange(0,1000)
plt.plot(xr,np.array(error).transpose());
plt.title('Error for 1000 iterations, step 0.001, random initial parameter values, normalized initial values');

确实是一个非常好的起点!仅仅通过归一化数据,我们就将初始错误值减少了一半,经过 1,000 次迭代后,错误下降了 20%。我们唯一要记住的是,在得到结果后,我们需要去归一化,以便保持初始的尺度和数据中心。所以,关于梯度下降就到这里。我们将在下一章中重新讨论它,以面对新的挑战。
逻辑回归
本书的方法之一是概括。在第一章中,我们从现实世界的简单表示开始,因此也开始了对信息结构进行分组或预测的简单标准。
在回顾了主要用于预测遵循建模线性函数的实值的线性回归之后,我们将进一步探讨其推广,这将使我们能够从先前拟合的线性函数开始,分离二元结果(表示一个样本属于一个类别)。因此,让我们开始这项技术,它将在本书的几乎所有后续章节中具有基本用途。
线性回归和逻辑回归的问题域
为了直观地理解逻辑回归的问题域,我们将使用图形表示。
在第一部分,我们展示了线性拟合函数,这是整个模型构建过程的主要目标,底部是目标数据分布。正如你清楚地看到的,数据现在是二元的,一个样本属于一个或另一个选项,中间没有其他东西。此外,我们还看到建模函数是一种新型;我们稍后将命名并研究其特性。你可能想知道这与线性函数有什么关系?好吧,正如我们稍后将会看到的,它将位于那个 s 形函数内部,适应其形状。

简化的数据分布描述,其中应用了线性或逻辑回归。
总结来说,线性回归可以想象为一个不断增长值的连续体。另一个领域是输出可以根据 x 值具有两个不同值的领域。在图像中显示的特定情况下,我们可以看到随着自变量的增加,向一个可能结果的趋势变得明显,而 sigmoid 函数使我们能够从两个在时间上没有明显分离的输出之间过渡,这给我们提供了一个在非发生/发生区域重叠区的估计概率。
在某种程度上,术语有些令人困惑,因为我们正在进行回归,得到的是一个连续值,但现实中,最终目标是构建一个对离散变量分类问题的预测。
关键在于理解我们将获得一个项目属于一个类别的概率,而不是一个完全离散的值。
逻辑函数的前身——对数几率函数
在我们研究逻辑函数之前,我们将回顾其基础函数,即对数几率函数,它赋予它一些更一般的特性。
本质上,当我们谈论对数几率函数时,我们是在处理随机变量 p 的函数;更具体地说,是与伯努利分布相对应的函数。
链接函数
由于我们正在尝试构建一个广义线性模型,我们希望从一个线性函数开始,并从因变量获得一个映射到概率分布。
由于我们模型的输出类型是二进制的,通常选择的分布是伯努利分布,而链接函数,倾向于逻辑函数,是logit 函数。
Logit 函数
我们可以使用的可能变量之一是几率的自然对数,即 p 等于 1。这个函数被称为 logit 函数:

我们也可以称 logit 函数为对数几率函数,因为我们正在计算给定概率 p 的对数几率 (p/1-p)。
Logit 函数的性质
因此,正如我们可以直观推断的那样,我们用独立变量的组合替换 x,无论它们的值如何,并用任何从负无穷大到无穷大的出现替换 x。我们将响应缩放到 0 到 1 之间。

Logit 函数的主要范围特性的描述
Logit 逆函数的重要性
假设我们计算 logit 函数的逆。logit 的简单逆变换将给出以下表达式:

Logit 函数的解析定义
这个函数就是 Sigmoid 函数。
Sigmoid 或逻辑函数
逻辑函数将代表我们在新的回归任务中要表示的二进制选项。逻辑函数定义为如下(为了清晰起见,将自变量从 α 改为 t):

你将在以下章节中找到这个新图形很常见,因为它将被非常频繁地用作神经网络和其他应用的激活函数。在以下图中,你可以找到 Sigmoid 函数的图形表示:
标准 Sigmoid
我们如何解释并给这个函数赋予意义以用于我们的建模任务?这个方程的正常解释是 t 代表一个简单的自变量,但我们将改进这个模型,假设 t 是单个解释变量 x 的线性函数(t 是多个解释变量的线性组合的情况类似处理),如下所示:

因此,我们可以再次从原始的 logit 方程开始:

我们将到达回归方程,该方程将给出以下方程的回归概率:

注意,p(帽)表示一个估计概率。什么将给我们一个如何接近解的度量?当然,一个精心选择的损失函数!
以下图像显示了从可能的无限结果域到最终被减少到 [0,1] 范围的映射,其中 p 是表示事件发生的概率。这在一个简单的方案中显示,这是 logit 函数的结构和域转换(从线性到由 Sigmoid 模型的概率):

线性方程的 logit 的函数映射,结果是一个 sigmoid 曲线
哪些变化将影响线性函数的参数?它们是那些将改变 sigmoid 函数的中心斜率和从零的位移的值,从而使它更精确地减少回归值与实际数据点之间的误差。
逻辑函数的性质
函数空间中的每一条曲线都可以用它可以应用到的可能目标来描述。在逻辑函数的情况下,它们如下所示:
-
根据一个或多个独立变量建模事件 p 的概率。例如,根据先前资格获得奖项的概率
-
估计(这是回归部分)与事件不发生的可能性相关的特定观测值的 p。
-
使用二元响应预测独立变量变化的影响。
-
通过计算一个项目属于特定类的概率来对观测值进行分类。
多类应用 - softmax 回归
到目前为止,我们只对只有两个类别的情况进行分类,或者用概率语言来说,是事件发生的概率 p。但这个逻辑回归也可以方便地推广到许多类的情况。
正如我们之前看到的,在逻辑回归中,我们假设标签是二元的 (y(i)∈{0,1}),但 softmax 回归允许我们处理 y(i)∈{1,…,K},其中 K 是类别的数量,标签 y 可以取 K 个不同的值,而不仅仅是两个。
给定一个测试输入 x,我们想要估计对于每个 k=1,…,K 的值,P(y=k|x) 的概率。softmax 回归将使这个输出成为一个 K-维向量(其元素之和为 1),从而给出我们的 K 个估计概率:

单变量逻辑回归结果与 N 类 softmax 回归的比较
实际示例 - 使用逻辑回归进行心脏病建模
是时候借助非常有用的逻辑回归来解决一个实际示例了。在这个第一个练习中,我们将基于人口年龄预测患有冠状动脉心脏病的概率。这是一个经典问题,将是理解这类回归分析的良好开端。
CHDAGE 数据集
对于第一个简单的例子,我们将使用一个非常简单且经常研究的数据集,该数据集发表在《应用逻辑回归》中,由 David W. Hosmer, Jr.、Stanley Lemeshow 和Rodney X. Sturdivant所著。我们列出 100 个受试者的年龄(AGE)和是否存在显著冠心病(CHD)的证据,在一个假设的心脏病风险因素研究中。表格还包含一个标识变量(ID)和一个年龄组变量(AGEGRP)。
结果变量是CHD,用0表示CHD不存在,或用1表示它在个体中存在。一般来说,可以使用任何两个值,但我们发现使用零和一最为方便。我们将这个数据集称为CHDAGE数据。
数据集格式
CHDAGE数据集是一个两列的 CSV 文件,我们将从外部仓库下载它。在第一章中,我们使用了原生的 TensorFlow 方法来读取数据集。在这一章中,我们将使用一个互补且流行的库来获取数据。新增这一部分的原因是,鉴于数据集只有 100 个元组,直接读取一行是实用的,而且我们还可以从 pandas 库中免费获得简单但强大的分析方法。
在这个项目的第一阶段,我们将开始加载CHDAGE数据集的一个实例。然后我们将打印关于数据的关键统计数据,接着进行预处理。在绘制了一些数据图表之后,我们将构建一个由激活函数组成的模型,该激活函数将是一个 softmax 函数,对于它变成标准逻辑回归的特殊情况,即当只有两个类别(疾病的有无)时。
让我们先导入所需的库:
import numpy as np
import pandas as pd
from sklearn import datasets
from sklearn import linear_model
import seaborn.apionly as sns
%matplotlib inline
import matplotlib.pyplot as plt
sns.set(style='whitegrid', context='notebook')
让我们使用 pandas 的read_csv函数从 CSV 原始文件中读取数据集,并使用 matplotlib 的散点函数绘制数据分布。正如我们所看到的,通过年份,确实存在一种与随着年龄增长而出现的冠心病相关的模式:
df = pd.read_csv("data/CHD.csv", header=0)
plt.figure() # Create a new figure
plt.axis ([0,70,-0.2,1.2])
plt.title('Original data')
plt.scatter(df['age'],df['chd']) #Plot a scatter draw of the random datapoints
这是原始数据的当前图表:

现在,我们将使用 scikit-learn 的逻辑回归对象创建一个逻辑回归模型,然后调用fit函数,该函数将创建一个 sigmoid 函数,以最小化我们的训练数据的预测误差:
logistic = linear_model.LogisticRegression(C=1e5)
logistic.fit(df['age'].reshape(100,1),df['chd'].reshape(100,1))
LogisticRegression(C=100000.0, class_weight=None, dual=False,
fit_intercept=True, intercept_scaling=1, max_iter=100,
multi_class='ovr', n_jobs=1, penalty='l2', random_state=None,
solver='liblinear', tol=0.0001, verbose=0, warm_start=False)
现在是展示结果的时候了。在这里,我们将从 10 岁到 90 岁生成一个线性空间,包含 100 个细分。
对于域的每个样本,我们将展示发生概率(1)和不发生概率(0,或前一个值的逆)。
此外,我们还将展示预测结果以及原始数据点,这样我们就可以在单个图形中匹配所有元素:
x_plot = np.linspace(10, 90, 100)
oneprob=[]
zeroprob=[]
predict=[]
plt.figure(figsize=(10,10))
for i in x_plot:
oneprob.append (logistic.predict_proba(i)[0][1]);
zeroprob.append (logistic.predict_proba(i)[0][0]);
predict.append (logistic.predict(i)[0]);
plt.plot(x_plot, oneprob);
plt.plot(x_plot, zeroprob)
plt.plot(x_plot, predict);
plt.scatter(df['age'],df['chd'])

原始数据分布、建模逻辑曲线及其逆函数的同步绘图
摘要
在本章中,我们回顾了使用简单和确定性的函数来建模数据的主要方法。
在下一章中,我们将使用更复杂的模型,这些模型可以达到更高的复杂性,处理更高级的抽象,并且对于最近出现的各种各样的大量数据集非常有用,从简单的前馈网络开始。
参考文献
高尔顿,弗朗西斯,"遗传身高的回归到中等水平"。《大不列颠及爱尔兰人类学学会杂志》第 15 卷(1886 年):246-263。
沃克,斯特罗瑟·H.,和大卫·B.邓肯,"将事件概率作为几个独立变量的函数进行估计"。《生物计量学》第 54 卷第 1-2 期(1967 年):167-179。
科克斯,大卫·R,"二元序列的回归分析"。《皇家统计学会会刊。方法系列》(1958 年):215-242。
第五章:神经网络
作为一名开发者,你一定从每天在常规设备上看到的令人难以置信的神奇应用中产生了对机器学习的兴趣——自动语音翻译、图片风格转换、从样本图片生成新图片的能力等等。做好准备吧...我们将直接进入使所有这些成为可能的技术。
线性和逻辑模型,如我们所观察到的,在训练数据集的复杂度方面存在某些局限性,即使它们是高度细致和高效解决方案的基础。
一个模型需要有多复杂才能捕捉到作者的写作风格、猫和狗的图像概念,或基于视觉元素的植物分类?这些事情需要大量低级和高级细节的汇总,在我们的大脑中是通过专门的神经元集合捕获的,在计算机科学中是通过神经网络模型捕获的。
与预期相反,在这本书中,我将省略对神经系统的典型介绍,包括其能力、神经系统中的神经元数量、其化学特性等等。我发现这些话题给问题增添了一种不可能性,但我们即将学习的模型是简单的数学方程式,具有计算对应物,我们将尝试强调这一点,以便你,一个对算法感兴趣的人,能够轻松理解它们。
在本章中,我们将涵盖以下主题:
-
神经模型的历史,包括感知器和 ADALINE
-
神经网络和它们解决的问题类型
-
多层感知器
-
实现一个简单的神经网络层来模拟二元函数
本章将使你能够使用你每天看到的绝大多数令人惊叹的机器学习应用的基础构建块。那么,让我们开始吧!
神经模型的历史
在计算机科学的时间尺度上,神经模型作为一种试图构建大脑内部工作表示的学科,起源相当遥远。它们甚至可以追溯到现代计算机起源的中期——20 世纪 40 年代中期。
在那时,神经科学和计算机科学开始通过研究模拟大脑处理信息的方式开始合作,从其构成单元——神经元开始。
人类大脑学习功能的第一种数学方法可以归功于麦卡洛克和皮茨,他们在 1943 年的论文《内在于神经活动的逻辑演算:A Logical Calculus of Ideas Immanent in Nervous Activity:》中提出。

麦卡洛克和皮茨模型
这个简单的模型是一个基本但现实的算法模型。如果你使用线性函数作为传递函数,你会对发生的事情感到惊讶;这是一个简单的线性模型,就像我们在上一章中看到的那样。
你可能已经注意到我们现在使用w字母来指定模型要调整的参数。从现在开始,这将成为新的标准。我们旧的线性回归模型中的β参数现在将是w。
但模型还没有确定调整参数的方法。让我们向前推进到 20 世纪 50 年代,回顾一下感知器模型。
感知器模型
感知器模型是实现人工神经元的最简单方法之一。它最初在 20 世纪 50 年代末开发,第一个硬件实现是在 20 世纪 60 年代进行的。最初,它是机器的名字,后来变成了算法的名字。是的,感知器并不是我们一直认为的奇怪实体,它们是你作为开发者每天处理的算法!
让我们看看以下步骤,并学习它是如何工作的:
-
使用随机(低值)分布初始化权重。
-
选择一个输入向量并将其呈现给网络。
-
计算指定输入向量和权重值的网络的输出y'。
感知器的函数如下:

-
如果y’ ≠ y,通过添加变化Δw = yxi来修改所有连接,wi。
-
返回到步骤 2。
它基本上是一个学习二进制分类函数并将实函数映射到单个二进制函数的算法。
让我们在以下图中描述感知器的新架构,并分析算法的新方案:

感知器模型(与之前模型的变化突出显示)
感知器建立在它的前辈们的思想之上,但这里的创新之处在于我们增加了一个适当的学习机制!在下面的图中,我们用预定的公式突出了模型的新特性——反馈回路,其中我们计算我们的结果的误差,以及权重的调整:

感知器算法流程图
提高我们的预测能力——ADALINE 算法
ADALINE 是另一种用于训练神经网络的算法(是的,记住我们正在谈论算法)。在某种程度上,ADALINE 比感知器更先进,因为它增加了一种新的训练方法:梯度下降,你现在应该已经知道了。此外,它改变了在将激活输出应用于权重总和之前测量误差的点:

Adaline 模型(感知器增加的部分突出显示)
因此,这是以结构化方式表示 ADALINE 算法的标准方法。作为一个算法由一系列步骤组成,让我们以更详细的方式汇总这些步骤,并添加一些额外的数学细节:
-
使用随机(低值)分布初始化权重。
-
选择一个输入向量并将其呈现给网络。
-
计算指定输入向量和权重值的网络的输出 y'。
-
我们将取的输出值将是求和后的值:
y=Σ(xi * wi)
- 计算误差,比较模型输出与正确标签 o:
E=(o-y)²
它看起来像我们已经见过的东西吗?是的!我们基本上是在解决一个回归问题。
- 使用以下梯度下降递归调整权重:

- 返回到 步骤 2:

Adaline 算法流程图
感知器和 ADALINE 之间的相似之处和不同之处
我们已经介绍了现代神经网络先驱的简化解释。正如你所见,现代模型的所有元素几乎都是在 1950 年代和 1960 年代确定的!在继续之前,让我们尝试比较一下方法:
-
相似之处:
-
它们都是算法(重要的是要强调这一点)
-
它们应用于单层神经网络模型
-
它们是二分类的分类器
-
它们都有一个线性决策边界
-
它们都可以迭代学习,样本样本(感知器自然,ADALINE 通过随机梯度下降)
-
它们都使用阈值函数
-
-
差异:
-
感知器使用最终的类别决策来训练权重
-
ADALINE 使用连续的预测值(来自网络输入)来学习模型系数,并使用连续的浮点数等级来衡量误差的微妙变化,而不是布尔或整数
-
在我们完成单层架构和模型之前,我们将回顾 1960 年代末的一些发现,这些发现引发了神经网络社区的热烈讨论,并被认为是第一个 AI 寒冬,即对机器学习研究兴趣的突然下降。幸运的是,在那之后几年,研究人员找到了克服他们面临局限性的方法,但这将在本章的后续部分进行讨论。
早期模型的局限性
模型本身现在具有任何正常神经网络模型的大部分元素,但它有自己的问题。经过几年的快速发展,Minsky 和 Papert 在 1969 年出版的书籍 P**erceptrons 由于其主要观点——感知器只能处理线性可分问题,而这些问题只是从业者认为可解问题的一小部分——在领域内引起了轰动。在某种程度上,这本书暗示感知器几乎无用,除了最简单的分类任务。
这种新发现的缺陷可以表示为模型无法表示 XOR 函数,这是一个 布尔 函数,当输入不同时输出为 1,当它们相等时输出为 0:

模拟 XOR 函数的问题。没有任何一条线能够正确地分离 0 和 1 的值
如前图所示,主要问题是两个类别(交叉和点)都不是线性可分的;也就是说,我们无法在平面上用任何线性函数将它们分开。
这个检测到的问题导致该领域活动减少,持续了大约五年,直到反向传播算法的发展,这始于 20 世纪 70 年代中期。
单层和多层感知器
现在,我们将讨论更现代的时期,基于先前概念,以允许更复杂的现实元素被建模。
在本节中,我们将直接研究多层感知器(MLPs),这是最常用的配置,并将单层感知器视为前者的一个特例,突出它们之间的差异。
单层和多层感知器在 20 世纪 70 年代和 80 年代是最常用的架构,在神经网络的能力方面取得了巨大的进步。它们带来的主要创新如下:
-
它们是前馈网络,因为计算从输入开始,从一层流向另一层,没有任何循环(信息永远不会返回)
-
他们使用反向传播方法来调整他们的权重
-
将
step函数作为传递函数的使用被非线性函数如 sigmoid 所取代
MLP 起源
在探索了单单元神经网络模型的力量之后,一个明显的步骤是生成层或一组通常相互连接的单元(我们定义连接为将一个单元的输出发送给另一个单元的求和部分):

简单多层前馈神经网络示意图
前馈机制
在网络操作的这一阶段,数据将被输入到第一层,并从每个单元流向下一层的相应单元。然后它将在隐藏层中进行求和并传递,最后由输出层处理。这个过程是完全单向的,因此我们避免了数据流中的任何递归复杂性。
前馈机制在 MLP 中对应于建模过程的训练部分,负责提高模型性能。通常选择的算法被称为反向传播。
选择的优化算法——反向传播
从感知器算法开始,每个神经网络架构都有一种基于真实值与模型输出比较来优化其内部参数的方法。常见的假设是取(当时简单的)模型函数的导数,并迭代地朝着最小值工作。
对于复杂的多层网络,存在额外的开销,这与输出层的输出是函数复合的长链的结果有关,其中每一层的输出都被下一层的传递函数所包裹。因此,输出导数将涉及一个极其复杂的函数的导数。在这种情况下,提出了反向传播方法,并取得了优异的结果。
反向传播可以总结为一种用于计算导数的算法。其主要属性是计算效率高,适用于复杂函数。它也是线性感知器中最小均方误差算法的推广,这是我们已知的!
在反向传播算法中,错误的职责将在整个架构中应用于所有应用于数据的函数。因此,目标是最小化误差,即损失函数的梯度,在一系列深度复合函数上,这又将再次得到链式法则的帮助。
现在是时候定义我们现代神经网络的一般算法了,以下是一些步骤:
-
计算从输入到输出的前向信号。
-
根据预测值 a[k] 和目标值 t[k] 计算输出误差 E。
-
通过加权前一层中的权重和关联激活函数的梯度来反向传播错误信号。
-
根据反向传播的错误信号和从输入的前向信号,计算参数的梯度 𝛿E/𝛿θ。
-
使用计算出的梯度更新参数 θ ← θ - η 𝛿E/𝛿θ *.
让我们以图形方式回顾这个过程:

逐步展示前向传播和反向传播训练过程
在以下图中,我们将整个过程以算法方式表示。您可以看到与先前优化方法的巧合之处,以及涉及的计算块数量较少:

前向/反向传播方案的流程图
要解决的问题类型
神经网络可以用于回归问题和分类问题。常见的架构差异在于输出层:为了能够得到基于实数的结果,不应应用任何标准化函数,如 sigmoid 函数。这样,我们不会将变量的结果改变为许多可能的类别值之一,从而得到一系列可能的连续结果。让我们看看以下要解决的问题类型:
-
回归/函数逼近问题:这类问题使用最小平方误差函数、线性输出激活和 sigmoid 隐藏激活。这将给出一个输出实数值。
-
分类问题(两类,每类一个输出):在这种问题中,我们通常有一个交叉熵损失函数、一个 S 型输出和隐藏激活。S 型函数将给我们一个类发生的概率或非发生的概率。
-
分类问题(多类,每类一个输出):在这种问题中,我们将有一个交叉熵损失函数、softmax 输出和 sigmoid 隐藏激活,以便对单个输入的任何可能的类输出概率。
使用单层感知器实现简单函数
看一下以下代码片段,以实现一个单层感知器的单个函数:
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')
from pprint import pprint
%matplotlib inline
from sklearn import datasets
import matplotlib.pyplot as plt
定义和绘制传递函数类型
仅靠单变量线性分类器,神经网络的学习能力不会很好。甚至在机器学习中的一些轻微复杂的问题也涉及多个非线性变量,因此开发了多种变体来替代感知器的传递函数。
为了表示非线性模型,激活函数中可以使用多种不同的非线性函数。这意味着神经元对输入变量变化的反应方式将发生变化。在接下来的章节中,我们将定义主要的不同的传递函数,并通过代码定义和表示它们。
在本节中,我们将开始使用一些 面向对象编程(OOP)技术来表示问题域的实体。这将使我们能够在示例中以更清晰的方式表示概念。
让我们先创建一个 TransferFunction 类,它将包含以下两个方法:
-
getTransferFunction(x): 此方法将返回由类类型确定的激活函数 -
getTransferFunctionDerivative(x): 此方法将明确返回其导数
对于这两个函数,输入将是一个 NumPy 数组,函数将逐元素应用,如下所示:
>class TransferFunction:
def getTransferFunction(x):
raise NotImplementedError
def getTransferFunctionDerivative(x):
raise NotImplementedError
表示和理解传递函数
让我们看一下以下代码片段,以了解传递函数是如何工作的:
def graphTransferFunction(function):
x = np.arange(-2.0, 2.0, 0.01)
plt.figure(figsize=(18,8))
ax=plt.subplot(121)
ax.set_title(function.__name__)
plt.plot(x, function.getTransferFunction(x))
ax=plt.subplot(122)
ax.set_title('Derivative of ' + function.__name__)
plt.plot(x, function.getTransferFunctionDerivative(x))
S 型或逻辑函数
S 型或逻辑函数是标准的激活函数,非常适合用于计算分类属性中的概率。首先,让我们准备一个函数,该函数将用于绘制所有传递函数及其导数,范围从公共的 -2.0 到 2.0,这将使我们能够看到它们在 y 轴附近的主要特征。
S 型函数的经典公式如下:
class Sigmoid(TransferFunction): #Squash 0,1
def getTransferFunction(x):
return 1/(1+np.exp(-x))
def getTransferFunctionDerivative(x):
return x*(1-x)
graphTransferFunction(Sigmoid)
看一下以下图表:

玩转 S 型函数
接下来,我们将进行一项练习,以了解 sigmoid 函数在乘以权重并偏移以适应最终函数的最小值时是如何变化的。然后我们首先改变单个 sigmoid 函数的可能参数,看看它是如何拉伸和移动的:
ws=np.arange(-1.0, 1.0, 0.2)
bs=np.arange(-2.0, 2.0, 0.2)
xs=np.arange(-4.0, 4.0, 0.1)
plt.figure(figsize=(20,10))
ax=plt.subplot(121)
for i in ws:
plt.plot(xs, Sigmoid.getTransferFunction(i *xs),label= str(i));
ax.set_title('Sigmoid variants in w')
plt.legend(loc='upper left');
ax=plt.subplot(122)
for i in bs:
plt.plot(xs, Sigmoid.getTransferFunction(i +xs),label= str(i));
ax.set_title('Sigmoid variants in b')
plt.legend(loc='upper left');
看一下下面的图表:

让我们看一下下面的代码片段:
class Tanh(TransferFunction): #Squash -1,1
def getTransferFunction(x):
return np.tanh(x)
def getTransferFunctionDerivative(x):
return np.power(np.tanh(x),2)
graphTransferFunction(Tanh)
让我们看一下下面的图表:

矩形线性单元或 ReLU
ReLU 被称为矩形线性单元,它的一大优点是它不受梯度消失问题的影响,这个问题通常包括网络的第一层趋向于零值,或者是一个非常小的 epsilon:
class Relu(TransferFunction):
def getTransferFunction(x):
return x * (x>0)
def getTransferFunctionDerivative(x):
return 1 * (x>0)
graphTransferFunction(Relu)
让我们看一下下面的图表:

线性传递函数
让我们看一下下面的代码片段,以了解线性传递函数:
class Linear(TransferFunction):
def getTransferFunction(x):
return x
def getTransferFunctionDerivative(x):
return np.ones(len(x))
graphTransferFunction(Linear)
让我们看一下下面的图表:

定义神经网络的损失函数
就像机器学习中的每一个模型一样,我们将探讨我们将使用的可能函数,以确定我们的预测和分类做得有多好。
我们将要做的第一种区分是 L1 和 L2 误差函数类型。
L1,也称为最小绝对偏差(LAD)或最小绝对误差(LAE),具有非常有趣的属性,它简单地由模型最终结果与预期结果之间的绝对差值组成,如下所示:


L1 与 L2 属性
现在是时候对两种类型的损失函数进行面对面比较了:
-
鲁棒性:L1 是一个更鲁棒的损失函数,它可以表示为函数在受到异常值影响时的抵抗力,它将二次函数投影到非常高的值。因此,为了选择 L2 函数,我们应该对其进行非常严格的数据清理,以便它能够高效运行。
-
稳定性:稳定性属性评估了当出现大误差值时,误差曲线跳动的程度。L1 更不稳定,尤其是在非归一化数据集的情况下(因为
[-1, 1]范围内的数字在平方后会减小)。 -
解的唯一性:正如其二次性质所暗示的,L2 函数确保我们在寻找最小值时将有一个唯一的答案。L2 总是有一个唯一的解,但 L1 可能有多个解,因为我们可以找到许多路径,这些路径以分段线性函数的形式具有最小长度,与 L2 情况下的单线距离相比。
关于使用,过去属性的总和允许我们在正常情况下使用 L2 误差类型,特别是在解唯一性方面,这在我们开始最小化误差值时给我们提供了所需的确定性。在第一个例子中,我们将从简单的 L1 误差函数开始,用于教育目的。
让我们通过绘制样本 L1 和 L2 损失误差函数的误差结果来探索这两种方法。在下一个简单的例子中,我们将展示两种误差的非常不同的性质。在前两个例子中,我们将输入规范化在-1和1之间,然后是超出该范围的值。
如您所见,从样本0到3,二次误差稳步且连续地增加,但与非归一化数据相比,它可能会爆炸,尤其是在异常值的情况下,如下面的代码片段所示:
sampley_=np.array([.1,.2,.3,-.4, -1, -3, 6, 3])
sampley=np.array([.2,-.2,.6,.10, 2, -1, 3, -1])
ax.set_title('Sigmoid variants in b')
plt.figure(figsize=(10,10))
ax=plt.subplot()
plt.plot(sampley_ - sampley, label='L1')
plt.plot(np.power((sampley_ - sampley),2), label="L2")
ax.set_title('L1 vs L2 initial comparison')
plt.legend(loc='best')
plt.show()
让我们看一下下面的图表:

让我们以LossFunction类和getLoss方法的形式定义损失函数,该方法接收两个 NumPy 数组作为参数,y_或估计的函数值,以及y,期望值:
class LossFunction:
def getLoss(y_ , y ):
raise NotImplementedError
class L1(LossFunction):
def getLoss(y_, y):
return np.sum (y_ - y)
class L2(LossFunction):
def getLoss(y_, y):
return np.sum (np.power((y_ - y),2))
现在是定义目标函数的时候了,我们将将其定义为简单的Boolean。为了允许更快地收敛,它将第一个输入变量和函数的输出之间有一个直接的关系:
# input dataset
X = np.array([ [0,0,1],
[0,1,1],
[1,0,1],
[1,1,1] ])
# output dataset
y = np.array([[0,0,1,1]]).T
我们将使用的第一个模型是一个非常简单的神经网络,具有三个细胞和每个细胞的权重,没有偏差,以保持模型复杂度最小:
# initialize weights randomly with mean 0
W = 2*np.random.random((3,1)) - 1
print (W)
看一下以下由运行前面的代码生成的输出:
[[ 0.52014909]
[-0.25361738]
[ 0.165037 ]]
然后我们将定义一组变量来收集模型的误差、权重和训练结果进度:
errorlist=np.empty(3);
weighthistory=np.array(0)
resultshistory=np.array(0)
然后就是进行迭代误差最小化的时候了。在这种情况下,它将包括通过权重和神经元的传递函数 100 次传递整个真实表,调整权重的方向以减小误差。
注意,这个模型没有使用学习率,所以它应该快速收敛(或发散):
for iter in range(100):
# forward propagation
l0 = X
l1 = Sigmoid.getTransferFunction(np.dot(l0,W))
resultshistory = np.append(resultshistory , l1)
# Error calculation
l1_error = y - l1
errorlist=np.append(errorlist, l1_error)
# Back propagation 1: Get the deltas
l1_delta = l1_error * Sigmoid.getTransferFunctionDerivative(l1)
# update weights
W += np.dot(l0.T,l1_delta)
weighthistory=np.append(weighthistory,W)
让我们简单地回顾一下最后的评估步骤,通过打印l1的输出值。现在我们可以看到,我们正在非常直接地反映原始函数的输出:
print (l1)
看一下以下由运行前面的代码生成的输出:
[[ 0.11510625]
[ 0.08929355]
[ 0.92890033]
[ 0.90781468]]
为了更好地理解这个过程,让我们看看参数随时间的变化。首先,让我们绘制神经元权重图。如您所见,它们从随机状态变为接受第一列的整个值(这总是正确的),然后变为第二列几乎为0(正确 50%的时间),然后变为第三列的-2(主要是因为它必须触发表中的前两个元素的0):
plt.figure(figsize=(20,20))
print (W)
plt.imshow(np.reshape(weighthistory[1:],(-1,3))[:40], cmap=plt.cm.gray_r,
interpolation='nearest');
看一下以下输出,这是通过运行前面的代码生成的:
[[ 4.62194116]
[-0.28222595]
[-2.04618725]]
让我们看一下下面的截图:

让我们也回顾一下我们的解决方案是如何演变(在前 40 次迭代中)直到达到最后一次迭代;我们可以清楚地看到收敛到理想值:
plt.figure(figsize=(20,20))
plt.imshow(np.reshape(resultshistory[1:], (-1,4))[:40], cmap=plt.cm.gray_r,
interpolation='nearest');
让我们看一下以下截图:

我们可以看到错误是如何演变并趋向于零的,在不同的时期。在这种情况下,我们可以观察到它从负值摆动到正值,这是可能的,因为我们首先使用了 L1 误差函数:
plt.figure(figsize=(10,10))
plt.plot(errorlist);
让我们看一下以下截图:

简单神经网络训练误差的下降描述
摘要
在本章中,我们通过实现我们的第一个神经网络,迈出了解决复杂问题的重要一步。现在,以下架构将包含熟悉元素,我们将能够将本章获得的知识应用到新的架构中。
在下一章中,我们将探索更复杂的模型和问题,使用更多层和特殊配置,例如卷积和 dropout 层。
参考文献
参考以下内容:
-
McCulloch, Warren S. 和 Walter Pitts,神经活动中内在思想的逻辑演算。数学生物物理学通报 5.4 (1943):115-133.* Kleene, Stephen Cole。神经网和有限自动机中事件的表现。RAND 项目空军圣莫尼卡,1951。
-
Farley, B. W. A. C. 和 W. Clark,通过数字计算机模拟自组织系统。IRE 专业组信息理论交易 4.4 (1954):76-84。
-
Rosenblatt, Frank*,《感知器:大脑中信息存储和组织的概率模型》,心理评论 65.6 (1958):386.Rosenblatt, Frank. x.
-
神经动力学原理:感知器和大脑机制理论。斯巴丹图书,华盛顿特区,1961
-
Werbos, P.J. (1975),超越回归:行为科学预测和分析的新工具。
-
Preparata, Franco P. 和 Michael Ian Shamos,“引言。”计算几何。斯普林格纽约,1985. 1-35.
-
Rumelhart, David E.,Geoffrey E. Hinton 和 Ronald J,Williams。通过误差传播学习内部表示。No. ICS-8506. 加利福尼亚大学圣地亚哥认知科学研究所,1985.
-
Rumelhart, James L. McClelland 和 PDP 研究组。并行分布式处理:认知微观结构的探索,第 1 卷:基础。麻省理工学院出版社,1986.
-
Cybenko, G. 1989. 通过 Sigmoid 函数的叠加进行逼近 数学控制、信号与系统,2(4),303–314.
-
Murtagh, Fionn。用于分类和回归的多层感知器。神经计算 2.5 (1991):183-197.
-
Schmidhuber, Jürgen。神经网络中的深度学习:概述。神经网络 61 (2015):85-117.
第六章:卷积神经网络
好吧,现在事情变得有趣了!我们的模型现在可以学习更复杂的函数,我们现在准备进行一次关于更现代且出人意料的有效的模型的奇妙之旅
在堆叠层神经元成为提高模型的最流行解决方案之后,出现了更丰富节点的创新想法,始于基于人类视觉的模型。它们最初只是研究主题,随着图像数据集和更多处理能力的出现,它们使研究人员在分类挑战中几乎达到人类的准确性,我们现在准备在我们的项目中利用这种力量。
本章我们将涵盖以下主题:
-
卷积神经网络的起源
-
离散卷积的简单实现
-
其他操作类型:池化、dropout
-
迁移学习
卷积神经网络的起源
卷积神经网络(CNNs)有一个遥远的起源。它们在多层感知器得到完善的同时发展起来,第一个具体的例子是新认知机。
新认知机是一个分层、多层的人工神经网络(ANN),由福岛教授在 1980 年的一篇论文中引入,并具有以下主要特点:
-
自组织
-
对输入的偏移和变形具有容忍性

这个原始想法在 1986 年的原始反向传播论文的书籍版本中再次出现,并在 1988 年被用于语音识别中的时间信号。
设计在 1998 年得到了改进,一篇由 Ian LeCun、 梯度学习应用于文档识别的论文提出了 LeNet-5 网络,这是一种用于分类手写数字的架构。该模型与当时其他现有模型相比,性能有所提高,特别是在 SVM(当时最有效的操作之一)的几个变体上。
然后,2003 年出现了该论文的推广,“用于图像解释的分层神经网络”。但总的来说,几乎所有核都遵循原始想法,直到现在。
卷积入门
为了理解卷积,我们将从研究卷积算子的起源开始,然后我们将解释这个概念是如何应用于信息的。
卷积基本上是两个函数之间的操作,连续或离散,在实践中,它通过另一个函数过滤其中一个的效果。
它在众多领域中有许多用途,特别是在数字信号处理中,它是塑造和过滤音频、图像的首选工具,甚至在概率论中也有应用,它代表两个独立随机变量的和。
这些滤波能力与机器学习有什么关系呢?答案是,通过滤波器,我们将能够构建能够强调或隐藏输入中某些特征的网络节点(根据滤波器的定义),这样我们就可以构建用于检测所有特征的自动定制检测器,这些检测器可以用来检测特定的模式。我们将在接下来的章节中更详细地介绍这一点;现在,让我们回顾该操作的正式定义及其计算摘要。
连续卷积
卷积作为运算最早是在 18 世纪由 d'Alembert 在微分学初步发展期间创造的。该操作的常见定义如下:

如果我们尝试描述应用该操作所需的步骤以及它是如何结合两个函数的,我们可以以下列详细方式表达涉及的数学运算:
-
翻转信号:这是变量的 (-τ) 部分
-
平移它:这是由 t 的求和因子给出的 g(τ)
-
乘以它:f 和 g 的乘积
-
对结果曲线进行积分:这是不太直观的部分,因为每个瞬时值都是积分的结果
为了理解所有涉及的步骤,让我们直观地表示出两个函数在确定点 t[0] 之间计算卷积的所有步骤,f 和 g:

这种对卷积规则的直观近似也适用于函数的离散领域,这是我们实际工作的真实领域。因此,让我们首先定义它。
离散卷积
即使在有用的情况下,我们工作的领域无疑是数字化的,因此我们需要将这个操作转换到离散域。两个离散函数 f 和 g 的卷积操作是将原始积分转换为以下形式的等效求和:

这个原始定义可以应用于任意数量的维度上的函数。特别是,我们将处理二维图像,因为这是许多应用领域的领域,我们将在本章中进一步描述。
现在是学习我们通常应用卷积算子方式的时候了,这种方式是通过核来实现的。
核和卷积
在解决离散域中的实际问题时,我们通常有有限维度的二维函数(例如,可能是一个图像),我们希望通过另一个图像对其进行过滤。滤波器开发学科研究将不同类型的滤波器通过卷积应用于各种类别时产生的影响。最常用的函数类型是每维两个到五个元素,其余元素为 0 值。这些代表滤波函数的小矩阵被称为核。
卷积操作从n维矩阵(通常是一个表示图像的 2D 矩阵)的第一个元素开始,与核的所有元素相乘,将矩阵的中心元素应用于我们正在乘以的特定值,然后按照核的维度应用剩余的因子。在图像的情况下,最终结果是一个等效图像,其中某些元素(例如,线条和边缘)被突出显示,而其他元素(例如,在模糊的情况下)则被隐藏。
在以下示例中,您将看到如何将特定的 3 x 3 核应用于特定的图像元素。这以扫描模式重复应用于矩阵的所有元素:

在应用核时,还需要考虑一些额外的元素,特别是步长和填充,这些元素完成了适应特殊应用案例的规格。让我们来看看步长和填充。
步长和填充
在应用卷积操作时,可以对过程应用的一种变化是改变核的位移单位。这个参数可以按维度指定,称为步长。在以下图像中,我们展示了步长应用的一些示例。在第三种情况下,我们看到一个不兼容的步长,因为核不能应用于最后一步。根据库的不同,这种类型的警告可以被忽略:

在应用核时,另一个重要的事实是,核越大,图像/矩阵边缘不接收答案的单位就越多,因为我们需要覆盖整个核。为了应对这种情况,填充参数将在图像上添加一个指定宽度的边框,以便核能够均匀地应用于边缘像素/元素。这里是对填充参数的图形描述:

在描述了卷积的基本概念之后,让我们通过一个实际例子来实现卷积,看看它如何应用于真实图像,并对其效果有一个直观的认识。
在一个例子中实现 2D 离散卷积操作
为了理解离散卷积操作的机制,让我们对这个概念进行简单的直观实现,并将其应用于具有不同类型核的样本图像。让我们导入所需的库。由于我们将以尽可能清晰的方式实现算法,我们将只使用最必要的库,例如 NumPy:
import matplotlib.pyplot as plt
import imageio
import numpy as np
使用imageio包的imread方法,让我们读取图像(作为三个相等的通道导入,因为它为灰度图)。然后我们切分第一个通道,将其转换为浮点数,并使用 matplotlib 显示:
arr = imageio.imread("b.bmp") [:,:,0].astype(np.float)
plt.imshow(arr, cmap=plt.get_cmap('binary_r'))
plt.show()

现在是定义内核卷积操作的时候了。正如我们之前所做的那样,我们将简化 3 x 3 内核上的操作,以便更好地理解边界条件。apply3x3kernel将在图像的所有元素上应用内核,返回一个等效的新图像。请注意,为了简单起见,我们将内核限制为 3 x 3,因此图像的 1 像素边界不会有一个新值,因为我们没有考虑填充:
class ConvolutionalOperation:
def apply3x3kernel(self, image, kernel): # Simple 3x3 kernel operation
newimage=np.array(image)
for m in range(1,image.shape[0]-2):
for n in range(1,image.shape[1]-2):
newelement = 0
for i in range(0, 3):
for j in range(0, 3):
newelement = newelement + image[m - 1 + i][n - 1+
j]*kernel[i][j]
newimage[m][n] = newelement
return (newimage)
正如我们在前面的章节中看到的,不同的内核配置突出了原始图像的不同元素和属性,构建的过滤器在经过许多个训练周期后可以专门处理非常高级的特征,例如眼睛、耳朵和门。在这里,我们将生成一个以名称为键、内核系数按 3 x 3 数组排列的内核字典。模糊过滤器相当于计算 3 x 3 点邻域的平均值,恒等简单地返回像素值,拉普拉斯是一个经典的导数过滤器,突出显示边界,然后两个索贝尔过滤器将在第一种情况下标记水平边缘,在第二种情况下标记垂直边缘:
kernels = {"Blur":[[1./16., 1./8., 1./16.], [1./8., 1./4., 1./8.], [1./16., 1./8., 1./16.]]
,"Identity":[[0, 0, 0], [0., 1., 0.], [0., 0., 0.]]
,"Laplacian":[[1., 2., 1.], [0., 0., 0.], [-1., -2., -1.]]
,"Left Sobel":[[1., 0., -1.], [2., 0., -2.], [1., 0., -1.]]
,"Upper Sobel":[[1., 2., 1.], [0., 0., 0.], [-1., -2., -1.]]}
让我们生成一个ConvolutionalOperation对象,并生成一个比较内核图形图表,以查看它们如何比较:
conv = ConvolutionalOperation()
plt.figure(figsize=(30,30))
fig, axs = plt.subplots(figsize=(30,30))
j=1
for key,value in kernels.items():
axs = fig.add_subplot(3,2,j)
out = conv.apply3x3kernel(arr, value)
plt.imshow(out, cmap=plt.get_cmap('binary_r'))
j=j+1
plt.show()
<matplotlib.figure.Figure at 0x7fd6a710a208>
在最终图像中,你可以清楚地看到我们的内核如何在图像上检测到几个高细节特征——在第一个中,你看到的是未更改的图像,因为我们使用了单位内核,然后是拉普拉斯边缘检测器、左侧边界检测器、顶部边界检测器,然后是模糊操作器:

在回顾了连续和离散域上卷积操作的主要特征之后,我们可以得出结论,基本上,卷积内核突出或隐藏模式。根据训练的或(在我们的例子中)手动设置的参数,我们可以开始发现图像中的许多元素,例如不同维度的方向和边缘。我们还可以通过模糊内核覆盖一些不需要的细节或异常值,例如。此外,通过堆叠卷积层,我们甚至可以突出更高阶的复合元素,如眼睛或耳朵。
这种卷积神经网络的特征是它们相对于先前数据处理技术的优势:我们可以非常灵活地确定某个数据集的主要组成部分,并将进一步样本表示为这些基本构建块的组合。
现在是查看另一种通常与前者结合使用的层的时刻——池化层。
子采样操作(池化)
子采样操作包括应用一个核(尺寸可变)并通过将图像分成mn 块并取代表该块的元素来减少输入维度的扩展,从而通过某个确定的因子降低图像分辨率。在 2 x 2 核的情况下,图像大小将减半。最著名的操作是最大(max pool)、平均(avg pool)和最小(min pool)池化。
以下图像展示了如何应用一个 2 x 2 maxpool核,应用于一个单通道 16 x 16 矩阵。它只是保持它所覆盖的内部区域的最大值:

现在我们已经看到了这个简单的机制,让我们问问自己,它的主要目的是什么?子采样层的主要目的与卷积层相关:在保留最重要的信息元素的同时,减少信息和复杂性的数量和复杂性。换句话说,它们构建了底层信息的紧凑表示。
现在是时候编写一个简单的池化算子了。与卷积算子相比,编写起来要简单得多,而且更直接,在这种情况下,我们只将实现最大池化,它选择 4 x 4 邻域中最亮的像素并将其投影到最终图像:
class PoolingOperation:
def apply2x2pooling(self, image, stride): # Simple 2x2 kernel operation
newimage=np.zeros((int(image.shape[0]/2),int(image.shape[1]/2)),np.float32)
for m in range(1,image.shape[0]-2,2):
for n in range(1,image.shape[1]-2,2):
newimage[int(m/2),int(n/2)] = np.max(image[m:m+2,n:n+2])
return (newimage)
让我们应用新创建的池化操作,正如你所见,最终图像的分辨率要粗糙得多,而且总体上细节更亮:
plt.figure(figsize=(30,30))
pool=PoolingOperation()
fig, axs = plt.subplots(figsize=(20,10))
axs = fig.add_subplot(1,2,1)
plt.imshow(arr, cmap=plt.get_cmap('binary_r'))
out=pool.apply2x2pooling(arr,1)
axs = fig.add_subplot(1,2,2)
plt.imshow(out, cmap=plt.get_cmap('binary_r'))
plt.show()
你可以看到差异,尽管它们很微妙。最终图像的精度较低,所选像素作为环境中的最大值,产生了一个更亮的图像:

使用 dropout 操作提高效率
正如我们在前面的章节中观察到的,过拟合是每个模型的潜在问题。这种情况也适用于神经网络,数据可以在训练集上做得很好,但在测试集上却不行,这使得它对泛化没有用。
因此,在 2012 年,由 Geoffrey Hinton 领导的一个团队发表了一篇论文,其中描述了 dropout 操作。其操作很简单:
-
选择一个随机的节点数量(所选节点与总数之比是一个参数)
-
所选权重的值被重置为零,从而在后续层中使之前连接的同伴无效
dropout 层的优势
这种方法的主要优势是它防止了同一层中所有神经元同步优化它们的权重。这种在随机组中进行的适应,防止了所有神经元收敛到同一个目标,从而解耦了权重。
对于 dropout 的应用,还发现了一个第二性质,即隐藏单元的激活变得稀疏,这也是一个期望的特性。
在下面的图中,我们展示了原始的、全连接的多层神经网络及其相关的具有 dropout 功能的网络:
深度神经网络
现在我们有了丰富的层数,是时候开始了解神经网络架构随时间演变的历程了。从 2012 年开始,一系列新的、越来越强大的层组合迅速出现,并且势头不可阻挡。这一套架构采用了深度学习这一术语,我们可以大致将其定义为至少包含三个层的复杂神经网络架构。它们还倾向于包含比单层感知器更先进的层,如卷积层。
随时间演变的深度卷积网络架构
深度学习架构可以追溯到 20 年前,并在很大程度上由解决人类视觉问题的挑战所引导。让我们来看看主要的深度学习架构及其主要构建块,然后我们可以为我们的目的重用它们。
Lenet 5
正如我们在卷积神经网络的历史介绍中所见,卷积层是在 20 世纪 80 年代发现的。但直到 1990 年代末,可用的技术还不够强大,无法构建复杂的层组合。
大约在 1998 年,在贝尔实验室,在研究手写数字解码的过程中,伊恩·李飞飞提出了一种新的方法——结合卷积、池化和全连接层来解决识别手写数字的问题。
在这个时候,SVM 和其他更多数学定义的问题被或多或少地成功应用,CNNs 的基本论文表明神经网络可以与当时最先进的方法相比拟地表现良好。
在下面的图中,展示了该架构的所有层,它接收一个 28 x 28 像素的灰度图像作为输入,并返回一个包含每个字符概率的 10 元素向量:

Alexnet
几年后,尽管李飞飞将神经网络应用于其他任务,如人脸和物体识别,但可用结构化数据和原始处理能力的指数级增长使得团队得以扩大规模,并将模型调整到几年前被认为不可能的程度。
促进该领域创新的一个因素是图像识别基准Imagenet的可用性,它包含数百万张按类别组织的物体图像。
从 2012 年开始,每年都会举办大规模视觉识别挑战赛(LSVRC),这有助于研究人员在网络配置上进行创新,每年都取得了更好的成果。
由 Alex Krizhevsky 开发的 AlexNet 是第一个赢得这一挑战的深度卷积网络,并为未来的几年设定了先例。它由一个与 Lenet-5 结构相似的模型组成,但其卷积层的深度达到数百个单位,总参数数量达到数百万。
在接下来的挑战中,牛津大学的 Visual Geometry Group (VGG) 出现了一个强大的竞争者,其 VGG 模型。
VGG 模型
VGG 网络架构的主要特点是它将卷积核的大小减少到简单的 3 x 3 矩阵,并将它们按序列组合,这与之前的竞争者不同,它们的核尺寸较大(高达 11 x 11)。
反讽的是,一系列小的卷积权重加起来却是一个非常大的参数数量(达到数百万级别),因此它必须通过多种措施进行限制。

GoogLeNet 和 Inception 模型
GoogLeNet 是在 2014 年赢得 LSVRC 的神经网络架构,是大型 IT 公司系列中第一个真正成功的尝试,自 2014 年以来,这一系列主要被拥有巨额预算的公司赢得。
GoogLeNet 基本上是九个连锁 Inception 模块的深度组合,几乎没有修改。以下图示中展示了这些 Inception 模块,它们是由微小的卷积块组成,并穿插着一个 3 x 3 的最大池化节点:

即使具有这样的复杂性,GoogLeNet 仍然设法减少了所需的参数数量(与两年前发布的 AlexNet 相比,从 6000 万减少到 1100 万),并提高了准确性(与 AlexNet 相比,误差从 16.4% 降至 6.7%)。此外,Inception 模块的重复使用允许敏捷的实验。
但这并不是这个架构的最后一个版本;不久之后,就创建了一个 Inception 模块的第二个版本,具有以下特点。
批归一化的 Inception V2 和 V3
2015 年 12 月,随着论文 Rethinking the Inception Architecture for Computer Vision 的发布,谷歌研究发布了 Inception 架构的新版本。
内部协方差偏移问题
原始 GoogLeNet 的一个主要问题是训练不稳定。正如我们之前看到的,输入归一化基本上是将所有输入值中心化,并将其值除以标准差,以便为反向传播的梯度提供一个良好的基线。
在大规模数据集的训练过程中发生的情况是,在多个训练示例之后,不同的值振荡开始放大均值参数值,就像在共振现象中一样。这种现象被称为协方差偏移。
为了减轻这一点,解决方案是对原始输入值以及每一层的输出值进行归一化,避免在它们开始偏离平均值之前出现在层之间的不稳定性。
除了批量归一化之外,还有许多增量添加到 V2 中:
-
将卷积的数量减少到最大 3 x 3
-
增加网络的总体深度
-
在每一层上使用宽度增加技术来改善特征组合
-
卷积分解
Inception V3 基本上在相同的架构上实现了所有提出的创新,并添加了批量归一化到网络的辅助分类器中。
在下面的图中,我们表示新的架构。注意卷积单元的减小尺寸:

到了 2015 年底,这一系列架构的最后一次基本改进来自另一家公司,微软,以ResNets的形式。
残差网络(ResNet)
这种新的架构出现在 2015 年 12 月(或多或少与 Inception V3 同时),它有一个简单但新颖的想法——不仅应该使用每个构成层的输出,而且架构还应该将层的输出与原始输入相结合。
在下面的图中,我们观察到 ResNet 模块的一个简化视图。它清楚地显示了卷积系列结束时的求和操作,以及最后的 ReLU 操作:

模块的卷积部分包括从 256 个值减少到 64 个值的特征减少,一个 3 x 3 的滤波层保持特征数量,以及一个将 1 x 1 层从 64 x 256 个值增加特征的层。最初,它跨越了 100 多个层,但在最近的发展中,ResNet 也被用于少于 30 层的深度,但具有更宽的分布。
既然我们已经看到了近年来主要发展的概述,让我们直接进入研究人员为 CNNs 发现的几种主要应用类型。
CNN 深层解决的问题类型
CNN 在过去被用来解决各种各样的问题。以下是主要问题类型的回顾,以及对其架构的简要参考:
-
分类
-
检测
-
分割
分类
如我们之前所见,分类模型将图像或其他类型的输入作为参数,返回一个数组,其元素数量与可能类别的数量相同,每个都有一个相应的概率。
这种解决方案的正常架构是一个复杂的卷积和池化层的组合,最后是一个逻辑层,显示任何预训练类别的概率。
检测
检测增加了一层复杂性,因为它需要猜测图像中一个或多个相关元素的位置,然后尝试对每个这些信息元素进行分类。
对于这个任务,解决个体本地化问题的常见策略是将分类和回归问题结合起来——一个(分类)用于对象的类别,剩下的一个(回归)用于确定检测到的对象的坐标——然后将损失合并为一个共同的损失。
对于多个元素,第一步是确定感兴趣区域的数量,寻找图像中统计上显示出属于同一对象的信息块的地方,然后只对检测到的区域应用分类算法,寻找概率高的阳性案例。
分割
分割增加了额外的复杂性,因为模型必须在图像中定位元素并标记所有定位对象的精确形状,如下面的插图所示:

对于这个任务,最常见的方法是实现顺序下采样和上采样操作,仅使用每个像素的可能结果数量恢复高分辨率图像,这些结果标记了该元素的类别编号。
使用 Keras 部署深度神经网络
在这个练习中,我们将生成 Keras 应用程序库提供的先前描述的 Inception 模型的实例。首先,我们将导入所有必需的库,包括 Keras 模型处理、图像预处理库、用于优化变量的梯度下降以及几个 Inception 实用工具。此外,我们将使用 OpenCV 库调整新的输入图像,以及常见的 NumPy 和 matplotlib 库:
from keras.models import Model
from keras.preprocessing import image
from keras.optimizers import SGD
from keras.applications.inception_v3 import InceptionV3, decode_predictions, preprocess_input
import matplotlib.pyplot as plt
import numpy as np
import cv2
Using TensorFlow backend.
Keras 使得加载模型变得非常简单。你只需要调用InceptionV3类的新实例,然后我们将基于随机梯度下降分配一个优化器,以及用于损失的类别交叉熵,这对于图像分类问题非常适合:
model=InceptionV3()
model.compile(optimizer=SGD(), loss='categorical_crossentropy')
现在模型已加载到内存中,是时候使用cv库加载和调整照片了,然后我们调用 Keras 应用程序的预处理输入,这将归一化值:
# resize into VGG16 trained images' format
im = cv2.resize(cv2.imread('blue_jay.jpg'), (299, 299))
im = np.expand_dims(im, axis=0)
im = im /255.
im = im - 0.5
im = im * 2
plt.figure (figsize=(10,10))
plt.imshow(im[0], cmap=plt.get_cmap('binary_r'))
plt.show()
这是图像归一化后的样子——注意我们的图像结构理解是如何改变的,但从模型的角度来看,这是让模型收敛的最佳方式:

现在我们将调用模型的predict方法,这将显示神经网络最后一层的输出结果,一个包含每个类别概率的数组。decode_predictions方法读取一个字典,其中所有类别编号作为索引,类别名称作为值,因此它提供了检测到的项目分类的名称,而不是数字:
out = model.predict(im)
print('Predicted:', decode_predictions(out, top=3)[0])
print (np.argmax(out))
Predicted: [('n01530575', 'brambling', 0.18225007), ('n01824575', 'coucal', 0.13728797), ('n01560419', 'bulbul', 0.048493069)]
10
如您所见,通过这种方法,我们从一系列类似鸟类中得到了一个非常近似的预测。对输入图像和模型本身的进一步调整可能导致更精确的答案,因为蓝松鸦是包含在 1,000 个可能类别中的类别之一。
使用 Quiver 探索卷积模型
在这个实际例子中,我们将借助 Keras 库和 Quiver 加载我们之前研究过的一个模型(在这个例子中,是 Vgg19)。然后我们将观察架构的不同阶段,以及不同层如何通过一定的输入工作。
使用 Quiver 探索卷积网络
Quiver (github.com/keplr-io/quiver) 是一个最近且非常方便的工具,用于在 Keras 的帮助下探索模型。它创建了一个可以被现代网络浏览器访问的服务器,并允许可视化模型的结构以及从输入层到最终预测的输入图像的评估。
以下代码片段将创建一个 VGG16 模型实例,然后我们将允许 Quiver 读取当前目录中的所有图像,并启动一个网络应用,允许我们与模型及其参数进行交互:
from keras.models import Model
from keras.preprocessing import image
from keras.optimizers import SGD
from keras.applications.vgg16 import VGG16
import keras.applications as apps
model=apps.vgg16.VGG16()
from quiver_engine.server import launch
launch(model,input_folder=".")
然后脚本将下载 VGG16 模型权重(你需要一个好的连接,因为它有几百兆字节)。然后它将模型加载到内存中,并创建一个监听 5000 端口的服务器。
Keras 库下载的模型权重已经用 Imagenet 进行了彻底的训练,因此它已经准备好在我们的数据集的 1,000 个类别上获得非常好的准确率。
在下面的截图中,我们看到加载网络应用索引页面后的第一个屏幕。在左侧,显示了网络架构的交互式图形表示。在中心右侧,你可以选择当前目录中的一张图片,应用将自动将其作为输入,并打印出输入的最可能五种结果。
截图还显示了第一个网络层,它基本上由三个矩阵组成,代表原始图像的红、绿和蓝成分:

然后,当我们深入到模型层时,我们遇到了第一个卷积层。在这里,我们可以看到这个阶段主要突出高级特征,就像我们用 3x3 滤波器设置的那样,例如不同的边缘类型、亮度和对比度:

让我们再前进一点。现在我们可以看到一个不专注于全局特征的中间层。相反,我们看到它已经训练了中间特征,例如不同的纹理集、角度或特征集,如眼睛和鼻子:

当到达最后的卷积层时,真正抽象的概念出现了。这一阶段展示了我们现在训练的模型是多么的强大,因为我们现在看到的是没有任何有用(对我们来说)意义的突出元素。这些新的抽象类别将在一些全连接层之后,导致最终的解决方案,这是一个包含 1,000 个元素的数组,具有浮点概率值,是 ImageNet 中每个类别的概率值:

我们希望您能够探索不同的示例和层的输出,并尝试发现它们如何突出不同类别图像的不同特征。
现在是时候着手研究一种新的机器学习类型了,这种类型包括将先前训练好的网络应用于解决新的问题类型。这被称为迁移学习。
实现迁移学习
在这个例子中,我们将实现之前看到的一个例子,用预训练卷积神经网络的最后阶段替换,并为一组新元素训练最后阶段,应用于分类。它有以下优点:
-
它建立在图像分类任务中已证明有效的模型之上
-
它减少了训练时间,因为我们能够以可能需要数周计算能力才能达到的精度重用系数。
数据集类别将是来自 flower17 数据集的两种不同的花卉类型。这是一个包含每个类别 80 个图像的 17 类别花卉数据集。选择的花是英国的一些常见花卉。图像具有大范围、姿态和光照变化,而且同一类别内也存在图像的大范围变化和与其他类别的相似性。在这种情况下,我们将收集前两个类别(水仙花和紫菀),并在预训练的 VGG16 网络上构建一个分类器。
首先,我们将进行图像数据增强,因为图像的数量可能不足以抽象出每个物种的所有元素。让我们首先导入所有必需的库,包括应用、预处理、检查点模型和相关对象,以便我们保存中间步骤,以及cv2和NumPy库用于图像处理和数值基础操作:
from keras import applications
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
from keras.models import Sequential, Model
from keras.layers import Dropout, Flatten, Dense, GlobalAveragePooling2D
from keras import backend as k
from keras.callbacks import ModelCheckpoint, LearningRateScheduler, TensorBoard, EarlyStopping
from keras.models import load_model
from keras.applications.vgg16 import VGG16, decode_predictions,preprocess_input
import cv2
import numpy as np
Using TensorFlow backend.
在本节中,我们将定义所有影响输入、数据源和训练参数的变量:
img_width, img_height = 224, 224
train_data_dir = "train"
validation_data_dir = "validation"
nb_train_samples = 300
nb_validation_samples = 100
batch_size = 16
epochs = 50
现在我们将调用 VGG16 预训练模型,不包括顶部的平坦层:
model = applications.VGG16(weights = "imagenet", include_top=False, input_shape = (img_width, img_height, 3))
# Freeze the layers which you don't want to train. Here I am freezing the first 5 layers.
for layer in model.layers[:5]:
layer.trainable = False
#Adding custom Layers
x = model.output
x = Flatten()(x)
x = Dense(1024, activation="relu")(x)
x = Dropout(0.5)(x)
x = Dense(1024, activation="relu")(x)
predictions = Dense(2, activation="softmax")(x)
# creating the final model
model_final = Model(input = model.input, output = predictions)
现在是时候编译模型并为训练和测试数据集创建图像数据增强对象了:
# compile the model
model_final.compile(loss = "categorical_crossentropy", optimizer = optimizers.SGD(lr=0.0001, momentum=0.9), metrics=["accuracy"])
# Initiate the train and test generators with data Augumentation
train_datagen = ImageDataGenerator(
rescale = 1./255,
horizontal_flip = True,
fill_mode = "nearest",
zoom_range = 0.3,
width_shift_range = 0.3,
height_shift_range=0.3,
rotation_range=30)
test_datagen = ImageDataGenerator(
rescale = 1./255,
horizontal_flip = True,
fill_mode = "nearest",
zoom_range = 0.3,
width_shift_range = 0.3,
height_shift_range=0.3,
rotation_range=30)
现在我们将生成新的增强数据:
train_generator = train_datagen.flow_from_directory(
train_data_dir,
target_size = (img_height, img_width),
batch_size = batch_size,
class_mode = "categorical")
validation_generator = test_datagen.flow_from_directory(
validation_data_dir,
target_size = (img_height, img_width),
class_mode = "categorical")
# Save the model according to the conditions
checkpoint = ModelCheckpoint("vgg16_1.h5", monitor='val_acc', verbose=1, save_best_only=True, save_weights_only=False, mode='auto', period=1)
early = EarlyStopping(monitor='val_acc', min_delta=0, patience=10, verbose=1, mode='auto')
Found 120 images belonging to 2 classes.
Found 40 images belonging to 2 classes.
是时候为模型拟合新的最终层了:
model_final.fit_generator(
train_generator,
samples_per_epoch = nb_train_samples,
nb_epoch = epochs,
validation_data = validation_generator,
nb_val_samples = nb_validation_samples,
callbacks = [checkpoint, early])
Epoch 1/50
288/300 [===========================>..] - ETA: 2s - loss: 0.7809 - acc: 0.5000
/usr/local/lib/python3.5/dist-packages/Keras-1.2.2-py3.5.egg/keras/engine/training.py:1573: UserWarning: Epoch comprised more than `samples_per_epoch` samples, which might affect learning results. Set `samples_per_epoch` correctly to avoid this warning.
warnings.warn('Epoch comprised more than '
Epoch 00000: val_acc improved from -inf to 0.63393, saving model to vgg16_1.h5
304/300 [==============================] - 59s - loss: 0.7802 - acc: 0.4934 - val_loss: 0.6314 - val_acc: 0.6339
Epoch 2/50
296/300 [============================>.] - ETA: 0s - loss: 0.6133 - acc: 0.6385Epoch 00001: val_acc improved from 0.63393 to 0.80833, saving model to vgg16_1.h5
312/300 [===============================] - 45s - loss: 0.6114 - acc: 0.6378 - val_loss: 0.5351 - val_acc: 0.8083
Epoch 3/50
288/300 [===========================>..] - ETA: 0s - loss: 0.4862 - acc: 0.7986Epoch 00002: val_acc improved from 0.80833 to 0.85833, saving model to vgg16_1.h5
304/300 [==============================] - 50s - loss: 0.4825 - acc: 0.8059 - val_loss: 0.4359 - val_acc: 0.8583
Epoch 4/50
296/300 [============================>.] - ETA: 0s - loss: 0.3524 - acc: 0.8581Epoch 00003: val_acc improved from 0.85833 to 0.86667, saving model to vgg16_1.h5
312/300 [===============================] - 48s - loss: 0.3523 - acc: 0.8590 - val_loss: 0.3194 - val_acc: 0.8667
Epoch 5/50
288/300 [===========================>..] - ETA: 0s - loss: 0.2056 - acc: 0.9549Epoch 00004: val_acc improved from 0.86667 to 0.89167, saving model to vgg16_1.h5
304/300 [==============================] - 45s - loss: 0.2014 - acc: 0.9539 - val_loss: 0.2488 - val_acc: 0.8917
Epoch 6/50
296/300 [============================>.] - ETA: 0s - loss: 0.1832 - acc: 0.9561Epoch 00005: val_acc did not improve
312/300 [===============================] - 17s - loss: 0.1821 - acc: 0.9551 - val_loss: 0.2537 - val_acc: 0.8917
Epoch 7/50
288/300 [===========================>..] - ETA: 0s - loss: 0.0853 - acc: 0.9792Epoch 00006: val_acc improved from 0.89167 to 0.94167, saving model to vgg16_1.h5
304/300 [==============================] - 48s - loss: 0.0840 - acc: 0.9803 - val_loss: 0.1537 - val_acc: 0.9417
Epoch 8/50
296/300 [============================>.] - ETA: 0s - loss: 0.0776 - acc: 0.9764Epoch 00007: val_acc did not improve
312/300 [===============================] - 17s - loss: 0.0770 - acc: 0.9776 - val_loss: 0.1354 - val_acc: 0.9417
Epoch 9/50
296/300 [============================>.] - ETA: 0s - loss: 0.0751 - acc: 0.9865Epoch 00008: val_acc did not improve
312/300 [===============================] - 17s - loss: 0.0719 - acc: 0.9872 - val_loss: 0.1565 - val_acc: 0.9250
Epoch 10/50
288/300 [===========================>..] - ETA: 0s - loss: 0.0465 - acc: 0.9931Epoch 00009: val_acc did not improve
304/300 [==============================] - 16s - loss: 0.0484 - acc: 0.9901 - val_loss: 0.2148 - val_acc: 0.9167
Epoch 11/50
296/300 [============================>.] - ETA: 0s - loss: 0.0602 - acc: 0.9764Epoch 00010: val_acc did not improve
312/300 [===============================] - 17s - loss: 0.0634 - acc: 0.9744 - val_loss: 0.1759 - val_acc: 0.9333
Epoch 12/50
288/300 [===========================>..] - ETA: 0s - loss: 0.0305 - acc: 0.9931
现在我们用一朵水仙花图像来试一试。让我们测试分类器的输出,它应该输出一个接近[1.,0.]的数组,表示第一个选项的概率非常高:
im = cv2.resize(cv2.imread('test/gaff2.jpg'), (img_width, img_height))
im = np.expand_dims(im, axis=0).astype(np.float32)
im=preprocess_input(im)
out = model_final.predict(im)
print (out)
print (np.argmax(out))
[[ 1.00000000e+00 1.35796010e-13]]
0
因此,对于这类花朵,我们有一个非常明确的答案。你可以用新的图像进行实验,并用裁剪或扭曲的图像,甚至是相关类别的图像来测试模型,以检验其准确度水平。
参考文献
-
Fukushima, Kunihiko 和 Sei Miyake, 新认知机:一种用于视觉模式识别机制的自我组织神经网络模型. 神经网络中的竞争与合作。Springer, Berlin, Heidelberg, 1982. 267-285.
-
LeCun, Yann 等人. 将基于梯度的学习方法应用于文档识别. IEEE 86.11 (1998): 2278-2324.
-
Krizhevsky, Alex,Ilya Sutskever 和 Geoffrey E. Hinton,使用深度卷积神经网络进行 ImageNet 分类. 神经信息处理系统进展,2012.
-
Hinton, Geoffrey E. 等人, 通过防止特征检测器的共适应来改进神经网络. arXiv 预印本 arXiv:1207.0580 (2012).
-
Simonyan, Karen 和 Andrew Zisserman, 用于大规模图像识别的非常深的卷积神经网络. arXiv 预印本 arXiv:1409.1556 (2014).
-
Srivastava, Nitish 等人. Dropout:一种防止神经网络过拟合的简单方法. 机器学习研究杂志 15.1 (2014): 1929-1958.
-
Szegedy, Christian 等人, 重新思考计算机视觉中的 Inception 架构. IEEE 计算机视觉和模式识别会议论文集,2016.
-
He, Kaiming 等人, 用于图像识别的深度残差学习. IEEE 计算机视觉和模式识别会议论文集,2016.
-
Chollet, François, Xception: 深度学习中的深度可分离卷积. arXiv 预印本 arXiv:1610.02357 (2016).
摘要
本章提供了对媒体每天展示的令人惊叹的新应用中负责的一项技术的深刻见解。此外,通过提供的实际示例,你甚至能够创建新的定制解决方案。
由于我们的模型不足以解决非常复杂的问题,在下一章中,我们的范围将进一步扩大,将时间维度添加到我们泛化所包含的元素集中。
第七章:循环神经网络
在我们回顾了深度学习的最新发展之后,我们现在已经达到了机器学习的尖端,我们现在通过一系列称为 循环神经网络(RNNs) 的新算法,给我们的模型增加了一个非常特殊的维度(时间,因此是输入序列)。
解决有序问题——RNNs
在前面的章节中,我们考察了许多模型,从简单的到更复杂的,它们有一些共同的特性:
-
它们接受独特且独立的输入
-
它们有独特且固定大小的输出
-
输出将完全取决于当前输入的特征,而不依赖于过去或之前的输入
在现实生活中,大脑处理的信息具有固有的结构和顺序,我们感知到的每一个现象的组织和序列都会影响我们如何处理它们。这包括语音理解(句子中单词的顺序)、视频序列(视频中的帧顺序)和语言翻译。这促使新的模型被创造出来。其中最重要的模型被归类在 RNN 的范畴下。
RNN 定义
RNNs 是 人工神经网络(ANN)模型,其输入和输出都是序列。一个更正式的定义可以表达如下:
“一个 RNN 通过一个固定维度的多维向量(称为隐藏状态)来表示一个序列,该向量使用复杂的非线性函数结合新的观察结果。”
RNNs 具有高度的表达能力,可以实现任意内存限制的计算,因此,它们可以被配置以在困难的序列任务上实现非平凡的性能。
要建模的序列类型
RNNs 在输入和输出领域都使用序列模型。基于这一点,我们可以有所有可能的组合来解决不同类型的问题。在下面的图中,我们展示了该领域使用的主要架构,然后是递归架构的参考:

序列模式类型
RNN 的发展
RNNs 的起源与其他现代神经网络架构惊人地相似,可以追溯到 20 世纪 80 年代的 Hopfield 网络,但在 20 世纪 70 年代就有对应的网络。
循环架构第一次迭代的常见结构可以表示如下:

循环单元展开
经典的 RNN 节点具有与自身的循环连接,因此它们可以随着输入序列的进展而调整它们的权重。此外,在图右边的部分,你可以看到网络如何被展开以生成基于它内部保存的随机模型的一组输出。它以激活的形式存储最近输入事件的表示(短期记忆,与长期记忆相对,由缓慢变化的权重体现)。这对许多应用都具有重要意义,包括语音处理、音乐创作(如Mozer, 1992)、自然语言处理(NLP)以及许多其他领域。
训练方法——时间反向传播
在我们研究过的大量模型类型之后,你可能已经能够在训练步骤的实施中看到一种模式。
对于循环神经网络来说,最著名的误差最小化技术是众所周知的(对我们来说)反向传播方法的变体,其中有一个简单的元素——时间反向传播(BPTT)通过展开所有输入时间步来实现。每个时间步有一个输入时间步,整个网络的一个副本和一个输出。每个时间步计算并累积误差,最后网络被卷起,权重被更新。
在空间上,展开的循环神经网络中的每个时间步可以看作是一个额外的层,考虑到从一个时间步到另一个时间步的依赖性以及每个时间步的输出如何作为后续时间步的输入。这可能导致非常复杂的训练性能要求,因此,时间反向传播应运而生。
以下伪代码表示整个过程:
Unfold the network to contain k instances of the cell
While (error < ε or iteration>max):
x = zeros(sequence_legth)
for t in range (0, n-sequence_length) # initialize the weights
copy sequence_length input values into the input x
p = (forward-propagate the inputs over the whole unfolded network)
e = y[t+k] - p; # calculate error as target - prediction
Back-propagate the error e, back across the whole unfolded network
Sum the weight changes in the k model instances together.
Update all the weights in f and g.
x = f(x, a[t]); # compute new input for the next time-step
传统 RNNs 的主要问题——梯度爆炸和消失
然而,循环神经网络(RNNs)的训练证明是困难的,尤其是在具有复杂长距离时间结构的难题上——这正是 RNNs 应该最有用的设置。由于它们的潜力尚未得到实现,解决 RNNs 训练困难的方法非常重要。
然而,用于学习短期记忆内容的最广泛使用的算法,却花费了太多时间或者根本不起作用,尤其是在输入和相应的教师信号之间的最小时间延迟很长的情况下。尽管从理论上来说非常吸引人,但现有方法并没有在传统前馈网络之上提供明显的实际优势。
RNNs 中的一个主要问题出现在反向传播阶段。由于其循环性质,错误反向传播的步数对应一个非常深的网络。这种梯度计算的级联可能导致最后阶段非常微小的值,或者相反,参数不断增长且无界。这些现象被称为梯度消失和梯度爆炸。这也是 LSTM 被创造出来的原因之一。
传统 BPTT 的问题在于,随时间向后传播的错误信号往往会爆炸或消失——反向传播错误的时序演化指数依赖于权重的尺寸。它可能导致权重振荡,或者花费过多时间,或者根本不起作用。
作为解决梯度消失和梯度爆炸问题的一系列不同尝试的结果,最终在 1997 年,Schmidhuber 和 Sepp 发表了一篇关于 RNNs 和 LSTM 的基础论文,为该领域所有现代发展铺平了道路。
LSTM
LSTMs 是 RNNs 的一个基本步骤,因为它们将长期依赖引入到细胞中。展开的细胞包含两条不同的参数线:一条代表长期状态,另一条代表短期记忆。
在步骤之间,长期遗忘不那么重要的信息,并添加来自短期事件的过滤信息,将它们纳入未来。
LSTMs 在其可能的用途上非常灵活,它们是最常用的循环模型之一,与稍后我们将解释的 GRUs 一起。让我们尝试将 LSTM 分解为其组成部分,以更好地理解它们是如何工作的。
门和乘法操作
LSTMs 有两个基本值:记住现在的重要事情,并缓慢忘记过去的不重要事情。我们可以使用什么机制来应用这种过滤?这被称为 门 操作。
门操作基本上有一个多元向量输入和一个过滤器向量,该向量将与输入进行点乘,允许或拒绝从输入中传输的元素。我们如何调整这个门的过滤器?这个多元控制向量(在图中用箭头标记)与一个具有 sigmoid 激活函数的神经网络层相连。如果我们应用控制向量并通过 sigmoid 函数,我们将得到一个类似二进制的输出向量。
在下面的图中,门将由一系列开关表示:

LSTM 门
过程中的另一个重要部分是乘法,它将训练好的过滤器形式化,有效地通过一个包含的门来乘以输入。箭头图标表示过滤信息将流动的方向:

门乘法步骤
现在,是时候更详细地描述一个 LSTM 细胞了。
LSTM 有三个门来保护和控制细胞状态:一个在数据流的开始,另一个在中间,最后一个在细胞信息边界的末端。这个操作将允许丢弃(希望不是重要的)低重要性的状态数据,并将(希望重要的)新数据纳入状态。
以下图显示了单个 LSTM 细胞操作中的所有概念。作为输入,我们有以下内容:
-
细胞状态,它将存储长期信息,因为它携带从细胞训练起源的优化权重
-
短期状态,h(t),它将在每次迭代中直接与当前输入结合,因此它将对输入的最新值有更大的影响:

LSTM 细胞的描绘,包括所有其组成部分
现在,让我们探索这个 LSTM 单元上的数据流,以便更好地理解不同的门和操作如何在单个细胞中协同工作。
第一部分 — 设置要遗忘的值(输入门)
在这个阶段,我们将来自短期记忆的值与输入本身结合,然后这些值将输出一个二进制函数,由一个多变量 Sigmoid 表示。根据输入和短期记忆的值,Sigmoid 输出将过滤长期知识,由细胞状态的权重表示:

状态遗忘参数设置
第二部分 — 设置要保留的值
现在,是时候设置过滤器,允许或拒绝将新的和短期记忆纳入细胞的半永久状态。然后是设置过滤器,允许或拒绝将新的和短期记忆纳入细胞的半永久状态。
因此,在这个阶段,我们将确定多少新的和半新的信息将被纳入细胞的新状态:

短期值选择步骤
第三部分 — 应用到细胞的变化
在这个序列的这一部分,我们最终将通过我们一直在配置的信息过滤器,结果我们将有一个更新的长期状态。
为了使新的和短期信息归一化,我们通过具有tanh激活的神经网络传递新的输入和短期状态。这将使我们能够以归一化的[-1,1]范围提供新的信息:

状态变化持久步骤
第四部分 — 输出过滤后的细胞状态
现在,轮到短期状态了。它也将使用新的和之前的短期状态来设置过滤长期状态的过滤器,再次通过 tanh 函数点乘,以将信息归一化到(-1,1)范围:

新短期生成步骤
使用能源消耗数据进行单变量时间序列预测
在这个例子中,我们将解决回归领域的问题。因此,我们将构建一个包含两个 LSTMs 的多层 RNN。我们将进行的回归类型是多对一类型,因为网络将接收一系列能源消耗值,并尝试根据前四个记录输出下一个值。
我们将要工作的数据集是关于一个家庭在一段时间内电力消耗的许多测量的汇编。正如我们可能推断的那样,这种行为很容易遵循模式(当居民使用微波炉准备早餐并在白天使用电脑时,它会增加,下午稍微减少,然后在晚上随着所有灯光的增加而增加,最后当居民入睡时减少到零)。
让我们先设置适当的环境变量并加载所需的库:
%matplotlib inline
%config InlineBackend.figure_formats = {'png', 'retina'}
import numpy as np
import pandas as pd
import tensorflow as tf
from matplotlib import pyplot as plt
from keras.models import Sequential
from keras.layers.core import Dense, Activation
from keras.layers.recurrent import LSTM
from keras.layers import Dropout
Using TensorFlow backend.
数据集描述和加载
在这个例子中,我们将使用电力负荷图数据集,来自Artur Trindade (archive.ics.uci.edu/ml/datasets/ElectricityLoadDiagrams20112014)。这是原始数据集的描述:
"数据集没有缺失值。
数值以每 15 分钟的 kW 为单位。要将数值转换为 kWh,必须将数值除以 4。
每一列代表一个客户。有些客户是在 2011 年之后创建的。在这些情况下,消费被视为零。
所有时间标签都报告葡萄牙小时。然而,所有日子都有 96 个测量值(24*15)。每年 3 月时间变化日(只有 23 小时)凌晨 1:00 至 2:00 之间的值对所有点都是零。每年 10 月时间变化日(有 25 小时)凌晨 1:00 至 2:00 之间的值汇总了两个小时的消耗。"
为了简化我们的模型描述,我们只取了一个客户的完整测量数据,并将其格式转换为标准 CSV。它位于本章代码文件夹的data子文件夹中。
因此,我们将从数据集中加载一个样本家庭消费的前 1500 个值:
df = pd.read_csv("data/elec_load.csv", error_bad_lines=False)
plt.subplot()
plot_test, = plt.plot(df.values[:1500], label='Load')
plt.legend(handles=[plot_test])
下面的图表显示了我们需要建模的数据子集:

如果我们看一下这个表示(我们取了前 1500 个样本),我们可以看到一个初始瞬态状态,可能是当测量被放置时,然后我们看到一个非常清晰的消费水平高低的周期。从简单的观察中,我们还可以看到周期大约是 100 个样本,非常接近这个数据集每天 96 个样本。
数据集预处理
为了确保反向传播方法的更好收敛,我们应该尝试对输入数据进行归一化。因此,我们将应用经典的缩放和居中技术,减去平均值,并按最大值的floor()进行缩放。为了获取所需值,我们使用 pandas 的describe()方法:
print(df.describe())
array=(df.values - 145.33) /338.21
plt.subplot()
plot_test, = plt.plot(array[:1500], label='Normalized Load')
plt.legend(handles=[plot_test])
Load
count 140256.000000
mean 145.332503
std 48.477976
min 0.000000
25% 106.850998
50% 151.428571
75% 177.557604
max 338.218126
这是我们的归一化数据的图表:

在这一步中,我们将准备我们的输入数据集,因为我们需要一个输入x(前 5 个值)以及相应的输入y(5 个时间步后的值)。然后,我们将前 13,000 个元素分配给训练集,然后我们将接下来的 1,000 个样本分配给测试集:
listX = []
listy = []
X={}
y={}
for i in range(0,len(array)-6):
listX.append(array[i:i+5].reshape([5,1]))
listy.append(array[i+6])
arrayX=np.array(listX)
arrayy=np.array(listy)
X['train']=arrayX[0:13000]
X['test']=arrayX[13000:14000]
y['train']=arrayy[0:13000]
y['test']=arrayy[13000:14000]
现在,我们将构建模型,它将是一个具有每个末尾都有 dropout 层的双 LSTM:
#Build the model
model = Sequential()
model.add(LSTM( units=50, input_shape=(None, 1), return_sequences=True))
model.add(Dropout(0.2))
model.add(LSTM( units=200, input_shape=(None, 100), return_sequences=False))
model.add(Dropout(0.2))
model.add(Dense(units=1))
model.add(Activation("linear"))
model.compile(loss="mse", optimizer="rmsprop")
现在是运行模型并调整权重的时候了。模型适配器将使用数据集的 8%作为验证集:
#Fit the model to the data
model.fit(X['train'], y['train'], batch_size=512, epochs=10, validation_split=0.08)
Train on 11960 samples, validate on 1040 samples
Epoch 1/10
11960/11960 [==============================] - 41s - loss: 0.0035 - val_loss: 0.0022
Epoch 2/10
11960/11960 [==============================] - 61s - loss: 0.0020 - val_loss: 0.0020
Epoch 3/10
11960/11960 [==============================] - 45s - loss: 0.0019 - val_loss: 0.0018
Epoch 4/10
11960/11960 [==============================] - 29s - loss: 0.0017 - val_loss: 0.0020
Epoch 5/10
11960/11960 [==============================] - 30s - loss: 0.0016 - val_loss: 0.0015
Epoch 6/10
11960/11960 [==============================] - 28s - loss: 0.0015 - val_loss: 0.0013
Epoch 7/10
11960/11960 [==============================] - 43s - loss: 0.0014 - val_loss: 0.0012
Epoch 8/10
11960/11960 [==============================] - 37s - loss: 0.0013 - val_loss: 0.0013
Epoch 9/10
11960/11960 [==============================] - 31s - loss: 0.0013 - val_loss: 0.0012
Epoch 10/10
11960/11960 [==============================] - 25s - loss: 0.0012 - val_loss: 0.0011
<keras.callbacks.History at 0x7fa435512588>
在重新缩放后,是时候看看我们的模型如何预测值与实际测试值相比,这些值没有参与模型的训练,以了解模型如何泛化样本住宅的行为:
# Rescale the test dataset and predicted data
test_results = model.predict( X['test'])
test_results = test_results * 338.21 + 145.33
y['test'] = y['test'] * 338.21 + 145.33
plt.figure(figsize=(10,15))
plot_predicted, = plt.plot(test_results, label='predicted')
plot_test, = plt.plot(y['test'] , label='test');
plt.legend(handles=[plot_predicted, plot_test]);

最终回归数据
摘要
在这一章中,我们的范围进一步扩大,将时间维度添加到我们想要包含在泛化中的元素集合中。此外,我们还学习了如何基于真实数据使用 RNN 解决实际问题。
但如果你认为你已经涵盖了所有可能的选项,还有许多更多的模型类型可以查看!
在下一章中,我们将讨论可以训练以产生非常聪明元素的尖端架构,例如,将著名画家的风格转移到图片上,甚至玩电子游戏!继续阅读以了解强化学习和生成对抗网络。
参考文献
-
Hopfield, John J,具有涌现集体计算能力的神经网络和物理系统。美国国家科学院院刊 79.8(1982):2554-2558。
-
Bengio, Yoshua,Patrice Simard 和 Paolo Frasconi,使用梯度下降学习长期依赖性是困难的。IEEE 神经网络杂志 5.2(1994):157-166。
-
Hochreiter, Sepp 和 Jürgen Schmidhuber,长短期记忆。神经计算 9.8(1997):1735-1780。
-
Hochreiter, Sepp。循环神经网络学习和梯度消失。国际不确定性和模糊知识系统杂志 6.2(1998):107-116。
-
Sutskever, Ilya,训练循环神经网络。多伦多大学,多伦多,安大略省,加拿大(2013)。
-
Chung, Junyoung, 等人,门控循环神经网络在序列建模中的实证评估。arXiv 预印本 arXiv:1412.3555(2014)。
第八章:近期模型和发展
在前几章中,我们探讨了机器学习模型的大量训练机制,从简单的传递机制开始,如众所周知的前馈神经网络。然后我们查看了一个更复杂且与现实世界相关的机制,接受一个确定的输入序列作为训练输入,即循环神经网络(RNNs)。
现在是时候看看两个最近结合了现实世界其他方面的参与者了。在第一种情况下,我们不仅有一个优化其模型的单一网络,还有一个参与者,他们将相互改进各自的结果。这是生成对抗网络(GANs)的情况。
在第二种情况下,我们将讨论一种不同类型的模型,它将尝试确定最大化奖励的最佳步骤集:强化学习。
GANs
GANs 是一种新的无监督学习模型,是过去十年中为数不多的颠覆性模型之一。它们有两个模型在迭代过程中相互竞争和改进。
该架构最初基于监督学习和博弈论,其主要目标是基本学会从同一类别的原始数据集中生成真实样本。
值得注意的是,关于 GAN 的研究数量几乎呈指数级增长,如下面的图表所示:

LSGAN 创建的模型
另一个非常有趣的例子是使用即插即用生成网络(PPGN)进行类条件图像采样,以填充真实 227 x 227 图像中的 100 x 100 缺失像素。
以下截图比较了 PPGN 变体和等效的 Photoshop 图像完成效果:

PPGN 生成的火山样本
以下截图说明了视觉概念的向量算术过程。它允许使用代表图像中对象的操作数,并可以在特征空间中添加或删除它们:
特征空间的向量算术操作
判别性和生成性模型
要理解对抗网络的概念,我们首先定义在典型的 GAN 设置中相互作用的两个模型:
-
生成器:这个模型的任务是接受来自标准随机分布(例如,来自 n 维高斯分布的样本)的样本,并产生一个看起来可能来自与XX相同分布的点。可以说,生成器想要欺骗判别器输出 1。从数学上讲,它学习一个将输入数据(x)映射到某些期望输出类别标签(y)的函数。在概率论术语中,它学习输入数据的条件分布P(y|x)。它区分两个(或更多)不同的数据类别——例如,一个训练后输出 1 的卷积神经网络,给定人脸图像时输出 1,否则输出 0。
-
判别器:这个模型的任务是区分来自真实数据和生成器生成的虚假数据。每个模型都会试图打败对方(生成器的目标是欺骗判别器,判别器的目标是不要被生成器欺骗):

GAN 的训练过程
更正式地说,生成器试图同时学习输入数据和标签的联合概率,即P(x, y)。因此,它也可以用于其他目的,例如创建可能的新(x, y)样本。它对数据的类别一无所知。相反,它的目的是生成符合训练数据分布的新数据。
在一般情况下,生成器和判别器都是神经网络,并且它们交替训练。它们各自的目标可以表示为一个我们可以通过梯度下降进行优化的损失函数。
最终结果是两个模型同时改进;生成器产生更好的图像,判别器在判断生成的样本是否为假样本方面变得更好。在实践中,最终结果是产生真正优秀和逼真的新样本(例如,自然环境的随机图片)的模型。
总结,GANs 的主要收获是:
-
GANs 是使用监督学习来近似不可处理的成本函数的生成模型
-
GANs 可以模拟许多成本函数,包括用于最大似然估计的那个
-
在高维、连续、非凸游戏中找到纳什均衡是一个重要的未解研究问题
-
GANs 是 PPGNs 的关键组成部分,能够从不同的图像类别生成引人注目的高分辨率样本
强化学习
强化学习是一个最近重新出现的领域,它在控制、寻找游戏和情境问题的解决方案等领域变得更加流行,在这些领域中,需要执行多个步骤来解决一个问题。
强化学习的一个正式定义如下:
“强化学习是代理必须通过与动态环境进行试错交互来学习行为所面临的问题。”(Kaelbling 等人,1996 年)。
为了有一个参考框架来解决问题类型,我们将首先回到 20 世纪 50 年代发展起来的一个数学概念,称为马尔可夫决策过程。
马尔可夫决策过程
在解释强化学习技术之前,我们将解释我们将用它们解决的问题类型。
当谈论强化学习时,我们想要优化马尔可夫决策过程的问题。它由一个数学模型组成,有助于在部分结果随机、部分受代理控制的决策情境中进行决策。
该模型的主要元素是一个代理、一个环境和一个状态,如下面的图所示:

强化学习过程的简化方案
代理可以执行某些动作(例如将桨向左或向右移动)。这些动作有时会导致奖励 r[t],这可以是正的或负的(例如得分的增加或减少)。动作会改变环境,并可能导致新的状态 s[t+1],其中代理可以执行另一个动作 a[t+1]。状态、动作和奖励的集合,以及从一个状态转换到另一个状态的规则,共同构成了马尔可夫决策过程。
决策元素
为了理解这个问题,让我们将自己置于问题解决环境中,并查看主要元素:
-
状态的集合
-
要采取的动作是从一个地方移动到另一个地方
-
奖励函数是表示边的值
-
策略是完成任务的方式
-
折扣因子,它决定了未来奖励的重要性
与传统的监督学习和无监督学习形式相比,主要区别在于计算奖励所需的时间,在强化学习中不是瞬时的;它是在一系列步骤之后到来的。
因此,下一个状态取决于当前状态和决策者的动作,状态不依赖于所有先前状态(它没有记忆),因此它符合马尔可夫属性。
由于这是一个马尔可夫决策过程,状态 s[t+1] 的概率只取决于当前状态 s[t] 和动作 a[t]:

展开式强化机制
整个过程的目标是生成一个策略 P,以最大化奖励。训练样本是元组,<s, a, r>。
优化马尔可夫过程
强化学习是代理与环境之间的迭代交互。在每个时间步发生以下情况:
-
处于某个状态时,决策者可以选择在该状态下可用的任何动作
-
在下一个时间步,过程通过随机移动到新状态并给予决策者相应的奖励来响应
-
过程进入其新状态的概率受所选动作的影响,这种影响以状态转换函数的形式出现
基本 RL 技术:Q 学习
最著名的强化学习技术之一,也是我们将在示例中实现的技术,是Q 学习。
Q 学习可用于在有限马尔可夫决策过程中找到任何给定状态的最佳动作。Q 学习试图最大化表示在状态s执行动作a时最大折现未来奖励的 Q 函数的值。
一旦我们知道了 Q 函数,状态s中的最佳动作a就是具有最高 Q 值的动作。然后我们可以定义一个策略π(s),它给出任何状态的最佳动作,如下所示:

我们可以用下一个点的 Q 函数(s[t+1], a[t+1], r[t+1], s[t+2])来定义转换点(s[t], a[t], r[t], s[t+1])的 Q 函数,类似于我们处理总折现未来奖励的方式。这个方程被称为Q 学习的贝尔曼方程:

在实践中,我们可以将 Q 函数视为一个查找表(称为Q 表),其中状态(用s表示)是行,动作(用a表示)是列,而元素(用Q(s, a)表示)是在给定行的状态下采取给定列的动作所获得的奖励。在任何状态下采取的最佳动作是具有最高奖励的动作:
initialize Q-table Q
observe initial state s
while (! game_finished):
select and perform action a
get reward r
advance to state s'
Q(s, a) = Q(s, a) + α(r + γ max_a' Q(s', a') - Q(s, a))
s = s'
你会意识到该算法基本上是在贝尔曼方程上进行随机梯度下降,通过状态空间(或剧集)回传奖励,并在许多试验(或时代)上平均。在这里,α是学习率,它决定了先前 Q 值和折现后的新最大 Q 值之间差异的多少应该被纳入。
我们可以用以下流程图来表示这个过程:

参考文献
-
贝尔曼,理查德,马尔可夫决策过程。数学与力学杂志(1957 年):679-684。
-
凯尔布林,莱斯利·帕克,迈克尔·L·利特曼,和安德鲁·W·摩尔,强化学习:综述。人工智能研究杂志 4(1996 年):237-285。
-
古德费洛,伊恩,等人,生成对抗网络,神经信息处理系统进展,2014 年
-
拉德福德,亚历克,卢克·梅茨,和索乌米特·奇塔拉,使用深度卷积生成对抗网络进行无监督表示学习。arXiv 预印本 arXiv:1511.06434(2015 年)。
-
伊索拉,菲利普,等人,条件对抗网络进行图像到图像的翻译。arXiv 预印本 arXiv:1611.07004(2016 年)。
-
Mao, Xudong, et al., 最小二乘生成对抗网络。arXiv 预印本 ArXiv:1611.04076 (2016)。
-
Eghbal-Zadeh, Hamid, 和 Gerhard Widmer, 生成对抗网络的似然估计。arXiv 预印本 arXiv:1707.07530 (2017)。
-
Nguyen, Anh, et al., 即插即用生成网络:潜在空间中条件迭代生成图像。arXiv 预印本 arXiv:1612.00005 (2016)。
摘要
在本章中,我们回顾了最近出现的最重要和最具创新性的两种架构。每天都有新的生成和强化模型以创新的方式被应用,无论是从已知类别的选择中生成可行的新元素,甚至是在策略游戏中战胜专业玩家。
在下一章中,我们将提供精确的说明,以便您可以使用和修改提供的代码,更好地理解您在整本书中学到的不同概念。
第九章:软件安装和配置
因此,现在是时候开始我们的告别了!在本章中,我们将详细说明构建我们的开发环境的所有指令。
在本章中,你将了解以下内容:
-
Linux 上的 Anaconda 和 pip 环境安装
-
macOS 上的 Anaconda 和 easy install 环境安装
-
Windows 上的 Anaconda 环境安装
因此,让我们从 Linux 的逐步说明开始。
Linux 安装
在我们看来,Linux 是你可以用于机器学习项目的最灵活的平台。正如你可能知道的,Linux 领域有大量的替代品,它们都有自己的特定包管理。
由于为所有这些发行版编写说明将占用大量页面,我们将专注于 Ubuntu 16.04 发行版的说明。
Ubuntu 无疑是最广泛的 Linux 发行版,在 Ubuntu 16.04 的特定情况下,它是一个 LTS 版本,即长期支持。这意味着我们将在本书中运行的基软件将得到直到 2021 年的支持。
你可以在 wiki.ubuntu.com/LTS 上找到关于 LTS 意义的更多信息。
关于 Ubuntu 作为科学计算发行版的可行性,尽管许多人认为它是面向新手的,但它提供了当前机器学习环境所需的所有技术的必要支持,并且拥有一个非常庞大的用户基础。
本章中的说明与基于 Debian 的发行版的说明非常相似;它们将需要最少的或没有更改即可工作。
初始发行版要求
对于 Python 环境的安装,你需要以下东西:
-
一种能够执行 AMD64 指令的计算机(通常称为 64 位处理器)
-
在云上运行的基于 AMD64 的镜像
在 AWS 上,一个合适的 Amazon Machine Image(AMI)是代码 ami-cf68e0d8。
在 Linux 上安装 Anaconda
安装 Python 发行版的一个非常流行的方式是通过软件包 Anaconda。它包含了一个完整的高性能 Python、Scala 和 R 生态系统,包括数据科学中最著名的包。它还包括通过 conda 可用的众多其他包,conda 是该包的主要实用工具,负责管理环境、包和依赖关系。
Anaconda 由 Continuum Analytics(continuum.io)构建和分发,该公司还维护这些包及其依赖关系。
为了安装 Anaconda,让我们首先下载安装程序包,写作时的版本为 4.2.0。
你可以在 www.anaconda.com/download/ 找到最新的包版本。
查看以下步骤以在 Linux 上安装 Anaconda:
- 让我们运行以下命令:
curl -O https://repo.continuum.io/archive/Anaconda3-4.2.0-Linux-
x86_64.sh
上述命令将生成以下输出:

- 然后,我们需要验证包的数据完整性,使用校验和或 SHA-256 类型。执行此操作的 Linux 命令是
sha256sum,如下所示:
sha256sum Anaconda3-4.4.0-Linux-x86_64.sh
上述命令将生成以下输出:

- 然后,让我们使用
bash解释器执行安装程序,如下所示:
bash Anaconda3-4.2.0-Linux-x86_64.sh
上述命令将生成以下输出:

- 按下Enter后,我们将看到许可协议,阅读后你可以通过输入
yes来接受,如下截图所示:

- 然后,是时候选择位置并开始安装所有包,如下截图所示:

- 在此之后,我们将被要求将已安装的 Anaconda 安装添加到路径中,主要是为了将库和二进制文件,特别是
conda实用工具,集成到系统中。然后安装完成,如下截图所示:

- 让我们通过运行以下命令测试当前的 Anaconda 安装:
source ~/.bashrc
conda list
上述命令将生成以下输出:

- 现在是时候通过运行以下命令创建一个 Python 3 环境:
conda create --name ml_env python=3
上述命令将生成以下输出:

- 要激活此新环境,让我们通过运行以下命令使用
source命令:
source activate ml_env
上述命令将生成以下输出:

- 当你的环境激活后,你的命令提示符前缀将如下所示:
python --version
上述命令将生成以下输出:

- 当你不再想使用该环境时,运行以下命令:
source deactivate
上述命令将生成以下输出:

- 如果你想要检查所有 conda 环境,你可以使用以下 conda 命令:
conda info --envs
上述命令将生成以下输出:

星号(*)表示当前活动环境。
- 通过运行以下命令安装额外的包:
conda install --name ml_env numpy
上述命令将生成以下输出:

- 要删除一个环境,你可以使用以下命令:
conda remove --name ml_env --all
- 添加剩余的库:
conda install tensorflow conda install -c conda-forge keras
pip Linux 安装方法
在本节中,我们将使用 pip(pip安装包)包管理器来安装项目所需的所有库。
Pip 是 Python 的默认包管理器,拥有大量的可用库,包括几乎所有主要的机器学习框架。
安装 Python 3 解释器
Ubuntu 16.04 默认解释器是 Python 2.7。因此,我们的第一步将是安装 Python 3 解释器和所需的库:
sudo apt-get install python3
安装 pip
为了安装pip包管理器,我们将使用 Ubuntu 的本地apt-get包管理器安装python3-pip包:
sudo apt-get install python3-pip
安装必要的库
执行以下命令来安装剩余的必要库。其中许多库是本书中实际示例所必需的:
sudo pip3 install pandas
sudo pip3 install tensorflow
sudo pip3 install keras
sudo pip3 install h5py
sudo pip3 install seaborn
sudo pip3 install jupyter
macOS X 环境安装
现在轮到 macOS X 安装了。安装过程与 Linux 非常相似,并且基于OS X High Sierra版本。
安装需要安装用户的sudo权限。
Anaconda 安装
Anaconda 可以通过图形安装程序或基于控制台的安装程序进行安装。在本节中,我们将介绍图形安装程序。首先,我们将从www.anaconda.com/download/下载安装程序包,并选择 64 位包:

一旦我们下载了安装程序包,我们将执行安装程序,并出现逐步的图形用户界面:

然后,我们选择安装位置(请注意,整个包需要大约 2 GB 的磁盘空间来安装):

首先,我们接受许可协议,然后实际安装所有必需的文件:

在完成必要的文件解压缩和安装过程后,我们就可以开始使用 Anaconda 工具了:

最后一步将是使用conda命令安装 Anaconda 分布中缺少的包:
conda install tensorflow conda install -c conda-forge keras
安装 pip
在本节中,我们将使用包含在setuptoolsPython 包中的easy_install包管理器安装pip包管理器,它是操作系统默认包含的。
对于这个过程,我们将在终端中执行以下命令:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ sudo brew install python3
通过 pip 安装剩余库
然后是安装所有剩余库的时候了:
sudo pip3 install pandas
sudo pip3 install tensorflow
sudo pip3 install keras
sudo pip3 install h5py
sudo pip3 install seaborn
sudo pip3 install jupyter
因此,Mac 的安装过程到此结束;让我们继续 Windows 安装过程。
Windows 安装
Windows 是一个 Python 可以无问题运行的平台。在本节中,我们将介绍在 Windows 平台上安装 Anaconda。
Anaconda Windows 安装
安装 Anaconda 的过程与 macOS 的非常相似,因为使用了图形安装程序。让我们首先从www.anaconda.com/download/下载安装程序包,并选择 64 位包:

下载安装程序后,接受许可协议,然后进入下一步:

然后,你可以选择为当前用户安装平台,或者为所有用户安装:

然后,你选择整个安装的安装目录。记住,这将占用接近 2 GB 的磁盘空间来安装:

环境安装完成后,你将在主 Windows 菜单中找到 Jupyter Notebook 快捷方式:

为了使用 Python 命令和 conda 工具,有一个方便的 Anaconda 提示符,它将加载所需的路径和环境变量:

最后一步是从 Anaconda 提示符中执行以下 conda 命令来安装缺失的包:
conda install tensorflow conda install -c conda-forge keras
摘要
恭喜!你已经到达了本节关于机器学习基本原理的实践总结的结尾。在本章的最后,我们介绍了很多帮助你构建机器学习计算环境的方法。
我们想借此机会真诚地感谢您仔细阅读,希望您觉得我们提供的材料有趣且引人入胜。希望你现在已经准备好开始利用我们提供的工具以及不断开发的新工具,以及我们努力提供的知识,去解决新的挑战性问题。
对于我们来说,编写这本书并寻找帮助您以实用方式理解概念的最佳方法是一段愉快的经历。不要犹豫,向出版商提供的渠道发送问题、建议或错误报告。
最好的祝愿,祝您学习愉快!



浙公网安备 33010602011771号