Sklearn 机器学习应用实用指南(一)
一、Scikit-Learn 简介
Scikit-Learn 是一个 Python 库,为实现监督和非监督机器学习算法提供了简单高效的工具。该库对每个人都是可访问的,因为它是开源的,并且是商业可用的。它构建在 NumPY、SciPy 和 matplolib 库之上,这意味着它是可靠的、健壮的,并且是 Python 语言的核心。
Scikit-Learn 专注于数据建模,而不是数据加载、清理、管理或操作。它也非常容易使用,而且相对来说没有编程错误。
机器学习
机器学习是让计算机自己编程。我们使用算法来实现这一点。一个算法是一套用于计算机计算或解决问题的规则。
机器学习提倡创建、研究和应用算法来提高数据驱动任务的性能。他们通过训练机器如何学习,使用工具和技术来回答有关数据的问题。
目标是构建健壮的算法,该算法可以操纵输入数据来预测输出,同时随着新数据的出现不断更新输出。发送到计算机的任何信息或数据都被认为是输入。计算机产生的数据被认为是输出。
在机器学习社区中,输入数据被称为特征集,输出数据被称为目标。该特征集也被称为特征空间。样本数据通常被称为训练数据。一旦用样本数据训练了算法,它就可以对新数据进行预测。新数据通常被称为测试数据。
机器学习分为两个主要领域:监督学习和非监督学习。由于机器学习通常侧重于基于从训练数据中学习到的已知属性的预测,因此我们的重点是监督学习。
监督学习是指数据集同时包含输入(或特征集)和期望输出(或目标)。也就是说,我们知道数据的属性。目标是做出预测。这种监督算法训练的能力是机器学习变得如此流行的很大一部分原因。
为了对新数据进行分类或回归,我们必须对具有已知结果的数据进行训练。我们通过将数据组织成相关的类别来对其进行分类。我们通过找到特征集数据和目标数据之间的关系来回归数据。
有了无监督学习,数据集只包含输入,没有想要的输出(或目标)。目标是探索数据并找到一些组织数据的结构或方法。虽然这不是本书的重点,但我们将探索一些无监督学习场景。
蟒蛇
您可以使用任何 Python 安装,但是出于几个原因,我建议使用 Anaconda 安装 Python。首先,它拥有超过 1500 万用户。其次,Anaconda 允许轻松安装所需的 Python 版本。第三,它预装了许多有用的机器学习库,包括 Scikit-Learn。点击这个链接查看您的操作系统和 Python 版本的 Anaconda 包列表: https://docs.anaconda.com/anaconda/packages/pkg-docs/ 。第四,它包括几个非常受欢迎的编辑器,包括 IDLE、Spyder 和 Jupyter 笔记本。第五,Anaconda 可靠且维护良好,消除了兼容性瓶颈。
你可以通过这个链接轻松下载安装 Anaconda:https://www.anaconda.com/download/。可以用这个链接更新: https://docs.anaconda.com/anaconda/install/update-version/ 。只需打开 Anaconda 并按照说明操作。我建议更新到当前版本。
Scikit-Learn
Python 的 Scikit-Learn 是最流行的机器学习库之一。它构建在 Python 库 NumPy、SciPy 和 Matplotlib 之上。该库是文档完善的、开源的、商业可用的,并且是开始机器学习的一个很好的工具。它也非常可靠且维护良好,其大量的算法集合可以很容易地集成到您的项目中。Scikit-Learn 专注于数据建模,而不是加载、操作、可视化和汇总数据。对于这样的活动,其他的库,比如 NumPy、pandas、Matplotlib 和 seaborn 也包括在内。Scikit-Learn 库作为 sklearn 导入到 Python 脚本中。
数据集
理解机器学习应用的一个很好的方法是通过 Python 数据驱动的代码例子。我们使用 Scikit-Learn、UCI 机器学习或 seaborn 数据集进行所有示例。Scikit-Learn 数据集包嵌入了一些小数据集,用于入门,并帮助获取机器学习库中常用的较大数据集,以对来自世界各地的数据进行算法基准测试。UCI 机器学习知识库维护 468 个数据集,以服务于机器学习社区。Seaborn 在 Matplotlib 的基础上提供了一个 API,该 API 在处理绘图样式、颜色默认值和便于可视化的常见统计绘图类型的高级函数时提供了简单性。它还很好地集成了 Pandas DataFrame 功能。
我们选择数据集作为我们的示例,因为机器学习社区使用它们进行学习、探索、基准测试和验证,因此我们可以在学习如何应用机器学习算法的同时将我们的结果与其他人进行比较。
我们的数据集分为分类数据和回归数据。分类数据的复杂性从简单到相对复杂不等。简单分类数据集包括 load_iris、load_wine、bank.csv 和 load_digits。复杂分类数据集包括 fetch_20newsgroups、MNIST 和 fetch_1fw_people。回归数据集包括 tips、redwine.csv、whitewine.csv 和 load_boston。
表征数据
在使用算法之前,最好先了解数据特征。每个数据集都经过精心选择,以帮助您获得机器学习最常见方面的经验。我们首先描述每个数据集的特征,以便更好地理解其组成和用途。数据集由分类和回归数据组织。
分类数据按照复杂性进一步组织。也就是说,我们从不复杂的简单分类数据集开始,以便读者可以专注于机器学习内容而不是数据。然后我们转向更复杂的数据集。
简单分类数据
分类是一种机器学习技术,用于预测因变量所属的类别。一个类是一个离散响应。在机器学习中,因变量通常被称为目标。基于数据集的独立变量来预测类别。自变量通常被称为特征集或特征空间。特征空间是用于表征数据的特征的集合。
简单数据集是那些具有有限数量特征的数据集。这样的数据集被称为具有低维特征空间的数据集。
虹膜数据
我们表征的第一个数据集是 load_iris,它由 Iris flower 数据组成。Iris 是一个多变量数据集,由来自三种鸢尾(鸢尾、海滨鸢尾和杂色鸢尾)的每一种的 50 个样本组成。每个样本包含四个特征,即以厘米为单位的萼片和花瓣的长度和宽度。Iris 是机器学习分类的典型测试用例。它也是数据科学文献中最著名的数据集之一,这意味着您可以根据许多其他可验证的示例来测试您的结果。
清单 1-1 中显示的第一个代码示例加载 Iris 数据,显示它的键、特征集和目标的形状、特征和目标名称、来自 DESCR 键的片段以及特征重要性(从最重要到最不重要)。
from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier
if __name__ == "__main__":
br = '\n'
iris = datasets.load_iris()
keys = iris.keys()
print (keys, br)
X = iris.data
y = iris.target
print ('features shape:', X.shape)
print ('target shape:', y.shape, br)
features = iris.feature_names
targets = iris.target_names
print ('feature set:')
print (features, br)
print ('targets:')
print (targets, br)
print (iris.DESCR[525:900], br)
rnd_clf = RandomForestClassifier(random_state=0,
n_estimators=100)
rnd_clf.fit(X, y)
rnd_name = rnd_clf.__class__.__name__
feature_importances = rnd_clf.feature_importances_
importance = sorted(zip(feature_importances, features),
reverse=True)
print ('most important features' + ' (' + rnd_name + '):')
[print (row) for i, row in enumerate(importance)]
Listing 1-1Characterize the Iris data set
继续执行清单 1-1 中的代码。请记住,您可以从本书的示例下载中找到示例。您不需要手动键入示例。更容易访问示例下载和复制/粘贴。
执行清单 1-1 的输出应该如下所示:
dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names', 'filename'])
features shape: (150, 4)
target shape: (150,)
feature set:
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
targets:
['setosa' 'versicolor' 'virginica']
============== ==== ==== ======= ===== ====================
Min Max Mean SD Class Correlation
============== ==== ==== ======= ===== ====================
sepal length: 4.3 7.9 5.84 0.83 0.7826
sepal width: 2.0 4.4 3.05 0.43 -0.4194
petal length: 1.0 6.9 3.76 1.76 0.9490 (high!)
petal width:
most important features (RandomForestClassifier):
(0.4604447396171521, 'petal length (cm)')
(0.4241162651271012, 'petal width (cm)')
(0.09090795402103086, 'sepal length (cm)')
(0.024531041234715754, 'sepal width (cm)')
代码从导入数据集和 RandomForestClassifier 包开始。RandomForestClassifier 是一种集成学习方法,它在训练时构建大量决策树,并输出作为类模式的类。
在这个例子中,我们仅使用它来返回特征重要性。主程序块首先加载数据并显示其特征。将特征集数据加载到变量 X 并将目标数据加载到变量 y 是机器学习社区的惯例。
代码通过在 pandas 数据上训练 RandomForestClassifier 得出结论,因此它可以返回特征重要性。当实际建模数据时,我们将 pandas 数据转换为 NumPy 以获得最佳性能。请记住,这些键是可用的,因为数据集嵌入在 Scikit-Learn 中。
注意,我们只从 DESCR 中提取了一小部分,其中包含了关于数据集的大量信息。我总是建议在开始任何机器学习实验之前,至少显示原始数据集的形状。
小费
RandomForestClassifier 是一种强大的机器学习算法,不仅可以对训练数据进行建模,还可以返回特征重要性。
擦除数据
我们表征的下一个数据集是 load_wine。load_wine 数据集由 178 个数据元素组成。每个元素有十三个特征,描述了三个目标类。它被认为是机器学习社区中的经典,并提供了一个简单的多分类数据集。
清单 1-2 中显示的下一个代码示例加载 wine 数据,并显示它的键、特性集和目标的形状、特性和目标名称、DESCR 键的一个片段以及特性重要性(从最重要到最不重要)。
from sklearn.datasets import load_wine
from sklearn.ensemble import RandomForestClassifier
if __name__ == "__main__":
br = '\n'
data = load_wine()
keys = data.keys()
print (keys, br)
X, y = data.data, data.target
print ('features:', X.shape)
print ('targets', y.shape, br)
print (X[0], br)
features = data.feature_names
targets = data.target_names
print ('feature set:')
print (features, br)
print ('targets:')
print (targets, br)
rnd_clf = RandomForestClassifier(random_state=0,
n_estimators=100)
rnd_clf.fit(X, y)
rnd_name = rnd_clf.__class__.__name__
feature_importances = rnd_clf.feature_importances_
importance = sorted(zip(feature_importances, features),
reverse=True)
n = 6
print (n, 'most important features' + ' (' + rnd_name + '):')
[print (row) for i, row in enumerate(importance) if i < n]
Listing 1-2Characterize load_wine
在执行清单 1-2 中的代码后,您的输出应该如下所示:
dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names'])
features: (178, 13)
targets (178,)
[1.423e+01 1.710e+00 2.430e+00 1.560e+01 1.270e+02 2.800e+00 3.060e+00
2.800e-01 2.290e+00 5.640e+00 1.040e+00 3.920e+00 1.065e+03]
feature set:
['alcohol', 'malic_acid', 'ash', 'alcalinity_of_ash', 'magnesium', 'total_phenols', 'flavanoids', 'nonflavanoid_phenols', 'proanthocyanins', 'color_intensity', 'hue', 'od280/od315_of_diluted_wines', 'proline']
targets:
['class_0' 'class_1' 'class_2']
6 most important features (RandomForestClassifier):
(0.19399882779940295, 'proline')
(0.16095401215681593, 'flavanoids')
(0.1452667364559143, 'color_intensity')
(0.11070045042456281, 'alcohol')
(0.1097465262717493, 'od280/od315_of_diluted_wines')
(0.08968972021098301, 'hue')
小费
要创建(实例化)一个机器学习算法(模型),只需将其赋给一个变量(例如 model = algorithm())。要基于模型进行训练,只需使其符合数据即可(例如,model.fit(X,y))。
代码从导入 load_wine 和 RandomForestClassifier 开始。主块显示关键点,将数据加载到 X 和 y 中,显示特征集 X 中的第一个向量,显示形状,并显示特征集和目标信息。代码以用 RandomForestClassifier 训练 X 结束,因此我们可以显示六个最重要的特性。请注意,我们显示了特性集 X 中的第一个向量,以验证所有特性都是数字的。
银行数据
清单 1-3 中显示的下一个代码示例处理银行数据。bank.csv 数据集由一家葡萄牙银行机构的直接营销活动组成。目标由客户是否会认购(是/否)定期存款(目标标签为 y)来描述。它由 41188 个数据元素组成,每个元素有 20 个特征。该网站还提供了 4119 个数据元素的 10%随机样本,用于计算成本更高的算法,如 svm 和 KNeighborsClassifier。
import pandas as pd
if __name__ == "__main__":
br = '\n'
f = 'data/bank.csv'
bank = pd.read_csv(f)
features = list(bank)
print (features, br)
X = bank.drop(['y'], axis=1).values
y = bank['y'].values
print (X.shape, y.shape, br)
print (bank[['job', 'education', 'age', 'housing',
'marital', 'duration']].head())
Listing 1-3Characterize bank data
在执行清单 1-3 中的代码后,您的输出应该如下所示:
['age', 'job', 'marital', 'education', 'default', 'housing', 'loan', 'contact', 'month', 'day_of_week', 'duration', 'campaign', 'pdays', 'previous', 'poutcome', 'emp.var.rate', 'cons.price.idx', 'cons.conf.idx', 'euribor3m', 'nr.employed', 'y']
(41188, 20) (41188,)
job education age housing marital duration
0 housemaid basic.4y 56 no married 261
1 services high.school 57 no married 149
2 services high.school 37 yes married 226
3 admin. basic.6y 40 no married 151
4 services high.school 56 no married 307
代码示例从导入 pandas 包开始。主程序块将银行数据从 CSV 文件加载到 Pandas 数据框架中,并显示列名(或特征)。要从 pandas 中检索列名,我们需要做的就是将数据帧做成一个列表,并将结果赋给一个变量。接下来,创建特征集 X 和目标 y。最后,会显示 X 和 y 形状以及一些选择功能。
数字数据
本小节的最后一个代码示例是 load_digits。load_digits 数据集由 1797 幅 8 × 8 的手写图像组成。每幅图像由 64 个像素(基于 8 × 8 矩阵)表示,构成了特征集。预测了十个目标,用数字 0 到 9 表示。
清单 1-4 包含表征 load_digits 的代码。
import numpy as np
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt
if __name__ == "__main__":
br = '\n'
digits = load_digits()
print (digits.keys(), br)
print ('2D shape of digits data:', digits.images.shape, br)
X = digits.data
y = digits.target
print ('X shape (8x8 flattened to 64 pixels):', end=' ')
print (X.shape)
print ('y shape:', end=' ')
print (y.shape, br)
i = 500
print ('vector (flattened matrix) of "feature" image:')
print (X[i], br)
print ('matrix (transformed vector) of a "feature" image:')
X_i = np.array(X[i]).reshape(8, 8)
print (X_i, br)
print ('target:', y[i], br)
print ('original "digits" image matrix:')
print (digits.images[i])
plt.figure(1, figsize=(3, 3))
plt.title('reshaped flattened vector')
plt.imshow(X_i, cmap="gray", interpolation="gaussian")
plt.figure(2, figsize=(3, 3))
plt.title('original images dataset')
plt.imshow(digits.images[i], cmap="gray",
interpolation='gaussian')
plt.show()
Listing 1-4Characterize load_digits
在执行清单 1-4 中的代码后,您的输出应该如下所示:
dict_keys(['data', 'target', 'target_names', 'images', 'DESCR'])
2D shape of digits data: (1797, 8, 8)
X shape (8x8 flattened to 64 pixels): (1797, 64)
y shape: (1797,)
vector (flattened matrix) of "feature" image:
[ 0\. 0\. 3\. 10\. 14\. 3\. 0\. 0\. 0\. 8\. 16\. 11\. 10\. 13\. 0\. 0\. 0\. 7.
14\. 0\. 1\. 15\. 2\. 0\. 0\. 2\. 16\. 9\. 16\. 16\. 1\. 0\. 0\. 0\. 12\. 16.
15\. 15\. 2\. 0\. 0\. 0\. 12\. 10\. 0\. 8\. 8\. 0\. 0\. 0\. 9\. 12\. 4\. 7.
12\. 0\. 0\. 0\. 2\. 11\. 16\. 16\. 9\. 0.]
matrix (transformed vector) of a "feature" image:
[[ 0\. 0\. 3\. 10\. 14\. 3\. 0\. 0.]
[ 0\. 8\. 16\. 11\. 10\. 13\. 0\. 0.]
[ 0\. 7\. 14\. 0\. 1\. 15\. 2\. 0.]
[ 0\. 2\. 16\. 9\. 16\. 16\. 1\. 0.]
[ 0\. 0\. 12\. 16\. 15\. 15\. 2\. 0.]
[ 0\. 0\. 12\. 10\. 0\. 8\. 8\. 0.]
[ 0\. 0\. 9\. 12\. 4\. 7\. 12\. 0.]
[ 0\. 0\. 2\. 11\. 16\. 16\. 9\. 0.]]
target: 8
original "digits" image matrix:
[[ 0\. 0\. 3\. 10\. 14\. 3\. 0\. 0.]
[ 0\. 8\. 16\. 11\. 10\. 13\. 0\. 0.]
[ 0\. 7\. 14\. 0\. 1\. 15\. 2\. 0.]
[ 0\. 2\. 16\. 9\. 16\. 16\. 1\. 0.]
[ 0\. 0\. 12\. 16\. 15\. 15\. 2\. 0.]
[ 0\. 0\. 12\. 10\. 0\. 8\. 8\. 0.]
[ 0\. 0\. 9\. 12\. 4\. 7\. 12\. 0.]
[ 0\. 0\. 2\. 11\. 16\. 16\. 9\. 0.]]
列表 1-4 还显示了数字 1-1 和 1-2 。图 1-1 是数据集中第 500 张图像的整形展平矢量。特征集 X 中的每个数据元素都表示为一个 64 像素的展平向量,因为 Scikit-Learn 无法识别 8 × 8 的图像矩阵,所以我们必须将第 500 个向量整形为 8 × 8 的图像矩阵才能可视化。图 1-2 是我们将数据加载到变量数字时,直接从可用的图像数据集中获取的第 500 张图像。
图 1-2
第 500 个数据元素的原始图像矩阵
图 1-1
第 500 个数据元素的整形扁平矢量
代码从导入 numpy、load_digits 和 matplotlib 包开始。主程序块将 load_digits 放入 digits 变量中,并显示其关键字:数据、目标、目标名称、图像和描述。它继续显示包含在图像中的图像的二维(2D)形状。图像中的数据由 1797 个 8 × 8 矩阵表示。接下来,特征数据(表示为向量)被放置在 X 中,目标数据被放置在 y 中。
特征向量是包含关于物体重要特征的信息的向量。数据中的数据由 1797 个 64 像素特征向量表示。图像的简单特征表示是每个像素的原始亮度值。因此,一个 8 × 8 的图像由 64 个像素表示。机器学习算法将特征数据作为向量进行处理,因此数据中的每个元素都必须是其 2D 图像矩阵的一维(1D)向量表示。
小费
特征数据必须由向量组成,以便与机器学习算法一起工作。
代码继续显示第 500 幅图像的特征向量。接下来,将第 500 个特征向量从其展平的 1D 向量形状转换成 2D 图像矩阵,并用 NumPy 整形函数显示。代码继续显示第 500 幅图像的目标值 y。接下来,通过参考图像来显示第 500 个图像矩阵。
我们将图像从其 1D 展平矢量状态转换为 2D 图像矩阵的原因是,大多数数据集不包括像 load_data 这样的 images 对象。因此,为了用机器学习算法可视化和处理数据,我们必须能够手动展平图像,并将展平的图像转换回其原始的 2D 矩阵形状。
代码以两种方式可视化第 500 张图片结束。首先,我们使用展平的矢量 X_i。其次,我们参考图像。虽然机器学习算法需要特征向量,但函数 imshow 需要 2D 图像矩阵来可视化。
复杂分类数据
现在让我们处理更复杂的数据集。复杂数据集是那些具有大量特征的数据集。这样的数据集被称为具有高维特征空间的数据集。
新闻组数据
我们描述的第一个数据集是 fetch_20newsgroups,它由 20 个主题的大约 18000 篇帖子组成。数据被分成训练测试子集。这种拆分基于特定日期前后发布的消息。
清单 1-5 包含描述 fetch_20newsgroups 的代码。
from sklearn.datasets import fetch_20newsgroups
if __name__ == "__main__":
br = '\n'
train = fetch_20newsgroups(subset='train')
test = fetch_20newsgroups(subset='test')
print ('data:')
print (train.target.shape, 'shape of train data')
print (test.target.shape, 'shape of test data', br)
targets = test.target_names
print (targets, br)
categories = ['rec.autos', 'rec.motorcycles', 'sci.space',
'sci.med']
train = fetch_20newsgroups(subset='train',
categories=categories)
test = fetch_20newsgroups(subset='test',
categories=categories)
print ('data subset:')
print (train.target.shape, 'shape of train data')
print (test.target.shape, 'shape of test data', br)
targets = train.target_names
print (targets)
Listing 1-5Characterize fetch_20newsgroups
在执行清单 1-5 中的代码后,您的输出应该如下所示:
data:
(11314,) shape of train data
(7532,) shape of test data
['alt.atheism', 'comp.graphics', 'comp.os.ms-windows.misc', 'comp.sys.ibm.pc.hardware', 'comp.sys.mac.hardware', 'comp.windows.x', 'misc.forsale', 'rec.autos', 'rec.motorcycles', 'rec.sport.baseball', 'rec.sport.hockey', 'sci.crypt', 'sci.electronics', 'sci.med', 'sci.space', 'soc.religion.christian', 'talk.politics.guns', 'talk.politics.mideast', 'talk.politics.misc', 'talk.religion.misc']
data subset:
(2379,) shape of train data
(1584,) shape of test data
['rec.autos', 'rec.motorcycles', 'sci.med', 'sci.space']
代码从导入 fetch_20newsgroups 开始。主程序块首先加载训练和测试数据,并显示它们的形状。训练数据包含 11314 个帖子,而测试数据包含 7532 个帖子。代码继续显示目标名称和类别。接下来,从类别的子集创建训练和测试数据。代码以显示子集的形状和目标名称结束。
MNIST 数据
我们表征的下一个数据集是 MNIST。MNIST(改进的国家标准和技术研究所)是一个大型的手写数字数据库,通常用于机器学习社区和其他工业图像处理应用程序的训练和测试。MNIST 包含 70000 个手写数字图像的例子,从 0 到 9,大小为 28 × 28。每个目标(或标签)存储为一个数字值。该特征集是 70000 个 28 × 28 图像的矩阵,每个图像自动展平为 784 像素。因此,70000 个数据元素中的每一个都是长度为 784 的向量。目标集是一个 70000 位数值的向量。
清单 1-6 包含了 MNIST 特有的代码。
import numpy as np
from random import randint
import matplotlib.pyplot as plt
def find_image(data, labels, d):
for i, row in enumerate(labels):
if d == row:
target = row
X_pixels = np.array(data[i])
return (target, X_pixels)
if __name__ == "__main__":
br = '\n'
X = np.load('data/X_mnist.npy')
y = np.load('data/y_mnist.npy')
target = np.load('data/mnist_targets.npy')
print ('labels (targets):')
print (target, br)
print ('feature set shape:')
print (X.shape, br)
print ('target set shape:')
print (y.shape, br)
indx = randint(0, y.shape[0]-1)
target = y[indx]
X_pixels = np.array(X[indx])
print ('the feature image consists of', len(X_pixels),
'pixels')
X_image = X_pixels.reshape(28, 28)
plt.figure(1, figsize=(3, 3))
title = 'image @ indx ' + str(indx) + ' is digit ' \
+ str(int(target))
plt.title(title)
plt.imshow(X_image, cmap="gray")
digit = 7
target, X_pixels = find_image(X, y, digit)
X_image = X_pixels.reshape(28, 28)
plt.figure(2, figsize=(3, 3))
title = 'find first ' + str(int(target)) + ' in dataset'
plt.title(title)
plt.imshow(X_image, cmap="gray")
plt.show()
Listing 1-6Characterize MNIST
在执行清单 1-6 中的代码后,您的输出应该如下所示:
labels (targets):
[0\. 1\. 2\. 3\. 4\. 5\. 6\. 7\. 8\. 9.]
feature set shape:
(70000, 784)
target set shape:
(70000,)
the feature image consists of 784 pixels
列表 1-6 还显示了数字 1-3 和 1-4 。图 1-3 是索引 6969 处数字 1 的整形图像。图 1-4 是数据集中数字 7 的第一幅图像。
图 1-4
数据集中第一个数字 7 的图像
图 1-3
索引 6969 处图像的整形展平矢量
代码从导入 randint 和其他必需的包开始。函数 find_image 定位图像的第一次出现。主块将 NumPy 文件中的数据加载到功能集 X、目标 y 和目标中。可变目标保存目标标签。它继续显示 X 和 y 的形状。特征集 X 由 70000 个 784 像素的向量组成,因此每个图像由 28 × 28 像素组成。
目标 y 由 70000 个标签组成。接下来,生成一个介于 0 和 69999 之间的随机整数,因此我们可以从数据集中显示一个随机图像。我们例子中的随机整数是 6969。索引 6969 处的图像是数字 1。显示图像的大小以验证其为 784 像素。然后,我们将 vector 6969 整形为一个 28 × 28 的矩阵,这样我们就可以用函数 imshow 来可视化。代码通过找到第一个数字 7 并显示它来结束。
面孔数据
本小节中描述的最终数据集是 fetch_1fw_people。fetch_1fw_people 数据集用于分类标记的人脸。它包含 1288 张人脸图像和 7 个目标。每幅图像由一个 50 × 37 的像素矩阵表示,因此特征集有 1850 个特征(基于一个 50 × 37 的矩阵)。总之,该数据由 1288 个标记的人脸组成,每个人脸由 1850 个像素组成,每个像素预测七个目标。
清单 1-7 包含描述 fetch_1fw_people 的代码。
import numpy as np
import matplotlib.pyplot as plt
if __name__ == "__main__":
br = '\n'
X = np.load('data/X_faces.npy')
y = np.load('data/y_faces.npy')
targets = np.load('data/faces_targets.npy')
print ('shape of feature and target data:')
print (X.shape)
print (y.shape, br)
print ('target faces:')
print (targets)
X_i = np.array(X[0]).reshape(50, 37)
image_name = targets[y[0]]
fig, ax = plt.subplots()
image = ax.imshow(X_i, cmap="bone")
plt.title(image_name)
plt.show()
Listing 1-7Characterize fetch_1fw_people
在执行清单 1-7 中的代码后,您的输出应该如下所示:
shape of feature and target data:
(1288, 1850)
(1288,)
target faces:
['Ariel Sharon' 'Colin Powell' 'Donald Rumsfeld' 'George W Bush'
'Gerhard Schroeder' 'Hugo Chavez' 'Tony Blair']
列表 1-7 也显示图 1-5 。图 1-5 是数据集中第一个数据元素的整形图像。
图 1-5
数据集中第一个数据元素的整形图像
代码从导入必需的包开始。主块将数据从 NumPy 文件加载到 X、y 和 targets 中。代码继续打印 X 和 y 的形状。X 包含 1288 个 1850 像素的向量,y 包含 1288 个目标值。然后显示目标标签。代码最后将第一个特征向量整形为 50 × 37 的图像,并用函数 imshow 显示出来。
回归数据
我们现在从分类转向回归。回归是一种基于数据集的自变量(或特征集)预测数值的机器学习技术。也就是说,我们正在测量特性集对一个数字输出的影响。我们表征回归的第一个数据集是 tips。
小费数据
tips 数据集与 seaborn 库集成在一起。它由餐馆的服务员小费和相关因素组成,包括小费、饭菜价格和一天中的时间。具体来说,特征包括 total_bill(餐费)、小费(小费)、性别(男性或女性)、吸烟者(是或否)、日期(周四、周五、周六或周日)、时间(白天或晚上)以及聚会的规模。特征编码如下:总账单(美元)、小费(美元)、性别(0 =男性,1 =女性)、吸烟者(0 =否,1 =是)、日(3 =星期四,4=Fri,5=星期六,6 =星期日)。Tips 数据由 244 个元素表示,具有预测一个目标的六个特征。目标是从顾客那里得到的小费。
列表 1-8 表征 tips 数据。
import numpy as np, pandas as pd, seaborn as sns
if __name__ == "__main__":
br = '\n'
sns.set(color_codes=True)
tips = sns.load_dataset('tips')
print (tips.head(), br)
X = tips.drop(['tip'], axis=1).values
y = tips['tip'].values
print (X.shape, y.shape)
Listing 1-8Characterize the tips data set
在执行清单 1-8 中的代码后,您的输出应该如下所示:
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4
(244, 6) (244,)
代码首先以 Pandas 数据帧的形式加载提示,显示前五条记录,将数据转换为 NumPy,并显示特性集和目标形状。Seaborn 数据被自动加载为 Pandas 数据帧。我们无法获取要素重要性,因为 RandomForestClassifier 需要数值数据。将数据集转换成这种形式需要大量的数据争论。我们将在后面的章节中把分类数据转换成数字。
红酒和白酒
我们表征的下两个数据集是 redwine.csv 和 whitewine.csv。数据集 redwine.csv 和 whitewine.csv 分别与红葡萄酒和白葡萄酒的*质量、*相关。这两种葡萄酒都是由葡萄牙 Vinho Verde 葡萄酒的变种组成。
该特性集由 11 个属性组成。输入属性基于客观测试,如 pH(物质的酸度或碱度)和酒精(体积百分比)。输出质量是基于感官数据,报告为至少三个葡萄酒专家评估的中位数。每位专家将葡萄酒质量从 0(非常差)到 10(非常好)分等级。红酒数据集有 1599 个实例,而白酒数据集有 4898 个。
清单 1-9 描述了 redwine.csv。
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
if __name__ == "__main__":
br = '\n'
f = 'data/redwine.csv'
red_wine = pd.read_csv(f)
X = red_wine.drop(['quality'], axis=1)
y = red_wine['quality']
print (X.shape)
print (y.shape, br)
features = list(X)
rfr = RandomForestRegressor(random_state=0,
n_estimators=100)
rfr_name = rfr.__class__.__name__
rfr.fit(X, y)
feature_importances = rfr.feature_importances_
importance = sorted(zip(feature_importances, features),
reverse=True)
n = 3
print (n, 'most important features' + ' (' + rfr_name + '):')
[print (row) for i, row in enumerate(importance) if i < n]
for row in importance:
print (row)
print ()
print (red_wine[['alcohol', 'sulphates', 'volatile acidity',
'total sulfur dioxide', 'quality']]. head())
Listing 1-9Characterize redwine
在执行清单 1-9 中的代码后,您的输出应该如下所示:
(1599, 11)
(1599,)
3 most important features (RandomForestRegressor):
(0.27432500255956216, 'alcohol')
(0.13700073893077233, 'sulphates')
(0.13053941311188708, 'volatile acidity')
(0.27432500255956216, 'alcohol')
(0.13700073893077233, 'sulphates')
(0.13053941311188708, 'volatile acidity')
(0.08068199773601588, 'total sulfur dioxide')
(0.06294612644261727, 'chlorides')
(0.057730976351602854, 'pH')
(0.055499749756166, 'residual sugar')
(0.05198192402458334, 'density')
(0.05114079873500658, 'fixed acidity')
(0.049730883807319035, 'free sulfur dioxide')
(0.04842238854446754, 'citric acid')
alcohol sulphates volatile acidity total sulfur dioxide quality
0 9.4 0.56 0.70 34.0 5.0
1 9.8 0.68 0.88 67.0 5.0
2 9.8 0.65 0.76 54.0 5.0
3 9.8 0.58 0.28 60.0 6.0
4 9.4 0.56 0.70 34.0 5.0
代码示例从加载 pandas 和 RandomForestRegressor 包开始。主程序块将 redwine.csv 加载到 Pandas DataFrame 中。然后显示特征和目标形状。代码最后用 RandomForestRegressor 训练 pandas 数据,显示三个最重要的特性,并显示数据集中的前五条记录。RandomForestRegressor 也是一种集成算法,但它用于目标为数字或连续的情况。
小费
对于使用该参数来稳定结果的算法,总是硬编码 random_state (例如,random_state=0)。
白葡萄酒示例遵循完全相同的逻辑,但是输出在数据集大小和特性重要性方面有所不同。
列表 1-10 描述了 whitewine.csv。
import numpy as np, pandas as pd
from sklearn.ensemble import RandomForestRegressor
if __name__ == "__main__":
br = '\n'
f = 'data/whitewine.csv'
white_wine = pd.read_csv(f)
X = white_wine.drop(['quality'], axis=1)
y = white_wine['quality']
print (X.shape)
print (y.shape, br)
features = list(X)
rfr = RandomForestRegressor(random_state=0,
n_estimators=100)
rfr_name = rfr.__class__.__name__
rfr.fit(X, y)
feature_importances = rfr.feature_importances_
importance = sorted(zip(feature_importances, features),
reverse=True)
n = 3
print (n, 'most important features' + ' (' + rfr_name + '):')
[print (row) for i, row in enumerate(importance) if i < n]
print ()
print (white_wine[['alcohol', 'sulphates',
'volatile acidity',
'total sulfur dioxide',
'quality']]. head())
Listing 1-10Characterize whitewine
在执行清单 1-10 中的代码后,您的输出应该如下所示:
(4898, 11)
(4898,)
3 most important features (RandomForestRegressor):
(0.24186185906056268, 'alcohol')
(0.1251626059551235, 'volatile acidity')
(0.11524332271725685, 'free sulfur dioxide')
alcohol sulphates volatile acidity total sulfur dioxide quality
0 8.8 0.45 0.27 170.0 6.0
1 9.5 0.49 0.30 132.0 6.0
2 10.1 0.44 0.28 97.0 6.0
3 9.9 0.40 0.23 186.0 6.0
4 9.9 0.40 0.23 186.0 6.0
波士顿数据
我们表征的最终数据集是 load_boston。load_boston 数据集包含波士顿不同位置的房价。它由 506 条记录组成,有 13 个特征和一个目标。目标值代表业主自住房屋的中值。
清单 1-11 表征 load_boston。
from sklearn.datasets import load_boston
from sklearn.ensemble import RandomForestRegressor
if __name__ == "__main__":
br = '\n'
boston = load_boston()
print (boston.keys(), br)
print (boston.feature_names, br)
X = boston.data
y = boston.target
print ('feature shape', X.shape)
print ('target shape', y.shape, br)
keys = boston.keys()
rfr = RandomForestRegressor(random_state=0,
n_estimators=100)
rfr.fit(X, y)
features = boston.feature_names
feature_importances = rfr.feature_importances_
importance = sorted(zip(feature_importances, features),
reverse=True)
[print(row) for i, row in enumerate(importance) if i < 3]
Listing 1-11Characterize load_boston
在执行清单 1-11 中的代码后,您的输出应该如下所示:
dict_keys(['data', 'target', 'feature_names', 'DESCR', 'filename'])
['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO'
'B' 'LSTAT']
feature shape (506, 13)
target shape (506,)
(0.45730362625767496, 'RM')
(0.35008661885681375, 'LSTAT')
(0.06518862820215894, 'DIS')
代码从导入 load_boston 和 RandomForestRegressor 开始。主块显示键,将数据加载到 X 和 y 中,并显示要素和数据形状。代码继续创建 RandomForestRegressor 并用 X 和 y 对其进行训练,以便显示特征的重要性。
特征缩放
特性缩放正在标准化特性集数据。当要素集数据在量值、单位和范围方面变化很大时,要素缩放很重要。如果这样的数据没有被缩放,一些机器学习算法可能表现不佳,因为它们不能正确地考虑特征集数据差异。为了缓解这个问题,我们将方差标准化。标准化将要素重新调整为均值为零(μ = 0)和标准差为 1(σ= 1)的标准正态分布的属性。也就是说,我们通过移除平均值并缩放至单位方差来重新缩放要素。
Scikit-Learn 应用 StandardScaler,它通过移除平均值并缩放到单位方差来标准化特征。清单 1-12 中显示的代码示例演示了 StandardScaler 如何与机器学习算法一起工作。
import numpy as np
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.linear_model import SGDClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
if __name__ == "__main__":
br = '\n'
digits = load_digits()
X = digits.data
y = digits.target
X_train, X_test, y_train, y_test =\
train_test_split(X, y, random_state=0)
sgd = SGDClassifier(random_state=0, max_iter=1000,
tol=0.001)
sgd.fit(X_train, y_train)
sgd_name = sgd.__class__.__name__
print ('<<' + sgd_name + '>>', br)
y_pred = sgd.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print ('unscaled \'test\' accuracy:', accuracy)
scaler = StandardScaler().fit(X_train)
X_train_std, X_test_std = scaler.transform(X_train),\
scaler.transform(X_test)
sgd_std = SGDClassifier(random_state=0, max_iter=1000,
tol=0.001)
sgd_std.fit(X_train_std, y_train)
y_pred = sgd_std.predict(X_test_std)
accuracy = accuracy_score(y_test, y_pred)
print ('scaled \'test\' accuracy:', np.round(accuracy, 4))
Listing 1-12Scaling load_digits
在执行清单 1-12 中的代码后,您的输出应该如下所示:
<<SGDClassifier>>
unscaled 'test' accuracy: 0.92
scaled 'test' accuracy: 0.9333
代码示例首先加载 train_test_split、SGDClassifier、StandardScaler、accuracy_score 和其他必需的包。train_test_split 将数据集中的向量(数据元素)分割成随机的训练测试子集。机器学习算法使用训练子集进行训练,而测试子集用于验证。
将数据分成训练测试子集是机器学习的基础,因为模型从训练数据中学习,而测试数据被认为是模型从未见过的新数据。由于测试数据从未被模型看到,我们可以确信我们的准确度分数是有效的。所以,从来没有用测试数据进行训练!
SGDClassifier 是一种分类算法,通过随机梯度下降(SGD)学习实现正则化线性模型。每次对每个样本估计损失(或误差)的梯度,并且该模型沿着递减的强度时间表(或学习速率)进行更新。分类是预测给定数据点的目标。目标也称为类、标签或类别。分类将在接下来的两章中深入讨论。
accuracy_score 用于计算准确度。主程序块首先将 load_digits 放入特征集 X 和目标集 y。代码接着将 X 和 y 分成训练测试子集。X_train 和 y_train 用于训练。X_test 和 y_test 用于验证。接下来,创建模型并将其分配给变量 sgd。然后用 X_train 和 y_train 对模型进行训练。然后根据 X_test 进行预测,并将其分配给 y_pred。通常,预测是根据测试数据进行的,但我们可以根据训练数据进行预测,以了解我们的模型表现如何。代码继续缩放训练数据,用缩放的数据训练模型,并显示精度。注意缩放提高了精确度。
降维
降维(或特征)是通过获得一组主变量(或分量)来减少所考虑的随机变量的数量。主成分是一组线性不相关变量的值。
前提是数据包含一些冗余或不相关的特征,因此可以删除而不会丢失太多信息。然而,请记住,维度缩减总是会导致一些信息丢失。
降维可以简化模型,减少训练时间,减少过拟合,避免维数灾难。过拟合是模型对数据训练得太好。也就是说,模型完全理解数据,但也会产生噪声(或误差)。因此,不需要的噪声成为模型理解数据的一部分。当在高维空间(可能包含成百上千个维度)处理数据时,诅咒尤为突出,因为它使得分析和组织数据变得非常困难。在低维空间如 2D 或人类经验中常见的三维(3D)空间中处理数据要容易得多。
维数约简对于无监督学习是有用的。无监督学习从特征数据中进行推断,而不知道它们各自的标记响应(或目标)。无监督学习对于探索数据中隐藏的模式或分组很有用。
三种常见的 Scikit-Learn 降维技术是主成分分析(PCA)、线性判别分析(LDA)和 Isomap。PCA 和 LDA 是线性降维方法。Isomap 是一种非线性降维方法。
清单 1-13 中显示的第一个代码示例利用 Iris 上的 PCA 和 LDA 来识别聚类。
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
from sklearn.discriminant_analysis import\
LinearDiscriminantAnalysis
import seaborn as sns, matplotlib.pyplot as plt
if __name__ == "__main__":
br = '\n'
iris = load_iris()
X = iris.data
y = iris.target
pca = PCA(n_components=0.95)
X_reduced = pca.fit_transform(X)
components = pca.n_components_
model = PCA(n_components=components)
model.fit(X)
X_2D = model.transform(X)
iris_df = sns.load_dataset('iris')
iris_df['PCA1'] = X_2D[:, 0]
iris_df['PCA2'] = X_2D[:, 1]
print (iris_df[['PCA1', 'PCA2']].head(3), br)
sns.set(color_codes=True)
sns.lmplot('PCA1', 'PCA2', hue="species",
data=iris_df, fit_reg=False)
plt.suptitle('PCA reduction')
lda = LinearDiscriminantAnalysis(n_components=2)
transform_lda = lda.fit_transform(X, y)
iris_df['LDA1'] = transform_lda[:,0]
iris_df['LDA2'] = transform_lda[:,1]
print (iris_df[['LDA1', 'LDA2']].head(3))
sns.lmplot('LDA1', 'LDA2', hue="species",
data=iris_df, fit_reg=False)
plt.suptitle('LDA reduction')
plt.show()
Listing 1-13PCA and LDA Iris dimensionality reduction
在执行清单 1-13 中的代码后,您的输出应该如下所示:
PCA1 PCA2
0 -2.684126 0.319397
1 -2.714142 -0.177001
2 -2.888991 -0.144949
LDA1 LDA2
0 8.061800 0.300421
1 7.128688 -0.786660
2 7.489828 -0.265384
列表 1-13 还显示了数字 1-6 和 1-7 。图 1-6 展示了 PCA 降维如何用于聚类的无监督学习可视化。图 1-7 展示了使用 LDA 进行降维如何有助于聚类的无监督学习可视化。
图 1-7
LDA 降维
图 1-6
主成分分析降维
代码示例从导入 PCA、LinearDiscriminantAnalysis 和其他必需的包开始。PCA 降低了由许多相关变量组成的数据的维数,同时保留了其大部分变化(或信息)。线性判别分析(LinearDiscriminantAnalysis)是判别函数分析(discriminant function analysis),它是将事物分配到同一类型的组、类或类别中的行为。
主程序块将 Iris 加载到 X 和 y 中,然后创建一个信息损失为 5%的 PCA 模型,并将 X 转换到 2D 空间,这样我们就可以自动确定主成分的最佳数量。接下来,创建模型并对数据进行训练。代码继续将 Iris 加载到 Pandas 数据帧中,这样我们就可以将两个主要组件切掉以便可视化。代码继续创建 LDA 模型,并根据数据对其进行训练。
请注意,LDA 对 X 和 y 数据进行训练,而 PCA 只对 X 数据进行训练。这两种方法都很好地实现了三种鸢尾的聚类可视化。
清单 1-14 中显示的最终代码示例使用 Isomap 来识别 load_digits 上的集群。
from sklearn.datasets import load_digits
from sklearn.manifold import Isomap
import matplotlib.pyplot as plt
if __name__ == "__main__":
br = '\n'
digits = load_digits()
X = digits.data
y = digits.target
print ('feature data shape:', X.shape)
iso = Isomap(n_components=2)
iso_name = iso.__class__.__name__
iso.fit(digits.data)
data_projected = iso.transform(X)
print ('project data to 2D:', data_projected.shape)
project_1, project_2 = data_projected[:, 0],\
data_projected[:, 1]
plt.figure(iso_name)
plt.scatter(project_1, project_2, c=y, edgecolor="none",
alpha=0.5, cmap="jet")
plt.colorbar(label='digit label', ticks=range(10))
plt.clim(-0.5, 9.5)
plt.show()
Listing 1-14Isomap visualization
在执行清单 1-14 中的代码后,您的输出应该如下所示:
feature data shape: (1797, 64)
project data to 2D: (1797, 2)
清单 1-14 还显示了图 1-8 ,展示了 load_digits 上的 Isomap 可视化。
图 1-8
load_digits 上聚类的 Isomap 可视化
代码示例从导入 Isomap 和其他必备包开始。主程序块将数字数据加载到 X 和 y 中,并显示特征集 X 的形状。代码继续创建 Isomap 模型,将数据投影到 2D 空间,将主要组件分割为变量 project_1 和 project_2,并进行可视化。Isomap 在识别数字群 0-9 方面表现出色。
Isomap 是一个优秀的非线性数据可视化工具。由于 load_digits 数据是非线性的,Isomap 工作得很好。
二、简单训练集的分类
分类是预测离散类别标签的问题。类也称为目标、标签或类别。通过对训练数据训练分类器算法来预测新数据如何被分类,从而应用分类。
机器学习分类数据集由特征(X)和目标(y)组成,其中输入变量 X 描述已知的离散输出变量 y 。特征数据通常被称为特征集(或特征空间)。分类被认为是监督学习,因为我们知道对应于特征集的目标。
咻!那太多了。所以,让我们看一个简单的例子来帮助你理解分类是如何工作的。假设我们有一个由四类水果组成的数据集,即“苹果”、“橘子”、“柠檬”和“酸橙”。每个数据元素(或行)通过质量、宽度、高度和颜色(特征)描述一个水果(目标)。因此,苹果和桔子可以通过不同的质量、宽度、高度和颜色来区分。
在这个例子中,类标签是水果的类型。每种水果都是独立的。也就是说,苹果很容易与其他种类的水果区分开来。目标是根据水果的质量、宽度、高度和颜色来预测水果的类型。
为了训练数据集,我们将数据分成训练测试子集。列车数据特征称为 X_train ,目标称为 y_train 。测试数据特征称为 X_test ,目标称为 y_test 。然后,我们建立一个分类模型来训练 X_train 和 y_train 数据。一旦对模型进行了训练,我们就可以从 X_test 和 y_test 数据中进行验证和预测,因为模型还没有看到测试数据。通过将测试数据排除在训练过程之外,它有效地充当了新数据。
小费
千万不要在测试数据上训练,以保持数据的纯净。
典型的训练测试分割是 70%/30%,但是应该根据数据集的大小来选择该比率。如果数据集很小,30%的测试集可能不包含所有的类或足够的信息来正确验证。此外,不同类别在训练集和测试集中的分布应该等于实际数据集。确保这种分布的最好方法是随机分割训练测试子集。幸运的是,Scikit-Learn 的 train_test_split 包会自动随机化拆分,但其默认的 train-test 拆分是 75%/25%。
我推荐一些处理机器学习问题时的通用步骤。首先,总是为了训练和验证的目的分割数据。第二,尝试扩展数据以潜在地提高性能。第三,试验训练和测试规模。第四,始终从基线模型、简单算法或基于数据集先前经验的算法开始。从算法的默认超参数开始。第五,试验更复杂的模型,因为 Scikit-Learn 是高效的,并且允许容易的模型替换。当处理大数据集时,尝试抽取随机样本以减少计算开销。当处理高维数据集时,尝试使用 PCA 或 LDA 进行降维,以减少计算开销。第六,调整前面步骤中确定的最佳算法,以获得最佳性能。最后,多做一些实验。机器学习是非常耗费时间和严谨的,所以要有耐心,不要放弃。
小费
总是从算法的默认超参数开始训练。
简单数据集
我们专注于四个简单的数据集来介绍机器学习分类:葡萄酒、数字、银行和月亮。我们没有在第一章介绍人造月亮,因为它是人为的。也就是说,Scikit-Learn 为 make_moons 提供了基础,我们按照自己认为合适的方式构建它。
葡萄酒数据分类
清单 2-1 中所示的代码示例对葡萄酒数据进行分类。
from sklearn.datasets import load_wine
from sklearn.preprocessing import StandardScaler
from sklearn.discriminant_analysis import\
LinearDiscriminantAnalysis as LDA
from sklearn.linear_model import SGDClassifier
from sklearn.model_selection import train_test_split
from sklearn import metrics
from random import *
if __name__ == "__main__":
br = '\n'
data = load_wine()
X = data.data
y = data.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.30, random_state=0)
lda = LDA().fit(X_train, y_train)
print (lda, br)
lda_name = lda.__class__.__name__
y_pred = lda.predict(X_train)
accuracy = metrics.accuracy_score(y_train, y_pred)
accuracy = str(accuracy * 100) + '%'
print (lda_name + ':')
print ('train:', accuracy)
y_pred_test = lda.predict(X_test)
accuracy = metrics.accuracy_score(y_test, y_pred_test)
accuracy = str(round(accuracy * 100, 2)) + '%'
print ('test: ', accuracy, br)
print('Confusion Matrix', lda_name)
print(metrics.confusion_matrix(y_test, lda.predict(X_test)), br)
std_scale = StandardScaler().fit(X_train)
X_train = std_scale.transform(X_train)
X_test = std_scale.transform(X_test)
sgd = SGDClassifier(max_iter=5, random_state=0)
print (sgd, br)
sgd.fit(X_train, y_train)
sgd_name = sgd.__class__.__name__
y_pred = sgd.predict(X_train)
y_pred_test = sgd.predict(X_test)
print (sgd_name + ':')
print('train: {:.2%}'.format(metrics.accuracy_score\(y_train, y_pred)))
print('test: {:.2%}\n'.format(metrics.accuracy_score\(y_test, y_pred_test)))
print('Confusion Matrix', sgd_name)
print(metrics.confusion_matrix(y_test, sgd.predict(X_test)), br)
n, ls = 100, []
for i, row in enumerate(range(n)):
rs = randint(0, 100)
sgd = SGDClassifier(max_iter=5, random_state=0)
sgd.fit(X_train, y_train)
y_pred = sgd.predict(X_test)
accuracy = metrics.accuracy_score(y_test, y_pred)
ls.append(accuracy)
avg = sum(ls) / len(ls)
print ('MCS (true test accuracy):', avg)
Listing 2-1Classify load_wine data
继续执行清单 2-1 中的代码。请记住,您可以从本书的示例下载中找到示例。您不需要手动键入示例。更容易访问示例下载和复制/粘贴。
执行清单 2-1 的输出应该如下所示:
LinearDiscriminantAnalysis(n_components=None, priors=None, shrinkage=None, solver="svd", store_covariance=False, tol=0.0001)
LinearDiscriminantAnalysis:
train: 100.0%
test: 98.15%
Confusion Matrix LinearDiscriminantAnalysis
[[19 0 0]
[ 1 21 0]
[ 0 0 13]]
SGDClassifier(alpha=0.0001, average=False, class_weight=None,
early_stopping=False, epsilon=0.1, eta0=0.0,
fit_intercept=True, l1_ratio=0.15,
learning_rate='optimal', loss="hinge", max_iter=5,
n_iter=None, n_iter_no_change=5, n_jobs=None,
penalty='l2', power_t=0.5, random_state=0, shuffle=True,
tol=None, validation_fraction=0.1, verbose=0,
warm_start=False)
SGDClassifier:
train: 100.00%
test: 100.00%
Confusion Matrix SGDClassifier
[[19 0 0]
[ 0 22 0]
[ 0 0 13]]
MCS (true test accuracy): 1.0
代码从导入度量、随机和必需的包开始。主程序块首先加载数据,并将其分成训练测试子集。请注意,我们将测试规模调整为 30%。接下来,创建线性判别分析(LDA)模型,并在训练集上进行训练。你可以调整测试规模,看看你的准确性是否有所提高。但是,不要搞得太大。您的模型需要训练数据来更好地理解和学习。
小费
要查看模型的超参数,只需在创建后打印保存模型的变量(例如,print (lda))。
LDA 是在第一章中介绍的,作为一种无监督的降维学习模型。LDA 是一个非常有趣的模型,因为它执行非监督维度缩减和监督分类。
数据缩放并不能提高 LDA 的性能,所以模型是在未缩放的数据上训练的。然后计算训练和测试子集的准确度分数。性能准确性通常只在测试数据上报告。但是,获得训练和测试精度以查看模型与数据的拟合程度是很有用的。在这种情况下,模型非常符合数据,因为训练精度和测试精度非常相似。如果训练精度远高于测试精度,则模型会过度拟合数据。
代码继续显示混淆矩阵。一个混淆矩阵描述了一个分类模型(或分类器)对一组真实值已知的测试数据的性能。由 19、21 和 13 组成的对角线是模型正确分类的地方。该模型只对测试集中的一个数据元素进行了错误分类,这非常有意义,测试准确率超过 98%。接下来,我们缩放数据,因为众所周知 SGDClassifier 在处理缩放数据时性能更好。该模型被训练,并且训练和测试精度与混淆矩阵一起显示。有了这个模型,分类就完美了。
代码的最后一部分是可选的。它使用蒙特卡罗实验来验证 SGD 分类器在葡萄酒数据上的性能。蒙特卡罗实验利用随机性解决确定性(或监督性)问题。完美的测试准确率 100%,我们应该有点怀疑。因此,我们运行了 100 次蒙特卡罗实验来获得实际的测试性能。如你所见,我们得到 100%!
蒙特卡罗实验是一种很好的获得精确度的方法,但是计算量非常大。我们在这种情况下是安全的,因为数据集既小又简单。对于高维数据的大数据集,蒙特卡罗实验并不是很实用。
线性判别分析和 SGD 分类器不是随机选择的。通过严格的反复试验和研究,这些算法被战略性地确定为最佳表现。
小费
每个数据集都是不同的,所以通过反复试验和研究来战略性地选择算法。
分类数字
清单 2-2 中显示的第一个代码示例加载数据,并将其分成训练测试子集。接下来,使用分类器 GaussianNB、SGDClassifier 和 svm 训练数据。算法 svm 表现最好。然后代码识别并可视化错误分类。代码以可视化第一个错误分类结束。
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import SGDClassifier
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt
import seaborn as sns
def find_misses(test, pred):
return [i for i, row in enumerate(test) if row != pred[i]]
if __name__ == "__main__":
br = '\n'
digits = load_digits()
X = digits.data
y = digits.target
X_train, X_test, y_train, y_test = train_test_split\
(X, y, random_state=0)
gnb = GaussianNB().fit(X_train, y_train)
gnb_name = gnb.__class__.__name__
y_pred = gnb.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print (gnb_name + ' \'test\' accuracy:', accuracy)
scaler = StandardScaler()
X_train_std = scaler.fit_transform(X_train)
X_test_std = scaler.fit_transform(X_test)
sgd = SGDClassifier(random_state=0, max_iter=1000, tol=0.001)
sgd_name = sgd.__class__.__name__
sgd.fit(X_train_std, y_train)
y_pred = sgd.predict(X_test_std)
accuracy = accuracy_score(y_test, y_pred)
print (sgd_name + ' \'test\' accuracy:', accuracy)
svm = SVC(gamma='auto').fit(X_train_std, y_train)
svm_name = svm.__class__.__name__
y_pred = svm.predict(X_test_std)
accuracy = accuracy_score(y_test, y_pred)
print (svm_name + ' \'test\' accuracy:', accuracy, br)
indx = find_misses(y_test, y_pred)
print ('total misclassifications (' + str(svm_name) +\ '):', len(indx), br)
print ('pred', 'actual')
misses = [(y_pred[row], y_test[row], i)
for i, row in enumerate(indx)]
[print (row[0], ' ', row[1]) for row in misses]
img_indx = misses[0][2]
img_pred = misses[0][0]
img_act = misses[0][1]
text = str(img_pred)
print(classification_report(y_test, y_pred))
cm = confusion_matrix(y_test, y_pred)
plt.figure(1)
ax = plt.axes()
sns.heatmap(cm.T, annot=True, fmt="d",
cmap='gist_ncar_r', ax=ax)
title = svm_name + ' confusion matrix'
ax.set_title(title)
plt.xlabel('true value')
plt.ylabel('predicted value')
test_images = X_test.reshape(-1, 8, 8)
plt.figure(2)
plt.title('1st misclassifcation')
plt.imshow(test_images[img_indx], cmap="gray", interpolation="gaussian")
plt.text(0, 0.05, text, color="r", bbox=dict(facecolor='white'))
plt.show()
Listing 2-2Classify load_digits data
在执行清单 2-2 中的代码后,您的输出应该如下所示:
GaussianNB 'test' accuracy: 0.8333333333333334
SGDClassifier 'test' accuracy: 0.9377777777777778
SVC 'test' accuracy: 0.9822222222222222
total misclassifications (SVC): 8
pred actual
7 2
1 8
7 9
9 5
4 7
4 3
2 8
4 1
precision recall f1-score support
0 1.00 1.00 1.00 37
1 0.98 0.98 0.98 43
2 0.98 0.98 0.98 44
3 1.00 0.98 0.99 45
4 0.93 1.00 0.96 38
5 1.00 0.98 0.99 48
6 1.00 1.00 1.00 52
7 0.96 0.98 0.97 48
8 1.00 0.96 0.98 48
9 0.98 0.98 0.98 47
micro avg 0.98 0.98 0.98 450
macro avg 0.98 0.98 0.98 450
weighted avg 0.98 0.98 0.98 450
列表 2-2 还显示了数字 2-1 和 2-2 。图 2-1 显示了最佳执行算法的混淆矩阵,即 svm。您会看到显示的 SVC ,因为我们正在实现 svm 算法的 SVC 实现。SVC 实现利用 C-支持向量分类,其被表示为 svm。Scikit 中的 SVC。图 2-2 显示了预测集中的第一个误分类,即数字 2 误分类为数字 7。如果我们查看混淆矩阵,我们可以在数字 7 的预测值行和数字 2 的真值列的交叉点上看到这种错误分类。因此,真实值(数字 2)被错误地预测(或错误分类)为数字 7。
图 2-2
预测集中的第一个错误分类
图 2-1
支持向量机的混淆矩阵。SVC 算法
代码示例首先导入 GaussianNB、confusion_matrix 和 classification_report 以及其他必需的包。高斯神经网络是一种优秀的基线算法,因为它速度快,在许多分类问题上表现良好,并且需要调整的超参数很少。
小费
如果您没有使用分类数据集的经验,GaussianNB 是一个很好的起点,因为它简单、快速、易于理解,并且需要调整的超参数很少。
函数 find _ misses 返回一个分类错误的数字列表。主块加载数据,将其分成训练测试子集,并使用 GaussianNB、SGDClassifier 和 svm 进行训练。
高斯神经网络是一种概率分类器,基于应用贝叶斯定理,具有特征之间的强独立性假设。SGDClassifier 是一个分类器,它实现了一个简单的随机梯度下降学习例程,支持不同的损失函数和分类惩罚。支持向量机(svm)建立一个模型,将新的样本分配到一个类别或另一个类别。
然后代码显示所有三个模型的测试精度。自从 svm。SVC 得分最高,我们用它来识别错误分类。然后显示错误分类总数。代码继续显示每个错误分类是如何呈现的。所以,第一个分类错误的数字是 2,它被误分类为 7。
接下来,代码创建一个分类报告(用于 svm。SVC 算法),它为每个数字提供了精确度、召回率和 f1_score 分数。准确性是报告分数的一个很好的方法,但是 f1_score 是一个值得考虑的方法,因为它是最保守的。
代码以显示一个 svm 结束。SVC 混淆矩阵和第一个误分类,其中 2 被误分类为 7。该图也很有趣,因为它显示了数字 2 的实际图像,以及它被分类为红色的数字 7 的方式。
一旦为一个数据集确定了一个很好的执行算法,我强烈推荐创建一个混淆矩阵可视化。不仅很容易理解算法对目标的分类有多好,它还允许更深入地检查算法在哪里没有按预期执行。
小费
创建混淆矩阵可视化是了解算法执行情况的一个很好的方式。
虽然我们在前面的例子中从 svm 获得了高准确度的分数,但是 Scikit-Learn 允许我们非常容易地替换分类器。因此,下一个代码示例有点疯狂,用六个额外的分类器训练葡萄酒数据。请记住,数据集是小而简单的。对于更大和更复杂的数据,替换分类器在计算上是昂贵的。
清单 2-3 中显示的下一个代码示例使用几个 Scikit-Learn 算法对葡萄酒数据进行分类,以识别有希望提高性能的数据。
import humanfriendly as hf
import time
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression,\
LogisticRegressionCV
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier,\
ExtraTreesClassifier, GradientBoostingClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
from sklearn.metrics import f1_score
def get_scores(model, Xtest, ytest, avg):
y_pred = model.predict(Xtest)
accuracy = accuracy_score(ytest, y_pred)
f1 = f1_score(ytest, y_pred, average=avg)
return (accuracy, f1)
def get_time(time):
return hf.format_timespan(time, detailed=True)
if __name__ == "__main__":
br = '\n'
digits = load_digits()
X = digits.data
y = digits.target
X_train, X_test, y_train, y_test = train_test_split\
(X, y, random_state=0)
scaler = StandardScaler()
X_train_std = scaler.fit_transform(X_train)
X_test_std = scaler.fit_transform(X_test)
lr = LogisticRegression(random_state=0, solver="lbfgs",
multi_class='auto', max_iter=4000)
lr.fit(X_train_std, y_train)
lr_name = lr.__class__.__name__
acc, f1 = get_scores(lr, X_test_std, y_test, 'micro')
print (lr_name + ' scaled \'test\':')
print ('accuracy:', acc, ', f1_score:', f1, br)
softmax = LogisticRegression(multi_class="multinomial",
solver="lbfgs", max_iter=4000,
C=10, random_state=0)
softmax.fit(X_train_std, y_train)
acc, f1 = get_scores(softmax, X_test_std, y_test, 'micro')
print (lr_name + ' (softmax) scaled \'test\':')
print ('accuracy:', acc, ', f1_score:', f1, br)
rf = RandomForestClassifier(random_state=0, n_estimators=100)
rf.fit(X_train_std, y_train)
rf_name = rf.__class__.__name__
acc, f1 = get_scores(rf, X_test_std, y_test, 'micro')
print (rf_name + ' \'test\':')
print ('accuracy:', acc, ', f1_score:', f1, br)
et = ExtraTreesClassifier(random_state=0, n_estimators=100)
et.fit(X_train, y_train)
et_name = et.__class__.__name__
acc, f1 = get_scores(et, X_test, y_test, 'micro')
print (et_name + ' \'test\':')
print ('accuracy:', acc, ', f1_score:', f1, br)
gboost_clf = GradientBoostingClassifier(random_state=0)
gb_name = gboost_clf.__class__.__name__
gboost_clf.fit(X_train, y_train)
acc, f1 = get_scores(gboost_clf, X_test, y_test, 'micro')
print (gb_name + ' \'test\':')
print ('accuracy:', acc, ', f1_score:', f1, br)
knn_clf = KNeighborsClassifier().fit(X_train, y_train)
knn_name = knn_clf.__class__.__name__
acc, f1 = get_scores(knn_clf, X_test, y_test, 'micro')
print (knn_name + ' \'test\':')
print ('accuracy:', acc, ', f1_score:', f1, br)
start = time.perf_counter()
lr_cv = LogisticRegressionCV(random_state=0, cv=5, multi_class="auto", max_iter=4000)
lr_cv_name = lr_cv.__class__.__name__
lr_cv.fit(X, y)
end = time.perf_counter()
elapsed_ls = end - start
timer = get_time(elapsed_ls)
print (lr_cv_name + ' timer:', timer)
acc, f1 = get_scores(lr_cv, X_test, y_test, 'micro')
print (lr_cv_name + ' \'test\':')
print ('accuracy:', acc, ', f1_score:', f1)
Listing 2-3Classifying load_digits with various algorithms
在执行清单 2-3 中的代码后,您的输出应该如下所示:
LogisticRegression scaled 'test':
accuracy: 0.9733333333333334, f1_score: 0.9733333333333334
LogisticRegression (softmax) scaled 'test':
accuracy: 0.9644444444444444, f1_score: 0.9644444444444444
RandomForestClassifier 'test':
accuracy: 0.9755555555555555, f1_score: 0.9755555555555555
ExtraTreesClassifier 'test':
accuracy: 0.9822222222222222, f1_score: 0.9822222222222222
GradientBoostingClassifier 'test':
accuracy: 0.9622222222222222, f1_score: 0.9622222222222222
KNeighborsClassifier 'test':
accuracy: 0.98, f1_score: 0.98
LogisticRegressionCV timer: 49 seconds and 38.45 milliseconds
LogisticRegressionCV 'test':
accuracy: 0.9822222222222222, f1_score: 0.9822222222222222
代码从导入 humanfriendly、time、LogisticRegression、LogisticRegressionCV、KNeighborsClassifier、GradientBoostingClassifier 和 ExtraTreesClassifier 以及其他必需的包开始。函数 get_scores 返回精度和 f1_score。函数 get_time 返回经过的时间。它有助于发现一个算法训练一个数据集需要多长时间。
LogisticRegression 是一种传统上仅限于两类分类问题的分类算法。Softmax(多项逻辑回归)分类使用逻辑回归进行多类分类。RandomForestClassifier 是一种集成学习方法,它在训练时构建大量决策树,并输出作为类模式的类。ExtraTreesClassifier 实现了一个元估计器,该估计器在数据集的各种子样本上拟合多个随机决策树(或额外的树),并使用平均来提高预测精度和控制过度拟合。GradientBoostingClassifier 以弱预测模型(通常为决策树)的形式生成预测模型。KNeighborsClassifier 实现了 k-nearest neighbors 投票,其中输入由特征空间中的 k 个最近的训练示例组成。特征空间指的是你的特征存在的 n 维空间。LogisticRegression 使用逻辑函数对数据进行建模。LogisticRegressionCV 使用逻辑回归实现交叉验证估计。
交叉验证(CV)将数据分成 n 个子集,并迭代 n 次。通过每次迭代, n 个子集中的一个被拿出来作为测试集,而其余的用于训练。每次迭代使用不同的子集。因此,精确度和误差在所有的 n 次试验中被平均。结果精度非常好,但是 CV 的计算代价很高。
GradientBoostingClassifier 和 ExtraTreesClassifier 是与 RandomForestClassifier 相似的集成方法,它们根据数据和平均结果拟合(或训练)许多决策树,以提高预测准确性。
小费
您可能需要安装这个人性化的软件包,因为 Anaconda 不会自动安装它。打开一个新的 Anaconda 提示符,如清单 2-4 所示进行安装。
pip install humanfriendly
Listing 2-4Install a new package
主程序块从加载数据并将其分成训练测试子集开始。每种算法训练数据并显示分数。请注意,LogisticRegressionCV 消耗了超过 46 秒的时间。尽管所有算法的表现都令人钦佩,但我们仍然无法达到 98.22%的准确率。
银行数据分类
清单 2-5 中显示的第一个代码示例从一个 CSV 文件中加载银行数据。接下来,教育功能被设计得更像样。
特征工程正在创建使机器学习算法工作的特征(基于数据的领域知识)。虽然特征工程是机器学习应用的基础,但它既困难又昂贵。
然后,代码将分类特征转换为数字特征,以便进行算法训练。这种转换通常被称为编码。
小费
机器学习算法只对数字数据进行运算。
最后,将显示五个最重要的要素以及数据要素和类计数。功能集和目标保存在 NumPy 文件中。
import numpy as np, pandas as pd
from sklearn.ensemble import RandomForestClassifier
if __name__ == "__main__":
br = '\n'
f = 'data/bank.csv'
data = pd.read_csv(f)
print ('original "education" categories:')
print (data.education.unique(), br)
data['education'] = np.where(data['education'] == 'basic.9y',
'basic', data['education'])
data['education'] = np.where(data['education'] == 'basic.6y',
'basic', data['education'])
data['education'] = np.where(data['education'] == 'basic.4y',
'basic', data['education'])
data['education'] = np.where(data['education'] == 'high.school', 'high_school', data.education)
data['education'] = np.where(data['education'] == 'professional.course', 'professional', data['education'])
data['education'] = np.where(data['education'] == 'university.degree', 'university', data['education'])
print ('engineered "education" categories:')
print (data.education.unique(), br)
print ('target value counts:')
print (data.y.value_counts(), br)
data_X = data.loc[:, data.columns != 'y']
cat_vars = ['job', 'marital', 'education', 'default', 'housing', 'loan', 'contact', 'month', 'day_of_week', 'poutcome']
data_new = pd.get_dummies(data_X, columns=cat_vars)
X = data_new.values
y = data.y.values
attributes = list(data_X)
rf = RandomForestClassifier(random_state=0, n_estimators=100)
rf.fit(X, y)
rf_name = rf.__class__.__name__
feature_importances = rf.feature_importances_
importance = sorted(zip(feature_importances, attributes), reverse=True)
n = 5
print (n, 'most important features' + ' (' + rf_name + '):')
[print (row) for i, row in enumerate(importance) if i < n]
print ()
features_file = 'data/features'
np.save(features_file, attributes)
features = np.load('data/features.npy')
print ('features:')
print (features, br)
y_file = 'data/y'
X_file = 'data/X'
np.save(y_file, y)
np.save(X_file, X)
d = {}
dvc = data.y.value_counts()
d['no'], d['yes'] = dvc['no'], dvc['yes']
dvc_file = 'data/value_counts'
np.save(dvc_file, d)
d = np.load('data/value_counts.npy')
print ('class counts:', d)
Listing 2-5Engineering and wrangling bank data
在执行清单 2-5 中的代码后,您的输出应该如下所示:
original "education" categories:
['basic.4y' 'high.school' 'basic.6y' 'basic.9y' 'professional.course'
'unknown' 'university.degree' 'illiterate']
engineered "education" categories:
['basic' 'high_school' 'professional' 'unknown' 'university' 'illiterate']
target value counts:
no 36548
yes 4640
Name: y, dtype: int64
5 most important features (RandomForestClassifier):
(0.28697175347986037, 'job')
(0.08761238456151103, 'month')
(0.0797624194551633, 'age')
(0.05492109153356108, 'day_of_week')
(0.04027613029914145, 'marital')
features:
['age' 'job' 'marital' 'education' 'default' 'housing' 'loan' 'contact' 'month' 'day_of_week' 'duration' 'campaign' 'pdays' 'previous' 'poutcome' 'emp.var.rate' 'cons.price.idx' 'cons.conf.idx' 'euribor3m' 'nr.employed']
class counts: {'no': 36548, 'yes': 4640}
该代码示例从导入必备包开始。主程序块读取数据并显示来自教育功能的原始值。代码继续对特征进行特征工程,并显示新值。请注意,只对单个特征进行特征工程是多么困难。接下来,分类特征由熊猫 get_dummies 函数编码为一个热编码(OHE)向量。Scikit-Learn 希望特征数据是数字,这就是为什么我们需要对它们进行编码。
OHE 向量也被称为虚拟变量。OHE 是一个很好的选择,因为它是机器学习中处理分类数据最常用的方法之一。OHE 获取每个类别值,并将其转换为大小为 I 的二进制向量(其中 I 是类别 I 中值的数量),并使除类别列之外的所有列都等于零。例如,在我们的数据集中,婚姻状况是“已婚”、“单身”或“离婚”。如果有人结婚了,OHE 编码一个[1 0 0]向量。如果是 single,OHE 编码一个[0 1 0]向量。最后,如果离婚,OHE 编码一个[0 0 1]向量。简单地说,1 位是热的,表示适合数据元素的类别。
然后,代码根据转换后的数据集创建特征集 X 和目标集 y。在 RandomForestClassifier 的帮助下显示特征重要性。接下来,X 和 y 被保存在 NumPy 文件中。最后,创建、保存和显示类计数。查看类计数有助于了解目标之间的平衡。
请注意,我们有更多的否值,而不是是值。所以,数据集有点不平衡。这种情况通常被称为不平衡类分布,即属于一个类的观测值数量明显低于属于其他类的观测值数量。在我们的例子中,是和不是的比例大约是 12.6%。所以,我们没有大问题。低于 5%的比率(或事件率)是一个问题,因为当这种情况发生时,机器学习算法可能产生不令人满意的分类。
现在银行数据已经准备好了,我们可以运行实验来识别高性能的分类算法,如下面的代码示例所示,如清单 2-6 所示。记住,许多小时的实验导致了这个例子中算法的选择。
import numpy as np, pandas as pd, random
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier,\
ExtraTreesClassifier
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import f1_score
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns
def get_scores(model, xtrain, ytrain, xtest, ytest, scoring):
ypred = model.predict(xtest)
train = model.score(xtrain, ytrain)
test = model.score(xtest, y_test)
f1 = f1_score(ytest, ypred, average=scoring)
return (train, test, f1)
def prep_data(data, target):
d = [data[i] for i, _ in enumerate(data)]
t = [target[i] for i, _ in enumerate(target)]
return list(zip(d, t))
def create_sample(d, n, replace="yes"):
if replace == 'yes': s = random.sample(d, n)
else: s = [random.choice(d) for i, _ in enumerate(d) if i < n]
Xs = [row[0] for i, row in enumerate(s)]
ys = [row[1] for i, row in enumerate(s)]
return np.array(Xs), np.array(ys)
if __name__ == "__main__":
br = '\n'
X = np.load('data/X.npy')
y = np.load('data/y.npy')
print ('full data set shape for X and y:')
print (X.shape, y.shape, br)
X_train, X_test, y_train, y_test = train_test_split\
(X, y, random_state=0)
et = ExtraTreesClassifier(random_state=0, n_estimators=100)
et.fit(X_train, y_train)
et_scores = get_scores(et, X_train, y_train, X_test, y_test, 'micro')
print (et.__class__.__name__ + '(train, test, f1_score):')
print (et_scores, br)
rf = RandomForestClassifier(random_state=0, n_estimators=100)
rf.fit(X_train, y_train)
rf_scores = get_scores(rf, X_train, y_train, X_test, y_test, 'micro')
print (rf.__class__.__name__ + '(train, test, f1_score):')
print (rf_scores, br)
sample_size = 4000
data = prep_data(X, y)
Xs, ys = create_sample(data, sample_size, replace="no")
print ('sample data set shape for X and y:')
print (Xs.shape, ys.shape, br)
X_train, X_test, y_train, y_test = train_test_split\
(Xs, ys, random_state=0)
scaler = StandardScaler().fit(X_train)
X_train_std, X_test_std = scaler.transform(X_train),\
scaler.transform(X_test)
knn = KNeighborsClassifier().fit(X_train, y_train)
knn_scores = get_scores(knn, X_train, y_train, X_test, y_test, 'micro')
print (knn.__class__.__name__ + '(train, test, f1_score):')
print (knn_scores, br)
svm = SVC(random_state=0, gamma="scale")
svm.fit(X_train_std, y_train)
svm_scores = get_scores(svm, X_train_std, y_train, X_test_std, y_test, 'micro')
print (svm.__class__.__name__ + '(train, test, f1_score):')
print (svm_scores, br)
knn_name, svm_name = knn.__class__.__name__,\
svm.__class__.__name__
y_pred_knn = knn.predict(X_test)
cm_knn = confusion_matrix(y_test, y_pred_knn)
cm_knn_T = cm_knn.T
y_pred_svm = svm.predict(X_test_std)
cm_svm = confusion_matrix(y_test, y_pred_svm)
cm_svm_T = cm_svm.T
plt.figure(knn.__class__.__name__)
ax = plt.axes()
sns.heatmap(cm_knn_T, annot=True, fmt="d", cmap="gist_ncar_r", cbar=False)
ax.set_title(str(knn_name) + ' confusion matrix')
plt.xlabel('true label')
plt.ylabel('predicted label')
plt.figure(str(svm_name) + ' confusion matrix' )
ax = plt.axes()
sns.heatmap(cm_svm_T, annot=True, fmt="d", cmap="gist_ncar_r", cbar=False)
ax.set_title(svm_name)
plt.xlabel('true label')
plt.ylabel('predicted label')
cnt_no, cnt_yes = 0, 0
for i, row in enumerate(y_test):
if row == 'no': cnt_no += 1
elif row == 'yes': cnt_yes += 1
cnt_no, cnt_yes = str(cnt_no), str(cnt_yes)
print ('true =>', 'no: ' + cnt_no + ', yes: ' + cnt_yes, br)
p_no, p_nox = cm_knn_T[0][0], cm_knn_T[0][1]
p_yes, p_yesx = cm_knn_T[1][1], cm_knn_T[1][0]
print ('knn classification report:')
print ('predict \'no\':', p_no, '(' +\str(p_nox) + ' misclassifed)')
print ('predict \'yes\':', p_yes, '(' +\str(p_yesx) + ' misclassifed)', br)
p_no, p_nox = cm_svm_T[0][0], cm_svm_T[0][1]
p_yes, p_yesx = cm_svm_T[1][1], cm_svm_T[1][0]
print ('svm classification report:')
print ('predict \'no\':', p_no, '(' +\str(p_nox) + ' misclassifed)')
print ('predict \'yes\':', p_yes, '(' +\str(p_yesx) + ' misclassifed)')
plt.show()
Listing 2-6Classifying bank data
在执行清单 2-6 中的代码后,您的输出应该如下所示:
full data set shape for X and y:
(41188, 61) (41188,)
ExtraTreesClassifier(train, test, f1_score):
(1.0, 0.9009420219481402, 0.9009420219481401)
RandomForestClassifier(train, test, f1_score):
(0.9999676281117478, 0.9121103233951636, 0.9121103233951636)
sample data set shape for X and y:
(4000, 61) (4000,)
KNeighborsClassifier(train, test, f1_score):
(0.9323333333333333, 0.916, 0.916)
SVC(train, test, f1_score):
(0.9376666666666666, 0.92, 0.92)
true => no: 902, yes: 98
knn classification report:
predict 'no': 869 (51 misclassifed)
predict 'yes': 47 (33 misclassifed)
svm classification report:
predict 'no': 883 (61 misclassifed)
predict 'yes': 37 (19 misclassifed)
列表 2-6 还显示了数字 2-3 和 2-4 。图 2-3 显示 KNeighborsClassifier 的混淆矩阵,图 2-4 显示 svm.SVC 的混淆矩阵。
图 2-4
svm。SVC 混淆矩阵
图 2-3
近邻分类器混淆矩阵
代码从导入必需的包开始。函数 get_scores 返回训练和测试准确度分数。函数 prep_data 将 NumPy 矩阵转换为向量列表,以便更容易地操作数据元素进行采样。函数 create_sample 构建一个随机样本,并将其作为 X 和 y NumPy 矩阵返回。
Scikit-Learn 算法只能训练表示为 NumPy 的数据。主块从上一个示例中创建的 NumPy 文件中加载 X 和 y。x 和 y 被分成训练测试子集。然后,代码使用 ExtraTreesClassifier 和 RandomForestClassifier 对数据进行定型。抽取 4000 个样本,以便我们可以使用 KNeighborsClassifier 和 svm.SVC 高效地进行训练。这两种算法是优秀的分类器,但是对于大数据集来说计算开销很大。
近邻分类器和支持向量机的混淆矩阵。然后显示 SVC,因为它们更适合数据。也就是说,这些模型的精确度更高,过度拟合更少。代码通过计算 KNeighborsClassifier 和 svm 的数据和错误分类的目标值的平衡得出结论。
值得注意的是 KNeighborsClassifier 和 svm。基于少于原始数据的 10% 的样本*,SVC 比其他算法执行得更好。这其实是很可观的!*
UCI 机器学习库包括从银行数据中随机选择的样本,其中有 10%的例子。为了完整起见,清单 2-7 中显示的下一个例子测试了这个样本的准确性。
import pandas as pd, numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier,\
ExtraTreesClassifier
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import f1_score
def get_scores(model, xtrain, ytrain, xtest, ytest, scoring):
ypred = model.predict(xtest)
train = model.score(xtrain, ytrain)
test = model.score(xtest, y_test)
f1 = f1_score(ytest, ypred, average=scoring)
return (train, test, f1)
if __name__ == "__main__":
br = '\n'
f = 'data/bank_sample.csv'
data = pd.read_csv(f)
print ('data shape:', data.shape, br)
data['education'] =\
np.where(data['education'] == 'basic.9y',
'basic', data['education'])
data['education'] = np.where(data['education'] == 'basic.6y',
'basic', data['education'])
data['education'] = np.where(data['education'] == 'basic.4y',
'basic', data['education'])
data['education'] = np.where(data['education'] == 'high.school', 'high_school', data.education)
data['education'] = np.where(data['education'] == 'professional.course', 'professional', data['education'])
data['education'] = np.where(data['education'] == 'university.degree', 'university', data['education'])
data_X = data.loc[:, data.columns != 'y']
cat_vars = ['job', 'marital', 'education', 'default', 'housing', 'loan', 'contact', 'month', 'day_of_week', 'poutcome']
data_new = pd.get_dummies(data_X, columns=cat_vars)
attributes = list(data_X)
y = data.y.values
X = data_new.values
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
rf = RandomForestClassifier(random_state=0, n_estimators=100)
rf.fit(X_train, y_train)
rf_name = rf.__class__.__name__
rf_scores = get_scores(rf, X_train, y_train, X_test, y_test, 'micro')
print (rf.__class__.__name__ + '(train, test, f1_score):')
print (rf_scores, br)
et = ExtraTreesClassifier(random_state=0, n_estimators=100)
et.fit(X_train, y_train)
et_name = et.__class__.__name__
et_scores = get_scores(et, X_train, y_train, X_test, y_test, 'micro')
print (et.__class__.__name__ + '(train, test, f1_score):')
print (et_scores, br)
scaler = StandardScaler().fit(X_train)
X_train_std, X_test_std = scaler.transform(X_train),\
scaler.transform(X_test)
knn = KNeighborsClassifier().fit(X_train, y_train)
knn_scores = get_scores(knn, X_train, y_train, X_test, y_test, 'micro')
print (knn.__class__.__name__ + '(train, test, f1_score):')
print (knn_scores, br)
svm = SVC(random_state=0, gamma="scale")
svm.fit(X_train_std, y_train)
svm_scores = get_scores(svm, X_train_std, y_train, X_test_std, y_test, 'micro')
print (svm.__class__.__name__ + '(train, test, f1_score):')
print (svm_scores)
Listing 2-7Classifying UCI Irvine sample bank data
在执行清单 2-7 中的代码后,您的输出应该如下所示:
data shape: (4119, 21)
RandomForestClassifier(train, test, f1_score):
(1.0, 0.9058252427184466, 0.9058252427184466)
ExtraTreesClassifier(train, test, f1_score):
(1.0, 0.8990291262135922, 0.8990291262135922)
KNeighborsClassifier(train, test, f1_score):
(0.9323405632890903, 0.8883495145631068, 0.8883495145631068)
SVC(train, test, f1_score):
(0.9494982194885077, 0.9, 0.9)
代码从导入必需的包开始。函数 get_scores 返回准确度分数。主程序块加载样本,设计教育特征,并将分类特征编码成 OHE 形式。我们不得不为这个例子添加工程师教育的特征,因为我们没有从我们已经设计的特征的完整数据集中抽取样本。
代码继续将 NumPy 数据加载到 X 和 y 中,将其分成训练测试子集,并用 RandomForestClassifier 进行训练。然后显示精确度。其余代码使用 ExtraTreesClassifier、KNeighborsClassifier 和 svm 进行训练。SVC 并显示准确度分数。
我们创建的样本的性能至少与来自 UCI 资源库的样本一样好。我们的样本甚至更小,这意味着我们的取样技术已经足够了。
给月亮分类
Scikit-Learn make_moons 数据主要用于可视化聚类和分类算法。然而,它也是一个很好的数据集,可以帮助您了解分类算法如何尝试分离二进制目标标签(或二进制分类)。make_moons 的部署描述了 2D 空间中的两个交错圆以及相关联的数据点。
通过可视化,我们可以很容易地看到两个(或二元)标签之间的分离。如果人眼可以在 2D 空间中轻松区分这种分离,分类算法应该也能做到。我们可以用一个例子来验证这一点。
清单 2-8 中显示的第一个代码示例创建了一个包含 1000 个元素的数据集,将特征数据及其关联的目标放入 Pandas DataFrame 中,并绘制出结果。每个特征元素表示用于在 2D 空间中绘图的 x 和 y 坐标。每个目标代表要素的标签,它是 0 或 1 的二进制值。
import matplotlib.pyplot as plt, pandas as pd
from sklearn import datasets
if __name__ == "__main__":
br = '\n'
X, y = datasets.make_moons(n_samples=1000, shuffle=True, noise=0.2, random_state=0)
df = pd.DataFrame(dict(x=X[:,0], y=X[:,1], label=y))
colors = {0:'magenta', 1:'cyan'}
fig, ax = plt.subplots()
data = df.groupby('label')
for key, label in data:
label.plot(ax=ax, kind="scatter", x="x", y="y", label=key, color=colors[key])
plt.show()
Listing 2-8Plot make_moons
在执行清单 2-8 中的代码后,您的输出应该类似于图 2-5 中所示的可视化结果:
图 2-5
随机生成的卫星数据的可视化
清单 2-9 中显示的下一个代码示例创建了一个包含 1000 个元素的 make_moons 数据集,将其分割成训练测试子集,并使用 svm 进行训练。SVC 和 KNeighborsClassifier。我特意选择了这两种算法,因为我知道它们会在二进制分类方面做得很好,因为它们会查看每个数据点。
from sklearn import datasets
from sklearn.neighbors import KNeighborsClassifier
from sklearn import svm
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
def get_scores(model, Xtrain, Xtest, ytrain, ytest):
y_ptrain = model.predict(Xtrain)
y_ptest = model.predict(Xtest)
acc_train = accuracy_score(ytrain, y_ptrain)
acc_test = accuracy_score(ytest, y_ptest)
name = model.__class__.__name__
return (name, acc_train, acc_test)
if __name__ == "__main__":
br = '\n'
X, y = datasets.make_moons(n_samples=1000, shuffle=True, noise=0.2, random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
knn = KNeighborsClassifier().fit(X_train, y_train)
accuracy = get_scores(knn, X_train, X_test, y_train, y_test)
print ('<<' + str(accuracy[0]) + '>>')
print ('train:', accuracy[1], 'test:', accuracy[2], br)
svm = svm.SVC(gamma='scale', random_state=0)
svm.fit(X_train, y_train)
accuracy = get_scores(svm, X_train, X_test, y_train, y_test)
print ('<<' + str(accuracy[0]) + '>>')
print ('train:', accuracy[1], 'test:', accuracy[2])
Listing 2-9Classify make_moons
在执行清单 2-9 中的代码后,您的输出应该如下所示:
<<KNeighborsClassifier>>
train: 0.9666666666666667 test: 0.964
<<SVC>>
train: 0.9653333333333334 test: 0.96
该代码示例从导入必备包开始。函数 get_scores 返回模型名称以及训练和测试准确度分数。主程序块首先加载样本数据,并将其分成训练测试子集。它继续用 KNeighborsClassifier 和 svm 训练数据。SVC 和报告准确性分数。正如所料,两种算法都非常准确地识别了标签,基本上没有过度拟合。
清单 2-10 中显示的最终代码示例通过将数据分成训练、测试和验证子集来扩展我们的知识。KNeighborsClassifier 用于训练和启用报告。
from sklearn.datasets import make_moons
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
def get_scores(model, Xtrain, ytrain, Xtest, ytest, Xvalid, yvalid):
y_ptrain = model.predict(Xtrain)
y_ptest = model.predict(Xtest)
y_pvalid = model.predict(Xvalid)
acc_train = accuracy_score(ytrain, y_ptrain)
acc_test = accuracy_score(ytest, y_ptest)
acc_valid = accuracy_score(yvalid, y_pvalid)
name = model.__class__.__name__
return (name, acc_train, acc_test, acc_valid)
if __name__ == "__main__":
br = '\n'
X_train, y_train = make_moons(n_samples=1000, shuffle=True, noise=0.2, random_state=0)
X_test, y_test = make_moons(n_samples=1000, shuffle=True, noise=0.2, random_state=0)
X_valid, y_valid = make_moons(n_samples=10000, shuffle=True, noise=0.2, random_state=0)
knn = KNeighborsClassifier().fit(X_train, y_train)
accuracy = get_scores(knn, X_train, y_train, X_test, y_test, X_valid, y_valid)
print ('train test valid split (technique 1):')
print ('<<' + str(accuracy[0]) + '>>')
print ('train:', accuracy[1], 'test:', accuracy[2], 'valid:', accuracy[3])
print ('sample split:', X_train.shape, X_test.shape, X_valid.shape)
print ()
X, y = make_moons(n_samples=1000, shuffle=True, noise=0.2, random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.25, random_state=0)
knn = KNeighborsClassifier().fit(X_train, y_train)
accuracy = get_scores(knn, X_train, y_train, X_test, y_test, X_valid, y_valid)
print ('train test valid split (technique 2):')
print ('<<' + str(accuracy[0]) + '>>')
print ('train:', accuracy[1], 'test:', accuracy[2], 'valid:', accuracy[3])
print ('sample split:', X_train.shape, X_test.shape, X_val.shape)
Listing 2-10Classify make_moons on train, validate, and test subsets
在执行清单 2-10 中的代码后,您的输出应该如下所示:
train test valid split (technique 1):
<<KNeighborsClassifier>>
train: 0.969 test: 0.969 valid: 0.9688
sample split: (1000, 2) (1000, 2) (10000, 2)
train test valid split (technique 2):
<<KNeighborsClassifier>>
train: 0.9616666666666667 test: 0.975 valid: 0.9694
sample split: (600, 2) (200, 2) (200, 2)
代码开始导入必需的包。函数 get_scores 被扩展以说明验证分数。主模块首先创建三个独立的测试、训练和验证子集。使用这种技术,我们创建了三个相同大小的数据集。虽然这种技术产生了很好的结果,但是随着数据集变得越来越大,它的计算开销也越来越大。实际上,这种技术要贵三倍,因为要创建和训练三个数据集。KNeighborsClassifier 用于训练、验证和测试。第二种技术非常常见,因为它将一个数据集分为训练、验证和测试。再次使用 KNeighborsClassifier。这两种技术的结果不相上下,并且和预期的一样优秀。
小费
只有当模型在训练和验证阶段完成训练后,才能使用测试数据,这样它才能对最终模型与训练数据的拟合程度进行无偏见的评估。
在行业中,机器学习工程师通过在训练之前将数据分成训练、测试和验证子集来试验数据问题。训练数据用于拟合(或训练)模型。模型从训练数据中观察和学习。
验证数据用于评估模型。机器学习工程师使用验证数据来微调模型的超参数。测试数据根据从拟合训练数据和用验证数据调整超参数中学到的知识,提供最终模型拟合的无偏评估。
三、复杂训练集的分类
复杂数据的分类与简单数据完全一样。数据被加载到特征集 X 和目标 y 中。X 数据由向量矩阵组成,其中每个向量代表一个数据元素,y 数据由目标向量组成。但是,复杂数据由大量的要素组成(成百上千)。这样的数据集通常被称为具有高维特征空间的数据集。文本数据也很复杂,因为每个文档都必须转换为适合机器学习算法的数值向量。
复杂数据集
我们专注于三个复杂的数据集:fetch_20newsgroups、MNIST 和 fetch_lfw_people。fetch_20newsgroups 由成千上万的新闻组帖子(文档)组成。MNIST 由数千幅 28 × 28 的图像组成,每幅图像由 784 个像素表示。fetch_lfw_people 由 1288 个 50 × 37 的图像组成,其中每个图像由 1850 个像素表示。
分类 fetch _ 新闻组
由于 Scikit-Learn 算法不接受原始文本,我们需要将其转换为可以用作输入的特征向量。TfidfVectorizer 将文本(表示为原始文档)转换为 TF-IDF 特征的矩阵‘54321’(可用作估计器输入的特征向量)。
TF-IDF(术语频率-逆文档频率)是一种数字统计,旨在反映一个单词在文档中的重要性。TF-IDF 是最受欢迎的术语加权方案之一,在数字图书馆基于文本的推荐系统中有 83%的使用。
TF-IDF 是一个非常大的话题。我们不会涉及太多细节,因为我们只是想用它来确定单词的重要性。然而,重要的是要知道单词重要性是由 TF-IDF 权重决定的,并且重要性与单词在文档中出现的次数成比例地增加。
只看词频的问题是,一些像“The”、“is”和“of”这样的词可能并不重要。因此,我们还可以查看逆文档频率,它降低了常用词的权重,增加了不常用词的权重。幸运的是,Scikit-Learn 包括了 TfidfVectorizer 包,它可以有效地结合单词和逆词频来提取有意义的信息。
将文本转换为要素数据不同于图像。图像变换包括将矩阵展平成相同长度的特征向量。对于文本,每个文档的大小通常不同。因此,我们需要一种类似 TF-IDF 的技术来将文档转换成 Scikit-Learn 算法可接受的 TF-IDF 特征矩阵。文本转换也更加复杂,因为字数会影响文档中单词的重要性。
文本分类特性与字数或频率有关,所以让我们使用一个适合这个目的的分类器。多项式贝叶斯分类器是一种朴素的贝叶斯分类器,适用于具有离散特征的多项式模型分类,如文本分类的字数。
清单 3-1 中显示的第一个代码示例对 fetch_20newsgroups 数据进行分类。第一次加载这个数据集时,您可能需要等待一段时间,所以请耐心等待。第一次加载后,您不会遇到延迟。
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline
from sklearn.metrics import confusion_matrix, f1_score
import matplotlib.pyplot as plt
import seaborn as sns
def predict_category(s, m, t):
pred = m.predict([s])
return t[pred[0]]
if __name__ == "__main__":
br = '\n'
train = fetch_20newsgroups(subset='train')
test = fetch_20newsgroups(subset='test')
print (train.target_names, br)
categories = ['rec.autos', 'rec.motorcycles', 'sci.space', 'sci.med']
train = fetch_20newsgroups(subset='train', categories=categories)
test = fetch_20newsgroups(subset='test', categories=categories)
print ('data subset:')
print (train.target.shape, 'shape of train data')
print (test.target.shape, 'shape of test data', br)
targets = train.target_names
mnb_clf = make_pipeline(TfidfVectorizer(), MultinomialNB())
print ('<<' + mnb_clf.__class__.__name__ + '>>', br)
mnb_clf.fit(train.data, train.target)
labels = mnb_clf.predict(test.data)
f1 = f1_score(test.target, labels, average="micro")
print ('f1_score', f1, br)
cm = confusion_matrix(test.target, labels)
plt.figure('confusion matrix')
sns.heatmap(cm.T, square=True, annot=True, fmt="d", cmap="gist_ncar_r", xticklabels=train.target_names, yticklabels=train.target_names, cbar=False)
print ('sci.med predictions:')
print (cm.T[2][2], 'correct predictions')
print (cm.T[2][0], 'misclassified as rec.autos')
print (cm.T[2][3], 'misclassified as sci.space')
plt.xlabel('true label')
plt.ylabel('predicted label')
plt.tight_layout()
print ('\n***PREDICTIONS***:')
y_pred = predict_category('payload on the mars rover', mnb_clf, targets)
print (y_pred)
y_pred = predict_category('car broke down on the highway', mnb_clf, targets)
print (y_pred)
y_pred = predict_category('dad died of cancer', mnb_clf, targets)
print (y_pred)
Listing 3-1Classify fetch_20newsgroups data
继续执行清单 3-1 中的代码。请记住,您可以从本书的示例下载中找到示例。您不需要手动键入示例。更容易访问示例下载和复制/粘贴。
执行清单 3-1 的输出应该如下所示:
['alt.atheism', 'comp.graphics', 'comp.os.ms-windows.misc', 'comp.sys.ibm.pc.hardware', 'comp.sys.mac.hardware', 'comp.windows.x', 'misc.forsale', 'rec.autos', 'rec.motorcycles', 'rec.sport.baseball', 'rec.sport.hockey', 'sci.crypt', 'sci.electronics', 'sci.med', 'sci.space', 'soc.religion.christian', 'talk.politics.guns', 'talk.politics.mideast', 'talk.politics.misc', 'talk.religion.misc']
data subset:
(2379,) shape of train data
(1584,) shape of test data
<<Pipeline>>
f1_score 0.9621212121212122
sci.med predictions:
370 correct predictions
1 misclassified as rec.autos
7 misclassified as sci.space
***PREDICTIONS***:
sci.space
rec.autos
sci.med
列表 3-1 也显示图 3-1 。图 3-1 显示了使用 TfidfVectorizer 文本转换的多项分类的混淆矩阵。
图 3-1
分类实验的混淆矩阵
代码从导入 fetch_20newsgroups、TfidfVectorizer、MultinomialNB 和其他一些熟悉的包开始。函数 predict_category 用于根据新数据预测类别标签。主程序块首先从 fetch_20newsgroups 加载训练和测试文档。接下来,它显示数据集中的目标类别。代码继续创建一个包含四个类别的训练和测试数据的子集,我选择在这个实验中使用这四个类别。请记住,您可以创建自己的子集。
子集训练和测试数据集用于建模。然后显示数据形状。接下来,使用 TfidfVectorizer 和 MultinomialNB 创建管道模型。TfidfVectorizer 提取文本并将其转换为数值向量,以便 MultinomialNB 可以对其进行训练。
小费
为了算法处理,文本数据必须转换成数字形式。
创建标签向量来保存来自测试数据子集的预测。显示准确度分数(f1_score)。 f1_score 是精度和召回分数的加权平均值。因此,这是一个更保守的估计。一个 f1 _ 以上的分数真的很好!
创建混淆矩阵是为了让我们了解我们的模型对测试数据的分类有多好。我们转置矩阵(cm。t ),使得预测标签在垂直轴上,真实(实际)标签在水平轴上。你不用转置,但我更容易解读结果。
混淆矩阵的对角线表示正确的分类。也就是说,对于 rec.autos 我们做了 389 个正确的分类,rec.motorcycles 我们做了 383 个正确的分类,sci.med 我们做了 370 个正确的预测,sci.space 我们做了 383 个正确的分类。错误分类是那些偏离对角线的分类。例如,我们将一个 sci.med 错误分类为 rec.autos,将七个错误分类为 sci.space。
代码结束时,根据训练好的模型对全新的数据进行预测。文字“火星探测车上的有效载荷”被归类为 sci.space,正确!接下来的两个文本字符串也能正确预测。由于模型准确率超过 96%,我们可以相当肯定我们的预测是可靠的。
前面的例子有一个问题。该算法能够从页眉、页脚和引号中理解文本的含义。就是我们选择的算法相当巧妙。
为了创建一个更真实的例子,我们可以从文本文档中删除页眉、页脚和引号,如清单 3-2 中的下一个例子所示。
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline
from sklearn.metrics import confusion_matrix, f1_score
import matplotlib.pyplot as plt
import seaborn as sns
def predict_category(s, m, t):
pred = m.predict([s])
return t[pred[0]]
if __name__ == "__main__":
br = '\n'
train = fetch_20newsgroups(subset='train')
test = fetch_20newsgroups(subset='test')
categories = ['rec.autos', 'rec.motorcycles', 'sci.space', 'sci.med']
train = fetch_20newsgroups(subset='train', categories=categories, remove=('headers', 'footers', 'quotes'))
test = fetch_20newsgroups(subset='test', categories=categories, remove=('headers', 'footers', 'quotes'))
targets = train.target_names
mnb_clf = make_pipeline(TfidfVectorizer(), MultinomialNB())
print ('<<' + mnb_clf.__class__.__name__ + '>>', br)
mnb_clf.fit(train.data, train.target)
labels = mnb_clf.predict(test.data)
f1 = f1_score(test.target, labels, average="micro")
print ('f1_score', f1, br)
cm = confusion_matrix(test.target, labels)
plt.figure('confusion matrix')
sns.heatmap(cm.T, square=True, annot=True, fmt="d", cmap="gist_ncar_r", xticklabels=train.target_names, yticklabels=train.target_names, cbar=False)
plt.xlabel('true label')
plt.ylabel('predicted label')
plt.tight_layout()
print ('***PREDICTIONS***:')
y_pred = predict_category('payload on the mars rover', mnb_clf, targets)
print (y_pred)
y_pred = predict_category('car broke down on the highway', mnb_clf, targets)
print (y_pred)
y_pred = predict_category('dad died of cancer', mnb_clf, targets)
print (y_pred)
plt.show()
Listing 3-2Classify fetch_20newsgroups removing identifying information
执行清单 3-2 的输出应该如下所示:
<<Pipeline>>
f1_score 0.8440656565656567
***PREDICTIONS***:
sci.space
rec.autos
sci.med
列表 3-2 也显示图 3-2 。图 3-2 显示了移除页眉、页脚和引号后的混淆矩阵。
图 3-2
没有页眉、页脚和引号的混淆矩阵
代码与前面的例子非常相似,只是我们从训练和测试子集中删除了页眉、页脚和引号。请注意,f1_score 下降到 84%多一点,这是一个相当大的下降!这是一个更现实的场景,因为文本数据可能不包括识别信息。
新数据的预测似乎是正确的,但是请注意,我预测的文本非常清楚。也就是说,我们训练的模型很容易做出正确的预测,因为文本中有直接指向正确类别的单词。例如,火星车上的有效载荷肯定是科幻空间*,因为该短语包含单词火星。*
在这种情况下,混淆矩阵是一个很好的视觉效果,因为它显示有许多错误分类,特别是试图区分 rec.autos 和 rec.motorcycles。当然,这很有意义,因为汽车和摩托车比我们子集中的其他两个类别更相似。
错误分类也是搜索算法和数据改进的好地方。也许需要更多的数据来提高准确性。也许该算法分类错误,因为它的一些或所有超参数需要调整。
MNIST 分类
在第一章中介绍了 MNIST,它是一个大型手写数字数据库,通常用于机器学习社区和其他工业图像处理应用程序的训练和测试。回顾一下,MNIST 包含 70000 张手写数字图像,大小为 28 × 28,从 0 到 9。每个目标存储为一个数字值。该特征集是 70000 个 28 × 28 图像的矩阵,每个图像自动展平为 784 像素。
使用整个 MNIST 数据集进行训练
接下来的两个代码示例为整个 MNIST 数据集定型。由于 MNIST 数据由高维特征空间组成,我们只使用精选分类器对其进行训练,以减少训练时间。
清单 3-3 中显示的第一个代码示例使用 RandomForestClassifier 和 ExtraTreesClassifier 训练 MNIST 数据,比较准确度得分,可视化混淆矩阵,并可视化错误分类场景。
import numpy as np, humanfriendly as hf
import time
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier,\
ExtraTreesClassifier
from sklearn.metrics import accuracy_score
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns
def get_time(time):
return hf.format_timespan(time, detailed=True)
def find_misses(test, pred):
return [i for i, row in enumerate(test) if row != pred[i]]
if __name__ == "__main__":
br = '\n'
X_file = 'data/X_mnist'
y_file = 'data/y_mnist'
X = np.load('data/X_mnist.npy')
y = np.load('data/y_mnist.npy')
X = X.astype(np.float32)
X_train, X_test, y_train, y_test = train_test_split\
(X, y, random_state=0)
rf = RandomForestClassifier(random_state=0, n_estimators=100)
rf_name = rf.__class__.__name__
print ('<<' + rf_name + '>>')
start = time.perf_counter()
rf.fit(X_train, y_train)
end = time.perf_counter()
elapsed_ls = end - start
timer = get_time(elapsed_ls)
rf_name = rf.__class__.__name__
y_pred = rf.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print ('\'test\' accuracy:', accuracy)
print (rf_name + ' timer:', timer, br)
cm = confusion_matrix(y_test, y_pred)
plt.figure(1)
ax = plt.axes()
sns.heatmap(cm.T, annot=True, fmt="d", cmap="gist_ncar_r", ax=ax)
ax.set_title(rf_name + 'confustion matrix')
plt.xlabel('true value')
plt.ylabel('predicted value')
et = ExtraTreesClassifier(random_state=0, n_estimators=100)
et_name = et.__class__.__name__
print ('<<' + et_name + '>>')
start = time.perf_counter()
et.fit(X_train, y_train)
end = time.perf_counter()
elapsed_ls = end - start
timer = get_time(elapsed_ls)
y_pred = et.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print ('\'test\' accuracy:', accuracy)
print (et_name + ' timer:', timer, br)
cm = confusion_matrix(y_test, y_pred)
plt.figure(2)
ax = plt.axes()
sns.heatmap(cm.T, annot=True, fmt="d", cmap="gist_ncar_r", ax=ax)
ax.set_title(et_name + 'confustion matrix')
plt.xlabel('true value')
plt.ylabel('predicted value')
indx = find_misses(y_test, y_pred)
print ('pred', 'actual')
misses = [(y_pred[row], y_test[row], i)
for i, row in enumerate(indx)]
[print (row[0], ' ', row[1]) for i, row in enumerate(misses)
if i < 5]
print()
img_act = y_test[indx[0]]
img_pred = y_pred[indx[0]]
print ('actual', img_act)
print ('pred', img_pred)
text = str(img_pred)
test_images = X_test.reshape(-1, 28, 28)
plt.figure(3)
plt.imshow(test_images[indx[0]], cmap="gray", interpolation="gaussian")
plt.text(0, 0.05, text, color="r", bbox=dict(facecolor='white'))
title = str(img_act) + ' misclassified as ' + text
plt.title(title)
plt.show()
Listing 3-3Classify MNIST data
执行清单 3-3 的输出应该如下所示:
<<RandomForestClassifier>>
'test' accuracy: 0.9687428571428571
RandomForestClassifier timer: 29 seconds and 620.77 milliseconds
<<ExtraTreesClassifier>>
'test' accuracy: 0.9727428571428571
ExtraTreesClassifier timer: 30 seconds and 462.9 milliseconds
pred actual
3.0 9.0
7.0 3.0
4.0 9.0
2.0 3.0
3.0 9.0
actual 9.0
pred 3.0
列表 3-3 还显示了数字 3-3 、 3-4 和 3-5 。图 3-3 显示了 RandomForestClassifier 的混淆矩阵。图 3-4 显示了提取树分类器的混淆矩阵。图 3-5 显示了第一次错误分类。
图 3-5
第一次错误分类
图 3-4
外分类学混淆矩阵
图 3-3
随机应变分类器混淆矩阵
代码从导入必需的包开始。函数 get_time 返回训练所需的时间。函数 find_misses 返回一个错误分类列表。主程序块将 MNIST 从 NumPy 文件加载到 X 和 y 中,将 X 转换成浮点数以供算法使用,并将数据分割成训练测试子集。
代码继续使用 RandomForestClassifier 和 ExtraTreesClassifier 对数据进行定型。对于这两种算法,显示精度和训练时间,并创建和可视化混淆矩阵。接下来,显示前五个错误分类。最后,可视化第一个错误分类(预测值:3.0,实际值:9.0)。请注意,训练确实需要一些时间(每个算法大约需要 30 秒)。原因是 MNIST 数据是由高维特征空间组成的。
请注意,人类很容易看出数字是 9 ,但对机器来说就不那么容易了,因为预测的是数字 3 。这种可视化以及混淆矩阵非常重要,因为它可以帮助数据科学家通过调整数据、试验不同的算法或改进算法来提高预测性能。
清单 3-4 中显示的下一个代码示例手动将数据分割成训练测试子集,以增加灵活性。也就是说,我们可以精确地调整训练测试子集的大小。
import numpy as np, humanfriendly as hf
import time
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.metrics import accuracy_score
from sklearn.metrics import classification_report
if __name__ == "__main__":
br = '\n'
X_file = 'data/X_mnist'
y_file = 'data/y_mnist'
X = np.load('data/X_mnist.npy')
y = np.load('data/y_mnist.npy')
X = X.astype(np.float32)
X_train, X_test, y_train, y_test = X[:60000], X[60000:],\
y[:60000], y[60000:]
shuffle_index = np.random.permutation(60000)
X_train, y_train = X_train[shuffle_index],\
y_train[shuffle_index]
et = ExtraTreesClassifier(random_state=0, n_estimators=100)
start = time.perf_counter()
et.fit(X_train, y_train)
end = time.perf_counter()
elapsed_ls = end - start
print (hf.format_timespan(elapsed_ls, detailed=True))
et_name = et.__class__.__name__
y_pred = et.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print (et_name + ' \'test\':', end=' ')
print ('accuracy:', accuracy, br)
rpt = classification_report(y_test, y_pred)
print (rpt)
Listing 3-4Classify MNIST with manual train-test shuffle
执行清单 3-4 的输出应该如下所示:
36 seconds and 533.76 milliseconds
ExtraTreesClassifier 'test': accuracy: 0.9706
precision recall f1-score support
0.0 0.97 0.99 0.98 980
1.0 0.99 0.99 0.99 1135
2.0 0.97 0.97 0.97 1032
3.0 0.97 0.96 0.96 1010
4.0 0.97 0.97 0.97 982
5.0 0.97 0.97 0.97 892
6.0 0.98 0.98 0.98 958
7.0 0.97 0.97 0.97 1028
8.0 0.97 0.96 0.96 974
9.0 0.95 0.95 0.95 1009
micro avg 0.97 0.97 0.97 10000
macro avg 0.97 0.97 0.97 10000
weighted avg 0.97 0.97 0.97 10000
代码从导入 classification_report 和其他必需的包开始。分类报告显示模型的精确度、召回率、F1 和支持度分数。
精度是分类器不将实际上是负面的实例标记为正面的能力。回忆是分类器找到所有肯定实例的能力。f1_ 分数是精确度和召回率的加权调和平均值,其中最好的分数是 1.0,最差的分数是 0.0。f1_ 分数通常低于精度度量,因为它们将精度和召回嵌入到计算中。 Support 是指定数据集中类的实际出现次数。
主块将 MNIST 加载到 X 和 y 中,并手动将数据混洗到训练测试子集中。在这种情况下,我们使用了更多的数据进行训练。具体来说,培训 6 万,测试 1 万。我们有更多的训练数据,如果我们有足够的数据来处理,这可能是一个很好的实验。接下来,我们用 ExtraTreesClassifier 进行训练,因为它在前面的例子中在 MNIST 上的表现比 RandomForestClassifier 好。最后,给出准确度分数和分类报告。
小费
如果您需要更大的灵活性,可以手动将数据放入训练测试子集。
训练 MNIST 样本数据
清单 3-5 中显示的第一个代码示例创建了一个来自 MNIST 的 4000 个数据元素的随机样本,以支持 svm 的高效训练。SVC 和 KNeighborsClassifier。这两种算法都是优秀的分类器,但众所周知,对于高维特征空间数据集,它们的计算开销很大。
import numpy as np, random, humanfriendly as hf
import time
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn import svm
from sklearn.neighbors import KNeighborsClassifier
import matplotlib.pyplot as plt
def prep_data(data, target):
d = [data[i] for i, _ in enumerate(data)]
t = [target[i] for i, _ in enumerate(target)]
return list(zip(d, t))
def create_sample(d, n, replace="yes"):
if replace == 'yes': s = random.sample(d, n)
else: s = [random.choice(d)
for i, _ in enumerate(d) if i < n]
Xs = [row[0] for i, row in enumerate(s)]
ys = [row[1] for i, row in enumerate(s)]
return np.array(Xs), np.array(ys)
def see_time(note):
end = time.perf_counter()
elapsed = end - start
print (note, hf.format_timespan(elapsed, detailed=True))
if __name__ == "__main__":
br = '\n'
X_file = 'data/X_mnist'
y_file = 'data/y_mnist'
X = np.load('data/X_mnist.npy')
y = np.load('data/y_mnist.npy')
X = X.astype(np.float32)
sample_size = 4000
data = prep_data(X, y)
Xs, ys = create_sample(data, sample_size, replace="no")
X_train, X_test, y_train, y_test = train_test_split(
Xs, ys, test_size=0.10, random_state=0)
scaler = StandardScaler().fit(X_train)
X_train_std, X_test_std = scaler.transform(X_train),\
scaler.transform(X_test)
svm = svm.SVC(random_state=0, gamma="scale")
svm_name = svm.__class__.__name__
print ('<<', svm_name, '>>')
start = time.perf_counter()
svm.fit(X_train_std, y_train)
see_time('train:')
start = time.perf_counter()
y_pred = svm.predict(X_test_std)
see_time('predict:')
start = time.perf_counter()
train_score = svm.score(X_train_std, y_train)
test_score = svm.score(X_test_std, y_test)
see_time('score:')
print ('train score:', train_score, 'test score', test_score, br)
knn = KNeighborsClassifier()
knn_name = knn.__class__.__name__
print ('<<', knn_name, '>>')
start = time.perf_counter()
knn.fit(X_train, y_train)
see_time('train:')
start = time.perf_counter()
y_pred = knn.predict(X_test)
see_time('predict:')
start = time.perf_counter()
train_score = knn.score(X_train, y_train)
test_score = knn.score(X_test, y_test)
see_time('score:')
print ('train score:', train_score, 'test score:', test_score)
Listing 3-5Classify MNIST with sample data
执行清单 3-5 的输出应该如下所示:
train: 6 seconds and 538.51 milliseconds
predict: 780.46 milliseconds
score: 7 seconds and 755.28 milliseconds
train score: 0.9802777777777778 test score 0.9075
<< KNeighborsClassifier >>
train: 116.53 milliseconds
predict: 1 second and 605.23 milliseconds
score: 15 seconds and 924.84 milliseconds
train score: 0.9519444444444445 test score: 0.91
代码从导入必需的包开始。函数 prep_data 将数据转换为数据元素列表,以便于采样。函数 create_sample 接受准备好的数据并创建一个随机样本而不替换。函数 see_time 返回经过的时间。
主块将数据加载到 X 和 y 中。接下来,创建 4000 个数据元素的样本,并将其分成训练测试子集。代码继续用 svm 训练样本。SVC 和 KNeighborsClassifier。
我们需要用这些算法来抽取随机样本,因为当训练大型数据集时,特别是那些具有高维特征空间的数据集时,这些算法的计算开销很大。
对于每种算法,都报告了训练、预测和评分时间。到目前为止,我们只捕获了火车时间。但是,有趣的是,我们可以看到培训过程的每个阶段占用了多少时间。对于我们的样本,KNeighborsClassifier 报告了比 svm.SVC 更少的过拟合的可观结果。
小费
通过调整 sample_size 变量,可以很容易地试验样本大小。
清单 3-6 中显示的下一个 MNIST 示例利用 PCA 增加了 7000 个样本,而没有增加太多的计算开销。
import numpy as np, random, humanfriendly as hf
import time
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn import svm
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import f1_score
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt, seaborn as sns
def prep_data(data, target):
d = [data[i] for i, _ in enumerate(data)]
t = [target[i] for i, _ in enumerate(target)]
return list(zip(d, t))
def create_sample(d, n, replace="yes"):
if replace == 'yes': s = random.sample(d, n)
else: s = [random.choice(d)
for i, _ in enumerate(d) if i < n]
Xs = [row[0] for i, row in enumerate(s)]
ys = [row[1] for i, row in enumerate(s)]
return np.array(Xs), np.array(ys)
def see_time(note):
end = time.perf_counter()
elapsed = end - start
print (note, hf.format_timespan(elapsed, detailed=True))
def get_scores(model, xtrain, ytrain, xtest, ytest):
ypred = model.predict(xtest)
train = model.score(xtrain, ytrain)
test = model.score(xtest, y_test)
f1 = f1_score(ytest, ypred, average="macro")
return (ypred, train, test, f1)
if __name__ == "__main__":
br = '\n'
X_file = 'data/X_mnist'
y_file = 'data/y_mnist'
X = np.load('data/X_mnist.npy')
y = np.load('data/y_mnist.npy')
X = X.astype(np.float32)
data = prep_data(X, y)
sample_size = 7000
Xs, ys = create_sample(data, sample_size, replace="no")
pca = PCA(n_components=0.95, random_state=0)
Xs_reduced = pca.fit_transform(Xs)
print ('sample feature shape:', Xs.shape)
components = pca.n_components_
print ('feature components with PCA:', components, br)
X_train, X_test, y_train, y_test = train_test_split(
Xs_reduced, ys, test_size=0.10, random_state=0)
scaler = StandardScaler().fit(X_train)
X_train_std, X_test_std = scaler.transform(X_train),\
scaler.transform(X_test)
start = time.perf_counter()
svm = svm.SVC(random_state=0).fit(X_train_std, y_train)
svm_name = svm.__class__.__name__
svm_scores = get_scores(svm, X_train_std, y_train, X_test_std, y_test)
cm_svm = confusion_matrix(y_test, svm_scores[0])
see_time(svm_name + ' total training time:')
print (svm_name + ':', svm_scores[1], svm_scores[2], svm_scores[3], br)
start = time.perf_counter()
knn = KNeighborsClassifier().fit(X_train, y_train)
knn_name = knn.__class__.__name__
knn_scores = get_scores(knn, X_train, y_train, X_test, y_test)
cm_knn = confusion_matrix(y_test, knn_scores[0])
see_time(knn_name + ' total training time:')
print (knn_name + ':', knn_scores[1], knn_scores[2], knn_scores[3])
plt.figure(svm_name)
ax = plt.axes()
sns.heatmap(cm_svm.T, annot=True, fmt="d", cmap="gist_ncar_r", ax=ax)
ax.set_title(str(svm_name) + ' confustion matrix')
plt.xlabel('true value')
plt.ylabel('predicted value')
plt.figure(knn_name)
ax = plt.axes()
sns.heatmap(cm_knn.T, annot=True, fmt="d", cmap="gist_ncar_r", ax=ax)
ax.set_title(str(knn_name) + ' confustion matrix')
plt.xlabel('true value')
plt.ylabel('predicted value')
plt.show()
Listing 3-6Classify MNIST with sample data and PCA
执行清单 3-6 的输出应该如下所示:
sample feature shape: (7000, 784)
feature components with PCA: 150
SVC total training time: 14 seconds and 290.91 milliseconds
SVC: 0.9955555555555555 0.9428571428571428 0.9425480948692136
KNeighborsClassifier total training time: 10 seconds and 313.37 milliseconds
KNeighborsClassifier: 0.9601587301587302 0.9371428571428572 0.9358573966927535
列表 3-6 还显示了数字 3-6 和 3-7 。图 3-6 显示 svm.SVC 的混淆矩阵。图 3-7 显示 KNeighborsClassifier 的混淆矩阵。
图 3-7
近邻分类器的混淆矩阵
图 3-6
svm 的混淆矩阵。交换虚拟电路
该代码示例从导入必备包开始。函数 prep_data 创建了一个数据元素列表,以便于处理。函数 create_sample 创建 7000 个数据元素的随机样本,没有替换。函数 see_time 返回经过的时间。函数 get_scores 返回分数。
主模块首先将数据加载到 X 和 y 中,然后创建 7000 个数据元素的随机样本。PCA 被用来将 784 个特征减少到 150 个特征,并且有 5%的信息丢失。特征集是从 PCA 模型创建的。显示原始样本形状以及 PCA 中减少的特征成分。接下来,svm。SVC 和 KNeighborsClassifier 对样本进行训练。显示每个模型的总训练时间和得分。分数分别报告为训练准确度、测试准确度和测试 f1_score。代码最后为每个模型创建并显示混淆矩阵。
请注意,使用 7000 这个较大的样本对两个模型都有更好的拟合。也就是说,我们有更少的过度拟合。
随着数据集变得越来越大,采样在行业中是非常常见的做法。采样可以极大地减少计算开销,同时提供一个即使是计算开销最大的算法的预测能力的好主意。此外,降维结合采样可以进一步降低计算开销!
小费
采样降维可以显著降低计算开销。
分类 fetch_lfw_people
fetch_lfw_people 由来自野外(lfw)标记人脸的预处理图像组成,这是一个为研究无约束人脸识别而设计的数据库。LFW 包含了从网上收集的 13000 多张人脸图像。每张照片都标有照片中人的名字。要了解更多关于 LFW 的信息,请点击此链接: http://vis-www.cs.umass.edu/lfw/ 。在我们的实验中,我们只考虑数据集中至少有 70 张照片的人。图像被调整到 0.4 的纵横比。
清单 3-7 中显示的第一个代码示例用 svm 对 fetch_lfw_people 进行分类。SVC 是人脸识别中最有用的算法之一。
import numpy as np
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt
if __name__ == "__main__":
br = '\n'
X = np.load('data/X_faces.npy')
y = np.load('data/y_faces.npy')
images = np.load('data/faces_images.npy')
targets = np.load('data/faces_targets.npy')
_, h, w = images.shape
n_images = X.shape[0]
n_features = X.shape[1]
n_classes = len(targets)
print ('features:', n_features)
print ('images:', n_images)
print ('classes:', n_classes, br)
print ('target names:')
print (targets, br)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
pca = PCA(n_components=0.95, whiten=True, random_state=0)
pca.fit(X_train)
components = pca.n_components_
eigenfaces = pca.components_.reshape((components, h, w))
X_train_pca = pca.transform(X_train)
pca_name = pca.__class__.__name__
print ('<<' + pca_name + '>>')
print ('features (after PCA):', components)
print ('eigenface shape:', eigenfaces.shape, br)
print (pca, br)
svm = SVC(kernel='rbf', class_weight="balanced", gamma="scale", random_state=0)
svm_name = svm.__class__.__name__
svm.fit(X_train_pca, y_train)
X_test_pca = pca.transform(X_test)
y_pred = svm.predict(X_test_pca)
cr = classification_report(y_test, y_pred)
print ('classification report <<' + svm_name+ '>>')
print (cr)
ls = [np.array(eigenfaces[i].reshape(h, w))
for i, row in enumerate(range(9))]
fig, ax = plt.subplots(3, 3, figsize=(5, 6))
cnt = 0
for row in [0, 1, 2]:
for col in [0, 1, 2]:
ax[row, col].imshow(ls[cnt], cmap="bone", aspect="auto")
ax[row, col].set_axis_off()
cnt += 1
plt.tight_layout()
plt.show()
Listing 3-7Classify fetch_lfw_people data
执行清单 3-7 的输出应该如下所示:
features: 1850
images: 1288
classes: 7
target names:
['Ariel Sharon' 'Colin Powell' 'Donald Rumsfeld' 'George W Bush'
'Gerhard Schroeder' 'Hugo Chavez' 'Tony Blair']
<<PCA>>
features (after PCA): 135
eigenface shape: (135, 50, 37)
PCA(copy=True, iterated_power="auto", n_components=0.95,
random_state=0, svd_solver="auto", tol=0.0, whiten=True)
classification report <<SVC>>
precision recall f1-score support
0 1.00 0.61 0.76 28
1 0.63 0.94 0.76 63
2 0.90 0.75 0.82 24
3 0.88 0.86 0.87 132
4 0.75 0.75 0.75 20
5 1.00 0.59 0.74 22
6 0.90 0.82 0.86 33
micro avg 0.82 0.82 0.82 322
macro avg 0.87 0.76 0.79 322
weighted avg 0.85 0.82 0.82 322
列表 3-7 也显示图 3-8 。图 3-8 显示了 PCA 创建的特征脸。
图 3-8
PCA 生成的特征脸
该代码示例从加载必备包开始。主模块将图像数据加载到特征集 X、目标集 y,将图像矩阵加载到变量图像,将目标名称加载到变量目标。代码继续将数据分割成训练测试子集。接下来,创建 PCA 模型,白化设置为真。启用白化以减少输入数据(或 X_train)中的冗余。
因为每个图像由 1850 个像素组成,所以我们的特征集有 1850 个维度。因此,主成分分析允许我们将维数减少到 135。维度(或特征)越少,计算费用就越低。更少的特征也降低了模型的复杂性,这可以减轻过度拟合。
PCA 试图通过保留最重要的特征,用尽可能少的维度来表示训练数据方差。当 PCA 用于图像时,剩下的特征通常被称为特征脸。特征面表示投影到来自训练集的每个数据样本上以获得独立特征的主要图像集。也就是说,算法使用特征脸从数据中学习。
PCA 在 X_train 数据上训练,有 5%的信息丢失。接下来,确定 PCA 分量和特征面(或最佳剩余特征)。代码继续将带有 PCA 的 X_train 转换为带有 135 个特征(而不是 1850)的 X_train_pca。然后,我们用 svm 训练 X_train_pca,并创建预测集 y_pred,以便我们可以创建分类报告。代码以从特征脸的前九个数据元素创建图像结束。欲知详情,请访问: http://efavdb.com/machine-learning-for-facial-recognition-3/ 。
小费
PCA 不仅是无监督学习实验的良好模型,它还能够对训练集进行降维,从而为监督学习实验带来更快的处理和更少的过拟合。
清单 3-8 中显示的下一个代码示例与前一个示例完全一样地训练数据,但是这一次我们可视化了第一个正确的分类和第一个错误的分类。代码继续可视化四个随机预测。代码可能看起来非常复杂,但大部分工作都与使用 Matplotlib 创建漂亮的视觉效果有关。如果你还没有弄明白,Matplotlib 是而不是非常用户友好。
import numpy as np
from random import randint
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
import matplotlib.pyplot as plt
def find_misses(test, pred):
return [i for i, row in enumerate(test) if row != pred[i]]
def find_hit(n, ls):
return True if n in ls else False
def build_fig(indx, pos, color, one, two):
X_i = np.array(X_test[indx]).reshape(50, 37)
t = targets[y_test[indx]]
p = targets[y_pred[indx]]
ax = fig.add_subplot(pos)
image = ax.imshow(X_i, cmap='bone')
ax.set_axis_off()
ax.set_title(t)
ax.text(one, two, p, color=color, bbox=dict(facecolor='white'))
def chk_acc(rnds):
logic = [1 if y_test[row] == y_pred[row] else 0 for row in rnds]
colors = ['g' if row == 1 else 'r' for row in logic]
return colors
if __name__ == "__main__":
br = '\n'
X = np.load('data/X_faces.npy')
y = np.load('data/y_faces.npy')
images = np.load('data/faces_images.npy')
targets = np.load('data/faces_targets.npy')
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
pca = PCA(n_components=0.95, whiten=True, random_state=0)
pca.fit(X_train)
X_train_pca = pca.transform(X_train)
pca_name = pca.__class__.__name__
svm = SVC(kernel='rbf', class_weight="balanced", gamma="scale", random_state=0)
svm_name = svm.__class__.__name__
svm.fit(X_train_pca, y_train)
X_test_pca = pca.transform(X_test)
y_pred = svm.predict(X_test_pca)
misses = find_misses(y_test, y_pred)
miss = misses[0]
hit = 1
X_hit = np.array(X_test[hit]).reshape(50, 37)
y_test_hit = targets[y_test[hit]]
y_pred_hit = targets[y_pred[hit]]
X_miss = np.array(X_test[miss]).reshape(50, 37)
y_test_miss = targets[y_test[miss]]
y_pred_miss = targets[y_pred[miss]]
fig = plt.figure('1st Hit and Miss')
fig.suptitle('Visualize 1st Hit and Miss', fontsize=18, fontweight="bold")
build_fig(hit, 121, 'g', 0.4, 1.9)
build_fig(miss, 122, 'r', 0.4, 1.9)
rnd_ints = [randint(0, y_test.shape[0]-1) for row in range(4)]
colors = chk_acc(rnd_ints)
fig = plt.figure('Four Random Predictions')
build_fig(rnd_ints[0], 221, colors[0], .9, 4.45)
build_fig(rnd_ints[1], 222, colors[1], .9, 4.45)
build_fig(rnd_ints[2], 223, colors[2], .9, 4.45)
build_fig(rnd_ints[3], 224, colors[3], .9, 4.45)
plt.tight_layout()
plt.show()
Listing 3-8Classify fetch_lfw_people data and visualize
列表 3-8 显示数字 3-9 和 3-10 。图 3-9 是来自训练实验的首次命中(或正确分类)和首次未命中(错误分类)的可视化。图 3-10 是四个随机预测的可视化。
图 3-10
四个随机预测
图 3-9
训练实验的第一次失败
代码从导入必需的包开始。函数 find_misses 返回测试集中错误分类的索引。函数 find_hit 有助于发现所提供的索引是否被正确分类。
在提供的代码中没有实现函数 find_hit,但是您可以通过将索引和未命中列表输入到函数中来测试它。如果函数返回 True ,则预测是正确的;否则,预测会被错误分类。我测试了索引为 1 的函数,该函数返回 True。
函数 build_figure 使我们能够构建可视化效果。虽然代码看起来很复杂,但实际上非常细致。也就是说,正确定位文本需要时间。函数 chk_acc 为错误分类返回红色,为正确分类返回绿色。
主程序块加载数据并进行训练,与前面的例子完全一样。剩下的代码创建可视化效果。第一视觉显示测试集中的第一正确分类和第一错误分类。因此,Colin Powell 图像在索引 1 处被正确分类(测试集中的第 2 和第个数据元素)。乔治·w·布什的图像被错误地归类为科林·鲍威尔,而它恰好位于索引 0 处(测试集中的第一个数据元素)。
第二个可视化是通过生成四个随机数并使用它们作为测试集中可视化的索引来创建的。我们的可视化显示了四个正确分类中的三个。唯一的错误分类是乌戈·查韦斯被误认为科林·鲍威尔。
请记住,每次运行代码时,您都会看到不同的图像,因为我们会随机生成索引。此外,您很可能会看到四个正确的分类,因为我们的准确率是 85%。
清单 3-9 中显示的最终代码示例是为了完整性。我想告诉你如何使用 LDA 降维而不是 PCA。在这种情况下,LDA 表现不佳,但在给定不同数据集的情况下,它可能表现得更好。
import numpy as np
from sklearn.decomposition import PCA
from sklearn.discriminant_analysis import\
LinearDiscriminantAnalysis
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import classification_report
import warnings
if __name__ == "__main__":
br = '\n'
warnings.filterwarnings('ignore')
X = np.load('data/X_faces.npy')
y = np.load('data/y_faces.npy')
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
pca = PCA(n_components=0.95, whiten=True, random_state=0)
pca.fit(X_train)
components = pca.n_components_
lda = LinearDiscriminantAnalysis(n_components=components)
lda.fit(X_train, y_train)
X_train_lda = lda.transform(X_train)
svm = SVC(kernel='rbf', class_weight="balanced", gamma="scale", random_state=0)
svm_name = svm.__class__.__name__
svm.fit(X_train_lda, y_train)
X_test_lda = lda.transform(X_test)
y_pred = svm.predict(X_test_lda)
cr = classification_report(y_test, y_pred)
print ('classification report <<' + svm_name+ '>>')
print (cr)
Listing 3-9Dimensionality reduction with LDA
执行清单 3-9 的输出应该如下所示:
classification report <<SVC>>
precision recall f1-score support
0 1.00 0.21 0.35 28
1 0.84 0.49 0.62 63
2 0.69 0.38 0.49 24
3 0.56 0.96 0.71 132
4 0.50 0.15 0.23 20
5 0.73 0.36 0.48 22
6 0.67 0.42 0.52 33
micro avg 0.61 0.61 0.61 322
macro avg 0.71 0.43 0.49 322
weighted avg 0.68 0.61 0.58 322
代码很短,因为性能远低于 PCA (68%对 85%的准确率)。如果 PCA 是更好的模型,我们为什么要创建特征脸、预测和可视化?请注意,我们使用 PCA 来确定具有 5%信息丢失的最佳组件数量,然后 LDA 使用它来通过 svm.SVC 进行预测。