最小二乘法原理回顾
最小二乘法是一种数学优化技术,它通过最小化误差的平方和寻找数据的最佳函数匹配,对于线性回归问题,我们目标是找到一条直线 y = ax + b,使得所有数据点到这条直线的垂直距离的平方和最小。
给定一组数据点 (x₁, y₁), (x₂, y₂), ..., (xₙ, yₙ),我们需要求解斜率 a 和截距 b。
最小二乘法的正规方程如下:
-
求解斜率
a:a = (n * Σ(xy) - Σx * Σy) / (n * Σ(x²) - (Σx)²) -
求解截距
b:b = (Σy - a * Σx) / n
n是数据点的数量。Σx是所有x值的和。Σy是所有y值的和。Σ(xy)是所有x * y乘积的和。Σ(x²)是所有x平方的和。
我们的C语言实现将围绕计算这些求和项,然后代入公式求解 a 和 b。
C语言实现步骤
我们将分步实现这个过程:
- 定义数据结构:使用结构体来存储计算出的斜率和截距。
- 核心计算函数:编写一个函数,接收数据点数组、数据点数量作为输入,返回计算出的
a和b。 - 处理边界情况:检查数据点是否足够(至少2个点)以及分母是否为零(所有x值相同,此时直线为垂直,无解)。
- 主函数:创建一个示例数据集,调用核心函数,并打印结果。
完整C语言代码
下面是一个完整的、可运行的C语言实现。
#include <stdio.h>
#include <math.h> // 用于 fabs 函数
// 定义一个结构体来存储线性回归的结果
typedef struct {
double slope; // 斜率 a
double intercept; // 截距 b
int success; // 标记计算是否成功
} LinearRegressionResult;
/**
* @brief 使用最小二乘法计算线性回归的斜率和截距
*
* @param points 数据点数组,每个点是一个包含x和y的结构体
* @param numPoints 数据点的数量
* @return LinearRegressionResult 包含斜率、截距和成功状态的结构体
*/
LinearRegressionResult leastSquares(double points[][2], int numPoints) {
LinearRegressionResult result;
result.success = 0; // 默认设为失败
// 1. 检查数据点数量是否足够
if (numPoints < 2) {
printf("错误: 至少需要2个数据点才能进行线性回归,\n");
return result;
}
// 2. 初始化求和变量
double sumX = 0.0;
double sumY = 0.0;
double sumXY = 0.0;
double sumXSquare = 0.0;
// 3. 遍历数据点,计算各项求和
for (int i = 0; i < numPoints; i++) {
double x = points[i][0];
double y = points[i][1];
sumX += x;
sumY += y;
sumXY += x * y;
sumXSquare += x * x;
}
// 4. 计算斜率 a 和截距 b
// 分母: n * Σ(x²) - (Σx)²
double denominator = numPoints * sumXSquare - sumX * sumX;
// 5. 检查分母是否为零(所有x值都相同)
if (fabs(denominator) < 1e-9) { // 使用一个很小的数来避免浮点数精度问题
printf("错误: 所有x值都相同,无法计算唯一的斜率(分母为零),\n");
return result;
}
// 计算斜率 a
double numerator_a = numPoints * sumXY - sumX * sumY;
result.slope = numerator_a / denominator;
// 计算截距 b
result.intercept = (sumY - result.slope * sumX) / numPoints;
// 标记计算成功
result.success = 1;
return result;
}
int main() {
// 示例数据集
// 假设我们有以下 (x, y) 点:
// (1, 1), (2, 2), (3, 3), (4, 4.5), (5, 5)
// 这条线应该大致接近 y = 1.1x - 0.2
double dataPoints[][2] = {
{1.0, 1.0},
{2.0, 2.0},
{3.0, 3.0},
{4.0, 4.5},
{5.0, 5.0}
};
int numDataPoints = sizeof(dataPoints) / sizeof(dataPoints[0]);
// 调用最小二乘法函数
LinearRegressionResult lr = leastSquares(dataPoints, numDataPoints);
// 打印结果
if (lr.success) {
printf("最小二乘法计算成功!\n");
printf("拟合的直线方程为: y = %.4fx + %.4f\n", lr.slope, lr.intercept);
// 验证一下结果
double x_test = 6.0;
double y_predicted = lr.slope * x_test + lr.intercept;
printf("当 x = %.1f 时,预测的 y 值为: %.4f\n", x_test, y_predicted);
} else {
printf("最小二乘法计算失败,请检查输入数据,\n");
}
return 0;
}
代码讲解
-
LinearRegressionResult结构体:- 我们定义了这个结构体来让函数返回多个值(斜率、截距和状态),这比使用指针参数传递更清晰、更安全。
slope: 存储计算得到的a。intercept: 存储计算得到的b。success: 一个int类型的标志。1表示成功,0表示失败,这是一种简单的错误处理机制。
-
leastSquares函数:- 参数:
double points[][2]是一个二维数组,表示数据点。int numPoints是点的数量。 - 初始化:
sumX,sumY,sumXY,sumXSquare被初始化为0,用于累加。 - 求和循环:
for循环遍历所有数据点,根据公式计算四个核心求和项。 - 分母检查: 这是至关重要的一步,如果所有
x值都相同,分母n * Σ(x²) - (Σx)²会变为零,导致除零错误,我们使用fabs(denominator) < 1e-9来判断,因为浮点数计算可能存在微小的精度误差,直接判断== 0不可靠。 - 计算
a和b: 如果分母不为零,就按照正规方程公式计算斜率和截距。 - 返回结果: 将计算结果和成功状态填充到
LinearRegressionResult结构体中并返回。
- 参数:
-
main函数:- 定义数据: 我们创建了一个名为
dataPoints的二维数组来存储示例数据。 - 计算点数:
sizeof(dataPoints) / sizeof(dataPoints[0])是一个计算C数组元素个数的常用技巧。 - 调用函数: 调用
leastSquares函数并接收返回结果。 - 打印结果: 检查
result.success,如果成功,则格式化打印出最终的直线方程y = ax + b,我们还增加了一个简单的预测来展示结果的应用。
- 定义数据: 我们创建了一个名为
如何编译和运行
- 将上述代码保存为一个文件,
linear_regression.c。 - 打开终端或命令提示符。
- 使用GCC编译器进行编译,由于代码中包含了
math.h,我们需要链接数学库 (-lm)。gcc linear_regression.c -o linear_regression -lm
gcc: 编译器linear_regression.c: 源文件-o linear_regression: 指定输出的可执行文件名-lm: 链接数学库
- 运行生成的可执行文件:
./linear_regression
预期输出:
最小二乘法计算成功!
拟合的直线方程为: y = 1.1000x + -0.2000
当 x = 6.0 时,预测的 y 值为: 6.4000
这个输出与我们预期的 y ≈ 1.1x - 0.2 非常吻合,证明代码是正确的。
