自选4 随机森林算法:预测高中生数学成绩

R语言中的机器学习/数据挖掘

Posted by Jiawen Wu on December 30, 2018

前言

本post以Paulo Cortez和Alice Silva提供的葡萄牙高中生自我报告及学校成绩report数据复现其论文中的部分数据挖掘结果。


随机森林算法

随机森林属于集成学习算法,是通过使用某种规则整合一系列学习器的学习结果得到更好的学习效果的一种机器学习方法。随机森林的base是单棵决策树(decision tree)。

上面就是一个决策树的例子。它可以是二叉树或非二叉树,每个非叶节点有一个特征属性上的测试,每个分支代表这个特征属性在某个值域上的输出,每个叶节点则存放一个相应的类别。机器使用决策树分类的过程就是从根节点开始测试特征属性,按照测试的结果层层向下,直到最后的子叶节点,得到类别。

决策树提供了一个简单清晰的型来理解迭代分类过程。但是,在实践中,我们的变量可能会很多,数据规模也可能很大,单个决策树在应对现实问题时并不十分有效。所以我们就需要种一堆的树来帮我们解决问题,那就是决策森林了。随机森林顾名思义,是用随机的方式建立一个森林,在变量(列)的使用和数据(行)的使用上分别进行随机化,生成很多不一样的决策树,在森林里的决策树之间没有关联。分类时,进入森林的样本会被森林里的每一棵树都进行一下分类,最后汇总所有树的分类结果,看看哪一种分类的得票数最高就将这个样本分入哪一类中。

更多关于随机森林算法的介绍,大家可以自行google,或阅读Breiman(2001)原始文献

高中生学习成绩数据集

数据来源:UCI Machine Learning Repo - Student Performance Data Set

顺便列几个藏着矿的数据集资源:

回归正题!“高中生学习成绩”这个数据集收集了两所葡萄牙高中学生的学业情况。数据包括学生成绩,人口学信息,社会属性和学校相关特征),包含了学校文件信息和学生自我报告问卷。该数据集包含了数学(mat)和葡萄牙语(por)两个分数据集。Cortez和Silva使用这两个数据集建模预测学生高中毕业时的数学和葡萄牙语成绩,本post即希望重复数据分析过程,验证其在论文中报告的结果。

Reference: 点击此处下载

P. Cortez and A. Silva. Using Data Mining to Predict Secondary School Student Performance. In A. Brito and J. Teixeira Eds., Proceedings of 5th FUture BUsiness TEChnology Conference (FUBUTEC 2008) pp. 5-12, Porto, Portugal, April, 2008, EUROSIS, ISBN 978-9077381-39-7.

数据集中的变量

基于R语言的随机森林算法运用

需要用到的 R package:

  • library(randomForest) - 随机森林专用包
  • library(dplyr) - 高效率操作,有着像SQL一样的逻辑,还有管道函数!
  • library(caret) - Classification and Regression Training(数据挖掘中的分类与回归问题)

准备数据集

从UCI上下载下来的Student Performance Data Set中的数据是csv文件,分隔符是“;”

# Math course
d1=read.table(file.choose(),sep=";",header=TRUE)
# Portuguese language course
d2=read.table(file.choose(),sep=";",header=TRUE)
str(d1); print(nrow(d1)) # 395 students
str(d2); print(nrow(d2)) # 649 students

# merge Math course and Portuguese language course
d3=merge(d1,d2,by=c("school","sex","age","address","famsize","Pstatus","Medu","Fedu","Mjob","Fjob","reason","nursery","internet"))
str(d3); print(nrow(d3)) # 382 students

变量的信息可以在论文中查到,上面的图就是来自论文滴。都列得很清楚了,其中前面的29个参数来自于问卷,后面4个参数是学生学校出勤及成绩,来自于学校。

检查/清理数据(这个数据集中没有缺失值,所以如何处理缺失值的情况以后再说吧)

# 将含有NA值的case筛选出来 -> 没有缺失值
d1[!complete.cases(d1),]
# 返回含有较多NA值的case行数,NA值的个数超过了所有参数的20%定义为缺失值较多
manyNAs(d1, 0.2) 

d2[!complete.cases(d2),]
manyNAs(d2, 0.2)

# 根据论文中的操作,将成绩二分,低于10分的挂掉,高于10分的pass
d1$G3score.level <- cut(d1$G3,breaks=c(-Inf,10,Inf),labels=c('fail','pass'))
plot(d1$G3score.level)

d1_math <- d1[,c(-33)] # 论文中的A setting
# d1_math <- d1[,c(-33,-32)] # 论文中的B setting
# d1_math <- d1[,c(-33,-32,-31)] # 论文中的C setting
str(d1_math)

# 有可能需要处理一下数据类型
d1_math <- d1_math %>% mutate_if(is.character,as.factor) 

抽取样本

随机按比例抽取样本,保证训练集和测试集的样本都能代表人群(被预测指标在人群中的比例)。

set.seed(58251)
# caret::reateDataPartition allow you to create stratified random samples
indexes <- createDataPartition(d1_math$G3score.level,  
                               times = 1,
                               p = 0.7,
                               list = FALSE) 
 
d1_math.train <- d1_math[indexes,]
d1_math.test <- d1_math[-indexes,]
str(d1_math.train)
prop.table(table(d1_math$G3score.level))
prop.table(table(d1_math.train$G3score.level))
prop.table(table(d1_math.test$G3score.level))

创建森林

在使用模型前,我们需要确定模型中需要几棵树,可以通过画图大致判断模型内误差稳定时ntree的值。

set.seed(66749)
rf_ntree <- randomForest(G3score.level ~.,data=d1_math.train,ntree=1000)
plot(rf_ntree)

由图可以看出,在800颗树时,误差基本保持稳定,因而选择 ntree=800放入模型。

接下来,确定mtry值,mtry是指定节点中用于二叉树的变量个数,默认情况下数据集变量个数的二次方根(分类模型)或三分之一(预测模型)

将选择1-32(length(names(d1_math.train)) - 1)个变量进入首层用循环跑一遍,看看哪个的袋外错误率(out-of-bag error)最低。

n <- length(names(d1_math.train))
set.seed(12345)
for (i in 1:(n-1)){
  model <- randomForest(G3score.level ~., data = d1_math.train, mtry = i)
  err <- mean(model$err.rate)
  print(err)
}
# 9 min = 0.05503825
d1_math_rf1 <- randomForest(G3score.level ~.,data=d1_math.train,ntree=800,mtry=9,
                        proximity= TRUE)
d1_math_rf1

得到 mtry = 9 时袋外错误率最低。 这里引用知乎用户用R来上天的斑马写的R语言实现随机森林 来解释袋外错误率:

随机森林有一个重要的优点就是,没有必要对它进行交叉验证或者用一个独立的测试集来获得误差的一个无偏估计。它可以在内部进行评估,也就是说在生成的过程中就可以对误差建立一个无偏估计。

我们知道,在构建每棵树时,我们对训练集使用了不同的bootstrap sample(随机且有放回地抽取)。所以对于每棵树而言,部分训练实例没有参与这棵树的生成,它们称为第k棵树的oob样本。

  • 袋外错误率(oob error)计算方式如下:
    • 对每个样本计算它作为oob样本的树对它的分类情况
    • 以简单多数投票作为该样本的分类结果
    • 最后用误分个数占样本总数的比率作为随机森林的oob误分率

开始种树

d1_math_rf <- randomForest(G3score.level ~.,data=d1_math.train,ntree=800,mtry = 9,
                        proximity= TRUE)
d1_math_rf

Call:
 randomForest(formula = G3score.level ~ ., data = d1_math.train,      ntree = 800, mtry = 9, proximity = TRUE) 
               Type of random forest: classification
                     Number of trees: 800
No. of variables tried at each split: 9

        OOB estimate of  error rate: 5.4%
Confusion matrix:
     fail pass class.error
fail  123    8  0.06106870
pass    7  140  0.04761905

可以得到袋外错误率为5.4%

检查得到的随机森林

plot(d1_math_rf)
importance(d1_math_rf)
varImpPlot(d1_math_rf)

不出意料,高一高二的成绩是最重要的预测指标。

MDSplot(d1_math_rf, d1_math.train$G3score.level,k=2,palette=c("#E8948E","#3E91BA"))

用测试集来进行检验

d1_math_pred <- predict(d1_math_rf,newdata=d1_math.test)
freq_d1_math_pred <- table(d1_math_pred,d1_math.test$G3score.level)

> d1_math_pred fail pass
          fail   52    5
          pass    3   57

PCC <- sum(diag(freq_d1_math_pred))/sum(freq_d1_math_pred)

> 0.9316239

也就是测试集的正确率达到了93.2%

本post只是复现了Cortez和Silva(2008)论文中预测数学成绩/成绩采用二分法P(NP)设置/Setting A/随机森林算法结果。论文中还比较了其它常见机器学习算法(决策树、神经网络、支持向量机)在其它的数据setting(五个等级城际分类/不加入高一高二成绩)下的精确度。大家有兴趣的可以用UCI提供的数据集玩儿一下。

最后,附上论文中给的demo:决策树代码(他做了10折的交叉验证,也就是做十次分类)

library(rminer)
# 10-fold cross-validation
K=c("kfold",10) 

# execute 10 runs of a DT classification:
DT=mining(G3score.level~.,d1_math.train,model="dt",Runs=20,method=K)

# show mean classification error on test set:
print(mean(DT$error))

# 精确度为  0.926998

参考文件下载: