1. 神经网络基础:从感知机到激活函数
1.1 感知机的工作原理
感知机是神经网络最基本的组成单元,它的工作原理可以用一个简单的数学公式表示:
y = { 1 (w₁x₁ + w₂x₂ + ... + wₙxₙ + b > θ)
{ 0 (otherwise)
这个公式中,x₁到xₙ是输入信号,w₁到wₙ是对应的权重,b是偏置项,θ是阈值。当加权和超过阈值时,神经元就会被"激活",输出1;否则输出0。
注意:在实际实现中,我们通常会把阈值θ移到不等式左边,作为偏置项b的一部分。这样公式就简化为y = step(w·x + b),其中step是阶跃函数。
1.2 逻辑电路的感知机实现
感知机可以模拟基本的逻辑门电路,只需要调整权重和偏置的值:
- 与门(AND): w₁=1, w₂=1, b=-1.5
- 或门(OR): w₁=1, w₂=1, b=-0.5
- 与非门(NAND): w₁=-1, w₂=-1, b=1.5
这些参数设置使得感知机能够正确实现对应的逻辑运算。例如,对于与门:
- (0,0): 0+0-1.5=-1.5 ≤0 → 输出0
- (1,1): 1+1-1.5=0.5 >0 → 输出1
1.3 感知机的局限性
虽然感知机能实现与、或、非等线性可分函数,但它无法解决异或(XOR)问题。异或门的真值表如下:
| x1 | x2 | y |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
尝试用一条直线在二维平面上将输出为1和0的点分开是不可能的。这就是感知机的主要局限——它只能解决线性可分问题。
2. 从感知机到神经网络
2.1 引入多层感知机
为了解决异或等非线性可分问题,我们需要引入多层感知机。具体来说,可以通过组合多个感知机来实现:
- 第一层实现NAND和OR
- 第二层将第一层的输出作为输入,实现AND
这样构成的2层感知机网络就能正确实现异或功能。这个例子展示了深度网络解决复杂问题的潜力。
2.2 激活函数的关键作用
单层感知机使用阶跃函数作为激活函数:
step(x) = { 1 (x>0)
{ 0 (x≤0)
而神经网络使用平滑的激活函数,如sigmoid:
σ(x) = 1 / (1 + e⁻ˣ)
平滑激活函数有三个重要特性:
- 非线性:使神经网络能够学习复杂模式
- 可微:支持基于梯度的学习算法
- 有界:输出值在固定范围内(如0-1)
2.3 常用激活函数比较
| 函数 | 公式 | 特点 | 适用场景 |
|---|---|---|---|
| Sigmoid | 1/(1+e⁻ˣ) | 平滑,输出0-1 | 二分类输出层 |
| Tanh | (eˣ-e⁻ˣ)/(eˣ+e⁻ˣ) | 输出-1到1 | 隐藏层 |
| ReLU | max(0,x) | 计算简单,缓解梯度消失 | 隐藏层(常用) |
| Leaky ReLU | max(αx,x) | 解决"神经元死亡"问题 | 深层网络 |
提示:在实际应用中,ReLU及其变体(LReLU,PReLU等)通常是隐藏层的首选,因为它们能有效缓解梯度消失问题且计算高效。
3. 神经网络的前向传播
3.1 网络架构示例
考虑一个3层神经网络(输入层、隐藏层、输出层):
- 输入层:2个神经元
- 隐藏层:3个神经元
- 输出层:2个神经元
对应的参数矩阵维度:
- W₁: 2×3 (输入到隐藏)
- b₁: 1×3
- W₂: 3×2 (隐藏到输出)
- b₂: 1×2
3.2 前向传播的数学过程
-
输入层到隐藏层:
a₁ = XW₁ + b₁
z₁ = σ(a₁) # σ是激活函数 -
隐藏层到输出层:
a₂ = z₁W₂ + b₂
y = softmax(a₂) # 多分类使用softmax
其中,softmax函数定义为:
softmax(z)_i = e^{z_i} / Σ_j e^
3.3 Python实现示例
python复制import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def softmax(x):
exp_x = np.exp(x - np.max(x, axis=1, keepdims=True))
return exp_x / np.sum(exp_x, axis=1, keepdims=True)
# 初始化参数
W1 = np.random.randn(2, 3) * 0.01
b1 = np.zeros((1, 3))
W2 = np.random.randn(3, 2) * 0.01
b2 = np.zeros((1, 2))
# 前向传播
def forward(X):
a1 = np.dot(X, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
return y
4. 神经网络的学习机制
4.1 损失函数
神经网络通过最小化损失函数来学习。常用损失函数包括:
-
均方误差(MSE):
L = 1/N Σ(y_pred - y_true)²
适用于回归问题 -
交叉熵损失:
L = -1/N Σ y_true·log(y_pred)
适用于分类问题
交叉熵损失对错误预测的惩罚更大,因此能更快地推动学习过程。
4.2 梯度下降算法
参数更新公式:
θ = θ - η·∇L(θ)
其中η是学习率,控制更新步长。实际应用中常用变体:
- 随机梯度下降(SGD):每次用一个样本计算梯度
- 小批量梯度下降:用一小批样本(mini-batch)计算梯度
- 带动量的SGD:加入动量项加速收敛
4.3 反向传播算法
反向传播是高效计算梯度的方法,基于链式法则。以2层网络为例:
-
计算输出层梯度:
∂L/∂a₂ = y_pred - y_true -
隐藏层梯度:
∂L/∂W₂ = z₁ᵀ · (∂L/∂a₂)
∂L/∂b₂ = sum(∂L/∂a₂) -
传播到第一层:
∂L/∂a₁ = (∂L/∂a₂) · W₂ᵀ * σ'(a₁) -
输入层梯度:
∂L/∂W₁ = Xᵀ · (∂L/∂a₁)
∂L/∂b₁ = sum(∂L/∂a₁)
4.4 Python实现训练过程
python复制def train(X, y, epochs=1000, lr=0.1):
for epoch in range(epochs):
# 前向传播
a1 = np.dot(X, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y_pred = softmax(a2)
# 计算损失
loss = -np.mean(y * np.log(y_pred + 1e-15))
# 反向传播
da2 = y_pred - y
dW2 = np.dot(z1.T, da2)
db2 = np.sum(da2, axis=0, keepdims=True)
da1 = np.dot(da2, W2.T) * z1 * (1 - z1)
dW1 = np.dot(X.T, da1)
db1 = np.sum(da1, axis=0)
# 参数更新
W1 -= lr * dW1
b1 -= lr * db1
W2 -= lr * dW2
b2 -= lr * db2
if epoch % 100 == 0:
print(f"Epoch {epoch}, Loss: {loss:.4f}")
5. 实践技巧与常见问题
5.1 参数初始化
不恰当的初始化会导致梯度消失或爆炸问题。常用方法:
-
Xavier初始化:
W ~ N(0, sqrt(2/(n_in + n_out)))
适用于sigmoid/tanh -
He初始化:
W ~ N(0, sqrt(2/n_in))
适用于ReLU
5.2 学习率选择
学习率太大可能导致震荡不收敛,太小则收敛缓慢。策略:
-
学习率衰减:
随训练过程逐渐减小η -
自适应优化器:
如Adam、RMSprop等自动调整学习率
5.3 过拟合应对
-
正则化:
L2正则:L = L₀ + λ/2||W||²
L1正则:L = L₀ + λ|W| -
Dropout:
训练时随机丢弃部分神经元 -
早停:
验证集性能不再提升时停止训练
5.4 常见问题排查
-
梯度消失:
- 使用ReLU等激活函数
- 批归一化(BatchNorm)
- 残差连接
-
梯度爆炸:
- 梯度裁剪
- 权重正则化
-
模型不收敛:
- 检查数据预处理
- 调整学习率
- 验证损失计算是否正确
6. 完整神经网络实现
下面是一个完整的2层神经网络类实现,包含训练和评估功能:
python复制import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
from sklearn.preprocessing import OneHotEncoder
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size):
# 参数初始化
self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(2./input_size)
self.b1 = np.zeros((1, hidden_size))
self.W2 = np.random.randn(hidden_size, output_size) * np.sqrt(2./hidden_size)
self.b2 = np.zeros((1, output_size))
def relu(self, x):
return np.maximum(0, x)
def relu_deriv(self, x):
return (x > 0).astype(float)
def softmax(self, x):
exp_x = np.exp(x - np.max(x, axis=1, keepdims=True))
return exp_x / np.sum(exp_x, axis=1, keepdims=True)
def forward(self, X):
self.a1 = np.dot(X, self.W1) + self.b1
self.z1 = self.relu(self.a1)
self.a2 = np.dot(self.z1, self.W2) + self.b2
return self.softmax(self.a2)
def compute_loss(self, y_pred, y_true):
m = y_true.shape[0]
log_probs = -np.log(y_pred[range(m), y_true.argmax(axis=1)] + 1e-15)
return np.sum(log_probs) / m
def backward(self, X, y_true, y_pred):
m = X.shape[0]
# 输出层梯度
da2 = y_pred - y_true
self.dW2 = np.dot(self.z1.T, da2) / m
self.db2 = np.sum(da2, axis=0, keepdims=True) / m
# 隐藏层梯度
da1 = np.dot(da2, self.W2.T) * self.relu_deriv(self.a1)
self.dW1 = np.dot(X.T, da1) / m
self.db1 = np.sum(da1, axis=0, keepdims=True) / m
def update_params(self, lr):
self.W1 -= lr * self.dW1
self.b1 -= lr * self.db1
self.W2 -= lr * self.dW2
self.b2 -= lr * self.db2
def train(self, X, y, epochs=1000, lr=0.1, batch_size=32):
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2)
y_train_oh = OneHotEncoder().fit_transform(y_train.reshape(-1,1)).toarray()
for epoch in range(epochs):
# Mini-batch训练
for i in range(0, X_train.shape[0], batch_size):
X_batch = X_train[i:i+batch_size]
y_batch = y_train_oh[i:i+batch_size]
# 前向传播
y_pred = self.forward(X_batch)
# 反向传播
self.backward(X_batch, y_batch, y_pred)
# 参数更新
self.update_params(lr)
# 验证评估
if epoch % 100 == 0:
train_pred = self.forward(X_train).argmax(axis=1)
train_acc = np.mean(train_pred == y_train.argmax(axis=1))
val_pred = self.forward(X_val).argmax(axis=1)
val_acc = np.mean(val_pred == y_val.argmax(axis=1))
print(f"Epoch {epoch}: Train Acc={train_acc:.4f}, Val Acc={val_acc:.4f}")
# 使用示例
X, y = make_classification(n_samples=1000, n_features=20, n_classes=3, n_informative=5)
y = OneHotEncoder().fit_transform(y.reshape(-1,1)).toarray()
model = TwoLayerNet(input_size=20, hidden_size=64, output_size=3)
model.train(X, y, epochs=1000, lr=0.01, batch_size=32)
这个实现包含了神经网络训练的关键要素:
- 前向传播计算
- 损失函数(交叉熵)
- 反向传播梯度计算
- 参数更新
- 小批量训练
- 验证评估
在实际应用中,还可以添加更多高级特性如学习率调度、早停、正则化等来提升模型性能。
