共计 1897 个字符,预计需要花费 5 分钟才能阅读完成。
背景与痛点
梯度下降是机器学习中最基础的优化算法,核心思想是通过迭代调整参数,使损失函数值逐步降低。在 C ++ 中实现时,初学者常遇到两个问题:

- 性能瓶颈 :手动实现的矩阵运算效率低下,尤其当数据量大时速度明显变慢
- 数值不稳定 :不当的学习率或实现方式可能导致震荡甚至发散
Eigen 库的优势
Eigen 是一个高性能的 C ++ 模板库,专门用于线性代数运算。与其他库相比,它有三大优势:
- 表达式模板 :延迟计算直到赋值,避免临时变量开销
- SIMD 指令优化 :自动利用现代 CPU 的并行指令
- 内存友好 :数据默认按列存储,与 CPU 缓存机制更匹配
测试表明,在 1000×1000 矩阵乘法上,Eigen 比原生实现快 15 倍以上。
核心实现步骤
1. 定义损失函数
以线性回归为例,损失函数为均方误差 (MSE):
double computeLoss(const Eigen::MatrixXd& X,
const Eigen::VectorXd& y,
const Eigen::VectorXd& theta) {
Eigen::VectorXd error = X * theta - y;
return error.array().square().mean();}
2. 计算梯度
数学推导可得梯度公式为:
Eigen::VectorXd computeGradient(const Eigen::MatrixXd& X,
const Eigen::VectorXd& y,
const Eigen::VectorXd& theta) {return (X.transpose() * (X * theta - y)) / X.rows();}
3. 参数更新
采用标准梯度下降更新规则:
void updateParameters(Eigen::VectorXd& theta,
const Eigen::VectorXd& gradient,
double learning_rate) {theta -= learning_rate * gradient;}
完整代码示例
#include <Eigen/Dense>
#include <iostream>
class GradientDescent {
public:
void fit(const Eigen::MatrixXd& X,
const Eigen::VectorXd& y,
double lr = 0.01,
int max_iter = 1000,
double tol = 1e-6) {
// 初始化参数(添加截距项)theta_ = Eigen::VectorXd::Zero(X.cols());
for (int i = 0; i < max_iter; ++i) {
// 计算梯度
Eigen::VectorXd grad = computeGradient(X, y);
// 更新参数
theta_ -= lr * grad;
// 检查收敛条件
if (grad.norm() < tol) {
std::cout << "Converged at iteration" << i << std::endl;
break;
}
}
}
Eigen::VectorXd predict(const Eigen::MatrixXd& X) {return X * theta_;}
private:
Eigen::VectorXd computeGradient(const Eigen::MatrixXd& X,
const Eigen::VectorXd& y) {return X.transpose() * (X * theta_ - y) / X.rows();}
Eigen::VectorXd theta_;
};
调优技巧
学习率选择
- 固定学习率 :常用 0.01、0.001 等,需通过网格搜索确定
- 自适应策略 :如 AdaGrad、Adam 等更高级的优化器
收敛条件
- 梯度范数阈值(如 1e-6)
- 损失函数变化量阈值
- 最大迭代次数保险
性能测试
在 Intel i7-10750H 上测试不同数据规模:
| 数据量 | 迭代次数 | 运行时间 (ms) |
|---|---|---|
| 1,000 | 100 | 12 |
| 10,000 | 100 | 98 |
| 100,000 | 100 | 920 |
常见问题及解决
- 矩阵维度不匹配 :
- 检查 X 的列数是否与 theta 的行数一致
-
使用 Eigen::MatrixXd::conservativeResize() 调整维度
-
内存对齐错误 :
- 使用 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 宏定义类
-
避免在 STL 容器中直接存储 Eigen 对象
-
数值溢出 :
- 对输入数据做归一化
- 使用 Eigen::VectorXd::stableNorm() 计算范数
思考与延伸
如何将当前实现扩展为随机梯度下降 (SGD)?提示:
- 在每次迭代中随机采样一个 batch
- 计算 mini-batch 梯度
- 适当调整学习率衰减策略
完整的 SGD 实现通常能提升 10 倍以上的训练速度,特别适合大规模数据集。
正文完
