090. 使用C语言实现简单的机器学习算法

在C语言中实现一个简单的机器学习算法是一个很好的学习项目,可以帮助你理解机器学习的基本原理。这里将展示一个简单的线性回归算法的实现。线性回归是一种基本的监督学习算法,用于预测连续的输出值。

线性回归算法简介

线性回归的目标是找到一个线性模型 y=θ0​+θ1​x,使得模型的预测值与实际值之间的误差最小化。通常使用最小二乘法来求解最优的参数 θ0​ 和 θ1​。

算法步骤

  1. 数据准备:准备训练数据集,包括输入特征 x 和目标值 y。
  2. 初始化参数:初始化线性模型的参数 θ0​ 和 θ1​。
  3. 计算预测值:使用当前参数计算预测值 y^​=θ0​+θ1​x。
  4. 计算损失:计算预测值与实际值之间的误差,通常使用均方误差(MSE)。
  5. 更新参数:通过梯度下降法更新参数,以最小化损失函数。
  6. 迭代优化:重复步骤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;
}

代码说明

  1. 数据准备:定义了简单的训练数据集 xy
  2. 初始化参数: 初始化线性模型的参数 θ0​ 和 θ1​ 为 0。
  3. 预测函数predict 函数根据当前参数计算预测值。
  4. 损失函数computeCost 函数计算均方误差(MSE)。
  5. 梯度下降法:在 gradientDescent 函数中,通过计算梯度并更新参数来最小化损失函数。
  6. 迭代优化:每隔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 个簇,通过最小化簇内数据点与簇中心的距离来实现。算法的基本步骤如下:

  1. 初始化:随机选择 K 个数据点作为初始簇中心(centroids)。
  2. 分配簇:将每个数据点分配到最近的簇中心所在的簇。
  3. 更新簇中心:重新计算每个簇的中心(即簇内所有点的均值)。
  4. 迭代:重复步骤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;
}

代码说明

数据点和簇中心

  • 使用结构体 DataPointCluster 分别表示数据点和簇中心。

  • 数据点存储在全局数组 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)