1.评估分类方法的性能
- 拥有能够度量实用性而不是原始准确度的模型性能评价方法是至关重要的。
- 3种数据类型评价分类器:真实的分类值;预测的分类值;预测的估计概率。之前的分类算法案例只用了前2种。
- 对于单一预测类别,可将predict函数设定为class类型,如果要得到预测的概率,可设为为prob、posterior、raw或probability等类型。predict大部分情况下返回对结果不同水平的预测概率。
#朴素贝斯分类的预测概率
predicted_prob=predict(model,test_data,type="raw")
#决策树C5.0分类器
predicted_proc=predict(model,test_data,type="prob")
- 一般而言,预测值和真实值都是同一类时的预测概率会比较极端(接近0或1),但总有一些相反的,概率值介于中间,这时判断模型是否可用,可通过对测试数据应用各种误差度量的方法。
# obtain the predicted probabilities
sms_test_prob <- predict(sms_classifier, sms_test, type = "raw")
head(sms_test_prob)
1.1 混淆矩阵
混淆矩阵是一张二维表(第一维是所有可能的预测类别,第二维是真实的类别),按照预测值是否匹配真实值来对预测值进行分类。二值分类是2X2混淆矩阵,三值分类模型是3X3混淆矩阵。
-
阳性和阴性:相对的概念,无任何隐含的价值判断。一般将感兴趣的类别(或目标)设为阳性。
-
度量性能:准确度(成功率)
- 错误率
gmodels::CrossTable
代码示例:
数据下载链接: https://pan.baidu.com/s/1YiFdOHX8rWrVKB97FRY8Iw 提取码: eka6
## Confusion matrixes in R ----
sms_results <- read.csv("sms_results.csv")
# the first several test cases
head(sms_results)
# test cases where the model is less confident
head(subset(sms_results, prob_spam > 0.40 & prob_spam < 0.60))
# test cases where the model was wrong
head(subset(sms_results, actual_type != predict_type))
# specifying vectors
table(sms_results$actual_type, sms_results$predict_type)
# alternative solution using the formula interface (not shown in book)
xtabs(~ actual_type + predict_type, sms_results)
# using the CrossTable function
library(gmodels)
CrossTable(sms_results$actual_type, sms_results$predict_type)
# accuracy and error rate calculation --
# accuracy
(152 + 1203) / (152 + 1203 + 4 + 31)
# error rate
(4 + 31) / (152 + 1203 + 4 + 31)
# error rate = 1 - accuracy
1 - 0.9748201
1.2 其他评价指标
分类和回归训练R包caret提供了更多的计算性能度量指标的函数。
## Beyond accuracy: other performance measures ----
library(caret)
confusionMatrix(sms_results$predict_type,
sms_results$actual_type,
positive = "spam") #检测垃圾信息是目标,将其设为阳性
1)Kappa统计量
- Kappa对准确度进行调整,表示的是预测值和真实值之间的一致性。
-
Kappa计算公式:
Pr是预测值和真实值之间的真实一致性a和期望一致性e的比例。Kappa统计量使用期望的一致性Pr(e)对准确度进行调整,Pr(e)是完全的偶然性导致的预测值和实际值相同的概率。
计算示例:
pr(a)=0.865+0.111=0.976
pr(e)=0.868*0.886+0.132*0.114=0.784096
kappa=(pr(a)-pr(e))/(1-pr(e))=0.89
- 其他计算Kappa的函数:
# calculate kappa via the vcd package
library(vcd)
Kappa(table(sms_results$actual_type, sms_results$predict_type))
- vcd::Kappa函数计算出来的kappa有加权和没加权的,对于二分类,加不加权都一样,一般关注不加权的就好。加权主要用于存在不同尺度一致性的情况。
# calculate kappa via the irr package
library(irr)
kappa2(sms_results[1:2])
- irr::kappa2函数可直接使用数据框中的预测值向量和实际分类向量来计算Kappa值。
注意不要用内置的kappa函数,它与Kappa统计量没关系。
2)灵敏度与特异性
- 用来权衡做决策时保守or激进的度量。权衡时最典型的做法是:对模型进行调整或者使用不同的模型,直到能通过灵敏度和特异性的阈值为止。
- 灵敏度(真阳性率):度量阳性样本被正确分类的比例。
- 特异性(真阴性率):度量阴性样本被正确分类的比例。
如上面的混淆矩阵中,手动计算:
# Sensitivity and specificity
# example using SMS classifier
sens <- 152 / (152 + 31)
sens
spec <- 1203 / (1203 + 4)
spec
caret包中的sensitivity和specificity函数可直接计算:
# example using the caret package
library(caret)
sensitivity(sms_results$predict_type,
sms_results$actual_type,
positive = "spam")
specificity(sms_results$predict_type,
sms_results$actual_type,
negative = "ham")
3)精确度与回溯精确度
- 这两者也与分类时的折中方案有关。
- 精确度(阳性预测值):真阳性在所有预测为阳性案例中的比例。
- 回溯精确度:度量结果的完备性,真阳性与阳性总数的比例(计算与灵敏度一样,只是解释不同:捕捉大量阳性样本,具有很宽的范围)。
手动计算:
# Precision and recall
prec <- 152 / (152 + 4)
prec
rec <- 152 / (152 + 31)
rec
caret包中的posPredValue函数计算:
# example using the caret package
library(caret)
posPredValue(sms_results$predict_type,
sms_results$actual_type,
positive = "spam")
sensitivity(sms_results$predict_type,
sms_results$actual_type,
positive = "spam")
4)F度量
- F度量(F1记分/F记分):将精确度和回溯精确度合并成一个单一值(通过调和平均值来整合)的模型性能度量方式。
计算:
# F-measure
f <- (2 * prec * rec) / (prec + rec)
f
f <- (2 * 152) / (2 * 152 + 4 + 31)
f
- 整合成一个单一值比较方便,但需要假设精确度和回溯精确度具有同样的权重。
1.3 性能权衡可视化(ROC曲线)
- 可视化可以考察度量如何在大范围的值之间变化,还可以在单个图形中同时比较多个分类器的方法。
- ROC(受试者工作特征)曲线:常用来检查在找出真阳性和避免假阳性之间的权衡。
- ROC曲线横轴表假阳性比例(1-特异性),纵轴表真阳性比例(灵敏度),所以也称为灵敏度/特异性图。
- ROC曲线上的点表示不同假阳性阈值上的真阳性的比例。
-
AUC(Area Under the ROC):ROC曲线下面积来度量识别阳性值的能力。位于0.5(无预测值分类器)-1(完美分类器)之间,解释AUC得分可参考(比较主观):
- ROC+AUC:两个ROC曲线可能形状不同,但具有相同的AUC,因此AUC可能具有误导性,最好是和ROC曲线定性分析结合使用。
代码示例:
## Visualizing Performance Tradeoffs ----
library(ROCR)
pred <- prediction(predictions = sms_results$prob_spam,
labels = sms_results$actual_type)
# ROC curves
perf <- performance(pred, measure = "tpr", x.measure = "fpr")
plot(perf, main = "ROC curve for SMS spam filter", col = "blue", lwd = 2)
# add a reference line to the graph
abline(a = 0, b = 1, lwd = 2, lty = 2)
定性分析可看到上图ROC曲线占据了图形左上角的区域,接近完美分类器;定量分析则通过函数来计算AUC。
# calculate AUC
perf.auc <- performance(pred, measure = "auc")
#返回S4对象,存储信息的位置称为槽(slots),槽的前缀为@
str(perf.auc) #查看所有槽
unlist(perf.auc@y.values) #简化为数值向量
AUC值可达到0.98。但这个模型对其他数据集是否也表现好呢?需要测试外部数据来推断模型的预测性能。
2.评估未来的性能
- 当训练数据进行了错误的预测时会产生再带入误差。与信赖再带入误差相比,更好的方式时评估模型对其从未见过数据的性能。一般就是将数据分为训练集和测试集,但当数据集很小时,这样的划分会减小样本量,是不合适的。
2.1 保持法
- 保持法就是常见的数据划分训练集和测试集的过程:训练集用来生成模型,应用到测试集来生成预测结果进行评估。一般1/3的数据用于测试,2/3用于训练。
- 保持法不允许测试集的结果影响模型,但如果基于重复测试的结果选择一个最好的模型,则会违反这个原则。因此可再分出第三个数据集,集验证集。(注:前面的章节中我们只划分了训练和测试集两类数据,实际上违反了这一原则,那些测试集更准确地应该称为验证集。如果我们使用测试集来做决策,从结果中挑选最好的模型,那么评估将不再是对未来性能的无偏估计)
- 验证集用来对模型迭代和改善。测试集只使用一次,最后输出对未来预测的错误率估计。一般数据划分50%训练集,25%测试集,25%验证集。
- 分层随机抽样:确保随机划分后每个类别的比例与总体数据中的比例近似相等。可用caret:::createDataPartition函数实现。
数据下载链接: https://pan.baidu.com/s/1O9JYXUZnQfVGIU-VWGTptA 提取码: 7q7q
# partitioning data
library(caret)
credit <- read.csv("credit.csv")
# Holdout method
# using random IDs
random_ids <- order(runif(1000))
credit_train <- credit[random_ids[1:500],]
credit_validate <- credit[random_ids[501:750], ]
credit_test <- credit[random_ids[751:1000], ]
# using caret function
#返回行号
in_train <- createDataPartition(credit$default,
p = 0.75, #该划分中样本的比例
list = FALSE) #防止结果存储成列表
credit_train <- credit[in_train, ]
credit_test <- credit[-in_train, ]
- 一般的,模型在更大的数据集中训练可得到更好的性能,所以常见的做法是:在选择和评估了最终的模型之后,将模型在整个数据集(训练集+测试集+验证集)上重新训练,使模型最大化地利用所有数据。
- 重复保持法:保持法的一种特殊形式,对多个随机保持样本的模型分别评估,然后用结果的均值来评价整个模型的性能。
2.2 交叉验证
- 重复保持法使k折交叉验证(k折CV,将数据随机分成k个完全分隔的部分)的基础,k折交叉验证已称为业界评估模型性能的标准。
- 最常用10折交叉验证(每一折包含总数据的10%),机器学习模型使用剩下的90%数据建模,包含10%数据的这一折用来评估,训练和评估模型进行不同的10次,将输出所有折的平均性能指标。
- 留一交叉验证法:将每个样本作为1折,用最大数目的样本来建模,但计算量太大,很少用。
- 使用caret::createFolds函数创建交叉验证数据集:
# 10-fold CV
folds <- createFolds(credit$default, k = 10)
str(folds)
credit01_test <- credit[folds$Fold01, ]
credit01_train <- credit[-folds$Fold01, ]
可以用不同数据集手动执行以上步骤10次,然后建模评估,最后将所有性能度量取均值作为总体的性能。但我们肯定可以通过编程来实现自动化,这里以10折CV建立C5.0决策树模型为例,然后估计Kappa统计量:
## Automating 10-fold CV for a C5.0 Decision Tree using lapply() ----
library(caret)
library(C50)
library(irr)
credit <- read.csv("credit.csv")
set.seed(123)
folds <- createFolds(credit$default, k = 10)
cv_results <- lapply(folds, function(x) {
credit_train <- credit[-x, ]
credit_test <- credit[x, ]
credit_model <- C5.0(default ~ ., data = credit_train)
credit_pred <- predict(credit_model, credit_test)
credit_actual <- credit_test$default
kappa <- kappa2(data.frame(credit_actual, credit_pred))$value
return(kappa)
})
str(cv_results)
mean(unlist(cv_results))
kappa值很低,总体模型性能差,下一章会讲如何改进。
2.3 自助法抽样
- 自助法抽样(bootstrap):主要指一些统计方法,通过对数据进行随机抽样的方式来估计大数据集的内容。各种随机产生的数据集的结果可以通过平均值计算得到一个最终的估计值,用来评估未来的性能。
- 与k折CV的不同:交叉验证将数据分隔开来,每个样本只能出现一次,而自助法是有放回的抽样,每个样本可以被选择多次。因此自助法抽样对完整数据集的代表性更弱,但它对于小数据集的效果更好。除了度量性能之外,它还能提高模型性能。
-
0.632自助法:每个样本包含在训练集中的概率是63.2%。通过训练数据集(过于乐观)和测试集(过于悲观)的函数来计算最终的性能度量: