@77qingliu
2018-04-20T09:09:00.000000Z
字数 4922
阅读 2595
machine-learning python
决策树是一种常用的分类和回归机器学习方法,是最经常使用的机器学习算法之一。这里只讨论分类决策树。
决策树模型呈树形结构,在分类问题中,表示基于特征对实例分类的过程。它可以认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。
决策树学习通常包括3个步骤:特征选择、决策树的生成和剪枝。
这里以机器学习-周志华书中西瓜书数据集为例子。在挑选西瓜的时候,我们要面对“这是好瓜吗?”这样的问题。进行决策时,通常会进行一系列的判断或“子决策”:我们先看“它是什么颜色?”,如果是“青绿色”,则我们再看“它的根蒂是什么形态?”,如果是“卷缩”,我们再判断“它敲起来是什么声音?”,最后,我们得出最终决策:这是一个好瓜。这个决策过程如图所示:

如何构造一个决策树?这里采用递归的方法,伪代码如下:
def createBranch():'''此处运用了迭代的思想。 感兴趣可以搜索 迭代 recursion, 甚至是 dynamic programing。'''检测数据集中的所有数据的分类标签是否相同:If so return 类标签Else:寻找划分数据集的最好特征(划分之后信息熵最小,也就是信息增益最大的特征)划分数据集创建分支节点for 每个划分的子集调用函数 createBranch (创建分支的函数)并增加返回结果到分支节点中return 分支节点
决策树学习的关键是根据属性划分子集,也就是如何最优化生成子树。那么怎么划分子树最好呢?一般而言我们希望随着划分的不断进行,我们希望决策树的分支节点所包含的样本尽可能属于同一类,即节点的“纯度”越来越高。
那么如何判断节点的纯度呢?这里使用信息论的算法。
信息熵
信息熵是度量样本集合纯度的最常用指标。假定样本集合D中第k类样本所占的比例为,则信息熵D的定义为
信息增益
假设离散属性a有V个可能的取值(),若使用a来对样本集D进行划分,则会产生V个分支节点,其中第v个分支节点包含了D中所在的属性a上取值为的样本,记为。我们可根据信息熵的定义,计算出的信息熵,再考虑到不同分支节点所包含的样本量不同,给每个分支节点赋予权重,即样本数越多的分支结点的影响越大,于是可计算出用属性a对样本集进行划分所获得的“信息增益”
还有其他如增益率、基尼系数等等算法,这里就不一一介绍了。
收集数据:可以使用任何方法。
准备数据:树构造算法
分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。
训练算法:构造树的数据结构。
测试算法:使用训练好的树计算错误率。
使用算法:此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。
这里使用周志华-机器学习里面的西瓜数据集-使用决策树方法判断西瓜的好坏。
具体的数据集如下:
| 编号 | 色泽 | 根蒂 | 敲声 | 纹理 | 脐部 | 触感 | 密度 | 含糖率 | 好瓜 |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 青绿 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | 0.697 | 0.46 | 是 |
| 2 | 乌黑 | 蜷缩 | 沉闷 | 清晰 | 凹陷 | 硬滑 | 0.774 | 0.376 | 是 |
| 3 | 乌黑 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | 0.634 | 0.264 | 是 |
| 4 | 青绿 | 蜷缩 | 沉闷 | 清晰 | 凹陷 | 硬滑 | 0.608 | 0.318 | 是 |
| 5 | 浅白 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | 0.556 | 0.215 | 是 |
| 6 | 青绿 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 软粘 | 0.403 | 0.237 | 是 |
| 7 | 乌黑 | 稍蜷 | 浊响 | 稍糊 | 稍凹 | 软粘 | 0.481 | 0.149 | 是 |
| 8 | 乌黑 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 硬滑 | 0.437 | 0.211 | 是 |
| 9 | 乌黑 | 稍蜷 | 沉闷 | 稍糊 | 稍凹 | 硬滑 | 0.666 | 0.091 | 否 |
| 10 | 青绿 | 硬挺 | 清脆 | 清晰 | 平坦 | 软粘 | 0.243 | 0.267 | 否 |
| 11 | 浅白 | 硬挺 | 清脆 | 模糊 | 平坦 | 硬滑 | 0.245 | 0.057 | 否 |
| 12 | 浅白 | 蜷缩 | 浊响 | 模糊 | 平坦 | 软粘 | 0.343 | 0.099 | 否 |
| 13 | 青绿 | 稍蜷 | 浊响 | 稍糊 | 凹陷 | 硬滑 | 0.639 | 0.161 | 否 |
| 14 | 浅白 | 稍蜷 | 沉闷 | 稍糊 | 凹陷 | 硬滑 | 0.657 | 0.198 | 否 |
| 15 | 乌黑 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 软粘 | 0.36 | 0.37 | 否 |
| 16 | 浅白 | 蜷缩 | 浊响 | 模糊 | 平坦 | 硬滑 | 0.593 | 0.042 | 否 |
| 17 | 青绿 | 蜷缩 | 沉闷 | 稍糊 | 稍凹 | 硬滑 | 0.719 | 0.103 | 否 |
根据色泽、根蒂、敲声、纹理、脐部、触感、密度、含糖率这些特征判断西瓜是好瓜还是坏瓜。
将数据读入,存入pandas dataframe中
def createDataSet():''' 数据读入 '''rawData = StringIO("""编号,色泽,根蒂,敲声,纹理,脐部,触感,密度,含糖率,好瓜1,青绿,蜷缩,浊响,清晰,凹陷,硬滑,0.697,0.46,是2,乌黑,蜷缩,沉闷,清晰,凹陷,硬滑,0.774,0.376,是3,乌黑,蜷缩,浊响,清晰,凹陷,硬滑,0.634,0.264,是4,青绿,蜷缩,沉闷,清晰,凹陷,硬滑,0.608,0.318,是5,浅白,蜷缩,浊响,清晰,凹陷,硬滑,0.556,0.215,是6,青绿,稍蜷,浊响,清晰,稍凹,软粘,0.403,0.237,是7,乌黑,稍蜷,浊响,稍糊,稍凹,软粘,0.481,0.149,是8,乌黑,稍蜷,浊响,清晰,稍凹,硬滑,0.437,0.211,是9,乌黑,稍蜷,沉闷,稍糊,稍凹,硬滑,0.666,0.091,否10,青绿,硬挺,清脆,清晰,平坦,软粘,0.243,0.267,否11,浅白,硬挺,清脆,模糊,平坦,硬滑,0.245,0.057,否12,浅白,蜷缩,浊响,模糊,平坦,软粘,0.343,0.099,否13,青绿,稍蜷,浊响,稍糊,凹陷,硬滑,0.639,0.161,否14,浅白,稍蜷,沉闷,稍糊,凹陷,硬滑,0.657,0.198,否15,乌黑,稍蜷,浊响,清晰,稍凹,软粘,0.36,0.37,否16,浅白,蜷缩,浊响,模糊,平坦,硬滑,0.593,0.042,否17,青绿,蜷缩,沉闷,稍糊,稍凹,硬滑,0.719,0.103,否""")df = pd.read_csv(rawData, sep=",")return df
类别变量重编码
对于类别变量(Categorical Variable),例如:男和女、high和low等,这种字符串变量一般是不能直接输入到算法模型中的,需要重编码为数字1,2,3,4等或者是二进制bitmap,否则会报错ValueError: could not convert string to float。
sklearn库里面提供了LabelEncoder 和 OneHotEncoder两种重编码方法。
from sklearn.preprocessing import LabelEncoderle = LabelEncoder()le.fit_transform(['男','女'])>> array([1, 0], dtype=int64)
from sklearn import preprocessing# 需要先将字符转成数值features = ['男', '女']enc = preprocessing.LabelEncoder()features = enc.fit_transform(features)# 再将数值特征OneHotEncoderfeatures = features.reshape(-1, 1) # Needs to be the correct shapeohe = preprocessing.OneHotEncoder(sparse=False) #Easier to readohe.fit_transform(features)>> array([[ 0., 1.],[ 1., 0.]])
通俗的讲,分类变量如性别:男,女,LabelEncoder将它直接变成数值变量0,1。而oneHotEncoder将变量扩维,变成对应的哑变量,男变成[0, 1],女变成[1, 0]。想要了解更多见这篇文章CNBLOGS。
树模型直接用LabelEncoder就可以。
class MultiColumnLabelEncoder:def __init__(self,columns = None):self.columns = columns # array of column names to encodedef fit(self,X,y=None):return self # not relevant heredef transform(self,X):'''Transforms columns of X specified in self.columns usingLabelEncoder(). If no columns specified, transforms allcolumns in X.'''output = X.copy()if self.columns is not None:for col in self.columns:output[col] = LabelEncoder().fit_transform(output[col])else:for colname,col in output.iteritems():output[colname] = LabelEncoder().fit_transform(col)return outputdef fit_transform(self,X,y=None):return self.fit(X,y).transform(X)
有些细节问题需要特别注意,详见关于一些重编码的坑
这里使用信息增益作为划分标准,对决策树进行训练
参考链接: DecisionTreeClassifier
def predict_train(x_train, y_train):''''''clf = tree.DecisionTreeClassifier(criterion='entropy')clf.fit(x_train, y_train)''' 系数反映每个特征的影响力。越大表示该特征在分类中起到的作用越大 '''print('feature_importances_: %s' % clf.feature_importances_)return clf
我们需要借助graphviz这个软件来作图,需要提前安装这个软件,并且将dot这个可执行文件加入系统环境变量中:
def visualize_tree(tree, feature_names):"""Create tree png using graphviz.Args----tree -- scikit-learn DecsisionTree.feature_names -- list of feature names."""with open("dt.dot", 'w', encoding='utf-8') as f:export_graphviz(tree, out_file=f,feature_names=feature_names)command = ["dot", "-Tpng", "dt.dot", "-o", "dt.png"]subprocess.check_call(command)
最终得到的是以下图形

注意!使用中文会出现乱码问题。这里需要将
with open("dt.dot", 'w', encoding='utf-8')这里面生成的dt.dot文件重新打开,并且在里面加入node [shape=box fontname="FangSong"] ;,然后将文件另存为不带BOM的UTF-8编码文件,最后在命令行里面手动通过dot -Tpng dt.dot -o dt.png手动生成最后的图片。
直接调用sklearn的包真是方便!以后有机会再手写代码吧,最近忙着找工作,先糊弄过去再说:)。
文章的代码都在machine_learning_in_action这个仓库里面。
这里遇到的坑主要是重编码以及乱码的问题,如果遇到同样的问题请查看前面的链接。