第5章:神经网络(Neural Networks)
一、导读
1.1 本章学习目标
第5章介绍神经网络——深度学习的基石。神经网络通过多层非线性变换,能够学习数据中复杂的模式。从简单的感知机到现代的深度神经网络,这一章将带你理解神经网络的核心原理。
本章的核心学习目标包括:
- 理解神经元模型:加权求和 + 激活函数的基本结构
- 掌握多层前馈神经网络:网络结构、前向传播过程
- 理解反向传播算法:梯度如何在网络中传播,参数如何更新
- 掌握常见激活函数:sigmoid、ReLU、tanh 的特点和适用场景
- 理解深度学习的含义:为什么"深度"如此重要
- 了解常见的神经网络模型:CNN、RNN 的基本概念
1.2 为什么本章如此重要
神经网络是当前机器学习最成功的方法论。从图像识别到自然语言处理,从游戏 AI 到蛋白质结构预测,神经网络几乎在所有领域都取得了突破性进展。
理解神经网络的原理,不仅有助于使用深度学习工具,更重要的是理解其局限性。反向传播的梯度消失/爆炸问题、过拟合问题、训练不稳定性等,都需要深入理解才能有效解决。
1.3 核心问题引导
单个神经元能解决什么问题?为什么感知机无法解决 XOR 问题?
多层网络比单层网络强在哪里?
反向传播算法的数学原理是什么?链式法则如何应用?
为什么 ReLU 比 sigmoid 更常用?
"深度"学习的"深度"到底意味着什么?
神经网络为什么容易过拟合?如何防止?
二、核心概念详解
2.1 神经元模型
2.1.1 基本结构
神经元是神经网络的基本单元,模拟生物神经元的工作方式:
$$output = f(\sum_{i} w_i x_i + b) = f(w^T x + b)$$
其中 $f$ 是激活函数,$w$ 是权重,$b$ 是偏置。
import numpy as np
class Neuron:
"""单个神经元"""
def __init__(self, n_inputs, activation='sigmoid'):
self.weights = np.random.randn(n_inputs) * 0.01
self.bias = 0
self.activation = activation
def sigmoid(self, z):
return 1 / (1 + np.exp(-np.clip(z, -500, 500)))
def relu(self, z):
return np.maximum(0, z)
def forward(self, x):
z = np.dot(x, self.weights) + self.bias
if self.activation == 'sigmoid':
return self.sigmoid(z)
elif self.activation == 'relu':
return self.relu(z)
return z
# 示例
neuron = Neuron(n_inputs=3, activation='sigmoid')
x = np.array([1.0, 2.0, 3.0])
output = neuron.forward(x)
print(f"神经元输出: {output:.4f}")2.1.2 感知机与 XOR 问题
感知机是最简单的神经元,使用阶跃函数作为激活函数。感知机只能解决线性可分问题,无法解决 XOR 问题。
def perceptron_xor_problem():
"""感知机无法解决XOR问题"""
# XOR 数据
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 0])
print("=== XOR 问题 ===")
print("输入 → 输出")
for i in range(4):
print(f"[{X[i][0]}, {X[i][1]}] → {y[i]}")
print("\nXOR 不是线性可分的:")
print("无法用一条直线将 (0,0),(1,1) 与 (0,1),(1,0) 分开")
print("因此单层感知机无法解决 XOR 问题")
print("\n解决方案:使用多层网络")
print("第一层:学习两个线性边界")
print("第二层:组合两个边界,形成非线性决策")
perceptron_xor_problem()2.2 多层前馈神经网络
2.2.1 网络结构
多层前馈神经网络由输入层、一个或多个隐藏层和输出层组成。每一层的神经元与下一层全连接。
class NeuralNetwork:
"""多层前馈神经网络"""
def __init__(self, layer_sizes):
"""
layer_sizes: [输入维度, 隐藏层1, 隐藏层2, ..., 输出维度]
"""
self.layer_sizes = layer_sizes
self.n_layers = len(layer_sizes) - 1
self.weights = []
self.biases = []
# 初始化参数
for i in range(self.n_layers):
w = np.random.randn(layer_sizes[i], layer_sizes[i+1]) * 0.01
b = np.zeros((1, layer_sizes[i+1]))
self.weights.append(w)
self.biases.append(b)
def sigmoid(self, z):
return 1 / (1 + np.exp(-np.clip(z, -500, 500)))
def sigmoid_derivative(self, a):
return a * (1 - a)
def relu(self, z):
return np.maximum(0, z)
def relu_derivative(self, z):
return (z > 0).astype(float)
def forward(self, X):
"""前向传播"""
self.activations = [X]
self.z_values = []
a = X
for i in range(self.n_layers):
z = a @ self.weights[i] + self.biases[i]
self.z_values.append(z)
if i < self.n_layers - 1:
a = self.relu(z) # 隐藏层用ReLU
else:
a = self.sigmoid(z) # 输出层用sigmoid
self.activations.append(a)
return a
def backward(self, y, learning_rate=0.01):
"""反向传播"""
m = y.shape[0]
y = y.reshape(-1, 1)
# 输出层误差
delta = self.activations[-1] - y
# 从输出层向隐藏层传播
for i in range(self.n_layers - 1, -1, -1):
# 计算梯度
dw = self.activations[i].T @ delta / m
db = np.sum(delta, axis=0, keepdims=True) / m
# 传播误差到下一层
if i > 0:
delta = delta @ self.weights[i].T
delta = delta * self.relu_derivative(self.z_values[i-1])
# 更新参数
self.weights[i] -= learning_rate * dw
self.biases[i] -= learning_rate * db
def train(self, X, y, epochs=1000, learning_rate=0.01):
"""训练网络"""
losses = []
for epoch in range(epochs):
output = self.forward(X)
loss = np.mean((output - y.reshape(-1, 1)) ** 2)
losses.append(loss)
self.backward(y, learning_rate)
if epoch % 200 == 0:
print(f"Epoch {epoch}: Loss = {loss:.4f}")
return losses
def predict(self, X, threshold=0.5):
output = self.forward(X)
return (output >= threshold).astype(int).flatten()
# 示例:解决XOR问题
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 0])
nn = NeuralNetwork([2, 4, 1])
nn.train(X, y, epochs=1000, learning_rate=0.5)
predictions = nn.predict(X)
print(f"\nXOR 预测结果: {predictions}")
print(f"真实标签: {y}")2.3 反向传播算法
2.3.1 数学推导
反向传播算法是训练神经网络的核心。它利用链式法则计算损失函数对每个参数的梯度。
对于第 $l$ 层的参数 $W^{[l]}$:
$$\frac{\partial L}{\partial W^{[l]}} = \delta^{[l]} \cdot (a^{[l-1]})^T$$
其中 $\delta^{[l]}$ 是第 $l$ 层的误差信号,通过链式法则从输出层逐层传播。
def backprop_demo():
"""反向传播的逐步推导"""
print("=== 反向传播算法 ===")
print("\n1. 前向传播:计算每一层的输出")
print(" z[l] = W[l] @ a[l-1] + b[l]")
print(" a[l] = activation(z[l])")
print()
print("2. 计算输出层误差:")
print(" delta[L] = a[L] - y (对于MSE损失+sigmoid)")
print()
print("3. 反向传播误差:")
print(" delta[l] = (W[l+1].T @ delta[l+1]) * activation'(z[l])")
print()
print("4. 计算梯度:")
print(" dW[l] = delta[l] @ a[l-1].T / m")
print(" db[l] = sum(delta[l]) / m")
print()
print("5. 更新参数:")
print(" W[l] = W[l] - lr * dW[l]")
print(" b[l] = b[l] - lr * db[l]")
backprop_demo()2.3.2 梯度消失与梯度爆炸
def gradient_problem_demo():
"""梯度消失和梯度爆炸演示"""
print("=== 梯度消失与梯度爆炸 ===")
# 梯度消失:sigmoid 激活函数
z_values = np.array([-5, -2, 0, 2, 5])
sigmoid_deriv = np.array([0.0067, 0.105, 0.25, 0.105, 0.0067])
print("\nSigmoid 导数(梯度消失):")
for z, d in zip(z_values, sigmoid_deriv):
print(f" z={z:>3}: sigmoid'(z)={d:.4f}")
print(" → 导数最大只有0.25,多层连乘后梯度急剧缩小")
# ReLU 缓解梯度消失
print("\nReLU 导数(缓解梯度消失):")
for z in z_values:
d = 1.0 if z > 0 else 0.0
print(f" z={z:>3}: relu'(z)={d:.4f}")
print(" → 正区间导数为1,梯度不会缩小")
# 梯度爆炸
print("\n梯度爆炸:当权重过大时")
print(" 如果 W 的绝对值 > 1,多层连乘后梯度指数增长")
print(" 解决方案:梯度裁剪、权重初始化、Batch Normalization")
gradient_problem_demo()2.4 激活函数
def compare_activations():
"""对比不同激活函数"""
z = np.linspace(-5, 5, 100)
# Sigmoid
sigmoid = 1 / (1 + np.exp(-z))
# Tanh
tanh = np.tanh(z)
# ReLU
relu = np.maximum(0, z)
# Leaky ReLU
leaky_relu = np.where(z > 0, z, 0.01 * z)
print("=== 激活函数对比 ===")
print(f"{'函数':<15} {'输出范围':<15} {'导数范围':<15} {'特点'}")
print("-" * 65)
print(f"{'Sigmoid':<15} {'(0, 1)':<15} {'(0, 0.25)':<15} {'概率输出,梯度消失'}")
print(f"{'Tanh':<15} {'(-1, 1)':<15} {'(0, 1)':<15} {'零均值,梯度消失'}")
print(f"{'ReLU':<15} {'[0, ∞)':<15} {'{0, 1}':<15} {'计算快,缓解消失,死亡ReLU'}")
print(f"{'Leaky ReLU':<15} {'(-∞, ∞)':<15} '{0.01, 1}':<15} {'解决死亡ReLU'}")
print("\n选择建议:")
print("- 隐藏层:ReLU(默认选择)")
print("- 二分类输出层:Sigmoid")
print("- 多分类输出层:Softmax")
print("- 回归输出层:线性")
compare_activations()2.5 深度学习
2.5.1 为什么"深度"重要
深度网络比浅层网络有更强的表示能力。理论上,一个足够宽的单隐藏层网络可以近似任意连续函数(万能近似定理)。但深度网络用更少的参数达到同样的表示能力。
def depth_vs_width():
"""深度 vs 宽度"""
print("=== 深度 vs 宽度 ===")
print("\n万能近似定理:")
print("单个隐藏层的前馈网络可以近似任意连续函数")
print("但需要的隐藏层神经元数量可能指数级大")
print()
print("深度网络的优势:")
print("- 用层次化的特征表示复杂函数")
print("- 第1层:学习边缘、纹理等低级特征")
print("- 第2层:组合低级特征为中级特征")
print("- 第3层:组合中级特征为高级语义")
print()
print("示例:识别'猫'")
print("第1层:边缘检测")
print("第2层:纹理、颜色模式")
print("第3层:眼睛、耳朵等部件")
print("第4层:完整的猫脸")
depth_vs_width()2.5.2 常见深度学习模型
def deep_learning_models():
"""常见深度学习模型概述"""
models = {
'CNN (卷积神经网络)': {
'特点': '局部连接、权值共享、平移不变性',
'应用': '图像分类、目标检测、图像分割',
'关键层': '卷积层、池化层、全连接层',
},
'RNN (循环神经网络)': {
'特点': '参数共享、隐藏状态、处理序列数据',
'应用': '自然语言处理、语音识别、时间序列',
'变体': 'LSTM、GRU(解决梯度消失)',
},
'Transformer': {
'特点': '自注意力机制、并行计算、长距离依赖',
'应用': 'NLP(BERT、GPT)、计算机视觉(ViT)',
'关键组件': '多头注意力、位置编码、前馈网络',
},
}
print("=== 常见深度学习模型 ===\n")
for name, info in models.items():
print(f"【{name}】")
for key, value in info.items():
print(f" {key}: {value}")
print()
deep_learning_models()三、实战案例
3.1 案例一:从零实现神经网络
def neural_network_from_scratch():
"""从零实现完整的神经网络"""
np.random.seed(42)
# 生成螺旋数据集
n = 200
theta = np.random.rand(n, 1) * 2 * np.pi
r = np.arange(n).reshape(-1, 1) / n
X = np.hstack([r * np.cos(theta), r * np.sin(theta)])
y = (np.arange(n) % 2).reshape(-1, 1)
# 网络参数
input_size = 2
hidden_size = 16
output_size = 1
learning_rate = 0.1
# 初始化
W1 = np.random.randn(input_size, hidden_size) * 0.1
b1 = np.zeros((1, hidden_size))
W2 = np.random.randn(hidden_size, output_size) * 0.1
b2 = np.zeros((1, output_size))
# 训练
for epoch in range(500):
# 前向传播
z1 = X @ W1 + b1
a1 = np.maximum(0, z1) # ReLU
z2 = a1 @ W2 + b2
a2 = 1 / (1 + np.exp(-z2)) # Sigmoid
# 计算损失
loss = -np.mean(y * np.log(a2 + 1e-10) + (1 - y) * np.log(1 - a2 + 1e-10))
# 反向传播
dz2 = a2 - y
dW2 = a1.T @ dz2 / n
db2 = np.sum(dz2, axis=0, keepdims=True) / n
da1 = dz2 @ W2.T
dz1 = da1 * (z1 > 0)
dW1 = X.T @ dz1 / n
db1 = np.sum(dz1, axis=0, keepdims=True) / n
# 更新参数
W2 -= learning_rate * dW2
b2 -= learning_rate * db2
W1 -= learning_rate * dW1
b1 -= learning_rate * db1
if epoch % 100 == 0:
acc = np.mean((a2 >= 0.5) == y)
print(f"Epoch {epoch}: Loss={loss:.4f}, Acc={acc:.2%}")
print(f"\n最终准确率: {np.mean((a2 >= 0.5) == y):.2%}")
neural_network_from_scratch()3.2 案例二:不同网络结构的对比
def compare_network_architectures():
"""对比不同网络结构"""
np.random.seed(42)
# 生成数据
n = 300
X = np.random.randn(n, 10)
w_true = np.random.randn(10)
y = (X @ w_true > 0).astype(int)
# 划分数据集
indices = np.random.permutation(n)
train_idx, test_idx = indices[:240], indices[240:]
X_train, X_test = X[train_idx], X[test_idx]
y_train, y_test = y[train_idx], y[test_idx]
architectures = [
([10, 1], "单层网络"),
([10, 8, 1], "两层网络"),
([10, 16, 8, 1], "三层网络"),
([10, 32, 16, 8, 1], "四层网络"),
]
print("=== 不同网络结构对比 ===")
print(f"{'结构':<20} {'训练准确率':<12} {'测试准确率':<12}")
print("-" * 48)
for layers, name in architectures:
nn = NeuralNetwork(layers)
nn.train(X_train, y_train, epochs=500, learning_rate=0.05)
train_acc = np.mean(nn.predict(X_train) == y_train)
test_acc = np.mean(nn.predict(X_test) == y_test)
print(f"{name:<20} {train_acc:<12.2%} {test_acc:<12.2%}")
compare_network_architectures()3.3 案例三:激活函数对比实验
def activation_comparison():
"""对比不同激活函数的训练效果"""
np.random.seed(42)
# 生成数据
n = 500
X = np.random.randn(n, 20)
w_true = np.random.randn(20)
y = (X @ w_true > 0).astype(int)
indices = np.random.permutation(n)
X_train, y_train = X[indices[:400]], y[indices[:400]]
X_test, y_test = X[indices[400:]], y[indices[400:]]
activations = {
'Sigmoid': lambda z: 1 / (1 + np.exp(-np.clip(z, -500, 500))),
'Tanh': lambda z: np.tanh(z),
'ReLU': lambda z: np.maximum(0, z),
}
print("=== 激活函数对比 ===")
print(f"{'激活函数':<12} {'训练准确率':<12} {'测试准确率':<12}")
print("-" * 40)
for act_name, act_func in activations.items():
nn = NeuralNetwork([20, 32, 16, 1])
# 修改激活函数
nn.relu = act_func
nn.train(X_train, y_train, epochs=300, learning_rate=0.01)
train_acc = np.mean(nn.predict(X_train) == y_train)
test_acc = np.mean(nn.predict(X_test) == y_test)
print(f"{act_name:<12} {train_acc:<12.2%} {test_acc:<12.2%}")
activation_comparison()四、本章小结
第5章系统介绍了神经网络的核心内容:
神经元模型:加权求和 + 激活函数。感知机只能解决线性可分问题。
多层前馈网络:通过多层非线性变换学习复杂模式。反向传播算法通过链式法则计算梯度。
反向传播:从输出层到输入层逐层传播误差,计算每个参数的梯度。
激活函数:Sigmoid(概率输出)、ReLU(隐藏层首选)、Tanh(零均值)。
深度学习:深度网络通过层次化特征表示实现强大的学习能力。
常见模型:CNN(图像)、RNN(序列)、Transformer(注意力机制)。
神经网络的关键挑战包括梯度消失/爆炸、过拟合、训练不稳定性等。现代技术如 ReLU、Batch Normalization、Dropout、残差连接等有效缓解了这些问题。
五、思考与练习
思考题
为什么感知机不能解决 XOR 问题?添加一个隐藏层后为什么就可以了?
反向传播中,为什么需要保存前向传播的中间结果?
ReLU 的"死亡神经元"问题是什么?Leaky ReLU 如何解决?
为什么深层网络比浅层网络更难训练?
Dropout 为什么能防止过拟合?
编程练习
实现一个带有 Batch Normalization 的神经网络,观察训练速度的变化。
实现 Dropout,在不同 keep_prob 下比较训练和测试性能。
用 NumPy 实现一个简单的 CNN(卷积层 + 池化层 + 全连接层),在 MNIST 数据集上测试。