[关闭]
@77qingliu 2018-04-20T09:09:00.000000Z 字数 4922 阅读 2844

machine-learning python

决策树模型


决策树是一种常用的分类和回归机器学习方法,是最经常使用的机器学习算法之一。这里只讨论分类决策树。
决策树模型呈树形结构,在分类问题中,表示基于特征对实例分类的过程。它可以认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。
决策树学习通常包括3个步骤:特征选择、决策树的生成和剪枝。

决策树场景

这里以机器学习-周志华书中西瓜书数据集为例子。在挑选西瓜的时候,我们要面对“这是好瓜吗?”这样的问题。进行决策时,通常会进行一系列的判断或“子决策”:我们先看“它是什么颜色?”,如果是“青绿色”,则我们再看“它的根蒂是什么形态?”,如果是“卷缩”,我们再判断“它敲起来是什么声音?”,最后,我们得出最终决策:这是一个好瓜。这个决策过程如图所示:

决策树原理

决策树生成算法

如何构造一个决策树?这里采用递归的方法,伪代码如下:

  1. def createBranch():
  2. '''
  3. 此处运用了迭代的思想。 感兴趣可以搜索 迭代 recursion, 甚至是 dynamic programing。
  4. '''
  5. 检测数据集中的所有数据的分类标签是否相同:
  6. If so return 类标签
  7. Else:
  8. 寻找划分数据集的最好特征(划分之后信息熵最小,也就是信息增益最大的特征)
  9. 划分数据集
  10. 创建分支节点
  11. for 每个划分的子集
  12. 调用函数 createBranch (创建分支的函数)并增加返回结果到分支节点中
  13. return 分支节点

属性划分选择

决策树学习的关键是根据属性划分子集,也就是如何最优化生成子树。那么怎么划分子树最好呢?一般而言我们希望随着划分的不断进行,我们希望决策树的分支节点所包含的样本尽可能属于同一类,即节点的“纯度”越来越高。
那么如何判断节点的纯度呢?这里使用信息论的算法。

还有其他如增益率、基尼系数等等算法,这里就不一一介绍了。

决策树开发流程


收集数据:可以使用任何方法。
准备数据:树构造算法
分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。
训练算法:构造树的数据结构。
测试算法:使用训练好的树计算错误率。
使用算法:此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。

决策树项目案例

这里使用周志华-机器学习里面的西瓜数据集-使用决策树方法判断西瓜的好坏。
具体的数据集如下:

编号 色泽 根蒂 敲声 纹理 脐部 触感 密度 含糖率 好瓜
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中

  1. def createDataSet():
  2. ''' 数据读入 '''
  3. rawData = StringIO(
  4. """编号,色泽,根蒂,敲声,纹理,脐部,触感,密度,含糖率,好瓜
  5. 1,青绿,蜷缩,浊响,清晰,凹陷,硬滑,0.697,0.46,是
  6. 2,乌黑,蜷缩,沉闷,清晰,凹陷,硬滑,0.774,0.376,是
  7. 3,乌黑,蜷缩,浊响,清晰,凹陷,硬滑,0.634,0.264,是
  8. 4,青绿,蜷缩,沉闷,清晰,凹陷,硬滑,0.608,0.318,是
  9. 5,浅白,蜷缩,浊响,清晰,凹陷,硬滑,0.556,0.215,是
  10. 6,青绿,稍蜷,浊响,清晰,稍凹,软粘,0.403,0.237,是
  11. 7,乌黑,稍蜷,浊响,稍糊,稍凹,软粘,0.481,0.149,是
  12. 8,乌黑,稍蜷,浊响,清晰,稍凹,硬滑,0.437,0.211,是
  13. 9,乌黑,稍蜷,沉闷,稍糊,稍凹,硬滑,0.666,0.091,否
  14. 10,青绿,硬挺,清脆,清晰,平坦,软粘,0.243,0.267,否
  15. 11,浅白,硬挺,清脆,模糊,平坦,硬滑,0.245,0.057,否
  16. 12,浅白,蜷缩,浊响,模糊,平坦,软粘,0.343,0.099,否
  17. 13,青绿,稍蜷,浊响,稍糊,凹陷,硬滑,0.639,0.161,否
  18. 14,浅白,稍蜷,沉闷,稍糊,凹陷,硬滑,0.657,0.198,否
  19. 15,乌黑,稍蜷,浊响,清晰,稍凹,软粘,0.36,0.37,否
  20. 16,浅白,蜷缩,浊响,模糊,平坦,硬滑,0.593,0.042,否
  21. 17,青绿,蜷缩,沉闷,稍糊,稍凹,硬滑,0.719,0.103,否
  22. """)
  23. df = pd.read_csv(rawData, sep=",")
  24. return df

特征工程

类别变量重编码

对于类别变量(Categorical Variable),例如:男和女、high和low等,这种字符串变量一般是不能直接输入到算法模型中的,需要重编码为数字1,2,3,4等或者是二进制bitmap,否则会报错ValueError: could not convert string to float。

sklearn库里面提供了LabelEncoder 和 OneHotEncoder两种重编码方法。

  1. from sklearn.preprocessing import LabelEncoder
  2. le = LabelEncoder()
  3. le.fit_transform(['男','女'])
  4. >> array([1, 0], dtype=int64)
  1. from sklearn import preprocessing
  2. # 需要先将字符转成数值
  3. features = ['男', '女']
  4. enc = preprocessing.LabelEncoder()
  5. features = enc.fit_transform(features)
  6. # 再将数值特征OneHotEncoder
  7. features = features.reshape(-1, 1) # Needs to be the correct shape
  8. ohe = preprocessing.OneHotEncoder(sparse=False) #Easier to read
  9. ohe.fit_transform(features)
  10. >> array([[ 0., 1.],[ 1., 0.]])

通俗的讲,分类变量如性别:男,女,LabelEncoder将它直接变成数值变量0,1。而oneHotEncoder将变量扩维,变成对应的哑变量,男变成[0, 1],女变成[1, 0]。想要了解更多见这篇文章CNBLOGS。

树模型直接用LabelEncoder就可以。

  1. class MultiColumnLabelEncoder:
  2. def __init__(self,columns = None):
  3. self.columns = columns # array of column names to encode
  4. def fit(self,X,y=None):
  5. return self # not relevant here
  6. def transform(self,X):
  7. '''
  8. Transforms columns of X specified in self.columns using
  9. LabelEncoder(). If no columns specified, transforms all
  10. columns in X.
  11. '''
  12. output = X.copy()
  13. if self.columns is not None:
  14. for col in self.columns:
  15. output[col] = LabelEncoder().fit_transform(output[col])
  16. else:
  17. for colname,col in output.iteritems():
  18. output[colname] = LabelEncoder().fit_transform(col)
  19. return output
  20. def fit_transform(self,X,y=None):
  21. return self.fit(X,y).transform(X)

有些细节问题需要特别注意,详见关于一些重编码的坑

训练模型

这里使用信息增益作为划分标准,对决策树进行训练
参考链接: DecisionTreeClassifier

  1. def predict_train(x_train, y_train):
  2. '''
  3. '''
  4. clf = tree.DecisionTreeClassifier(criterion='entropy')
  5. clf.fit(x_train, y_train)
  6. ''' 系数反映每个特征的影响力。越大表示该特征在分类中起到的作用越大 '''
  7. print('feature_importances_: %s' % clf.feature_importances_)
  8. return clf

可视化

我们需要借助graphviz这个软件来作图,需要提前安装这个软件,并且将dot这个可执行文件加入系统环境变量中:

  1. def visualize_tree(tree, feature_names):
  2. """Create tree png using graphviz.
  3. Args
  4. ----
  5. tree -- scikit-learn DecsisionTree.
  6. feature_names -- list of feature names.
  7. """
  8. with open("dt.dot", 'w', encoding='utf-8') as f:
  9. export_graphviz(tree, out_file=f,
  10. feature_names=feature_names)
  11. command = ["dot", "-Tpng", "dt.dot", "-o", "dt.png"]
  12. 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这个仓库里面。
这里遇到的坑主要是重编码以及乱码的问题,如果遇到同样的问题请查看前面的链接。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注