前言

本文的定位是介绍使用sklearn做机器学习建模的一些最常用的通用性操作,比如建模流程,还有常用的辅助模块,如划分训练集测试集,交叉验证,网格搜索等。可以作为学习sklearn前的入门材料,跑起来第一个模型。需要注意的是,本文并不使用严格的计算机术语,一切代码示例以实用为主,为方便大家理解会有一些口语化的表达。

下面是本文会使用到的python库及模块,运行环境为jupyter notebook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

from sklearn.tree import DecisionTreeClassifier # 决策树分类

from sklearn.datasets import load_iris # 鸢尾花数据集

from sklearn.model_selection import train_test_split # 切分数据
from sklearn.model_selection import cross_val_score # 交叉验证
from sklearn.model_selection import GridSearchCV # 网格搜索

import pickle # 保存及读取模型

import warnings
warnings.filterwarnings("ignore")

sklearn建模流程

一个简单的实例

数据准备

sklearn建模的流程非常的简单,核心的流程就只有两步,创建模型与训练。

下面使用sklearn自带的数据集,选择决策树模型作为示例,演示一个最简单化的建模流程。

首先我们读取数据

1
2
3
4
iris = load_iris()  # 读取数据
# 划分特征与标签
data = pd.DataFrame(iris.data) # 特征
target = pd.DataFrame(iris.target) # 标签

因为这里是使用sklearn库中datasets模块自带的数据集,所以数据读取会相对比较简单,这里我按照我的习惯将数据转换成了pandasDataFrame对象。

接下来就是切分数据集,将其切分为训练集与测试集。什么是训练集与测试集呢,简单来说,训练集是给模型学习的,测试集是用于测试模型对于未知数据的效果的,可以近似反映其在新数据中的表现。做一个比喻就是,训练集是练习册,而测试集是模拟考,而模拟考的成绩可以用于近似高考的成绩。

1
2
# 划分训练集与测试集
Xtrain, Xtest, Ytrain, Ytest = train_test_split(data, target, test_size=0.2)

其中,前两个参数输入的是数据的特征与标签,参数test_size代表划分出的测试集占数据集的比例,比如这里输入0.2就表示将数据集二八分,八是训练集,二是测试集。而Xtrain, Xtest, Ytrain, Ytest分别代表训练集特征,测试集特征,训练集标签,测试集标签。

建模与训练

数据准备好了,现在可以进行建模了(这里模型参数就默认了)

1
2
clf = DecisionTreeClassifier()  # 创建模型
clf.fit(Xtrain, Ytrain) # 训练

其中,clf你可以理解为一个变量,只不过该变量储存的是一个模型。然后就是调用fit()方法,填入数据,对模型进行训练,这里的两个参数分别是数据的特征与标签,也就是数学上的自变量和因变量,用数学语言表述就是XXYY,这里看不懂没关系,结合数据来看自然就懂了。在经过训练之前模型都只是一个空壳,只有经过数据的训练,这个模型才能用起来。这基本上是使用sklearn进行建模亘古不变的前两步。

接下来就可以根据自己的需要调用已经训练好的模型的各种方法或者参数了,比如在测试集上打分

1
clf.score(Xtest, Ytest)  # 测试集打分

这会输出该模型在测试集上的准确率

1
0.9333333333333333

再比如使用模型做预测

1
clf.predict(Xtest)  # 使用模型做预测

输出结果

1
2
array([1, 1, 1, 2, 0, 0, 0, 1, 1, 2, 2, 0, 0, 2, 2, 1, 0, 1, 2, 1, 1, 1,
1, 2, 2, 1, 2, 0, 0, 2])

导出模型

假设经过上述过程,我们将模型调整到了一个我们满意的结果,那么现在我们就要将训练好的模型保存下来。

1
2
3
# 使用全部数据训练模型
clf = DecisionTreeClassifier() # 创建模型
clf.fit(data, target) # 训练

可以看到训练好的模型被储存在变量clf中,现在我们使用pickle库保存模型

1
2
3
4
# 保存模型
clf_file = 'clf.pkl' # 保存模型的文件名(格式为pkl)
with open(clf_file, 'wb') as f:
pickle.dump(clf, f) # 将训练好的模型clf存储在变量f中,并保存到本地

上述代码将模型保存为一个pkl文件,之后我们就可以调用我们训练好的模型了。比如

1
2
3
4
5
# 调用模型
with open('clf.pkl', 'rb') as f:
clf_load = pickle.load(f) # 读取模型文件

clf_load.predict(data) # 使用模型做预测

输出为

1
2
3
4
5
6
7
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])

模型训练

经过上述实例,大家应该知道了大致的一个建模流程,接下来我们补充一下上述讲到的函数的细节。首先是训练部分,也就是

1
2
clf = DecisionTreeClassifier()  # 创建模型
clf.fit(Xtrain, Ytrain) # 训练

这里需要额外补充的不多,首先演示代码的模型参数是默认的,一般我们都会填入具体的参数的,画风一般是这样

1
2
3
4
5
clf = DecisionTreeClassifier(criterion='gini'
,max_depth=10
,min_samples_leaf=10
)
clf.fit(Xtrain, Ytrain)

然后是fit()方法,主要是填入数据的格式。填入的数据是支持numpyarray类型,也支持pandasDataFrame类型的。

数据切分

划分训练集与测试集一般使用的是train_test_split函数,这个函数的参数不多,这里就顺便讲了

1
Xtrain, Xtest, Ytrain, Ytest = train_test_split(data, target, test_size=0.2, random_state=1008, shuffle=True)

参数random_state是随机数种子,用于保证每次跑出来的结果一样。参数shuffle是指是否在划分前将数据进行打乱,默认为True,也就是进行打乱

模型评估

接下来是模型的score()方法,其实模型评估这块细讲还是有不少指标的,这里不细讲,就只说score()方法的默认指标。

首先,我们要知道机器学习问题可以被划分为分类问题,回归问题与聚类问题,聚类问题暂且不说。如果是分类问题,那选取评估指标,第一想法是什么,那自然是正确率,所以对于分类模型,调用score()方法输出的就是模型在所给数据集上的正确率。然后是回归问题,回归问题一般都会借用我们高中就学过的一元线性回归中使用的R2R^2,也是一个在0到1之间,并且越大越好的指标,非常直观。

模型导出与调用

sklearn中可以使用pickle库保存模型与读取模型,这不是唯一的方法,但是大多数情况下都可以使用该方法。用法直接将上述代码改下文件名和模型变量就行。

交叉验证

交叉验证讲下来有很多方法,这里只讲最常用的K折交叉验证,相比于一般的划分训练集与测试集,也就是上面用过的方法,K折交叉验证将整个数据集平均划分为K份,然后每次取一份作为测试集,剩下的K-1份作为训练集,也就是总共会进行K次测试,这可以降低模型评分的方差,示例图如下

image-20240308105244960

sklearn中使用sklearn.model_selection中的cross_val_score函数实现,这个函数的参数很多,但是我们这里只讲几个最常用的

1
cross_val_score(estimator, X, y, cv, scoring=None)

各参数意义如下

estimator:机器学习模型,未经过fit

X, y:特征与标签

cv:交叉验证的折数,也就是K,默认为5(不同版本默认不一样,建议用的时候都自己填上)

scoring:使用的评价指标,默认为score()方法使用的指标

现在我们来用决策树跑一个10折交叉验证

1
2
3
# 交叉验证
clf = DecisionTreeClassifier(random_state=1008)
cross_val_score(clf, data, target, cv=10)

这会输出一个数组,包含10次测试的分数,一般我们都会直接对其取平均,也就是

1
cross_val_score(clf, data, target, cv=10).mean()

注:交叉验证一直有一个争议,就是交叉验证之前需不需要划分训练集与测试集,也就是cross_val_score到底是要填入data, target还是Xtrain, Ytrain。在我看来,从实用的角度来说,其实两者并无区别,我的习惯一般是不分。

超参数曲线

在机器学习建模中,离不开的步骤就是调参,而在调参的过程中,最常用的辅助工具就是超参数曲线,其原理就是通过遍历一个参数的多个结果,来观察参数变化对模型结果的影响,以此为依据来进行参数选择。

注:这里的超参数曲线与判断过拟合欠拟合用的学习曲线不同,注意区分

超参数曲线一般通过matplotlab来绘制,以参数max_depth为例

1
2
3
4
5
6
7
8
9
10
11
# 绘制超参数曲线
test = []
for i in range(15):
clf = DecisionTreeClassifier(criterion='gini'
,max_depth=i+1
)
score = cross_val_score(clf, Xtrain, Ytrain, cv=5, scoring="accuracy").mean() # K折交叉验证(使用评价指标为准确度)
test.append(score)

plt.plot(range(1,16),test,color="red",label="max_depth")
plt.legend()

超参数曲线如下

image-20240308134417373

从这个参数曲线中可以看到,参数大于2后评分基本没有太大变化,为了防止过拟合的出现可以选择4作为超参数。(当然这里只是一个小数据demo,实际调参中一般没这么简单,还要靠一点经验的,例如哪个参数先调,哪个后调之类的)

绘制超参数曲线的代码基本是模板化的,可以参考上述代码,根据自己的习惯进行修改形成自己的模板。

网格搜索

上述的超参数曲线只能调整单个超参数的结果,而调参一般都不止需要调一个参数,这时候就可以通过网格搜索来辅助调参。网格搜索是遍历给出的所有参数值的所有组合,并筛选出评分最高的参数组合,这可以通过sklearn.model_selection中的GridSearchCV函数实现,其常用参数如下

1
GridSearchCV(estimator, param_grid, cv, scoring=None)

estimator:机器学习模型,未经过fit

param_grid:参数-取值列表,类型为字典,键名为参数名,键值为参数所有可能取值

cv:交叉验证的折数,也就是K,网格搜索默认使用交叉验证进行评分

scoring:使用的评价指标,默认为score()方法使用的指标

注:GridSearchCV是需要fit的,所以参数里没有数据与标签

而最佳的参数组合与其得分可以通过属性best_params_best_score_得到

现在我们以决策树的常用参数为例,进行网格搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 网格搜索
parameters = {'splitter':('best','random')
,'criterion':("gini","entropy")
,"max_depth":[*range(1,10)]
,'min_samples_leaf':[*range(1,50,5)]
,'min_impurity_decrease':[*np.linspace(0,0.5,20)]
}

clf = DecisionTreeClassifier()
GS = GridSearchCV(clf, parameters, cv=5) # 网格搜索使用交叉验证进行评分
GS.fit(Xtrain, Ytrain)

print("最优组合:", GS.best_params_)
print("最优评分", GS.best_score_)

注1:网格搜索是一种很贵的方法,他可以通过暴力枚举进行调参,但是同时,这也会及其消耗运算资源。因为每增加一个需要调整的参数就是增加一个维度,这会有维度爆炸的问题。因此建议通过超参数曲线等方法尽可能缩小网格搜索的搜索范围,之后再进行网格搜索

注2:对于较大规模的网格搜索,可以用一个简单的方法来估计运行时间,首先,先跑一次交叉验证,记录其耗时,这可以通过python自带的time库或者jupyter notebook的插件来实现,再乘以参数组合数,就可以得到大致运行时间。

总结

本文简单介绍了进行机器学习建模的最简单流程,以及一些最常用的辅助工具,从实用性的角度来说,这可以使读者最快速的跑起来一个机器学习模型,当然,本文也省略了很多细节,具体的机器学习模型原理与代码实现,已经另外的一些机器学习中技术,还有一些案例分享,可以移步本博客的其他文章。