线性回归是机器学习领域最基础的算法之一,它通过寻找最佳拟合直线来描述自变量(x)和因变量(y)之间的线性关系。在嵌入式开发、物联网设备等资源受限环境中,用C语言实现线性回归模型具有独特优势——它不依赖任何第三方库,可以直接部署在各种微控制器上。
这个实现的核心是求解回归系数(斜率和截距),数学上通过最小二乘法计算。公式推导过程如下:
在C语言中,我们需要:
注意:虽然现代机器学习框架如TensorFlow、PyTorch提供了现成的线性回归实现,但理解底层数学原理和手动编码实现,对深入掌握算法本质至关重要。这也是为什么许多高校的机器学习课程仍要求学生用C/Java等基础语言实现算法。
考虑到C语言的特性,我们采用最简洁的数据表示方式:
c复制// 数据集结构体
typedef struct {
double *x; // 特征数组
double *y; // 目标值数组
int size; // 数据量
} Dataset;
// 模型参数结构体
typedef struct {
double slope; // 斜率b1
double intercept; // 截距b0
} LinearModel;
这种设计有三大优势:
c复制// 计算数组和
double sum(double arr[], int n) {
double total = 0;
for(int i=0; i<n; i++) {
total += arr[i];
}
return total;
}
// 计算数组乘积和
double sum_product(double arr1[], double arr2[], int n) {
double total = 0;
for(int i=0; i<n; i++) {
total += arr1[i] * arr2[i];
}
return total;
}
c复制void train_model(LinearModel *model, Dataset data) {
double x_sum = sum(data.x, data.size);
double y_sum = sum(data.y, data.size);
double xy_sum = sum_product(data.x, data.y, data.size);
double xx_sum = sum_product(data.x, data.x, data.size);
double denominator = data.size * xx_sum - x_sum * x_sum;
model->slope = (data.size * xy_sum - x_sum * y_sum) / denominator;
model->intercept = (y_sum - model->slope * x_sum) / data.size;
}
编程技巧:在分母计算前可以添加一个极小值(如1e-10)防止除零错误,这是实际工程中的常见做法。
c复制#include <stdio.h>
#include <stdlib.h>
typedef struct {
double *x;
double *y;
int size;
} Dataset;
typedef struct {
double slope;
double intercept;
} LinearModel;
double sum(double arr[], int n) {
double total = 0;
for(int i=0; i<n; i++) total += arr[i];
return total;
}
double sum_product(double arr1[], double arr2[], int n) {
double total = 0;
for(int i=0; i<n; i++) total += arr1[i] * arr2[i];
return total;
}
void train_model(LinearModel *model, Dataset data) {
double x_sum = sum(data.x, data.size);
double y_sum = sum(data.y, data.size);
double xy_sum = sum_product(data.x, data.y, data.size);
double xx_sum = sum_product(data.x, data.x, data.size);
double denominator = data.size * xx_sum - x_sum * x_sum;
// 添加极小值防止除零
denominator += (denominator == 0) ? 1e-10 : 0;
model->slope = (data.size * xy_sum - x_sum * y_sum) / denominator;
model->intercept = (y_sum - model->slope * x_sum) / data.size;
}
double predict(LinearModel model, double x) {
return model.intercept + model.slope * x;
}
int main() {
// 示例数据:房屋面积(平米) vs 价格(万元)
double x[] = {50, 60, 70, 80, 90};
double y[] = {150, 180, 210, 240, 270};
int data_size = sizeof(x)/sizeof(x[0]);
Dataset data = {x, y, data_size};
LinearModel model;
train_model(&model, data);
printf("训练结果:\n");
printf("斜率(b1): %.4f\n", model.slope);
printf("截距(b0): %.4f\n", model.intercept);
// 预测100平米房屋价格
double new_x = 100;
printf("\n预测 %.1f 平米的价格: %.2f 万元\n",
new_x, predict(model, new_x));
return 0;
}
编译运行后程序输出:
code复制训练结果:
斜率(b1): 3.0000
截距(b0): 0.0000
预测 100.0 平米的价格: 300.00 万元
这个结果完全符合预期,因为我们的示例数据是完美的线性关系(y=3x)。实际项目中数据会有噪声,斜率和截距通常不会是整齐的数字。
原始实现存在潜在的数值问题:
改进方案:
c复制// 使用均值中心化改进的train_model
void train_model_improved(LinearModel *model, Dataset data) {
double x_mean = sum(data.x, data.size) / data.size;
double y_mean = sum(data.y, data.size) / data.size;
double variance = 0, covariance = 0;
for(int i=0; i<data.size; i++) {
double x_diff = data.x[i] - x_mean;
variance += x_diff * x_diff;
covariance += x_diff * (data.y[i] - y_mean);
}
model->slope = covariance / variance;
model->intercept = y_mean - model->slope * x_mean;
}
在嵌入式环境中要特别注意:
改进后的数据结构:
c复制#define MAX_DATA_SIZE 100
typedef struct {
double x[MAX_DATA_SIZE];
double y[MAX_DATA_SIZE];
int size;
} SafeDataset;
c复制// 计算R平方系数
double r_squared(LinearModel model, Dataset data) {
double y_mean = sum(data.y, data.size) / data.size;
double ss_total = 0, ss_res = 0;
for(int i=0; i<data.size; i++) {
ss_total += pow(data.y[i] - y_mean, 2);
double y_pred = predict(model, data.x[i]);
ss_res += pow(data.y[i] - y_pred, 2);
}
return 1 - (ss_res / ss_total);
}
c复制// 保存模型到文件
void save_model(LinearModel model, const char* filename) {
FILE *file = fopen(filename, "w");
if(file) {
fprintf(file, "%.15lf\n%.15lf", model.intercept, model.slope);
fclose(file);
}
}
// 从文件加载模型
void load_model(LinearModel *model, const char* filename) {
FILE *file = fopen(filename, "r");
if(file) {
fscanf(file, "%lf\n%lf", &model->intercept, &model->slope);
fclose(file);
}
}
在资源受限设备上,浮点运算可能代价高昂。可以采用定点数算术:
c复制typedef int32_t fixed_t;
#define FIXED_SCALE 1024 // Q10.21格式
fixed_t float_to_fixed(double x) {
return (fixed_t)(x * FIXED_SCALE);
}
double fixed_to_float(fixed_t x) {
return (double)x / FIXED_SCALE;
}
// 定点数版本的求和
fixed_t sum_fixed(fixed_t arr[], int n) {
fixed_t total = 0;
for(int i=0; i<n; i++) {
total += arr[i];
}
return total;
}
对于流式数据,可以实现在线学习算法:
c复制void online_learn(LinearModel *model, double x, double y, double learning_rate) {
double prediction = predict(*model, x);
double error = y - prediction;
// 梯度下降更新
model->intercept += learning_rate * error;
model->slope += learning_rate * error * x;
}
对于大数据集,可以使用OpenMP并行化求和计算:
c复制#include <omp.h>
double parallel_sum(double arr[], int n) {
double total = 0;
#pragma omp parallel for reduction(+:total)
for(int i=0; i<n; i++) {
total += arr[i];
}
return total;
}
在实际项目中,我通常会先实现基础版本确保算法正确性,然后再逐步添加这些优化。过早优化是万恶之源,但了解这些技术路线对处理真实场景至关重要。