数据预处理是一个总括性术语,涵盖了数据科学家为使数据更适合其目标任务而进行的一系列操作。举例来说,在对 Twitter 数据做情感分析之前,您可能需要去除所有 HTML 标签、空白字符、展开缩写,并将推文拆分为其包含的词列表。处理空间数据时,您可能会对其进行缩放,使其与单位无关,也就是说无论原始测量是以英里还是厘米为单位,算法都不受影响。然而,数据预处理并非孤立进行。换言之,预处理是达成目标的手段,并无一成不变的规则:如我们将看到的,有一些常见做法,您也可以逐步形成对何种方法有效的直觉,但归根结底,预处理通常是以结果为导向的流程的一部分,其效果需要在具体情境中评估。
假设我们有带标签的数据,例如包含红酒特征的数据(如酒精含量、密度、柠檬酸含量、pH 等;这些是预测变量),目标变量为“质量”,标签为“好”“差”。那么,给定一款新的、未标注的红酒的特征,分类任务就是预测其“质量”。当所有预测变量都是数值型(类别型的情形也有对应方法)时,我们可以将每一行/每一款酒视为 n 维空间中的一个点。在这种情况下,k 近邻(k-NN)是一个概念上与计算上都很简单的分类方法:对于每一款新的未标注红酒,我们选定某个整数 k,在 n 维预测变量空间中计算其 k 个最近邻。然后查看这 k 个邻居的标签(即“好”或“差”),并将出现次数最多的标签赋给新红酒(例如 k = 5,若 3 个邻居投“好”、2 个投“差”,则模型将该酒标为“好”)。需要注意的是,这里训练模型仅仅是存储数据点:没有需要拟合的参数!
y = y1 <=5# is the rating <= 5?# plot histograms of original target variable# and aggregated target variableplt.figure(figsize=(20,5));plt.subplot(1, 2, 1 );plt.hist(y1);plt.xlabel('original target value')plt.ylabel('count')plt.subplot(1, 2, 2);plt.hist(y)plt.xlabel('aggregated target value')plt.show()技术支持
现在我们几乎可以进行 k 近邻了。不过在比较有无预处理时模型的表现之前,我们需要先弄清如何衡量模型的“好坏”:
k 近邻:表现如何?
分类任务有多种性能度量。至关重要的一点是要认识到,度量的选择高度依赖于领域与问题本身。对于类别较为平衡的数据集(所有目标值大致同样常见),数据科学家通常会使用准确率作为性能度量。事实上,如我们将看到的,准确率是 scikit-learn 中 k 近邻与逻辑回归的默认评分方法。那么什么是准确率?它只是正确预测的数量除以预测总数:
$$\text{Accuracy}=\frac{\text{Number of Correct Predictions}}{\text{Total Number of Predictions}}.$$
from sklearn.cross_validation import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)We now build the k-NN model, make predictions on the test setand compare these predictions to the ground truth in order to get a measure of model performance:from sklearn import neighbors, linear_modelknn = neighbors.KNeighborsClassifier(n_neighbors =5)knn_model_1 = knn.fit(X_train, y_train)print('k-NN accuracy for test set: %f'% knn_model_1.score(X_test, y_test))k-NN accuracy for test set:0.612500技术支持
下面我将(i)对数据进行缩放,(ii)使用 k 近邻,(iii)检查模型表现。我将使用 scikit-learn 的scale 函数,它会对传入数组的所有特征(列)进行标准化。
from sklearn.preprocessing import scaleXs = scale(X)from sklearn.cross_validation import train_test_splitXs_train, Xs_test, y_train, y_test = train_test_split(Xs, y, test_size=0.2, random_state=42)knn_model_2 = knn.fit(Xs_train, y_train)print('k-NN score for test set: %f'% knn_model_2.score(Xs_test, y_test))print('k-NN score for training set: %f'% knn_model_2.score(Xs_train, y_train))y_true, y_pred = y_test, knn_model_2.predict(Xs_test)print(classification_report(y_true, y_pred))技术支持
k-NN score for test set:0.712500k-NN score for training set:0.814699 precision recall f1-score support False0.720.790.75179True0.700.620.65 141avg / total 0.710.710.71320技术支持
我们已经看到,预处理(以缩放与居中为代表)在数据科学流水线中占据着关键位置,而我们这样做是为了倡导一种整体化的机器学习思路。后续文章中,我希望将讨论扩展到其他类型的预处理,例如数值数据的变换与类别数据的预处理——它们都是数据科学家工具箱中的要件。在此之前,下一篇我将探讨缩放在基于回归的方法处理分类问题时的作用。具体而言,我会考察逻辑回归,您将看到其结果与刚才在 k 近邻背景下看到的非常不同。
import pandas as pdimport matplotlib.pyplot as pltfrom sklearn.cross_validation import train_test_splitfrom sklearn import neighborsfrom sklearn.preprocessing import scalefrom sklearn.metrics import classification_report # Set the the number of neighbors for k-NNn_neig = 5# Set sc = True if you want to scale your featuressc = False# Load datadf = pd.read_csv('http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv ' , sep = ';')X = df.drop('quality' , 1).values# drop target variable# Here we scale, if desiredif sc == True: X = scale(X)# Target valuey1 = df['quality'].values# original target variabley = y1 <= 5# new target variable: is the rating <= 5?# Split the data into a test set and a training setX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# Train k-NN model and print performance on the test setknn = neighbors.KNeighborsClassifier(n_neighbors = n_neig)knn_model = knn.fit(X_train, y_train)y_true, y_pred = y_test, knn_model.predict(X_test)print('k-NN accuracy for test set: %f' % knn_model.score(X_test, y_test))print(classification_report(y_true, y_pred))技术支持
本文将以数值数据的缩放为例(数值数据:由数字组成的数据,与类别/字符串相对;缩放:用基本算术改变数据取值范围;后文详述),展示为何应将预处理视作更大结构——机器学习(ML)流水线——的一部分。为此,我们将看到一个现实案例,说明缩放如何提升模型表现。
我将先介绍机器学习中的分类问题,以及其中一种最简单的算法——k 近邻(k-Nearest Neighbors,k-NN)。为体会在此类场景中缩放数值数据的重要性,我还会介绍模型性能度量,以及训练集与测试集的概念。您将结合一个数据集看到这些概念与实践:我们尝试对红酒的品质进行分类。同时,我会把预处理放在最有用的位置——迭代式数据科学流水线的开端附近。文中所有示例均使用 Python。若您不熟悉 Python,可在此处查看我们的 DataCamp 课程。我将使用 pandas 处理数据框,使用 scikit-learn 进行机器学习。
本文是三部分教程的第一部分:
第二部分:居中、缩放与逻辑回归
第三部分:合成数据的缩放
机器学习中分类问题的简要介绍
对现实世界事物进行分类与标注是一门古老的技艺。公元前 4 世纪,亚里士多德构建了一个生物分类体系,沿用达 2000 年之久。在现代,分类通常被表述为机器学习任务,具体来说,是一种监督学习任务。监督学习的基本原则很直接:我们有一堆由预测变量与一个目标变量组成的数据。监督学习的目标,是在给定预测变量的情况下,构建一个“擅长”预测目标变量的模型。若目标变量由类别构成(例如“点击”或“未点击”,“恶性”或“良性”肿瘤),我们称该学习任务为分类。相反,若目标是连续变化的变量(例如房价),则是回归任务。
一个示例有助于理解:考虑心脏病数据集,其中包含 75 个预测变量,如“年龄”“性别”“是否吸烟”等,目标变量表示是否患有心脏病,取值范围为 0(无心脏病)到 4。关于该数据集的许多研究都集中在区分是否患病上。这是一个分类任务。若您尝试预测目标变量的实际取值(0 到 4),则是回归问题(因为目标变量是有序的)。我将在下一篇讨论回归。这里我将考察分类任务中最简单的算法之一:k 近邻算法。
机器学习中的 k 近邻分类
假设我们有带标签的数据,例如包含红酒特征的数据(如酒精含量、密度、柠檬酸含量、pH 等;这些是预测变量),目标变量为“质量”,标签为“好”“差”。那么,给定一款新的、未标注的红酒的特征,分类任务就是预测其“质量”。当所有预测变量都是数值型(类别型的情形也有对应方法)时,我们可以将每一行/每一款酒视为 n 维空间中的一个点。在这种情况下,k 近邻(k-NN)是一个概念上与计算上都很简单的分类方法:对于每一款新的未标注红酒,我们选定某个整数 k,在 n 维预测变量空间中计算其 k 个最近邻。然后查看这 k 个邻居的标签(即“好”或“差”),并将出现次数最多的标签赋给新红酒(例如 k = 5,若 3 个邻居投“好”、2 个投“差”,则模型将该酒标为“好”)。需要注意的是,这里训练模型仅仅是存储数据点:没有需要拟合的参数!
k 近邻的可视化描述
下图展示了二维情形下的 k-NN:如何对中间的数据点分类?如果 k=3,您会将其分类为红色;如果 k=5,则为绿色。
用 Python(scikit-learn)实现 k-NN
首先注意各预测变量的取值范围:“游离二氧化硫”的范围从 0 到约 70,而“挥发性酸度”从约 0 到约 1.2。更具体地说,前者的范围比后者大两个数量级。任何关注数据点间距离的算法(例如 k-NN),都可能因此把注意力集中(且不公平地)放在取值范围更大的变量上,比如“游离二氧化硫”,而该变量很可能只是噪声。由此我们有了对数据进行缩放的动机,稍后将会进行。
目标变量是红酒的“质量”评分,范围为 3 到 8。为便于讲解,让我们把它转为二分类变量:“好”(评分 > 5)与“差”(评分 <= 5)。我们还将分别绘制目标变量原始形式与聚合形式的直方图,以便把握整体情况。
k 近邻:表现如何?
注:也可通过混淆矩阵来定义准确率,在二分类问题中常以真正例与假负例来表述;从混淆矩阵还能导出其他常见的模型性能度量,例如精确率(真正例数除以真阳性与假阳性之和)与召回率(真正例数除以真正例与假负例之和);另一个度量F1 分数是精确率与召回率的调和平均。参见 machine learning mastery 对这些度量的清晰讲解;也可查看维基百科关于混淆矩阵与F1 分数的条目。
k 近邻:实践中的表现与训练-测试切分
现在我们将引入缩放与居中,这是对数值数据最基础的预处理方法,看看它们是否以及如何影响模型表现。
预处理机制:缩放与居中
在对数据运行模型(如回归:预测连续变量;或分类:预测离散变量)之前,几乎总需要做一些预处理。对于数值变量,常见做法是对数据进行归一化或标准化。这些术语是什么意思?
归一化的含义是将数据集缩放到最小值为 0、最大值为 1。为此,我们将每个数据点 x 变换为
$$x_{normalized} = \frac{x-x_{min}}{x_{max}-x_{min}}.$$标准化略有不同;其作用是围绕 0 对数据进行居中,并按标准差进行缩放:
$$x_{standardized} = \frac{x-\mu}{\sigma},$$其中 $\mu$ 和 $\sigma$ 分别为数据集的均值与标准差。首先要注意,这些变换只改变数据的取值范围,而不改变其分布。您可能还会进一步使用其他变换,如对数变换或 Box-Cox 变换,使数据更接近高斯分布(钟形曲线)。但在继续之前,重要的是要问:为什么要缩放数据?在什么情况下更合适?例如,在分类问题中是否比在回归中更重要?
我们先深入分类问题,看看缩放如何影响 k 近邻的表现:
预处理:实践中的缩放
下面我将(i)对数据进行缩放,(ii)使用 k 近邻,(iii)检查模型表现。我将使用 scikit-learn 的scale 函数,它会对传入数组的所有特征(列)进行标准化。
所有这些指标都提升了 0.1,约合 16% 的提升,且意义明显!正如上文所提,缩放前有许多预测变量的数值范围相差数个数量级,这意味着在 k-NN 等算法的语境中,一两个变量可能会占据主导。缩放数据的两个主要原因是:
我们已经看到,预处理(以缩放与居中为代表)在数据科学流水线中占据着关键位置,而我们这样做是为了倡导一种整体化的机器学习思路。后续文章中,我希望将讨论扩展到其他类型的预处理,例如数值数据的变换与类别数据的预处理——它们都是数据科学家工具箱中的要件。在此之前,下一篇我将探讨缩放在基于回归的方法处理分类问题时的作用。具体而言,我会考察逻辑回归,您将看到其结果与刚才在 k 近邻背景下看到的非常不同。
在下方的交互窗口中,您可以自行尝试。先修改变量 n_neig,即 k-NN 算法使用的邻居数量。您也可以通过将 sc = True 来对数据进行缩放。然后运行整个脚本,查看模型的准确率与分类报告。
术语表
监督学习:从预测变量推断目标变量的任务。例如,从“年龄”“性别”“吸烟状态”等预测变量推断“是否患有心脏病”这一目标变量。
分类任务:若监督学习中的目标变量由类别构成(例如“点击/未点击”“恶性/良性”肿瘤),则该任务为分类任务。
回归任务:若监督学习中的目标变量是连续变化变量(如房价)或有序类别变量(如“红酒质量评分”),则该任务为回归任务。
k 近邻:一种用于分类任务的算法,将数据点赋予其 k 个最近邻多数表决所决定的标签。
预处理:数据科学家为使数据更适合其目标任务而使用的各种操作。例如,在对 Twitter 数据进行情感分析之前,您可能需要去除 HTML 标签、空白字符、展开缩写,并将推文拆分为包含的词列表。
居中与缩放:均为对数值数据(即由数字组成,而非类别或字符串)的预处理形式;居中是指从每个数据点中减去该变量的均值,使新变量的均值为 0;缩放是指将每个数据点乘以一个常数,以改变数据的取值范围。有关其重要性与示例,请参见正文。
本文由 Jupyter 笔记本生成。您可以在此处下载该笔记本。