1从生物神经元到人工神经元
人工神经网络(Artificial Neural Network, ANN)的灵感来源于生物大脑的神经元网络。一个生物神经元通过树突接收来自其他神经元的信号,在细胞体内进行整合,当信号强度超过阈值时,通过轴突向下游神经元传递电信号。
McCulloch 和 Pitts 在 1943 年提出了第一个数学模型:M-P 神经元。它将生物神经元的运作抽象为三个步骤:接收多个输入信号(每个信号有不同的权重 wᵢ)、对所有加权输入求和并与阈值 θ 比较、通过激活函数产生输出。
这个看似简单的模型却是所有深度学习的基石。现代深度神经网络虽然在规模和复杂度上远超最初的感知机,但每个神经元的基本运算模式——加权求和、非线性变换——始终未变。
学习建议:在纸上画一个神经元,标注输入、权重、偏置、激活函数和输出。然后手动计算一次前向传播。这个简单的练习比看十篇教程都有效。
2感知机:最早的神经网络
感知机(Perceptron)由 Frank Rosenblatt 于 1958 年提出,是最简单的神经网络——只有一个神经元。它接收输入 x,计算 z = w·x + b,然后用阶跃函数判断:z ≥ 0 则输出 1,否则输出 0。
感知机的训练规则非常直观:如果预测正确,权重不变;如果预测错误,向正确的方向调整权重。具体地,当 yᵢ = 1 但预测为 0 时,增加权重(w := w + η·xᵢ);当 yᵢ = 0 但预测为 1 时,减小权重(w := w - η·xᵢ)。
感知机收敛定理保证了:如果数据线性可分,感知机算法在有限步内一定会找到一个完美分类的超平面。但它有两个致命缺陷:只能处理线性可分数据(连 XOR 问题都解决不了);只能输出 0 或 1,无法给出置信度。
import numpy as np
class Perceptron:
"""从零实现经典感知机"""
def __init__(self, lr=0.01, n_iters=1000):
self.lr = lr
self.n_iters = n_iters
self.w = None
self.b = None
def fit(self, X, y):
n_samples, n_features = X.shape
self.w = np.zeros(n_features)
self.b = 0.0
for _ in range(self.n_iters):
errors = 0
for xi, yi in zip(X, y):
z = np.dot(xi, self.w) + self.b
y_pred = 1 if z >= 0 else 0
update = self.lr * (yi - y_pred)
self.w += update * xi
self.b += update
errors += int(update != 0)
if errors == 0:
print(f"收敛!迭代 {_+1} 次")
break
return self
def predict(self, X):
z = X @ self.w + self.b
return np.where(z >= 0, 1, 0)
# 测试 AND 门(线性可分)
X_and = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y_and = np.array([0, 0, 0, 1])
p = Perceptron(lr=0.1, n_iters=100)
p.fit(X_and, y_and)
print(f"AND 门预测: {p.predict(X_and)}")
# 测试 XOR 门(线性不可分——感知机无法解决)
X_xor = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y_xor = np.array([0, 1, 1, 0])
p2 = Perceptron(lr=0.1, n_iters=100)
p2.fit(X_xor, y_xor)
print(f"XOR 门预测: {p2.predict(X_xor)} (应不正确)")| 逻辑门 | 输入 (0,0) | 输入 (0,1) | 输入 (1,0) | 输入 (1,1) | 线性可分 |
|---|---|---|---|---|---|
AND | 0 | 0 | 0 | 1 | ✅ |
OR | 0 | 1 | 1 | 1 | ✅ |
NOT | 1 | 0 | 1 | 0 | ✅ (单输入) |
XOR | 0 | 1 | 1 | 0 | ❌ 需要多层 |
NAND | 1 | 1 | 1 | 0 | ✅ |
XOR 问题是感知机无法跨越的鸿沟——正是这个缺陷导致了第一次 AI 冬天(1969-1980)。Minsky 和 Papert 在《Perceptrons》中证明了感知机的局限性,使得神经网络研究停滞了十几年。
3激活函数:引入非线性的关键
如果没有激活函数,无论多少层网络叠加,本质上仍然是一个线性变换(线性函数的组合仍是线性的)。激活函数的作用是为网络引入非线性能力,使其能够拟合任意复杂的函数。
Sigmoid 函数 σ(z) = 1/(1+e⁻ᶻ):将输出压缩到 (0, 1) 区间,可解释为概率。在二分类的输出层仍然广泛使用。但它在输入较大或较小时梯度接近零,导致梯度消失问题。
Tanh 函数 tanh(z):将输出压缩到 (-1, 1) 区间,是零均值的(比 Sigmoid 更好)。但同样存在梯度消失问题。
ReLU 函数 ReLU(z) = max(0, z):现代深度学习中最常用的激活函数。计算极其高效(只需一个比较操作),在正区间的梯度恒为 1,有效缓解梯度消失。缺点是"死亡 ReLU"问题——如果某个神经元的输出始终为负,它的梯度永远为零,权重不再更新。
Leaky ReLU 和 GELU 是 ReLU 的改进版本。GELU(高斯误差线性单元)在 Transformer 中被广泛使用,它在负区间保留了小概率的激活,使得梯度始终不为零。
import numpy as np
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def sigmoid_grad(z):
s = sigmoid(z)
return s * (1 - s)
def relu(z):
return np.maximum(0, z)
def relu_grad(z):
return (z > 0).astype(float)
def gelu(z):
"""GELU 近似实现"""
return 0.5 * z * (1 + np.tanh(
np.sqrt(2 / np.pi) * (z + 0.044715 * z**3)
))
# 激活函数及梯度对比
z_vals = [-10, -5, -1, 0, 1, 5, 10]
print(f"{'z':>6} | {'Sigmoid':>9} | {'SigGrad':>9} | {'ReLU':>6} | {'ReLUGrad':>6}")
print("-" * 52)
for z in z_vals:
print(f"{z:6.1f} | {sigmoid(z):9.6f} | {sigmoid_grad(z):9.6f} | {relu(z):6.1f} | {relu_grad(z):6.1f}")
# 梯度消失演示
print("\n梯度消失演示(深层网络中的 Sigmoid 梯度衰减):")
gradient = 1.0
for layer in range(1, 11):
gradient *= 0.25 # sigmoid 最大梯度约 0.25
print(f" 第 {layer:2d} 层: 梯度 = {gradient:.2e}")| 激活函数 | 输出范围 | 梯度范围 | 计算成本 | 主要优点 | 主要缺点 |
|---|---|---|---|---|---|
Sigmoid | (0, 1) | (0, 0.25] | 高(指数运算) | 可解释为概率 | 梯度消失、非零均值 |
Tanh | (-1, 1) | (0, 1] | 高(指数运算) | 零均值 | 梯度消失 |
ReLU | [0, ∞) | {0, 1} | 极低 | 计算快、缓解梯度消失 | 死亡 ReLU |
Leaky ReLU | (-∞, ∞) | {0.01, 1} | 极低 | 避免死亡 ReLU | 负区间斜率需选择 |
GELU | (-∞, ∞) | 连续 | 中 | 光滑、Transformer 标准 | 计算稍复杂 |
4多层感知机(MLP):从线性到万能
多层感知机(Multi-Layer Perceptron, MLP)是在输入层和输出层之间增加了一个或多个隐藏层的神经网络。正是这些隐藏层使得网络能够学习非线性决策边界。
万能近似定理(Universal Approximation Theorem):一个具有足够多神经元的单隐藏层 MLP,使用非线性激活函数,可以以任意精度逼近任何定义在紧凑集上的连续函数。这个定理从理论上保证了 MLP 的表达能力。
但"能逼近"不等于"能学好"。定理没有告诉我们:需要多少神经元、如何高效训练、以及能否泛化到未见数据。这些问题的答案催生了深度学习的大量研究成果。
一个典型的 MLP 前向传播过程:第 l 层的输出 a⁽ˡ⁾ = f(W⁽ˡ⁾ · a⁽ˡ⁻¹⁾ + b⁽ˡ⁾),其中 f 是激活函数。从输入 a⁽⁰⁾ = x 开始,逐层计算直到输出层。
class MLP:
"""从零实现多层感知机(2 个隐藏层)"""
def __init__(self, layer_sizes, lr=0.01):
"""layer_sizes: [输入维度, 隐藏1, 隐藏2, 输出维度]"""
self.lr = lr
self.weights = []
self.biases = []
# He 初始化
for i in range(len(layer_sizes) - 1):
fan_in = layer_sizes[i]
limit = np.sqrt(2.0 / fan_in)
w = np.random.randn(fan_in, layer_sizes[i+1]) * limit
b = np.zeros((1, layer_sizes[i+1]))
self.weights.append(w)
self.biases.append(b)
def forward(self, X):
self.z_list = []
self.a_list = [X]
a = X
for i in range(len(self.weights) - 1):
z = a @ self.weights[i] + self.biases[i]
a = np.maximum(0, z) # ReLU
self.z_list.append(z)
self.a_list.append(a)
# 输出层(无激活,用于回归)
z = a @ self.weights[-1] + self.biases[-1]
self.z_list.append(z)
self.a_list.append(z)
return z
def backward(self, X, y):
m = X.shape[0]
dz = (self.a_list[-1] - y) / m # MSE 梯度
dw_list, db_list = [], []
for i in range(len(self.weights) - 1, -1, -1):
dw = self.a_list[i].T @ dz
db = np.sum(dz, axis=0, keepdims=True)
dw_list.insert(0, dw)
db_list.insert(0, db)
if i > 0:
dz = (dz @ self.weights[i].T) * (self.z_list[i-1] > 0)
for i in range(len(self.weights)):
self.weights[i] -= self.lr * dw_list[i]
self.biases[i] -= self.lr * db_list[i]
def train(self, X, y, epochs=1000):
for epoch in range(epochs):
out = self.forward(X)
loss = np.mean((out - y) ** 2)
self.backward(X, y)
if epoch % 200 == 0:
print(f"Epoch {epoch:4d}: Loss = {loss:.6f}")
# 测试 XOR
X = np.array([[0,0],[0,1],[1,0],[1,1]], dtype=float)
y = np.array([[0],[1],[1],[0]], dtype=float)
np.random.seed(42)
mlp = MLP([2, 8, 4, 1], lr=0.5)
mlp.train(X, y, epochs=2000)
pred = mlp.forward(X)
print("\nXOR 结果:")
for i in range(4):
print(f" {X[i].astype(int).tolist()} -> {pred[i][0]:.4f} (期望 {int(y[i][0])})")5反向传播:神经网络的学习引擎
反向传播(Backpropagation)是训练神经网络的核心算法。它的本质是链式法则(Chain Rule)在计算图上的高效应用。
理解反向传播的关键是计算图(Computational Graph)。每个运算(加法、乘法、激活函数)都是图中的一个节点。前向传播时,数据从输入流向输出;反向传播时,梯度从输出流回输入。
链式法则告诉我们:如果 z = g(y) 且 y = f(x),那么 ∂z/∂x = (∂z/∂y) · (∂y/∂x)。在神经网络中,这意味着损失函数对某一层权重的梯度,可以通过逐层传递梯度来计算。
反向传播的四个基本方程(BPE1-BPE4)构成了完整的梯度计算框架。最关键的洞察是:每个神经元的误差 δ⁽ˡ⁾ 可以从后一层的误差 δ⁽ˡ⁺¹⁾ 反向计算得到,这避免了为每个权重单独计算梯度的巨大开销。
前向传播:计算每一层的 z = W·a + b 和 a = f(z)
输出层误差:δᴸ = ∂L/∂aᴸ ⊙ f'(zᴸ)
隐藏层误差:δˡ = (Wˡ⁺¹)ᵀ · δˡ⁺¹ ⊙ f'(zˡ)
权重梯度:∂L/∂Wˡ = δˡ · (aˡ⁻¹)ᵀ
偏置梯度:∂L/∂bˡ = δˡ
用梯度下降更新所有权重:W := W - α·∂L/∂W
调试技巧:用数值梯度验证反向传播的正确性。对每个权重 w 计算 (L(w+ε) - L(w-ε)) / 2ε,与反向传播计算的梯度对比。如果差异 > 1e-7,说明反向传播实现有误。
6梯度消失与梯度爆炸:深度网络的训练难题
当神经网络变深时,反向传播中梯度需要乘以多层权重矩阵。根据链式法则,梯度是连乘的形式——如果每个矩阵的谱范数小于 1,梯度会指数级衰减(消失);如果大于 1,梯度会指数级增长(爆炸)。
梯度消失的后果:浅层(靠近输入)的权重几乎不更新,网络退化为只有最后几层在训练,深度的优势完全丧失。这正是 sigmoid/tanh 时代深层网络无法训练的根本原因。
解决方案包括:使用 ReLU 类激活函数(正区间梯度恒为 1);Xavier/He 初始化(让每层的输出方差保持一致);残差连接(ResNet,让梯度可以直接跨层传播);层归一化/批归一化(稳定每层的输入分布);梯度裁剪(限制梯度的最大范数,防止爆炸)。
# 演示梯度消失问题
import numpy as np
def demonstrate_vanishing_gradient():
"""展示不同激活函数的梯度消失效应"""
n_layers = 20
for act_name, max_grad in [("Sigmoid", 0.25), ("ReLU", 1.0)]:
grad = 1.0
print(f"\n{act_name} ({n_layers} 层):")
for layer in range(1, n_layers + 1):
grad *= max_grad
if layer in [1, 5, 10, 15, 20]:
print(f" 第 {layer:2d} 层: 梯度 = {grad:.2e}")
demonstrate_vanishing_gradient()
# He 初始化 vs 随机初始化
print("\nHe 初始化 vs 随机初始化:")
for init_name, scale in [("Xavier", np.sqrt(2/20)), ("He", np.sqrt(2/10)), ("Bad", 5.0)]:
W = np.random.randn(10, 10) * scale
a = np.maximum(0, W @ np.random.randn(10, 100))
print(f" {init_name:8s}: 权重 std={W.std():.4f}, 激活 std={a.std():.4f}")| 问题 | 原因 | 症状 | 解决方案 |
|---|---|---|---|
梯度消失 | 连乘 < 1 的因子 | 浅层权重几乎不更新 | ReLU, He 初始化, 残差连接 |
梯度爆炸 | 连乘 > 1 的因子 | 权重更新过大, NaN | 梯度裁剪, 更好的初始化 |
死亡 ReLU | 负输入导致梯度为零 | 部分神经元永久关闭 | Leaky ReLU, 降低学习率 |
激活饱和 | Sigmoid/Tanh 极值区 | 梯度接近零, 学习停滞 | 换 ReLU, 特征标准化 |
7权重初始化与学习率策略
好的初始化和学习率策略决定了神经网络能否成功训练。
权重初始化:如果权重太大,激活值会饱和(Sigmoid/Tanh)或爆炸(ReLU);如果权重太小,信号会衰减到零。Xavier 初始化假设激活函数是线性的,让每层的输入和输出方差保持一致,适用于 Sigmoid/Tanh。He 初始化考虑了 ReLU 在负区间的截断,方差放大两倍,是 ReLU 网络的标准选择。
学习率调度:固定学习率往往不是最优的。学习率太大可能跳过最优解,太小则训练极慢。常用的调度策略包括:Step Decay(每 N 个 epoch 衰减)、Cosine Annealing(余弦衰减,在终点附近精细化搜索)、Warmup(先小后大再小,Transformer 训练的标准配置)。
ReLU 网络使用 He 初始化:std = sqrt(2/fan_in)
Sigmoid/Tanh 使用 Xavier 初始化:std = sqrt(1/fan_in)
学习率是最关键的超参数——先调 LR,再调其他
从 LR=0.01 开始尝试,以 10 倍间隔搜索 {0.1, 0.01, 0.001, ...}
Warmup + Cosine Annealing 是 Transformer 训练的标配
Adam 优化器通常比 SGD 对学习率更不敏感
永远不要用大的随机值初始化权重!这是新手最常见的错误之一。错误的初始化会导致激活值饱和或爆炸,网络从一开始就无法学习。