首页 / 资讯中心 / 文章详情

R语言机器学习小程序代码:跑通、调参与复现验收

R语言机器学习小程序代码:跑通、调参与复现验收 ★ FEATURED ARTICLE
简介这份R语言机器学习示例代码包面向希望快速上手机器学习流程的初学者、数据分析师及研究人员围绕数据清洗、特征选择、标准化、模型训练、预测评估与结果可视化等核心环节组织内容。包内共25个文件以12个Rmd源文件和12个HTML渲染结果为主另有1个Markdown说明压缩包约10.12MBRmd便于复现与修改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还是factorsum(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()排除标签和IDifelse()把二分类标签转成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.sizeglmnet用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/negpredict()就会报新水平或给出反向概率。保存模型时把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自动生成候选lambdaroc_auc_vec()的event_level second对应正类在第二个水平。外层每折的AUC取平均才是对调参流程的估计。这个循环比单次train()慢但能看出分数是不是被调参撑起来的。验收时别只看AUC按表逐项过一遍任何一项失败都先回到对应章节查检查项通过标准失败先看入口脚本从根目录source()不报错工作目录、source()顺序依赖环境sessionInfo()与锁文件一致renv.lock、系统库预处理prep()只在训练折调用配方调用位置、泄漏预测输入预测行数等于输入行数缺失值、因子水平概率输出概率列名和正类水平固定type参数、标签顺序复现同种子两次AUC差异可解释并行、全局随机数阈值校准可以用验证集的概率和标签算一遍混淆矩阵从0.3到0.7逐点看召回和精确率不要直接拿0.5交差。我吃过一次亏交叉验证AUC很漂亮独立测试集上少数类召回惨淡后来才发现预处理在划分外做了归一化分数虚高。从那以后我每次拿到R语言机器学习小程序代码都先跑入口脚本、再锁依赖、最后用嵌套验证复核一遍不再被单次交叉验证的漂亮分数牵着走。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站