在前面的两篇文章中,我给你讲了决策树算法。决策树算法是经常使用的数据挖掘算法,这是因为决策树就像一个人脑中的决策模型一样,呈现出来非常直观。基于决策树还诞生了很多数据挖掘算法,比如随机森林(Random forest)。 今天我来带你用决策树进行项目的实战。 决策树分类的应用场景非常广泛,在各行各业都有应用,比如在金融行业可以用决策树做贷款风险评估,医疗行业可以用决策树生成辅助诊断,电商行业可以用决策树对销售额进行预测等。 在了解决策树的原理后,今天我们用sklearn工具解决一个实际的问题:泰坦尼克号乘客的生存预测。 ## sklearn中的决策树模型 首先,我们需要掌握sklearn中自带的决策树分类器DecisionTreeClassifier,方法如下: ``` clf = DecisionTreeClassifier(criterion='entropy') ``` 到目前为止,sklearn中只实现了ID3与CART决策树,所以我们暂时只能使用这两种决策树,在构造DecisionTreeClassifier类时,其中有一个参数是criterion,意为标准。它决定了构造的分类树是采用ID3分类树,还是CART分类树,对应的取值分别是entropy或者gini:
  • entropy: 基于信息熵,也就是ID3算法,实际结果与C4.5相差不大;
  • gini:默认参数,基于基尼系数。CART算法是基于基尼系数做属性划分的,所以criterion=gini时,实际上执行的是CART算法。
  • 我们通过设置criterion='entropy’可以创建一个ID3决策树分类器,然后打印下clf,看下决策树在sklearn中是个什么东西? ``` DecisionTreeClassifier(class_weight=None, criterion='entropy', 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=False, random_state=None, splitter='best') ``` 这里我们看到了很多参数,除了设置criterion采用不同的决策树算法外,一般建议使用默认的参数,默认参数不会限制决策树的最大深度,不限制叶子节点数,认为所有分类的权重都相等等。当然你也可以调整这些参数,来创建不同的决策树模型。 我整理了这些参数代表的含义: 在构造决策树分类器后,我们可以使用fit方法让分类器进行拟合,使用predict方法对新数据进行预测,得到预测的分类结果,也可以使用score方法得到分类器的准确率。 下面这个表格是fit方法、predict方法和score方法的作用。 ## Titanic乘客生存预测 **问题描述** 泰坦尼克海难是著名的十大灾难之一,究竟多少人遇难,各方统计的结果不一。现在我们可以得到部分的数据,具体数据你可以从GitHub上下载:[https://github.com/cystanford/Titanic_Data](https://github.com/cystanford/Titanic_Data) (完整的项目代码见:[https://github.com/cystanford/Titanic_Data/blob/master/titanic_analysis.py](https://github.com/cystanford/Titanic_Data/blob/master/titanic_analysis.py) 你可以跟着学习后自己练习) 其中数据集格式为csv,一共有两个文件:
  • train.csv是训练数据集,包含特征信息和存活与否的标签;
  • test.csv: 测试数据集,只包含特征信息。
  • 现在我们需要用决策树分类对训练集进行训练,针对测试集中的乘客进行生存预测,并告知分类器的准确率。 在训练集中,包括了以下字段,它们具体为:
    **生存预测的关键流程** 我们要对训练集中乘客的生存进行预测,这个过程可以划分为两个重要的阶段:
  • **准备阶段**:我们首先需要对训练集、测试集的数据进行探索,分析数据质量,并对数据进行清洗,然后通过特征选择对数据进行降维,方便后续分类运算;
  • **分类阶段**:首先通过训练集的特征矩阵、分类结果得到决策树分类器,然后将分类器应用于测试集。然后我们对决策树分类器的准确性进行分析,并对决策树模型进行可视化。
  • 下面,我分别对这些模块进行介绍。 **模块1:数据探索** 数据探索这部分虽然对分类器没有实质作用,但是不可忽略。我们只有足够了解这些数据的特性,才能帮助我们做数据清洗、特征选择。 那么如何进行数据探索呢?这里有一些函数你需要了解:
  • 使用info()了解数据表的基本情况:行数、列数、每列的数据类型、数据完整度;
  • 使用describe()了解数据表的统计情况:总数、平均值、标准差、最小值、最大值等;
  • 使用describe(include=[‘O’])查看字符串类型(非数字)的整体情况;
  • 使用head查看前几行数据(默认是前5行);
  • 使用tail查看后几行数据(默认是最后5行)。
  • 我们可以使用Pandas便捷地处理这些问题: ``` import pandas as pd # 数据加载 train_data = pd.read_csv('./Titanic_Data/train.csv') test_data = pd.read_csv('./Titanic_Data/test.csv') # 数据探索 print(train_data.info()) print('-'*30) print(train_data.describe()) print('-'*30) print(train_data.describe(include=['O'])) print('-'*30) print(train_data.head()) print('-'*30) print(train_data.tail()) ``` 运行结果: ``` <class 'pandas.core.frame.DataFrame'> RangeIndex: 891 entries, 0 to 890 Data columns (total 12 columns): PassengerId 891 non-null int64 Survived 891 non-null int64 Pclass 891 non-null int64 Name 891 non-null object Sex 891 non-null object Age 714 non-null float64 SibSp 891 non-null int64 Parch 891 non-null int64 Ticket 891 non-null object Fare 891 non-null float64 Cabin 204 non-null object Embarked 889 non-null object dtypes: float64(2), int64(5), object(5) memory usage: 83.6+ KB None ------------------------------ PassengerId Survived ... Parch Fare count 891.000000 891.000000 ... 891.000000 891.000000 mean 446.000000 0.383838 ... 0.381594 32.204208 std 257.353842 0.486592 ... 0.806057 49.693429 min 1.000000 0.000000 ... 0.000000 0.000000 25% 223.500000 0.000000 ... 0.000000 7.910400 50% 446.000000 0.000000 ... 0.000000 14.454200 75% 668.500000 1.000000 ... 0.000000 31.000000 max 891.000000 1.000000 ... 6.000000 512.329200 [8 rows x 7 columns] ------------------------------ Name Sex ... Cabin Embarked count 891 891 ... 204 889 unique 891 2 ... 147 3 top Peter, Mrs. Catherine (Catherine Rizk) male ... B96 B98 S freq 1 577 ... 4 644 [4 rows x 5 columns] ------------------------------ PassengerId Survived Pclass ... Fare Cabin Embarked 0 1 0 3 ... 7.2500 NaN S 1 2 1 1 ... 71.2833 C85 C 2 3 1 3 ... 7.9250 NaN S 3 4 1 1 ... 53.1000 C123 S 4 5 0 3 ... 8.0500 NaN S [5 rows x 12 columns] ------------------------------ PassengerId Survived Pclass ... Fare Cabin Embarked 886 887 0 2 ... 13.00 NaN S 887 888 1 1 ... 30.00 B42 S 888 889 0 3 ... 23.45 NaN S 889 890 1 1 ... 30.00 C148 C 890 891 0 3 ... 7.75 NaN Q [5 rows x 12 columns] ``` **模块2:数据清洗** 通过数据探索,我们发现Age、Fare和Cabin这三个字段的数据有所缺失。其中Age为年龄字段,是数值型,我们可以通过平均值进行补齐;Fare为船票价格,是数值型,我们也可以通过其他人购买船票的平均值进行补齐。 具体实现的代码如下: ``` # 使用平均年龄来填充年龄中的nan值 train_data['Age'].fillna(train_data['Age'].mean(), inplace=True) test_data['Age'].fillna(test_data['Age'].mean(),inplace=True) # 使用票价的均值填充票价中的nan值 train_data['Fare'].fillna(train_data['Fare'].mean(), inplace=True) test_data['Fare'].fillna(test_data['Fare'].mean(),inplace=True) ``` Cabin为船舱,有大量的缺失值。在训练集和测试集中的缺失率分别为77%和78%,无法补齐;Embarked为登陆港口,有少量的缺失值,我们可以把缺失值补齐。 首先观察下Embarked字段的取值,方法如下: ``` print(train_data['Embarked'].value_counts()) ``` 结果如下: ``` S 644 C 168 Q 77 ``` 我们发现一共就3个登陆港口,其中S港口人数最多,占到了72%,因此我们将其余缺失的Embarked数值均设置为S: ``` # 使用登录最多的港口来填充登录港口的nan值 train_data['Embarked'].fillna('S', inplace=True) test_data['Embarked'].fillna('S',inplace=True) ``` **模块3:特征选择** 特征选择是分类器的关键。特征选择不同,得到的分类器也不同。那么我们该选择哪些特征做生存的预测呢? 通过数据探索我们发现,PassengerId为乘客编号,对分类没有作用,可以放弃;Name为乘客姓名,对分类没有作用,可以放弃;Cabin字段缺失值太多,可以放弃;Ticket字段为船票号码,杂乱无章且无规律,可以放弃。其余的字段包括:Pclass、Sex、Age、SibSp、Parch和Fare,这些属性分别表示了乘客的船票等级、性别、年龄、亲戚数量以及船票价格,可能会和乘客的生存预测分类有关系。具体是什么关系,我们可以交给分类器来处理。 因此我们先将Pclass、Sex、Age等这些其余的字段作特征,放到特征向量features里。 ``` # 特征选择 features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked'] train_features = train_data[features] train_labels = train_data['Survived'] test_features = test_data[features] ``` 特征值里有一些是字符串,这样不方便后续的运算,需要转成数值类型,比如Sex字段,有male和female两种取值。我们可以把它变成Sex=male和Sex=female两个字段,数值用0或1来表示。 同理Embarked有S、C、Q三种可能,我们也可以改成Embarked=S、Embarked=C和Embarked=Q三个字段,数值用0或1来表示。 那该如何操作呢,我们可以使用sklearn特征选择中的DictVectorizer类,用它将可以处理符号化的对象,将符号转成数字0/1进行表示。具体方法如下: ``` from sklearn.feature_extraction import DictVectorizer dvec=DictVectorizer(sparse=False) train_features=dvec.fit_transform(train_features.to_dict(orient='record')) ``` 你会看到代码中使用了fit_transform这个函数,它可以将特征向量转化为特征值矩阵。然后我们看下dvec在转化后的特征属性是怎样的,即查看dvec的feature_names_属性值,方法如下: ``` print(dvec.feature_names_) ``` 运行结果: ``` ['Age', 'Embarked=C', 'Embarked=Q', 'Embarked=S', 'Fare', 'Parch', 'Pclass', 'Sex=female', 'Sex=male', 'SibSp'] ``` 你可以看到原本是一列的Embarked,变成了“Embarked=C”“Embarked=Q”“Embarked=S”三列。Sex列变成了“Sex=female”“Sex=male”两列。 这样train_features特征矩阵就包括10个特征值(列),以及891个样本(行),即891行,10列的特征矩阵。 **模块4:决策树模型** 刚才我们已经讲了如何使用sklearn中的决策树模型。现在我们使用ID3算法,即在创建DecisionTreeClassifier时,设置criterion=‘entropy’,然后使用fit进行训练,将特征值矩阵和分类标识结果作为参数传入,得到决策树分类器。 ``` from sklearn.tree import DecisionTreeClassifier # 构造ID3决策树 clf = DecisionTreeClassifier(criterion='entropy') # 决策树训练 clf.fit(train_features, train_labels) ``` **模块5:模型预测&评估** 在预测中,我们首先需要得到测试集的特征值矩阵,然后使用训练好的决策树clf进行预测,得到预测结果pred_labels: ``` test_features=dvec.transform(test_features.to_dict(orient='record')) # 决策树预测 pred_labels = clf.predict(test_features) ``` 在模型评估中,决策树提供了score函数可以直接得到准确率,但是我们并不知道真实的预测结果,所以无法用预测值和真实的预测结果做比较。我们只能使用训练集中的数据进行模型评估,可以使用决策树自带的score函数计算下得到的结果: ``` # 得到决策树准确率 acc_decision_tree = round(clf.score(train_features, train_labels), 6) print(u'score准确率为 %.4lf' % acc_decision_tree) ``` 运行结果: ``` score准确率为 0.9820 ``` 你会发现你刚用训练集做训练,再用训练集自身做准确率评估自然会很高。但这样得出的准确率并不能代表决策树分类器的准确率。 这是为什么呢? 因为我们没有测试集的实际结果,因此无法用测试集的预测结果与实际结果做对比。如果我们使用score函数对训练集的准确率进行统计,正确率会接近于100%(如上结果为98.2%),无法对分类器的在实际环境下做准确率的评估。 那么有什么办法,来统计决策树分类器的准确率呢? 这里可以使用K折交叉验证的方式,交叉验证是一种常用的验证分类准确率的方法,原理是拿出大部分样本进行训练,少量的用于分类器的验证。K折交叉验证,就是做K次交叉验证,每次选取K分之一的数据作为验证,其余作为训练。轮流K次,取平均值。 K折交叉验证的原理是这样的:
  • 将数据集平均分割成K个等份;
  • 使用1份数据作为测试数据,其余作为训练数据;
  • 计算测试准确率;
  • 使用不同的测试集,重复2、3步骤。
  • 在sklearn的model_selection模型选择中提供了cross_val_score函数。cross_val_score函数中的参数cv代表对原始数据划分成多少份,也就是我们的K值,一般建议K值取10,因此我们可以设置CV=10,我们可以对比下score和cross_val_score两种函数的正确率的评估结果: ``` import numpy as np from sklearn.model_selection import cross_val_score # 使用K折交叉验证 统计决策树准确率 print(u'cross_val_score准确率为 %.4lf' % np.mean(cross_val_score(clf, train_features, train_labels, cv=10))) ``` 运行结果: ``` cross_val_score准确率为 0.7835 ``` 你可以看到,score函数的准确率为0.9820,cross_val_score准确率为 0.7835。 这里很明显,对于不知道测试集实际结果的,要使用K折交叉验证才能知道模型的准确率。 **模块6:决策树可视化** sklearn的决策树模型对我们来说,还是比较抽象的。我们可以使用Graphviz可视化工具帮我们把决策树呈现出来。
    安装Graphviz库需要下面的几步:
  • 安装graphviz工具,这里是它的下载地址;[http://www.graphviz.org/download/](http://www.graphviz.org/download/)
  • 将Graphviz添加到环境变量PATH中;
  • 需要Graphviz库,如果没有可以使用pip install graphviz进行安装。
  • 这样你就可以在程序里面使用Graphviz对决策树模型进行呈现,最后得到一个决策树可视化的PDF文件,可视化结果文件Source.gv.pdf你可以在GitHub上下载:[https://github.com/cystanford/Titanic_Data](https://github.com/cystanford/Titanic_Data) ## 决策树模型使用技巧总结 今天我用泰坦尼克乘客生存预测案例把决策树模型的流程跑了一遍。在实战中,你需要注意一下几点:
  • 特征选择是分类模型好坏的关键。选择什么样的特征,以及对应的特征值矩阵,决定了分类模型的好坏。通常情况下,特征值不都是数值类型,可以使用DictVectorizer类进行转化;
  • 模型准确率需要考虑是否有测试集的实际结果可以做对比,当测试集没有真实结果可以对比时,需要使用K折交叉验证cross_val_score;
  • Graphviz可视化工具可以很方便地将决策模型呈现出来,帮助你更好理解决策树的构建。

  • 我上面讲了泰坦尼克乘客生存预测的六个关键模块,请你用sklearn中的决策树模型独立完成这个项目,对测试集中的乘客是否生存进行预测,并给出模型准确率评估。数据从GitHub上下载即可。 最后给你留一个思考题吧,我在构造特征向量时使用了DictVectorizer类,使用fit_transform函数将特征向量转化为特征值矩阵。DictVectorizer类同时也提供transform函数,那么这两个函数有什么区别? 欢迎你在评论区留言与我分享你的答案,也欢迎点击“请朋友读”把这篇文章分享给你的朋友或者同事,一起交流一下。