Python 机器学习系统设计指南(二)
七、特征——算法如何看待世界
到目前为止,在本书中,我们为创建、提取或以其他方式操作特征提出了许多方法和理由。在这一章中,我们将直接讨论这个话题。正确的特征,有时被称为属性 T2,是机器学习模型的核心组成部分。一个有错误特征的复杂模型是没有价值的。特征是我们的应用看待世界的方式。对于除了最简单的任务之外的所有任务,我们将在将特征输入模型之前对其进行处理。我们可以通过许多有趣的方式来做到这一点,这是一个非常重要的话题,用整整一章来讨论它是合适的。
直到最近十年左右,机器学习模型才例行公事地使用成千上万甚至更多的特征。这使我们能够解决许多不同的问题,例如与样本数量相比,我们的特征集很大的问题。两个典型的应用是遗传分析和文本分类。对于遗传分析,我们的变量是一组基因表达系数。这些是基于样本中存在的基因的数量,例如,取自组织活检。可以执行分类任务来预测患者是否患有癌症。训练样本和测试样本的总数可以小于 100。另一方面,原始数据中的变量数量可能在 6000 到 60000 之间。这不仅会转化为大量的特征,还意味着特征之间的取值范围也相当大。在本章中,我们将涵盖以下主题:
- 特征类型
- 运营和统计
- 结构化特征
- 转变特征
- 主成分分析
特征类型
有三种不同类型的特征:数量特征、顺序特征和分类特征。我们也可以考虑第四种类型的特征——布尔型,因为这种类型确实有一些不同的性质,尽管它实际上是一种分类特征。这些特征类型可以根据它们传递的信息量来排序。数量特征具有最高的信息容量,其次是序数、分类和布尔。
让我们看一下表格分析:
|特征类型
|
命令
|
规模
|
趋势
|
散布
|
形状
| | --- | --- | --- | --- | --- | --- | | 定量 | 是 | 是 | 均值 | 范围、方差和标准偏差 | 偏斜度、峰度 | | 序数 | 是 | 不 | 中位数 | 有多少人 | 钠 | | 分类 | 不 | 不 | 方式 | 钠 | 钠 |
上表显示了三种类型的功能、它们的统计数据和属性。每个要素从表中下一行的要素继承统计信息。例如,定量特征的中心趋势测量包括中位数和众数。
数量特征
数量特征的区别特征是连续的,通常涉及到映射到实数。通常,特征值可以映射到实数的子集,例如,以年表示年龄;但是,在计算统计数据(如平均值或标准偏差)时,必须注意使用满刻度。因为定量特征有一个有意义的数字标度,所以它们经常用在几何模型中。当在树模型中使用它们时,它们会导致二进制拆分,例如,使用阈值,其中高于阈值的值归一个子级,等于或低于阈值的值归另一个子级。树模型对比例的单调变换不敏感,即不改变特征值顺序的变换。例如,对于树模型来说,如果我们以厘米或英寸为单位测量长度,或者使用对数或线性比例,这并不重要,我们只需将阈值更改为相同的比例。树模型忽略了数量特征的尺度,将它们视为序数。基于规则的模型也是如此。对于概率模型,如朴素贝叶斯分类器,需要将定量特征离散到有限数量的箱中,并因此转换为分类特征。
序数特征
序数要素是具有不同顺序但没有比例的要素。它们可以被编码为整数值;然而,这样做并不意味着任何规模。一个典型的例子是门牌号。在这里,我们可以通过门牌号码来辨别一所房子在街道上的位置。我们假设 1 号房会在 20 号房之前,10 号和 11 号房会相互靠近。然而,数字的大小并不意味着任何规模;例如,没有理由相信 20 号房会比 1 号房大。序数特征的域是完全有序的集合,例如一组字符或字符串。因为序数特征缺乏线性标度,所以加或减没有意义;因此,对序数要素进行平均等操作通常没有意义,也不会产生任何关于要素的信息。类似于树模型中的数量特征,序数特征导致二元分割。一般来说,序数特征在大多数几何模型中不容易使用。例如,线性模型假设一个欧几里德实例空间,其中的特征值被视为笛卡尔坐标。对于基于距离的模型,如果我们将它们编码为整数,并且它们之间的距离只是它们之间的差异,那么我们可以使用序数特征。这有时被称为 海明距离。
分类特征
分类特征,有时被称为名义特征,没有任何顺序或比例,因此,除了表示一个值最频繁出现的模式之外,不允许任何统计汇总。分类特征通常最好用概率模型来处理;但是,它们也可以在基于距离的模型中使用汉明距离,并且对于相等的值将距离设置为 0,对于不相等的值将距离设置为 1。分类特征的一个子类型是**布尔特征,**映射为真或假的布尔值。
操作和统计
特征可以通过可对其执行的允许操作来定义。考虑两个特征:一个人的年龄和电话号码。虽然这两个特征都可以用整数来描述,但它们实际上代表了两种截然不同的信息类型。当我们看到哪些操作可以有效地执行时,这是显而易见的。比如计算一群人的平均年龄,会给我们一个有意义的结果;计算平均电话号码不会。
我们可以把可以对一个特征执行的可能计算的范围称为它的统计量。这些统计数据描述了数据的三个不同方面。这些是——它的中心倾向,它的 T3】分散,它的 T6】形状。
为了计算数据的中心趋势,我们通常使用以下一个或多个统计量:平均值(或平均值)、中值(或有序列表中的中间值)和模式(或所有值的大多数)。模式是唯一可以应用于所有数据类型的统计信息。为了计算中位数,我们需要能够以某种方式排序的特征值,即序数或数量。为了计算平均值,数值必须以某种比例表示,如线性比例。换句话说,它们需要数量特征。
最常见的计算离差的方式是通过方差或标准差的统计。这两者实际上是相同的度量,但尺度不同,标准差很有用,因为它与要素本身的尺度相同。此外,请记住,平均值和中间值之间的绝对差异永远不会大于标准偏差。测量离差的一个更简单的统计量是范围,它只是最小值和最大值之间的差值。当然,从这里,我们可以通过计算中距点来估计特征的中心趋势。测量离差的另一种方法是使用百分位数或十分位数等单位来测量低于特定值的实例的比率。例如,p百分位是 p 百分比实例低于的值。
测量形状统计稍微复杂一点,可以使用样本的 中心时刻的概念来理解。其定义如下:
这里, n 为样本数, μ 为样本均值, k 为整数。当 k = 1 时,第一个中心矩为 0 ,因为这只是平均值的平均偏差,始终为 0 。第二个中心矩是与平均值的平均平方偏差,即方差。我们可以定义偏斜度如下:
这里 ơ 是标准差。如果这个公式给出的值是正的,那么更多的情况下值高于平均值而不是低于平均值。当绘制图表时,数据向右倾斜。当偏斜为负时,反之亦然。
我们可以将峰度定义为第四中心时刻的类似关系:
可以证明正态分布的峰度为 3。在高于这个值时,分布将更多达到峰值。峰度值低于 3 时,分布将更平坦*。*
*我们之前讨论了三种类型的数据,即分类数据、顺序数据和数量数据。
机器学习模型将以非常不同的方式对待不同的数据类型。例如,分类特征上的决策树分裂将产生与值一样多的子代。对于序数和数量特征,分割将是二元的,每个父母基于一个阈值产生两个孩子。因此,树模型将数量特征视为序数,而忽略特征尺度。当我们考虑像贝叶斯分类器这样的概率模型时,我们可以看到它实际上将序数特征视为分类特征,而它处理数量特征的唯一方法是将它们转化为有限数量的离散值,从而将其转换为分类数据。
一般来说,几何模型需要定量的特征。例如,线性模型在欧几里得实例空间中运行,要素充当笛卡尔坐标。每个要素值都被视为与其他要素值的标量关系。基于距离的模型,如 k-最近邻模型,可以通过将相等值的距离设置为 0,不相等值的距离设置为 1 来合并分类特征。同样,我们可以通过计算两个值之间的值的数量,将序数特征纳入基于距离的模型。如果我们将特征值编码为整数,那么距离就是简单的数值差。通过选择适当的距离度量,可以将序数和分类特征结合到基于距离的模型中。
结构化特征
我们假设每个实例可以表示为特征值的向量,并且所有相关方面都由该向量表示。这有时被称为 抽象,因为我们过滤掉不必要的信息,用向量来表示现实世界的现象。例如,将列夫·托尔斯泰的全部作品表示为词频向量是一种抽象。我们不假装这个抽象将服务于任何超过一个非常特殊的有限的应用。我们可能会了解到托尔斯泰对语言的使用,也许会引出一些关于托尔斯泰创作的情感和主题的信息。然而,我们不太可能对这些作品中描绘的 19 世纪俄国的广阔背景有任何重要的了解。一个人类读者,或者一个更复杂的算法,将不是从每个单词的计数中,而是从这些单词的结构中获得这些见解。
我们可以用类似于在数据库编程语言(如 SQL)中考虑查询的方式来考虑结构化特征。一个 SQL 查询可以表示一个变量的集合,用来做一些事情,比如找到一个特定的短语或者找到所有涉及特定字符的段落。我们在机器学习环境中所做的是用这些集合属性创建另一个特征。
结构化特征可以在构建模型之前创建,也可以作为模型本身的一部分创建。在第一种情况下,这个过程可以理解为从一阶逻辑到命题逻辑的转换。这种方法的一个问题是,由于与现有功能的组合,它可能会导致潜在功能数量的激增。另一个要点是,正如在 SQL 中一个子句可以覆盖另一个子句的子集一样,结构特征也可以是逻辑相关的。这在特别适合自然语言处理的机器学习分支中被利用,称为归纳逻辑编程。
变换特征
当我们转换特征时,我们的目标显然是让它们对我们的模型更有用。这可以通过添加、删除或更改特征所表示的信息来实现。一个共同特征变换是改变特征类型的变换。一个典型的例子是二值化,即将一个分类特征转化为一组二值特征。另一个例子是将序数特征变为分类特征。在这两种情况下,我们都会丢失信息。首先,单个分类特征的值是互斥的,这不是通过二进制表示来表达的。在第二种情况下,我们丢失了订购信息。这些类型的转换可以被认为是归纳性的,因为它们由一个定义明确的逻辑过程组成,除了决定首先执行这些转换之外,它不涉及客观的选择。
使用sklearn.preprocessing.Binarizer模块可以轻松进行二值化。让我们来看看以下命令:
from sklearn.preprocessing import Binarizer
from random import randint
bin=Binarizer(5)
X=[randint(0,10) for b in range(1,10)]
print(X)
print(bin.transform(X))
以下是上述命令的输出:
分类特征通常需要编码成整数。考虑一个非常简单的数据集,只有一个分类特征,城市,有三个可能的值,悉尼、珀斯和墨尔本,我们决定将这三个值分别编码为 0、1 和 2。如果这个信息要用于线性分类器,那么我们把约束写成一个带有权重参数的线性不等式。然而,问题是这个权重不能编码为三路选择。假设我们有两个类,东海岸和西海岸,我们需要我们的模型提出一个决策函数,该函数将反映珀斯位于西海岸,悉尼和墨尔本都位于东海岸的事实。用一个简单的线性模型,当特征以这种方式编码时,那么决策函数就不能得出一个将悉尼和墨尔本放在同一个类中的规则。解决方案是将特征空间放大为三个特征,每个特征都有自己的权重。这个叫做一热编码。Sciki-learn 实现OneHotEncoder()功能来执行该任务。这是一个估计器,它将每个分类特征的 m 个可能值转换成 m 个二进制特征。假设我们使用的数据模型由前面示例中描述的城市特征和另外两个特征组成——性别(可以是男性或女性)和职业(可以有三个值——医生、律师或银行家)。因此,举例来说,来自悉尼的女银行家将被表示为*【1,2,0】*。为以下示例添加了另外三个示例:
from sklearn.preprocessing import OneHotEncoder
enc = OneHotEncoder()
enc.fit([[1,2,0], [1, 1, 0], [0, 2, 1], [1, 0, 2]])
print(enc.transform([1,2,0]).toarray())
我们将获得以下输出:
由于我们在这个数据集中有两个性别、三个城市和三个工作,所以变换数组中的前两个数字代表性别,后三个数字代表城市,最后三个数字代表职业。
离散化
我已经简要地提到了与决策树相关的阈值化的思想,其中我们通过找到合适的特征值来分割,从而将序数或数量特征转换为二进制特征。有许多方法,既有监督的,也有无监督的,可用于在连续数据中找到适当的分割,例如,使用中心趋势(监督的)统计,如平均值或中值,或基于信息增益等标准优化目标函数。
我们可以更进一步,创建多个阈值,将一个数量特征转化为一个序数特征。这里,我们将一个连续的数量特征分成许多离散的序数值。这些值中的每一个被称为一个箱,每个箱代表原始定量特征上的一个区间。许多机器学习模型需要离散值。使用离散值创建基于规则的模型变得更加容易理解。离散化还使特征更加紧凑,并可能使我们的算法更加高效。
最常见的方法之一是选择箱,使得每个箱具有大约相同数量的实例。这被称为等频离散化,如果我们将其应用于两个面元,那么这与使用中值作为阈值是一样的。这种方法非常有用,因为可以通过代表分位数的方式设置仓位边界。例如,如果我们有 100 个箱,那么每个箱代表一个百分点。
或者,我们可以选择边界,使每个面元具有相同的间隔宽度。这叫做等宽离散化。计算该面元宽度间隔值的一种方法是简单地用面元数量划分特征范围。有时,特征没有上限或下限,我们无法计算其范围。在这种情况下,可以使用高于和低于平均值的标准偏差的整数。宽度和频率离散化都是无监督的。它们不需要任何关于类标签的知识就能工作。
现在让我们把注意力转向监督离散化。基本上有两种方法:自上而下的或分裂,以及聚集或自下而上的方法。顾名思义,分裂的工作原理是首先假设所有样本都属于同一个容器,然后逐步分裂容器。凝聚方法从每个实例的一个箱开始,并逐步合并这些箱。这两种方法都需要一些停止标准来决定是否需要进一步拆分。
通过阈值递归划分特征值的过程是划分离散化的一个例子。为了做到这一点,我们需要一个评分函数来找到特定特征值的最佳阈值。一种常见的方法是计算分裂的信息增益或其熵。通过确定特定分割覆盖了多少正样本和负样本,我们可以基于此标准逐步分割特征。
简单的离散化操作可以通过熊猫切割和 qcut 方法进行。考虑以下示例:
import pandas as pd
import numpy as np
print(pd.cut(np.array([1,2,3,4]), 3, retbins = True, right = False))
以下是观察到的输出:
正常化
阈值化和离散化,两者都去除了定量特征的尺度,根据应用,这可能不是我们想要的。或者,我们可能想给序数或分类特征增加一个尺度。在无监督的环境中,我们称之为规范化。这通常用于处理在不同尺度上测量的定量特征。近似正态分布的特征值可以转换为 z 分数。这只是高于或低于平均值的标准偏差的符号数。阳性 z 分数表示高于平均值的标准偏差数,阴性 z 分数表示低于平均值的标准偏差数。对于某些特征,使用方差可能比使用标准差更方便。
一种更严格的规范化形式以 0 到 1 的比例表示一个特征。如果我们知道一个要素范围,我们可以简单地使用线性缩放,即以最低值和最高值之差除以原始要素值和最低值之差。这表现在以下方面:
这里fnT3】为归一化特征, f 为原始特征, l 和 h 分别为最低值和最高值。在许多情况下,我们可能不得不猜测范围。如果我们对某个特定分布有所了解,例如,在正态分布中,超过 99%的值可能落在平均值的+3 或-3 标准偏差范围内,那么我们可以写出如下线性标度:
这里, μ 为平均值, ơ 为标准差。
校准
有时,我们需要给一个序数或分类特征增加标度信息。这叫做功能校准。这是一种有监督的特征变换,有许多重要的应用。例如,它允许需要缩放特征的模型(如线性分类器)处理分类和序数数据。它还为模型提供了将特征视为序数、类别或数量的灵活性。对于二元分类,我们可以利用正类的后验概率,给定一个特征值,来计算尺度。对于许多概率模型,如朴素贝叶斯,这种校准方法还有一个额外的优点,即一旦校准了特征,模型就不需要任何额外的训练。对于分类特征,我们可以通过简单地从训练集中收集相对频率来确定这些概率。
在某些情况下我们可能需要将数量特征或序数特征转化为类别特征,同时保持一个顺序。我们这样做的主要方式之一是通过物流校准的流程。如果我们假设特征以相同的方差正态分布,那么我们可以用给定特征值 v 来表示似然比,即正类和负类的比值,如下所示:
其中 d 质数是两类平均值除以标准偏差的差值:
还有, z 是 z 的分数:
为了抵消不均匀类分布的效应,我们可以使用以下公式计算校准特征:
你可能会注意到,这正是我们用于逻辑回归的 sigmoid 激活函数。总结逻辑校准,我们主要使用三个步骤:
- 估计正类和负类的类均值。
- 将特征转换为 z 分数。
- 应用 sigmoid 函数给出校准概率。
有时,我们可能会跳过最后一步,特别是如果我们使用基于距离的模型,其中我们期望比例是相加的,以便计算欧几里德距离。您可能会注意到,我们最终校准的特征在比例上是倍增的。
另一种校准技术等渗校准,用于定量和顺序特征。这使用了所谓的 ROC 曲线(代表接收器操作员特征),类似于第 4 章、模型–从信息中学习中讨论逻辑模型时使用的覆盖图。不同的是,使用 ROC 曲线,我们将轴归一化为*【0,1】*。
我们可以使用sklearn包来创建 ROC 曲线:
import matplotlib.pyplot as plt
from sklearn import svm, datasets
from sklearn.metrics import roc_curve, auc
from sklearn.cross_validation import train_test_split
from sklearn.preprocessing import label_binarize
from sklearn.multiclass import OneVsRestClassifier
X, y = datasets.make_classification(n_samples=100,n_classes=3,n_features=5, n_informative=3, n_redundant=0,random_state=42)
# Binarize the output
y = label_binarize(y, classes=[0, 1, 2])
n_classes = y.shape[1]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.5)
classifier = OneVsRestClassifier(svm.SVC(kernel='linear', probability=True, ))
y_score = classifier.fit(X_train, y_train).decision_function(X_test)
fpr, tpr, _ = roc_curve(y_test[:,0], y_score[:,0])
roc_auc = auc(fpr, tpr)
plt.figure()
plt.plot(fpr, tpr, label='ROC AUC %0.2f' % roc_auc)
plt.plot([0, 1], [0, 1], 'k--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic')
plt.legend(loc="best")
plt.show()
以下是观察到的输出:
ROC 曲线绘制了不同阈值的真阳性率与假阳性率。在上图中,这由虚线表示。一旦我们构建了 ROC 曲线,我们就可以计算凸包的每一段中的阳性数, m i ,以及实例总数, n i 。然后使用以下公式计算校准特征值:
在这个公式中, c 是先验概率,即正类概率与负类概率的比值。
到目前为止,在我们关于特征转换的讨论中,我们假设我们知道每个特征的所有值。在现实世界中,情况往往并非如此。如果我们使用概率模型,我们可以通过对所有特征值进行加权平均来估计缺失特征的值。一个重要的考虑是缺失特征值的存在可能与目标变量相关。例如,个人病史中的数据反映了所进行的检测类型,而这又与某些疾病的风险因素评估有关。
如果我们使用的是树模型,我们可以随机选择一个缺失的值,允许模型在其上拆分。然而,这不适用于线性模型。在这种情况下,我们需要通过插补的过程来填写缺失的值。对于分类,我们可以简单地使用观察特征的平均值、中值和模式的统计来估算缺失的值。如果我们想考虑特征相关性,我们可以为每个不完整的特征构建一个预测模型来预测缺失值。
由于 scikit-learn 估计器总是假设数组中的所有值都是数字,因此缺失的值,无论是编码为空格、n an 还是其他占位符,都会产生错误。此外,由于我们可能不想丢弃整行或整列,因为这些可能包含有价值的信息,我们需要使用插补策略来完成数据集。在下面的代码片段中,我们将使用Imputer类:
from sklearn.preprocessing import Binarizer, Imputer, OneHotEncoder
imp = Imputer(missing_values='NaN', strategy='mean', axis=0)
print(imp.fit_transform([[1, 3], [4, np.nan], [5, 6]]))
以下是输出:
很多机器学习算法要求特征是标准化的。这意味着,当单个特征看起来或多或少像均值和单位方差接近于零的正态分布数据时,它们将发挥最佳作用。最简单的方法是从每个特征中减去平均值,然后用标准偏差除以该平均值。这可以通过sklearn.preprocessing()功能中的scale()功能或standardScaler()功能来实现。虽然这些函数将接受稀疏数据,但它们可能不应该在这种情况下使用,因为将稀疏数据居中可能会破坏其结构。在这些情况下,建议使用MacAbsScaler()或maxabs_scale()功能。前者根据每个特征的最大绝对值分别进行缩放和平移。后者将每个特征单独缩放至 [-1,1] 的范围。另一个具体的例子是当我们在数据中有异常值时。在这些情况下,建议使用robust_scale()或RobustScaler()功能。
通常,我们可能希望通过添加多项式项来增加模型的复杂性。这可以使用PolynomialFeatures()功能来完成:
from sklearn.preprocessing import PolynomialFeatures
X=np.arange(9).reshape(3,3)
poly=PolynomialFeatures(degree=2)
print(X)
print(poly.fit_transform(X))
我们将观察以下输出:
主成分分析
主成分分析 ( PCA )是我们可以应用于特征的最常见的降维形式。考虑由两个要素组成的数据集的示例,我们希望将这个二维数据转换为一维数据。一种自然的方法是画一条最接近的线,并将每个数据点投影到这条线上,如下图所示:
主成分分析试图通过最小化数据点和我们试图将数据投影到的直线之间的距离来找到一个表面来投影数据。对于更一般的情况,我们有 n 维,我们想把这个空间缩小到 k 维,我们找到 k 向量 u(1),u(2)、...,u(k) 将数据投影到其上,以最小化投影误差。也就是说,我们试图找到一个 k 维表面来投影数据。
这表面上看起来像线性回归,但在几个重要方面有所不同。对于线性回归,我们试图预测给定输入变量的某个输出变量的值。在主成分分析中,我们不是试图预测一个输出变量,而是试图找到一个子空间来投射我们的输入数据。如前图所示,误差距离不是点和线之间的垂直距离,如线性回归的情况,而是点和线之间最接近的正交距离。因此,误差线与轴成一定角度,并与我们的投影线成直角。
重要的一点是,在大多数情况下,主成分分析需要对特征进行缩放和均值归一化,也就是说,特征的均值为零,并且具有可比较的值范围。我们可以使用以下公式计算平均值:
通过替换以下内容计算总和:
如果特征具有明显不同的比例,我们可以使用以下方法重新缩放:
这些功能可在sklearn.preprocessing模块中获得。
计算低维向量和这些向量上我们投影原始数据的点的数学过程包括首先计算协方差矩阵,然后计算这个矩阵的特征向量。从第一性原理计算这些值是一个相当复杂的过程。幸运的是,sklearn包有一个库来完成这个任务:
from sklearn.decomposition import PCA
import numpy as np
X = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]])
pca = PCA(n_components=1)
pca.fit(X)
print(pca.transform(X))
我们将获得以下输出:
总结
我们可以通过多种方式来转换和构建新的特征,以使我们的模型更有效地工作,并给出更准确的结果。一般来说,没有硬性的规则来决定特定模型使用哪种方法。这在很大程度上取决于您使用的特征类型(数量、顺序或分类)。一个好的第一种方法是标准化和缩放特征,如果模型需要,将特征转换为合适的类型,就像我们通过离散化所做的那样。如果模型表现不佳,可能需要应用进一步的预处理,如主成分分析。在下一章中,我们将研究如何通过使用集成来组合不同类型的模型,以提高性能并提供更大的预测能力。*
八、集成学习
创建机器学习集成的动机来自清晰的直觉,并且基于丰富的理论历史。在许多自然和人工系统中,多样性使它们对扰动更有弹性。同样,我们已经看到,对大量测量结果进行平均通常可以得到更稳定的模型,该模型不太容易受到随机波动的影响,例如数据收集中的异常值或误差。
在本章中,我们将把这个相当大而多样的空间分成以下几个主题:
- “类型”集合
- 制袋材料
- 随机森林
- 助推
集合型式
集成技法可以大致分为两种类型:
- 平均法:这是法,其中几个估计量独立运行,它们的预测值被平均。这包括随机森林和装袋方法。
- Boosting 方法:这是中的方法,使用基于错误率的数据的加权分布,依次构建弱学习者。
集成方法使用多个模型来获得比任何单个组成模型更好的性能。其目的不仅是建立多样化和健壮的模型,而且在诸如处理速度和返回时间等限制内工作。当处理大型数据集和快速响应时,这可能是一个重大的发展瓶颈。故障排除和诊断是使用所有机器学习模型的一个重要方面,尤其是当我们处理可能需要几天运行的模型时。
可以创建的机器学习集成的类型和模型本身一样多种多样,主要考虑围绕三件事:我们如何划分数据,我们如何选择模型,以及我们使用什么方法来组合它们的结果。这种简单的说法实际上包含了一个非常大而多样的空间。
装袋
打包,也称为自举聚合,有几种风格,它们是通过从训练数据中抽取随机子集的方式来定义的。最常见的是,装袋是指抽取样品进行替换。因为样本被替换,所以生成的数据集可能包含重复项。这也意味着数据点可能会从特定生成的数据集中排除,即使该生成的数据集与原始数据集大小相同。每个生成的数据集都是不同的,这是一种在集合中的模型之间创建多样性的方法。我们可以使用以下示例计算样本中未选择数据点的概率:
这里, n 是自举样本数。每个 n 自举样本产生不同的假设。通过平均模型或者通过选择大多数模型预测的类别来预测类别。考虑一组线性分类器。如果我们使用多数投票来确定预测的类,我们创建一个分段线性分类器边界。如果我们将投票转换为概率,那么我们将实例空间划分为多个片段,每个片段都可能有不同的分数。
还应该提到的是,使用特征的随机子集是可能的,有时也是可取的;这叫做 子空间采样。Bagging 估计器最适合复杂模型,如完全开发的决策树,因为它们可以帮助减少过度拟合。它们提供了一种简单、现成的方法来改进单一模型。
Scikit-learn 实现了一个BaggingClassifier和BaggingRegressor对象。以下是它们最重要的一些参数:
参数
|
类型
|
描述
|
默认
|
| --- | --- | --- | --- |
| base_estimator | 估计量 | 这就是这个集成所基于的模型。 | 决策图表 |
| n_estimators | (同 Internationalorganizations)国际组织 | 这是基础估计量的数。 | Ten |
| max_samples | Int 或 float | 这是要抽取的样本数量。若浮绘max_samples*X.shape[0]。 | One |
| max_features | Int 或 float | 这是要绘制的特征数量。若浮绘max_features*X.shape[1]。 | One |
| bootstrap | 布尔代数学体系的 | 这些是替换抽取的样本。 | 真实的 |
| bootstrap_features | 布尔代数学体系的 | 这些是替换绘制的特征。 | 错误的 |
作为一个例子,下面的片段实例化了一个 bagging 分类器,该分类器包括 50 个决策树分类器基估计器,每个估计器建立在一半特征和一半样本的随机子集上:
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn import datasets
bcls=BaggingClassifier(DecisionTreeClassifier(),max_samples=0.5, max_features=0.5, n_estimators=50)
X,y=datasets.make_blobs(n_samples=8000,centers=2, random_state=0, cluster_std=4)
bcls.fit(X,y)
print(bcls.score(X,y))
随机森林
基于树的模型特别适合集成,主要是因为它们对训练数据的变化非常敏感。当与 子空间采样一起使用时,树模型可以非常有效,导致模型更加多样化,并且由于集成中的每个模型只处理特征的子集,因此减少了训练时间。这使用特征的不同随机子集构建每棵树,因此被称为随机森林。
随机林通过查找林中单个树中分区的交集来划分实例空间。它定义了一个比林中任何单独的树创建的分区都更精细的分区,也就是说,它将包含更多的细节。原则上,一个随机森林可以映射回一个单独的树,因为每个交集对应于两个不同树的分支的组合。随机森林可以被认为是本质上是基于树的模型的替代训练算法。bagging 集成中的线性分类器能够学习单个线性分类器无法学习的复杂决策边界。
sklearn.ensemble模块有两种基于决策树的算法,随机森林和极随机树。它们都通过在构造中引入随机性来创建不同的分类器,并且都包括用于分类和回归的类。对于RandomForestClassifier和RandomForestRegressor类,每个树都是使用自举样本构建的。模型选择的分割不是所有要素中的最佳分割,而是从要素的随机子集中选择的。
多余的树
与随机森林一样,extra trees方法使用特征的随机子集,但是使用随机生成的最佳阈值集而不是使用最具区别性的阈值。这是以偏差的小幅增加为代价来降低方差的。两个班是ExtraTreesClassifier和ExtraTreesRegressor。
我们来看一个分类器和extra trees分类器的例子。在这个例子中,我们使用VotingClassifier来组合不同的分类器。投票分类器可以帮助平衡单个模型的弱点。在本例中,我们向函数传递四个权重。这些权重决定了每个模型对整体结果的贡献。我们可以看到,这两个树模型在训练数据上表现过度,但在测试数据上表现更好。我们还可以看到ExtraTreesClassifier在测试集上取得了比RandomForest对象稍好的结果。此外,VotingClasifier对象在测试集上的表现优于其所有组成分类器。值得一提的是,在不同的权重和不同的数据集上运行时,可以看到每个模型的性能是如何变化的:
from sklearn import cross_validation
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.ensemble import VotingClassifier
from sklearn import datasets
def vclas(w1,w2,w3, w4):
X , y = datasets.make_classification(n_features= 10, n_informative=4, n_samples=500, n_clusters_per_class=5)
Xtrain,Xtest, ytrain,ytest= cross_validation.train_test_split(X,y,test_size=0.4)
clf1 = LogisticRegression(random_state=123)
clf2 = GaussianNB()
clf3 = RandomForestClassifier(n_estimators=10,bootstrap=True, random_state=123)
clf4= ExtraTreesClassifier(n_estimators=10, bootstrap=True,random_state=123)
clfes=[clf1,clf2,clf3,clf4]
eclf = VotingClassifier(estimators=[('lr', clf1), ('gnb', clf2), ('rf', clf3),('et',clf4)],
voting='soft',
weights=[w1, w2, w3,w4])
[c.fit(Xtrain, ytrain) for c in (clf1, clf2, clf3,clf4, eclf)]
N = 5
ind = np.arange(N)
width = 0.3
fig, ax = plt.subplots()
for i, clf in enumerate(clfes):
print(clf,i)
p1=ax.bar(i,clfes[i].score(Xtrain,ytrain,), width=width,color="black")
p2=ax.bar(i+width,clfes[i].score(Xtest,ytest,), width=width,color="grey")
ax.bar(len(clfes)+width,eclf.score(Xtrain,ytrain,), width=width,color="black")
ax.bar(len(clfes)+width *2,eclf.score(Xtest,ytest,), width=width,color="grey")
plt.axvline(3.8, color='k', linestyle='dashed')
ax.set_xticks(ind + width)
ax.set_xticklabels(['LogisticRegression',
'GaussianNB',
'RandomForestClassifier',
'ExtraTrees',
'VotingClassifier'],
rotation=40,
ha='right')
plt.title('Training and test score for different classifiers')
plt.legend([p1[0], p2[0]], ['training', 'test'], loc='lower left')
plt.show()
vclas(1,3,5,4)
您将观察以下输出:
树模型允许我们根据它们贡献的样本的预期分数来评估特征的相对等级。这里,我们使用一个来评估分类任务中每个特征的重要性。特征的相对重要性基于它在树中的表示位置。树顶部的特征有助于最终决定更大比例的输入样本。
以下示例使用ExtraTreesClassifier类来映射要素重要性。我们使用的数据集由 10 张图片组成,每张 40 人,共 400 张图片。每张图片都有一个标签,表明这个人的身份。在这个任务中,每个像素都是一个特征;在输出中,像素的亮度代表特征的相对重要性。像素越亮,特征越重要。请注意,在这个模型中,最亮的像素在前额区域,我们应该小心如何解释这一点。由于大多数照片都是从头顶上方照射的,这些像素的重要性显然很高,这可能是因为前额往往被更好地照射,因此揭示了关于个人的更多细节,而不是一个人前额的内在属性来表明他们的身份:
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_olivetti_faces
from sklearn.ensemble import ExtraTreesClassifier
data = fetch_olivetti_faces()
def importance(n_estimators=500, max_features=128, n_jobs=3, random_state=0):
X = data.images.reshape((len(data.images), -1))
y = data.target
forest = ExtraTreesClassifier(n_estimators,max_features=max_features, n_jobs=n_jobs, random_state=random_state)
forest.fit(X, y)
dstring=" cores=%d..." % n_jobs + " features=%s..." % max_features +"estimators=%d..." %n_estimators + "random=%d" %random_state
print(dstring)
importances = forest.feature_importances_
importances = importances.reshape(data.images[0].shape)
plt.matshow(importances, cmap=plt.cm.hot)
plt.title(dstring)
#plt.savefig('etreesImportance'+ dstring + '.png')
plt.show()
importance()
前面代码的输出如下:
助推
在本书的前面,我介绍了 PAC 学习模型的思想和概念类的思想。一个相关的想法是 弱可学性。在这里,集成中的每一个学习算法只需要表现得比机会稍好。例如,如果集合中的每个算法至少有 51%的时间是正确的,则弱可学习性的标准得到满足。事实证明,PAC 和弱可学习性的思想本质上是相同的,只是对于后者,我们放弃了算法必须达到任意高精度的要求。然而,它只是比随机假设表现得更好。你可能会问,这有什么用?通常更容易找到粗略的经验法则而不是高度精确的预测法则。这种弱学习模式的表现可能只是略好于偶然性;然而,如果我们通过在数据的不同加权分布上多次运行来提升这个学习者,并通过组合这些学习者,我们有希望构建一个单一的预测规则,它的性能比任何单个弱学习规则都好得多。
助推是一个简单而有力的想法。它通过考虑模型的训练误差来扩展 bagging。例如,如果我们训练一个线性分类器,发现它错误地分类了某一组实例。如果我们在包含这些错误分类实例的副本的数据集上训练一个后续模型,那么我们会期望这个新训练的模型在测试集上会表现得更好。通过在训练集中包含错误分类实例的副本,我们将数据集的平均值向这些实例转移。这迫使学习者专注于最难分类的例子。在实践中,这是通过赋予错误分类的实例更高的权重,然后修改模型来考虑这一点来实现的,例如,在线性分类器中,我们可以使用加权平均来计算类均值。
从一个总和为 1 的统一权重数据集开始,我们运行分类器,可能会对一些实例进行错误分类。为了增加这些实例的权重,我们给它们分配了总权重的一半。例如,考虑一个给我们以下结果的分类器:
| |预测阳性
|
预测负值
|
总数
| | --- | --- | --- | --- | | 实际位置。 | Twenty-four | Sixteen | Forty | | 实际负值。 | nine | Fifty-one | Sixty | | 总计 | Thirty-three | Sixty-seven | One hundred |
误差率为 ɛ = (9 + 16)/100 = 0.25 。
我们希望将错误权重的一半分配给错误分类的样本,由于我们从总和为 1 的统一权重开始,因此分配给错误分类示例的当前权重只是错误率。因此,为了更新权重,我们将它们乘以因子 1/2ɛ 。假设错误率小于 0.5,这将导致错误分类示例的权重增加。为了确保权重总和仍然为 1,我们将正确分类的示例乘以 (1-ɛ) 。在这个例子中,错误分类样本的初始权重的错误率是. 25,我们希望它是. 5,也就是总权重的一半,所以我们将这个初始错误率乘以 2。正确分类实例的权重为1/2(1-ɛ= 2/3。将这些权重考虑到下表中:
| |预测阳性
|
预测负值
|
总数
| | --- | --- | --- | --- | | 实际位置。 | Sixteen | Thirty-two | Forty-eight | | 实际负值。 | Eighteen | Thirty-four | Sixty | | 总计 | Thirty-three | Sixty-seven | One hundred |
我们需要的最后一块是一个置信因子 α ,它被应用于集合中的每个模型。这用于基于来自每个单独模型的加权平均值进行集合预测。我们希望随着误差的减小,这个数值会增加。确保这种情况发生的常见方法是将置信因子设置为以下值:
因此,我们得到了一个数据集,如下所示:
然后,我们初始化一个相等的加权分布,如下所示:
使用弱分类器 h t ,我们可以编写一个更新的规则如下:
使用归一化因子,如下所示:
请注意,*exp(-yIht(xI)为正且大于 1,如果-yIht(xI)*为正,如果 x i 为错误分类,则会出现这种情况。结果是,更新规则将增加错误分类示例的权重,并降低正确分类样本的权重。
我们可以按如下方式编写最终的分类器:
Adaboost
其中最流行的增强算法被称为 AdaBoost 或自适应增强。这里,决策树分类器被用作基础学习器,并且它在不可线性分离的数据上建立决策边界:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import make_blobs
plot_colors = "br"
plot_step = 0.02
class_names = "AB"
tree= DecisionTreeClassifier()
boost=AdaBoostClassifier()
X,y=make_blobs(n_samples=500,centers=2, random_state=0, cluster_std=2)
boost.fit(X,y)
plt.figure(figsize=(10, 5))
# Plot the decision boundaries
plt.subplot(121)
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, plot_step),
np.arange(y_min, y_max, plot_step))
Z = boost.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
cs = plt.contourf(xx, yy, Z, cmap=plt.cm.Paired)
plt.axis("tight")
for i, n, c in zip(range(2), class_names, plot_colors):
idx = np.where(y == i)
plt.scatter(X[idx, 0], X[idx, 1],
c=c, cmap=plt.cm.Paired,
label="Class %s" % n)
plt.title('Decision Boundary')
twoclass_output = boost.decision_function(X)
plot_range = (twoclass_output.min(), twoclass_output.max())
plt.subplot(122)
for i, n, c in zip(range(2), class_names, plot_colors):
plt.hist(twoclass_output[y == i],
bins=20,
range=plot_range,
facecolor=c,
label='Class %s' % n,
alpha=.5)
x1, x2, y1, y2 = plt.axis()
plt.axis((x1, x2, y1, y2))
plt.legend(loc='upper left')
plt.ylabel('Samples')
plt.xlabel('Score')
plt.title('Decision Scores')
plt.show()
print("Mean Accuracy =%f" % boost.score(X,y))
以下是前面命令的输出:
梯度助推
梯度树增强对于回归和分类问题都是非常有用的算法。它的一个主要优势是它自然地处理混合数据类型,并且它对异常值也相当健壮。此外,它比许多其他算法具有更好的预测能力;然而,它的顺序体系结构使它不适合并行技术,因此,它不能很好地扩展到大型数据集。对于类数较多的数据集,建议改用RandomForestClassifier。梯度增强通常使用决策树来建立基于弱学习者集合的预测模型,对代价函数应用优化算法。
在下面的示例中,我们创建了一个构建梯度增强分类器的函数,并绘制了其累积损失与迭代次数的关系图。GradientBoostingClassifier类有一个oob_improvement_属性,用于计算每次迭代的测试损失估计值。与之前的迭代相比,这让我们减少了损失。这对于确定最佳迭代次数是非常有用的启发。这里,我们绘制了两个梯度增强分类器的累积改进。每个分类器都是相同的,但是学习速率不同,虚线为 .01 ,实线为 .001 。
学习率缩小了每棵树的贡献,这意味着在估计器的数量上有一个折衷。在这里,我们实际上看到,与具有较低学习率的模型相比,具有较大学习率的模型似乎更快地达到其最佳性能。然而,这种模式似乎取得了更好的整体效果。在实践中通常发生的是oob_improvement在大量迭代中以悲观的方式偏离。让我们来看看以下命令:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import ensemble
from sklearn.cross_validation import train_test_split
from sklearn import datasets
def gbt(params, X,y,ls):
clf = ensemble.GradientBoostingClassifier(**params)
clf.fit(X_train, y_train)
cumsum = np.cumsum(clf.oob_improvement_)
n = np.arange(params['n_estimators'])
oob_best_iter = n[np.argmax(cumsum)]
plt.xlabel('Iterations')
plt.ylabel('Improvement')
plt.axvline(x=oob_best_iter,linestyle=ls)
plt.plot(n, cumsum, linestyle=ls)
X,y=datasets.make_blobs(n_samples=50,centers=5, random_state=0, cluster_std=5)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=9)
p1 = {'n_estimators': 1200, 'max_depth': 3, 'subsample': 0.5,
'learning_rate': 0.01, 'min_samples_leaf': 1, 'random_state': 3}
p2 = {'n_estimators': 1200, 'max_depth': 3, 'subsample': 0.5,
'learning_rate': 0.001, 'min_samples_leaf': 1, 'random_state': 3}
gbt(p1, X,y, ls='--')
gbt(p2, X,y, ls='-')
您将观察到以下输出:
集合策略
我们研究了两种广泛的集成技术:打包,应用于随机森林和额外的树,以及增强,特别是 AdaBoost 和梯度树增强。当然还有许多其他的变体以及它们的组合。在这一章的最后一节,我想研究一些为特定任务选择和应用不同系综的策略。
一般来说,在分类任务中,有三个原因可以解释为什么模型可能会对测试实例进行错误分类。首先,如果来自不同类别的特征由相同的特征向量描述,这可能是不可避免的。在概率模型中,当类分布重叠时会发生这种情况,因此一个实例对于几个类具有非零的可能性。这里我们只能近似一个目标假设。
分类错误的第二个原因是模型不具备充分表达目标假设的表达能力。例如,如果数据不能线性分离,即使最好的线性分类器也会对实例进行错误分类。这是由于分类器的偏差。尽管没有单一的一致同意的方法来测量偏差,我们可以看到非线性决策边界比线性决策边界具有更小的偏差,或者更复杂的决策边界比简单的决策边界具有更小的偏差。我们还可以看到,树模型的偏差最小,因为它们可以继续分支,直到每个叶子只覆盖一个实例。
现在,似乎我们应该尽量减少偏见;然而,在大多数情况下,降低偏差往往会增加方差,反之亦然。正如你可能已经猜到的,方差是分类错误的第三个来源。高方差模型高度依赖于训练数据。例如,最近邻分类器将实例空间分割成单个训练点。如果决策边界附近的训练点被移动,那么该边界将改变。树模型也是高方差的,但原因不同。考虑我们以这样一种方式改变训练数据,即在树根处选择不同的特征。这可能会导致树的其余部分不同。
线性分类器的袋装集成能够通过分段构造来学习更复杂的决策边界。集成中的每个分类器创建一段决策边界。这表明 bagging,实际上任何集成方法,都能够减少高偏差模型的偏差。然而,我们在实践中发现,提升通常是减少偏见的更有效方式。
注
Bagging 主要是一种方差减少技术,boosting 主要是一种偏差减少技术。
Bagging 集成在高方差模型(如复杂树)中最有效,而 boosting 通常用于高偏差模型(如线性分类器)。
我们可以从利润的角度来看提振。这可以理解为距决策边界的有符号距离;正号表示正确的类别,负号表示错误的类别。可以证明的是,即使样本已经在决策边界的正确一侧,提升也可以增加这一裕度。换句话说,即使训练误差为零,boosting 也可以继续提高测试集的性能。
其他方法
集合方法的主要变化是通过改变基本模型的预测组合方式来实现的。我们实际上可以将这定义为一个学习问题,假设一组基本分类器的预测作为特征来学习一个 元模型,该模型最好地组合了它们的预测。学习线性元模型被称为叠加或叠加泛化。堆叠使用所有学习者的加权组合,并且在分类任务中,使用诸如逻辑回归的组合器算法来进行最终预测。不像装袋或助推,也像装桶一样,堆叠通常用于不同类型的模型。
典型的堆叠程序包括以下步骤:
- 将训练集分成两个不连贯的集合。
- 在第一套基础上训练几个基础学习者。
- 在第二组测试基础学习者。
- 使用上一步的预测来训练更高水平的学习者。
请注意,前三个步骤与交叉验证相同;然而,基础学习者不是采取赢家通吃的方法,而是被组合在一起,可能是非线性的。
这个主题的一个变体是逆势。这里,选择算法用于为每个问题选择最佳模型。例如,这可以通过给每个模型的预测赋予权重来使用感知来选择最佳模型。由于有大量不同的模型,有些模型比其他模型需要更长的训练时间。在集成中使用这种方法的一种方式是首先使用快速但不精确的算法来选择哪个较慢但更精确的算法可能做得最好。
我们可以使用一组不同的基础学习者来融合多样性。这种多样性来自不同的学习算法,而不是数据。这意味着每个模型可以使用相同的训练集。通常,基本模型由相同类型但具有不同超参数设置的集合组成。
总的来说,集成由一组基本模型和一个元模型组成,它们被训练来找到组合这些基本模型的最佳方式。如果我们使用一组加权模型,并以某种方式组合它们的输出,我们假设如果一个模型的权重接近于零,那么它对输出的影响将非常小。可以想象,基础分类器具有负权重,并且在这种情况下,相对于其他基础模型,它的预测将被反转。我们甚至可以更进一步,甚至在训练基础模型之前,就试图预测它的表现。这有时被称为元学习。这包括,首先,在大量数据集合上训练各种模型,并构建一个模型来帮助我们回答一些问题,例如哪个模型在特定数据集上可能优于另一个模型,或者数据是否表明特定(元)参数可能效果最好?
请记住,当在所有可能问题的空间上进行评估时,没有任何学习算法能够胜过另一个算法,例如,如果所有可能的序列都是可能的,则预测下一个数字是一个序列。当然,现实世界中的学习问题具有非均匀分布,这使得我们能够在这些问题上建立预测模型。元学习的重要问题是如何设计元模型所基于的特征。它们需要结合训练模型和数据集的相关特征。该必须包括除特征数量和类型以及样本数量之外的数据方面。
总结
在这一章中,我们研究了 scikit-learn 中的主要集成方法及其实现。很明显,这里有很大的工作空间,找到最适合不同类型问题的技术是关键的挑战。我们看到,偏差和方差的问题都有各自的解决方案,理解每个问题的关键指标至关重要。获得好的结果通常需要大量的实验,使用本章中描述的一些简单技术,你可以开始你的机器学习集成之旅。
在下一章,也是最后一章,我们将介绍最重要的主题——模型选择和评估,并从不同的角度研究一些现实世界的问题。
九、设计策略和案例研究
除了可能的数据例外,评估可能是机器学习科学家花费大部分时间做的事情。盯着数字和图表的列表,满怀希望地看着他们的模型运行,并认真尝试理解它们的输出。评价是一个循环的过程;我们运行模型,评估结果,并插入新的参数,每次都希望这会带来性能提升。随着我们提高每个评估周期的效率,我们的工作变得更加愉快和高效,有一些工具和技术可以帮助我们实现这一点。本章将通过以下主题介绍其中一些:
- 评估模型性能
- 型号选择
- 真实案例研究。
- 机器学习设计一目了然
评估模型性能
测量模型的性能是一项重要的机器学习任务,并且有许多不同的参数和启发式方法来完成这项任务。定义评分策略的重要性不可低估,在 Sklearn 中,基本上有三种方法:
- 评估者评分:这个指的是使用评估者内置的
score()方法,具体到每个评估者 - 评分参数:这是指依靠内部评分策略的交叉验证工具
- 度量功能:这些在度量模块中实现
我们已经看到了估计量score()方法的例子,例如clf.score()。在线性分类器的情况下,score()方法返回平均精度。这是一种快速简单的方法来衡量个人评估者的表现。然而,由于多种原因,这种方法本身通常是不够的。
如果我们记得的话,准确率就是真阳性和真阴性的情况之和除以样本数。以此作为衡量标准表明,如果我们对一些患者进行测试,看看他们是否患有某种疾病,简单地预测每个患者都没有疾病可能会给我们带来很高的准确性。显然,这不是我们想要的。
衡量绩效的更好方法是使用精度、( P )和召回、( R )。如果从记起第 4 章、模型–从信息中学习中的表格,精度,或者说特异性,就是预测阳性实例正确的比例,即 TP/(TP+FP) 。回忆,或者说敏感,就是 TP/(TP+FN) 。F-测度定义为 2RP/(R+P) 。这些措施忽略了真实的阴性率,所以他们没有对一个模型处理阴性病例的效果进行评估。
与其使用估计器的评分方法,不如使用特定的评分参数,如cross_val_score对象提供的参数。这有一个cv参数,控制数据如何分割。它通常被设置为 int,它决定了对数据进行多少次随机连续拆分。每个都有不同的分割点。此参数也可以设置为训练和测试拆分的可选项,或者可以用作交叉验证生成器的对象。
cross_val_score中同样重要的是评分参数。这通常由表示评分策略的字符串来设置。分类时默认为精度,一些常用值为f1、precision、recall,以及这些值的微平均、宏平均和加权版本。对于回归估计量,scoring值为mean_absolute_error、mean_squared error、median_absolute_error和r2。
下面的代码使用 10 个连续的拆分来估计数据集上三个模型的性能。在这里,我们打印出了每个分数的平均值,使用了四个模型中每一个的几个度量。在现实世界中,我们可能需要以一种或多种方式对数据进行预处理,将这些数据转换应用于我们的测试集和训练集非常重要。为了使这变得更容易,我们可以使用sklearn.pipeline模块。这依次应用了一系列变换和一个最终估计器,它允许我们将几个可以交叉验证的步骤组合在一起。这里,我们还使用StandardScaler()类来缩放数据。通过使用两条管道将缩放应用于逻辑回归模型和决策树:
from sklearn import cross_validation
from sklearn.tree import DecisionTreeClassifier
from sklearn import svm
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import samples_generator
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import StandardScaler
from sklearn.cross_validation import cross_val_score
from sklearn.pipeline import Pipeline
X, y = samples_generator.make_classification(n_samples=1000,n_informative=5, n_redundant=0,random_state=42)
le=LabelEncoder()
y=le.fit_transform(y)
Xtrain, Xtest, ytrain, ytest = cross_validation.train_test_split(X, y, test_size=0.5, random_state=1)
clf1=DecisionTreeClassifier(max_depth=2,criterion='gini').fit(Xtrain,ytrain)
clf2= svm.SVC(kernel='linear', probability=True, random_state=0).fit(Xtrain,ytrain)
clf3=LogisticRegression(penalty='l2', C=0.001).fit(Xtrain,ytrain)
pipe1=Pipeline([['sc',StandardScaler()],['mod',clf1]])
mod_labels=['Decision Tree','SVM','Logistic Regression' ]
print('10 fold cross validation: \n')
for mod,label in zip([pipe1,clf2,clf3], mod_labels):
#print(label)
auc_scores= cross_val_score(estimator= mod, X=Xtrain, y=ytrain, cv=10, scoring ='roc_auc')
p_scores= cross_val_score(estimator= mod, X=Xtrain, y=ytrain, cv=10, scoring ='precision_macro')
r_scores= cross_val_score(estimator= mod, X=Xtrain, y=ytrain, cv=10, scoring ='recall_macro')
f_scores= cross_val_score(estimator= mod, X=Xtrain, y=ytrain, cv=10, scoring ='f1_macro')
print(label)
print("auc scores %2f +/- %2f " % (auc_scores.mean(), auc_scores.std()))
print("precision %2f +/- %2f " % (p_scores.mean(), p_scores.std()))
print("recall %2f +/- %2f ]" % (r_scores.mean(), r_scores.std()))
print("f scores %2f +/- %2f " % (f_scores.mean(), f_scores.std()))
执行时,您将看到以下输出:
这些技术有几种变体,最常用的是所谓的 k 倍交叉验证。这使用了有时被称为*的“留一个出去”*的策略。首先,使用折叠的 k —1 作为训练数据来训练模型。剩余的数据用于计算性能度量。对每个折叠重复这一过程。性能计算为所有折叠的平均值。
Sklearn 使用cross_validation.KFold对象实现了这一点。重要参数是一个必需的int,表示元素的总数,以及一个n_folds参数,默认为3,表示折叠的数量。它还带有可选的shuffle和random_state参数,指示在拆分前是否对数据进行洗牌,以及使用什么方法生成随机状态。默认的random_state参数是使用 NumPy 随机数发生器。
在下面的代码片段中,我们使用了LassoCV对象。这是用 L1 正则化训练的线性模型。如果您还记得,正则化线性回归的优化函数包括一个常数(α),它乘以 L1 正则化项。LassoCV对象会自动设置该 alpha 值,为了了解其有效性,我们可以将所选 alpha 与每个 k 折叠上的分数进行比较:
import numpy as np
from sklearn import cross_validation, datasets, linear_model
X,y=datasets.make_blobs(n_samples=80,centers=2, random_state=0, cluster_std=2)
alphas = np.logspace(-4, -.5, 30)
lasso_cv = linear_model.LassoCV(alphas=alphas)
k_fold = cross_validation.KFold(len(X), 5)
alphas = np.logspace(-4, -.5, 30)
for k, (train, test) in enumerate(k_fold):
lasso_cv.fit(X[train], y[train])
print("[fold {0}] alpha: {1:.5f}, score: {2:.5f}".
format(k, lasso_cv.alpha_, lasso_cv.score(X[test], y[test])))
上述命令的输出如下:
有时,需要保留每个文件夹中类别的百分比。这是使用分层交叉验证完成的。当班级不平衡时,也就是说,当一些班级人数较多而其他班级人数很少时,这可能会有所帮助。使用分层cv对象可能有助于纠正模型中可能导致偏差的缺陷,因为一个类没有以足够大的数量表示在一个文件夹中,无法进行准确的预测。然而,这也可能导致不必要的方差增加。
在下面的例子中,我们使用分层交叉验证来测试分类分数的重要性。这是通过在随机化标签后重复分类程序来完成的。 p 值是得分大于最初获得的分类得分的运行百分比。这段代码片段使用cross_validation.permutation_test_score方法,该方法将估计值、数据和标签作为参数。这里,我们打印出初始测试分数、 p 值以及每个排列的分数:
import numpy as np
from sklearn import linear_model
from sklearn.cross_validation import StratifiedKFold, permutation_test_score
from sklearn import datasets
X,y=datasets.make_classification(n_samples=100, n_features=5)
n_classes = np.unique(y).size
cls=linear_model.LogisticRegression()
cv = StratifiedKFold(y, 2)
score, permutation_scores, pvalue = permutation_test_score(cls, X, y, scoring="f1", cv=cv, n_permutations=10, n_jobs=1)
print("Classification score %s (pvalue : %s)" % (score, pvalue))
print("Permutation scores %s" % (permutation_scores))
这给出了以下输出:
车型选择
有许多超参数可以调整以提高性能。这通常不是一个简单的过程,要确定各个参数的效果,包括单独的和相互结合的。常见的尝试包括获取更多的训练示例、添加或移除特征、添加多项式特征以及增加或减少正则化参数。考虑到我们可以花费大量时间收集更多数据,或者以其他方式操纵数据,重要的是您花费的时间很可能会产生富有成效的结果。最重要的方法之一是使用网格搜索。
网格搜索
sklearn.grid_search.GridSearchCV对象用于对指定的参数值进行详尽的搜索。这允许通过定义的参数集进行迭代,并以各种度量的形式报告结果。GridSearchCV对象的重要参数是估计量和参数网格。param_grid参数是一个字典,或字典列表,参数名称作为键,参数设置列表作为值。这使得能够搜索任意序列的估计器参数值。任何估计器的可调参数都可以用于网格搜索。默认情况下,网格搜索使用估计器的score()功能来评估参数值。对于分类来说,这是准确性,正如我们所看到的,这可能不是最好的衡量标准。在本例中,我们将GridSearchCV对象的评分参数设置为f1。
在下面的代码中,我们在 L1 和 L2 正则化下,在一系列C值(逆正则化参数)上执行搜索。我们使用metrics.classification_report类打印出详细的分类报告:
from sklearn import datasets
from sklearn.cross_validation import train_test_split
from sklearn.grid_search import GridSearchCV
from sklearn.metrics import classification_report
from sklearn.linear_model import LogisticRegression as lr
X,y=datasets.make_blobs(n_samples=800,centers=2, random_state=0, cluster_std=4)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.5, random_state=0)
tuned_parameters = [{'penalty': ['l1'],
'C': [0.01, 0.1, 1, 5]},
{'penalty': ['l2'], 'C': [0.01, 0.1, 1, 5]}]
scores = ['precision', 'recall','f1']
for score in scores:
clf = GridSearchCV(lr(C=1), tuned_parameters, cv=5,
scoring='%s_weighted' % score)
clf.fit(X_train, y_train)
print("Best parameters on development set:")
print()
print(clf.best_params_)
print("Grid scores on development set:")
for params, mean_score, scores in clf.grid_scores_:
print("%0.3f (+/-%0.03f) for %r"
% (mean_score, scores.std() * 2, params))
print("classification report:")
y_true, y_pred = y_test, clf.predict(X_test)
print(classification_report(y_true, y_pred))
我们观察到以下输出:
网格搜索可能是优化超参数最常用的方法,但是,有时它可能不是最佳选择。RandomizedSearchCV对象实现了对可能参数的随机搜索。它使用类似于GridSearchCV对象的字典,然而,对于每个参数,可以设置一个分布,在该分布上将进行值的随机搜索。如果字典包含一个值列表,那么这些值将被统一采样。此外,RandomizedSearchCV对象还包含一个n_iter参数,该参数实际上是采样参数设置数量的计算预算。它默认为 10,在高值时,通常会给出更好的结果。然而,这是以运行时为代价的。
网格搜索的蛮力方法还有其他选择,这些在估算器中提供,如LassoCV和ElasticNetCV。这里,估计器本身通过沿着正则化路径拟合来优化其正则化参数。这通常比使用网格搜索更有效。
学习曲线
了解模型运行情况的一个重要方法是使用学习曲线。考虑当我们增加样本数量时训练和测试错误会发生什么。考虑一个简单的线性模型。训练样本少,很容易拟合参数,训练误差小。随着训练集的增长,它变得更难适应,平均训练误差可能会增加。另一方面,随着样本的增加,交叉验证误差可能会减小,至少在开始时是这样。有了更多的样本进行训练,模型将能够更好地适应新的样本。考虑具有高偏差的模型,例如,具有两个参数的简单线性分类器。这只是一条直线,因此当我们开始添加训练示例时,交叉验证错误最初会减少。然而,在某一点之后,仅仅因为一条直线的限制,增加训练样本并不会显著降低误差,它根本无法拟合非线性数据。如果我们观察训练误差,我们会发现,像前面一样,它最初随着更多的训练样本而增加,并且在某个点,它将近似等于交叉验证误差。在高偏差的例子中,交叉验证和训练误差都很高。这表明,如果我们知道我们的学习算法有很高的偏差,那么仅仅增加更多的训练例子是不太可能显著改善模型的。
现在,考虑一个具有高方差的模型,比如说具有大量多项式项,并且正则化参数的值很小。随着我们增加更多的样本,训练误差将缓慢增加,但保持相对较小。随着更多训练样本的加入,交叉验证集的误差将会减小。这是过度装配的迹象。具有高方差的模型的指示特征是训练误差和测试误差之间的巨大差异。这表明,增加训练示例将降低交叉验证误差,因此,添加训练样本是改进高方差模型的可能方式。
在下面的代码中,随着样本量的增加,我们使用学习曲线对象来绘制测试误差和训练误差。当一个特定的模型正遭受高偏差或高方差时,这应该给你一个指示。在这种情况下,我们使用的是逻辑回归模型。从这段代码的输出中我们可以看出,模型可能存在偏差,因为两个训练测试的误差都相对较高:
from sklearn.pipeline import Pipeline
from sklearn.learning_curve import learning_curve
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn import cross_validation
from sklearn import datasets
X, y = datasets.make_classification(n_samples=2000,n_informative=2, n_redundant=0,random_state=42)
Xtrain, Xtest, ytrain, ytest = cross_validation.train_test_split(X, y, test_size=0.5, random_state=1)
pipe = Pipeline ([('sc' , StandardScaler()),('clf', LogisticRegression( penalty = 'l2'))])
trainSizes, trainScores, testScores = learning_curve(estimator=pipe, X=Xtrain, y= ytrain,train_sizes=np.linspace(0.1,1,10),cv=10, n_jobs=1)
trainMeanErr=1-np.mean(trainScores, axis=1)
testMeanErr=1-np.mean(testScores, axis=1)
plt.plot(trainSizes, trainMeanErr, color='red', marker='o', markersize=5, label = 'training error')
plt.plot(trainSizes, testMeanErr, color='green', marker='s', markersize=5, label = 'test error')
plt.grid()
plt.xlabel('Number of Training Samples')
plt.ylabel('Error')
plt.legend(loc=0)
plt.show()
以下是前面代码的输出:
真实世界案例研究
现在,我们将继续讨论一些真实世界的机器学习场景。首先,我们将建立一个推荐系统,然后我们将研究温室中的一些集成害虫管理系统。
建立推荐系统
推荐系统是信息过滤的一种,一般有两种方式:基于内容的过滤和协同过滤。在基于内容的过滤中,系统试图模拟用户的长期兴趣,并基于此选择项目。另一方面,协同过滤基于与具有相似偏好的人选择的项目的相关性来选择项目。正如您所料,许多系统使用这两种方法的混合。
基于内容的过滤
基于内容的过滤使用项目的内容,它被表示为一组描述符术语,将它们与用户简档进行匹配。使用从用户先前查看过的项目中提取的相同术语来构建用户简档。一个典型的在线书店将从文本中提取关键词来创建用户档案并提出建议。尽管在需要特定领域知识的情况下,可能需要手动添加这些术语,但是在许多情况下,提取这些术语的过程可以是自动的。在处理非基于文本的项目时,术语的手动添加尤其重要。比如说,通过将护舷放大器与电吉他联系起来,从图书馆中提取关键词相对容易。在许多情况下,这将涉及到人类基于特定的领域知识来创建这些关联,例如通过将挡泥板放大器与电吉他相关联。一旦构建完成,我们需要选择一种能够学习用户配置文件并做出适当推荐的学习算法。最常用的两个模型是向量空间模型和潜在语义索引模型。使用向量空间模型,我们创建了一个表示文档的稀疏向量,其中文档中的每个不同术语对应于向量的一个维度。权重用于指示术语是否出现在文档中。当它出现时,它显示 1 的重量,当它不出现时,它显示 0 的重量。基于单词出现次数的权重也被使用。
替代模型,潜在语义索引,可以在几个方面改进向量模型。考虑这样一个事实,相同的概念经常被许多不同的词描述,也就是说,用同义词。例如,我们需要知道,在大多数情况下,计算机显示器和计算机屏幕是一回事。此外,考虑到许多单词有不止一个不同的含义,例如,单词鼠标可以是动物或计算机界面。语义索引通过建立一个术语文档矩阵来整合这些信息。每个条目代表一个特定术语在文件中出现的次数。一组文档中的每个术语有一行,每个文档有一列。通过被称为单值分解的数学过程,这个单个矩阵可以被分解成三个矩阵,这三个矩阵将文档和术语表示为因子值的向量。本质上,这是一种降维技术,我们通过它创建代表多个单词的单个特征。基于这些派生特征提出了一个建议。该建议基于文档中的语义关系,而不是简单地匹配相同的单词。这种技术的缺点是计算量大,运行速度慢。对于一个必须实时工作的推荐系统来说,这可能是一个很大的限制。
协同过滤
协同过滤采用不同的方法,用于多种设置,尤其是在社交媒体的环境中,有多种实现方式。大多数采取街区的方式。这是基于这样一种想法,即你更有可能相信你朋友的推荐,或者那些有相似兴趣的人,而不是那些你不太熟悉的人。
在这种方法中,使用其他人推荐的加权平均值。权重由个体之间的相关性决定。也就是说,偏好相似的人将比不太相似的人获得更高的权重。在拥有成千上万用户的大型系统中,在运行时计算所有权重变得不可行。取而代之的是使用邻域的推荐。通过使用特定的权重阈值或者通过基于最高相关性的选择来选择该邻域。
在下面的代码中,我们使用了用户字典和他们对音乐专辑的评价。当我们绘制用户对两张专辑的评分时,这种模型的几何性质最为明显。很容易看出,用户在剧情上的距离很好地表明了他们的评分有多相似。欧几里得距离衡量用户之间的距离,即他们的偏好匹配程度。我们还需要一种方法来考虑两个人之间的联系,为此我们使用皮尔逊相关指数。一旦我们能够计算出用户之间的相似度,我们就按照相似度的顺序对他们进行排名。从这里,我们可以计算出哪些专辑可以推荐。这是通过将每个用户的相似性分数乘以他们的评分来实现的。然后将其相加并除以相似性得分,本质上是基于相似性得分计算加权平均值。
另一种方法是找到项目之间的相似性。这叫基于项目的协同过滤;这与我们用来计算相似度得分的基于用户的协同过滤形成对比。基于项目的方法是为每个项目找到相似的项目。一旦我们有了所有专辑之间的相似之处,我们就可以为特定用户生成推荐。
让我们看一下示例代码实现:
import pandas as pd
from scipy.stats import pearsonr
import matplotlib.pyplot as plt
userRatings={'Dave': {'Dark Side of Moon': 9.0,
'Hard Road': 6.5,'Symphony 5': 8.0,'Blood Cells': 4.0},'Jen': {'Hard Road': 7.0,'Symphony 5': 4.5,'Abbey Road':8.5,'Ziggy Stardust': 9,'Best Of Miles':7},'Roy': {'Dark Side of Moon': 7.0,'Hard Road': 3.5,'Blood Cells': 4,'Vitalogy': 6.0,'Ziggy Stardust': 8,'Legend': 7.0,'Abbey Road': 4},'Rob': {'Mass in B minor': 10,'Symphony 5': 9.5,'Blood Cells': 3.5,'Ziggy Stardust': 8,'Black Star': 9.5,'Abbey Road': 7.5},'Sam': {'Hard Road': 8.5,'Vitalogy': 5.0,'Legend': 8.0,'Ziggy Stardust': 9.5,'U2 Live': 7.5,'Legend': 9.0,'Abbey Road': 2},'Tom': {'Symphony 5': 4,'U2 Live': 7.5,'Vitalogy': 7.0,'Abbey Road': 4.5},'Kate': {'Horses': 8.0,'Symphony 5': 6.5,'Ziggy Stardust': 8.5,'Hard Road': 6.0,'Legend': 8.0,'Blood Cells': 9,'Abbey Road': 6}}
# Returns a distance-based similarity score for user1 and user2
def distance(prefs,user1,user2):
# Get the list of shared_items
si={}
for item in prefs[user1]:
if item in prefs[user2]:
si[item]=1
# if they have no ratings in common, return 0
if len(si)==0: return 0
# Add up the squares of all the differences
sum_of_squares=sum([pow(prefs[user1][item]-prefs[user2][item],2)
for item in prefs[user1] if item in prefs[user2]])
return 1/(1+sum_of_squares)
def Matches(prefs,person,n=5,similarity=pearsonr):
scores=[(similarity(prefs,person,other),other)
for other in prefs if other!=person]
scores.sort( )
scores.reverse( )
return scores[0:n]
def getRecommendations(prefs,person,similarity=pearsonr):
totals={}
simSums={}
for other in prefs:
if other==person: continue
sim=similarity(prefs,person,other)
if sim<=0: continue
for item in prefs[other]:
# only score albums not yet rated
if item not in prefs[person] or prefs[person][item]==0:
# Similarity * Score
totals.setdefault(item,0)
totals[item]+=prefs[other][item]*sim
# Sum of similarities
simSums.setdefault(item,0)
simSums[item]+=sim
# Create a normalized list
rankings=[(total/simSums[item],item) for item,total in totals.items( )]
# Return a sorted list
rankings.sort( )
rankings.reverse( )
return rankings
def transformPrefs(prefs):
result={}
for person in prefs:
for item in prefs[person]:
result.setdefault(item,{})
# Flip item and person
result[item][person]=prefs[person][item]
return result
transformPrefs(userRatings)
def calculateSimilarItems(prefs,n=10):
# Create a dictionary similar items
result={}
# Invert the preference matrix to be item-centric
itemPrefs=transformPrefs(prefs)
for item in itemPrefs:
# if c%100==0: print("%d / %d" % (c,len(itemPrefs)))
scores=Matches(itemPrefs,item,n=n,similarity=distance)
result[item]=scores
return result
def getRecommendedItems(prefs,itemMatch,user):
userRatings=prefs[user]
scores={}
totalSim={}
# Loop over items rated by this user
for (item,rating) in userRatings.items( ):
# Loop over items similar to this one
for (similarity,item2) in itemMatch[item]:
# Ignore if this user has already rated this item
if item2 in userRatings: continue
# Weighted sum of rating times similarity
scores.setdefault(item2,0)
scores[item2]+=similarity*rating
# Sum of all the similarities
totalSim.setdefault(item2,0)
totalSim[item2]+=similarity
# Divide each total score by total weighting to get an average
rankings=[(score/totalSim[item],item) for item,score in scores.items( )]
# Return the rankings from highest to lowest
rankings.sort( )
rankings.reverse( )
return rankings
itemsim=calculateSimilarItems(userRatings)
def plotDistance(album1, album2):
data=[]
for i in userRatings.keys():
try:
data.append((i,userRatings[i][album1], userRatings[i][album2]))
except:
pass
df=pd.DataFrame(data=data, columns = ['user', album1, album2])
plt.scatter(df[album1],df[album2])
plt.xlabel(album1)
plt.ylabel(album2)
for i,t in enumerate(df.user):
plt.annotate(t,(df[album1][i], df[album2][i]))
plt.show()
print(df)
plotDistance('Abbey Road', 'Ziggy Stardust')
print(getRecommendedItems(userRatings, itemsim,'Dave'))
您将看到以下输出:
这里我们已经绘制了两张专辑的用户评分,基于此,我们可以看到用户凯特和罗布是比较接近的,也就是他们对于这两张专辑的喜好是相似的。另一方面,用户 Rob 和 Sam 相距甚远,说明对这两张专辑的喜好不同。我们还为用户打印出推荐戴夫以及每张推荐专辑的相似度评分。
由于协同过滤依赖于其他用户的评分,当文档的数量变得比评分的数量大得多时,就会出现问题,因此用户评分的项目数量在所有项目中只占很小的比例。有几种不同的方法可以帮助您解决这个问题。评级可以从他们在网站上浏览的项目类型中推断出来。另一种方法是以混合方式用基于内容的过滤来补充用户的评分。
回顾案例研究
本案例研究的一些重要方面如下:
- 它是网络应用的一部分。它必须实时运行,并且依赖于用户交互。
- 有广泛的实践和理论资源可用。这是一个经过深思熟虑的问题,有几个明确的解决方案。我们不必重新发明轮子。
- 这主要是一个营销项目。它有一个基于推荐的销量成功的量化指标。
- 失败的代价相对较低。少量的误差是可以接受的。
温室昆虫检测
不断增长的人口和不断增加的气候变化给 21 世纪的农业带来了独特的挑战。温室等受控环境提供最佳生长条件和最大限度地有效利用水和养分等投入的能力,将使我们能够在不断变化的全球气候中继续养活不断增长的人口。
现在有许多食品生产系统基本上是自动化的,这些系统可能相当复杂。水产养殖系统可以在鱼缸和生长架之间循环营养和水,本质上是在人工环境中创造一个非常简单的生态。水的营养成分是受控制的,温度、湿度和二氧化碳含量也是如此。这些特征存在于非常精确的范围内,以优化生产。
温室内的环境条件可能非常有利于疾病和害虫的快速传播。早期发现和检测前体症状,如真菌或昆虫产卵,对控制这些疾病和害虫至关重要。出于环境、食品质量和经济方面的原因,我们希望只应用最低限度的目标控制,因为这主要涉及应用、杀虫剂或任何其他生物制剂。
这里的目标是创建一个自动系统,该系统将检测疾病或昆虫的类型和位置,然后选择并理想地实施控制。这是一项相当大的事业,有许多不同的组成部分。许多技术是分开存在的,但是在这里我们以许多非标准的方式将它们结合起来。该方法主要是实验性的:
通常的检测方法是直接人类观察。这是一项非常耗时的任务,需要一些特殊的技能。它也非常容易出错。自动化这本身将有巨大的好处,也是创建自动化 IPM 系统的重要起点。首要任务之一是为每个目标确定一套指标。一种自然的方法是让专家或专家小组将短视频剪辑分类为无害虫或感染了一种或多种目标物种。接下来,在这些片段上训练分类器,希望它能够获得预测。这种方法在过去已经被使用,例如,温室中的早期害虫检测(马丁,莫伊桑,2004),用于检测害虫。
在典型的设置中,摄像机被放置在整个温室中,以最大化采样面积。为了早期发现害虫,像茎、叶节和其他区域这样的关键植物器官是有针对性的。由于视频和图像分析的计算成本很高,因此可以使用对运动敏感的摄像机,这些摄像机被智能编程,在检测到昆虫运动时开始记录。
早期暴发中的变化相当微妙,可以被指示为植物损伤、变色、生长减少和昆虫或其卵的存在的组合。温室中多变的光照条件加剧了这一困难。处理这些问题的一种方法是使用认知视觉方法。这将问题分成许多子问题,每个子问题都依赖于上下文。例如,当天气晴朗时,或者根据一天中不同时间的光照条件,使用不同的模型。这方面的知识可以在初步的弱学习阶段构建到模型中。这给了它一个内在的启发,在给定的环境中应用适当的学习算法。
一个重要的要求是我们要区分不同的昆虫种类,一种方法是通过捕捉昆虫的动态成分,也就是它们的行为。许多昆虫可以通过它们的运动类型来区分,例如,在紧密的圆圈中飞行,或者在短时间内静止不动。此外,昆虫可能有其他行为,如交配或产卵,这可能是需要控制的重要指标。
监控可以通过多种渠道进行,最著名的是视频和静态摄影,以及使用来自其他传感器的信号,如红外、温度和湿度传感器。所有这些输入都需要打上时间和位置标记,以便在机器学习模型中有意义地使用。
视频处理首先涉及减去背景并分离序列的运动分量。在像素级,照明条件导致强度、饱和度和像素间对比度的变化。在图像级别,阴影等条件只影响图像的一部分,而背光会影响整个图像。
在本例中,我们从视频记录中提取帧,并在系统中的单独路径中处理它们。与视频处理相反,在视频处理中,我们对一段时间内的帧序列感兴趣,以便检测运动,而在视频处理中,我们对来自几个摄像机的单个帧感兴趣,这些帧同时聚焦在同一位置。这样,我们可以建立一个三维模型,这可能是有用的,特别是对于跟踪生物量的变化。
我们的机器学习模型的最终输入是环境传感器。标准控制系统测量温度、相对湿度、二氧化碳水平和光线。此外,高光谱和多光谱传感器能够检测可见光谱之外的频率。这些信号的性质需要它们自己独特的处理路径。作为如何使用它们的一个例子,考虑我们的目标之一是一种我们知道存在于狭窄湿度和温度范围内的真菌。假设温室的一部分中的紫外线传感器短暂地检测到指示真菌的频率范围。我们的模型会记录这一点,如果湿度和温度在这个范围内,那么可以启动控制。这种控制可能只是在可能爆发的地方打开通风口或打开风扇,将该地区局部冷却到真菌无法生存的温度。
显然,系统最复杂的部分是动作控制器。这实际上包括两个元素:输出表示目标害虫存在与否的二进制向量的多标签分类器和输出控制策略的动作分类器本身。
检测各种病原体和害虫需要许多不同的组件和许多不同的系统。标准的方法是为每个目标创建一个单独的学习模型。如果我们把每一个都作为独立的、不相关的活动来进行控制,那么这种多模型的方法是有效的。然而,许多过程,如疾病的发展和传播以及昆虫的突然爆发,可能是由一个共同的原因引起的。
回顾案例研究
本案例研究的一些重要方面如下:
- 这主要是一个研究项目。它有一个很长的时间表,涉及大量的未知。
- 它包括许多相互关联的系统。每一个都可以单独工作,但是在某些时候需要集成回整个系统。
- 它需要大量的领域知识。
机器学习一目了然
物理设计过程(涉及人类、决策、约束,最重要的是:不可预测性)与我们正在构建的机器学习系统有相似之处。分类器的决策边界、数据约束以及使用随机性来初始化或在模型中引入多样性只是我们可以建立的三个连接。更深层次的问题是,我们能把这个类比理解到什么程度。如果我们试图构建人工智能,问题是,“我们是在试图复制人类智能的过程,还是简单地模仿其后果,即做出合理的决策?”这当然是激烈的哲学讨论的时机已经成熟,虽然有趣,但与目前的讨论基本无关。然而,重要的一点是,通过观察自然系统,如大脑,并试图模仿它们的行为,可以学到很多东西。
真正的人类决策发生在更广泛的复杂大脑活动背景下,在设计过程的设定中,我们做出的决策往往是群体决策。与人工神经网络集成的类比是不可抗拒的。就像一群学习能力很弱的候选人一样,在一个项目的整个生命周期中,所做的决定最终会产生比任何个人贡献都大得多的结果。重要的是,一个不正确的决定,类似于决策树中的不良分裂,不会浪费时间,因为弱学习者的部分作用是排除不正确的可能性。在一个复杂的机器学习项目中,意识到许多工作没有直接导致成功的结果可能会令人沮丧。最初的重点应该是提供令人信服的论据,证明积极的结果是可能的。
当然,机器学习系统和设计过程本身之间的类比过于简单。团队动力学中有许多东西不是用机器学习集成来表示的。例如,人类的决策发生在相当虚幻的情感、直觉和一生经历的背景下。此外,团队动态往往是由人员野心、微妙的偏见以及团队成员之间的关系所塑造的。重要的是,管理团队必须融入设计过程。
任何规模的机器学习项目都需要协作。空间太大了,任何一个人都不可能完全认识到所有不同的相互关联的因素。如果不是许多人努力发展理论、编写基本算法、收集和组织数据,即使是本书中概述的简单演示任务也是不可能的。
在时间和资源限制内成功地组织一个主要项目需要大量的技能,而这些不一定是软件工程师或数据科学家的技能。显然,我们必须定义在任何给定的背景下,成功意味着什么。一个理论研究项目,要么以一定程度的确定性,要么以较小程度的不确定性来否定或证明一个特定的理论,被认为是成功的。理解这些限制可能会给我们现实的期望,换句话说,一个可实现的成功标准。
最常见和最持久的限制之一是数据不足或不准确。数据收集方法是如此重要的一个方面,然而在许多项目中却被忽视了。数据收集过程是交互式的。不改变系统,就不可能询问任何动态系统。此外,系统的某些组件比其他组件更容易观察,因此,可能会成为更广泛的未观察或不可观察组件的不准确表示。在许多情况下,我们对复杂系统的了解与我们不了解的相比相形见绌。这种不确定性嵌入在物理现实的随机本质中,也是我们在任何预测任务中必须诉诸概率的原因。对于给定的行动,决定什么样的概率水平是可接受的,比如根据疾病的估计概率来治疗潜在的患者,这取决于治疗疾病与否的后果,而这通常取决于人,无论是医生还是患者,来做出最终的决定。域外有许多问题可能会影响这样的决定。
人类问题的解决,虽然有很多相似之处,但却是与机器问题解决的根本区别。它依赖于很多东西,尤其是情绪和身体状态,也就是神经系统被包裹在其中的化学和电浴。人类的思维不是一个确定性的过程,这其实是一件好事,因为它使我们能够以新颖的方式解决问题。创造性问题解决包括将不同的想法或概念联系起来的能力。通常,这个灵感来自一个完全不相关的事件,众所周知的牛顿的苹果。人类大脑将每天经历的这些经常是随机的事件编织成某种连贯的、有意义的结构的能力,是我们渴望在机器中建立的虚幻能力。
总结
毫无疑问,在机器学习中最难做的事情是将其应用于独特的、以前未解决的问题。我们已经试验了许多示例模型,并使用了一些最流行的机器学习算法。现在的挑战是将这些知识应用到你关心的重要新问题上。我希望这本书以某种方式向您介绍了使用 Python 进行机器学习的可能性。