神经网络与机器学习入门直觉

简单 🟢AI 学习
5 个标签
预计阅读时间:18 分钟
神经网络机器学习梯度下降过拟合AI基础

神经网络与机器学习入门直觉

在深入了解大语言模型、Transformer 或 AI Agent 之前,先搞懂机器学习最底层的几个直觉,会让后面所有更复杂的概念都变得容易理解。这篇文章不追求数学上的严谨完备,而是用尽量通俗的类比和最小可运行的代码,帮你建立起"模型是怎么学习的"这条主线。

🌐 AI、机器学习、深度学习:三个圈的关系

很多人把"人工智能""机器学习""深度学习"当成同义词混用,但它们其实是三个大小不同、层层嵌套的圈:

codeCode
┌─────────────────────────────────────────────┐
│  人工智能 AI(让机器表现出"智能行为"的所有方法)      │
│                                               │
│   ┌───────────────────────────────────────┐ │
│   │  机器学习 ML(从数据中自动总结规律的方法)    │ │
│   │                                         │ │
│   │    ┌─────────────────────────────┐    │ │
│   │    │ 深度学习 DL(用多层神经网络    │    │ │
│   │    │ 实现机器学习的一类具体方法)    │    │ │
│   │    └─────────────────────────────┘    │ │
│   │                                         │ │
│   └───────────────────────────────────────┘ │
└─────────────────────────────────────────────┘

人工智能(AI)是最大的圈,泛指一切"让计算机表现出类似人类智能"的技术,历史可以追溯到上世纪五十年代。早期 AI 的代表是专家系统:工程师把领域专家的知识写成一条条"如果...那么..."的规则,比如医疗诊断系统里写死"如果发烧且咳嗽且血常规白细胞升高,那么怀疑细菌感染"。这类系统完全不依赖数据学习,规则都是人手工编码的,本质上还是传统程序,只是规则库特别庞大。

机器学习(ML)是 AI 内部的一个子集,核心思想是不再由人手写规则,而是让算法从大量历史数据中自己"统计"出规律。经典代表是线性回归、决策树、支持向量机(SVM)、随机森林这些方法。比如要预测房价,机器学习模型会看几千套房子的"面积、地段、楼龄"和"成交价",自己拟合出一个数学函数,而不是让人写"面积每多10平米加5万"这种硬编码规则。

深度学习(DL)又是机器学习内部更小的子集,特指用"多层神经网络"作为模型结构来做机器学习。它和传统机器学习的关键区别在于特征工程:传统机器学习通常需要人先设计好"特征"(比如从图片里手工提取边缘、颜色直方图),再交给算法学习;而深度学习让网络自己从原始数据(像素、文字)里逐层学习出有用的特征,层数越深,学到的特征越抽象。图像识别、语音识别、以及本知识库里其它文章讲到的大语言模型(Transformer、注意力机制等,详见 LLM 基础相关文章),都属于深度学习的范畴,这里不再展开这些更高级的架构细节,本文聚焦在最基础的神经网络直觉上。

一个简单的记忆方式:专家系统靠人写规则,机器学习靠统计找规律,深度学习靠很深的神经网络自己找规律。三者是包含关系,不是并列关系,深度学习一定是机器学习,机器学习一定是 AI 的一种实现路径,但反过来不成立。

📐 从写规则到喂数据:机器是怎么"学习"的

理解机器学习最重要的一步,是理解它和传统编程之间范式上的根本反转

传统编程范式是:

codeCode
规则(人写的代码)+ 数据(输入)  ──→  程序运行  ──→  结果

程序员想清楚逻辑,把逻辑写成 if/else、函数、算法,程序严格按照这些规则处理输入数据、产出结果。规则是死的,是人一行行敲出来的。

机器学习范式恰好反过来**:

codeCode
数据(输入)+ 结果(标签/答案) ──→  训练算法  ──→  规则(模型/参数)

人不再直接写"如何判断"的规则,而是先收集大量"输入 + 正确答案"的样本,让算法自动去"拟合"出一套能够从输入推出答案的规律,这套规律通常表现为一堆数字参数,而不是可读的 if/else 语句。

用一个具体的例子来说明这个转变:判断一封邮件是不是垃圾邮件

传统编程范式下,你可能会写这样的规则:

pythonCode
def is_spam_rule_based(email_text):
    spam_keywords = ["免费领取", "点击链接", "限时抢购", "中奖", "转账汇款"]
    for keyword in spam_keywords:
        if keyword in email_text:
            return True
    return False

这种写法的问题很明显:垃圾邮件发送者只要换几个词就能绕过规则,正常促销邮件也可能被误判,而且关键词列表需要人不断手工维护,永远追不上花样翻新的垃圾邮件。

机器学习范式下,你不再手写关键词规则,而是:

1.收集几万封邮件,每封都标注好"是/不是垃圾邮件"(这叫标注数据);
2.把每封邮件转成一组数字特征(比如某些词出现的频率、邮件长度、是否包含链接等);
3.让一个学习算法(哪怕是很简单的逻辑回归)去自动寻找"哪些特征组合、配上多大的权重,最能区分垃圾邮件和正常邮件";
4.算法训练完之后,输出的不是人写的 if/else,而是一组权重数字,比如"出现链接"这个特征权重是 +2.3,"来自已知联系人"这个特征权重是 -3.1;
5.遇到新邮件时,把它的特征代入这组权重算一个分数,分数超过阈值就判定为垃圾邮件。

这里最本质的变化是:规则不再是人的经验直接编码,而是从大量真实数据里"拟合"出来的。数据越多越有代表性,模型总结的规律往往就越准。这也是为什么现代 AI 系统常说"数据是新的代码"——你换一批更好的数据,就相当于给程序换了一套逻辑,完全不需要改一行手写代码。这个从"人写规则"到"数据驱动"的范式转变,正是后面要讲的神经元、梯度下降、训练集划分等一切概念存在的原因。

🧠 一个神经元的诞生:权重、偏置与激活

深度学习模型是由成千上万个"神经元"堆叠组成的,但每一个神经元本身的计算极其简单,理解一个神经元,就理解了整个网络的最小构建单元。

一个神经元做的事情可以拆成三步:

1.加权求和:把多个输入值,分别乘以各自的权重(weight),再加起来。权重代表"这个输入对结果有多重要",权重越大说明这个输入的影响力越强,权重可以是正数(促进)也可以是负数(抑制)。
2.加偏置:在加权求和的结果上再加一个偏置(bias),偏置相当于给神经元一个"基础倾向",即使所有输入都是 0,偏置也能让神经元输出一个非零的默认值,这样模型的表达能力更灵活。
3.激活函数:把上一步算出来的数值,通过一个非线性函数进行变换,得到神经元最终的输出。

用数学公式表示就是:

codeCode
z = w1*x1 + w2*x2 + ... + wn*xn + b
output = activation(z)

其中 x 是输入,w 是对应的权重,b 是偏置,activation 是激活函数。用 Python 实现一个最简单的神经元前向计算:

pythonCode
import math

def sigmoid(z):
    """sigmoid 激活函数:把任意实数压缩到 (0, 1) 之间,常用于二分类输出"""
    return 1 / (1 + math.exp(-z))

def relu(z):
    """ReLU 激活函数:负数变 0,正数保持不变,是目前隐藏层最常用的激活函数"""
    return max(0, z)

def neuron_forward(inputs, weights, bias, activation_fn):
    """
    模拟单个神经元的前向计算
    inputs: 输入特征列表,比如 [0.5, 1.2, -0.3]
    weights: 每个输入对应的权重列表
    bias: 偏置标量
    activation_fn: 激活函数
    """
    # 第一步:加权求和
    z = sum(w * x for w, x in zip(weights, inputs)) + bias
    # 第二步:激活
    return activation_fn(z)

# 示例:一个判断"是否值得投篮"的玩具神经元
# 输入特征:[距离篮筐远近(归一化), 防守压力(归一化)]
inputs = [0.3, 0.6]
weights = [-1.5, -0.8]   # 距离越远、防守越紧,权重为负,抑制输出
bias = 1.0

score_sigmoid = neuron_forward(inputs, weights, bias, sigmoid)
score_relu = neuron_forward(inputs, weights, bias, relu)

print(f"sigmoid 输出: {score_sigmoid:.3f}")  # 输出一个 0~1 之间的"投篮信心值"
print(f"relu 输出: {score_relu:.3f}")

为什么一定要有激活函数,而不能只做加权求和?因为如果没有激活函数(或者只用线性激活),无论把多少层神经元叠在一起,最终整个网络等价于一次简单的线性变换,表达能力和一个神经元没有本质区别,学不会任何复杂的非线性规律(比如"贵且旧的房子不一定便宜"这种非线性关系)。激活函数引入了非线性,才让多层网络具备了拟合复杂函数的能力。

两个最常见的激活函数各有分工:sigmoid 把输出压缩到 (0, 1) 区间,天然适合表示"概率",常用在二分类任务的输出层;但它在输入绝对值较大时梯度会变得非常小(饱和),层数一多容易出现"梯度消失",训练变慢甚至学不动。ReLU(Rectified Linear Unit)则简单粗暴:输入为负就输出 0,输入为正就原样输出,计算快、梯度传导稳定,是现在绝大多数网络隐藏层的默认选择。可以把 sigmoid 理解成"温和的判断阀门",把 ReLU 理解成"非黑即白的开关",两者搭配不同场景使用。

单个神经元只能做出非常简单的判断,真正的神经网络是把成百上千个这样的神经元分层排列、前后连接,前一层的输出作为后一层的输入,层层组合之后就能表达极其复杂的函数关系——这也是"深度"学习中"深"字的由来:网络的层数越多、越深,理论上能表达的模式就越复杂。

⛰️ 训练就是下山:损失函数与梯度下降

神经元的权重和偏置一开始都是随机初始化的,一个刚初始化的网络做出的预测基本等于瞎猜。"训练"这个过程,就是不断调整这些权重和偏置,让模型的预测越来越接近正确答案的过程。这个调整过程可以用一个非常直观的比喻理解:下山

想象你被蒙上眼睛,站在一座山上的某个位置,任务是走到山谷最低点。你看不到全局地图,只能感受到脚下坡度的方向。合理的策略是:每一步都朝着"当前脚下最陡的下坡方向"迈一小步,走一步之后重新感受坡度,再迈下一步,如此反复,最终就能逼近谷底。

在机器学习里:

山的海拔高度,对应损失函数(Loss Function)的值,它衡量的是"模型当前预测的错误程度",预测越离谱,损失值越大;预测越准,损失值越小,理想情况下损失为 0(完美预测)。常见的损失函数比如回归任务用的均方误差(预测值和真实值差的平方的平均),分类任务用的交叉熵损失。
你所在的位置(横纵坐标),对应模型当前的权重和偏置的取值组合
感受脚下坡度的方向,对应梯度(Gradient),也就是损失函数相对于每个参数的变化率,它指出了"如果把某个权重稍微调大一点,损失会变大还是变小"。
朝坡度反方向走一小步,就是梯度下降(Gradient Descent):因为梯度指向损失上升最快的方向,所以要往梯度的反方向更新参数,才能让损失下降。

用简化的伪代码描述这个过程:

pythonCode
# 简化版梯度下降伪代码(以拟合 y = w*x + b 为例)
def train_simple_model(x_data, y_data, learning_rate=0.01, epochs=1000):
    w, b = 0.0, 0.0  # 随机初始化(这里简化为从0开始)
    n = len(x_data)

    for epoch in range(epochs):
        # 1. 前向计算:用当前参数做预测
        predictions = [w * x + b for x in x_data]

        # 2. 计算损失(均方误差)
        loss = sum((pred - y) ** 2 for pred, y in zip(predictions, y_data)) / n

        # 3. 计算梯度:损失函数对 w 和 b 的偏导数
        grad_w = sum(2 * (pred - y) * x for pred, y, x in zip(predictions, y_data, x_data)) / n
        grad_b = sum(2 * (pred - y) for pred, y in zip(predictions, y_data)) / n

        # 4. 沿梯度反方向更新参数,学习率控制步子迈多大
        w = w - learning_rate * grad_w
        b = b - learning_rate * grad_b

        if epoch % 200 == 0:
            print(f"第{epoch}轮,loss={loss:.4f}, w={w:.3f}, b={b:.3f}")

    return w, b

公式里的 `learning_rate`(学习率)就是"下山每一步迈多大",它的取值极其关键:

学习率太小:每一步迈得太碎,虽然方向正确,但收敛速度极慢,可能训练几万轮损失还没降到理想水平,浪费大量计算资源,甚至在实际工程中因为训练时间超预算而被迫中断。
学习率太大:每一步迈得太猛,容易直接跨过山谷最低点,冲到对面山坡上,导致损失不降反升,参数在谷底附近来回震荡甚至发散,训练完全无法收敛。
合适的学习率:通常需要结合经验、学习率预热、学习率衰减策略(训练初期大步快跑,后期小步精调)等方式,逐步找到一个让损失稳定下降又不至于震荡的取值。

真实的深度学习模型参数动辄数百万到上千亿个(比如超大规模语言模型),损失函数所在的"地形"是极高维度的复杂曲面,实际训练中会用到梯度下降的各种改进版本(比如带动量的 SGD、Adam 优化器等)来加速收敛、跳出局部小坑,但核心思想和这里的"下山"比喻完全一致:根据误差反推调整方向,一步步把参数调整到损失更小的地方

📊 数据为王:训练集、测试集与过拟合

模型再精巧,如果没有高质量、划分合理的数据来训练和检验,一样得不到好的效果。一个标准的机器学习流程通常会把手头的数据切分成三份:

训练集(Training Set):用来实际训练模型,也就是上一节讲的梯度下降过程中用到的数据,模型会"看到"这部分数据的输入和正确答案,并据此调整参数。通常占总数据的 60%~80%。
验证集(Validation Set):训练过程中用来"阶段性打分"的数据,模型不会用它来更新参数,但工程师会用验证集上的表现来调整超参数(比如学习率、网络层数、正则化强度),或者决定什么时候该停止训练。
测试集(Test Set):最终、只用一次的"期末考试",在模型完全训练调优完成之后才拿出来评估,用于估计模型在真实世界、从未见过的数据上的表现。测试集绝对不能被用来调整任何参数或超参数,否则评估结果会失真、偏乐观。

三者的关系可以类比学生备考:训练集是平时的练习题和答案,验证集是模拟考(用来查漏补缺、调整复习策略),测试集是真正的期末考试(只考一次,考完才知道成绩,不能提前看到"考题里包含哪些真实场景")。

在训练过程中,最常遇到的两个问题是过拟合(Overfitting)欠拟合(Underfitting)

欠拟合:模型太简单,或者训练不充分,连训练集本身都学不好,训练集和测试集上的表现都很差。好比学生根本没有掌握知识点,做练习题和考试都考不好。
过拟合:模型在训练集上表现极好(甚至能把训练数据"背下来"),但在没见过的测试集上表现明显变差。好比学生把练习册的题目和答案死记硬背下来,题目稍微换个说法、换个数字就完全不会做了——他学到的不是"解题方法"这个可泛化的规律,而是"这道题的答案是什么"这种死记硬背的特例。

识别过拟合最常用的方法是观察学习曲线:把训练过程中每一轮(epoch)的训练集损失和验证集损失分别画成两条曲线。如果训练损失持续下降,但验证损失下降到某个点之后开始掉头上升,两条曲线之间的差距越拉越大,这就是典型的过拟合信号——模型已经开始"死记硬背"训练数据里的噪声和细节,而不是学习普适规律了。相反,如果训练损失和验证损失都居高不下、迟迟降不下来,说明是欠拟合,可能需要更复杂的模型、更多的训练轮数,或者更有效的特征。

针对过拟合,工程实践中有几种常用的应对手段:

1.L2 正则化(权重衰减):在损失函数里额外加一项"所有权重平方和"的惩罚项,逼迫模型倾向于用更小、更平滑的权重去拟合数据,避免某些权重畸形增大去死记硬背个别样本的噪声。
2.Dropout:训练过程中,每一轮随机"临时关闭"网络里的一部分神经元(不参与本轮计算),这样网络不能过度依赖某几个特定神经元的组合,被迫学习更鲁棒、更分散的特征表示,相当于同时训练了很多个"瘦身版"子网络并取平均效果。
3.早停(Early Stopping):持续监控验证集损失,一旦验证损失连续若干轮不再下降甚至开始上升,就提前终止训练,保留验证损失最低时刻的那组参数,避免模型在后续训练中继续朝着"死记硬背训练集"的方向发展。
4.数据增强(Data Augmentation):通过对已有数据做适度的随机变换(比如图像的旋转、裁剪、翻转、加噪声,文本的同义替换等)人为扩充训练样本的多样性,让模型看到更多"变体",减少对某个具体样本细节的依赖,从而提升泛化能力。

这几种手段可以组合使用,本质上都是在给模型的学习过程"加约束",提醒它不要过度迎合训练集里的细枝末节,而要去捕捉真正能推广到新数据上的普遍规律。这也是为什么"数据为王"——同样的模型结构和训练技巧,喂给它高质量、有代表性、划分严谨的数据集,往往比一味增加模型复杂度更能带来实质性的效果提升。

🔑 小结

回顾一下这条主线:AI、机器学习、深度学习是三个逐层缩小的圈,深度学习是用多层神经网络实现机器学习的一种具体方式;机器学习相比传统编程的根本转变,是从"人写规则"变成"从数据里学规则";神经网络最基本的构建单元是神经元,靠"加权求和 + 偏置 + 激活函数"完成一次非线性判断;训练网络的过程就是用损失函数衡量误判程度,再用梯度下降沿着误差减小的方向不断微调参数,好比蒙眼下山;而支撑这一切的根基是高质量、划分合理的数据,并通过正则化、Dropout、早停、数据增强等手段防止模型死记硬背、丧失泛化能力。掌握了这几条直觉,再去理解 Transformer、注意力机制、大语言模型的预训练与微调等更复杂的话题时,会发现它们都只是在这套基本框架之上做的结构和规模上的升级。