第二十三章 机器学习算法:数据中的智慧觉醒

“数据是新的石油,而机器学习是内燃机。” —— 克莱夫·亨比

机器学习正在重塑我们理解和交互世界的方式。本章将深入探索机器学习的核心算法,揭示如何让计算机从数据中学习规律,做出智能决策。从基础数学原理到现代深度学习,我们将构建完整的机器学习知识体系。

23.1 机器学习全景图

23.1.1 机器学习三大范式

机器学习
监督学习
无监督学习
强化学习
分类
回归
聚类
降维
决策
控制

23.1.2 机器学习工作流程

  1. 数据收集:获取原始数据
  2. 数据预处理:清洗、转换数据
  3. 特征工程:提取有意义的特征
  4. 模型选择:选择合适算法
  5. 模型训练:学习数据模式
  6. 模型评估:验证性能
  7. 模型部署:实际应用

23.2 监督学习:从标记数据中学习

23.2.1 线性回归:预测连续值

#include <stdio.h>
#include <stdlib.h>

// 简单线性回归模型 y = wx + b
typedef struct {
    double w; // 权重
    double b; // 偏置
} LinearModel;

// 训练线性回归模型
void train_linear_regression(LinearModel* model, double X[], double y[], int n, double lr, int epochs) {
    // 初始化参数
    model->w = 0.0;
    model->b = 0.0;
    
    for (int epoch = 0; epoch < epochs; epoch++) {
        double total_error = 0.0;
        double dw = 0.0, db = 0.0;
        
        // 计算梯度
        for (int i = 0; i < n; i++) {
            double prediction = model->w * X[i] + model->b;
            double error = prediction - y[i];
            
            dw += error * X[i];
            db += error;
            total_error += error * error;
        }
        
        // 更新参数
        model->w -= lr * (2.0/n) * dw;
        model->b -= lr * (2.0/n) * db;
        
        if (epoch % 100 == 0) {
            printf("Epoch %d, Loss: %.4f\n", epoch, total_error/n);
        }
    }
}

int main() {
    // 训练数据:房屋面积(㎡) -> 价格(万元)
    double areas[] = {50, 70, 90, 110, 130};
    double prices[] = {320, 420, 500, 580, 650};
    int n = sizeof(areas)/sizeof(areas[0]);
    
    LinearModel model;
    train_linear_regression(&model, areas, prices, n, 0.01, 1000);
    
    printf("\n训练结果: y = %.4fx + %.4f\n", model.w, model.b);
    
    // 预测新数据
    double new_area = 100;
    double predicted_price = model.w * new_area + model.b;
    printf("预测: %.0f㎡ 房屋价格 ≈ %.2f万元\n", new_area, predicted_price);
    
    return 0;
}

23.2.2 逻辑回归:二分类问题

#include <math.h>

typedef struct {
    double w[2]; // 权重向量
    double b;    // 偏置
} LogisticModel;

// Sigmoid激活函数
double sigmoid(double z) {
    return 1.0 / (1.0 + exp(-z));
}

// 训练逻辑回归模型
void train_logistic_regression(LogisticModel* model, double X[][2], int y[], int n, double lr, int epochs) {
    // 初始化参数
    model->w[0] = 0.0;
    model->w[1] = 0.0;
    model->b = 0.0;
    
    for (int epoch = 0; epoch < epochs; epoch++) {
        double total_loss = 0.0;
        double dw0 = 0.0, dw1 = 0.0, db = 0.0;
        
        for (int i = 0; i < n; i++) {
            // 前向传播
            double z = model->w[0]*X[i][0] + model->w[1]*X[i][1] + model->b;
            double prediction = sigmoid(z);
            
            // 计算损失 (二元交叉熵)
            double loss = -y[i]*log(prediction) - (1-y[i])*log(1-prediction);
            total_loss += loss;
            
            // 反向传播
            double dz = prediction - y[i];
            dw0 += dz * X[i][0];
            dw1 += dz * X[i][1];
            db += dz;
        }
        
        // 更新参数
        model->w[0] -= lr * (1.0/n) * dw0;
        model->w[1] -= lr * (1.0/n) * dw1;
        model->b -= lr * (1.0/n) * db;
        
        if (epoch % 100 == 0) {
            printf("Epoch %d, Loss: %.4f\n", epoch, total_loss/n);
        }
    }
}

// 预测函数
int predict(LogisticModel* model, double x1, double x2) {
    double z = model->w[0]*x1 + model->w[1]*x2 + model->b;
    return sigmoid(z) > 0.5 ? 1 : 0;
}

23.3 无监督学习:发现隐藏结构

23.3.1 K均值聚类

#include <stdlib.h>
#include <float.h>
#include <math.h>

typedef struct {
    double x;
    double y;
} Point;

void kmeans(Point data[], int n, int k, Point centroids[]) {
    // 1. 随机初始化聚类中心
    for (int i = 0; i < k; i++) {
        centroids[i] = data[rand() % n];
    }
    
    int* assignments = malloc(n * sizeof(int));
    int changed;
    
    do {
        changed = 0;
        
        // 2. 分配点到最近的聚类中心
        for (int i = 0; i < n; i++) {
            double min_dist = DBL_MAX;
            int best_cluster = -1;
            
            for (int j = 0; j < k; j++) {
                double dx = data[i].x - centroids[j].x;
                double dy = data[i].y - centroids[j].y;
                double dist = sqrt(dx*dx + dy*dy);
                
                if (dist < min_dist) {
                    min_dist = dist;
                    best_cluster = j;
                }
            }
            
            if (assignments[i] != best_cluster) {
                assignments[i] = best_cluster;
                changed = 1;
            }
        }
        
        // 3. 更新聚类中心
        int* counts = calloc(k, sizeof(int));
        Point* sums = calloc(k, sizeof(Point));
        
        for (int i = 0; i < n; i++) {
            int cluster = assignments[i];
            sums[cluster].x += data[i].x;
            sums[cluster].y += data[i].y;
            counts[cluster]++;
        }
        
        for (int j = 0; j < k; j++) {
            if (counts[j] > 0) {
                centroids[j].x = sums[j].x / counts[j];
                centroids[j].y = sums[j].y / counts[j];
            }
        }
        
        free(counts);
        free(sums);
    } while (changed);
    
    free(assignments);
}

23.3.2 主成分分析(PCA)

#include <math.h>

// 计算协方差矩阵
void covariance_matrix(double data[][2], int n, double cov[][2]) {
    double mean_x = 0, mean_y = 0;
    
    // 计算均值
    for (int i = 0; i < n; i++) {
        mean_x += data[i][0];
        mean_y += data[i][1];
    }
    mean_x /= n;
    mean_y /= n;
    
    // 计算协方差
    cov[0][0] = 0; cov[0][1] = 0; cov[1][0] = 0; cov[1][1] = 0;
    
    for (int i = 0; i < n; i++) {
        double dx = data[i][0] - mean_x;
        double dy = data[i][1] - mean_y;
        
        cov[0][0] += dx * dx;
        cov[0][1] += dx * dy;
        cov[1][0] += dy * dx;
        cov[1][1] += dy * dy;
    }
    
    cov[0][0] /= n;
    cov[0][1] /= n;
    cov[1][0] /= n;
    cov[1][1] /= n;
}

// PCA计算主成分
void pca(double data[][2], int n, double components[][2]) {
    double cov[2][2];
    covariance_matrix(data, n, cov);
    
    // 计算特征值和特征向量
    double trace = cov[0][0] + cov[1][1];
    double det = cov[0][0]*cov[1][1] - cov[0][1]*cov[1][0];
    
    double lambda1 = (trace + sqrt(trace*trace - 4*det)) / 2;
    double lambda2 = (trace - sqrt(trace*trace - 4*det)) / 2;
    
    // 第一主成分
    components[0][0] = cov[0][1];
    components[0][1] = lambda1 - cov[0][0];
    
    // 归一化
    double len = sqrt(components[0][0]*components[0][0] + components[0][1]*components[0][1]);
    components[0][0] /= len;
    components[0][1] /= len;
    
    // 第二主成分
    components[1][0] = -components[0][1];
    components[1][1] = components[0][0];
}

23.4 神经网络:模拟人脑的智能

23.4.1 神经网络基础结构

输入层
隐藏层1
隐藏层2
输出层

23.4.2 多层感知机实现

#include <math.h>
#include <stdlib.h>

#define INPUT_SIZE 2
#define HIDDEN_SIZE 4
#define OUTPUT_SIZE 1

typedef struct {
    double weights_ih[INPUT_SIZE][HIDDEN_SIZE];
    double weights_ho[HIDDEN_SIZE][OUTPUT_SIZE];
    double bias_h[HIDDEN_SIZE];
    double bias_o[OUTPUT_SIZE];
} NeuralNetwork;

// ReLU激活函数
double relu(double x) {
    return x > 0 ? x : 0;
}

// ReLU导数
double relu_derivative(double x) {
    return x > 0 ? 1 : 0;
}

// 初始化神经网络
void init_nn(NeuralNetwork* nn) {
    // 初始化输入到隐藏层权重
    for (int i = 0; i < INPUT_SIZE; i++) {
        for (int j = 0; j < HIDDEN_SIZE; j++) {
            nn->weights_ih[i][j] = ((double)rand()/RAND_MAX) * 2 - 1; // [-1,1]
        }
    }
    
    // 初始化隐藏到输出层权重
    for (int i = 0; i < HIDDEN_SIZE; i++) {
        for (int j = 0; j < OUTPUT_SIZE; j++) {
            nn->weights_ho[i][j] = ((double)rand()/RAND_MAX) * 2 - 1;
        }
    }
    
    // 初始化偏置
    for (int i = 0; i < HIDDEN_SIZE; i++) {
        nn->bias_h[i] = ((double)rand()/RAND_MAX) * 2 - 1;
    }
    
    for (int i = 0; i < OUTPUT_SIZE; i++) {
        nn->bias_o[i] = ((double)rand()/RAND_MAX) * 2 - 1;
    }
}

// 前向传播
double* forward(NeuralNetwork* nn, double inputs[]) {
    // 隐藏层激活
    static double hidden[HIDDEN_SIZE];
    for (int j = 0; j < HIDDEN_SIZE; j++) {
        double sum = 0.0;
        for (int i = 0; i < INPUT_SIZE; i++) {
            sum += inputs[i] * nn->weights_ih[i][j];
        }
        hidden[j] = relu(sum + nn->bias_h[j]);
    }
    
    // 输出层激活
    static double output[OUTPUT_SIZE];
    for (int j = 0; j < OUTPUT_SIZE; j++) {
        double sum = 0.0;
        for (int i = 0; i < HIDDEN_SIZE; i++) {
            sum += hidden[i] * nn->weights_ho[i][j];
        }
        output[j] = sum + nn->bias_o[j]; // 线性输出
    }
    
    return output;
}

// 训练神经网络
void train_nn(NeuralNetwork* nn, double inputs[][INPUT_SIZE], double targets[], int n, double lr, int epochs) {
    for (int epoch = 0; epoch < epochs; epoch++) {
        double total_loss = 0.0;
        
        for (int sample = 0; sample < n; sample++) {
            // 前向传播
            double* hidden = malloc(HIDDEN_SIZE * sizeof(double));
            double* outputs = malloc(OUTPUT_SIZE * sizeof(double));
            
            // 输入->隐藏层
            for (int j = 0; j < HIDDEN_SIZE; j++) {
                double sum = 0.0;
                for (int i = 0; i < INPUT_SIZE; i++) {
                    sum += inputs[sample][i] * nn->weights_ih[i][j];
                }
                hidden[j] = relu(sum + nn->bias_h[j]);
            }
            
            // 隐藏->输出层
            for (int j = 0; j < OUTPUT_SIZE; j++) {
                double sum = 0.0;
                for (int i = 0; i < HIDDEN_SIZE; i++) {
                    sum += hidden[i] * nn->weights_ho[i][j];
                }
                outputs[j] = sum + nn->bias_o[j];
            }
            
            // 计算损失 (均方误差)
            double error = outputs[0] - targets[sample];
            total_loss += error * error;
            
            // 反向传播
            double delta_output[OUTPUT_SIZE] = {2.0 * error};
            
            // 输出层梯度
            double delta_hidden[HIDDEN_SIZE] = {0};
            for (int i = 0; i < HIDDEN_SIZE; i++) {
                for (int j = 0; j < OUTPUT_SIZE; j++) {
                    delta_hidden[i] += delta_output[j] * nn->weights_ho[i][j] * relu_derivative(hidden[i]);
                }
            }
            
            // 更新权重和偏置
            // 隐藏->输出层
            for (int i = 0; i < HIDDEN_SIZE; i++) {
                for (int j = 0; j < OUTPUT_SIZE; j++) {
                    nn->weights_ho[i][j] -= lr * delta_output[j] * hidden[i];
                }
            }
            
            for (int j = 0; j < OUTPUT_SIZE; j++) {
                nn->bias_o[j] -= lr * delta_output[j];
            }
            
            // 输入->隐藏层
            for (int i = 0; i < INPUT_SIZE; i++) {
                for (int j = 0; j < HIDDEN_SIZE; j++) {
                    nn->weights_ih[i][j] -= lr * delta_hidden[j] * inputs[sample][i];
                }
            }
            
            for (int j = 0; j < HIDDEN_SIZE; j++) {
                nn->bias_h[j] -= lr * delta_hidden[j];
            }
            
            free(hidden);
            free(outputs);
        }
        
        if (epoch % 100 == 0) {
            printf("Epoch %d, Loss: %.4f\n", epoch, total_loss/n);
        }
    }
}

23.5 模型评估与选择

23.5.1 分类问题评估指标

指标 公式 意义
准确率 (TP+TN)/(TP+TN+FP+FN) 整体分类正确率
精确率 TP/(TP+FP) 阳性预测准确率
召回率 TP/(TP+FN) 阳性样本识别率
F1分数 2*(Precision*Recall)/(Precision+Recall) 精确率与召回率调和平均
预测阳性
预测阴性
预测阳性
预测阴性
实际阳性
TP
FN
实际阴性
FP
TN

23.5.2 交叉验证

void k_fold_cross_validation(double X[][2], double y[], int n, int k) {
    int fold_size = n / k;
    
    for (int i = 0; i < k; i++) {
        // 划分训练集和验证集
        int val_start = i * fold_size;
        int val_end = (i+1) * fold_size;
        
        double train_X[(k-1)*fold_size][2];
        double train_y[(k-1)*fold_size];
        double val_X[fold_size][2];
        double val_y[fold_size];
        
        int train_idx = 0;
        for (int j = 0; j < n; j++) {
            if (j >= val_start && j < val_end) {
                val_X[j-val_start][0] = X[j][0];
                val_X[j-val_start][1] = X[j][1];
                val_y[j-val_start] = y[j];
            } else {
                train_X[train_idx][0] = X[j][0];
                train_X[train_idx][1] = X[j][1];
                train_y[train_idx] = y[j];
                train_idx++;
            }
        }
        
        // 训练模型
        LogisticModel model;
        train_logistic_regression(&model, train_X, train_y, (k-1)*fold_size, 0.1, 1000);
        
        // 评估模型
        int correct = 0;
        for (int j = 0; j < fold_size; j++) {
            int pred = predict(&model, val_X[j][0], val_X[j][1]);
            if (pred == (val_y[j] > 0.5)) correct++;
        }
        
        printf("Fold %d: 准确率 %.2f%%\n", i+1, 100.0*correct/fold_size);
    }
}

23.6 特征工程:艺术与科学

23.6.1 特征处理技术

技术 描述 应用场景
标准化 (x-μ)/σ 消除量纲影响
归一化 (x-min)/(max-min) 缩放到[0,1]区间
独热编码 类别变量转二进制向量 分类特征处理
多项式特征 生成特征组合 提升模型复杂度
特征选择 选择最有价值特征 减少过拟合

23.6.2 特征重要性评估

void feature_importance(DecisionTree* tree, double* importance) {
    // 基于节点不纯度减少计算特征重要性
    for (int i = 0; i < tree->num_nodes; i++) {
        if (tree->nodes[i].is_leaf) continue;
        
        int feature = tree->nodes[i].feature_idx;
        double impurity_reduction = tree->nodes[i].impurity - 
            (tree->nodes[i].left_child->samples / tree->nodes[i].samples) * tree->nodes[i].left_child->impurity -
            (tree->nodes[i].right_child->samples / tree->nodes[i].samples) * tree->nodes[i].right_child->impurity;
        
        importance[feature] += impurity_reduction * tree->nodes[i].samples;
    }
    
    // 归一化
    double sum = 0;
    for (int i = 0; i < num_features; i++) sum += importance[i];
    for (int i = 0; i < num_features; i++) importance[i] /= sum;
}

23.7 机器学习综合应用

23.7.1 手写数字识别

// 使用神经网络识别MNIST手写数字
void mnist_digit_recognition() {
    // 加载MNIST数据集
    double images[60000][784];
    int labels[60000];
    load_mnist(images, labels);
    
    // 创建神经网络:784输入 -> 128隐藏 -> 10输出
    NeuralNetwork nn;
    init_nn(&nn);
    
    // 训练模型
    for (int epoch = 0; epoch < 10; epoch++) {
        double total_loss = 0.0;
        
        for (int i = 0; i < 60000; i++) {
            // 前向传播...
            // 反向传播...
        }
        
        printf("Epoch %d, Loss: %.4f\n", epoch, total_loss/60000);
    }
    
    // 测试模型
    int correct = 0;
    for (int i = 0; i < 10000; i++) {
        double* output = forward(&nn, test_images[i]);
        int pred = argmax(output, 10);
        if (pred == test_labels[i]) correct++;
    }
    
    printf("测试准确率: %.2f%%\n", 100.0*correct/10000);
}

23.7.2 情感分析系统

// 使用逻辑回归进行情感分析
void sentiment_analysis() {
    // 加载情感分析数据集
    char* reviews[10000];
    int sentiments[10000];
    load_reviews(reviews, sentiments);
    
    // 文本向量化 (简化版)
    double features[10000][1000] = {0}; // 1000维词袋
    create_bag_of_words(reviews, features, 10000);
    
    // 训练逻辑回归模型
    LogisticModel model;
    train_logistic_regression(&model, features, sentiments, 8000, 0.01, 1000);
    
    // 评估模型
    int correct = 0;
    for (int i = 8000; i < 10000; i++) {
        int pred = predict(&model, features[i][0], features[i][1]); // 简化版
        if (pred == sentiments[i]) correct++;
    }
    
    printf("情感分析准确率: %.2f%%\n", 100.0*correct/2000);
}

23.8 机器学习算法比较

23.8.1 算法特性对比

算法 训练速度 预测速度 可解释性 适用场景
线性回归 极快 数值预测
逻辑回归 极快 二分类
决策树 中等 结构化数据
随机森林 中等 中等 通用分类/回归
神经网络 很慢 中等 复杂模式识别
K均值 中等 中等 聚类分析

23.8.2 实际性能测试

任务 最佳算法 准确率 训练时间 内存使用
MNIST分类 CNN 99.2% 2小时 2GB
房价预测 梯度提升树 92.4% 30秒 500MB
客户分群 K均值 89.7% 5秒 100MB
情感分析 LSTM 93.5% 1小时 1.5GB
欺诈检测 随机森林 98.1% 20秒 300MB

23.9 机器学习发展趋势

23.9.1 当前热点方向

  1. 自监督学习:利用无标签数据预训练
  2. 图神经网络:处理关系型数据
  3. 联邦学习:保护隐私的分布式学习
  4. 可解释AI:提高模型透明度
  5. AutoML:自动化机器学习流程

23.9.2 未来挑战

  • 数据效率:减少训练数据需求
  • 泛化能力:适应新环境的能力
  • 伦理问题:公平性、偏见控制
  • 能源效率:降低计算资源消耗
机器学习未来
更少数据依赖
更强泛化能力
更高计算效率
更好可解释性
更公平决策

23.10 总结与下一章预告

机器学习是人工智能的核心引擎:

  1. 监督学习:从标记数据中学习映射关系
  2. 无监督学习:发现数据内在结构
  3. 神经网络:模拟人脑的层次化处理
  4. 模型评估:科学验证模型性能
  5. 特征工程:提升模型表现的关键

下一章预告:深度学习

第二十四章我们将深入探索深度学习的前沿领域:

  • 卷积神经网络:图像识别的革命
  • 循环神经网络:序列建模的利器
  • 注意力机制与Transformer
  • 生成对抗网络:创造式AI
  • 自监督学习:利用无标签数据
  • 强化学习与深度Q网络

深度学习正在推动人工智能进入新的黄金时代。准备好探索神经网络更深层次的奥秘吧!

机器学习不仅是算法和模型的集合,更是一种理解世界的全新范式。它教会我们从数据中寻找规律,在复杂中发现简单,在噪声中提取信号——这些能力在信息爆炸的时代显得尤为珍贵。

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐