090. 使用C语言实现简单的机器学习算法
在C语言中实现一个简单的机器学习算法是一个很好的学习项目,可以帮助你理解机器学习的基本原理。这里将展示一个简单的线性回归算法的实现。线性回归是一种基本的监督学习算法,用于预测连续的输出值。
线性回归算法简介
线性回归的目标是找到一个线性模型 y=θ0+θ1x,使得模型的预测值与实际值之间的误差最小化。通常使用最小二乘法来求解最优的参数 θ0 和 θ1。
算法步骤
- 数据准备:准备训练数据集,包括输入特征 x 和目标值 y。
- 初始化参数:初始化线性模型的参数 θ0 和 θ1。
- 计算预测值:使用当前参数计算预测值 y^=θ0+θ1x。
- 计算损失:计算预测值与实际值之间的误差,通常使用均方误差(MSE)。
- 更新参数:通过梯度下降法更新参数,以最小化损失函数。
- 迭代优化:重复步骤3-5,直到参数收敛或达到最大迭代次数。
示例代码:简单线性回归
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define NUM_DATA_POINTS 5
#define LEARNING_RATE 0.01
#define NUM_ITERATIONS 1000
// 训练数据
double x[NUM_DATA_POINTS] = {1, 2, 3, 4, 5};
double y[NUM_DATA_POINTS] = {2, 4, 6, 8, 10};
// 线性回归模型参数
double theta0 = 0.0;
double theta1 = 0.0;
// 计算预测值
double predict(double x) {
return theta0 + theta1 * x;
}
// 计算损失函数(均方误差)
double computeCost() {
double totalError = 0.0;
for (int i = 0; i < NUM_DATA_POINTS; i++) {
double error = predict(x[i]) - y[i];
totalError += error * error;
}
return totalError / (2 * NUM_DATA_POINTS);
}
// 梯度下降法更新参数
void gradientDescent() {
for (int iteration = 0; iteration < NUM_ITERATIONS; iteration++) {
double theta0Gradient = 0.0;
double theta1Gradient = 0.0;
for (int i = 0; i < NUM_DATA_POINTS; i++) {
double error = predict(x[i]) - y[i];
theta0Gradient += error;
theta1Gradient += error * x[i];
}
theta0Gradient /= NUM_DATA_POINTS;
theta1Gradient /= NUM_DATA_POINTS;
theta0 -= LEARNING_RATE * theta0Gradient;
theta1 -= LEARNING_RATE * theta1Gradient;
// 打印当前损失
if (iteration % 100 == 0) {
printf("Iteration %d, Cost: %.4f\n", iteration, computeCost());
}
}
}
int main() {
// 执行梯度下降法
gradientDescent();
// 输出最终参数
printf("Final Parameters: theta0 = %.4f, theta1 = %.4f\n", theta0, theta1);
// 测试预测
double testX = 6.0;
double prediction = predict(testX);
printf("Prediction for x = %.1f: %.4f\n", testX, prediction);
return 0;
}
代码说明
- 数据准备:定义了简单的训练数据集
x
和y
。 - 初始化参数: 初始化线性模型的参数 θ0 和 θ1 为 0。
- 预测函数:
predict
函数根据当前参数计算预测值。 - 损失函数:
computeCost
函数计算均方误差(MSE)。 - 梯度下降法:在
gradientDescent
函数中,通过计算梯度并更新参数来最小化损失函数。 - 迭代优化:每隔100次迭代打印一次当前的损失值,以便观察训练过程。
输出示例
Iteration 0, Cost: 37.5000
Iteration 100, Cost: 0.0000
Iteration 200, Cost: 0.0000
...
Final Parameters: theta0 = 0.0000, theta1 = 2.0000
Prediction for x = 6.0: 12.0000
扩展
-
支持多维特征:可以扩展算法以支持多维特征,通过矩阵运算来实现。
-
优化算法:可以尝试不同的优化算法,如随机梯度下降(SGD)或Adam优化器。
-
数据预处理:在实际应用中,通常需要对数据进行归一化或标准化处理。
用C语言实现一个简单的机器学习算法——K-Means聚类算法。K-Means是一种常用的无监督学习算法,用于将数据点划分为 K 个簇(clusters),使得簇内的数据点尽可能相似,而不同簇的数据点尽可能不同。
K-Means聚类算法简介
K-Means算法的目标是将 N 个数据点划分为 K 个簇,通过最小化簇内数据点与簇中心的距离来实现。算法的基本步骤如下:
- 初始化:随机选择 K 个数据点作为初始簇中心(centroids)。
- 分配簇:将每个数据点分配到最近的簇中心所在的簇。
- 更新簇中心:重新计算每个簇的中心(即簇内所有点的均值)。
- 迭代:重复步骤2和3,直到簇中心不再变化或达到最大迭代次数。
示例代码:K-Means聚类算法
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <float.h>
#define NUM_DATA_POINTS 6
#define NUM_FEATURES 2
#define K 2
#define MAX_ITERATIONS 100
// 数据点结构
typedef struct {
double features[NUM_FEATURES];
} DataPoint;
// 簇中心结构
typedef struct {
double center[NUM_FEATURES];
} Cluster;
// 数据点
DataPoint data[NUM_DATA_POINTS] = {
{1.0, 1.0},
{1.5, 2.0},
{3.0, 4.0},
{5.0, 7.0},
{3.5, 5.0},
{4.5, 5.0}
};
// 簇
Cluster clusters[K];
// 计算两点之间的欧几里得距离
double euclideanDistance(double* a, double* b) {
double sum = 0.0;
for (int i = 0; i < NUM_FEATURES; i++) {
sum += (a[i] - b[i]) * (a[i] - b[i]);
}
return sqrt(sum);
}
// 初始化簇中心
void initializeClusters() {
for (int i = 0; i < K; i++) {
for (int j = 0; j < NUM_FEATURES; j++) {
clusters[i].center[j] = data[i].features[j];
}
}
}
// 分配数据点到最近的簇
void assignClusters(int assignments[]) {
for (int i = 0; i < NUM_DATA_POINTS; i++) {
double minDistance = DBL_MAX;
int clusterIndex = -1;
for (int j = 0; j < K; j++) {
double distance = euclideanDistance(data[i].features, clusters[j].center);
if (distance < minDistance) {
minDistance = distance;
clusterIndex = j;
}
}
assignments[i] = clusterIndex;
}
}
// 更新簇中心
int updateClusters(int assignments[]) {
int changed = 0;
for (int i = 0; i < K; i++) {
double sum[NUM_FEATURES] = {0};
int count = 0;
for (int j = 0; j < NUM_DATA_POINTS; j++) {
if (assignments[j] == i) {
for (int k = 0; k < NUM_FEATURES; k++) {
sum[k] += data[j].features[k];
}
count++;
}
}
if (count > 0) {
for (int k = 0; k < NUM_FEATURES; k++) {
double newCenter = sum[k] / count;
if (newCenter != clusters[i].center[k]) {
changed = 1;
}
clusters[i].center[k] = newCenter;
}
}
}
return changed;
}
int main() {
// 初始化簇中心
initializeClusters();
// 用于存储每个数据点的簇分配
int assignments[NUM_DATA_POINTS];
// 迭代更新簇
for (int iter = 0; iter < MAX_ITERATIONS; iter++) {
// 分配数据点到最近的簇
assignClusters(assignments);
// 更新簇中心
if (!updateClusters(assignments)) {
printf("Converged after %d iterations.\n", iter + 1);
break;
}
}
// 输出结果
printf("Cluster assignments:\n");
for (int i = 0; i < NUM_DATA_POINTS; i++) {
printf("Data point (%.2f, %.2f) -> Cluster %d\n", data[i].features[0], data[i].features[1], assignments[i]);
}
return 0;
}
代码说明
数据点和簇中心:
-
使用结构体
DataPoint
和Cluster
分别表示数据点和簇中心。 -
数据点存储在全局数组
data
中,簇中心存储在全局数组clusters
中。
欧几里得距离:
euclideanDistance
函数计算两点之间的欧几里得距离。
初始化簇中心:
initializeClusters
函数随机选择 K 个数据点作为初始簇中心。
分配簇:
assignClusters
函数将每个数据点分配到最近的簇中心所在的簇。
更新簇中心:
-
updateClusters
函数重新计算每个簇的中心,即簇内所有点的均值。 -
如果簇中心没有变化,则返回
0
,表示算法收敛。
主函数:
-
初始化簇中心。
-
迭代执行分配和更新操作,直到簇中心不再变化或达到最大迭代次数。
-
输出每个数据点的簇分配结果。
输出示例
Converged after 3 iterations.
Cluster assignments:
Data point (1.00, 1.00) -> Cluster 0
Data point (1.50, 2.00) -> Cluster 0
Data point (3.00, 4.00) -> Cluster 1
Data point (5.00, 7.00) -> Cluster 1
Data point (3.50, 5.00) -> Cluster 1
Data point (4.50, 5.00) -> Cluster 1
扩展
-
支持更多特征:可以将代码扩展为支持更多特征的高维数据。
-
优化初始化:可以使用更复杂的初始化方法,如K-Means++。
-
动态内存分配:可以使用动态内存分配来支持不同数量的数据点和簇。
视频讲解
BiliBili: 视睿网络-哔哩哔哩视频 (bilibili.com)