1. 神经网络基础概念解析
1.1 神经元的基本构成要素
神经网络的基石是神经元模型,它由三个关键部分组成:输入信号、权重参数和激活函数。在实际编码中,我们通常会这样实现一个神经元:
python复制class Neuron:
def __init__(self, input_size):
self.weights = np.random.randn(input_size) * 0.1 # 初始化权重
self.bias = 0.0 # 初始化偏置
def forward(self, inputs):
z = np.dot(inputs, self.weights) + self.bias
return self.activation(z)
注意:权重初始化不宜过大也不宜过小。过大会导致梯度爆炸,过小会导致梯度消失。通常使用Xavier初始化或He初始化等更科学的方法。
偏置项(b)在神经网络中扮演着关键角色。它相当于给神经元的激活设置了一个基准线。当加权和(w1x1 + w2x2 + ...)为0时,偏置决定了神经元是否会被激活。例如,b=-3表示需要输入信号的加权和超过3,该神经元才会被显著激活。
1.2 激活函数比较与选择
激活函数是神经网络非线性的来源,常见的三种激活函数各有特点:
Sigmoid函数:
python复制def sigmoid(x):
return 1 / (1 + np.exp(-x))
特点:输出范围(0,1),适合概率输出。但存在梯度消失问题,当输入绝对值较大时梯度接近0。
ReLU函数:
python复制def relu(x):
return np.maximum(0, x)
特点:计算简单,解决了梯度消失问题。但存在"神经元死亡"问题(负半轴梯度为0)。
Softmax函数:
python复制def softmax(x):
exps = np.exp(x - np.max(x, axis=1, keepdims=True))
return exps / np.sum(exps, axis=1, keepdims=True)
特点:输出总和为1,适合多分类问题的输出层。
在实际项目中,隐藏层通常使用ReLU或其变体(如LeakyReLU),二分类输出层用Sigmoid,多分类输出层用Softmax。
2. 神经网络的前向传播机制
2.1 矩阵运算的本质
神经网络的前向传播本质上是矩阵乘法和激活函数的交替应用。以一个两层的神经网络为例:
python复制# 输入X的形状:(batch_size, input_dim)
# 第一层
z1 = np.dot(X, W1) + b1 # W1形状:(input_dim, hidden_dim)
a1 = relu(z1)
# 第二层
z2 = np.dot(a1, W2) + b2 # W2形状:(hidden_dim, output_dim)
output = softmax(z2)
这里有几个关键点需要注意:
- 矩阵乘法的维度必须匹配:前一个矩阵的列数等于后一个矩阵的行数
- 偏置项通常使用广播机制自动扩展到batch中所有样本
- 现代深度学习框架使用批量处理(batch processing)提高计算效率
2.2 输出层的设计原则
输出层的设计取决于任务类型:
回归问题:
- 使用线性激活(恒等函数)
- 损失函数通常用均方误差(MSE)
- 输出层神经元数量=预测值维度
二分类问题:
- 使用Sigmoid激活
- 损失函数用二元交叉熵
- 输出层1个神经元
多分类问题:
- 使用Softmax激活
- 损失函数用分类交叉熵
- 输出层神经元数量=类别数
实际经验:在推理阶段可以省略Softmax计算,直接取最大值对应的类别,因为Softmax是单调函数不影响排序。
3. 神经网络的学习算法
3.1 损失函数的选择依据
损失函数衡量模型预测与真实值的差距,常见的有:
均方误差(MSE):
python复制def mse_loss(y_pred, y_true):
return np.mean((y_pred - y_true)**2)
适用于回归问题,对异常值敏感。
交叉熵误差:
python复制def cross_entropy(y_pred, y_true):
epsilon = 1e-8 # 防止log(0)
return -np.mean(y_true * np.log(y_pred + epsilon))
适用于分类问题,特别当类别不平衡时表现更好。
为什么不能用准确率作为优化目标?因为准确率是离散指标,不可微,无法用梯度下降法优化。而损失函数是连续可微的,能够提供梯度信息。
3.2 梯度下降法的实现细节
梯度下降法的核心代码如下:
python复制def gradient_descent(params, grads, lr=0.01):
for param, grad in zip(params, grads):
param -= lr * grad
return params
学习率(lr)的选择至关重要:
- 太大:可能跳过最优解甚至发散
- 太小:收敛速度慢,可能陷入局部最优
实践中可以采用学习率衰减策略:
python复制initial_lr = 0.1
decay_rate = 0.95
for epoch in range(epochs):
lr = initial_lr * (decay_rate ** epoch)
# 训练代码...
3.3 Mini-batch训练策略
全量梯度下降计算开销大,随机梯度下降噪声大。Mini-batch是折中方案:
python复制batch_size = 128
for epoch in range(epochs):
# 打乱数据
permutation = np.random.permutation(X_train.shape[0])
X_shuffled = X_train[permutation]
y_shuffled = y_train[permutation]
for i in range(0, X_train.shape[0], batch_size):
X_batch = X_shuffled[i:i+batch_size]
y_batch = y_shuffled[i:i+batch_size]
# 前向传播和反向传播...
batch_size的选择经验:
- 太小:更新方向噪声大,收敛不稳定
- 太大:内存占用高,每次更新计算量大
- 常用值:32/64/128/256,通常选择2的幂次方(与硬件设计匹配更好)
4. 误差反向传播原理
4.1 链式法则的应用
反向传播的核心是链式法则。以Sigmoid层为例:
python复制class Sigmoid:
def __init__(self):
self.out = None
def forward(self, x):
self.out = 1 / (1 + np.exp(-x))
return self.out
def backward(self, dout):
dx = dout * (1.0 - self.out) * self.out
return dx
反向传播时,梯度计算遵循以下规律:
- 加法节点:梯度均等分配
- 乘法节点:梯度交换相乘
- 激活函数:乘以该点的局部梯度
4.2 Softmax-with-Loss层的设计
将Softmax和交叉熵损失合并计算可以提高数值稳定性:
python复制class SoftmaxWithLoss:
def __init__(self):
self.loss = None
self.y = None
self.t = None
def forward(self, x, t):
self.t = t
self.y = softmax(x)
self.loss = cross_entropy(self.y, self.t)
return self.loss
def backward(self, dout=1):
batch_size = self.t.shape[0]
dx = (self.y - self.t) / batch_size
return dx
这个设计有两大优势:
- 反向传播时梯度形式简洁:(y - t)
- 避免了单独计算Softmax和交叉熵时的数值不稳定问题
5. 神经网络实践:MNIST分类
5.1 数据预处理要点
MNIST数据预处理的关键步骤:
python复制# 加载数据
mnist = fetch_openml('mnist_784', version=1, as_frame=False)
X = mnist['data'] / 255.0 # 归一化到[0,1]
y = mnist['target'].astype(np.int32)
# One-hot编码
encoder = OneHotEncoder(sparse_output=False)
y_onehot = encoder.fit_transform(y.reshape(-1, 1))
# 划分训练测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y_onehot, test_size=0.2, random_state=42)
注意事项:
- 像素值归一化可以加速收敛
- 分类问题必须进行one-hot编码
- 随机划分时保持类别分布均衡
5.2 网络架构设计
一个简单的两层MLP实现:
python复制class TwoLayerMLP:
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(hidden_size)
self.W2 = np.random.randn(hidden_size, output_size) * np.sqrt(2./hidden_size)
self.b2 = np.zeros(output_size)
def forward(self, X):
# 第一层
self.z1 = np.dot(X, self.W1) + self.b1
self.a1 = relu(self.z1)
# 第二层
self.z2 = np.dot(self.a1, self.W2) + self.b2
self.a2 = softmax(self.z2)
return self.a2
def backward(self, X, y, lr=0.1):
# 反向传播
batch_size = X.shape[0]
# 输出层梯度
dz2 = (self.a2 - y) / batch_size
dW2 = np.dot(self.a1.T, dz2)
db2 = np.sum(dz2, axis=0)
# 隐藏层梯度
dz1 = np.dot(dz2, self.W2.T) * relu_derivative(self.a1)
dW1 = np.dot(X.T, dz1)
db1 = np.sum(dz1, axis=0)
# 参数更新
self.W2 -= lr * dW2
self.b2 -= lr * db2
self.W1 -= lr * dW1
self.b1 -= lr * db1
5.3 训练过程监控
训练过程中需要监控的关键指标:
python复制# 训练循环
for epoch in range(epochs):
# 训练步骤...
# 计算训练集和验证集指标
train_pred = model.forward(X_train)
train_loss = cross_entropy(train_pred, y_train)
train_acc = accuracy(np.argmax(train_pred, axis=1),
np.argmax(y_train, axis=1))
val_pred = model.forward(X_val)
val_loss = cross_entropy(val_pred, y_val)
val_acc = accuracy(np.argmax(val_pred, axis=1),
np.argmax(y_val, axis=1))
print(f"Epoch {epoch+1}: "
f"Train Loss={train_loss:.4f}, Acc={train_acc:.4f} | "
f"Val Loss={val_loss:.4f}, Acc={val_acc:.4f}")
当观察到以下情况时需要调整模型:
- 训练损失下降但验证损失上升 → 过拟合
- 训练和验证损失都下降很慢 → 学习率可能太小
- 训练和验证准确率都很低 → 模型容量不足
6. 神经网络调优经验
6.1 参数初始化技巧
不同激活函数对应的初始化方法:
Sigmoid/tanh:
python复制# Xavier初始化
W = np.random.randn(fan_in, fan_out) * np.sqrt(1./fan_in)
ReLU及其变体:
python复制# He初始化
W = np.random.randn(fan_in, fan_out) * np.sqrt(2./fan_in)
初始化不当会导致:
- 梯度消失:所有激活值趋近0,梯度无法传播
- 梯度爆炸:激活值过大,梯度数值不稳定
6.2 超参数调优策略
关键超参数及其典型范围:
| 超参数 | 典型范围/选择 | 调整策略 |
|---|---|---|
| 学习率 | 1e-5到1e-1 | 对数尺度搜索 |
| 批量大小 | 32-256 | 根据GPU内存选择 |
| 隐藏层大小 | 64-1024 | 从大到小剪枝 |
| 层数 | 2-10 | 逐步增加 |
实用的调优方法:
- 先使用较大学习率快速验证模型可行性
- 使用学习率预热(learning rate warmup)
- 早停法(early stopping)防止过拟合
6.3 常见问题诊断
梯度消失/爆炸:
- 现象:参数更新量极小/极大
- 解决方案:梯度裁剪、更好的初始化、残差连接
过拟合:
- 现象:训练准确率高但测试准确率低
- 解决方案:Dropout、L2正则化、数据增强
欠拟合:
- 现象:训练和测试准确率都低
- 解决方案:增加模型容量、延长训练时间、减少正则化
7. 神经网络进阶话题
7.1 优化算法比较
除了标准梯度下降,还有多种优化算法:
动量法(Momentum):
python复制v = gamma * v + lr * grad
param -= v
Adam:
python复制m = beta1*m + (1-beta1)*grad
v = beta2*v + (1-beta2)*(grad**2)
param -= lr * m / (np.sqrt(v) + epsilon)
选择建议:
- 全连接网络:Adam通常表现良好
- RNN/Transformer:带热重启的SGD可能更好
- 计算机视觉:SGD with momentum仍然流行
7.2 批量归一化(BatchNorm)
BatchNorm层通常添加在激活函数前:
python复制class BatchNorm:
def __init__(self, dim, eps=1e-5, momentum=0.9):
self.gamma = np.ones(dim)
self.beta = np.zeros(dim)
self.eps = eps
self.momentum = momentum
self.running_mean = np.zeros(dim)
self.running_var = np.ones(dim)
def forward(self, x, train=True):
if train:
batch_mean = np.mean(x, axis=0)
batch_var = np.var(x, axis=0)
self.running_mean = self.momentum*self.running_mean + (1-self.momentum)*batch_mean
self.running_var = self.momentum*self.running_var + (1-self.momentum)*batch_var
x_norm = (x - batch_mean) / np.sqrt(batch_var + self.eps)
else:
x_norm = (x - self.running_mean) / np.sqrt(self.running_var + self.eps)
return self.gamma * x_norm + self.beta
BatchNorm的优势:
- 允许使用更大的学习率
- 减少对初始化的依赖
- 有一定的正则化效果
7.3 正则化技术
Dropout:
python复制class Dropout:
def __init__(self, p=0.5):
self.p = p
self.mask = None
def forward(self, x, train=True):
if train:
self.mask = np.random.rand(*x.shape) > self.p
return x * self.mask / (1 - self.p)
return x
L2正则化:
在损失函数中添加权重惩罚项:
python复制loss = cross_entropy_loss + 0.5*lambda_*(np.sum(W1**2) + np.sum(W2**2))
正则化的选择:
- 小数据集:优先使用Dropout
- 大数据集:L2正则化可能足够
- 深层网络:结合使用多种正则化技术
