前面几篇文章我们通过几个小案例熟悉了在Python中使用sklearn模块来用做机器学习项目的一般步骤,并通过机器学习中最简单的KNN算法进行演示。这里我们总结一下,对于一个机器学习一般有如下套路:
- 导包并查看数据形状以及基本信息,例如样本个数,字段,标签(目标值)等信息
- 使用mold section下的train_test_split方法拆分数据集。注意train_test_split方法自动随机选择数据,因而会打乱顺序,这个正是我们需要的
- 创建+声明算法模型
- 使用第三步中拆分的X_train,y_train训练模型
- 使用训练好的模型进行预测并评估模型
- 调参,提高模型精度
在使用过程中,我们也能感受到sklearn将数据拆分预处理以及模型训练等方法进行了高度的封装,我们只需要调用即可。模型怎么实现的理论上我们不需要关注。这样我们就可以把主要的精力放在思考如何提高模型的预测的精度这个问题上。
sklearn中也提供一些标准化的操作来提高模型精度,例如Gridsearch寻找最优参数,数据归一化等。这个就是我们下面的案例将要用到的技术。但是想要更好的训练模型,这就需要我们花一些精力来理解数据了,也就是常说的要深入理解业务。这样才能明白数据指标间的相关性,才能更好的取舍。这些需要在工作慢慢的积累总结和思考。另外,现在我们使用的这些skleran中自带的数据集都是处理过的,但是在真实世界的项目中数据往往是混乱且不完整的,需要我们花很大部部份精力来对数据进行清洗。
好了,我们开始本次的学习。这一次我们用一个癌症预测的数据集来做一个完整的项目。
1. 导入数据并查看
import numpy as np
import pandas as pd
df = pd.read_csv('./cancer.csv', sep = '\t')
print(df.shape)
df.head()
因为原始数据在一个csv文件中,我们导入pandas进行读取和处理,只后查看前 5项如下:
image.png
可以看到数据集中共有569个样本,每个样本包含id、标签值Diagnosis(其中M为恶性,Malignancy;B为良性,Benign)以及检测样本的其他信息。本次机器学习的任务就是归纳各项检测信息与标签值M/B的对应关系建立模型来预测。
2. 数据清洗拆分以及初步的分析查看
因为该数据已经是处理好的数据集,不在需要清洗,直接拆分成和
df.drop('ID', axis=1, inplace = True) #丢地id字段,跟M/B无关
X = df.iloc[:, 1:]
y = df['Diagnosis'].str.lower() #转化为小写,方便后面出图
y.value_counts() #统计m/b的总数
将和患病与否没有关系的id字段丢掉,在用df.iloc切片。在这里顺便多说一句,我们之前学的用pandas进行数据分析在这里也完全用的上。熟练使用pandas是用python进行数据分析和数据挖掘必须要过的坎,也是机器学习的基础。
在训练模型之前,我们可以习惯性的查看一下各项数据的相关性。我们知道数据集中有32列,除了id和Diagnosis之外还有30个字段。我们调用pandas里面的plotting.scatter_matrix方法可以一键查看每一项的数据分布以及每两项之间的相关性。
因为有30个字段,画在一张图上比较拥挤。我们先画出前6项感受一下:
df_1_plot = pd.plotting.scatter_matrix(X.iloc[:,:6], c=y, figsize=(20, 20),
marker='o', hist_kwds={"bins": 20}, s=40, alpha=.8)
代码中第一个参数X.iloc[:,:6]表示我们要分析数据框X中的前6项,c=y表示用的y的标签值来区分颜色,本案例中y取值为m或者b,所有有两种颜色。效果如下:
2项值.png
其中每一行为数据框中一列值的情况,包含该列数值的概率分布图,以及该列与其他5列的相关性散点图。通过这张图可以很直观的感受到用那些指标可以更容易的区分m和b
3. 创建模型并训练
之前我们说过使用sklearn创建一个模型+训练+预测一般都是3-4行代码搞定,因为sklearn做了大量的封装,方便我们使用。下面我们就在来一遍声明模型+训练+预测并输出算法在test数据集上的准确率。
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y , test_size = 0.2)
knn = KNeighborsClassifier()
knn.fit(X_train, y_train)
y_ = knn.predict(X_test)
score1_ = (y_ == y_test).mean()
print('使用算法的默认参数获得的正确率为: {:.3f}'.format(score1_))
>>>使用算法的默认参数获得的正确率为: 0.904
可以看到即使使用KNN算法的默认参数,也依然获得了90%的正确率。原因其实我们在上面的散点图矩阵里面弄也能窥见一斑:m和b的边界还算是比较明显的,也就是有比较明显的决策边界。这样除了边界上的点可能会出错外,其他点的“周围邻居”大部分都是同一类型,好区分。即使在边界上,因为是二分类随便猜也有50%的概率对。下面我们就着重讲述如何提高模型的精度。
4. 使用GridSearchCV方法寻找最佳参数
之前我们提到过KNN算法中有值,权重weight,距离计算方式等参数会影响模型的精度。在一开始,我们通过使用for循环,遍历每个参数来查看每个参数下模型的准确率。理论上如果有三个参数,我们可以嵌套三个for循环逐个遍历每种组合来获得最佳参数,但是确实太low!
我们知道sklearn最大的特点就是封装!sklearn下的model_selection模块里面已经封装好了GridSearchCV这个方法来帮助我们完成最佳参数寻找这个过程。GridSearchCV全称就是Grid Search Cross validation,从名称我们就可以知道该方法就是用不同参数形成的网格使用交叉验证方法获取正确率最大的参数组合。下面我们就来试一试:
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV # GridSearchCV: 网格搜索 + cross valdation, 寻找最佳参数组合
knn = KNeighborsClassifier()
parameters_group = {'n_neighbors': [i for i in range(1,31)],
'weights' : ['distance', 'uniform'],
'p' : [1,2]}
grid_knn = GridSearchCV(knn, parameters_group, scoring='accuracy', cv=6)
grid_knn.fit(X_train, y_train)
上面的代码中,我们先创建了一个待遍历的参数组合parameters_group,包含k值从1到30,权重,以及p值(其中1为为曼哈顿距离L1,2为欧式距离L2),然后创建一个模型grid_knn并调用GridSearchCV方法把knn和待遍历的参数组合放进去在训练,结果如下:
image.png训练结束只后,我们可以调用模型自带的best_estimator_/best_params_/best_score_查看结果:
print(grid_knn.best_estimator_)
print(grid_knn.best_params_)
print(grid_knn.best_score_)
>>>KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=None, n_neighbors=10, p=1,
weights='distance')
>>>{'n_neighbors': 10, 'p': 1, 'weights': 'distance'}
>>>0.9472527472527472
可以看到找到一组最佳参数:'n_neighbors': 10, 'p': 1, 'weights': 'distance',并是模型在训练集上的精度提高到了94.7%。那么在测试集上的表现呢?
grid_y_ = grid_knn.predict(X_test)
score2_ = (grid_y_ == y_test).mean()
print('使用GridSearchCV获得的参数的模型正确率为: {:.3f}'.format(score2_))
>>> 使用GridSearchCV获得的参数的模型正确率为: 0.921
相比于默认参数的正确率score1_的90%提高了2个百分点。参数已经最佳了,还有其他方法吗?这就是下面要说的数据的归一化。
5. 数据归一化操作再一次提高模型精度
我们先说一下为什么要数据归一化。
从上面的原始数据我们可以看到有些特征值很小,例如smothness_mean,一般在0.1左右。但是有些特征值很大,如area_mean, 这样在计算距离的时候,不同的特征值就给了算法不同的权重。这样在我们不清楚该特征值对结果的贡献大小的时候是不利的。因而需要把不同尺度下的特征值缩放到同一个观察尺寸下来衡量。下面就用最大值最小值来进行归一化。方法就是用每个值减去最小值在除以这一列值的区间(最大值—最小值),结果如下:
从结果中我们可以看到归一化之后所有数据取值都在0和1之间。
我们在调用 GridSearchCV方法,看看归一化之后的模型表现怎么样:
X_train, X_test, y_train, y_test = train_test_split(X_normal, y , test_size = 0.2)
knn = KNeighborsClassifier()
parameters_group = {'n_neighbors': [i for i in range(1,31)],
'weights' : ['distance', 'uniform'],
'p' : [1,2]}
grid_knn_normal = GridSearchCV(knn, parameters_group, scoring='accuracy', cv=6)
grid_knn_normal.fit(X_train, y_train)
训练之后同样我们看一下最佳参数下的表现:
print(grid_knn_normal.best_estimator_)
print(grid_knn_normal.best_params_)
print(grid_knn_normal.best_score_)
>>> KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=None, n_neighbors=6, p=1,
weights='distance')
>>> {'n_neighbors': 6, 'p': 1, 'weights': 'distance'}
>>> 0.9736263736263736
不错,正确率有了进一步的提升,达到了97%。再看看在测试集上的表现。
normal_y_ = grid_knn_normal.predict(X_test)
score3_ = (normal_y_ == y_test).mean()
print('使用GridSearchCV获得的参数的模型正确率为: {:.4f}'.format(score3_))
>>> 使用GridSearchCV获得的参数的模型正确率为: 0.9737
可以看到在第二步通过GridSearchCV方法获得的score2_ = 92%的基础上,进行数据归一化之后,显著的提升了正确率到了97%以上。这个成绩在某些情况下已经可以帮忙医生做初步的诊断了。
好了,以上就是本次的全部内容。文中所涉及到的原始数据和代码已经打包放在公众号内。关注下方二维码,在公众号回复“KNN”即可获得。
谢谢!
网友评论