在之前的文章中也介绍了KNN的算法原理,并且完成了两个案例进一步的理解了KNN。这都使用的是自己写的kNN分类器,scikit-learn包在机器学习和数据挖掘中是一个强大的包,其中就包含了许多的算法实现以及实用的功能。而在《Python数据挖掘入门与实践》这本书中,并没有像《机器学习》或者《机器学习实战》中的那样介绍原理或者自己实现某个算法,而是直接使用sklearn(scikit-learn)中的分类器,但是除此之外还介绍了一些实用的功能。下面就基于这本书的一个KNN例子,来简单介绍一下如何使用sklearn这个强大的包。
书中的例子使用的是UCI机器学习数据库中的电离层数据,这些数据是由高频天线收集的。这些天线的目的是侦测在电离层和高层大气中存不存在由自由电子组成的特殊结构。如果一条数据能给出特殊结构存在的证据,这条数据就属于好的那一类(在数据集中用“g”表示),否则就是坏的(用“b”表示)。
下载下来看一下这些数据:
虽然不清楚这些数据代表什么意思,但是不影响我们的后续步骤,只需要知道这是一个351条数据,每条数据有35列,前34列分别代表17座天线采集的数据,每座天线采集两个数据。然后最后一列是代表这个数据是好还是坏,如果是‘g’就是好的,如果是‘b’就是坏的。
我们先将数据读取到矩阵中,设计loadDataSet函数,返回数据集以及对应的标签:
def loadDataSet():
"""
加载数据集
:return:
"""
# 创建一个与数据集相等的矩阵,不包含标签
x = np.zeros((351, 34), dtype=float)
# 用来保存标签
y = np.zeros((351, ), dtype=bool)
# 加载数据集
reader = csv.reader(open('ionosphere.data'))
# 同时获取索引和值
for i, row in enumerate(reader):
# 将数据添加到x中
data = [float(datum) for datum in row[:-1]]
x[i] = data
# 等于g设置为1,否则设置为0
y[i] = row[-1] == 'g'
return x, y
准备好数据集之后我们就可以直接调用sklearn中的knn分类器进行分类计算。但是为了能够看出这个分类器的准确率,我们还需要将数据集划分为训练集和测试集,使用训练数据集进行训练分类器,然后使用测试集观察正确率。
划分训练集和测试集可以使用sklearn中的train_test_split。
from sklearn.model_selection import train_test_split
该函数可以将数据集进行随机划分(根据随机数种子划分,对于同一数据集随机数种子一样,划分的结果一样),同时可以设置测试集占比例等,详细可参考官方文档。
我们使用上述函数将总的数据集划分为训练集和测试集,之后使用训练集训练分类器,最后使用测试集进行测试,计算出准确率。
而sklearn中的kNN分类器使用起来也十分方便,需要将其导入:
from sklearn.neighbors import KNeighborsClassifier
导入之后创建实例,使用fit进行训练,然后使用predict方法就可以预测分类结果。
def split_dataset_test(dataset, labels):
"""
手动对整个数据集进行划分测试
:param dataset 整个数据集
:param labels 数据集对应的标签
:return:
"""
# 划分训练集和测试数据集,不设置test_size的话默认的为0.25
x_train, x_test, y_train, y_test = train_test_split(dataset, labels, random_state=14)
# 创建一个kNN分类器
estimator = KNeighborsClassifier()
# 使用训练集进行训练
estimator.fit(x_train, y_train)
# 使用测试集进行测试算法
y_predicted = estimator.predict(x_test)
# 计算正确率
accuracy = np.mean(y_test == y_predicted) * 100
print('使用划分数据集进行训练的正确率为:{0: .1f}%'.format(accuracy))
运行以下能够看到准确率:
上述中我们把数据集划分为训练集和测试集,用训练集训练算法,在测试集上评估效果。但是这个占的运气成分比较大,如果测试集比较简单,特征值都比较明显,得到的分类结果就很准确。反之,运气不好了,测试集比较古怪得到的结果自然就不准确。
所以我们就需要尽可能的避免这种情况,在日常生活中我们遇到类似的事情,最简单的办法就是多次试验取平均值。所谓的交叉验证也是如此,既然实验一次可能不太准确,那么我们就多实验几次。
每次切分时,都要保证这次得到的训练集和测试集与上次不一样,还要确保每条数据都只能用来测试一次。算法描述如下:
而在sklearn中提供了这种算法,可以将其导入,详细使用参考官方文档:
from sklearn.model_selection import cross_val_score
这个函数默认使用Stratified K Fold方法切分数据集,我们在使用这个函数的时候需要指定scoring参数,将其设置为“accuracy”,在返回结果中就是一个包含多个准确率的结果集,当然这个scoring参数还有其他选项,详细介绍参考官方文档。
def val_score(dataSet, labels):
"""
使用sklearn中的交叉验证计算正确率
:param dataSet:
:param labels:
:return:
"""
estimator = KNeighborsClassifier()
scores = cross_val_score(estimator, dataSet, labels, scoring='accuracy')
average_accuracy = np.mean(scores) * 100
print('使用交叉验证得到的准确率为:{0: .1f}%'.format(average_accuracy))
使用交叉验证得到的平均准确率为:
在上述过程中使用的kNN分类器,默认的k值使用的是5,但是kNN算法会随着k的不同最后的分类准确率也不相同,所以找到一个合适的k值作为分类器的参数,让分类器拥有最高的准确率就比较重要。
简单的方法就是使用不同的分类器进行计算,观察在k值不同的情况下,准确率的变化情况。
def test_n_neighbors(dataSet, labels):
"""
使用不同的k值进行计算准确率
:param dataSet 数据集
:param labels 对应的标签
:return:
"""
# 用来保存平均准确率
avg_scores = []
# 全部的准确率
all_scores = []
# 设定k的值从1到20
parameter_values = list(range(1, 21))
# 对每个k值的准确率进行计算
for n_neighbors in parameter_values:
# 创建KNN分类器
estimator = KNeighborsClassifier(n_neighbors=n_neighbors)
scores = cross_val_score(estimator, dataSet, labels, scoring='accuracy')
avg_scores.append(np.mean(scores))
all_scores.append(scores)
plt.plot(parameter_values, avg_scores, '-o')
plt.show()
通过绘图能够明显的看出随着k的增大,准确率处于一个下降的趋势:
当k为2.5的时候,准确率取到最大值。
在之前的使用k-近邻算法改进约会网站的配对效果提到过,数据归一化的概念。将整体的数据规划到0和1之间,从而消除某些特征数据的取值范围不同而带来的影响。
而在sklearn中已经提供了数据归一化的函数实现:
from sklearn.preprocessing import MinMaxScaler
通过导入该对象,使用其fit_transform函数可以快速对数据完成归一化处理。在这里我们先将数据进行破坏,计算出准确率,然后再将其归一化处理之后再次计算准确率。
def test_autoNorm(dataSet, labels):
"""
将数据进行归一化
:param dataSet: 原始数据
:param labels: 对应的标签
:return:
"""
# 创建一个副本
x_broken = np.array(dataSet)
# 每隔一列将特征除以10
x_broken[:, ::2] /= 10
estimator = KNeighborsClassifier()
# 计算原始数据的准确率
original_scores = cross_val_score(estimator, dataSet, labels, scoring='accuracy')
print('原始数据的准确率为:{0: .1f}%'.format(np.mean(original_scores) * 100))
# 计算破坏数据及之后的准确率
broken_scores = cross_val_score(estimator, x_broken, labels, scoring='accuracy')
print('破坏数据集之后的准确率为:{0: .1f}%'.format(np.mean(broken_scores) * 100))
# 然后将其归一化
x_transformed = MinMaxScaler().fit_transform(x_broken)
# 再次计算准确率
transformed_scores = cross_val_score(estimator, x_transformed, labels, scoring='accuracy')
print('数据归一化后的准确率为:{0: .1f}%'.format(np.mean(transformed_scores) * 100))
通过运行结果可以看出,破坏数据集之后的准确率明显下降,但是归一化处理之后的准确率又提升了回来。
顾名思义,流水线就是以流水线的那种工作模式来完成工作。我们可以将一连串的操作输入到流水线中,但是其中的最后一步必须是估计器,前几步是转换器(做一些归一化操作等)。然后流水线就会按照步骤将其不断的进行转换处理,最后通过估计器将其分类。
在sklearn中提供了流水线的工作方法,将其导入:
from sklearn.pipeline import Pipeline
导入之后我们需要给Pipeline设置步骤,其中每一步都是用元组(’名称’,步骤)的形式进行输入的。 在这里我们延用上一小节中的内容,先将数据进行归一化,然后再将其分类的步骤操作。所以我们可以通过如下代码创建一个流水线:
scaling_pipeline = Pipeline([('scale', MinMaxScaler()), ('predict', KNeighborsClassifier())])
注意其中的’名称’部分是可以修改的,在这里使用的是’scale’和’predict’,并且每个名称对应的步骤需要给出。然后在交叉验证中用流水线替换原来的估计其即可:
def test_pipeline(dataSet, labels):
"""
使用sklearn中的流水线步骤进行计算
:param dataSet:
:param labels:
:return:
"""
# 创建一个副本
x_broken = np.array(dataSet)
# 每隔一列将特征除以10
x_broken[:, ::2] /= 10
# 创建流水线
scaling_pipeline = Pipeline([('scale', MinMaxScaler()), ('predict', KNeighborsClassifier())])
scores = cross_val_score(scaling_pipeline, x_broken, labels, scoring='accuracy')
print('使用流水线结构的准确率为:{0: .1f}%'.format(np.mean(scores) * 100))
运行结果与之前的一致:
在上述的内容中,大致介绍了sklearn中的几个功能:
上述的介绍中,知识简单的使用了这些功能,当然这些东西还有更强大的作用,其中的许多参数也并没有使用到,可以通过官方文档来补全这方面的知识内容。
在sklearn这个强大的包中提供了需要实用的功能以及算法模型,我们可以通过这些包来加快我们的开发流程。并且其中的一些功能使用起来十分顺手,能够通过短短的几行代码就能够实现极其强大的功能。但是我们不仅需要熟练的使用这些功能,也要明白它们的工作原理,不能单纯的做一个调包侠。
代码地址:源码。