简介这份资源面向具备一定统计基础、希望用R语言落地机器学习算法的数据分析人员与学生围绕监督学习与非监督学习两条主线提供可直接运行的算法实现代码。包内共20个文件以8个R脚本和6个CSV数据集为主另含LICENSE等说明文件压缩包约16KB体积轻便便于快速下载后本地复现。内容覆盖数据预处理模板、简单线性回归、多元线性回归、多项式回归、支持向量回归与决策树回归等典型回归场景并配有对应数据文件方便读者对照代码理解建模流程。已有244人学习下载适合作为入门练手与课程实验的参考素材帮助读者在R环境中熟悉从数据清洗、特征处理到模型训练与评估的完整链路为后续扩展到分类、聚类等更复杂算法打下基础。1. 从一堆散装脚本到一套能跑通的 R 机器学习工作流很多人第一次用 R 做机器学习都是从复制一段randomForest或者caret::train的代码开始的。跑通了截图发出去觉得“我会了”。但真到了要换数据集、要调参、要对比多个算法、要解释模型结果的时候散装脚本立刻变成一团乱麻数据预处理写死在某个角落训练集和测试集划分靠手动改行号模型评估指标东拼西凑换一个算法就得重写一遍流程。这份“R语言机器学习各种ML算法的实现”资源解决的就是这个问题——它不是某一个算法的演示而是一套把数据准备、模型训练、调参、评估、对比串起来的完整实现集合。适合已经会一点 R 基础语法、但还没把机器学习流程跑顺的人也适合用惯了 Python 的 scikit-learn、想看看 R 生态里对应工具怎么用的人。下面我按自己拆包复现的顺序把这份资源里真正值得动手的部分讲清楚。2. 环境准备与数据入口把依赖和数据结构先理顺2.1 包依赖的安装策略与版本锁定R 的机器学习生态有个特点同一个功能往往有多个包可选而且包与包之间的函数名冲突非常常见。比如caret和randomForest都有predict方法dplyr和base都有filter。如果不做隔离后面调试时会遇到一堆“函数被遮蔽”的警告甚至结果算错都不知道。我一般会先建一个项目级的.Rprofile在里面统一设置 CRAN 镜像和加载顺序。常见做法是用renv做依赖快照这样换机器复现时不会因为包版本漂移导致结果对不上。# 安装 renv 并初始化项目依赖快照 install.packages(renv) renv::init() # 批量安装机器学习常用包 pkgs - c( caret, # 统一训练接口 randomForest, # 随机森林 e1071, # SVM 与朴素贝叶斯 xgboost, # 梯度提升 glmnet, # 正则化线性模型 rpart, # 决策树 rpart.plot, # 树可视化 pROC, # ROC 曲线 ggplot2, # 绘图 dplyr # 数据处理 ) install.packages(pkgs) renv::snapshot() # 把当前版本写入 renv.lock逻辑说明renv::init()会在项目目录下生成renv/和renv.lock之后所有安装的包版本都会被记录。renv::snapshot()把当前状态固化下来别人拿到你的项目后执行renv::restore()就能还原一模一样的环境。参数上pkgs向量里我刻意把caret放在第一位因为它的train函数会统一调用后面这些算法包先加载它能减少命名冲突的概率。提示如果你不想用 renv至少要在脚本开头用sessionInfo()记录一次版本出问题时这是唯一的后悔药。2.2 数据读入与训练集划分的固定随机种子资源里默认用的是内置数据集做演示但真实场景下你肯定要换成自己的 CSV 或数据库表。这里有个血泪经验训练集和测试集的划分如果不固定随机种子每次跑出来的评估指标都会不一样调参时你根本分不清是参数变好了还是划分变好了。library(caret) library(dplyr) # 读入数据假设第一列是标签其余是特征 raw_data - read.csv(your_dataset.csv, stringsAsFactors FALSE) # 把标签列转成因子caret 分类任务要求标签是 factor raw_data$label - as.factor(raw_data$label) # 固定随机种子保证每次划分一致 set.seed(2024) # 按 7:3 划分训练集和测试集 train_index - createDataPartition(raw_data$label, p 0.7, list FALSE) train_set - raw_data[train_index, ] test_set - raw_data[-train_index, ] # 检查两边标签分布是否接近 prop.table(table(train_set$label)) prop.table(table(test_set$label))逻辑说明createDataPartition是caret提供的分层抽样函数它会尽量保持训练集和测试集中各类别的比例与原始数据一致。参数p 0.7表示训练集占 70%list FALSE表示返回索引向量而不是列表。set.seed(2024)必须放在划分之前数字本身无所谓但一旦定下来就不要改。最后用prop.table(table(...))对比两边分布如果某个类别在测试集里占比偏差超过 5%说明数据量太小需要考虑交叉验证而不是单次划分。2.3 缺失值与特征类型的预处理清单R 的很多算法包对缺失值处理方式不一致randomForest默认不能有 NAxgboost可以自动处理稀疏但要求输入是数值矩阵glmnet要求先做标准化。如果不在入口处统一处理后面每个算法都要单独写一遍清洗逻辑。我一般会按下面这个顺序过一遍数值型特征用中位数填充缺失值然后做标准化均值 0标准差 1。类别型特征用众数填充缺失值然后做独热编码one-hot encoding。标签列确认没有缺失且类别数不要超过 50否则要考虑合并稀有类别。# 数值型缺失值用中位数填充 num_cols - sapply(raw_data, is.numeric) for (col in names(raw_data)[num_cols]) { median_val - median(raw_data[[col]], na.rm TRUE) raw_data[[col]][is.na(raw_data[[col]])] - median_val } # 类别型缺失值用众数填充 cat_cols - sapply(raw_data, is.factor) for (col in names(raw_data)[cat_cols]) { mode_val - names(sort(table(raw_data[[col]]), decreasing TRUE))[1] raw_data[[col]][is.na(raw_data[[col]])] - mode_val } # 用 caret 做独热编码和标准化 dummy_model - dummyVars(~ ., data raw_data[, !names(raw_data) %in% label]) encoded_data - predict(dummy_model, newdata raw_data)逻辑说明dummyVars会把所有因子列展开成 0/1 列数值列保持不变。注意~ .表示用除标签外的所有列做编码所以我在data参数里手动排除了label。标准化这一步我通常放在caret::preProcess里做而不是在这里手动算因为preProcess可以把训练集上算出的均值和标准差应用到测试集避免数据泄漏。3. 用 caret 统一训练接口一次写好换算法只改一个字符串3.1 train 函数的参数结构与重采样控制caret::train最大的价值是把几十种算法的训练接口统一成了同一个函数签名。你只需要改method参数其他像重采样方式、调参网格、评估指标都可以复用。这比自己给每个算法写一套循环要省事得多。# 定义统一的训练控制参数 ctrl - trainControl( method cv, # 交叉验证 number 5, # 5 折 classProbs TRUE, # 输出类别概率用于 ROC summaryFunction twoClassSummary, # 用 AUC 做评估 savePredictions final # 保存最终预测结果 ) # 训练随机森林 rf_model - train( label ~ ., data train_set, method rf, trControl ctrl, metric ROC, tuneLength 5 ) # 训练 SVM svm_model - train( label ~ ., data train_set, method svmRadial, trControl ctrl, metric ROC, tuneLength 5 )逻辑说明trainControl里的method cv指定重采样方式number 5是折数。classProbs TRUE和summaryFunction twoClassSummary必须同时设置否则metric ROC会报错。tuneLength 5表示让 caret 自动为每个算法生成 5 组候选参数如果你有明确想试的参数组合可以用tuneGrid代替。注意savePredictions final会把交叉验证中每一折的预测结果存下来后面画 ROC 曲线时直接取用不用重新预测。3.2 调参网格的手动指定与随机搜索tuneLength适合快速跑一遍看大概但真要压榨模型性能还是得手动指定tuneGrid。以随机森林为例最关键的参数是mtry每次分裂时随机抽取的特征数它直接控制树与树之间的相关性。# 手动指定随机森林的 mtry 网格 rf_grid - expand.grid( mtry c(2, 4, 6, 8, 10) ) rf_model_tuned - train( label ~ ., data train_set, method rf, trControl ctrl, metric ROC, tuneGrid rf_grid, ntree 500 # 树的数量固定住不参与调参 ) # 查看调参结果 ggplot(rf_model_tuned) theme_bw() rf_model_tuned$bestTune逻辑说明expand.grid生成参数组合的笛卡尔积这里只调mtryntree固定在 500。为什么不调ntree因为随机森林的树数量增加到一定程度后性能提升会趋于平缓而计算时间线性增长通常 500 到 1000 就够用了。ggplot(rf_model_tuned)会画出不同mtry对应的 AUC 曲线bestTune返回最优组合。如果特征数很多比如超过 100手动网格会太慢这时候可以用method xgbTree配合tuneLength做随机搜索或者直接用caret::train的search random参数。3.3 模型评估与多算法对比的汇总方式训练完几个模型后最直观的对比方式是resamples函数。它会把多个模型的交叉验证结果放在一起计算差异的置信区间。# 收集多个模型的结果 model_list - list( RF rf_model_tuned, SVM svm_model, XGB xgb_model ) # 对比重采样结果 resamp - resamples(model_list) summary(resamp) # 画箱线图对比 bwplot(resamp, metric ROC)逻辑说明resamples要求所有模型使用相同的trainControl和相同的训练集否则对比没有意义。summary(resamp)会输出每个模型 AUC 的均值、中位数、最小最大值。bwplot画出各模型在每一折上的 AUC 分布如果两个模型的箱线图重叠很多说明它们性能差异不显著选哪个都行这时候可以优先选训练时间短或解释性强的那个。4. 各算法实现细节随机森林、SVM、XGBoost 的参数怎么设4.1 随机森林的 mtry 与节点大小随机森林在 R 里有两个常用实现randomForest包和ranger包。caret的method rf默认调用前者method ranger调用后者。ranger速度更快支持并行适合数据量大的场景。关键参数除了mtry还有nodesize叶节点最小样本数。分类任务默认nodesize 1回归任务默认nodesize 5。如果你的数据噪声很大适当增大nodesize可以防止过拟合。# 用 ranger 训练开启并行 library(doParallel) cl - makePSOCKcluster(4) registerDoParallel(cl) rf_ranger - train( label ~ ., data train_set, method ranger, trControl ctrl, metric ROC, tuneGrid expand.grid( mtry c(3, 5, 7), splitrule gini, min.node.size c(1, 5, 10) ), num.trees 800, importance impurity ) stopCluster(cl)逻辑说明makePSOCKcluster(4)开 4 个并行 worker具体数量建议不超过 CPU 核心数。splitrule gini是分类任务的分裂准则回归任务用variance。min.node.size对应randomForest里的nodesize。importance impurity会计算变量重要性后面可以用varImp提取。注意并行集群用完要stopCluster否则后台进程会一直占着内存。4.2 SVM 的核函数选择与代价参数SVM 在caret里有几种写法svmLinear线性核、svmRadial径向基核、svmPoly多项式核。大多数情况下从svmRadial开始试因为它能拟合非线性边界而且只有两个关键参数C代价和sigma核宽度。# 手动调 SVM 的 C 和 sigma svm_grid - expand.grid( C c(0.25, 0.5, 1, 2, 4), sigma c(0.01, 0.05, 0.1, 0.2) ) svm_tuned - train( label ~ ., data train_set, method svmRadial, trControl ctrl, metric ROC, tuneGrid svm_grid, preProcess c(center, scale) # SVM 对尺度敏感必须标准化 ) svm_tuned$bestTune逻辑说明C越大对误分类的惩罚越重容易过拟合C越小容忍度越高可能欠拟合。sigma越大决策边界越弯曲sigma越小边界越平滑。preProcess c(center, scale)是 SVM 的标配因为它是基于距离的算法如果不标准化量纲大的特征会主导距离计算。注意preProcess是在交叉验证的每一折内部独立执行的不会造成数据泄漏。4.3 XGBoost 的稀疏矩阵输入与早停XGBoost 在 R 里有两个接口xgboost包的原生接口和caret的xgbTree。原生接口更灵活支持早停和自定义评估函数但需要把数据转成xgb.DMatrix。library(xgboost) # 构造稀疏矩阵 train_matrix - model.matrix(label ~ . - 1, data train_set) test_matrix - model.matrix(label ~ . - 1, data test_set) # 标签要转成 0/1 数值 train_label - as.numeric(train_set$label) - 1 test_label - as.numeric(test_set$label) - 1 dtrain - xgb.DMatrix(data train_matrix, label train_label) dtest - xgb.DMatrix(data test_matrix, label test_label) # 设置参数并开启早停 params - list( objective binary:logistic, eval_metric auc, max_depth 6, eta 0.1, subsample 0.8, colsample_bytree 0.8 ) xgb_model - xgb.train( params params, data dtrain, nrounds 1000, watchlist list(train dtrain, test dtest), early_stopping_rounds 50, verbose 1 ) # 最优迭代轮数 xgb_model$best_iteration逻辑说明model.matrix会把因子列展开成独热编码- 1表示去掉截距项。objective binary:logistic是二分类任务多分类用multi:softprob。eta是学习率越小需要越多轮次但泛化可能更好。early_stopping_rounds 50表示如果验证集 AUC 连续 50 轮没有提升就停止训练best_iteration返回最优轮数。注意watchlist里同时放了训练集和测试集实际使用时应该用独立的验证集而不是测试集否则早停会间接用到测试集信息。5. 避坑与排查那些让结果对不上的常见问题5.1 因子水平不一致导致预测报错现象训练集上跑得好好的模型用测试集预测时抛出factor label has new levels错误。原因训练集和测试集在独热编码或因子化时如果某个类别只在测试集出现或者某个类别在训练集中被删除了两边的因子水平就对不上。解决在划分数据之前先对完整的原始数据做因子化并显式指定levels。或者用caret::dummyVars时设置fullRank TRUE并在预测时用同一个dummyVars对象转换测试集。# 正确做法在完整数据上定义因子水平 all_levels - unique(raw_data$label) raw_data$label - factor(raw_data$label, levels all_levels) # 划分后再检查 stopifnot(identical(levels(train_set$label), levels(test_set$label)))5.2 交叉验证中的数据泄漏现象交叉验证的 AUC 高得离谱比如 0.99但换一批新数据预测效果很差。原因在划分训练集和测试集之前就做了标准化或缺失值填充导致测试集的统计信息“泄漏”到了训练过程中。解决把所有预处理步骤放进caret::train的preProcess参数或者用recipes包构建预处理流程确保每一步都在交叉验证的折内独立执行。5.3 并行计算导致随机种子失效现象开了并行之后每次跑出来的结果不一样即使设置了set.seed。原因doParallel的 worker 进程有各自的随机数状态主进程的种子不会自动传递过去。解决在trainControl里设置seeds参数为每个重采样和每个模型指定种子。# 为 5 折交叉验证和 5 个调参组合设置种子 set.seed(2024) seeds - vector(mode list, length 6) for (i in 1:5) seeds[[i]] - sample.int(1000, 5) seeds[[6]] - sample.int(1000, 1) ctrl - trainControl( method cv, number 5, seeds seeds, classProbs TRUE, summaryFunction twoClassSummary )5.4 类别不平衡导致 AUC 虚高现象正负样本比例 1:99模型把所有样本都预测为多数类准确率 99%但 AUC 只有 0.5。原因准确率在不平衡数据上具有欺骗性AUC 虽然比准确率好但如果正样本太少AUC 的方差也会很大。解决用caret::train的weights参数给少数类加权或者用ROSE包做合成采样。评估指标改用prSummary精确率-召回率曲线下的面积它对不平衡数据更敏感。5.5 模型保存与跨版本加载失败现象用saveRDS保存的模型换一台机器或升级 R 版本后readRDS报错。原因R 的模型对象里保存了训练时的函数环境引用包版本变化后这些引用可能失效。解决保存模型时用saveRDS配合compress FALSE减少兼容性问题但更稳妥的做法是保存模型的预测结果和参数而不是整个对象。如果必须保存对象在加载后先用predict跑一遍小样本验证。6. 进阶技巧用嵌套交叉验证得到更诚实的性能估计前面讲的交叉验证调参有一个隐藏问题你用同一批数据既选参数又评估性能最终报告的 AUC 会偏乐观。嵌套交叉验证nested cross-validation把调参和评估拆成两层外层划分评估集内层在训练集上调参。这样得到的性能估计更接近模型在全新数据上的表现。library(caret) library(mlbench) # 外层 5 折内层 3 折 outer_ctrl - trainControl(method cv, number 5) inner_ctrl - trainControl( method cv, number 3, classProbs TRUE, summaryFunction twoClassSummary ) # 手动实现嵌套交叉验证 set.seed(2024) outer_folds - createFolds(train_set$label, k 5, list TRUE) nested_auc - numeric(5) for (i in seq_along(outer_folds)) { # 外层留出一折作为评估集 eval_idx - outer_folds[[i]] eval_set - train_set[eval_idx, ] inner_train - train_set[-eval_idx, ] # 内层在剩余数据上调参 inner_model - train( label ~ ., data inner_train, method rf, trControl inner_ctrl, metric ROC, tuneLength 5 ) # 用最优参数在外层评估集上预测 pred_prob - predict(inner_model, eval_set, type prob)[, 2] roc_obj - pROC::roc(eval_set$label, pred_prob, quiet TRUE) nested_auc[i] - pROC::auc(roc_obj) } # 嵌套交叉验证的 AUC 估计 mean(nested_auc) sd(nested_auc)逻辑说明外层循环每次留出一折作为评估集内层在剩下的数据上用 3 折交叉验证选最优参数。inner_model返回的是内层调参后的最终模型直接用它在外层评估集上预测。nested_auc的均值就是嵌套交叉验证的性能估计标准差反映了评估的稳定性。这个流程比单层交叉验证慢 5 倍左右但当你需要向别人报告一个可信的性能数字时它值得多花这些时间。我自己的习惯是任何要写进报告或论文的模型性能都必须走一遍嵌套交叉验证。单层交叉验证的结果只用来快速筛选算法和参数范围不作为最终结论。从那以后我每次评估模型都强制走一遍嵌套流程虽然慢但至少不会在换数据时翻车。希望帮到你。本文还有配套的精品资源点击获取