Hello!树先生?
决策树模型详解
Hi,大家好,我是晨曦
这期我们机器学习的内容,我们来看一下我们在很多文章或者教程中都常常能见到的——树模型
那么,我们就开始吧~

晨曦碎碎念系列传送门
树模型家族
树模型是一个大概念,也就是说,树模型本身还有很多成员,这里我们列举常用的,并且给其进行一个归类来方便大家理解
机器学习算法主要分为有监督学习和无监督学习,有监督学习主要解决两个事情:
1.回归;2.分类
其中为了解决分类问题,我们主要需要使用以下模型:
1.逻辑回归模型;
2.朴素贝叶斯模型;
3.决策树模型;
4.支持向量机模型;
5.随机森林模型等等
大家可以很清楚的看到其中的决策树模型,这个模型可以说延申出来了很多模型
我们通过一个例子来给大家详细的阐释一下
有一天,一个农夫种了一棵树,这棵树叫决策树,这棵树的诞生目的是为了划分土地,因为农夫的一块土地和另一位农夫的土地相邻,但是有一天农夫发现一棵树不够啊,所以他准备多种几颗,把一棵棵决策树高效的组合起来就变成了随机森林,农夫看着这一片森林,满意的点了点头,这样划分土地就很清晰了,但是有一天,村长来了,说你这个树经济效益不行啊,砍了,农夫只能把大部分树都砍了,但是留到最后一颗的时候,农夫想了想舍不得,于是等下一次村长再来的时候,告诉村长这棵树的目的不是为了划分土地,而是这棵树可以预测耕种的时间,村长说那里留着吧,并且问农夫这颗树叫什么名字,农夫想了想肯定不能叫以前的名字啊,于是说,这棵树叫回归树,村长听了觉得名字不错,回答家里发现自己院子里也有棵树,想着希望这棵树也可以起到某种预测的作用,所以给自己这棵树起名叫做模型树,故事到这里就结束了
我们来总结一下,我们常用的树模型,主要包括:
1.决策树
2.回归树
3.模型树
4.随机森林
如果硬是要给其进行分类,其实晨曦觉得没有太大的必要,我们只需要知道有这几种树模型,然后我们拿到数据后去匹配这几个模型算法的要求足以。
粗暴理解
其实这几种树模型个人理解皆是以决策树为基础,随机森林是决策树的巧妙组合,而回归树也是在决策树算法的基础上,利用每个叶子节点所有样本的目标变量的均值进行数值变量预测。同样地,模型树也采用决策树算法进行分类,但在每个叶子节点建立一个线性回归模型,并以此进行数值变量预测,这也就是为什么决策树算法一般用于样本的分类,但是也可以用于数值变量的预测,因为其有两个延申出来的“好兄弟”
背景知识 & 概念
前面的知识很重要,因为学到后面如果分类搞不清楚,就会混淆各种概念
那么既然我们已经了解了概念上的分类,那么我们就以决策树来开启我们这期推文的讲解
首先我们需要了解决策树的概念
这里我们用朴素的语言来解释,其实要晨曦来说,决策树可以说是最符合我们人思考模式的一种模型了,因为我们在思考事物的时候最擅长的就是把大事情划分成一个个小事情加以权衡利弊后得到最终的结论,这么说可能有点抽象,晨曦依旧举个例子来帮助大家理解
医生判断患者是否感冒遵循的判断逻辑其实就是决策树模型的一个宏观的体现
第一步:医生观察患者脸色没有发红,判断患者没有感冒,但是如果患者了脸色发红,医生进入下一个阶段的判断
第二步:医生观察患者脸色发红,但是体温不高,判断患者没有感冒,但是如果患者体温很高,则医生就判断患者感冒
通过上面简单的例子我们就能了解到决策树其实本质就是把一个大问题划分为几个小变量,然后不断进行判断最后得到结论,如下图所示(其中根节点代表全部样本;叶节点代表一个亚群;内部节点代表着某种可以划分亚群的属性)
那么这里自然而然就引入了一些概念,我们在前面把这些概念理清楚可以更好的帮助我们理解后面的内容
首先,既然我们需要按照某个变量把我们的问题进行划分,不管是感冒、求职等等,我们都可以人为的进行划分,比如说感冒我可以划分的因素有很多,比如是否发热、是否流鼻涕、是否头晕等等,求职我也可以划分很多变量,比如说是否高新、是否辛苦、上班距离长短等等,所以到这里你会发现,我们可以划分的变量有很多很多,难道我们都要纳入我们的决策树模型之中吗?
显然不是,这里自然就引入了一个概念——信息熵
这里其实就分为两种理解方式了,有的人执着于概念的理解,但是这里晨曦准备直观的给大家解释一下,在生物信息学中或者说在统计学中,信息量越大,数据的离散度越大,信息熵越小,当然这个结论还可以延展,一个物体内部越纯粹,即信息熵越小,反之则越大(这里有困惑的原因是因为信息学和统计学产生的冲突,大家可以直接记忆结论)
上面的解释,大家肯定在网上很多地方都看到过,看完只有一个疑问,什么是纯粹?
假设我们有了一个样本集合,这个时候就会出现两种情况,这个样本集合压根就好似一个整体,每一个样本都无法进行归类,这就说明这个样本集合的信息熵很低,反之,如果一个样本集合内部的样本可以划分出很多类别,我们则说这个样本集合的信息熵较前者来说较高
到这里我们了解了决策树模型中第一个重要的概念,下面我们将介绍下一个概念——信息增益
这里的概念和推导公式我就不讲了,我直接举一个例子来方便大家理解
比如说我们一个大样本中包含两个类别即黄种人和白种人
这里我首先按照性别来划分这个大样本,那么就会出现两堆,分别是男人和女人,其中男人中包含着黄种人和白种人,女人中也一样,我们可以通过计算得到划分后两个集合的纯度,也就是信息熵,然后我们把这两个信息熵求加权平均后再和大样本的信息熵进行比较,用划分后的减去划分前的信息熵,得到就是信息增益
这个数值大说明按照性别划分大样本是一个比较好的属性,因为让我们的纯度提高了,适合去构建决策树,反之也不行
至此,我们把概念串联一下:决策树的构建用过信息熵最终计算得到信息增益,信息增益量越大,这个属性作为一棵树的根节点就能使这棵树更简洁;因此,决策树的构造原则选信息增益最大的对应属性作为节点
最后总结一下:读到这里,各位小伙伴应该已经明确了决策树的定义以及其相关概念,我们再来概述一下决策树的优缺点
决策树的优点:
1.决策树易于理解和解释,可以可视化分析,容易提取出规则;
2.对于数据要求较低,可以处理连续数据、离散数据以及缺失值等,不需要预先对输入数据进行正则化、归一化等处理
3.能够处理不相关特征
4.运算速度快
决策树的缺点
1.容易发生过拟合(随机森林可以很大程度上减少过拟合);
2.对于那些各类别样本数量不一致的数据,在决策树中,进行属性划分时,不同的判定准则会带来不同的属性选择倾向;信息增益准则对可取数目较多的属性有所偏好(典型代表ID3算法),而增益率准则(CART)则对可取数目较少的属性有所偏好
3.ID3算法计算信息增益时结果偏向数值比较多的特征
有了上面的基础知识,那么下面我们就开始对算法进行探索,毕竟懂得了理论,我们也需要去学习如何使用~
演示
决策树的整体流程可以分为三个步骤
1.特征选择
2.决策树生成
3.决策树剪枝
仔细体会一下这句话:我们通过信息增益的大小来选择合适的特征,特征选择完毕后纳入进决策树算法并生成决策树模型,因为决策树的缺点在于有可能出现过拟合,所以我们需要进行剪枝,其中剪枝又分为两大类,分别是预剪枝和后剪枝
预剪枝:即在生成决策树的过程中提前停止树的增长
1.当树达到一定的深度时,停止树的生长
2.当到达当前节点的样本数量少于某个阈值的时候,停止树的增长
3.计算每次分裂对测试集的准度提升,当小于某个阈值的时候,不再继续扩展
后剪枝:已生成的决策树上进行剪枝,得到简化版的决策树
然后目前来说我们构建决策树模型的算法主要包含三种
1.ID3算法
2.C4.5算法
3.CART算法
ID3 算法
ID3 是最早提出的决策树算法,他就是利用信息增益来选择特征的。
C4.5 算法(主要)
他是 ID3 的改进版,他不是直接使用信息增益,而是引入“信息增益比”指标作为特征的选择依据。
CART(Classification and Regression Tree)
这种算法即可以用于分类,也可以用于回归问题。CART 算法使用了基尼系数取代了信息熵模型。
这些算法的本质其实就是为了求得最可以代表样本的属性
读到这里的小伙伴可能会发懵了,怎么突然又多了这么多概念,别急我们这里主要选择第二种算法来构建决策树,其它概念小伙伴们可以私底下自行学习
下面我们就用代码来实际的构建一下,其实代码还是很好理解的~
#示例#准备输入数据loc<-"http://archive.ics.uci.edu/ml/machine-learning-databases/"ds<-"breast-cancer-wisconsin/breast-cancer-wisconsin.data"url<-paste(loc,ds,sep="")data<-read.table(url,sep=",",header=F,na.strings="?")str(data)names(data)<-c("ID","thickness","size","shape","adhesion","single","Nuclear","chromatin","routine","Mitosis","type")data$type[data$type==2]<-"benign"data$type[data$type==4]<-"malignant"data<-data[-1] #删除ID信息set.seed(1234) #随机抽样设置种子train<-sample(nrow(data),0.7*nrow(data)) #抽样函数,第一个参数为向量,nrow()返回行数 后面的是抽样参数前tdata<-data[train,] #训练集vdata<-data[-train,] #验证集#采用的数据是UCI机器学习数据库里的威斯康星州乳腺癌数据集,通过对数据的分析,提取出关键特征来判断乳腺癌患病情况#tdata为训练数据集#vdata为测试数据集
下面是数据各变量翻译成中文的信息:
#加载决策树R包library(C50)#这里使用的C5.0相当于C4.5的升级版
credit_model <- C5.0(tdata[,-10], as.factor(tdata$type))#第一个参数是训练集排除终点结局变量的属性信息#第二个参数为转化为因子形式的终点结局变量然后我们来看一下我们获得的这个决策树模型summary(credit_model)#Call:#C5.0.default(x = tdata[, -10], y = as.factor(tdata$type))#C5.0 [Release 2.07 GPL Edition] Sat Dec 11 10:17:24 2021#-------------------------------#Class specified by attribute `outcome'#Read 489 cases (10 attributes) from undefined.data#Decision tree:#size <= 2:#:...single <= 2: benign (280/2)#: single > 2:#: :...shape <= 2: benign (17)#: shape > 2: malignant (7/1)#在肿块大小小于等于2的前提下,如果单个表皮细胞小于等于2,则判断为良性,如果单个表皮细胞大于2,则继续分类,肿块形状小于等于2为良性,大于2为恶性#括号内的数字为符合该决策标准的数量,拿第二行举例:(280/2)为在做出决定的280个例子中,有两个例子被错误的归类为良性,也就是说这两个例子其实为恶性#size > 2:#:...thickness > 6: malignant (101)# thickness <= 6:# :...Nuclear > 2: malignant (63.3/7.3)# Nuclear <= 2:# :...single <= 4: benign (15.2)#            single > 4: malignant (5.5/0.5)#Evaluation on training data (489 cases):# Decision Tree # ---------------- #   Size      Errors  #      7   11( 2.2%)   <<# (a) (b) <-classified as# ---- ----# 310 9 (a): class benign# 2 168 (b): class malignant#上面是一个混淆矩阵,显示了这个模型的预测信息#Errors字段指出,该模型正确分裂了489个示例中除了11个之外的所有示例,也就是说错误率为2.2%#共有9个实际上是良性但是却被归为恶性,也就是所谓的假阳性#共有2个实际上是恶性但是却别归为良性,也就是所谓的假阴性  #  Attribute usage:# 100.00% size# 66.87% single# 37.83% thickness# 16.56% Nuclear#   4.91%  shape#Time: 0.0 secs
决策树通常有将模型与训练数据过度拟合的倾向
因此,在训练数据上报告的错误率可能过于乐观,而在测试数据集上评估决策树尤为重要。
#在测试数据上应用我们的模型credit_pred <- predict(credit_model, vdata[,-10])#绘制可视化library(gmodels)CrossTable(vdata$type, credit_pred, prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE,            dnn = c('actual type''predicted type'))#Total Observations in Table:  210 # | predicted type # actual type | benign | malignant | Row Total | #-------------|-----------|-----------|-----------|# benign | 127 | 12 | 139 | # | 0.605 | 0.057 | | #-------------|-----------|-----------|-----------|# malignant | 5 | 66 | 71 | # | 0.024 | 0.314 | | #-------------|-----------|-----------|-----------|#Column Total | 132 | 78 | 210 | #-------------|-----------|-----------|-----------|#计算准确率correct = sum(credit_pred==vdata$type)/nrow(vdata)correct#[1] 0.9190476然后接下来我们当然是要探索一下,我们可以如何改进我们的模型#改进模型#我们只需要添加一个额外的参数,以便限制使用的单独决策树的数量。试验参数设定了一个上限;如果该算法认识到额外的试验似乎没有提高准确性,那么该算法将停止添加树。我们将从10次试验开始——这个数字已经成为事实上的标准,因为研究表明,这将使测试数据的错误率降低约25%credit_boost10 <- C5.0(tdata[,-10], as.factor(tdata$type),trials = 10)summary(credit_boost10)#可以很直观的看到我们的模型在训练集上的准确性得到了提高#测试集上验证credit_boost_pred10 <- predict(credit_boost10, vdata[,-10])CrossTable(vdata$type, credit_boost_pred10, prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE, dnn = c('actual type', 'predicted type'))#Total Observations in Table:  210 # | predicted type # actual type | benign | malignant | Row Total | #-------------|-----------|-----------|-----------|# benign | 130 | 9 | 139 | # | 0.619 | 0.043 | | #-------------|-----------|-----------|-----------|# malignant | 3 | 68 | 71 | # | 0.014 | 0.324 | | #-------------|-----------|-----------|-----------|#Column Total | 133 | 77 | 210 | #-------------|-----------|-----------|-----------|#计算准确率correct = sum(credit_boost_pred10==vdata$type)/nrow(vdata)correct#[1] 0.9428571
可以看到经过设置上限,我们可以提高我们的模型准确性
但是这里又会产生一个疑问:既然可以提高模型的准确性,为什么我们不在每个模型中都尝试使用这个参数呢?
首先,如果构建决策树需要大量的计算时间,那么构建许多树可能是不切实际的
trails参数为指定增强迭代次数的整数。值为一表示使用了单个模型,所以说如果对于本身构建一个决策树都费很长时间的模型来说,迭代10次只会让时间更加漫长
其次,如果训练数据的噪声很大,那么增强可能根本不会导致改进。不过,如果需要更高的准确性,我们还是值得一试的
当然我们这时候仍然可以从另一方面对模型进行调整
试问,错误与错误之间彼此是不相同的,有些错误容易接受,有些错误则不容易接受,比如说假阳性的错误并不会导致患者的病情恶化甚至于剥夺其生命,但是假阴性的结果就会直接导致患者的病情进展甚至最后剥夺其生命,所以我们想要避免的其实是假阴性的情况发生
#C5.0算法允许我们对不同类型的错误分配一个惩罚,以阻止树犯代价更高的错误。惩罚在成本矩阵中指定,成本矩阵指定每个错误相对于其他错误的代价高多少倍#假设我们认为假阴性是假阳性错误的4倍。那么,我们的成本矩阵就可以定义为:error_cost <- matrix(c(0, 1, 4, 0), nrow = 2)# [,1] [,2]#[1,] 0 4#[2,] 1 0#值1表示否,值2表示是。行用于预测值,列用于实际值#根据这个矩阵所定义的,当算法正确分类否或是时,不分配代价,但是假阴性的代价为4,而假阳性的代价为1#为了了解这如何影响分类,让我们使用C5.0()函数的成本参数将其应用于我们的决策树中:credit_cost <- C5.0(tdata[,-10], as.factor(tdata$type),costs = error_cost)credit_cost_pred <- predict(credit_cost, vdata[,-10])CrossTable(vdata$type, credit_cost_pred, prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE, dnn = c('actual type', 'predicted type'))#Total Observations in Table: 210 # | predicted type # actual type | benign | malignant | Row Total | #-------------|-----------|-----------|-----------|# benign | 125 | 14 | 139 | # | 0.595 | 0.067 | | #-------------|-----------|-----------|-----------|# malignant | 0 | 71 | 71 | # | 0.000 | 0.338 | | #-------------|-----------|-----------|-----------|#Column Total | 125 | 85 | 210 | #-------------|-----------|-----------|-----------|correct = sum(credit_cost_pred==vdata$type)/nrow(vdata)correct#[1] 0.9333333
与我们最好的增强模型相比,这个版本总体上犯更多的错误:准确率下降了1%,然而,错误的类型差别很大,在准确率下降不是很大的前提下,如果能够避免假阴性的出现,那么显然,这个模型是让我们更加接受的
接下来我们来介绍另一种决策树的构建算法
#准备输入数据loc<-"http://archive.ics.uci.edu/ml/machine-learning-databases/"ds<-"breast-cancer-wisconsin/breast-cancer-wisconsin.data"url<-paste(loc,ds,sep="")data<-read.table(url,sep=",",header=F,na.strings="?")str(data)names(data)<-c("ID","thickness","size","shape","adhesion","single","Nuclear","chromatin","routine","Mitosis","type")data$type[data$type==2]<-"benign"data$type[data$type==4]<-"malignant"data<-data[-1] #删除ID信息set.seed(1234) #随机抽样设置种子train<-sample(nrow(data),0.7*nrow(data)) #抽样函数,第一个参数为向量,nrow()返回行数 后面的是抽样参数前tdata<-data[train,] #训练集vdata<-data[-train,] #验证集#采用的数据是UCI机器学习数据库里的威斯康星州乳腺癌数据集,通过对数据的分析,提取出关键特征来判断乳腺癌患病情况#tdata为训练数据集#vdata为测试数据集#建立决策树模型library(rpart)dtree<-rpart(类别~.,data=tdata,method="class", parms=list(split="information"))#使用ID3算法时候,split = “information” ,使用CART算法的时候, split = “gini”
剪枝(pruning)是决策树学习算法对付“过拟合”的主要手段,在决策树学习中,为了尽可能正确分类训练样本,结合划分过程将不断重复,有事会造成决策树分支过多,这时就可能因训练样本学得“太好了”,以致于把训练集自身的一些特点当做所有数据都具有的一般性质而导致过拟合。因此,可通过主动去掉一些分支来降低过拟合的风险
#观察纳入变量的信息并且选择合适的复杂度参数printcp(dtree)#Classification tree:#rpart(formula = type ~ ., data = tdata, method = "class", parms = list(split = "information"))#Variables actually used in tree construction:#[1] Nuclear size thickness#这个模型选择了三个变量纳入到模型之中#Root node error: 170/489 = 0.34765#n= 489 # CP nsplit rel error xerror xstd#1 0.817647 0 1.000000 1.00000 0.061946#2 0.041176 1 0.182353 0.21765 0.034401#3 0.017647 3 0.100000 0.19412 0.032631#4 0.010000      4  0.082353 0.19412 0.032631#cp是参数复杂度(complexity parameter)作为控制树规模的惩罚因子,简而言之,就是cp越大,树分裂规模(nsplit)越小。输出参数(rel error)指示了当前分类模型树与空树之间的平均偏差比值。xerror为交叉验证误差,xstd为交叉验证误差的标准差。可以看到,当nsplit为3和4的时候交叉误差是相同的,决策树剪枝的目的是为了获得更小的交叉验证误差,这里一样,我们也就没有了剪枝的目的#tree<-prune(dtree,cp=0.0125)#如果剪枝运行这个代码#tree<-prune(dtree,cp=dtree$cptable[which.min(dtree$cptable[,"xerror"]),"CP"])#自动判断最小cp点,然后进行剪枝
模型都已经修饰完毕,我们就可以可视化看一下
#可视化opar<-par(no.readonly = T)par(mfrow=c(1,2))#这里定义的全局变量为1行2个图library(rpart.plot)png(file = "./R/tree1.png")rpart.plot(dtree,branch=1,type=2,fallen.leaves=T,cex=0.8,sub="剪枝前")png(file = "./R/tree2.png")rpart.plot(tree,branch=1,type=4,fallen.leaves=T,cex=0.8,sub="剪枝后")par(opar)dev.off()
这里图形中的两个数据,我们拿第一个方块中的数据举例,100%代表着这一块整体的机率,然后0.35代表着有35%恶性的可能,也就是benign的相反情况,也就是错误率
我们来解读一下这个图,首先,我们这些样品整体抽中这些样品的机率是100%,因为我们的抽选目标就是这些,然后恶性的机率为35%,然后当size<3的时候,即为yes,然后进入下一个蓝色方块,这个时候,整体良性的机率为62%,也就是说当size<3的时候,良性机率为62%,恶性机率为3%,然后继续判断,当thickness<7的时候,有61%的机率为良性,恶性的机率仅为1%,注意,这里的61%并不是针对全局62%来计算的
predtree<-predict(dtree,newdata=vdata,type="class") #利用预测集进行预测table(vdata$type,predtree,dnn=c("真实值","预测值")) #输出混淆矩阵# 预测值#真实值 benign malignant# benign 129 10# malignant 2 69#计算准确率correct = sum(predtree==vdata$type)/nrow(vdata)correct#[1] 0.9428571
至此,三种算法的决策树构建都讲解完毕了~
看起来选择经典的C5.0可能是一个更好的选择
到这里决策树模型就给大家介绍完毕了~
呼呼,感觉文字比较长还是有点吃力,这篇推文晨曦尽可能写的很直白,也没有用一堆概念式的术语,因为作为咱们医学生信挖掘领域来说,能用、会用就已经足够了~
本期推文内容参考:Machine Learning with R
那么本期推文到这里就结束啦~
我是晨曦,我们下期再见
晨曦单细胞笔记系列传送门
晨曦从零开始学画图系列传送门
晨曦单细胞数据库系列传送门

END

撰文丨晨   曦
排版丨四金兄
主编丨小雪球
欢迎大家关注解螺旋生信频道-挑圈联靠公号~
继续阅读
阅读原文