scikit-learn 是一个著名的机器学习框架,它实现了很多数据挖掘算法、通用编程接口、标准化测试工具等等。
这篇文章主要讲解使用 sklearn 的数据挖掘通用框架的搭建方法,还有一些使用接口的通用方法,以及自己实现一些接口。sklearn 提供的接口使用起来很相似,主要有以下三类。
- 估计器 (Estimator):用于分类、聚类和回归分析。
- 转换器(Transformer):用于数据预处理和数据转换。
- 流水线(Pipeline):组合数据挖掘流程,便于再次使用。
我们下面分别来说一说他们:
1. 估计器
估计器,或者有些人喜欢叫预测器(predicter),是一些由 sklearn 封装好的类,他们至少需要包括两个方法:
- fit() :训练算法,设置内部参数。该函数接收训练集及其类别两个参数。返回一个训练好的模型对象。
- predict() :参数为测试集。预测测试集类别,并返回一个包含测试集各条数据类别的数组。或者估计预测数值,同样返回一个包含测试集各条数据类别的数组。
- score(): 参数为测试集数据,测试集标签,直接返回准确度
下面我们来看一个例子,看看估计器是如何被使用的。数据可以从 http://archive.ics.uci.edu/ml/machine-learning-databases/ionosphere/ 里面的 Data Folder 处下载。这是一个二分类任务,最后一个值不是“g”就是“b”,表示数据的好坏,即是否提供了有价值的信息。
首先导入必要的库
import numpy as np
import csv
import os
然后导入数据集,按照常规的做法我们将数据导入 X 中,将类别导入到 y 中。因为我们不需要做其他的分析,所以我们就直接导入到 numpy 数组中了。
datafile = "D:\jupyter\sk-learn\dataset"
data_filename = os.path.join(datafile, "ionosphere.data")
X = np.zeros((351, 34), dtype='float')
y = np.zeros((351,), dtype='bool')
with open(data_filename, 'r') as input_file:
reader = csv.reader(input_file)
for i, row in enumerate(reader):
data = [float(datum) for datum in row[:-1]]
X[i] = data
y[i] = row[-1] == 'g'
接下来需要创建训练集和测试集。导入并运行 train_test_split 函数。
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=14)
下面就是我们的估计器的使用,我们这次使用的估计器是基于 K 近邻的 KNeighborsClassifier
from sklearn.neighbors import KNeighborsClassifier
# 创建估计器
estimator = KNeighborsClassifier()
# 训练估计器
estimator.fit(X_train, y_train)
# 使用训练好的模型预测
y_predicted = estimator.predict(X_test)
# 计算准确度
accuracy = np.mean(y_test == y_predicted)
accuracy
# 0.8636363636363636
estimator.score(X_test, y_test)
# 0.8636363636363636
为了更加客观的评价模型的好坏,避免数据集选择对我们的影响,我们可以使用交叉验证的方法。scikit-learn 提供了几种交叉检验方法。有个辅助函数实现了上述交叉检验步骤,现在把它导进来。
from sklearn.model_selection import cross_val_score
scores = cross_val_score(estimator, X, y, scoring='accuracy', cv=10)
average_accuracy = np.mean(scores) * 100
print("The average accuracy is {0:.1f}%".format(average_accuracy))
# The average accuracy is 83.0%
准确率还不是很高我们可以通过调整参数来提高准确度,那么如何选取参数呢,一个很直观的想法就是,测试一系列 n_neighbors 的值,比如从 1到 20,可以重复进行多次实验,观察不同的参数值所带来的结果之间的差异。
avg_scores = []
all_scores = []
parameter_values = list(range(1, 21))
for n_neighbors in parameter_values:
estimator = KNeighborsClassifier(n_neighbors=n_neighbors)
scores = cross_val_score(estimator, X, y, scoring='accuracy', cv=10)
avg_scores.append(np.mean(scores))
all_scores.append(scores)
%matplotlib inline
from matplotlib import pyplot as plt
plt.plot(parameter_values, avg_scores, '-o')
plt.show()
从上图可以看到对于我们的这个问题似乎近邻数选择 2 是个不错的选择。
estimator = KNeighborsClassifier(n_neighbors=2)
estimator.fit(X_train, y_train)
y_predicted = estimator.predict(X_test)
accuracy = np.mean(y_test == y_predicted)
accuracy
# 0.9204545454545454
2. 转换器
如果说估计器是输入一组数据输出一个预测值,那么转换器就是输入一组数据,输出转化后的数据。
正如它的名字一样,这类的接口常常被用于转化数据,比如说处理非数值型数据,抽取组合特征等等,一般来说估计器用于创建模型,转换器用于数据预处理
一般来说转换器有下面三个方法:
- fit() : 简单来说,就是求得训练集X的均值啊,方差啊,最大值啊,最小值啊这些训练集X固有的属性。可以理解为一个训练过程。
- transform() : 在Fit的基础上,进行标准化,降维,归一化等操作(看具体用的是哪个工具,如PCA,StandardScaler等)
- fit_transform() : fit_transform是fit和transform的组合,既包括了训练又包含了转换。
transform()和fit_transform()二者的功能都是对数据进行某种统一处理(比如标准化~N(0,1),将数据缩放(映射)到某个固定区间,归一化,正则化等)
fit_transform(trainData)对部分数据先拟合fit,找到该part的整体指标,如均值、方差、最大值最小值等等(根据具体转换的目的),然后对该trainData进行转换transform,从而实现数据的标准化、归一化等等。
根据对之前部分trainData进行fit的整体指标,对剩余的数据(testData)使用同样的均值、方差、最大最小值等指标进行转换transform(testData),从而保证train、test处理方式相同。所以,一般都是这么用:
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
sc.fit_tranform(X_train)
sc.tranform(X_test)
# 也可以这么写:
sc.fit(X_train)
sc.tranform(X_train)
sc.tranform(X_test)
如果fit_transfrom(trainData)后,使用fit_transform(testData)而不transform(testData),虽然也能归一化,但是两个结果不是使用的相同参数,sc.fit(X_train)计算出来一个基于训练集的参数,sc.fit(X_test)计算出来一个基于测试集的参数,有的时候我们并不希望这样做。
下面我举一个关于使用转换器进行数据预处理的例子,我们还是使用之前的数据集,但是我们要先对它做一些破坏
X_broken = np.array(X)
# 每隔一行,就把第二个特征的值除以10
X_broken[:,::2] /= 10
scores = cross_val_score(estimator, X, y, scoring='accuracy' ,cv=10)
print("The average accuracy for is {0:.1f}%".format(np.mean(scores) * 100))
broken_scores = cross_val_score(estimator, X_broken, y, scoring='accuracy' ,cv=10)
print("The 'broken' average accuracy for is {0:.1f}%".format(np.mean(broken_scores) * 100))
# The average accuracy for is 88.7%
# The 'broken' average accuracy for is 84.1%
从之前的 88.7% 变成了 84.1%,这是由各个特征之间的数量差距太大造成的,我们把特征值转变到0到1之间就能解决这个问题。我们接下来用 MinMaxScaler 类进行基于特征的规范化。这个类可以把每个特征的值域规范化为0到1之间。最小值用0代替,最大值用1代替,其余值介于两者之间。
我们在预处理器 MinMaxScaler 上调用转换函数。有些转换 器 要 求 像 训 练 分 类 器 那 样 先 进 行 训 练 , 但 是 MinMaxScaler 不 需 要 , 直 接 调 用 fit_transform() 函数,即可完成训练和转换。下面使用的是链式调用方法
from sklearn.preprocessing import MinMaxScaler
X_transformed = MinMaxScaler().fit_transform(X_broken)
scores = cross_val_score(estimator, X, y, scoring='accuracy' ,cv=10)
print("The average accuracy for is {0:.1f}%".format(np.mean(scores) * 100))
broken_scores = cross_val_score(estimator, X_transformed, y, scoring='accuracy' ,cv=10)
print("The 'broken' average accuracy for is {0:.1f}%".format(np.mean(broken_scores) * 100))
The average accuracy for is 88.7%
The 'broken' average accuracy for is 89.0%
3. 流水线
随着实验的增加,操作的复杂程度也在提高。我们可能需要切分数据集,对特征进行二值化处理,以特征或数据集中的个体为基础规范化数据,除此之外还可能需要其他各种操作。要跟踪记录所有这些操作可不容易,如果中间出点问题,先前实验的结果将很难再现。常见问题有落下步骤,数据转换错误,或进行了不必要的转换操作等。另一个问题就是代码的先后顺序。上一节,我们创建了 X_transformed 数据集,然后创建了一个新的估计器用于交叉检验。如果有多个步骤,就需要跟踪代码中对数据集进行的每一步操作。
为了解决这种混乱的局面,我们可以使用流水线组织我们的代码。还是使用上面的例子,我们分为了两步:
- (1) 用 MinMaxScaler 将特征取值范围规范到0~1。
- (2) 指定 KNeighborsClassifier 分类器。
from sklearn.pipeline import Pipeline
# 创建流水线
scaling_pipeline = Pipeline([('scale', MinMaxScaler()),
('predict', KNeighborsClassifier(n_neighbors=2))])
# 运行流水线
scores = cross_val_score(scaling_pipeline, X_broken, y,scoring='accuracy', cv=10)
print("The pipeline scored an average accuracy for is {0:.1f}%".format(np.mean(scores) * 100))
The pipeline scored an average accuracy for is 89.0%
准确度跟上面分开写的一样,都是 89.0% 。