简介:这份R语言机器学习示例代码包面向希望快速上手机器学习流程的初学者、数据分析师及研究人员,围绕数据清洗、特征选择、标准化、模型训练、预测评估与结果可视化等核心环节组织内容。包内共25个文件,以12个Rmd源文件和12个HTML渲染结果为主,另有1个Markdown说明,压缩包约10.12MB;Rmd便于复现与修改,HTML可直接浏览运行结果,说明文档帮助梳理使用方式。案例覆盖线性回归、逻辑回归、决策树、随机森林、支持向量机、朴素贝叶斯、集成学习、caret建模、正则化及时间序列prophet分析等常见主题,并展示交叉验证、性能指标评估和基础图表绘制。已有132人学习,适合按主题对照源码与输出,理解R语言机器学习从数据准备到模型评价的完整链路,也可作为课程实验和项目练习的参考。
1. 先跑通再谈调参:R语言机器学习小程序代码的入口与边界
R语言机器学习小程序代码最容易被误当成“改个路径就能跑”的示例集,真正拿到手才会发现,入口脚本、数据字典、预处理配方和模型对象之间有一串隐性约定。它解决的不是算法推导,而是把数据读入、特征处理、交叉验证、调参、预测和保存串成一条能重复执行的链路,适合已经会用R做数据分析、想把机器学习流程固化成小工具的人。代码包里的脚本通常不会替你判断业务口径,也不会自动处理因子水平、缺失值和版本差异;这些边界恰好决定它能不能从演示走到可复现。先跑通入口、再锁依赖、最后核对预处理和预测的一致性,比一上来改模型参数更稳。
2. 拆包先看调用链:R机器学习小程序的目录、依赖与数据流
拿到一份R语言机器学习小程序代码,最省时间的动作不是打开模型脚本改参数,而是先确认调用链。入口脚本决定读哪个配置文件、加载哪些包、按什么顺序source()其他脚本;数据流决定训练表和预测表在哪个环节被改写。很多“跑不通”并不是算法问题,而是工作目录、相对路径、包版本或标签列名在入口处就已经错位。先把调用链画清楚,后面改预处理和调参才有落点。
2.1 先找入口脚本和调用链
我一般会先在代码包根目录列两层文件,再搜读数据、读模型和训练函数的调用位置。这样做的好处是避免直接打开某个train.R就改,结果改的是没有被入口引用的旧脚本。常见结构是R/放函数,data/放训练和测试表,models/放模型对象,output/放预测结果;入口可能叫main.R、run_all.R或带00_前缀。名字不重要,重要的是它是否把读数据、预处理、训练、预测串起来。
# 在代码包根目录执行:先看两层文件,再定位读数据和训练调用 find . -maxdepth 2 -type f | sort grep -RIn --include="*.R" -E "read_csv|readRDS|source\\(|train\\(" . | head -50find的-maxdepth 2控制只看两层,避免在依赖目录里翻太久;sort让输出稳定,方便对照。grep的--include="*.R"只搜R脚本,-E开启扩展正则,read_csv|readRDS|source\(|train\(覆盖常见入口动作;head -50先看前五十条,防止输出淹没终端。若代码包里没有明显入口,就按source()关系手工串一个,不要直接在每个脚本里重复setwd()。
# 00_run_all.R source("R/01_load_data.R", encoding = "UTF-8") source("R/02_preprocess.R", encoding = "UTF-8") source("R/03_train_model.R", encoding = "UTF-8") source("R/04_predict.R", encoding = "UTF-8")source()的顺序就是数据流顺序,encoding = "UTF-8"用于避免中文注释或列名在不同系统下乱码。参数不需要花哨,关键是所有路径都相对代码包根目录,而不是某个人的绝对路径。入口脚本里不要夹带install.packages(),安装依赖单独放环境检查脚本,否则每次跑模型都联网,失败点会混在一起。
提示:入口脚本能跑通之前,不要改模型参数。先让读数据、预处理、训练、预测四步各自留下日志或输出,后面排错才有证据。
2.2 用renv和sessionInfo锁定依赖
R语言机器学习代码的版本差异很现实:同一个train()调用,在不同包版本下可能参数默认值不同,predict()返回类型也可能变。代码包如果能跑,先记录sessionInfo();如果要长期复现,用renv把依赖锁在项目内。不要等到预测脚本报“找不到方法”才回头查版本,那时训练结果可能已经不可比。
# 01_check_env.R pkgs <- c("readr", "dplyr", "recipes", "caret", "yardstick", "ranger") missing <- pkgs[!vapply(pkgs, requireNamespace, logical(1), quietly = TRUE)] if (length(missing) > 0) install.packages(missing) sapply(pkgs, packageVersion) sessionInfo()pkgs向量按入口脚本里的library()调用补齐,不用的包不要塞进来;requireNamespace(..., quietly = TRUE)只检查包是否可用,不加载命名空间,适合做环境体检;install.packages()只在缺包时触发,联网环境不稳定时可以先手动装。packageVersion()逐个输出版本,sessionInfo()记录R版本、平台和已加载包。把这些输出保存到output/session_info.txt,复现时先比对,比口头说“我这边能跑”有用。
if (!requireNamespace("renv", quietly = TRUE)) install.packages("renv") renv::init() renv::snapshot()renv::init()在项目内建立隔离库和锁文件,snapshot()把当前依赖写入锁文件。参数不指定时按项目扫描结果处理;如果代码包已有renv.lock,不要反复init()覆盖,先renv::restore()。系统库依赖如压缩、网络、图形设备仍可能影响安装,锁文件不是后悔药,环境检查脚本和锁文件要一起用。
2.3 从read_csv到模型对象的数据流
数据流里最容易被忽略的是标签列、ID列和缺失值标记。训练表通常有id、若干特征和label,测试表可能没有label,也可能多出几列。读数据时统一na参数,先把空串、NA、NULL都转成缺失值,再做类型体检。不要一边读入一边mutate改标签,否则后面排查标签分布时已经看不出原始口径。
library(readr) library(dplyr) train <- read_csv("data/train.csv", na = c("", "NA", "NULL")) test <- read_csv("data/test.csv", na = c("", "NA", "NULL")) dim(train) names(train) train %>% count(label, sort = TRUE) setdiff(names(test), names(train))na = c("", "NA", "NULL")把常见缺失标记统一转成NA,避免空串被当成类别水平;dim()和names()先确认行列规模与列名;count(label, sort = TRUE)看标签分布,类别严重不平衡时交叉验证要用分层抽样;setdiff(names(test), names(train))找测试集多出的列,常见是预测结果列或无关ID。参数上,sort = TRUE只是让输出按频数排序,不影响数据。
# R/config.R cfg <- list( data_dir = "data", model_dir = "models", label_col = "label", id_col = "id", seed = 2024 ) dir.create(cfg$model_dir, showWarnings = FALSE)把路径、标签列、ID列和随机种子收进cfg,后面脚本统一引用,避免这个脚本写label、那个脚本写target。dir.create(..., showWarnings = FALSE)在目录已存在时不报警,适合反复执行。seed不是装饰品,数据划分、交叉验证和并行训练都要在固定种子下进行;换种子重跑是验证稳定性,不是每次随手改。
3. 把脏数据挡在模型外:R语言预处理脚本里的类型、缺失与泄漏
R语言机器学习小程序的预处理脚本通常不长,但边界最多。类型不对、缺失值被静默丢弃、因子水平在训练和预测之间不一致、归一化在交叉验证外做,都会让模型分数看起来漂亮、上线却翻车。预处理的目标不是把数据“洗得好看”,而是让训练折学到的变换规则能原样应用到验证折和预测集。先做体检,再选公式接口或矩阵接口,最后把规则固化成recipes配方,这条顺序比堆函数更重要。
3.1 类型、缺失和标签分布先做体检
体检先看每个变量的类型、缺失数、唯一值数量和数值范围。数值变量被读成字符,常见原因是列里混了单位或空串;分类变量唯一值过多,可能是ID被误当特征;数值范围差异过大,归一化前要确认没有极端值。标签分布决定交叉验证要不要分层,也决定评估指标用准确率还是AUC、召回率。体检代码不追求复杂,关键是每个变量都过一遍。
library(dplyr) library(purrr) profile_one <- function(x) { tibble( type = class(x)[1], n_na = sum(is.na(x)), n_unique = n_distinct(x, na.rm = TRUE), min = if (is.numeric(x)) min(x, na.rm = TRUE) else NA_real_, max = if (is.numeric(x)) max(x, na.rm = TRUE) else NA_real_ ) } profile <- imap_dfr(train, ~ profile_one(.x) %>% mutate(var = .y, .before = 1)) profileclass(x)[1]取第一类,能快速看出是numeric、character还是factor;sum(is.na(x))统计缺失;n_distinct(..., na.rm = TRUE)看唯一值;min和max只对数值变量计算,其余返回NA_real_避免类型冲突。imap_dfr()把变量名带入结果,.before = 1让var排在第一列。参数na.rm = TRUE在全是缺失时会给出警告,体检阶段可以接受,但后续要处理。
prop.table(table(train$label, useNA = "ifany"))table()看标签频数,useNA = "ifany"把缺失标签也列出来,prop.table()转比例。若某个类别占比很低,交叉验证要用分层抽样,评估时不要只看准确率;若标签有缺失,先确认是训练表本身的问题还是读入参数没覆盖。体检阶段不要急着删行,删除前要记录删了多少、为什么删。
3.2 公式接口与矩阵接口怎么选
R语言机器学习代码常见两种接口:公式接口label ~ .和矩阵接口x/y。公式接口适合caret、tidymodels和大多数模型函数,会自动处理因子和哑变量;矩阵接口适合glmnet、xgboost等需要数值矩阵的包,需要自己用model.matrix()展开。选错接口的典型后果是:训练时自动生成哑变量,预测时手工构造的列顺序不一致,最后报“列数不匹配”。
# 公式接口:标签先转成合法因子水平 train$label <- factor(train$label, levels = c("neg", "pos")) form <- as.formula(paste("label ~ . -", cfg$id_col)) # 矩阵接口:先构建设计矩阵,再对齐训练和预测列 X <- model.matrix(~ . - 1, data = train[, setdiff(names(train), c("label", cfg$id_col))]) y <- ifelse(train$label == "pos", 1, 0)公式接口里label ~ . - id排除ID列,as.formula()方便把配置项拼进去;标签转因子时显式指定levels,避免按字母顺序生成不符合业务含义的水平。矩阵接口里model.matrix(~ . - 1, ...)去掉截距,setdiff()排除标签和ID,ifelse()把二分类标签转成0/1。参数-1不是必须,取决于模型是否自带截距;一旦选定,训练和预测必须用同一套构造逻辑。
注意:
caret对分类标签有命名要求,常见做法是用neg、pos这类字母开头的水平。标签是0/1、中文或带空格时,部分模型会直接报错,这不是模型坏了,是接口约定没满足。
3.3 用recipes把预处理固化
预处理最容易出问题的地方是“先对全量数据做归一化和插补,再划分训练验证”。这样验证折的均值、方差和插补邻居已经包含了验证集信息,交叉验证分数会虚高。正确做法是先划分,再在训练折上prep()配方,验证折和预测集只bake()。recipes的好处是把零散操作写成一个对象,保存模型时一起保存,预测时不会漏步骤。
library(recipes) library(caret) set.seed(cfg$seed) idx <- createDataPartition(train$label, p = 0.8, list = FALSE) tr <- train[idx, ] va <- train[-idx, ] rec <- recipe(label ~ ., data = tr) %>% update_role(all_of(cfg$id_col), new_role = "id") %>% step_zv(all_predictors()) %>% step_impute_knn(all_predictors(), neighbors = 5) %>% step_normalize(all_numeric_predictors()) %>% step_dummy(all_nominal_predictors()) %>% prep(training = tr) tr_baked <- bake(rec, new_data = tr) va_baked <- bake(rec, new_data = va)createDataPartition(..., p = 0.8, list = FALSE)按标签分层抽80%做训练,返回索引而不是列表;update_role()把ID列标记为ID,避免进入模型;step_zv()删除零方差变量;step_impute_knn(..., neighbors = 5)用KNN插补,neighbors可改3到10,类别特征多时慎用;step_normalize()只对数值预测变量做标准化;step_dummy()把名义变量转哑变量。prep(training = tr)只在训练集学习参数,bake()把同一套规则应用到训练集和验证集。顺序上先插补再标准化,避免缺失值影响均值方差。
# 预测集也要走同一个配方 test_baked <- bake(rec, new_data = test) stopifnot(ncol(test_baked) == ncol(tr_baked))bake()保证列名和哑变量结构与训练一致,stopifnot()是廉价但有效的断言:列数不一致就直接停,不要等到predict()报错。若测试集没有标签列,recipe(label ~ ., data = tr)的outcome在bake()时可能缺失,常见做法是给测试集加一个与训练同水平的占位列,或在配方里把标签角色单独处理。这个坑在第5章还会展开。
4. 训练脚本怎么改:交叉验证、网格搜索与模型保存的参数落点
训练脚本是小程序代码里最诱人的部分,很多人一上来就换模型、加网格、调复杂度。更稳的顺序是:先固定交叉验证方案和评估指标,再设一个粗网格找方向,最后保存模型和配方并用独立预测脚本验证。R语言机器学习里,caret和tidymodels都能把重采样、调参和预测串起来,但参数落点不同;这份代码包如果已经选了其中一套,不要混着改,先把现有调用链跑通。
4.1 交叉验证的种子、分层与并行
交叉验证不是“填个5折”就完事。分类任务要分层,否则某折里少数类样本太少,AUC会抖动;二分类概率模型要开classProbs,评估指标用ROC;需要复现时,种子要在训练前固定,并行后端不要随意换核数。并行能省时间,但也会改变随机数消耗顺序,调参结果对不上时先查这里。
library(caret) set.seed(cfg$seed) ctrl <- trainControl( method = "cv", number = 5, classProbs = TRUE, summaryFunction = twoClassSummary, savePredictions = "final", sampling = NULL )method = "cv"指定K折交叉验证,number = 5是折数,样本少时可以用10折但训练时间上升;classProbs = TRUE让模型输出类别概率,twoClassSummary才能计算ROC和敏感度;savePredictions = "final"保存最终模型的折外预测,方便复查;sampling = NULL表示不做额外重采样,类别不平衡时先试分层交叉验证,不要一上来就对全量数据做过采样。参数改动的原则是一次只动一个,改完记录结果。
library(doParallel) cl <- makePSOCKcluster(2) registerDoParallel(cl) # 训练结束后执行 stopCluster(cl)makePSOCKcluster(2)创建两个并行工作进程,核数不要超过物理核心数,也不要和系统里其他重任务抢满;registerDoParallel()注册后端。并行训练前仍要set.seed(),训练结束后stopCluster(cl)释放资源。Windows下PSOCK后端更稳,Linux下也可以用FORK,但涉及图形设备或全局状态时PSOCK更可控。
4.2 网格搜索参数怎么设
网格搜索最怕两件事:参数空间大而无效,参数名和模型方法不匹配。caret的tuneGrid列名必须和method对应,比如ranger用mtry、splitrule、min.node.size,glmnet用alpha、lambda。先跑粗网格,观察fit$results里指标随参数的变化,再围绕最优值局部细化。不要一开始就把所有组合铺满,训练时间会把人劝退。
grid <- expand.grid( mtry = c(2, 4, 6), splitrule = "gini", min.node.size = c(1, 5) ) fit <- train( label ~ ., data = tr, method = "ranger", trControl = ctrl, tuneGrid = grid, metric = "ROC", na.action = na.fail, importance = "permutation" ) fit$bestTunemtry是每次分裂时随机抽取的候选变量数,分类任务常从sqrt(p)附近试;splitrule = "gini"是分类树的不纯度准则;min.node.size控制叶节点最小样本,太小容易过拟合,太大容易欠拟合。metric = "ROC"要求二分类概率输出,na.action = na.fail让缺失值在训练阶段直接暴露,而不是被静默丢弃;importance = "permutation"用排列重要性,比默认的杂质重要性更慢但更稳。fit$bestTune是当前网格下的最优组合,不是最终结论。
# 粗网格找到方向后,再局部细化 best_mtry <- fit$bestTune$mtry refine_grid <- expand.grid( mtry = seq(max(1, best_mtry - 1), best_mtry + 1, by = 1), splitrule = "gini", min.node.size = c(1, 3, 5) )局部细化把mtry限制在最优值附近,避免无意义地扩大搜索;seq(max(1, ...))防止mtry出现0或负数。细化后仍用同一个ctrl和metric,不要中途换评估指标,否则前后结果不可比。若fit$results里多组参数指标几乎一样,优先选更简单的模型,别为了小数点后第三位牺牲可解释性和训练成本。
4.3 模型保存与预测脚本的因子对齐
模型保存不是只存fit。预处理配方、标签水平、训练列名、配置项都要一起落盘,否则预测脚本只能靠记忆重建。预测时最容易翻车的是因子水平:训练集标签是neg/pos,预测集读进来变成character或按字母顺序变成pos/neg,predict()就会报新水平或给出反向概率。保存模型时把rec和fit一起存,预测前显式对齐水平。
saveRDS(fit, file.path(cfg$model_dir, "ranger_fit.rds")) saveRDS(rec, file.path(cfg$model_dir, "recipe.rds")) newdata <- read_csv("data/test.csv", na = c("", "NA", "NULL")) label_levels <- levels(tr$label) newdata$label <- factor(newdata$label, levels = label_levels) new_baked <- bake(rec, new_data = newdata) pred_prob <- predict(fit, new_data = new_baked, type = "prob") pred_class <- predict(fit, new_data = new_baked)saveRDS()保存单个对象,file.path()拼路径比字符串粘贴稳;levels(tr$label)从训练集取水平顺序,factor(..., levels = ...)强制预测集对齐。bake()应用配方,predict(..., type = "prob")返回概率,type = "raw"或默认返回类别。若测试集没有真实标签,占位列只是为了满足公式接口,预测后不要把占位标签写进结果。最后检查预测行数:
stopifnot(nrow(pred_prob) == nrow(newdata)) readr::write_csv(pred_prob, file.path(cfg$model_dir, "pred_prob.csv"))stopifnot()保证预测行数和输入行数一致,write_csv()输出概率表。预测脚本不要覆盖原始测试表,结果写到output/或models/下,文件名带日期或种子,方便回溯。
5. 五个翻车现场:R机器学习小程序常见报错的排查顺序
R语言机器学习小程序的报错往往不是单点问题,而是数据、预处理、训练和预测四个环节的约定不一致。排查时先看错误发生在哪一步,再回看这一步的输入对象:训练时看tr和配方,预测时看newdata和模型保存时的列结构。下面五类问题出现频率高,基本覆盖了“训练能跑、预测翻车”的多数场景。每条按现象、原因、解决写,照着顺序查比盲目改模型快。
5.1 训练能跑预测报错:因子水平不一致
现象:训练脚本正常结束,预测时predict()报“New factor levels not present in training data”,或者概率列名对不上。原因通常是训练集标签被转成neg/pos,预测集读进来还是0/1或中文;也可能是分类预测变量的因子水平在两次读入时顺序不同。R的公式接口会把字符自动转因子,但自动转换的顺序不一定和训练集一致。
解决:保存训练集的标签水平和分类变量水平,预测前显式对齐;预处理统一走bake(),不要手工model.matrix()。标签水平用levels(tr$label)取,分类变量可以用recipes的step_dummy()固定。若caret要求标签名合法,训练阶段就改成neg/pos,不要等到预测再补救。改完先跑一行str(new_baked),确认列名和类型与tr_baked一致,再跑predict()。
5.2 缺失值在预测时被静默丢弃
现象:训练时没有报错,预测结果行数比输入少,或者某些行概率全是NA。原因可能是读数据时na参数不一致,训练集把空串当缺失,预测集把空串当类别;也可能是predict()内部用了na.omit,把含缺失的行直接删掉。更隐蔽的是配方只在训练集做了插补,预测集出现新缺失时没有对应步骤。
解决:读数据统一na = c("", "NA", "NULL");训练时设na.action = na.fail,让缺失尽早暴露;配方里加step_impute_knn()或step_impute_median(),并确保bake()应用到预测集。预测后加断言:
stopifnot(nrow(new_baked) == nrow(newdata)) sum(is.na(new_baked))stopifnot()防止行数悄悄变少,sum(is.na())看预处理后还剩多少缺失。若插补后仍有缺失,检查是不是所有预测变量都被插补步骤覆盖,ID列和标签列不要误插补。
5.3 同一种子结果不同:并行与全局随机数
现象:set.seed()明明设了,两次跑出来的AUC还是不一样。原因常见有三类:并行后端改变了随机数消耗顺序;训练前有其他随机操作,比如先做了随机抽样再train();交叉验证的折种子没有固定。还有一种情况是换了caret版本,重采样的默认行为变了。
解决:把set.seed()放在train()之前,且中间不要插入额外随机操作;并行训练固定核数,训练结束stopCluster();需要严格复现时用trainControl(..., seeds = ...)给重采样和最终模型固定种子。每次训练后保存sessionInfo()和fit$results,对比两次的包版本和参数网格。若仍然对不上,先关并行跑一次单核,确认是不是并行后端的问题。
5.4 交叉验证分数虚高:预处理在划分外做
现象:交叉验证AUC 0.95,独立测试集只有0.7,换一折数据分数波动很大。原因通常是对全量数据做了归一化、插补、PCA或特征选择,再划分训练验证;验证折的统计量已经进入训练过程,分数自然虚高。另一种情况是先用全量数据选特征,再做交叉验证,特征选择也泄漏了。
解决:先把数据划分为训练和验证,再在训练折内prep()配方;交叉验证时让train()接收配方对象,由重采样内部处理预处理。不要在外面先prep()全量数据再传给train()。需要更严格的评估时,用嵌套交叉验证:外层评估泛化,内层调参。分数突然变低不一定是模型变差,可能只是泄漏被堵住了。
5.5 包升级后S3方法失效:predict返回类型变了
现象:原来predict(fit, newdata)返回因子,升级包之后返回列表、报错,或者type = "prob"列名变了。原因可能是模型包或caret升级后S3方法注册变化,也可能是旧rds模型对象跨版本读取,内部结构不再匹配。R的模型对象不是永久兼容的,尤其是保存了训练时环境信息的对象。
解决:用renv锁依赖,读旧模型前先看class(fit)和str(predict(...));跨大版本时不要硬读旧rds,用保存的配方和训练脚本重训。预测脚本里显式写type参数,不要依赖默认值。模型和配方一起保存、一起加载,缺一个都可能让预测行为变。升级包之前先跑一遍基准脚本,记录指标和预测列名,升级后对比,别等上线才发现概率列反了。
6. 从跑通到可交付:嵌套验证、阈值校准与复现验收技巧
单次交叉验证的分数只能说明“这套流程在当前划分下能跑”,不能说明调参本身有没有过拟合。要把R语言机器学习小程序从“能跑”推到“可交付”,我一般会补两步:用嵌套交叉验证估计调参偏差,用独立验证集或时间外样本校准阈值。嵌套验证的外层只做评估,内层做调参;阈值不要默认0.5,按业务召回和精确率的平衡点选。下面这段循环不追求快,追求每一步可解释。
library(caret) library(yardstick) set.seed(cfg$seed) folds <- createFolds(tr$label, k = 5, returnTrain = TRUE) nested_auc <- numeric(length(folds)) for (i in seq_along(folds)) { inner_tr <- tr[folds[[i]], ] inner_va <- tr[-folds[[i]], ] inner_ctrl <- trainControl( method = "cv", number = 3, classProbs = TRUE, summaryFunction = twoClassSummary ) m <- train( label ~ ., data = inner_tr, method = "glmnet", trControl = inner_ctrl, tuneLength = 5, metric = "ROC" ) p <- predict(m, newdata = inner_va, type = "prob")[, "pos"] nested_auc[i] <- roc_auc_vec(inner_va$label, p, event_level = "second") } mean(nested_auc)createFolds(..., returnTrain = TRUE)生成外层训练索引,inner_tr和inner_va是外层折;inner_ctrl只在内层调参,tuneLength = 5让glmnet自动生成候选lambda;roc_auc_vec()的event_level = "second"对应正类在第二个水平。外层每折的AUC取平均,才是对调参流程的估计。这个循环比单次train()慢,但能看出分数是不是被调参撑起来的。
验收时别只看AUC,按表逐项过一遍,任何一项失败都先回到对应章节查:
| 检查项 | 通过标准 | 失败先看 |
|---|---|---|
| 入口脚本 | 从根目录source()不报错 | 工作目录、source()顺序 |
| 依赖环境 | sessionInfo()与锁文件一致 | renv.lock、系统库 |
| 预处理 | prep()只在训练折调用 | 配方调用位置、泄漏 |
| 预测输入 | 预测行数等于输入行数 | 缺失值、因子水平 |
| 概率输出 | 概率列名和正类水平固定 | type参数、标签顺序 |
| 复现 | 同种子两次AUC差异可解释 | 并行、全局随机数 |
阈值校准可以用验证集的概率和标签算一遍混淆矩阵,从0.3到0.7逐点看召回和精确率,不要直接拿0.5交差。我吃过一次亏:交叉验证AUC很漂亮,独立测试集上少数类召回惨淡,后来才发现预处理在划分外做了归一化,分数虚高。从那以后我每次拿到R语言机器学习小程序代码,都先跑入口脚本、再锁依赖、最后用嵌套验证复核一遍,不再被单次交叉验证的漂亮分数牵着走。希望帮到你。
本文还有配套的精品资源,点击获取