"> "> 机器学习-基本步骤 | Yufei Luo's Blog

机器学习-基本步骤

概述

要构建一个完整的机器学习项目,一般可以按照如下步骤进行:

  1. 了解整个项目的概况,获取原始数据
  2. 对数据做特征工程
  3. 选择合适的机器学习方法,按所选择的策略对模型进行训练和优化
  4. 模型的部署、监控和维护

通常,第二步和第三步是重要且需要花费很多时间的步骤。同时这两步也相互依赖,需要根据数据固有的特点选择合适的机器学习模型,也需要根据所选择的机器学习模型来决定特征工程的具体操作。在下文中,将对机器学习的详细步骤进行说明,并使用Python的Scikit-learn函数库提供一些简单的示例。与此同时,在每个步骤中也会对Scikit-learn函数库的使用方法进行总结。

注意:在本文使用到的数据中,每一行的数据代表一个样本,每一列的数据代表一个特征。它是使用Python做机器学习相关任务时,所遵守的一个约定俗成的规则。Scikit-learn、PyTorch、TensorFlow等机器学习与深度学习相关的函数库都是按照这一规则进行开发的。

特征工程

在机器学习任务中,数据和特征决定了机器学习的上限(参考偏差-方差分解中的噪声部分,噪声决定了机器学习任务的难度),而模型和算法只是逼近这个上限而已。而这里的数据便指的是经过特征工程得到的数据。

特征工程指的是把原始数据转变为模型的训练数据的过程,它的目的是获取更好的数据特征,使得机器学习模型可以更好地逼近学习的上限。

特征工程的步骤包括:

  1. 数据初步分析。这一步是为了对数据做初步的了解,便于在下一步对其进行预处理。
  2. 数据预处理。包括对变量进行编码,将字符串转换为数字,对变量的取值范围进行缩放等等。
  3. 特征选择。特征过多对于模型训练会额外增加负担,而且有些特征对于模型的表现影响很小,因此需要对特征进行筛选。
  4. 降维。维度过大会影响一些模型的效果,因此必要时需要对特征进行降维处理。

上述的每一个步骤都包含一些具体的方法,可以参考本博客中专门介绍特征工程的文章。

模型训练与评估

在对数据做完特征工程之后,接下来需要选择合适的模型来进行训练,并评估模型的训练效果。对于监督学习来说,模型的训练效果可以定量地计算出来;而对于非监督学习的效果,通常需要人为地去评估。下面我们以监督学习为例,来介绍模型训练与评估的流程。

下面的内容中,我们以Sklearn中的iris数据集为例,来说明模型训练与评估的过程。需要注意的是,这一部分的目的主要是为了说明模型的训练过程,再加上iris数据集比较“干净”,因此略去了特征筛选的过程。但是在实际应用中,特征工程这一过程是必不可少的。

1
2
3
4
from sklearn.datasets import load_iris
orig_data=load_iris()
iris_data=orig_data.data #鸢尾花特征,包括花萼长度等4条属性
iris_target=orig_data.target #鸢尾花的类别

分割数据集

在训练模型之前,首先要对数据集进行分割。sklearn.model_select模块提供了一些可以用来划分数据集的函数,它们使用起来非常方便。

train_test_split函数为基本的划分数据集的函数,它会使用一个随机数种子,将数据集打乱之后按照一定的比例划分为训练集和测试集两部分。这一函数可以传入多个要划分的数据,此时会使用一套相同的索引对数据集进行划分,但是要保证这些数据在维度0上的大小相同。函数的返回值为:数据1的训练集、数据1的测试集、数据2的训练集、数据2的测试集……。

其中重要参数的含义如下:

  • train_size:可以传入一个整数,代表训练集的大小;或者传入一个0-1的浮点数,代表训练集占数据集的百分比
  • test_size:可以传入一个整数,代表测试集的大小;或者传入一个0-1的浮点数,代表测试集占数据集的百分比。需要注意的是,训练集+测试集必须要小于等于整个数据集的大小。二者可以同时传入;也可以只传入一个,另一个会根据传入的参数自动推断
  • random_state:一个随机数种子,用于复现数据集的划分方式
1
2
from sklearn.model_selection import train_test_split
train_data,test_data,train_label,test_label=train_test_split(iris_data,iris_target,test_size=0.2,random_state=1234)

此外,sklearn.model_select模块中的下面这些类可以用来生成划分数据集的索引,对应于一些常用的划分数据集的方法:

  • K折交叉验证:将全部训练集划分为K个不相交的子集
    • KFold:普通的K折划分
    • GroupKFold:需要传入一个额外的参数,代表每个样本所属的分组
    • StratifiedKFold:按照标签的类别比例进行划分,保证每个分组内每一类的比例相同
  • 留一法:
    • LeaveOneOut:留下一个样本作为测试样本,其余为训练样本
    • LeavePOut:留下P个样本作为测试样本,其余为训练样本
    • LeaveOneGroupOut:每个分组留下一个作为测试样本
    • LeavePGroupsOut:每个分组留下P个作为测试样本
  • 随机划分法:
    • ShuffleSplit:随机打乱之后再划分
    • GroupShuffleSplit:同样是随机打乱之后再划分,但是按照分组进行操作
    • StratifiedShuffleSplit:随机打乱并按照类别比例分层划分

生成索引之后,便可以使用这些索引来获得对应的训练集与测试集。上述这些类的使用方法比较相似,因此下面以StratifiedShuffleSplit为例来演示其用法:

1
2
3
4
5
6
7
from sklearn.model_selection import StratifiedShuffleSplit
stratified_split=StratifiedShuffleSplit(n_splits=2,test_size=0.2,random_state=1234) #在StratifiedShuffleSplit类中,初始化参数n_splits代表生成多少组划分的索引,每一组的索引独立
for train_index,test_index in stratified_split.split(iris_data,iris_target): #因上面的n_splits=2,此处的for循环会进行两轮
print(train_index,test_index)
#验证测试集与训练集中的类别比例是否相同
print(pd.Series(iris_target[train_index]).value_counts())
print(pd.Series(iris_target[test_index]).value_counts())
[141 139  62 ...  39 106  51] [104  24  47 ... 112 126 119]
2    40
1    40
0    40
dtype: int64
2    10
1    10
0    10
dtype: int64
[  2 127   8 ... 145  62  38] [138 126  52 ...  10  96 123]
2    40
1    40
0    40
dtype: int64
2    10
1    10
0    10
dtype: int64

我们使用上一步的分层划分结果,对接下来模型训练过程中使用的iris数据集进行分割:

1
2
3
4
train_data=iris_data[train_index]
train_label=iris_target[train_index]
test_data=iris_data[test_index]
test_label=iris_target[test_index]

模型选择与训练

在模型的选择上,通常需要考虑很多因素,包括但不限于下面这些:

  • 原始数据的类型:不同的模型擅长处理不同类型的原始数据。例如,目前主流的用于处理图片数据的方法是使用卷积神经网络(CNN);对于表格类的数据,通常需要根据特征是连续值还是离散值来判断,如果离散的特征较多,可以考虑朴素贝叶斯、决策树等模型,而连续型特征较多时则通常使用线性回归、逻辑回归等模型;此外,自然语言类或是时间序列数据可以考虑循环神经网络(RNN)模型,图结构模型可以考虑图神经网络(GNN)模型。
  • 模型部署环境:训练好的模型需要部署在什么样的环境也是选择模型的一个重要考量。如果需要将模型部署在嵌入式系统中时,就需要在保证模型预测效果的前提下,选择计算速度更快以及体积更小的模型;如果要使用神经网络模型,但是在部署环境中没有GPU的话,就需要控制神经网络的规模;等等。
  • 模型的可解释性:在某些场合中需要让训练出来的模型具有一定的可解释性,例如金融的风控系统、科学研究等领域。由于神经网络的可解释性较差,因此在需要模型具有可解释性的场合,就需要选择如线性回归、支持向量机、逻辑回归这样可解释性较好的模型。
  • 模型的使用场景:在一些情形如科学研究或者工程建模中,可能会要求训练好的模型可以求导,此时就无法使用类似于决策树这样,函数表达式为分段函数的模型。

在iris数据集中,所有数据中一共包含了3种类型的标签,因此我们要做的是一个多分类的任务。iris数据集的特征都是连续值,因此下面以决策树和支持向量机这两种模型为例,来说明模型的训练过程。需要说明的是,sklearn提供的模型默认支持多分类任务(默认使用One versus Rest分类),不需要使用sklearn.multiclass中的类对其进行包装。

备注:

sklearn.multiclass模块提供了OneVsRestClassifierOneVsOneClassifier两种多分类模型的封装函数,使用时传入一个基分类器即可,使用示例:

1
2
3
>from sklearn.multiclass import OneVsRestClassifier
>from sklearn.tree import DecisionTreeClassifier
>ovr_clf=OneVsRestClassifier(DecisionTreeClassifier()) #传入一个基本分类器作为参数

在不同的机器学习模型中,通常都存在着不同的超参数。要调整这些超参数,可以使用网格搜索或随机搜索的方式。sklearn.model_selection模块提供了GridSearchCV类和RandomizedSearchCV类,实现了通过K折交叉验证来寻找最优参数的方法。在寻找模型超参数的时候,可以使用它们来方便操作。在寻找超参数的时候,通常需要给出超参数的取值范围,这一范围的设定依赖于经验以及数据本身的一些特性。

特别需要注意的是,在使用网格搜索或者是随机搜索的fit函数时,搜索结束之后会自动使用查找到的最优参数组合初始化一个新模型,再使用传入的所有训练集对这个新模型进行训练。也就是说,相当于是超参数搜索完成之后又自动完成了下面两个步骤:

1
2
x_classifier=XClassifier(**grid_search_X.best_params_) #X代表模型名称
x_classifier.fit(train_data,train_label)
如果不想自动完成这一步骤,需要在初始化GridSearchCV类和RandomizedSearchCV类的时候,传入refit=False(默认为True)。

搜索完成之后,可以调用类的属性来查看结果,常用的有:

  • best_score_:查看最高得分
  • best_params_:查看搜索出来的最优参数
  • best_estimator_:输出最优模型
  • cv_results_:查看搜索的详细结果

首先,我们构造一个决策树模型,使用网格搜索寻找合适的超参数并训练它:

1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.model_selection import GridSearchCV
from sklearn.tree import DecisionTreeClassifier

param_tree_classifier={
'max_depth':[3,4,5],
'min_samples_split':[2,4,8],
'max_leaf_nodes':[5,10,20],
'min_samples_leaf':[1,2,4]
}

grid_search_tree=GridSearchCV(DecisionTreeClassifier(),param_tree_classifier,cv=5,scoring='precision_macro') #第一个参数为一个模型的实体;第二个参数为一个dict,包含了要调整的参数名和取值;cv代表多少折交叉验证;scoring代表超参数选择时的评分标准,可以使用 sorted(sklearn.metrics.SCORERS.keys()) 查看所有可选的标准
grid_search_tree.fit(train_data,train_label)

然后我们再构造用于多分类的支持向量机模型,这个示例中我们使用随机的参数搜索:

1
2
3
4
5
6
7
8
9
from sklearn.model_selection import RandomizedSearchCV
from sklearn.multiclass import OneVsRestClassifier
from sklearn.svm import SVC

params_svc={
'C':np.linspace(0,10,100)
}
rand_search_svc=RandomizedSearchCV(SVC(),params_svc,cv=5,scoring='precision_macro',n_iter=30) #随机搜索时可以传入n_iter参数限制搜索次数
rand_search_svc.fit(train_data,train_label)

备注1:sklearn中的模型分类

下列为经常使用到的监督模型和非监督模型所包含在sklearn的模块:

  • sklearn.cluster:包括一些聚类模型
  • sklearn.discriminant_analysis:包括LDA等模型
  • sklearn.ensemble:包括Adaboost、GBDT、随机森林等融合模型
  • sklearn.linear_model:包括线性回归、逻辑回归等模型
  • sklearn.naive_bayes:包括朴素贝叶斯等模型
  • sklearn.neighbors:包括K近邻等模型
  • sklearn.svm:包括支持向量机模型
  • sklearn.tree:包括决策树模型

备注2:一般来说,每一个监督学习的模型都有一个fit()predict()函数。fit需要传入两个参数,一个代表训练集的特征矩阵,另一个代表训练集数据的标签;predict函数需要传入测试集的特征矩阵,模型会根据之前fit函数学习到的结果进行预测:

***.fit(features, labels)

***.predict(features)

有些分类器可以给出不同的分类所对应的概率,使用***.predict_proba(features)即可

模型评估

在模型训练完成之后,需要将测试集的数据送到模型中,得到预测结果,然后可以使用真实值与预测值进行对比。sklearn.metrics模块提供了一些评价函数,方便我们计算一些评价指标。

对于分类模型,常用的评价函数包括:

  • log_loss (限于输出概率值的分类模型)
  • confusion_matrix
  • precision_score,recall_score,f1_score
  • precision_recall_curve,roc_auc_score,roc_curve
  • accuracy_score

对于回归模型,常用的评价函数有:

  • mean_squared_error
  • mean_absolute_error
  • hinge_loss

这些函数的使用方法比较类似,都需要传入两个参数,一个代表预测值,另一个为真实值。下面我们使用之前训练好的决策树模型和支持向量机模型进行预测,然后再计算它们预测结果的准确率,并查看它们的混淆矩阵:

1
2
pred_svc=grid_search_tree.predict(test_data)
pred_tree=rand_search_svc.predict(test_data)
1
2
3
from sklearn.metrics import accuracy_score
print('The accuracy of decision tree model: ',accuracy_score(pred_tree,test_label))
print('The accuracy of support vector classifier model: ',accuracy_score(pred_svc,test_label))
The accuracy of decision tree model:  0.9
The accuracy of support vector classifier model:  0.8666666666666667

接下来,我们计算它们的混淆矩阵,查看它们在对测试集进行分类时的具体表现:

1
2
from sklearn.metrics import confusion_matrix
confusion_matrix(pred_tree,test_label)
array([[10,  0,  0],
       [ 0, 10,  3],
       [ 0,  0,  7]], dtype=int64)
1
confusion_matrix(pred_svc,test_label)
array([[10,  0,  0],
       [ 0,  9,  3],
       [ 0,  1,  7]], dtype=int64)

需要说明的是,由于机器学习通常需要在许多操作中使用到随机数,比如划分数据集、集成模型的boostrap操作、神经网络模型的初始化参数、等等。因此,模型的预测效果具有一定的随机性。在实际应用中,经常需要使用不同的随机数种子多次重复上述过程,再对这些训练好的模型进行融合,从而获得最终的模型。

模型保存与加载

训练好的模型可以将其保存到本机上,并在下次使用的时候加载:

1
2
3
from sklearn.externals import joblib
joblib.dump(rand_search_svc.best_estimator_,'svc_model.pkl') #保存模型
loaded_model=joblib.load('svc_model.pkl') #加载模型

实际案例

在下面的内容中,我们将使用Kaggle数据竞赛平台上的Titanic数据集作为示例,来演示如何完成一个实际的机器学习项目。由于下面的内容主要是为了演示操作流程,因此在特征处理、模型选择以及模型超参数调整等问题上,我们采取了一些比较简单的办法。在真实项目中,主要流程与下面所介绍的没有区别,但是每个流程中的具体操作将会更复杂一些。

该数据集的下载地址为:https://www.kaggle.com/c/titanic

1
2
3
train_data=pd.read_csv('titanic/train.csv') #训练集数据,包含标签
test_data=pd.read_csv('titanic/test.csv') #测试集数据,不包含标签
test_label=pd.read_csv('titanic/titanic.csv') #测试集数据的实际标签

初步分析

首先,我们需要查看数据所包含的内容,从而对数据的概况做一些了解:

1
train_data
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S
... ... ... ... ... ... ... ... ... ... ... ... ...
886 887 0 2 Montvila, Rev. Juozas male 27.0 0 0 211536 13.0000 NaN S
887 888 1 1 Graham, Miss. Margaret Edith female 19.0 0 0 112053 30.0000 B42 S
888 889 0 3 Johnston, Miss. Catherine Helen "Carrie" female NaN 1 2 W./C. 6607 23.4500 NaN S
889 890 1 1 Behr, Mr. Karl Howell male 26.0 0 0 111369 30.0000 C148 C
890 891 0 3 Dooley, Mr. Patrick male 32.0 0 0 370376 7.7500 NaN Q

891 rows × 12 columns

同时,我们也需要查看训练集与测试集中的每一个特征是否含有缺失值,并根据这些特征中缺失值的个数、该列其他非缺失值的取值等信息,来决定如何处理缺失值。

1
train_data.count()
PassengerId    891
Survived       891
Pclass         891
Name           891
Sex            891
Age            714
SibSp          891
Parch          891
Ticket         891
Fare           891
Cabin          204
Embarked       889
dtype: int64
1
test_data.count()
PassengerId    418
Pclass         418
Name           418
Sex            418
Age            332
SibSp          418
Parch          418
Ticket         418
Fare           417
Cabin           91
Embarked       418
dtype: int64

借助于竞赛主办方提供的原始数据描述,同时在网上搜索了一些此项目做特征工程的方法分享,我们可以得到下面的特征处理初步思路:

  • PassengerId:乘客的ID号码,对于构造模型没有帮助,可以直接删除。
  • Survived:乘客是否存活,1代表存活,0代表未存活。它属于模型要预测的值,因此我们要构造的模型是一个二分类的模型。
  • Pclass:乘客船票对应的席位等级,可以取1、2、3三个离散的整数值。我们需要分析它是否与乘客存活显著相关。
  • Name:乘客的姓名。对于外国人的姓名来说,其中的头衔或者姓氏可能对应于身份地位比较高或是有特殊身份的人物。但是这样的处理手段要求了解一些相关的知识,因此出于简单考虑,在下面的特征处理过程中我们选择将其直接删除。
  • Sex:乘客的性别,有male和female两种。由于机器学习模型无法将字符串作为模型输入直接进行计算,因此需要将这一项转换为数字类型。我们需要分析它是否与乘客存活显著相关。
  • Age:乘客年龄,为一系列的离散值。在训练集与测试集中,这一特征的缺失值占了20%,但是由于这一特征应该属于比较重要的特征,不能直接删除,而是需要考虑如何处理;同时由于对这一特征的具体信息仍不够了解,无法得知后续的处理方法,还需要做一些深入的分析。
  • SibSp:与乘客一同上船的兄弟姐妹/配偶数量,为一系列离散值。我们需要分析它是否与乘客存活显著相关。
  • Parch:与乘客一同上船的父母/子女数量,为一系列离散值。我们需要分析它是否与乘客存活显著相关。
  • Ticket:船票编号,在网上的一些分享中提到了其中有少量的编号是重复的,可能对于预测结果有帮助。不过出于简便,我们选择将其直接删除。
  • Fare:船票价格,为一系列连续值。我们在测试集中看到Fare存在一个缺失值。考虑到缺失值只有一个,比较简单的办法是直接取其它数据的平均值进行填充。我们需要分析它是否与乘客存活显著相关。
  • Cabin:乘客所在的座位号,可以看到它是字母+数字的组合,并存在大量的空值。需要进一步研究以决定如何处理。
  • Embarked:乘客在哪个港口上船,为C、Q、S三个字母中的一个。同Sex类似,需要转换为数字进行处理。但是注意到在训练集中,有两条数据的这一特征是缺失的,测试集中没有缺失这一特征的样本,因此我们可以直接删除训练集中的这两个样本。同时,我们需要分析它是否与乘客存活显著相关。

接下来,我们对上述特征进行进一步的分析,以决定如何对其进行处理。

Pclass:这一特征只有3种取值,因此我们可以直接通过做数据透视表进行分析。可以发现席位等级与乘客是否存活具有很强的相关性,因此需要保留这一特征。

1
pd.crosstab(train_data['Pclass'],train_data['Survived'])
Survived 0 1
Pclass
1 80 136
2 97 87
3 372 119

Sex:同样通过做数据透视表进行分析,我们发现性别与乘客是否存活也具有很强的相关性,因此需要保留这一特征。

1
pd.crosstab(train_data['Sex'],train_data['Survived'])
Survived 0 1
Sex
female 81 233
male 468 109

Embarked:通过做数据透视表进行分析,我们发现这一特征与乘客是否存活也具有较强的相关性,因此需要保留这一特征。

1
pd.crosstab(train_data['Embarked'],train_data['Survived'])
Survived 0 1
Embarked
C 75 93
Q 47 30
S 427 217

Parch:由于这一特征包含的值较多,我们选择作图进行分析。从图中可以看出,Parch的值与生还率有一定的相关性,当Parch的值大于3时生还率较低,小于3时生还率高一些。

1
sns.barplot(x="Parch", y="Survived", data=train_data)


png

SibSp:我们同样选择作图对这一特征进行分析。从图中可以看出,SibSp的值与生还率有一定的相关性,表现出先增高后降低的变化趋势。

1
sns.barplot(x="SibSp", y="Survived", data=train_data)


png

Age:通过查看Age特征的一些统计量,我们发现训练集与测试集上它们的统计学特性差别不大。

1
train_data['Age'].describe()
count    714.000000
mean      29.699118
std       14.526497
min        0.420000
25%       20.125000
50%       28.000000
75%       38.000000
max       80.000000
Name: Age, dtype: float64
1
test_data['Age'].describe()
count    332.000000
mean      30.272590
std       14.181209
min        0.170000
25%       21.000000
50%       27.000000
75%       39.000000
max       76.000000
Name: Age, dtype: float64

我们作图分析年龄与生还之间是否有关,以及它们之间的关系如何:

1
2
3
facet = sns.FacetGrid(train_data, hue="Survived",aspect=2)
facet.map(sns.kdeplot,'Age',shade= True)
facet.set(xlim=(0, train_data['Age'].max()))


png

通过以上分析可知,Age对生还的影响很大,因此我们不能直接拿平均值或者中位数进行填充。可以通过使用其他特征训练一个能够预测Age的模型,让这个模型去预测Age的值。

Fare:我们通过作图分析票价与生还之间的关系,可以发现票价越高的乘客,其生还的概率越高。因此这一特征属于比较重要的特征,需要保留。

1
2
3
facet = sns.FacetGrid(train_data, hue="Survived",aspect=2)
facet.map(sns.kdeplot,'Fare',shade= True)
facet.set(xlim=(0, train_data['Fare'].max()))

png

Cabin:对于Cabin特征,我们先查看NaN值与非NaN值对于生还是否有影响,为此我们可以做一个交叉表来查看。交叉表的内容明显说明了Cabin值是否为NaN对于生还率有着巨大的影响,因此不能直接删除。

1
pd.crosstab(train_data['Cabin'].isnull(),train_data['Survived'])
Survived 0 1
Cabin
False 68 136
True 481 206

由于Cabin特征为字母+数字的组合,我们推测字母应该对应船舱号,而数字对应座位号。我们去掉数字,查看字母部分与最终的生还是否有关。为此,我们需要做另外一个交叉表。从交叉表的内容可以看出,在训练集中不同的船舱号所对应的生还率有着比较明显的区别。

1
2
3
4
5
6
7
def remove_num(x):
if x is np.NaN:
return 'N'
else:
return x[0]

pd.crosstab(train_data['Survived'],train_data['Cabin'].map(remove_num))
Cabin A B C D E F G N T
Survived
0 8 12 24 8 8 5 2 481 1
1 7 35 35 25 24 8 2 206 0

考虑到船舱号的类别较多,如果使用树模型的话,不需要将船舱号进行独热编码,可以将船舱号中的字母部分取出,作为一个类别;但是如果不使用树模型的话,由于需要转换为独热编码,这样做则会导致维度过高,因此我们只考虑船舱号部分是否为NaN。

数据处理

通过上述的分析,我们得到了数据处理的基本思路。首先我们需要处理的是年龄的缺失值填充问题,我们推测与年龄关系比较大的是Pclass、SibSp、Parch、Fare这四个特征,因此我们考虑使用这四个特征训练一个随机森林模型,用于对缺失值的预测。步骤如下:

1
2
3
train_data_age=train_data.dropna(subset=['Age'])
Ages=train_data_age['Age']
train_data_age=train_data_age[['Pclass','SibSp','Parch','Fare']]
1
2
3
4
5
6
7
8
9
10
11
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV
param_tree_reg={
'max_depth':[4,5,6],
'min_samples_split':[5,10,20,40],
'max_leaf_nodes':[10,15,20,30],
'min_samples_leaf':[5,10,20]
}

rf_reg_ages=GridSearchCV(RandomForestRegressor(n_estimators=200,max_samples=500),param_tree_reg)
rf_reg_ages.fit(train_data_age,Ages)
GridSearchCV(estimator=RandomForestRegressor(max_samples=500, n_estimators=200),
             param_grid={'max_depth': [4, 5, 6],
                         'max_leaf_nodes': [10, 15, 20, 30],
                         'min_samples_leaf': [5, 10, 20],
                         'min_samples_split': [5, 10, 20, 40]})

接下来,我们准备分别训练以决策树和逻辑回归作为基础模型的两种bagging集成模型(即每个模型独立,模型最终输出取决于它们的平均输出),为此需要实现两种不同的处理方法。为了方便复用,将其定义为函数的形式:

1
2
3
4
5
6
7
8
9
10
from functools import partial
def fill_age(data,model):
ages=data['Age']
data_nullage=data[['Pclass','SibSp','Parch','Fare']]
data_nullage=data_nullage[ages.isnull()]
pred_ages=model.predict(data_nullage)
ages[ages.isnull()]=pred_ages
return ages

fill_age_rf=partial(fill_age,model=rf_reg_ages)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from sklearn.preprocessing import LabelEncoder
from sklearn.impute import SimpleImputer

def random_forest_dataprocess(data,fill_age_func,is_training_data=True):
data=data.drop(['PassengerId','Name','Ticket'],axis=1)
data['Sex']=LabelEncoder().fit_transform(data['Sex'])
data['Fare']=data['Fare'].fillna(data['Fare'].mean())
data['Cabin']=LabelEncoder().fit_transform(data['Cabin'].map(remove_num))
data=data.dropna(subset=['Embarked'])
data['Embarked']=LabelEncoder().fit_transform(data['Embarked'])
data['Age']=fill_age_func(data)
if is_training_data:
label=data['Survived']
data=data.drop('Survived',axis=1)
return data,label
else:
return data

# 在逻辑回归中,注意要将特征的取值规约到相近的数量级上!
def lr_dataprocess(data,fill_age_func,is_training_data=True):
data=data.drop(['PassengerId','Name','Ticket'],axis=1)
data['Cabin']=data['Cabin'].isnull()
data['Fare']=data['Fare'].fillna(data['Fare'].mean())
data['Age']=fill_age_func(data)
data['Fare']=data['Fare']/100
data['Age']=data['Age']/100
data['SibSp']=data['SibSp']/10
data['Parch']=data['Parch']/10
data=data.dropna(subset=['Embarked'])
if is_training_data:
label=data['Survived']
data=data.drop('Survived',axis=1)
data=pd.get_dummies(data)
data=pd.get_dummies(data,columns=['Pclass','Cabin'])
if is_training_data:
return data,label
else:
return data
1
2
3
4
train_data_tree,train_label=random_forest_dataprocess(train_data,fill_age_rf)
test_data_tree=random_forest_dataprocess(test_data,fill_age_rf,is_training_data=False)
train_data_lr,train_label=lr_dataprocess(train_data,fill_age_rf)
test_data_lr=lr_dataprocess(test_data,fill_age_rf,is_training_data=False)

在做完基本的数据预处理之后,用于随机森林模型的特征有8个,用于逻辑回归模型的特征有14个。用于随机森林模型的特征数量适中,但是用于逻辑回归特征的数量有些多,需要对其进行筛选。我们使用相关系数筛选的方法,筛选出相关系数排名前10位的特征:

1
2
3
4
from sklearn.feature_selection import SelectKBest,SelectPercentile
kbest_lr=SelectKBest(lambda x,y:np.abs(np.corrcoef(x,y,rowvar=False)[:-1,-1]),k=10)
train_data_lr=kbest_lr.fit_transform(train_data_lr,train_label)
test_data_lr=kbest_lr.transform(test_data_lr)

模型训练与评估

接下来,便可以构造模型进行训练。在训练过程中,我们使用网格搜索的方法来寻找最佳参数。由于网格搜索比较耗费时间,因此我们使用两种基础模型进行超参数优化,并基于搜索结果来构造集成模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV
param_tree_clf={
'max_depth':[4,5,6],
'min_samples_split':[5,10,20,40],
'max_leaf_nodes':[10,15,20,30],
'min_samples_leaf':[5,10,20]
}
grid_search_tree=GridSearchCV(DecisionTreeClassifier(criterion='entropy'),param_tree_clf)
grid_search_tree.fit(train_data_tree,train_label)
rf_clf=RandomForestClassifier(criterion='entropy',n_estimators=5000,max_samples=500,**grid_search_tree.best_params_)
rf_clf.fit(train_data_tree,train_label)
pred_rf=rf_clf.predict(test_data_tree)
1
2
3
4
5
6
7
8
9
10
11
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import BaggingClassifier
from sklearn.model_selection import GridSearchCV
param_lr_clf={
'C':np.logspace(0,10,11,base=2)/100
}
grid_search_lr=GridSearchCV(LogisticRegression(),param_lr_clf)
grid_search_lr.fit(train_data_lr,train_label)
lr_bag_clf=BaggingClassifier(LogisticRegression(**grid_search_lr.best_params_),n_estimators=5000,max_samples=500)
lr_bag_clf.fit(train_data_lr,train_label)
pred_lr=lr_bag_clf.predict(test_data_lr)

训练完成之后,接下来使用一些评价标准查看模型的预测结果:

1
2
3
4
5
from sklearn.metrics import confusion_matrix
print('Confusion matrix of random forest classifier:')
print(confusion_matrix(pred_rf,test_label['Survived']))
print('Confusion matrix of logistic regression:')
print(confusion_matrix(pred_lr,test_label['Survived']))
Confusion matrix of random forest classifier:
[[237  45]
 [ 17 119]]
Confusion matrix of logistic regression:
[[244  76]
 [ 10  88]]
1
2
3
4
5
from sklearn.metrics import accuracy_score
print('Accuracy of random forest classifier:')
print(accuracy_score(pred_rf,test_label['Survived']))
print('Accuracy of logistic regression:')
print(accuracy_score(pred_lr,test_label['Survived']))
Accuracy of random forest classifier:
0.8516746411483254
Accuracy of logistic regression:
0.7942583732057417

参考

  1. https://www.cnblogs.com/wxquare/p/5484636.html
  2. https://www.zhihu.com/question/29316149
  3. https://www.cnblogs.com/nolonely/p/7007432.html
  4. https://zhuanlan.zhihu.com/p/31743196
  5. https://www.jianshu.com/p/e79a8c41cb1a