在人工智能与机器学习入门:使用Kaggle完成Titanic推断学习一文中,给出了使用Kaggle进行机器学习入门的方法,本文基于上文的需求。
尝试使用决策树模型来训练数据,并进行test数据集的测试。
什么是决策树
决策树,简单来讲可以认为是一个大的 if else
判断树,有了决策树后,测试集中的数据便可以使用该决策树进行判断了。
比如根据Titanic的训练数据构造了上次决策树后,便可以根据测试数据的性别、年龄等属性来预测测试数据的幸存率了。
决策树分为两个类型,分别是 分类树 与 回归树,如果我们最终预测的结果为分类问题,比如基于乘客的属性来观测乘客 是 否 幸存,根据人员的的特性预测试是 男生 还是 女生 ,根据瓶盖的特定来预计是 娃哈哈 农夫山泉 康师傅 则创建的应该是分类树;但如果我们预计的是乘客的 生存率 房子的价格 则创建的则应该是 回归树。
构建决策分类树
在决策树模型中,如果根据已有的训练数据构建决策树是该算法的核心。比如有基于以下4条数据构建决策树:
id | 喜欢颜色 | 是否有喉结 | 身高 | 性别 |
---|---|---|---|---|
1 | 绿 | 否 | 165 | 女 |
2 | 蓝 | 是 | 170 | 男 |
3 | 粉 | 否 | 172 | 女 |
4 | 绿 | 是 | 175 | 男 |
那么决策树可以是以下几种情况:
通过上述三个决策树不难发现,根据当前的情况,可以构造中多种决策树。在上述3个决策树中,由于最后一个决策树判断的次数最少,所以是当前的最优决策树,而在当前情况下,我们构建的目标则正是第3种决策树。
那么这种最优的决策树怎么构建呢?
通过观察也不难发现,是否有喉结这个特性在决定性别这个类别时,起到了最关键了作用,因为是否有喉结直接直接判断出性别的类别。在比较另外两个属性(特征) 喜欢的颜色
与 身高
相比较,喜欢的颜色
相较于 身高
属性(特征),更容易的能够区分出性别,所以当前示例数据的决策条件的优先级应该是:是否有喉结
> 喜欢的颜色
> 身高
决策算法
实际上,在构建每个决策的条件时,它的算法大体都是这样的:
- 如果当前需要决策的样本中某一项属性(特征)均相同,则移除这个属性(特征)。
- 获取当前样本属性(特征)中最容易区分出类别的属性(特征)A
- 将属性(特征)A做为当前决策条件
当数据量稍大时,我们就无法用心算来确定应该用哪个特征做为当前的决策条件了,而基尼系数和基于熵则是使用算法来描述了人们心中是如何找出当前样本中最容易区分的特征。
更多阅读:人工智能与机器学习入门:基尼系数(Gini Index)和基于熵(Entropy)
在机器学习领域,引用了不纯度做为特征是否容易区分的标准,不纯度越低,说明纯度越高,则认为更容易区分。
以是否有喉结
为例,当其值为是,人数为2全部为男性,则当前纯度为1,不纯度为0;,当其值为否,人数为2全部为女性,则当前纯度为1,不纯度为0;所以是否有喉结
加权平均加权平均不纯度为: 0.5 * 0 + 0.5 * 0 = 0
。所以是否有喉结
应该为决策树构建时候是优先决策条件。
实际上基尼系数的算法会更科学一些,我们上述例子仅仅是为了方便理解
算法应用
我们仍然以人工智能与机器学习入门:使用Kaggle完成Titanic推断学习提到的数据为例,调用决策树算法,并使用
首先我们安装一个提供决策树算法的包 scikit-learn
(实际上,该包提供了众多的算法)
python -m pip install scikit-learn
代码如下:
# This Python 3 environment comes with many helpful analytics libraries installed
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
from sklearn.preprocessing import LabelEncoder
from sklearn.tree import DecisionTreeClassifier
# 读取数据文件
# 删除无用的特征值,以避免无用的决策构建:(我认为)乘客ID,乘客的姓名,乘客车票的编号,乘客的座舱与存活率并无关联
# 对于.drop() 方法而言,当 axis=1 时,意味着要删除的是列而不是行
train_data = pd.read_csv("/Users/panjie/study/kaggle/train.csv").drop(['PassengerId', 'Name', 'Ticket', 'Cabin'], axis=1)
# 由于决策树没有办法处理某个特性上的缺失值,所以还需要其它数据的情况,为那些缺失了相关属性的数据,设置一个合理的值
# 用中位数(比如一共99个登记了年龄的乘客,按年龄从小到大排序后,第50个乘客的年龄就是中位数)填充年龄
# 在统计学中,中位数比平均数更具有代表性。
# 举个简单的列子,一个部门三个员工。年收入分别为8万,9万,100万,则你们的年收入平均数是39万元,而你们的年收入中位数是9万元
# 所以:当领导汇报时,如果目标是突出员工收入高,则应该用平均数; 而如果他的目标是突出收入低,则应该用中位数
train_data['Age'] = train_data['Age'].fillna(train_data['Age'].median())
# 众数比较简单,就是出现次数最多的数。
train_data['Embarked'] = train_data['Embarked'].fillna(train_data['Embarked'].mode()[0]) # 用众数填充登船港口
print("填充后的数据:\n")
print(train_data.head())
# 决策树在处理特征值时,只支持数字类型。所以我们需要把一些原始数据中的string类型,转换为数字类型
# 比如 Sex 中有male或female,则在转换时将其对应转换为 0 或 1
# 创建个转换的缓存,因为测试数据也需要进行转换。在些需要保证训练数据与测试数据在进行字符串转换到数字时的一致性
label_encoders = {}
categorical_columns = ['Sex', 'Embarked'] # 根据源数据情况,定义要转的列
for col in categorical_columns:
le = LabelEncoder()
train_data[col] = le.fit_transform(train_data[col]) # 将字符串转换为数字类别
label_encoders[col] = le # 缓存对应的列的转换对象,以使得测试数据的转换规则与训练数据相同
print("转换分类后数据:\n")
print(train_data.head())
# 删除 Survived 特征后做为训练集
x_train = train_data.drop('Survived', axis=1)
# 获取 Survived 做为结果集
y_train = train_data['Survived']
# 创建决策树模型,使用基尼系数作为不纯度衡量标准
# 同时设置一个随机状态,它的作用是当两个决策条件的gini系数相同时,会根据该值来确定选择某一个
# 如果不设置random_state则当两个决策条件的gini系数相同时,会随机选择某一个,这会使得在多个决策的gini系统相同时,
# 多次执行当前程序后会得到不同的结果
decisionTreeClassifier = DecisionTreeClassifier(criterion='gini', random_state=42)
# 使用训练集及结果集训练模型( 变量的名字前缀的x,y 来源于 y = f(x) 的通俗写法)
decisionTreeClassifier.fit(x_train, y_train)
# 加载测试数据
test_data = pd.read_csv("/Users/panjie/study/kaggle/test.csv").drop(['PassengerId', 'Name', 'Ticket', 'Cabin'], axis=1)
test_data['Age'] = test_data['Age'].fillna(test_data['Age'].median()) # 使用中位数填充年龄
test_data['Embarked'] = test_data['Embarked'].fillna(test_data['Embarked'].mode()[0]) # 用众数填充登船港口
print("测试数据\n")
print(test_data.head())
# 完成字符串类型到数字的转换
for col in categorical_columns:
test_data[col] = label_encoders[col].fit_transform(test_data[col])
print("分类后数据\n")
print(test_data.head())
# 预测
y_pred = decisionTreeClassifier.predict(test_data)
print("预测值:\n")
print(y_pred)
# 加载真实标签
true_labels = pd.read_csv("/Users/panjie/study/kaggle/gender_submission.csv")
# 将下载文件的存活结果取出来,并变成数字数组
y_true = true_labels['Survived'].to_numpy()
print("真实值\n")
print(y_true)
# 结论
print("准确率:", accuracy_score(y_true, y_pred))
print("分类报告:\n", classification_report(y_true, y_pred))
print("混淆矩阵:\n", confusion_matrix(y_true, y_pred))
运行结果如下;
填充后的数据:
Survived Pclass Sex Age SibSp Parch Fare Embarked
0 0 3 male 22.0 1 0 7.2500 S
1 1 1 female 38.0 1 0 71.2833 C
2 1 3 female 26.0 0 0 7.9250 S
3 1 1 female 35.0 1 0 53.1000 S
4 0 3 male 35.0 0 0 8.0500 S
转换分类后数据:
Survived Pclass Sex Age SibSp Parch Fare Embarked
0 0 3 1 22.0 1 0 7.2500 2
1 1 1 0 38.0 1 0 71.2833 0
2 1 3 0 26.0 0 0 7.9250 2
3 1 1 0 35.0 1 0 53.1000 2
4 0 3 1 35.0 0 0 8.0500 2
测试数据
Pclass Sex Age SibSp Parch Fare Embarked
0 3 male 34.5 0 0 7.8292 Q
1 3 female 47.0 1 0 7.0000 S
2 2 male 62.0 0 0 9.6875 Q
3 3 male 27.0 0 0 8.6625 S
4 3 female 22.0 1 1 12.2875 S
分类后数据
Pclass Sex Age SibSp Parch Fare Embarked
0 3 1 34.5 0 0 7.8292 1
1 3 0 47.0 1 0 7.0000 2
2 2 1 62.0 0 0 9.6875 1
3 3 1 27.0 0 0 8.6625 2
4 3 0 22.0 1 1 12.2875 2
预测值:
[0 0 1 1 1 0 0 0 1 0 0 0 1 0 1 1 0 1 1 0 1 1 1 0 1 0 1 1 1 0 0 0 1 0 1 0 0
0 0 1 1 1 0 1 1 0 1 1 1 1 1 0 1 0 1 0 0 0 0 1 0 0 0 1 1 1 1 0 0 1 1 0 0 0
1 0 0 1 0 1 1 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 0 1 0 1 0 0 1 1 0 0
1 1 1 1 0 0 1 0 1 1 1 1 0 1 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 1 0
1 0 1 0 0 1 0 0 1 0 0 1 1 1 1 1 0 1 0 0 1 0 1 0 0 0 0 1 1 1 1 1 0 1 1 1 1
0 1 0 0 0 0 0 0 1 1 0 1 0 0 0 1 1 0 1 0 0 0 0 1 0 0 1 0 0 0 0 1 0 1 0 1 0
1 1 1 0 0 1 0 0 0 1 0 0 1 0 1 1 1 1 1 1 1 0 0 0 1 0 1 0 0 0 0 0 0 1 1 0 1
0 0 0 1 1 0 0 0 1 0 0 0 1 1 1 0 1 0 0 0 0 0 1 1 0 0 0 1 0 0 0 1 0 0 0 0 0
0 0 0 0 1 0 0 0 1 1 0 1 0 0 0 1 1 0 1 1 1 0 1 0 0 0 0 1 1 0 1 0 0 0 1 1 0
1 0 0 0 0 0 0 0 1 0 1 0 1 0 0 0 1 1 0 0 0 0 0 1 0 1 1 0 1 1 1 1 1 0 1 1 1
0 1 0 0 1 1 0 0 0 0 1 0 1 1 0 1 0 0 0 0 0 1 1 0 0 1 1 1 0 0 1 0 1 1 0 1 0
0 1 1 1 1 0 0 1 0 0 0]
真实值
[0 1 0 0 1 0 1 0 1 0 0 0 1 0 1 1 0 0 1 1 0 0 1 0 1 0 1 0 0 0 0 0 1 1 0 0 1
1 0 0 0 0 0 1 1 0 0 0 1 1 0 0 1 1 0 0 0 0 0 1 0 0 0 1 0 1 1 0 0 1 1 0 1 0
1 0 0 1 0 1 0 0 0 0 0 0 1 1 1 0 1 0 1 0 0 0 1 0 1 0 1 0 0 0 1 0 0 0 0 0 0
1 1 1 1 0 0 1 0 1 1 0 1 0 0 1 0 1 0 0 0 0 1 0 0 0 0 0 1 0 1 1 0 0 0 0 0 0
0 0 1 0 0 1 0 0 1 1 0 1 1 0 1 0 0 1 0 0 1 1 0 0 0 0 0 1 1 0 1 1 0 0 1 0 1
0 1 0 1 0 0 0 0 0 0 0 0 1 0 1 1 0 0 1 0 0 1 0 1 0 0 0 0 1 1 0 1 0 1 0 1 0
1 0 1 1 0 1 0 0 0 1 0 0 0 0 0 0 1 1 1 1 0 0 0 0 1 0 1 1 1 0 0 0 0 0 0 0 1
0 0 0 1 1 0 0 0 0 1 0 0 0 1 1 0 1 0 0 0 0 1 0 1 1 1 0 0 0 0 0 0 1 0 0 0 0
1 0 0 0 0 0 0 0 1 1 0 0 0 1 0 0 0 1 1 1 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1 0 0
1 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 0 1 1 0 1 1 0 1 1 0
0 1 0 0 1 1 1 0 0 0 0 0 1 1 0 1 0 0 0 0 0 1 0 0 0 1 0 1 0 0 1 0 1 0 0 0 0
0 1 1 1 1 1 0 1 0 0 0]
准确率: 0.7488038277511961
分类报告:
precision recall f1-score support
0 0.85 0.74 0.79 266
1 0.63 0.77 0.69 152
accuracy 0.75 418
macro avg 0.74 0.75 0.74 418
weighted avg 0.77 0.75 0.75 418
混淆矩阵:
[[196 70]
[ 35 117]]
以下简单对运行的结果进行说明,更多的还需要进行相关统计学的学习。
- 准确率 accuracy:识别的结果的正确率,比如当前共预测了418条数据,其它预测正确的为313条,则预测的正确率为 0.7488038277511961。它直接衡量了算法的准备度。该值越接近1说明算法越好。
- 精确率 precision:在此例中:模型共预测了非幸存者
196 + 35 = 230
人,其中196人为真实的非幸存者,所以在非幸存者预测中,其精准度为:196/230=0.85
; 模型同时预测的幸存者人数为117 + 70 = 187
人,其中117人为真实的幸存者,所以在幸存者预测中,其精准度为117/187=0.63
。所以在某个类别上该值越大,说明在该类别上预测的越精准。 - 召回率 recall:这里的召回需要简单解释一下,不同于平常我们日常生活接触的汽车召回的召回率,这个召回率指的在信息检索领域中的通过关键字可以召回多少相关资料回来。比如与
检索机器
相关的文档有10个,在信息检索时,输入检索机器
后返回了其中的7个文档,则召回率为0.7。在本例中,预测非幸存者共计196人,但实际上非幸存者共为196 + 70 = 266
人,所以对非幸存者分类而言,召回率为196/266=0.74
。 同时,也可以计算出幸存者的召回率。此值越接近1说明预测的越好。 - F1 分数 f1-score:精确率与召回率的算术平均值,对于非幸存者而言
(0.85 + 0.74 / 2) = 0.79
, 此值越接近1说明预测的越好。 - 支持度 support: 指样本个数,比如此例中非幸存者的个数为 266, 幸存者的个数为 152,总数为 418 人。
- 宏平均 macro avg:可以简单的理解为各个类别的算术平均数
- 加权平均 weighted avg:各个类别的加权(以支持度为权重)平均数
混淆矩阵: 显示了4个数字,对于本例而言,分别指:
- 实际未幸存,预测同样为未幸存的个数
- 实际未幸存,但预测为幸存的个数
- 实际幸存,但预测为未幸存的个数
- 实际幸存,预测同样为幸存的个数
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。