23. 使用交叉验证快速选择模型#
23.1. 介绍#
前面的课程中,我们一起学习了多个不同的分类预测方法。当你在实际工程实践时,如何快速地从这些机器学习方法中找到最适合的那一个呢?这就涉及到模型选择的知识。本次挑战中,我们将了解到 K 折交叉验证方法,并使用该方法完成模型选择。
23.2. 知识点#
K 折交叉验证
K 折子集均分
鲍鱼年龄分类
我们学习了监督学习中的多个不同的分类方法,相信你已经对这些方法的原理和实现过程有了充分掌握。当你学完这些方法后,可能会产生一个疑问,那就是如何快速从不同的机器学习方法中挑选出最适合当前应用场景的方法呢?
要回答这个问题,我们先回顾一下 K-近邻方法 的实验内容。在 K-近邻课程的最后,实验介绍了 K 值的选择。当时,我们通过尝试从 2 到 10 的不同 K 值找到最适合的那一个。这个办法虽然看起来有点「笨」,但用起来还不错。
模型选择过程有点类似。当我们要从多个模型中快速选择一个较为合适的模型时,也可以把数据依次放到每个模型中去测试,找到泛化能力较强的那一个。虽然这是一个「笨」办法,但在实验流程上也有一些取巧的步骤。其中之一,就是今天要介绍的 K 折交叉验证。
关于交叉验证,实际上前面已经有所接触,这里就不再重复介绍概念了。
23.3. 数据集预处理#
下面正式开始本次挑战。本次挑战所使用到的是
Abalone(鲍鱼)年龄数据集
challenge-6-abalone.csv
。首先,实验需要通过下面的链接下载该数据集。
# 数据集下载链接 https://cdn.aibydoing.com/aibydoing/files/challenge-6-abalone.csv
挑战:加载并预览数据集前 5 行。
import pandas as pd
## 代码开始 ### (≈ 2 行代码)
df = None
## 代码结束 ###
参考答案 Exercise 23.1
wget -nc https://cdn.aibydoing.com/aibydoing/files/challenge-6-abalone.csv
import pandas as pd
### 代码开始 ### (≈ 2 行代码)
df = pd.read_csv("challenge-6-abalone.csv")
df.head()
### 代码结束 ###
期望输出
M | 0.455 | 0.365 | 0.095 | 0.514 | 0.2245 | 0.101 | 0.15 | 15 | |
---|---|---|---|---|---|---|---|---|---|
0 | M | 0.35 | 0.265 | 0.09 | 0.2255 | 0.0995 | 0.0485 | 0.07 | 7 |
1 | F | 0.53 | 0.42 | 0.135 | 0.677 | 0.2565 | 0.1415 | 0.21 | 9 |
2 | M | 0.44 | 0.365 | 0.125 | 0.516 | 0.2155 | 0.114 | 0.155 | 10 |
3 | I | 0.33 | 0.255 | 0.08 | 0.205 | 0.0895 | 0.0395 | 0.055 | 7 |
4 | I | 0.425 | 0.3 | 0.095 | 0.3515 | 0.141 | 0.0775 | 0.12 | 8 |
此时,你会发现该数据集的列名有些问题,好像是数据值。其实,这是由于数据集不规范造成的,该数据集的列名位于最后一行,我们可以预览后 5 行查看。
挑战:预览数据集最后 5 行。
## 代码开始 ### (≈ 1 行代码)
## 代码结束 ###
参考答案 Exercise 23.2
### 代码开始 ### (≈ 1 行代码)
df.tail()
### 代码结束 ###
期望输出
M | 0.455 | 0.365 | 0.095 | 0.514 | 0.2245 | 0.101 | 0.15 | 15 | |
---|---|---|---|---|---|---|---|---|---|
4172 | M | 0.59 | 0.44 | 0.135 | 0.966 | 0.439 | 0.2145 | 0.2605 | 10 |
4173 | M | 0.6 | 0.475 | 0.205 | 1.176 | 0.5255 | 0.2875 | 0.308 | 9 |
4174 | F | 0.625 | 0.485 | 0.15 | 1.0945 | 0.531 | 0.261 | 0.296 | 10 |
4175 | M | 0.71 | 0.555 | 0.195 | 1.9485 | 0.9455 | 0.3765 | 0.495 | 12 |
4176 | Sex | Length | Diameter | Height | Whole weight | Shucked weight | Viscera weight | Shell weight | Rings |
如上表所示,索引为 4176 的行竟然是列名。所以,我们需要将列名放到正确的位置,也就是要重新生成数据集组成的 DataFrame。
挑战:给数据集设定正确的列名,并保留被错误用于列名的数据行,同时删除最后一行被错误放置的列名。
## 代码开始 ### (≈ 3~7 行代码)
df = None
## 代码结束 ###
参考答案 Exercise 23.3
### 代码开始 ### (≈ 3~7 行代码)
columns_name = df.iloc[df.index[-1]].values
new_line = df.columns.values
df = df.drop(df.index[-1])
df.columns = columns_name
df = pd.DataFrame([new_line], columns=columns_name).append(df, ignore_index=True)
### 代码结束 ###
### 方法二
df = pd.read_csv("challenge-6-abalone.csv", header=-1)
df.columns = df.iloc[-1].values
df = df.drop(df.index[-1])
运行测试
pd.concat([df.head(2), df.tail(2)])
期望输出
Sex | Length | Diameter | Height | Whole weight | Shucked weight | Viscera weight | Shell weight | Rings | |
---|---|---|---|---|---|---|---|---|---|
0 | M | 0.455 | 0.365 | 0.095 | 0.514 | 0.2245 | 0.101 | 0.15 | 15 |
1 | M | 0.35 | 0.265 | 0.09 | 0.2255 | 0.0995 | 0.0485 | 0.07 | 7 |
4175 | F | 0.625 | 0.485 | 0.15 | 1.0945 | 0.531 | 0.261 | 0.296 | 10 |
4176 | M | 0.71 | 0.555 | 0.195 | 1.9485 | 0.9455 | 0.3765 | 0.495 | 12 |
到目前为止,我们已经设定好了正确的数据集格式。该数据集前 8 列统计了鲍鱼的一些生理特征,例如性别、长度、重量等。最后 1 列为目标列,统计了鲍鱼的环数(Rings),环数从 1-30 变化,值越大代表鲍鱼的年龄越大。
你可能会意识到,如果我们要预测鲍鱼的年龄,那么这应该是一个回归问题。所以,本次实验将 1-10 环定义为 small(小鲍鱼), 11-20 环定为 middle(中型鲍鱼), 21-30 定为 large(老鲍鱼)。
于此同时,针对 Sex 列出现的 3 个性别种类 M, F, I,我们分别使用数值 0,1,2 进行替换。
挑战:将数据集目标值(Rings)按照区间替换为 3 种类别,并按照上文要求替换 Sex 列。
提示:按区间替换是可使用
pd.cut()
,具体参考官方文档。
## 代码开始 ### (≈ 3~5 行代码)
## 代码结束 ###
参考答案 Exercise 23.4
### 代码开始 ### (≈ 3~5 行代码)
df['Rings'] = pd.to_numeric(df['Rings'])
df['Rings'] = pd.cut(df.Rings, bins=[0, 10, 20, 30], labels=['small','middle','large'])
df['Sex'] = df.Sex.replace({'M':0, 'F':1, 'I':2})
### 代码结束 ###
运行测试
print(df.iloc[[3, 6, 12, 83]]["Rings"].values)
df.head()
期望输出
[small, middle, middle, large]
Sex | Length | Diameter | Height | Whole weight | Shucked weight | Viscera weight | Shell weight | Rings | |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0.455 | 0.365 | 0.095 | 0.514 | 0.2245 | 0.101 | 0.15 | middle |
1 | 0 | 0.35 | 0.265 | 0.09 | 0.2255 | 0.0995 | 0.0485 | 0.07 | small |
2 | 1 | 0.53 | 0.42 | 0.135 | 0.677 | 0.2565 | 0.1415 | 0.21 | small |
3 | 0 | 0.44 | 0.365 | 0.125 | 0.516 | 0.2155 | 0.114 | 0.155 | small |
4 | 2 | 0.33 | 0.255 | 0.08 | 0.205 | 0.0895 | 0.0395 | 0.055 | small |
23.4. K 折子集均分#
接下来,我们将上面预处理之后的数据集平均划分为 K 个子集。这里,使用 scikit-learn 提供的 K 折划分方法如下:
sklearn.model_selection.KFold(n_splits=3, shuffle=False, random_state=None)
其中参数:
- n_splits : 默认 3;最小为 2,表示 K 折子集划分的 K 值。 - shuffle : 默认 False; 当为 True 时,会对数据产生随机搅动。 - random_state : 默认 None;随机数种子。
挑战:使用
KFold()
将数据集划分为 10 折,指定参数:shuffle=False
,random_state=50
。
## 代码开始 ### (≈ 2 行代码)
kf = None
## 代码结束 ###
参考答案 Exercise 23.5
### 代码开始 ### (≈ 2 行代码)
from sklearn.model_selection import KFold
kf = KFold(n_splits=10, random_state=50)
### 代码结束 ###
运行测试
kf
期望输出
KFold(n_splits=10, random_state=50, shuffle=False)
如果想要得到划分的结果,就可以通过
for
循环完成:
# 直接运行查看结果
for train_index, test_index in kf.split(df):
print("TRAIN:", len(train_index), "TEST:", len(test_index))
这里使用
len()
打印了
train_index
的长度,你也可以自行尝试打印
train_index
中的内容。
23.5. K 折交叉验证#
上面,我们使用
KFold()
可以很方便地完成 K
折划分,你可以将划分的结果用于训练模型并验证。为了方便,scikit-learn
还提供了直接交叉验证的方法,可以大大缩减代码量:
sklearn.model_selection.cross_val_score(estimator, X, y=None, groups=None, scoring=None, cv=None, n_jobs=1, verbose=0, fit_params=None, pre_dispatch=‘2*n_jobs’)
主要参数:
- estimator : 模型。 - X : 特征组成的数组。 - y : 目标值组成的数组。 - cv : K 折数量。
挑战:使用 K-近邻方法构建机器学习分类模型,并进行 10 折交叉验证。
## 代码开始 ### (≈ 4~7 行代码)
cross_val_score()
## 代码结束 ###
参考答案 Exercise 23.6
### 代码开始 ### (≈ 4~7 行代码)
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
features = df.iloc[:, 0:8]
target = df['Rings']
model = KNeighborsClassifier()
cross_val_score(model, X=features, y=target, cv=10)
### 代码结束 ###
期望输出
array([0.75417661, 0.72009569, 0.77990431, 0.72966507, 0.73205742, 0.74401914, 0.76498801, 0.74580336, 0.75779376, 0.73860911])
由于 10 折交叉验证会进行 10 次实验,所以最终输出了 10 个分类准确率的评价结果。
如果我们要使用鲍鱼数据集训练分类预测模型,为了快速筛选出表现较好的模型,就可以使用 K 折交叉验证。所以,接下来我们将得到本章课程中所学习模型在默认参数下的 10 折交叉验证结果。
挑战:使用 10 折交叉验证方法测试鲍鱼数据集在逻辑回归、K 近邻、支持向量机、人工神经网络、决策树、随机森林、Adaboost 默认参数下的表现结果,并取 10 折交叉验证结果取平均值。
提示:人工神经网络模型调用方法为:
sklearn.neural_network.MLPClassifier
。
"""加载分类器模块
"""
## 代码开始 ### (≈ 7 行代码)
## 代码结束 ###
"""各分类模型 10 折交叉验证函数
"""
def classifiers():
"""
参数:无
返回:
scores -- 各分类模型 10 折交叉验证平均结果(列表)
"""
### 代码开始 ### (> 10 行代码)
scores = []
### 代码结束 ###
return scores
参考答案 Exercise 23.7
"""加载分类器模块
"""
### 代码开始 ### (≈ 7 行代码)
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.neural_network import MLPClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import AdaBoostClassifier
### 代码结束 ###
"""各分类模型 10 折交叉验证函数
"""
def classifiers():
"""
参数:无
返回:
scores -- 各分类模型 10 折交叉验证平均结果(列表)
"""
### 代码开始 ### (> 10 行代码)
scores = []
models = [
LogisticRegression(),
KNeighborsClassifier(),
SVC(),
MLPClassifier(),
DecisionTreeClassifier(),
RandomForestClassifier(),
AdaBoostClassifier()]
for model in models:
score = cross_val_score(model, X=features, y=target, cv=10)
mean_score = np.mean(score)
scores.append(mean_score)
### 代码结束 ###
return scores
运行测试(执行时间较长)
classifiers()
参考输出:
[0.7543513318292162,0.7467112482377095, 0.7443091754229052,0.7648902636235668, 0.6856593267369182,0.7455041701414079, 0.7318638234403398]
你会发现,当我们使用默认参数时,7 种分类预测模型的表现都还不错,其中支持向量机、人工神经网络、随机森林等表现突出。此时,我们就可以先初选出这几种方法,然后通过调参得到更理想的结果。
○ 欢迎分享本文链接到你的社交账号、博客、论坛等。更多的外链会增加搜索引擎对本站收录的权重,从而让更多人看到这些内容。