资讯详情

R语言机器学习算法手写实现资源:从线性回归到神经网络

📅 2026/10/19 17:28:07 | 华诺云谱 👁 阅读
R语言机器学习算法手写实现资源:从线性回归到神经网络
简介这份资源面向具备一定统计基础、希望用R语言落地机器学习算法的数据分析人员与学生围绕监督学习与非监督学习两条主线覆盖线性回归、逻辑回归、支持向量机、决策树、随机森林、梯度提升机、朴素贝叶斯、神经网络以及K-means聚类、关联规则等常见算法的实现思路。压缩包共20个文件以8个R脚本和6个CSV数据集为主另含许可证等说明文件整体约16KB体量轻便便于直接运行与二次修改。目录按回归、决策树回归、多项式回归、简单与多元线性回归、SVR及数据预处理等模块组织配套数据文件可支撑从数据清洗、特征选择到模型训练与评估的完整流程演练。已有244人学习下载适合作为课程实验、项目练手或算法对照复现的参考素材帮助读者在R环境中快速理解各算法的调用方式与适用场景。1. 一份把 R 语言机器学习算法拆到能跑的资源到底长什么样很多人对 R 语言的印象还停留在统计检验和画图觉得它跟「机器学习工程」沾不上边。但如果你手头正好有一份按算法逐个实现的 R 语言机器学习资源情况就完全不一样了——它把分类、回归、聚类、降维这些常见任务用 R 的原生语法和经典包一条条写出来而不是调一个黑盒函数就完事。这份资源适合两类人一类是想从零理解算法内部计算过程的开发者另一类是已经会用 Python 跑模型、但需要把逻辑迁移到 R 环境做统计分析的人。它不追求工程化部署重点在于「每个算法都能在本地 R 会话里跑通、看到中间结果、改参数看变化」。接下来我按实际拆包的顺序把这份资源的结构、跑法和改造点讲清楚。2. 技术选型与目录结构为什么用 R 而不是调包2.1 核心依赖与运行环境这份资源的技术栈非常克制没有引入 tidymodels 或 caret 这类高层封装框架而是直接用 R 基础语法配合几个经典包。常见做法是数据处理用base R的data.frame和apply家族矩阵运算用matrix原生支持算法实现尽量手写核心循环只在必要处调用stats包里的lm、glm、kmeans、prcomp等函数作为对照。运行环境上R 版本建议 4.0 以上因为部分矩阵运算和字符串处理在旧版本上有兼容问题。依赖包清单大致如下包名用途是否必须stats线性模型、广义线性模型、聚类、主成分是R 自带MASS判别分析、稳健回归是classKNN 分类与回归是tree决策树构建与剪枝是randomForest随机森林实现是e1071支持向量机、朴素贝叶斯是nnet单层神经网络、多项逻辑回归是cluster轮廓系数、聚类评估是安装命令很直接在 R 控制台里执行# 一次性安装所有依赖包 install.packages(c(MASS, class, tree, randomForest, e1071, nnet, cluster))这里有个细节stats是 R 启动时自动加载的基础包不需要单独安装但如果你在脚本里显式调用library(stats)也不会报错。我一般会在脚本开头统一写library()调用方便一眼看清依赖。2.2 目录组织与文件命名资源解压后目录结构大致是这样的R-ML-Algorithms/ ├── data/ # 数据集目录 │ ├── iris.csv │ ├── mtcars.csv │ └── simulated_reg.csv ├── R/ # 算法实现脚本 │ ├── 01_linear_regression.R │ ├── 02_logistic_regression.R │ ├── 03_knn.R │ ├── 04_decision_tree.R │ ├── 05_random_forest.R │ ├── 06_svm.R │ ├── 07_naive_bayes.R │ ├── 08_kmeans.R │ ├── 09_pca.R │ └── 10_neural_network.R ├── utils/ # 辅助函数 │ ├── plot_utils.R │ └── eval_utils.R └── README.md每个算法脚本都是独立可运行的不依赖其他脚本的输出。utils/里放的是画图函数和评估指标计算比如混淆矩阵、RMSE、轮廓系数这些函数被各个算法脚本用source()引入。这种组织方式的好处是你想看哪个算法直接打开对应文件从头跑到尾就行不用管其他文件的状态。2.3 数据流的统一约定所有脚本遵循同一套数据流约定先读 CSV再做训练测试划分然后建模、预测、评估。划分比例默认 7:3随机种子固定为 42保证结果可复现。常见做法是写一个train_test_split函数放在utils/eval_utils.R里# 简单的训练测试划分函数 train_test_split - function(data, ratio 0.7, seed 42) { set.seed(seed) # 固定随机种子 n - nrow(data) # 样本总数 idx - sample(seq_len(n), size floor(ratio * n)) # 随机抽取训练索引 list( train data[idx, ], # 训练集 test data[-idx, ] # 测试集 ) }参数说明data是输入数据框ratio是训练集占比seed保证每次划分结果一致。返回一个列表用$train和$test取对应数据。这个函数虽然简单但统一了所有脚本的划分逻辑避免每个文件里重复写sample()。3. 核心算法实现拆解从线性回归到神经网络3.1 线性回归的手写梯度下降资源里线性回归给了两种实现一种直接调lm()另一种手写梯度下降。手写版本更能看清算法本质。核心代码如下# 手写梯度下降求解线性回归 gradient_descent_lr - function(X, y, lr 0.01, epochs 1000) { X - cbind(1, X) # 添加截距列 m - nrow(X) # 样本数 n - ncol(X) # 特征数含截距 theta - rep(0, n) # 参数初始化 loss_history - numeric(epochs) # 记录每轮损失 for (i in seq_len(epochs)) { pred - X %*% theta # 预测值 error - pred - y # 残差 grad - (1 / m) * t(X) %*% error # 梯度计算 theta - theta - lr * grad # 参数更新 loss_history[i] - (1 / (2 * m)) * sum(error^2) # MSE损失 } list(theta theta, loss loss_history) }逻辑说明X是特征矩阵y是目标向量。第一步给X加一列全 1对应截距项。梯度公式是(1/m) * X^T * (X*theta - y)这是均方误差对参数的导数。lr控制步长太大会震荡太小收敛慢资源里默认 0.01对标准化后的数据比较稳。epochs是迭代轮数1000 轮对这个小数据集足够。返回的loss_history可以用来画收敛曲线判断是否还需要增加轮数。参数调优建议如果损失曲线下降太慢先把lr调到 0.1 试试如果震荡降到 0.001。特征记得先标准化否则不同量纲的列会导致梯度方向被大数值特征主导。3.2 KNN 的距离计算与 k 值选择KNN 部分没有直接调class::knn()而是先手写了一个距离矩阵计算再调包做对照。手写版本的核心是欧氏距离# 计算欧氏距离矩阵 euclidean_dist - function(A, B) { # A: m x d 矩阵B: n x d 矩阵 # 返回 m x n 距离矩阵 A_sq - rowSums(A^2) # A 每行平方和 B_sq - rowSums(B^2) # B 每行平方和 cross - A %*% t(B) # 交叉项 dist_sq - outer(A_sq, B_sq, ) - 2 * cross sqrt(pmax(dist_sq, 0)) # 防止负数开方 }这里用了矩阵运算加速比双重循环快很多。pmax(dist_sq, 0)是防止浮点误差导致微小负数。得到距离矩阵后对每个测试样本取最近的 k 个训练样本投票决定类别。k 值选择上资源里用了一个简单的循环从 1 到 20 逐个试看测试集准确率# 遍历 k 值找最优 k_values - 1:20 acc - sapply(k_values, function(k) { pred - knn(train train_x, test test_x, cl train_y, k k) mean(pred test_y) }) best_k - k_values[which.max(acc)]注意knn()函数要求输入是矩阵或数据框且不能有因子列。如果数据里有分类特征需要先做哑变量编码。另外 k 一般取奇数避免二分类投票平局。3.3 决策树的分裂准则与剪枝决策树部分用了tree包但资源里把分裂过程用信息增益重新讲了一遍。核心分裂逻辑是遍历每个特征和每个切分点计算分裂后的信息增益# 计算信息熵 entropy - function(y) { p - table(y) / length(y) # 各类别比例 -sum(p * log2(p 1e-10)) # 加小量防log(0) } # 计算按某特征某阈值分裂后的信息增益 info_gain - function(x, y, threshold) { left - y[x threshold] # 左子集标签 right - y[x threshold] # 右子集标签 if (length(left) 0 || length(right) 0) return(0) p_left - length(left) / length(y) # 左子集权重 p_right - 1 - p_left # 右子集权重 parent_entropy - entropy(y) # 父节点熵 child_entropy - p_left * entropy(left) p_right * entropy(right) parent_entropy - child_entropy # 信息增益 }逻辑说明entropy()计算标签分布的混乱程度值越大越不纯。info_gain()遍历一个特征的所有可能切分点选增益最大的。实际建树时对每个特征都算一遍取全局最优。1e-10是防止log2(0)报错。剪枝部分用了tree::prune.tree()通过交叉验证选择复杂度参数cp。资源里给了plotcp()的画图代码能直观看到不同cp下的交叉验证误差。常见坑是cp设太小导致过拟合设太大又欠拟合一般选误差曲线拐点处的值。3.4 随机森林的袋外误差与变量重要性随机森林直接调randomForest包但资源里解释了ntree和mtry两个关键参数。ntree是树的数量默认 500一般够用mtry是每次分裂随机抽取的特征数分类任务默认sqrt(p)回归默认p/3。# 随机森林建模与变量重要性 rf_model - randomForest(Species ~ ., data train, ntree 500, mtry 2, importance TRUE) print(rf_model$confusion) # 混淆矩阵 varImpPlot(rf_model) # 变量重要性图importance TRUE才会计算变量重要性。varImpPlot()画出两个指标MeanDecreaseAccuracy 和 MeanDecreaseGini前者更可靠。袋外误差OOB error在print(rf_model)里直接显示可以当作测试误差的估计不用单独划验证集。注意如果特征里有缺失值randomForest默认用近邻填补但效果不一定好建议先处理缺失。另外mtry可以写个循环调优但资源里没展开留作改造点。3.5 支持向量机与核函数选择SVM 部分用e1071::svm()重点讲了核函数的选择。线性核适合线性可分数据径向基核RBF适合非线性。资源里用 iris 数据集对比了两种核的效果# 线性核与RBF核对比 svm_linear - svm(Species ~ ., data train, kernel linear) svm_rbf - svm(Species ~ ., data train, kernel radial, gamma 0.1, cost 1) pred_linear - predict(svm_linear, test) pred_rbf - predict(svm_rbf, test) cat(线性核准确率:, mean(pred_linear test$Species), \n) cat(RBF核准确率:, mean(pred_rbf test$Species), \n)gamma控制 RBF 核的宽度越大越容易过拟合cost控制惩罚力度越大越严格。资源里建议用tune.svm()做网格搜索但为了保持脚本简洁只给了手动调参的示例。3.6 K-means 与 PCA 的无监督流程K-means 部分手写了迭代过程核心是分配和更新两步循环# 手写K-means my_kmeans - function(X, k, max_iter 100) { centroids - X[sample(nrow(X), k), ] # 随机初始化中心 for (iter in seq_len(max_iter)) { dists - as.matrix(dist(rbind(X, centroids)))[1:nrow(X), (nrow(X)1):(nrow(X)k)] labels - apply(dists, 1, which.min) # 分配簇 new_centroids - t(sapply(seq_len(k), function(i) { colMeans(X[labels i, , drop FALSE]) # 更新中心 })) if (all(new_centroids centroids)) break # 收敛判断 centroids - new_centroids } list(labels labels, centroids centroids) }PCA 部分用prcomp()重点讲了标准化和主成分个数选择。资源里用碎石图scree plot判断保留几个主成分一般选累计方差贡献率到 85% 以上。4. 避坑与常见问题跑 R 机器学习脚本时容易翻车的几个点4.1 因子类型导致建模报错现象调randomForest()或svm()时报错「分类变量不能有超过 53 个水平」或「因子水平不匹配」。原因R 默认把字符串列读成因子如果某列取值太多或者训练集和测试集的因子水平不一致模型函数就会拒绝。解决读数据时加stringsAsFactors FALSE建模前手动把目标列转因子并确保训练测试集用同一套水平data$Species - factor(data$Species, levels c(setosa, versicolor, virginica))4.2 特征量纲不统一导致距离计算失效现象KNN 或 K-means 结果很差准确率接近随机猜。原因不同特征数值范围差异大比如一个特征 0-1另一个 0-10000距离被大数值特征主导。解决建模前对所有数值特征做标准化data_scaled - scale(data[, -target_col])注意scale()返回矩阵如果后面要用数据框记得as.data.frame()。4.3 随机种子没固定导致结果不可复现现象同一份代码跑两次准确率不一样。原因sample()、randomForest()、kmeans()内部都有随机过程没设种子每次结果都变。解决在脚本开头统一set.seed(42)并在每次随机操作前再设一次。资源里所有脚本都在第一行设了种子但如果你自己加代码记得补上。4.4 训练测试划分后索引错位现象预测结果长度和测试集对不上或者评估指标算出来是 NA。原因用sample()划分后训练集和测试集的行名还是原始行名某些函数会按行名对齐导致错位。解决划分后重置行名train - train[sample(nrow(train)), ] rownames(train) - NULL rownames(test) - NULL4.5 包版本冲突导致函数行为不一致现象同样的svm()调用在别人机器上能跑在你这里报参数错误。原因e1071不同版本对gamma默认值处理不同旧版本默认1/ncol新版本可能变了。解决显式写出所有关键参数不依赖默认值。另外用sessionInfo()记录包版本方便排查。5. 二次开发与改造把这份资源变成你自己的算法试验台这份资源最大的价值不是「跑通」而是「改起来方便」。每个算法脚本都是独立的你可以拿其中一个当模板替换数据集、调整参数、甚至换算法实现。我一般会做三件事。第一把utils/eval_utils.R里的评估函数扩展一下。资源里只有准确率和 RMSE实际项目里还需要 F1、AUC、调整兰德指数。加一个evaluate_classification()函数输入真实标签和预测标签输出混淆矩阵和几个常用指标evaluate_classification - function(actual, predicted) { cm - table(Predicted predicted, Actual actual) accuracy - sum(diag(cm)) / sum(cm) precision - diag(cm) / rowSums(cm) recall - diag(cm) / colSums(cm) f1 - 2 * precision * recall / (precision recall) list(confusion cm, accuracy accuracy, precision precision, recall recall, f1 f1) }第二把参数调优从手动改成网格搜索。资源里 KNN 的 k 值选择是写死的循环你可以抽成一个通用函数输入参数列表和评估函数返回最优组合。这样换算法时只改参数范围不用重写循环。第三加一个run_all.R脚本用source()依次跑所有算法把结果汇总到一个数据框里对比。这样你改完某个算法跑一遍就能看到它在所有算法里的排名变化。改造时注意一个点资源里的数据集都是小样本如果你换成大样本手写梯度下降和 KNN 距离矩阵会非常慢。这时候要么改用optim()做优化要么直接用包里的高效实现。我自己的习惯是先用小数据把手写版本跑通理解算法行为再切到包函数处理真实数据。从那以后我每次改算法参数都强制先在小数据上验证逻辑再上大数据跑性能省得等半天才发现公式写错了。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑