课程目录 小象学院 - 人工智能
关注公众号【Python家庭】领取1024G整套教材、交流群学习、商务合作。整理分享了数套四位数培训机构的教材,现免费分享交流学习,并提供解答、交流群。
你要的白嫖教程,这里可能都有喔~
本关内容概述
欢迎来到本关的学习,本关先介绍sklearn中决策树的使用以及几个重要参数的对模型的影响;然后利用决策树模型对泰坦尼克号乘客的生还情况进行预测。准备好了吗?Let's go!
我们先简单看一下sklearn的官方文档对决策树模型的介绍,链接为 https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html 。
可以看到,DecisionTreeClassifier这个类位于sklearn.tree这个包下,并且有许多可以调整的超参数。接下来我们先通过一个简单的例子介绍一下sklearn中决策树模型的使用,并对几个典型参数的调整进行介绍。
sklearn中的决策树
我们采用sklearn自带的函数来生成一些模拟的数据,为了直观起见,我们将数据用散点图展示出来:
AI_15_0_1_使用make_moons函数生成一些模拟的数据
# 使用make_moons函数生成一些模拟的数据
from sklearn.datasets import make_moons
X, y = make_moons(noise=0.25, random_state=666)
%matplotlib inline
import matplotlib.pyplot as plt
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show();
y
array([1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1,
1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0,
0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0,
0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0])
接下来我们将用决策树算法对这个数据集进行分类,并通过对不同参数的调整来观察最终分类效果。
AI_15_0_2
from sys import path
path.append(r"../data/course_util")
from ai_course_15_1 import *
# 引入决策树分类器
from sklearn.tree import DecisionTreeClassifier
# 使用默认参数实例化一个决策树分类器
dt_clf = DecisionTreeClassifier()
# 训练数据
dt_clf.fit(X, y)
DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion='gini',
max_depth=None, max_features=None, max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort='deprecated',
random_state=None, splitter='best')
上面的程序中这里,我们实例化了一个决策树分类器,并且没有传入任何参数,这表示我们使用默认的参数对数据进行训练。
为了将训练得到的分类边界更加直观地画出来,我们提前准备好了一个plot_decision_boundary的函数,你直接调用就可以了:
AI_15_0_3
from sys import path
path.append(r"../data/course_util")
from ai_course_15_2 import *
%matplotlib inline
# 传入训练好的模型dt_clf,axis参数控制横纵坐标的范围
plot_decision_boundary(dt_clf, axis=[-1.5, 2.5, -1.0, 2.5])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show();
我们来分析一下上述程序输出的分类边界:
- 首先,通过观察发现,我们得到了一个不规则的决策边界,但每条分界线都只能是“横平竖直”的,这也算是决策树模型的一个缺点;
- 其次,可以看出目前的决策边界对于数据的拟合并不是非常的理想。
max_depth参数
先来调整一下 max_depth 参数,该参数控制的是决策树的最大深度。
AI_15_0_4
from sys import path
path.append(r"../data/course_util")
from ai_course_15_3 import *
%matplotlib inline
# 使用max_depth参数实例化一个决策树分类器
dt_clf2 = DecisionTreeClassifier(max_depth=2)
dt_clf2.fit(X, y)
plot_decision_boundary(dt_clf2, axis=[-1.5, 2.5, -1.0, 2.5])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show();
我们来分析一下程序的输出结果:
- 与上面使用默认参数得到的决策边界进行比较可以发现,前面得到的决策边界更加复杂,分类准确率更高,但模型对数据的拟合是过度的,换句话说,模型应用到新数据上的效果不一定好;
- 使用max_depth参数得到的这个决策边界更加简单,但分类错误的样本数量明显增多。
min_samples_split参数
再来看另外一个参数:min_samples_split,该参数的含义是决策树的某个节点至少要包含min_samples_split个样本才可以继续进行划分。
AI_15_0_5
from sys import path
path.append(r"../data/course_util")
from ai_course_15_4 import *
%matplotlib inline
# 使用min_samples_split参数实例化一个决策树分类器
dt_clf3 = DecisionTreeClassifier(min_samples_split=10)
dt_clf3.fit(X, y)
plot_decision_boundary(dt_clf3, axis=[-1.5, 2.5, -1.0, 2.5])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show();
从输出结果来看,这个决策边界介于之前得到的两个分类边界之间,既没有过度地拟合数据,也没有过于简单地对数据进行划分,是一个比较理想的决策边界。
sklearn还给我们提供了许多可以调节的参数,比如:
- min_samples_leaf
- max_leaf_nodes
- min_weight_fraction_leaf
- min_features
在这里我们就不一一进行演示了。在实际应用时,我们可以使用网格搜索等技术选出一组最优的超参数,从而得到一棵最佳的决策树。
案例:预测乘客的生还情况
接下来,我们将利用决策树算法来解决一个实际问题:预测泰坦尼克号乘客的生还情况。
在1912年,泰坦尼克号在第一次航行中,就因不幸撞上了冰山而沉没,造成了一场历史性的悲剧。在随后的事故调查中,船上乘客的信息逐渐被披露出来,我们的任务是通过分析这些数据,试图找出这次事故中乘客的生还逻辑。
获取数据与字段含义
我们先来看一下乘客的数据:数据可以在 https://chinahadoop-xc.oss-cn-beijing.aliyuncs.com/titanic.txt 这个网址上找到,利用 pandas 包中的 read_csv() 函数可以直接读取到这个文件中的数据,程序如下所示:
AI_15_0_6_导入pandas包
# 导入pandas包
import pandas as pd
# 从互联网上直接读取乘客数据
titanic = pd.read_csv('http://biostat.mc.vanderbilt.edu/wiki/pub/Main/DataSets/titanic.txt')
# 观察乘客数据的前5行
titanic.head(5)
row.names | pclass | survived | name | age | embarked | home.dest | room | ticket | boat | sex | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1st | 1 | Allen, Miss Elisabeth Walton | 29.0000 | Southampton | St Louis, MO | B-5 | 24160 L221 | 2 | female |
1 | 2 | 1st | 0 | Allison, Miss Helen Loraine | 2.0000 | Southampton | Montreal, PQ / Chesterville, ON | C26 | NaN | NaN | female |
2 | 3 | 1st | 0 | Allison, Mr Hudson Joshua Creighton | 30.0000 | Southampton | Montreal, PQ / Chesterville, ON | C26 | NaN | (135) | male |
3 | 4 | 1st | 0 | Allison, Mrs Hudson J.C. (Bessie Waldo Daniels) | 25.0000 | Southampton | Montreal, PQ / Chesterville, ON | C26 | NaN | NaN | female |
4 | 5 | 1st | 1 | Allison, Master Hudson Trevor | 0.9167 | Southampton | Montreal, PQ / Chesterville, ON | C22 | NaN | 11 | male |
不难发现,我们得到的乘客数据是一个表格,每一行表示一个乘客的信息,每一列表示一个字段,这种表格在pandas中叫作DataFrame。
与numpy中的 ndarray 相比,pandas中的 DataFrame 在数据之外,每一行增加了行号,每一列增加了列名。这样的设计使用起来是非常方便的,比如,我们可以通过 titanic['age'] 的方式直接取出 age 这一列的数据。
此外,pandas是基于numpy建立的包,因此,pandas中的 DataFrame 可以轻松地转换为numpy中的 ndarray,比如,通过 titanic.values 就得到了一个numpy中的 ndarray。通过前面的学习,我们了解到sklearn这个机器学习库处理的都是ndarray类型的数据,实际上,如果我们输入的是DataFrame类型的数据,sklearn库也会自动将其转换为ndarray类型,因此我们完全不用担心数据类型的问题。
pandas提供了很多实用的内置方法,非常适合完成数据分析的任务。接下来让我们通过泰坦尼克号这个案例,一起感受利用pandas进行数据分析的魅力吧!
我们先介绍一下读取的数据中每个字段的含义:
-
row.names:乘客的编号,可忽略;
-
pclass:船舱的等级,1st表示头等舱,2nd表示二等舱,3rd表示三等舱;
-
survived:是否生还,1表示是,0表示否;
-
name:乘客的姓名;
-
age:乘客的年龄,婴儿的年龄用小数表示;
-
embarked:登船的港口;
-
home.dest:出发地及目的地;
-
room:房间号;
-
ticket:船票号;
-
boat:救生艇编号;
-
sex:乘客的性别
在上面这些字段中,survived(是否生还)是我们想要预测的结果,我们将其作为标签;其余的字段都可以看作是特征。
得到了这些数据之后,是不是可以用这些数据直接训练我们的模型了呢?
先别急。在我们把数据“喂”给模型之前,要先确保这些数据是模型所需要的“好”数据。为了达到这个目的,我们至少还需要对数据进行三步操作:特征选择、数据清洗以及数据预处理。
特征选择
特征选择,顾名思义,就是要去掉不重要的特征,将重要的特征保留下来。
对于这个案例,我们可以根据对这场事故的了解,简单地保留pclass,age和sex这三个特征,而其他特征我们认为与最终的预测结果关联不大,直接舍弃掉。
AI_15_0_7
from sys import path
path.append(r"../data/course_util")
from ai_course_15_5 import *
# 选取pclass,age,sex3个特征作为数据集X;将survived字段作为标签y
X = titanic[['pclass', 'age', 'sex']]
y = titanic['survived']
# 查看数据集X的维度信息
X.shape
# 查看数据集X最后5行的数据
X.tail(5)
# 查看数据集X的整体统计信息
X.info()
我们来分别分析一下程序输出的三个结果:
- X的维度为(1313,3),表示数据集中有1313条乘客的信息,每条信息包含3个特征;
- 观察X的末尾5行数据,我们发现age特征中出现了很多NaN(Not a Number),即age特征中有很多的缺失值;
- 观察X的信息统计,我们重点关注一下age特征,age中只有633个非空的值,这也说明了age特征中存在很多缺失值,需要在下一步中进行处理。
数据清洗
数据清洗,通常包括对异常值的删除、对缺失值的填补以及对错误值的纠正。
在这个案例中,我们需要对age特征中的缺失值进行填补,这里我们采用“平均值”进行填补,分为两步:
-
先使用 mean() 方法计算出age特征的平均值
-
然后使用 fillna() 方法将age中的NaN值替换成平均值
程序如下所示:
AI_15_0_8
from sys import path
path.append(r"../data/course_util")
from ai_course_15_6 import *
# 计算age特征的平均值
mean_age = X['age'].mean()
print(mean_age)
# 将NaN值替换成平均值
X['age'] = X['age'].fillna(mean_age)
# 查看X的最后5行数据
X.tail(5)
31.19418104265403
pclass | age | sex | |
---|---|---|---|
1308 | 3rd | 31.194181 | male |
1309 | 3rd | 31.194181 | male |
1310 | 3rd | 31.194181 | male |
1311 | 3rd | 31.194181 | female |
1312 | 3rd | 31.194181 | male |
从程序的输出结果中可以看出,age特征中的NaN值已经替换成平均值。
数据预处理
经过前面的处理,现在的数据集看上去“美观”多了!在我们开始训练模型之前,请你再思考一下,还有哪些问题需要我们处理?
至少还有两个问题需要我们处理:
-
pclass和sex特征是字符串类型,必须转换成数值类型,后续才能代入模型进行计算,我们可以采用 map() 方法实现文字和数值之间的转换;
-
pclass、age和sex特征的取值范围差别较大,需要将这3个特征的取值都归一化到0到1之间,这种处理在之前案例中已经出现过很多次了,相信你对这种处理不会很陌生。
AI_15_0_9
from sys import path
path.append(r"../data/course_util")
from ai_course_15_7 import *
# 将'lst'转换为1,'2nd'转换为2,'3rd'转换为3
# 将'female'转换为0,'male'转换为1
X['pclass'] = X['pclass'].map({'1st':1, '2nd':2, '3rd':3})
X['sex'] = X['sex'].map({'female':0, 'male':1})
# 查看X的最后5行数据
print(X.tail(5))
# 将所有特征都归一化到0到1之间
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)
print(X_scaled)
X_scaled
pclass age sex
1308 NaN 31.194181 NaN
1309 NaN 31.194181 NaN
1310 NaN 31.194181 NaN
1311 NaN 31.194181 NaN
1312 NaN 31.194181 NaN
[[ nan 0.40705854 nan]
[ nan 0.02588189 nan]
[ nan 0.4211762 nan]
...
[ nan 0.43803523 nan]
[ nan 0.43803523 nan]
[ nan 0.43803523 nan]]
array([[ nan, 0.40705854, nan],
[ nan, 0.02588189, nan],
[ nan, 0.4211762 , nan],
...,
[ nan, 0.43803523, nan],
[ nan, 0.43803523, nan],
[ nan, 0.43803523, nan]])
经过上述三步的处理,我们终于得到了我们想要的数据:X_scaled 和 y,从这里我们可以看出,在整个机器学习中,准备数据这一步往往需要消耗大量时间和精力,其重要性不亚于对模型的训练和调整。
训练模型
接下来我们使用sklearn库中的决策树分类器来进行训练。
AI_15_0_10
from sys import path
path.append(r"../data/course_util")
from ai_course_15_8 import *
# 将数据划分为训练集和测试集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=100)
# 从sklearn中导入决策树分类器
from sklearn.tree import DecisionTreeClassifier
# 使用默认参数初始化决策树分类器
dt_clf = DecisionTreeClassifier()
# 使用训练集来训练决策树模型
dt_clf.fit(X_train, y_train)
# 计算该模型对测试集的预测准确率
dt_clf.score(X_test, y_test)
0.779467680608365
从程序的输出结果可以看出,我们使用默认参数训练的决策树模型在测试集上的预测准确率约为78%,看上去还不错。现在我们希望这个预测的准确率能提高到80%以上,你能想到什么好办法吗?
调参
我们可以尝试一下使用不同的超参数来构建决策树模型,也就是前面我们学到的网格搜索的方法!下面我们直接调用sklearn中的 GridSearchCV 类来搜索一下最优的参数配置。
AI_15_0_11
from sys import path
path.append(r"../data/course_util")
from ai_course_15_8 import *
# 使用默认参数初始化决策树分类器
dt_clf = DecisionTreeClassifier()
# 从sklearn中导入网格搜索GridSearchCV类
from sklearn.model_selection import GridSearchCV
# 配置一下需要搜索的参数名称和取值范围,用字典进行保存
params = {'max_depth':[2,3,4,5], 'min_samples_split':[2,4,6,8,10]}
# 将决策树分类器和需要搜索的参数传给GridSearchCV,它将自动为我们寻找出最优参数组合
dt_best = GridSearchCV(dt_clf, params)
dt_best.fit(X_train, y_train)
# 将寻找到的最优参数组合打印出来
print(dt_best.best_params_)
{'max_depth': 2, 'min_samples_split': 2}
在上面的程序中,我们分别对 max_depth 和 min_samples_split 这两个超参数进行了搜索,其中对max_depth搜索了2,3,4,5这四种取值,对min_samples_split搜索了2,4,6,8,10这五种取值。网格搜索的结果显示,max_depth=2,min_samples_split=2是一组最优的参数组合,让我们赶快编写程序试一下:
AI_15_0_12
from sys import path
path.append(r"../data/course_util")
from ai_course_15_8 import *
# 使用默认参数初始化决策树分类器
dt_clf = DecisionTreeClassifier(max_depth=2, min_samples_split=2)
# 使用训练集来训练决策树模型
dt_clf.fit(X_train, y_train)
# 计算该模型对测试集的预测准确率
dt_clf.score(X_test, y_test)
0.8403041825095057
我们使用网格搜索得到的参数重新构建的决策树模型,在测试集上可以得到 84% 左右的预测准确率,看起来网格搜索的威力真的不容小视!
利用模型对数据进行挖掘
现在我们已经训练好了一个效果还不错的决策树模型,接下来我们要利用这个模型做一些有意思的探索:
预测男女主角生还概率
在泰坦尼克号的电影中,男主角Jack和女主角Rose是虚构的两个人物,根据电影的情节我们假设:
- Jack住的是三等舱,年龄为23岁,性别当然为男性
- Rose住的是头等舱,年龄为20岁,性别当然为女性
由此,我们可以构建出Jack和Rose的数据如下:
import numpy as np
jack = np.array([[3, 23, 1]])
rose = np.array([[1, 20, 0]])
于是,我们可以利用我们训练的模型来预测一下男女主角生还的概率:
AI_15_0_13
from sys import path
path.append(r"../data/course_util")
from ai_course_15_9 import *
# 将所有特征都归一化到0到1之间
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
scaler.fit(X)
X_scaled = scaler.transform(X)
# 构建男女主角的数据,并进行归一化处理
import numpy as np
jack = np.array([[3, 23, 1]])
rose = np.array([[1, 20, 0]])
jack_scaled = scaler.transform(jack)
rose_scaled = scaler.transform(rose)
# 将数据划分为训练集和测试集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=100)
# 使用默认参数初始化决策树分类器
dt_clf = DecisionTreeClassifier(max_depth=2, min_samples_split=2)
# 使用训练集来训练决策树模型
dt_clf.fit(X_train, y_train)
# 预测男女主角的生还概率
dt_clf.predict_proba(jack_scaled)[0][1]
print(dt_clf.predict_proba(rose_scaled))
[[0.11055276 0.88944724]]
从程序输出结果可以看出,男主角Jack的生存概率只有15.6%左右,而女主角的生存概率高达88.9%,符合电影的结局。
模型预测错误的样本分析
我们对于模型预测错误的那些人会非常好奇:为什么模型预测一个乘客生存下来的概率很高,但实际中却没有存活呢?是因为我们的模型不够准确,还是背后另有隐情呢?
为了找出这些冷冰冰的数据背后的故事,我们可以这样做:
- 先利用模型计算出所有人生存下来的概率
- 将生存概率这一列数据合并到原始数据中
- 对那些模型预测生存概率很高,但是实际中却没有存活下来的乘客数据进行分析
AI_15_0_14
from sys import path
path.append(r"../data/course_util")
from ai_course_15_8 import *
# 使用默认参数初始化决策树分类器
dt_clf = DecisionTreeClassifier(max_depth=2, min_samples_split=2)
# 使用训练集来训练决策树模型
dt_clf.fit(X_train, y_train)
# 计算出所有人生存下来的概率
prob = dt_clf.predict_proba(X_scaled)[:,1]
# 将生存概率添加到原表数据中
titanic['probability'] = prob
# 查看Allison家族的人的信息
titanic[1:5]
row.names | pclass | survived | name | age | embarked | home.dest | room | ticket | boat | sex | probability | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 2 | 1st | 0 | Allison, Miss Helen Loraine | 2.0000 | Southampton | Montreal, PQ / Chesterville, ON | C26 | NaN | NaN | female | 0.889447 |
2 | 3 | 1st | 0 | Allison, Mr Hudson Joshua Creighton | 30.0000 | Southampton | Montreal, PQ / Chesterville, ON | C26 | NaN | (135) | male | 0.156342 |
3 | 4 | 1st | 0 | Allison, Mrs Hudson J.C. (Bessie Waldo Daniels) | 25.0000 | Southampton | Montreal, PQ / Chesterville, ON | C26 | NaN | NaN | female | 0.889447 |
4 | 5 | 1st | 1 | Allison, Master Hudson Trevor | 0.9167 | Southampton | Montreal, PQ / Chesterville, ON | C22 | NaN | 11 | male | 1.000000 |
从程序输出结果来看,第1行和第3行的乘客都有很高的生存概率,但实际中却都没有存活下来,这是为什么呢?
真实的情况是这样的:这4条记录是Allison一家人的信息,即爸爸(30岁)、妈妈(25岁)、女儿(2岁)以及一个不满一岁的男婴。
由于在救援时遵守女士优先的原则,因此原本妈妈可以带着女儿和小婴儿坐上救生艇离开,但是因为当时突然找不到小婴儿,于是妈妈坚持不肯上救生艇,在船上寻找婴儿;然而命运总是爱捉弄人,原来婴儿早就被一个护士带上了救生艇离开,但慌乱中忘记通知它的家人,从而造成这个悲伤的故事。
本关总结:在本关,我们先介绍了sklearn中决策树模型的使用,然后利用决策树实现了对泰坦尼克号乘客生还情况的预测。
今天我们就先学到这里吧,下一关见,拜拜~
联系我们,一起学Python吧
分享Python实战代码,入门资料,进阶资料,基础语法,爬虫,数据分析,web网站,机器学习,深度学习等等。
关注公众号「Python家庭」领取1024G整套教材、交流群学习、商务合作