机器学习·智械起源

数字如何从0到1构造智能

机器学习# 神经网络# 反向传播# 流形假设# MNIST# 数据工程# PyTorch

这是「机器学习」系列的第一篇。我们不假设你已熟悉神经网络,而是把最小的问题讲透:一个只会输出 0 和 1 的空白系统,如何一步步学会识别手写数字,并最终走向自主智能体。

必要的背景知识

主要涉及的数学学科:

  • 分析学(Analysis)

  • 线性代数(Linear Algebra)

  • 近世代数(Modern Algebra)

常用的知识点:

  • 多元离散微积分(Multivariable Discrete Calculus)
  • 张量(Tensor)代数
  • 流形假设(Manifold Hypothesis)

机器学习的本质

一般架构

一般地,我们认为机器学习遵循一个一般性的路线:

texttext
观察 → 表示 → 预测 → 决策 → 行动 → 反馈 → 更新 → 更好的观察与决策 
flowchart TD Z["0:空白系统<br/>只有 0/1 比特"] --> P["感知<br/>观察环境/获取数据"] P --> R["表示<br/>编码为向量/符号/世界模型"] R --> M["模型<br/>预测与推理"] M --> D["决策<br/>选择答案/动作/工具"] D --> A["行动<br/>输出/调用/控制"] A --> E["环境/结果<br/>数字世界或物理世界"] E --> F["反馈<br/>奖励/评价/新观察"] F --> L["学习/更新<br/>参数、记忆、策略"] L --> C{"闭环是否稳定?<br/>能力是否持续提升?"} C -- 否 --> P C -- 是 --> O["1:自主智能体<br/>持续感知—决策—行动—自改进"] O --> P

这也是经典的“神经连接主义学派”的理论方法;与此对应的还有“神经符号主义学派”,他们认为智能来源于大规模集群化的逻辑推导,但是由于Transformer的崛起,这个流派目前已经发展停滞了。本文讨论的机器学习也是以神经连接主义为基本的讨论对象。

流形假设

流形假设(manifold hypothesis)是机器学习中一个重要的经验性假设:

真实世界的高维数据虽然看起来维度很高,但它们并不是均匀填满整个高维空间,而是集中在一个低维流形上,或分布在该流形附近。

用数学语言说,若数据位于D维环境空间中,则假设存在一个内在维度 d \ll D 的流形M,数据点x大致满足

\begin{align} \mathcal{M} \subset \mathbb{R}^D \\ x \approx f(z)+\varepsilon,\quad z\in \mathbb{R}^d \\ \end{align}

其中f是从低维潜空间到高维数据空间的映射,ε是噪声。d称为数据的内在维度(潜在维度)。

直观理解

例如 MNIST 手写数字图像是 28\times28=784 维向量,但真正控制数字变化的因素可能只有笔画粗细、倾斜角度、位置、书写风格等少数几个变量。因此 784 维数据实际上可能集中在一个几十维甚至更低维的流形附近。人脸图像、语音、文本嵌入等也类似。

流形的一个重要性质是“局部欧氏”:虽然整体可能弯曲复杂,但每个点附近看起来像低维欧氏空间。因此可以用局部线性、切空间、图结构等工具来建模。

局限与争议

流形假设是经验假设,不是普遍定理。现实数据可能:

  • 由多个流形组成,而不是单一流形;
  • 内在维度并不低,或随尺度变化;
  • 存在强噪声、离群点和分形结构;
  • 流形高度弯曲,局部邻域选择困难;
  • 深度模型未必显式恢复真实流形。

因此,流形假设很有解释力,但使用时要注意其适用范围。它更多是理解高维数据几何结构、指导降维和表示学习的一个有用框架。

流形假设认为高维数据背后存在低维结构;机器学习的目标之一,就是发现并利用这种低维流形结构。

如何构造神经网络

数学模型

在正式开始之前请确保你熟悉相关的数学背景知识——虽然用到的不多,但是不明不白地“形而上学”主义是不可取的。

一般地我们对所有AI模型有一个标准的数学模型:

\mathbb{y} = \mathcal{M}(\mathbb{x})

抽象成一个拥有N个参数,输入一个张量x,输出张量y的函数,通过反向传播(BP)更新这个函数的N个参数以达到使其趋于满足我们的约束条件最小化的数学模型。

约束条件及参数更新

一般而言,我们称其为“损失函数(Loss Function)”,即让目标的误差(损失)最小化的函数。

\begin{align} \text{监督学习: } & \quad Loss = f(y, \hat{y}) \\ \text{无监督学习: } & \quad Loss = f(x, \hat{x}) \quad \text{(如重构误差)} \\ \text{自监督学习: } & \quad Loss = f(x, T(x)) \quad \text{(T 为数据增强/掩码/变换)} \\ \text{强化学习: } & \quad Loss = -\mathbb{E}_{\tau\sim\pi_\theta}\left[\sum_{t=0}^{T}\gamma^t r_t\right] \end{align}

通过损失函数可以计算模型每个参数的梯度,再通过梯度下降法更新参数,使参数朝让Loss缩小的方向调整——这便是反向传播算法的核心思想。

反向传播算法是机器学习的基石。

有用的视频:【官方双语】3Blue1Brown-深度学习之反向传播算法 合集

训练

假设我们有一个数据集,MNIST(美国国家标准与技术研究院改良数据集)是一个图像分类基准,包含 70,000 张 28x28 的手写数字灰度图像,分为 10 个类别,即数字 0 到 9。该数据集提供预先划分的数据集,其中包含 60,000 张训练图像和 10,000 张测试图像,长期以来一直是评估机器学习和计算机视觉算法的标准基准。训练一个手写数字识别的网络:

pythonpython
import torchimport torch.nn as nnimport torch.optim as optimfrom torch.utils.data import DataLoaderfrom torchvision import datasets, transformsimport matplotlib.pyplot as pltimport numpy as np# ========== 1. 配置参数 ==========BATCH_SIZE = 64EPOCHS = 5LR = 0.001DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")print(f"使用设备: {DEVICE}")# ========== 2. 数据加载与预处理 ==========transform = transforms.Compose([    transforms.ToTensor(),                       # 转为 [0,1] 的张量    transforms.Normalize((0.1307,), (0.3081,))   # MNIST 均值/标准差])train_dataset = datasets.MNIST(    root='./data', train=True, download=True, transform=transform)test_dataset = datasets.MNIST(    root='./data', train=False, download=True, transform=transform)train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)print(f"训练集大小: {len(train_dataset)}, 测试集大小: {len(test_dataset)}")# ========== 3. 定义 CNN 网络 ==========class Net(nn.Module):    def __init__(self):        super(Net, self).__init__()        # 卷积层        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)   # 1x28x28 -> 32x28x28        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)  # 32x28x28 -> 64x28x28        self.pool = nn.MaxPool2d(2, 2)                            # 尺寸减半        # 全连接层        self.fc1 = nn.Linear(64 * 7 * 7, 128)        self.fc2 = nn.Linear(128, 10)        self.dropout = nn.Dropout(0.25)    def forward(self, x):        x = self.pool(torch.relu(self.conv1(x)))   # 32x14x14        x = self.pool(torch.relu(self.conv2(x)))   # 64x7x7        x = x.view(-1, 64 * 7 * 7)                 # 展平        x = self.dropout(torch.relu(self.fc1(x)))        x = self.fc2(x)        return xmodel = Net().to(DEVICE)print(model)# ========== 4. 损失函数与优化器 ==========criterion = nn.CrossEntropyLoss()optimizer = optim.Adam(model.parameters(), lr=LR)# ========== 5. 训练函数 ==========def train(epoch):    model.train()    running_loss = 0.0    correct = 0    total = 0    for batch_idx, (data, target) in enumerate(train_loader):        data, target = data.to(DEVICE), target.to(DEVICE)        optimizer.zero_grad()        output = model(data)        loss = criterion(output, target)        loss.backward()        optimizer.step()        running_loss += loss.item()        pred = output.argmax(dim=1)        correct += pred.eq(target).sum().item()        total += target.size(0)        if (batch_idx + 1) % 200 == 0:            print(f"Epoch {epoch} [{batch_idx+1}/{len(train_loader)}] "                  f"Loss: {running_loss/200:.4f} "                  f"Acc: {100.*correct/total:.2f}%")            running_loss = 0.0    return 100. * correct / total# ========== 6. 测试函数 ==========def test():    model.eval()    test_loss = 0    correct = 0    with torch.no_grad():        for data, target in test_loader:            data, target = data.to(DEVICE), target.to(DEVICE)            output = model(data)            test_loss += criterion(output, target).item()            pred = output.argmax(dim=1)            correct += pred.eq(target).sum().item()    test_loss /= len(test_loader)    acc = 100. * correct / len(test_dataset)    print(f"\n测试集: 平均Loss: {test_loss:.4f}, 准确率: {acc:.2f}%\n")    return acc# ========== 7. 开始训练 ==========train_accs, test_accs = [], []for epoch in range(1, EPOCHS + 1):    train_acc = train(epoch)    test_acc = test()    train_accs.append(train_acc)    test_accs.append(test_acc)# 保存模型torch.save(model.state_dict(), "mnist_cnn.pth")print("模型已保存到 mnist_cnn.pth")# ========== 8. 可视化 ==========def visualize_results():    fig, axes = plt.subplots(1, 2, figsize=(12, 4))    # 8.1 训练曲线    axes[0].plot(range(1, EPOCHS+1), train_accs, 'o-', label='Train')    axes[0].plot(range(1, EPOCHS+1), test_accs, 's-', label='Test')    axes[0].set_xlabel('Epoch')    axes[0].set_ylabel('Accuracy (%)')    axes[0].set_title('Training / Test Accuracy')    axes[0].legend()    axes[0].grid(True)    plt.savefig("accuracy_curve.png", dpi=120)    plt.show()visualize_results()# ========== 9. 展示预测结果 ==========def show_predictions():    model.eval()    # 取一批测试数据    data, target = next(iter(test_loader))    data, target = data.to(DEVICE), target.to(DEVICE)    with torch.no_grad():        output = model(data)        preds = output.argmax(dim=1)    # 显示前 12 张    fig, axes = plt.subplots(3, 4, figsize=(10, 8))    for i, ax in enumerate(axes.flatten()):        img = data[i].cpu().squeeze() * 0.3081 + 0.1307   # 反归一化        ax.imshow(img, cmap='gray')        color = 'green' if preds[i] == target[i] else 'red'        ax.set_title(f"预测: {preds[i].item()} / 真值: {target[i].item()}",                     color=color)        ax.axis('off')    plt.tight_layout()    plt.savefig("predictions.png", dpi=120)    plt.show()show_predictions()

下载好数据集放置在./data/目录下即可完成全套训练并可视化展示,这是一切人工智能学习的开端,类似于“Hello,World!”

如何构造数据集

核心,好的数据往往事半功倍,差的数据模型再好也无济于事

设计数据集的结构

根据任务的不同,数据集一般情况下有三种结构:

  • 监督学习:标签-数据(模式识别)
  • 自监督学习:数据-掩膜/对抗样本/其他自监督信号(自回归生成)
  • 无监督学习:纯数据(一般用来数学建模)

处理数据集

划分数据集: 训练/验证/测试通常按 70%:15%:15%的比例构造,策略上:独立同分布用随机划分,类别不均衡用分层抽样,时序任务用时间划分,同一用户或设备必须整体归入一侧(分组划分)。测试集独立保存,只用于最终评估。

处理不平衡与增强: 重采样、加权损失;图像用翻转/裁剪/Mixup,文本用回译/同义替换,音频用加噪/变速,也可借助合成数据补长尾。注意增强不能改变标签语义,验证与测试集不做增强。

评估与迭代: 检查覆盖度、代表性、标签准确性、群体偏差和泄漏;若验证集分数异常高,先查泄漏而非庆祝。

texttext
小规模高质量数据跑基线 → 错误分析 → 针对性补长尾与困难样本 → 更新数据版本 → 重训 

存档: 用 DVC、Git LFS 管理版本,并写一份数据卡(Datasheet),固定随机种子保证可复现。

常见坑

  1. 用测试集调参,评估虚高。
  2. 同一用户/患者/设备跨训练和测试。
  3. 时序任务随机划分,用未来预测过去。
  4. 标签定义模糊,标注者各标各的。
  5. 只收容易样本,忽略长尾。
  6. 归一化等预处理用了全量统计量,造成泄漏。
  7. 忽略隐私、版权和偏见。

结语

千里之行,始于足下。到这里,你已经亲手训练出了第一个真正意义上的神经网络——但它处理的图像彼此独立。当数据换成语言、代码与时间序列,「让模型学会关注重点」就成了下一步:《机器学习·注意力的时代》。

除非特别说明,本站文章采用 CC BY-NC-SA 4.0 许可协议。

在 GitHub 编辑

相关阅读

4 阅读

C++ Standard Library List

C++标准库列表

截至 C++26(2026 年 3 月已定稿),标准库的组件非常庞大。下面按功能类别整理了一份核心标准库组件表格,覆盖从 C++11 到 C++26 的主要头文件及其作…

14 阅读

How to Design an Elegant Agent System

从 cell.cpp 提炼的十条构造原则 —— 把不确定性关进确定性的笼子

Agent 框架越做越厚,但真正的问题只有一个 —— 如何让一个只会预测下一个 token 的东西,可靠地改变外部世界。我以单文件 C++ Agent「cell.cpp」为样…

工程# Agent# 系统设计# 工程哲学# LLM
1 阅读

最小系统的设计原理

如何优雅地设计一个极客风格的系统

一般而言,根据钱老的《工程控制论》思想,一个系统可以由以下部分组成: 其基本结构可表示为: 其中: 比较器:计算偏差,例如 e=r−ye=r−y; 控制器…

随笔# 随笔# 工程哲学
6 阅读

你好,墨痕 —— 一个用 Python 渲染的水墨博客引擎

从 Markdown 到可部署站点:构建管线、渲染能力与思维链的全景笔记

这个站点由 Inkstone 渲染 —— 一个用 Python 写的静态博客引擎。它只做三件事:读 Markdown、读配置、写出一坨可以直接丢到任何静态托管上的文件。 设…

工程# 静态站点# Python# 构建系统# 前端

评论