AI: 神经网络
最后更新:2026-08-26
"深度学习"听起来高深莫测,但它的基本单元——神经元——只是一个简单的加权求和再做一次非线性变换。本章从感知机出发,带你一步步理解多层网络、激活函数、前向传播与反向传播,并用纯 Python 从零实现一个能解决 XOR 问题的 MLP。
1. 你将学到
- 感知机:最简单的神经网络单元
- 多层感知机(MLP)的结构与直觉
- 激活函数的作用与选择(Sigmoid / ReLU / Tanh)
- 前向传播与反向传播的核心直觉
- 深度学习 = 多层神经网络的叠加
2. 故事:10 行代码的"大脑"
(1) 痛点:深度学习到底是什么
Bob 听说"深度学习"能识别人脸、翻译语言、写文章,但他完全不理解神经网络是什么。他试过看 Wikipedia,满屏的偏导数和链式法则让他直接关掉了页面。"难道就不能用最简单的方式告诉我,一个'神经元'到底在干什么吗?"
(2) Alice 的 10 行代码
Alice 笑着打开 Python,10 行代码写了一个感知机——输入两个数,输出 0 或 1:
# A single perceptron: AND gate
def perceptron(x1, x2, w1=1, w2=1, b=-1.5):
return 1 if w1 * x1 + w2 * x2 + b > 0 else 0
for x1, x2 in [(0,0),(0,1),(1,0),(1,1)]:
print(f"AND({x1},{x2}) = {perceptron(x1, x2)}")
AND(0,0) = 0
AND(0,1) = 0
AND(1,0) = 0
AND(1,1) = 1
Bob 恍然:"这不就是一个加权投票嘛!两个输入都投 1,加权求和超过阈值就输出 1——原来神经网络就是把很多个这种简单决策单元连起来!"
(3) 收益:从简单单元到强大网络
Alice 点头:"没错。一个感知机只能做线性分类,但把几十、几百个感知机叠成多层网络,就能拟合极其复杂的函数——这就是深度学习的起点。"
3. 感知机——最简单的神经网络
(1) 感知机的数学定义
感知机(Perceptron)由心理学家 Rosenblatt 于 1958 年提出,是最早的人工神经元模型。它接收多个输入 $x_1, x_2, \ldots, x_n$,每个输入有一个权重 $w_i$,加上偏置 $b$,最后通过阶跃函数输出 0 或 1:
$$y = \text{step}\left(\sum_{i=1}^{n} w_i x_i + b\right)$$
其中 $\text{step}(z) = 1$ 若 $z > 0$,否则 $0$。
直觉:想象你在决定是否出门跑步——温度、空气质量、是否有空、心情——每个因素有一个"权重",加起来超过你的"懒惰阈值"就出门。
(2) 感知机的几何意义
感知机在二维空间画出一条直线 $w_1 x_1 + w_2 x_2 + b = 0$,直线一侧输出 1,另一侧输出 0。它能完美解决 AND、OR 等线性可分问题,但无法解决 XOR 问题——因为 XOR 的两类点无法被一条直线分开。
▶ 示例:纯 Python 实现感知机 AND 门(难度⭐)
# Perceptron AND gate with training
def step(z):
return 1 if z > 0 else 0
def train_perceptron(X, y, lr=0.1, epochs=10):
n_features = len(X[0])
weights = [0.0] * n_features
bias = 0.0
for epoch in range(epochs):
total_error = 0
for xi, yi in zip(X, y):
z = sum(w * x for w, x in zip(weights, xi)) + bias
pred = step(z)
error = yi - pred
total_error += abs(error)
for j in range(n_features):
weights[j] += lr * error * xi[j]
bias += lr * error
if total_error == 0:
break
return weights, bias
X = [[0,0],[0,1],[1,0],[1,1]]
y_and = [0, 0, 0, 1]
w, b = train_perceptron(X, y_and)
print(f"Weights: {w}, Bias: {b}")
for xi in X:
print(f"AND{xi} = {step(sum(ww*xx for ww,xx in zip(w,xi)) + b)}")
Weights: [0.2, 0.1], Bias: -0.2
AND[0, 0] = 0
AND[0, 1] = 0
AND[1, 0] = 0
AND[1, 1] = 1
4. 多层感知机(MLP)结构
(1) 为什么需要多层
单层感知机只能画直线,解决不了 XOR。但如果把两个感知机的输出再送进第三个感知机——就能用两条直线组合出非线性边界。这就是多层感知机(Multi-Layer Perceptron, MLP)的核心思想。
(2) MLP 的三层结构
| 层 | 作用 | 例子 |
|---|---|---|
| 输入层 | 接收原始特征 | 2 个节点(x₁, x₂) |
| 隐藏层 | 提取中间特征 | 4 个节点(学习到的特征) |
| 输出层 | 给出最终预测 | 1 个节点(0 或 1) |
每一层的每个节点都与上一层的所有节点相连(全连接),连接的强度就是权重。
(3) 神经网络前向传播流程
graph LR
X1["x₁"] --> H1["h₁ (sigmoid)"]
X1 --> H2["h₂ (sigmoid)"]
X2["x₂"] --> H1
X2 --> H2
H1 --> Y["y (output)"]
H2 --> Y
style X1 fill:#e1f5fe
style X2 fill:#e1f5fe
style H1 fill:#fff9c4
style H2 fill:#fff9c4
style Y fill:#c8e6c9
直觉:输入层像眼睛,隐藏层像大脑中间的处理区域,输出层像嘴巴说出答案。每一层都在做"加权求和 + 非线性变换"。
(4) 对比:感知机 vs MLP vs 深度网络
| 维度 | 感知机 | MLP | 深度网络 |
|---|---|---|---|
| 层数 | 1 层(无隐藏层) | 2-3 层 | ≥ 4 层(含多个隐藏层) |
| 能力 | 线性分类 | 非线性分类/回归 | 复杂特征自动提取 |
| 训练 | 感知机规则 | 反向传播 | 反向传播 + 优化技巧 |
| 典型应用 | 简单逻辑门 | 小规模分类/回归 | 图像识别、NLP、生成 |
| 参数量 | 几十 | 几百~几千 | tens of thousands~billions |
| 代表模型 | Rosenblatt 感知机 | 2 层 MLP | CNN、Transformer |
5. 激活函数——让网络拥有非线性
(1) 为什么需要激活函数
如果没有激活函数,不管有多少层,网络本质上还是一个线性变换——多层线性变换的叠加仍然是线性的。激活函数引入非线性,让网络有能力拟合复杂的曲线和边界。
类比:线性变换像"拉伸和旋转",无论拉伸旋转多少次,结果还是直线/平面。激活函数像"折叠"——把纸折一下,空间就变了。
(2) 常见激活函数对比
| 函数 | 公式 | 输出范围 | 优点 | 缺点 |
|---|---|---|---|---|
| Sigmoid | $\sigma(z)=\frac{1}{1+e^{-z}}$ | (0, 1) | 输出可当概率 | 梯度消失、非零中心 |
| Tanh | $\tanh(z)=\frac{e^z-e^{-z}}{e^z+e^{-z}}$ | (-1, 1) | 零中心 | 梯度消失 |
| ReLU | $\max(0, z)$ | [0, +∞) | 计算快、缓解梯度消失 | 神经元死亡(负区梯度为 0) |
| Leaky ReLU | $\max(0.01z, z)$ | (-∞, +∞) | 解决神经元死亡 | 需调超参 |
▶ 示例:不同激活函数输出值对比(难度⭐)
# Compare activation functions
import numpy as np
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def tanh(z):
return np.tanh(z)
def relu(z):
return np.maximum(0, z)
def leaky_relu(z, alpha=0.01):
return np.where(z > 0, z, alpha * z)
z_values = np.array([-5, -2, -1, 0, 1, 2, 5])
print(f"{'z':>5} | {'Sigmoid':>8} | {'Tanh':>8} | {'ReLU':>8} | {'LeakyReLU':>10}")
print("-" * 55)
for z in z_values:
s = sigmoid(z)
t = tanh(z)
r = float(relu(np.array([z]))[0])
lr = float(leaky_relu(np.array([z]))[0])
print(f"{z:5.1f} | {s:8.4f} | {t:8.4f} | {r:8.4f} | {lr:10.4f}")
z | Sigmoid | Tanh | ReLU | LeakyReLU
-------------------------------------------------------
-5.0 | 0.0067 | -0.9999 | 0.0000 | -0.0500
-2.0 | 0.1192 | -0.9640 | 0.0000 | -0.0200
-1.0 | 0.2689 | -0.7616 | 0.0000 | -0.0100
0.0 | 0.5000 | 0.0000 | 0.0000 | 0.0000
1.0 | 0.7311 | 0.7616 | 1.0000 | 1.0000
2.0 | 0.8808 | 0.9640 | 2.0000 | 2.0000
5.0 | 0.9933 | 0.9999 | 5.0000 | 5.0000
6. 前向传播与反向传播
(1) 前向传播:从输入到输出
前向传播(Forward Propagation)就是数据从输入层出发,逐层计算到输出层的过程:
- 计算隐藏层输入:$z^{(1)} = W^{(1)} \cdot x + b^{(1)}$
- 应用激活函数:$h = \sigma(z^{(1)})$
- 计算输出层输入:$z^{(2)} = W^{(2)} \cdot h + b^{(2)}$
- 输出预测:$\hat{y} = \sigma(z^{(2)})$
(2) 损失函数:衡量预测与真实的差距
常用均方误差(MSE)作为损失:
$$L = \frac{1}{2}(y - \hat{y})^2$$
损失越小,预测越准。训练的目标就是找到使损失最小的权重。
(3) 反向传播:从误差到权重更新
反向传播(Backpropagation)的核心思想:从输出层的误差出发,沿着网络反向逐层计算每个权重对误差的"贡献",然后调整权重。
直觉类比:一个工厂产品出问题——
- 先看最后一道工序(输出层)出了多少错
- 再追问上一道工序(隐藏层)贡献了多少错
- 逐层追溯,直到原材料(输入层)
- 每道工序根据自己贡献的错来调整操作方式
数学上就是链式法则(Chain Rule):$\frac{\partial L}{\partial w} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial z} \cdot \frac{\partial z}{\partial w}$
(4) 反向传播关键概念表
| 概念 | 含义 | 直觉 |
|---|---|---|
| 梯度(Gradient) | 损失对权重的偏导数 | 权重该往哪个方向调、调多少 |
| 学习率(Learning Rate) | 每步调整的步长 | 太大→跳过最优,太小→训练太慢 |
| 链式法则 | 逐层传递梯度 | 从输出到输入逐层追问"谁该负责" |
| 权重更新 | $w \leftarrow w - \eta \cdot \frac{\partial L}{\partial w}$ | 沿梯度反方向走一步 |
| 梯度消失 | 梯度逐层缩小趋近 0 | 前面几层几乎学不到东西 |
| 梯度爆炸 | 梯度逐层放大 | 权重更新太猛,模型不稳定 |
(5) 损失曲面直觉
想象你站在一座山上,目标是走到谷底(最小损失)。你每一步都沿着最陡的方向往下走——这就是梯度下降。学习率决定了你的步幅:
- 步幅太大:可能跨过谷底到对面山坡
- 步幅太小:走半天还在半山腰
- 最优步幅:稳定且快速地到达谷底
7. 用 sklearn 快速体验 MLP
▶ 示例:sklearn MLPClassifier 分类(难度⭐)
# MLPClassifier on XOR problem
from sklearn.neural_network import MLPClassifier
import numpy as np
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([0, 1, 1, 0])
mlp = MLPClassifier(hidden_layer_sizes=(4,), activation='relu',
max_iter=2000, random_state=42)
mlp.fit(X, y)
print("XOR Predictions:")
for xi in X:
print(f" {xi} -> {mlp.predict([xi])[0]}")
print(f"Accuracy: {mlp.score(X, y):.2f}")
XOR Predictions:
[0 0] -> 0
[0 1] -> 1
[1 0] -> 1
[1 1] -> 0
Accuracy: 1.00
▶ 示例:可视化决策边界变化(难度⭐⭐)
# Visualize MLP decision boundary on XOR
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neural_network import MLPClassifier
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([0, 1, 1, 0])
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
hidden_configs = [(2,), (4,), (8,)]
for ax, config in zip(axes, hidden_configs):
mlp = MLPClassifier(hidden_layer_sizes=config, activation='relu',
max_iter=3000, random_state=42)
mlp.fit(X, y)
xx, yy = np.meshgrid(np.linspace(-0.5, 1.5, 100),
np.linspace(-0.5, 1.5, 100))
Z = mlp.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
ax.contourf(xx, yy, Z, alpha=0.3, cmap='coolwarm')
ax.scatter(X[:,0], X[:,1], c=y, cmap='coolwarm', edgecolors='k', s=100)
ax.set_title(f"Hidden: {config}, Acc: {mlp.score(X,y):.2f}")
ax.set_xlabel("x1")
ax.set_ylabel("x2")
plt.tight_layout()
plt.savefig("mlp_decision_boundary.png", dpi=100)
plt.show()
▶ 示例:隐藏层节点数对性能影响(难度⭐⭐)
# Effect of hidden layer size on training loss
import numpy as np
from sklearn.neural_network import MLPClassifier
import matplotlib.pyplot as plt
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([0, 1, 1, 0])
fig, ax = plt.subplots(figsize=(8, 5))
for n_hidden in [2, 4, 8, 16]:
mlp = MLPClassifier(hidden_layer_sizes=(n_hidden,), activation='relu',
max_iter=3000, random_state=42, solver='lbfgs')
mlp.fit(X, y)
ax.plot(range(len(mlp.loss_curve_)), mlp.loss_curve_,
label=f"Hidden={n_hidden}")
ax.set_xlabel("Iteration")
ax.set_ylabel("Loss")
ax.set_title("Training Loss vs Hidden Layer Size")
ax.legend()
ax.set_yscale('log')
plt.savefig("hidden_size_loss.png", dpi=100)
plt.show()
8. 深度学习 = 多层神经网络
(1) 传统 ML vs 深度学习对比
| 维度 | 传统机器学习 | 深度学习 |
|---|---|---|
| 特征工程 | 需要人工设计和选择特征 | 自动从数据中学习特征 |
| 数据量需求 | 少量数据即可 | 通常需要大量数据 |
| 计算资源 | CPU 即可 | 通常需要 GPU |
| 模型可解释性 | 较高(决策树、线性模型) | 较低(黑箱) |
| 性能天花板 | 受特征质量限制 | 随数据和模型规模增长 |
| 代表算法 | SVM、随机森林、XGBoost | CNN、RNN、Transformer |
| 适用场景 | 结构化数据、小样本 | 图像、文本、语音等非结构化数据 |
(2) "深"是指什么
"深度"指的是隐藏层的数量。一般约定:
- 浅层网络:1 个隐藏层(MLP)
- 深度网络:2 个及以上隐藏层
- 超深网络:几十甚至上百层(ResNet-152 有 152 层)
层数越多,网络能提取的特征越抽象——第 1 层可能学边缘,第 2 层学纹理,第 3 层学部件,越往后越接近"语义"。
(3) 深度不一定更好
网络越深不等于效果越好。层数过多会导致:
- 梯度消失/爆炸:信号在层层传递中衰减或放大
- 过拟合:参数太多,记住训练数据而非学到规律
- 训练成本:更多参数 = 更长时间 + 更多算力
选择模型深度要根据任务复杂度和数据量——就像盖楼,不是越高越好,要考虑地基和预算。
9. 综合实战:从零实现 MLP 分类器
▶ 示例:从零实现 MLP 解决 XOR 问题(难度⭐⭐⭐)
下面我们用纯 Python(不用任何框架)实现一个完整的 MLP,包括前向传播、反向传播和训练循环,目标是解决 XOR 问题。
# MLP from scratch: solve XOR with pure Python + NumPy
import numpy as np
# --- Activation functions and their derivatives ---
def sigmoid(z):
return 1 / (1 + np.exp(-np.clip(z, -500, 500)))
def sigmoid_deriv(a):
return a * (1 - a)
# --- MLP class ---
class MLP:
def __init__(self, layer_sizes):
# layer_sizes e.g. [2, 4, 1] => 2-input, 4-hidden, 1-output
self.weights = []
self.biases = []
for i in range(len(layer_sizes) - 1):
w = np.random.randn(layer_sizes[i], layer_sizes[i+1]) * 0.5
b = np.zeros((1, layer_sizes[i+1]))
self.weights.append(w)
self.biases.append(b)
def forward(self, X):
self.activations = [X]
for i in range(len(self.weights)):
z = self.activations[-1] @ self.weights[i] + self.biases[i]
a = sigmoid(z)
self.activations.append(a)
return self.activations[-1]
def backward(self, y, lr=0.5):
m = y.shape[0]
deltas = [None] * len(self.weights)
# Output layer delta
output = self.activations[-1]
deltas[-1] = (output - y) * sigmoid_deriv(output)
# Hidden layer deltas (backprop)
for i in range(len(self.weights) - 2, -1, -1):
deltas[i] = (deltas[i+1] @ self.weights[i+1].T) * sigmoid_deriv(self.activations[i+1])
# Update weights and biases
for i in range(len(self.weights)):
self.weights[i] -= lr * (self.activations[i].T @ deltas[i]) / m
self.biases[i] -= lr * np.mean(deltas[i], axis=0, keepdims=True)
def train(self, X, y, epochs=5000, lr=0.5):
self.losses = []
for epoch in range(epochs):
pred = self.forward(X)
loss = np.mean((y - pred) ** 2) / 2
self.losses.append(loss)
self.backward(y, lr)
if epoch % 1000 == 0:
print(f"Epoch {epoch:4d} | Loss: {loss:.6f}")
def predict(self, X):
return (self.forward(X) > 0.5).astype(int)
# --- Train on XOR ---
np.random.seed(42)
X = np.array([[0,0],[0,1],[1,0],[1,1]], dtype=float)
y = np.array([[0],[1],[1],[0]], dtype=float)
mlp = MLP([2, 4, 1])
mlp.train(X, y, epochs=5000, lr=1.0)
print("\nFinal Predictions:")
preds = mlp.predict(X)
for i in range(4):
print(f" XOR{X[i].astype(int).tolist()} = {preds[i,0]} (target: {y[i,0].astype(int)})")
Epoch 0 | Loss: 0.132415
Epoch 1000 | Loss: 0.004726
Epoch 2000 | Loss: 0.002517
Epoch 3000 | Loss: 0.001751
Epoch 4000 | Loss: 0.001330
Final Predictions:
XOR[0, 0] = 0 (target: 0)
XOR[0, 1] = 1 (target: 1)
XOR[1, 0] = 1 (target: 1)
XOR[1, 1] = 0 (target: 0)
❓ 常见问题
📖 小节
| 概念 | 一句话总结 |
|---|---|
| 感知机 | 加权求和 + 阶跃函数,最简单的线性分类器 |
| MLP | 多层感知机,隐藏层带来非线性能力 |
| 激活函数 | 引入非线性,ReLU 是现代默认选择 |
| 前向传播 | 数据从输入层逐层计算到输出层 |
| 反向传播 | 误差从输出层逐层传回,更新权重 |
| 深度学习 | 多层神经网络,自动学习多层次特征 |
| 梯度下降 | 沿损失曲面最陡方向下山,寻找最优权重 |
核心记忆:一个神经元 = 加权求和 + 非线性变换。神经网络 = 很多神经元叠在一起。深度学习 = 很多层神经网络。从 10 行代码的感知机到 GPT,本质都是同一个范式。
📝 作业
基础(⭐)
实现 OR 门的感知机。OR 门的真值表为:(0,0)→0, (0,1)→1, (1,0)→1, (1,1)→1。修改本章感知机训练代码的标签,训练并验证结果。
# Starter code: OR gate perceptron
X = [[0,0],[0,1],[1,0],[1,1]]
y_or = [0, 1, 1, 1]
# TODO: train and test
进阶(⭐⭐)
用 sklearn 的 MLPClassifier 在 Iris 数据集上做分类,调参并记录不同配置的准确率:
# Starter code: MLP on Iris
from sklearn.datasets import load_iris
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import train_test_split
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.3, random_state=42)
# TODO: try different hidden_layer_sizes, activation, learning_rate
# Record accuracy for each config
尝试至少 3 种不同的 hidden_layer_sizes(如 (10,), (50,50), (100,))和 2 种激活函数,记录哪种配置效果最好。
挑战(⭐⭐⭐)
基于本章的纯 Python MLP 实现,画出手动实现的 MLP 的损失下降曲线:
# After training MLP from Section 9, plot the loss curve
import matplotlib.pyplot as plt
# mlp.losses is already recorded during training
plt.figure(figsize=(8, 5))
plt.plot(range(len(mlp.losses)), mlp.losses, linewidth=1)
plt.xlabel("Epoch")
plt.ylabel("Loss (MSE)")
plt.title("MLP Training Loss Curve (XOR)")
plt.yscale('log')
plt.grid(True, alpha=0.3)
plt.savefig("mlp_loss_curve.png", dpi=100)
plt.show()
进阶扩展:① 在同一张图上对比不同学习率(0.1, 0.5, 1.0, 2.0)的损失曲线;② 尝试不同隐藏层大小(2, 4, 8)对收敛速度的影响;③ 观察学习率过大时损失是否震荡甚至发散。