前言
大家可以先看看《机器学习理论基础》这个系列,就会发现自己编写一个学习算法还是挺麻烦的,特别是稍微复杂一点的,在某些文章中我也提到过sklearn,这个库我个人觉得是机器学习必备,使用简单但功能强大,堪称神库。sklearn全称是scikit-learn,网址在https://scikit-learn.org/stable/index.html。这个系列就结合一些实例,介绍一下这个库的用法。
Sklearn的基本建模流程
Sklearn对所有模型的建模流程都是通用的,这是它非常了不起的地方,总共只有三步。
1.实例化,建立评估模型对象;
2.通过模型接口训练模型;
3.通过模型结果提取需要的信息。
相应的,第一步,需要了解实例化时需要使用的参数。第二第三步需要了解数据属性和数据接口。
下面就是示例,其实也就是三步而已。
from sklearn import tree #导入需要的模块
clf = tree.DecisionTreeClassifier() #实例化
clf = clf.fit(X_train,y_train) #用训练集数据训练模型
result = clf.score(X_test,y_test) #导入测试集,从接口中调用需要的信息(正确率)或者predict
先导知识
如果大家不了解决策树的话,可以看看这篇文章。机器学习理论基础(5)-决策树
决策树模型参数
Sklearn中决策树的类都在”tree“这个模块下,决策树有分类树(tree.DecisionTreeClassifier)也有回归树(tree.DecisionTreeRegressor)。这里只讲分类树。
class sklearn.tree.DecisionTreeClassifier(criterion='gini', splitter='best',
max_depth=None, min_samples_split=2, min_samples_leaf=1,
min_weight_fraction_leaf=0.0, max_features=None, random_state=None,
max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None,
class_weight=None, presort='deprecated', ccp_alpha=0.0)
习惯就好,sklearn的类参数都是非常多。需要了解的参数我们只讲8个。
criterion
两个参数,其实就是ID3和CART算法的区别。
- 输入”entropy“,使用信息熵(Entropy)
- 输入”gini“,使用基尼系数(Gini Impurity)
random_state & splitter
决策树在建树时,是靠优化节点来追求一棵优化的树,但最优的节点能够保证最优的树吗?集成算法被用来解决这个问题:既然一棵树不能保证最优,那就建更多的不同的树,然后从中取最好的。怎样从一组数据集中建不同的树?在每次分枝时,不从使用全部特征,而是随机选取一部分特征,从中选取不纯度相关指标最优的作为分枝用的节点。这样,每次生成的树也就不同了。
random_state 用来设置分枝中的随机模式的参数,默认None。也可以输入任意整数作为随机数生成种子。设置的话,是为了保证每次随机生成的数是一致的(即使是随机的);如果不设置,那么每次生成的随机数都是不同的,
也就是说决策树其实是随机的(可以通过多运行几次score测试下)。
splitter也是用来控制决策树中的随机选项的,有两种输入值,输入”best",决策树在分枝时虽然随机,但是还是会优先选择更重要的特征进行分枝(重要性可以通过属性feature_importances_查看)
#特征重要性
clf.feature_importances_
[*zip(feature_name,clf.feature_importances_)]
输入“random",决策树在分枝时会更加随机,树会因为含有更多的不必要信息而更深更大,并因这些不必要信息而降低对训练集的拟合。这也是防止过拟合的一种方式。当你预测到你的模型会过拟合,用这两个参数来帮助你降低树建成之后过拟合的可能性。当然,树一旦建成,我们依然是使用剪枝参数来防止过拟合。
剪枝参数
为了让决策树有更好的泛化性,我们要对决策树进行剪枝。剪枝策略对决策树的影响巨大,正确的剪枝策略是优化决策树算法的核心。sklearn为我们提供了不同的剪枝策略:
max_depth
限制树的最大深度,超过设定深度的树枝全部剪掉。
这是用得最广泛的剪枝参数,在高维度低样本量时非常有效。决策树多生长一层,对样本量的需求会增加一倍,所以限制树深度能够有效地限制过拟合。实际使用时,建议从=3开始尝试,看看拟合的效果再决定是否增加设定深度。
min_samples_leaf & min_samples_split
min_samples_leaf限定,一个节点在分枝后的每个子节点都必须包含至少min_samples_leaf个训练样本,否则分枝就不会发生。一般来说,建议从=5开始使用。如果叶节点中含有的样本量变化很 大,建议输入浮点数作为样本量的百分比来使用。
min_samples_split限定,一个节点必须要包含至少min_samples_split个训练样本,这个节点才允许被分枝,否则分枝就不会发生。
这两个参数就是限定叶子和节点的训练样本个数,如果数目太少,就没必要分支,防止过拟合。
max_features & min_impurity_decrease
一般配合max_depth使用,用作树的”精修“。
max_features限制分枝时考虑的特征个数,超过限制个数的特征都会被舍弃。max_features是用来限制高维度数据的过拟合的剪枝参数,但其方法比较暴力,是直接限制可以使用的特征数量而强行使决策树停下的参数,在不知道决策树中的各个特征的重要性的情况下,强行设定这个参数可能会导致模型学习不足。如果希望通过降维的方式防止过拟合,建议使用PCA,ICA或者特征选择模块中的降维算法。 min_impurity_decrease限制信息增益的大小,信息增益小于设定数值的分枝不会发生。
目标权重参数
class_weight & min_weight_fraction_leaf
完成样本标签平衡的参数。样本不平衡是指在一组数据集中,标签的一类天生占有很大的比例。因此我们要使用class_weight参数对样本标签进行一定的均衡,给少量的标签更多的权重,让模型更偏向少数类,向捕获少数类的方向建模。该参数默认None,此模式表示自动给与数据集中的所有标签相同的权重。
有了权重之后,样本量就不再是单纯地记录数目,而是受输入的权重影响了,因此这时候剪枝,就需要搭配min_weight_fraction_leaf这个基于权重的剪枝参数来使用。
重要属性和接口
属性是在模型训练之后,能够调用查看的模型的各种性质。对决策树来说,最重要的是feature_importances_,能够查看各个特征对模型的重要性。 sklearn中许多算法的接口都是相似的,比如说我们之前已经用到的fit和score,几乎对每个算法都可以使用。除了这两个接口之外,决策树最常用的接口还有
apply和predict。apply中输入测试集返回每个测试样本所在的叶子节点的索引,predict输入测试集返回每个测试样本的标签。
#apply返回每个测试样本所在的叶子节点的索引
clf.apply(Xtest)
#predict返回每个测试样本的分类/回归结果
clf.predict(Xtest)
在这里不得不提的是,所有接口中要求输入X_train和X_test的部分,
输入的特征矩阵必须至少是一个二维矩阵。sklearn不接受任何一维矩阵作为特征矩阵被输入。
实例:泰坦尼克号幸存者预测
数据集来自: https://www.kaggle.com/c/titanic。
如何探索数据
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
data = pd.read_csv(r"./data.csv",index_col = 0)
探索数据常用命令:head 其实就是简单看看前几行的数据,看看表格到底长什么样,直接用data的话就是展示的太多了。
data.head()
探索数据常用命令:info data.info()
看到输出为:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 891 entries, 1 to 891
Data columns (total 11 columns):
Survived 891 non-null int64
Pclass 891 non-null int64
Name 891 non-null object #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(4), object(5)
memory usage: 83.5+ KB
数据预处理
筛选特征
我们预测的是存活的几率,那么Name就不重要了。其次,Cabin并没有告诉我们有用的信息,比如头等舱还是经济舱,而且缺失值实在太多了,这里就去掉。最后,Ticket同样可以删掉。
data.drop(["Cabin","Name","Ticket"],inplace=True,axis=1)
data.head()
处理缺失值
年龄和是否幸存有较强的相关性,所以缺失值我们需要填补,这里使用平均年龄填补。顺便把有缺失值的行删除。
data["Age"] = data["Age"].fillna(data["Age"].mean())
data = data.dropna()
处理非数字
#将二分类变量转换为数值型变量
data["Sex"] = (data["Sex"]== "male").astype("int")
#将三分类变量转换为数值型变量
labels = data["Embarked"].unique().tolist()
data["Embarked"] = data["Embarked"].apply(lambda x: labels.index(x))
当然这样直接的转成0,1,2是有前提的:就是特征的不同取值是没有联系的。比如说有一列是“教育水平”,分为“初中,高中,大学”,就不能单纯的换成0,1,2,因为不同学历之间的关系,没办法通过这种方式表达出来。大家要深入了解的话,可搜索“one-hot”编码。
拆分数据
先把数据和标签拆分,然后按经典的三七分,把数据拆分。由于是随机抽取的,所以索引是乱的。
X = data.iloc[:,data.columns != "Survived"]
y = data.iloc[:,data.columns == "Survived"]
from sklearn.model_selection import train_test_split
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y,test_size=0.3)
Xtrain.head(10)
修正测试集和训练集的索引。
for i in [Xtrain, Xtest, Ytrain, Ytest]:
i.index = range(i.shape[0])
训练模型
按照基本流程,三部曲,训练模型。得到分数后,再做十次交叉验证,看看模型的稳定性。
clf = DecisionTreeClassifier(random_state=25)
clf = clf.fit(Xtrain, Ytrain)
score_ = clf.score(Xtest, Ytest)
print(score_)
#进行十次交叉验证
score = cross_val_score(clf,X,y,cv=10).mean()
print(score)
结果一般般:
0.7865168539325843
0.7739274770173645
调整参数
先从max_depth开始调,为了看看到底是过拟合还是欠拟合,最好还是把训练集和测试集的表现都比较一下。
tr = []
te = []
for i in range(10):
clf = DecisionTreeClassifier(random_state=25
,max_depth=i+1
,criterion="entropy"
)
clf = clf.fit(Xtrain, Ytrain)
score_tr = clf.score(Xtrain,Ytrain)
score_te = cross_val_score(clf,X,y,cv=10).mean()
tr.append(score_tr)
te.append(score_te)
print(max(te))
plt.plot(range(1,11),tr,color="red",label="train")
plt.plot(range(1,11),te,color="blue",label="test")
plt.xticks(range(1,11))
plt.legend()
plt.show()
输出:
0.8177860061287026
结果还是只有81%,可以看到是有过拟合的倾向,我们还得继续调整参数。
网格搜索
网格搜索是帮助我们同时调整多个参数的技术,本质是枚举各个参数,一个一个尝试,看看哪个效果最好。我们先会给网格一个字典,对应的是调整的参数和调整参数的范围。所以网格搜索的时间是很长的,大家不要随便设置参数范围。
import numpy as np
parameters = {'splitter':('best','random')
,'criterion':("gini","entropy")
,"max_depth":[*range(1,10)]
,'min_samples_leaf':[*range(1,50,5)]
,'min_impurity_decrease':[*np.linspace(0,0.5,20)]
}
clf = DecisionTreeClassifier(random_state=25)
#交叉验证的次数依然是10次
GS = GridSearchCV(clf, parameters, cv=10)
GS.fit(Xtrain,Ytrain)
print(GS.best_params_)
print(GS.best_score_)
输出为:
{'criterion': 'gini',
'max_depth': 5,
'min_impurity_decrease': 0.0,
'min_samples_leaf': 1,
'splitter': 'random'}
0.8344051446945338
相比之前的分数提高了,可见网格产生了效果,可以找到较好的参数。为什么不是最好的参数?那是因为网格搜索也是有问题的,比如说它有四个参数,但用了其中三个参数,就能达到一个很好的效果,网格也不能舍弃最后一个参数,必须要使用完。所以有时候使用网格得到更差的分数,也是很正常的。
这里我们就简单学习了决策树的重要参数和实例,大家要深入了解还需多写多练。