【机器学习(六)】树形方法

1.决策树简介

决策树的特点:

  • 既可以处理分类问题,也可以处理回归问题
  • 对于缺失值数据也能比较好的处理
  • 高度可解释

决策树的思想:

  • 从树根开始把分类任务按顺序分解成一个个选择
  • 在优化和搜索的过程中,决策树用贪婪的启发式算法,评估当前学习阶段的最优选项

决策树的生成分如下几步:
1)节点的分裂:一般当一个节点所代表的属性无法给出判断时,将这一个节点分裂成若干个子节点;
2)阈值的确定:选择适当的阈值使得分类错误率最小。
3)深度的确定
4 )最终预测值的算法确定


2.熵

按照一般说法,熵就是混沌状态的一种度量。熵越大,表示越混沌。所谓的增熵原理,就是说宇宙中的事物都有自发变得更混乱的倾向,也就是说熵会不断增加。

任何物体,一定是从有序变成无序,而不是相反,因为无序总是更有可能发生。熵的计算公式如下:

信息熵(Entropy)
信息量是对信息的度量。越小概率的事情发生了产生的信息量越大,如湖南产生的地震了;越大概率的事情发生了产生的信息量越小,如太阳从东边升起来了。

信息量度量的是一个具体事件发生了所带来的信息,而熵则是在结果出来之前对可能产生的信息量的期望——考虑该随机变量的所有可能取值,即所有可能发生事件所带来的信息量的期望。即

3.CART 决策树算法

CART(Classification and Regression Tree ),分类和回归树

CART回归树

CART作为回归树时,用SSE来进行度量划分节点的优劣,采用叶子节点的均值或者中位数来预测输出结果。具体来说,度量目标是对于划分特征A,对应划分点s两边的数据集D1和D2,求出使D1和D2各自集合的均方差最小,同时D1和D2的均方差之和最小。

#给定一个分类点,将数据分为两类,分别计算这两类的均方差
compute_SSE_split <- function(v, y, split_point) {
  index<-v<split_point #index是false或者true,也可以认为是0和1
  y1<-y[index] #取出来所有为true对应的y值
  y2<-y[!index] #取出所有为fasle对应的y值
  SSE<-sum((y1-mean(y1))^2) + sum((y2-mean(y2))^2) #计算两堆数的方差
  return(SSE)
}

#给定一个向量v和一个y值,把向量v按照里面的每个元素进行分类,将数据分为两类,分别计算这两类数据的均方差
compute_all_SSE_splits <- function(v, y) {
  sapply(unique(v), function(sp) compute_SSE_split(v,y,sp))
}

set.seed(99)
#生成20个0,1,1次试验,概率0.5
x1<-rbinom(20,1,0.5) 
set.seed(100)
#生产20个均值为5,标准差也为5的随机数,加上10后,四舍五入,保留两位小数
x2<-round(10+rnorm(20,5,5),2) 
set.seed(101)
#生成y
y<-round((1+(x2*2/5)+x1-rnorm(20,0,3)),2)
rcart_df<-data.frame(x1,x2,y)
rcart_df

#对特征1计算所有的方差。因为x1只有0和1两个数,因此只会有2个结果。其中小于0并不能进行区分,其实是有一个结果有效。
x1splits<-compute_all_SSE_splits(x1,y)
#对特征2计算所有的方差。特征2是离散值,有多少个不同的特征,就会有多少个不同的结果。当然,对于最小的特征,其最终得到的sse也是无用的。
x2splits<-compute_all_SSE_splits(x2,y)

#画图
p1 <- ggplot(data = NULL, aes(x = unique(x1), y = x1splits)) + 
  geom_point(shape=4) + 
  ggtitle("SSE values for Splits on Feature x1") + 
  theme(plot.title = element_text(lineheight=.8, face="bold", vjust=2), legend.position="bottom") +
  xlab("x1") + 
  ylab("SSE") + 
  coord_cartesian(xlim = c(-0.1,1.1), ylim = c(225,235)) + 
  annotate("text", x = unique(x1), y = x1splits, label = round(x1splits,2), vjust = -1, hjust = 1, size = 3)
p1

p2 <- ggplot(data = NULL, aes(x = unique(x2), y = x2splits)) + 
  geom_point(shape=4) + 
  ggtitle("SSE values for Splits on Feature x2") + 
  theme(plot.title = element_text(lineheight=.8, face="bold", vjust=2), legend.position="bottom") +
  xlab("x2") + 
  ylab("SSE") + 
  coord_cartesian(xlim = c(10,30), ylim = c(110,250)) +
  annotate("text", x = unique(x2), y = x2splits, label = round(x2splits,2), vjust = -1, hjust = 1, size = 3)
p2

我们发现,最佳的分裂方式是124.05的SSE,所以我们的回归树就一个节点If x2<18.7。

CART分类树
作为分类树时,用基尼系数来进行度量划分节点的优劣,且预测结果采用叶子节点里概率最大的类别作为当前节点的预测类别。

目的:预测抵达某个节点,更有信心能预测类别

  • 按照在叶节点的数据点集合进行分类,让该类纯度更大
  • 节点纯度的衡量指标
  • 基尼系数(Gini index)
    基尼系数代表了模型的不纯度,基尼系数越小,不纯度越低,特征越好。这和信息增益(比)相反。

    假设K个类别,第k个类别的概率为pk,概率分布的基尼系数表达式为:

    如果是二分类问题,第一个样本输出概率为p,概率分布的基尼系数表达式为:

    CART采用的是不停的二分。会考虑把特征A分成{A1}和{A2,A3}、{A2}和{A1,A3}、{A3}和{A1,A2}三种情况,找到基尼系数最小的组合。

分类树与回归树的区别在样本的输出,如果样本输出是离散值,这是分类树;样本输出是连续值,这是回归树。

用基尼系数或者熵来代替SSE

#定义尼基系数函数
gini_index <- function(v) {
  t <- table(v)
  probs <- t / sum(t)
  terms <- sapply(probs,function(p) p*(1-p) )
  return(sum(terms))
}

#基尼系数越小,不纯度越低,特征越好
gini_index(v=c(0,0,0,1,1,1))
gini_index(v=c(0,0,0,1,1,1,1,1,1))
gini_index(v=c(0,0,0,1,1,1,2,2,2))
gini_index(v=c(1,1,1,1,1,1))

#二分类基尼系数
gini_binary <- function(p) {
  return(2 * p * (1 - p))
}
#二分类熵
entropy_binary <- function(p) {
  return(-(p*log2(p) + (1-p)*log2(1-p)))
}


x <- seq(0,1,length=10000)
gini <- gini_binary(x)
entropy <- entropy_binary(x)

#画图
p <- ggplot()+ 
  geom_line(data = NULL, aes(x = x, y = gini, lty = "Gini Index"))+
  geom_line(data = NULL, aes(x = x, y = entropy, lty = "Entropy"))+
  ggtitle("Splitting Criteria for Binary Classification")+
  theme(plot.title = element_text(lineheight=.8, face="bold", vjust=2),
               legend.position="bottom")+
  xlab("Probability of Class 1")+
  ylab("Splitting Criterion")+
  scale_linetype_discrete(name = "Splitting Criterion")+
  coord_cartesian(xlim = c(0,1), ylim = c(0,1))
p

4.总结及R的实现


1)C50包用法

  • 预测特定的纸币是真实的还是伪造

真实和伪造的两种纸币中挑选了一些样本,工业照相机对他们进行拍照。产生的灰度图用小波变换进行处理,在这个变换中构建了3个特征,加上图像的熵,构成了四个特征
#读取数据
bnote <- read.csv("data_banknote_authentication.txt", header=FALSE)
names(bnote) <- c("waveletVar", "waveletSkew", 
                  "waveletCurt", "entropy", "class")
bnote$class <- factor(bnote$class)

#划分测试集和训练集
library(caret)
set.seed(266)
bnote_sampling_vector <- createDataPartition(bnote$class, 
                                             p = 0.80, list = FALSE)
bnote_train <- bnote[bnote_sampling_vector,]
bnote_test <- bnote[-bnote_sampling_vector,]

#使用C50算法构建决策树
library(C50)
bnote_tree <- C5.0(class~.,data=bnote_train)

#在测试集上进行预测
bnote_predictions <- predict(bnote_tree,bnote_test)
mean(bnote_test$class == bnote_predictions) #在测试集上进行预测

summary(bnote_tree)
plot(bnote_tree)

2)rpart包用法

rpart包常用参数如下:


预测复杂的技能学习

即时战略(星际争霸2)的数据集
#预测星际争霸的比赛
skillcraft <- read.csv("SkillCraft1_Dataset.csv")
#去掉GameId,因为不是特征量,对分类无用
skillcraft<-skillcraft[-1]
skillcraft$TotalHours=as.numeric(skillcraft$TotalHours)
skillcraft$HoursPerWeek=as.numeric(skillcraft$HoursPerWeek)
skillcraft$Age=as.numeric(skillcraft$Age)

#划分测试集和训练集
library(caret)
set.seed(133)
skillcraft_sampling_vector <- createDataPartition(skillcraft$LeagueIndex, p = 0.80, list = FALSE)
skillcraft_train <- skillcraft[skillcraft_sampling_vector,]
skillcraft_test <- skillcraft[-skillcraft_sampling_vector,]

#使用rpart进行决策树分类
library(rpart)
regtree <- rpart(LeagueIndex~., data=skillcraft_train)

par(mar=c(2,1,1,1))
plot(regtree, uniform=TRUE, main="Regression Tree for Skillcraft Data Set")
#text(regtree, use.n=FALSE, all=TRUE, cex=.8)

#计算SSE
compute_SSE <- function(correct,predictions) {
  return(sum((correct-predictions)^2))
}

#计算SSE
regtree_predictions = predict(regtree, skillcraft_test)
(regtree_SSE <- compute_SSE(regtree_predictions, skillcraft_test$LeagueIndex))

#minsplit指定父节点和子节点中所包含的最少样本量,是最小分支节点数,这里指大于等于20,那么该节点会继续分划下去,否则停止
#minbucket:叶子节点最小样本数
#cp 复杂程度.用于控制树的复杂度,指某个点的复杂度,对每一步拆分,模型的拟合优度必须提高的程度。值越低,复杂度越高,默认为0.01;
#maxdepth 最大10层
#xval指定交叉验证的次数
regtree.random <- rpart(LeagueIndex~., data=skillcraft_train, 
                        control=rpart.control(minsplit=20, cp=0.001, maxdepth=10))
regtree.random_predictions = predict(regtree.random, skillcraft_test)
(regtree.random_SSE <- compute_SSE(regtree.random_predictions, 
                                   skillcraft_test$LeagueIndex))

#调参
library(e1071)
rpart.ranges<-list(minsplit=seq(5,50,by=5), 
                   cp=c(0,0.001,0.002,0.005,0.01,0.02,0.05,0.1,0.2,0.5), maxdepth=1:10)
(regtree.tune<-tune(rpart,LeagueIndex~., data=skillcraft_train, ranges=rpart.ranges))

#用调参后的值进行预测
regtree.tuned <- rpart(LeagueIndex~., data=skillcraft_train,
                       control=rpart.control(minsplit=50, cp=0.002, maxdepth=7))
regtree.tuned_predictions = predict(regtree.tuned, skillcraft_test)
(regtree.tuned_SSE <- compute_SSE(regtree.tuned_predictions, 
                                  skillcraft_test$LeagueIndex))

plot(regtree.tuned, uniform=TRUE, main="Tuned Regression Tree for Skillcraft Data Set")
text(regtree.tuned, use.n=TRUE, all=TRUE, cex=.8)

par(mar=c(2.5,10,2,2))
barplot(regtree.tuned$variable.importance,horiz=T,las=1,
        main="Variable Importance in Tuned Regression Tree")

1)使用rpart.control()函数的参数进行调优

  • 树的大小:minsplit
    尝试一次分裂所需数据点的最小数量
    每个节点至少有这么多数据点
  • 复杂度参数:cp
    默认是0.01
  • 叶节点和根节点之间节点数量的最大值:maxdepth
    默认值是30
    2)使用tune()函数

5.小结

  • 树的优势:
    很容易实现,很容易解释
    无需对数据的相关模型做出任何假设
    特征选择和数据缺失较为容易
    处理数据类型的范围很广
  • 树的劣势:
    可能分裂数量的指数增长,给类别型变量找到一个分裂变得成本很高
    数据中很小的变化可能改变树中较高位置的分裂决策变化,从而得到另一颗树,结果不稳定
    过拟合
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,230评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,261评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,089评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,542评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,542评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,544评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,922评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,578评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,816评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,576评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,658评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,359评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,920评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,859评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,381评论 2 342

推荐阅读更多精彩内容