深度学习
感知机
感知机的发展
感知机,是给定一组输入数据,自动找到一条决策边界,讲不通类别的数据正确区分开来的机器。
它是人类历史上第一个真正落地的可学习机器方案,让机器不再依赖人工编写的规则,而是自动从数据中学会分类。
虽然诞生于1957年,但它麻雀虽小,五脏俱全。完整包含了神经网络所有核心要素的雏形:输入、权重、求和、激活、误差反馈、参数更新。真正理解了感知机,就相当于掌握了神经网络的完整骨架。
思想起源:M-P 神经元(1943)
麦卡洛克与皮茨 联合发表了论文。
- 将生物神经元抽象位一个简单的逻辑单元。
- 同时接收多个兴奋或抑制信号。
- 当兴奋信号的总量超过某个阈值时,神经元激活并输出信号,否则保持沉默。
缺陷:权重必须由人工设定,机器本身无法学习。它只是一个精巧的逻辑装置。
思想起源:赫布理论(1949)
转机出现在1949年,心理学家 唐纳德·赫布出版了《行为的组织》。
- 如果两个神经元经常同时激活,它们之间的连接就会不断增强。
- 反之,如果不常同时激活,连接则会逐渐减弱。
- 这是人类历史上第一次从学习机制的角度解释神经连接的变化。
- 第一次给出了一个关键问题的生物学依据————权重是可以改变的。
第一台会学习的机器(1957)
弗兰克·罗森布拉特 在M-P神经元的基础上,加入了至关重要的创新:让权重通过错误反馈自动调整。
- 机器每做出一次错误预测,权重就自动修正一次。
- 如此反复,直到学会正确分类。
- 1958年,他亲手造出了一台真实的物理机器———— Mark I Perceptron。
让机器学会分类
感知机的工作原理非常简单:对输入做加权求和,与阈值比较做出判断,根据错误反馈微调修正权重,直到学会正确分类。

- 如果把感知机的激活函数(阶跃函数)改成Sigmoid就是逻辑回归!
感知机执行步骤
感知机执行步骤
第一步:输入与加权
接收一组输入信号
每一个输入对应权重
- 权重越大,这个特征越关键。
- 权重接近零,说明这个特征几乎可以忽略。
- 这正是对赫布理论的直接呼应————连接越强,影响越大。
第二步:求和与判断
把加权后的输入全部加起来,得到数值z:
其中b是偏置项(Bias),相当于对判断基准线的微调。得到z之后进行激活判断:
第三步:错误与修正
每做出一次预测,感知机就把结果和正确答案对比。如果预测正确,什么都不用做;如果预测错误,就按照下面的规则调整权重:
是正确答案, 是感知机的预测,两者之差是误差信号。 是特征的输入值,误差要按照输入的大小来分配责任。 是学习率,控制每次调整的幅度。

案例:预测身材偏胖还是偏瘦
为了让感知机的工作过程更直观,我们来做一个具体的案例:判断一个人的身材是偏胖还是偏瘦。
- 输入特征:收集一批人的数据,提取两个特征:
(身高)和 (体重)。 - 数据处理:消除身高和体重的量级差异,将数据压缩到0~1之间(归一化),保证学习稳定。
- 目标标签:偏瘦标记为0,偏胖标记为1。
- 训练过程:让感知机从对特征一无所知(权重为0)开始,不断看数据、预测、对比、修正权重。
当感知机对所有样本都能给出正确的预测时,它就找到了那条完美的“决策边界”。
| 序号 | 特征1 (身高) | 特征2 (体重) | 标签 (体型) |
|------|-------------|-------------|------------|
| 1 | 175 | 71 | 偏瘦 |
| 2 | 160 | 63 | 偏胖 |
| 3 | 172 | 69 | 偏瘦 |
| 4 | 162 | 65 | 偏胖 |
| 5 | 170 | 66 | 偏瘦 |
| 6 | 164 | 67 | 偏胖 |
| 7 | 168 | 64 | 偏瘦 |
| 8 | 166 | 69 | 偏胖 |
| 9 | 165 | 62 | 偏瘦 |
| 10 | 168 | 72 | 偏胖 |数据归一化
原始数据中身高的数值在160到170之间,而体重的数值在45到85之间,两个特征的量级差距很大。如果直接把原始数值塞进感知机,身高对权重更新的影响会远远大于体重,导致学习过程不稳定。因此,我们先对数据做一次归一化处理,把所有特征值统一压缩到0到1之间。
归一化后的数据 (X):
------------------------------------------------------------
| 序号 | 特征1 (身高) | 特征2 (体重) |
|------|--------------|--------------|
| 1 | 1.0000 | 0.9000 |
| 2 | 0.0000 | 0.1000 |
| 3 | 0.8000 | 0.7000 |
| 4 | 0.1333 | 0.3000 |
| 5 | 0.6667 | 0.4000 |
| 6 | 0.2667 | 0.5000 |
| 7 | 0.5333 | 0.2000 |
| 8 | 0.4000 | 0.7000 |
| 9 | 0.3333 | 0.0000 |
| 10 | 0.5333 | 1.0000 |
验证归一化范围:
特征1范围: [0.0000, 1.0000]
特征2范围: [0.0000, 1.0000]
============================================================定义感知机类
__init__负责设定超参数,fit是训练的入口。权重初始化为0,意味着感知机一开始对两个特征一无所知。每轮遍历所有样本,犯错就更新权重,没错就跳过。当某一轮所有样本都预测正确时,训练结束。
class Perceptron:
def __init__(self, learning_rate=1.0, n_iterations=100):
self.lr = learning_rate # 学习率
self.n_iterations = n_iterations # 最大训练轮数
self.weights = None # 权重 w
self.bias = None # 偏置 b
self.errors_per_epoch = [] # 记录每轮错误数
def fit(self, X, y):
n_samples, n_features = X.shape
# 权重和偏置全部初始化为0
self.weights = np.zeros(n_features)
self.bias = 0
self.errors_per_epoch = []
for epoch in range(self.n_iterations):
errors = 0 # 本轮错误计数
for idx in range(n_samples):
x_i = X[idx]
y_true = y[idx]
# 1. 计算加权和: z = w1 * x1 + w2 * x2 + b
z = np.dot(self.weights, x_i) + self.bias
# 2. 激活判断: z >= 0 输出1,否则输出0
y_pred = 1 if z >= 0 else 0
# 3. 计算误差
error = y_true - y_pred
# 4. 如果预测错误,更新权重和偏置
if error != 0:
self.weights += self.lr * error * x_i # 更新权重
self.bias += self.lr * error # 更新偏置
errors += 1 # 错误计数
print(f"第{epoch+1}轮,第{idx+1}个样本,更新权重:{self.weights},更新偏置:{self.bias}")
self.errors_per_epoch.append(errors)
# 本轮零错误 -> 收敛,训练结束
if errors == 0:
print(f"第{epoch+1}轮,错误数归零,训练收敛!")
break
return self
def predict(self, X):
# 计算加权和:z = w1 * x1 + w2 * x2 + b
z = np.dot(X, self.weights) + self.bias # type: ignore
# 激活判断:z >= 0 输出1,否则输出0
return np.where(z >= 0, 1, 0)开始训练
创建感知机实例,学习率设为0.5,最大训练100轮。调用fit开始训练,控制台会打印每一轮的权重变化过程。
model = Perceptron(learning_rate=0.5, n_iterations=100)
model.fit(X, labels)验证所有样本
predictions = model.predict(X)
for i in range(len(X)):
true_label = "偏胖" if labels[i] == 1 else "偏瘦"
pred_label = "偏胖" if predictions[i] == 1 else "偏瘦"
status = "✅" if predictions[i] == labels[i] else "❌"
print(f"样本{i+1}: 真实={true_label}, 预测={pred_label} {status}")
print(f"\n最终参数:w1={model.weights[0]:.4f}, w2={model.weights[1]:.4f}, b={model.bias:.4f}") # type: ignore可视化决策边界与训练曲线
经过若干轮训练之后,感知机最终会收敛到一组合适的参数,能够正确区分所有样本。这条仔二维平面上把“偏瘦”和“偏胖”分开的线,就是感知机找到的决策边界。

左图展示感知机在二维平面上找到的那条“决策边界”————把偏胖和偏瘦分开的直线。右图展示每轮训练的错误数变化,可以直观看到模型的学习过程:从一开始错误不断,到最终错误归零。
用训练好的模型预测新样本
训练好的模型就像一个“已经学会判断的老师傅”,你给它新数据,它直接告诉你结果。注意新数据也要做同样的归一化,因为模型是在归一化后的数据上学到的权重,输入必须保持一致。
身高=168cm, 体重=48kg -> 偏瘦
身高=162cm, 体重=82kg -> 偏胖
身高=175cm, 体重=70kg -> 偏瘦感知机的隐患
感知机只能解决线性可分的问题。但现实中的数据往往没有这么听话。逻辑运算中的XOR(异或)问题将这个缺陷暴露得淋漓尽致:
- XOR规则:输入相同时输出0,不同时输出1。
- 不存在一条直线能将其完整地分开。
- 这不是技巧问题,而是数学上的根本限制————XOR是线性不可分的

多层感知机
多层结构
复杂功能可以由简单模块组合
XOR问题在逻辑电路中早已解决,计算机的异或门是由更基础的门电路组合而成的。
:至少一个输入为1。 :两个输入不能同时为1

- 核心思想:复杂功能可以由简单模块层层组合得到。
多条线围出复杂区域
把基础功能的感知机组合堆叠,就可以解决异或这样的复杂问题。
- 单个感知机在特征空间里画一条线,只能做线性分割。
- 如果有两个感知机,就有两条线;三个感知机就有三条线。
- 多条线就可以围出任意复杂的区域。

用两层感知机解决XOR

- 输入层:接收两个特征
、 - 隐藏层:两个神经元使用不同的权重做并行运算,各自画一条分割线,输出0或1
- 输出层:将隐藏层的两个输出作为输入,再做一次加权求和与激活,得到异或判断
第一个隐藏神经元学会了至少一个输入为1,第二个学会了不能同时为1,输出层把两个条件取交集。
多层感知机的一般结构
多层感知机按层排列、层间全连接,构成了多层感知机(MLP)。

- 输入层接收原始特征,数个隐藏层进行中间运算,输出层给出最终结果。
- 同一层里的神经元并行提取多个不同的特征或规则。
- 后续层将前一层的输出作为输入,利用不同的权重进一步组合,逐层构造出更抽象、更有用的表示。
数学表达:函数的层层嵌套
线性难题:
如果每一层只是线性变换,那么无论堆多少层都是徒劳的。
假设第一层输出为
令
💡线性变换的组合仍是线性变换。层层叠加,做的全是无用功。
解决方案是在每个神经元的加权求和输出上,套一个非线性函数,称为激活函数(Activation Function):
有了非线性的
训练难题
多层感知机的理论能力:
1989年正式证明了万能逼近定理(Universal Approximation Theorem):
一个含有足够多神经元的单隐藏层MLP,可以以任意精度逼近任意连续函数。
- 这意味着MLP在理论上具备表达任何函数的能力。
- 表达能力(足够多神经元)和训练能力(如何找到正确的权重)是两个不同的问题。
如何训练模型仍旧是个问题
- MLP结构在1960年代就已经被提出。问题不是没人想到多层,而是没法训练。
- 当时的感知机学习规则只适用于单层网络。
- 核心难题:隐藏层的误差该怎么分配?输出层的错误我们看得到,但中间那些隐藏神经元,每个该为最终错误承担多少责任?没人知道怎么算这个梯度。
💡直到1986年,反向传播算法(Backpropagation)与多层网络结合,提供了一套高效计算多层梯度的方法。
神经网络训练流程

- 初始化各层神经元的权重和偏置参数;
- 输入数据做前向传播,得到预测结果;
- 用损失函数计算预测与真实标签的误差;
- 用反向传播从输出层逐层计算损失对参数的梯度;
- 用梯度下降根据梯度更新权重和偏置;
- 不断重复,让模型逐步收敛。
前向传播
数据沿网络从输入流向输出:线性加权 -> 加偏置 -> 激活函数,把信息重构为更高阶特征传递给下一层。

- 正是这种逐层抽象的机制,让神经网络具备了自动学习特征的能力。
神经元操作
每个神经元都做同一套两步操作
- 线性变换
- 非线性激活
差别只在于:每个神经元维护着自己的一套权重和偏置。以下图为例,仅参考(3输入 -> 4神经元):
从单神经元到整层矩阵化
第
为了运算效率,把同一层所有神经元打包成矩阵,一次乘法搞定整层:
示例:
同时解释了为什么神经网络计算天然适合GPU —— 它就是一连串大型矩阵乘法。
激活函数
前言
- 为什么需要激活函数?
展开查看
假设去掉所有激活函数,一个三层神经网络的输出:
把括号展开后整体退化为一次线性变换:
💡无论堆多少层,没有激活函数的神经网络始终是一个线性模型。
激活函数的作用:在每一层插入一道非线性的“弯”,让神经网络得以拟合任意复杂的函数。
- 为什么不把
改成非线性?
展开查看
- 线性运算的工程红利
- 硬件加速:GPU/TPU就是为矩阵乘法量身打造的,cuBLAS几毫秒就能算完亿级规模的
。 - 梯度极简:
,反向传播一行公式搞定。
一旦把它换成复杂非线性,这些红利全部消失。
- 数值指数级膨胀
以
- 前向:数值冲向无穷大或归零,模型瘫痪。
- 反向:求导系数巨大,梯度彻底失控。
激活函数需求:
- 非线性:最基本门槛,否则深度毫无意义。
- 几乎处处可导:反向传播必须乘上它的导数。
- 梯度行为良好:不消失也不爆炸,最好接近1。
- 计算便宜:训练中会被调用上亿次。
- 输出零中心:避免梯度方向被绑死,优化更顺。
- 单调/平滑/有界
- 一个好激活函数,是在表达能力、梯度传播、计算成本和数值稳定性之间取得平衡。
阶跃函数
阶跃函数(1958):最朴素的激活
1958年,罗森布拉特用阶跃函数搭出了感知机。

- 梯度无法传播:除
外导数恒为0,误差信号一碰到阶跃函数就被直接清零。多层网络从根本上无法训练。 - 信息损失太狠:输入是0.001还是1000,输出都是1。网络丧失对“程度”的感知,大量数值信息在激活的瞬间被永久丢弃。
Sigmoid
Sigmoid(1986):把开关变成旋钮
1986年辛顿等人把反向传播带回神经网络,它需要一个处处可导的激活函数————他们沿用了19世纪就用来描述人口增长的函数:

一条优雅的S形曲线,严格压在(0, 1),导数是一个简单乘法。
优势:
- 处处可导:梯度下降终于能工作————这是反向传播能跑起来的硬性前提。
- 天然概率解释:输出在(0, 1)间,
时取0.5————非常适合做二分类输出层。 - 平滑过度:输入1.5和1.6输出有微小但有意义的差异,神经元终于有了“模拟感”。
劣势:
- 梯度消失:导数最大值只有0.25,但反向传播每过一层梯度就要乘一次导数,深层梯度越来越小,几乎学不动。
- 输出非零中心:输出永远是正数。下一层在反向传播时算出来的梯度往往同号。权重更新失去灵活性————优化器走Z字折线,无法直奔最优。
- 计算贵 + 天然饱和:含指数项,比简单乘法贵得多;指数式压缩使两端斜率以指数速度衰减。
💡如今Sigmoid主要保留下来的位置,是二分类任务的输出层。
Tanh
Tanh(1990s):把Sigmoid摆正
90年代初,杨立昆在贝尔实验室研究手写数字识别,被Sigmoid的非零中心训练震荡困扰。1998年的Efficient BackProp中他系统总结了Tanh的优势:

输出范围被拉伸到(-1, 1),关于原点对称。
✅改进:
- 导数最大值1,比Sigmoid大4倍,梯度衰减更慢。
- 零中心输出,Z字震荡明显减轻,收敛更快。
❌仍然存在:
- 本质仍是S形————两端饱和没有变。
- 依赖指数函数,计算成本同样不低。
💡凭借Tanh,LeNet在90年代末已经能自动识别美国10%~20%的手写支票金额。但深层网络时代仍未到来————这只是一次改良,不是一次突破。
ReLU
ReLU(2010):懒得动脑的天才
2010年,辛顿和同事提出了一个简单到令人发指的激活函数:

优势:
- 大幅缓解梯度消失:正半轴导数恒为1,链式法则在这里几乎变成“梯度直通车”,深层网络第一次真正能训。
- 计算极快:只需一次比较操作。没有指数、没有除法————大规模训练里被放大成可观的速度优势。
- 天然稀疏激活:负输入直接置0————任意一次前向只有部分神经元真正激活,自带正则化效果。
代价:
- 死亡ReLU:某次更新后预激活值恒为负,则输出永远0,导数永远0————神经元彻底死掉,救不回来。
- 非零中心:输出要么0、要么正数。但在正半轴梯度直通车面前,这点Z字震荡基本可以接受。
- 输出无上界:深层 + 大参数下激活值可能一路放大,引发数值不稳定————所以ReLU时代离不开BatchNorm。
💡ReLU很快取代Sigmoid和Tanh,称为深度学习时代最具代表性的激活函数。
在2012年的ImageNet竞赛上,AlexNet把图像分类错误率一举降到15.3%————这样的优势在ImageNet历史上前所未有,所用激活函数正是ReLU。
💡从那一刻起,整个CV圈迅速转向深度学习————ReLU几乎亲手开启了现代深度学习时代。
Leaky ReLU
Leaky ReLU:给负半轴留一条缝
既然ReLU在负半轴归零很“死”,那就给它留一个极小的斜率:

- 负半轴导数永远不为0,神经元不会彻底死亡。
- 当训练过程中出现大量神经元死亡时,可以一试。
- 但
是手动超参,0.01不一定最优;多数情况下ReLU + 良好初始化 + BatchNorm 已经够用。
GELU
GELU(2016):把硬门换成软门
ReLU在

2016年提出,2018年随BERT进入大众视野,此后GPT、ViT以及几乎所有主流Transformer类模型相继跟进。
💡GELU在大模型时代的地位,几乎相当于ReLU在CNN时代的地位。
✅适用场景:Transformer类模型(BERT/GPT/ViT)的前馈网络层。
❌代价:需要tanh、三次方、平方根————比ReLU慢得多。
Swish/SiLU
::: Swish/SiLU:机器自己挑出来的 2017年Google Brain 用神经架构搜索让模型自己挑激活函数,结果搜出来的形式和GELU几乎一模一样:

其实2016年Elfwing等人就独立提出过完全相同的函数————叫SiLU(Sigmoid Linear Unit)。PyTorch中即nn.SiLU。
相比GELU的优势:只需一个Sigmoid,计算更轻。
:::
Swish实战场景:
- 轻量级CNN:EfficientNet、MobileNetV3在精度与速度的权衡中都选择了Swish。
- 大模型FFN:LLaMA引领Mistral/Qwen/PaLM等开源大模型FFN采用SwiGLU。
- “想比ReLU多走一步”:在对计算稍敏感、又希望比ReLU更进一步的场景,Swish是高质量替代。
💡Swish
总结
激活函数80年演化

💡演化方向不是越来越复杂,而是越来越懂得在表达能力、梯度传播和工程效率之间做权衡。
- 阶跃函数:解决了“要不要激活”————但太硬,无法反向传播,信息丢得太狠。
- Sigmoid:第一次让可导的非线性进入神经网络————但两端饱和、非零中心、深层学不动。
- Tanh:Sigmoid的零中心改良————但本质仍是S形,饱和问题没根本上解决。
- ReLU:用最直接的方式保住梯度————正半轴恒为1,真正开启了现代深度学习。
- GELU / Swish:把ReLU的硬折角磨成平滑过度————大模型/Transformer时代的精修。
今天,到底该选谁?
- 输出层:概率问题用Sigmoid/Softmax。
- 普通隐藏层:很多场景下ReLU仍然够用。
- Transformer / 大模型:GELU / SiLU(Swish)更常见。
💡激活函数提供“非线性”,梯度下降提供“学习能力”————一个让网络能拟合任意函数,一个让网络能找到正确参数。
损失函数
前向传播得到
线性回归常用MSE(均方误差):
线性回归、逻辑回归里的答案是梯度下降:算出损失对每个参数的偏导,组成向量——梯度,它指向损失上升最快的方向。
要让损失下降,沿负梯度方向走一小步即可:
反向传播
前向传播:

- 预测值-6离目标0还差得远,损失高达18。下面靠反向传播更新。
反向传播: 
梯度下降
用梯度下降更新参数
设学习率
| 参数 | 旧值 | 梯度 | 更新量 | 新值 |
|---|---|---|---|---|
| 2 | 18 | 1.82 | ||
| 3 | 12 | 2.88 | ||
| -1 | -36 | -0.64 |
流程总结与实现
神经网络学习的本质:
前向传播 算误差 --> 反向传播 算梯度 --> 梯度下降 更新参数 --> 再次前向传播 .....
- 如此循环成千上万次,直到损失降到接近0,模型就训练好了。
用PyTorch实现:自动微分的优雅
只需写出前向公式,PyTorch会在后台自动构建计算图,再用一行loss.backward()把所有梯度一次算出来。
import torch
import torch.nn as nn
# 数据
x = torch.tensor([[1.0]]) # 输入1
t = torch.tensor([[0.0]]) # 预测结果0
# 网络
model = nn.Sequential(
nn.Linear(1, 1, bias=False), # w1
nn.ReLU(),
nn.Linear(1, 1, bias=False), # w2
nn.ReLU(),
nn.Linear(1, 1, bias=False), # w3
)
# 初始化权重,与上面手算示例保持一致
with torch.no_grad(): # 关掉梯度后初始化权重
model[0].weight.fill_(2.0)
model[2].weight.fill_(3.0)
model[4].weight.fill_(-1.0)
# 损失函数 + 随机梯度下降优化器
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
# 训练100轮
for epoch in range(100):
loss = criterion(model(x), t) # 前向传播 + 计算损失
optimizer.zero_grad() # 清空上一轮残留的梯度
loss.backward() # 反向传播,自动计算梯度
optimizer.step() # 梯度下降更新权重
if epoch % 10 == 0:
print(f"Epoch {epoch+1:3d} | 预测值: {model(x).item():7.4f} | 损失: {loss.item():.4f}")注意:PyTorch的nn.MSELoss()默认是
