在深度学习领域,理解神经网络训练的本质是入门的关键。让我们从一个实际案例开始:假设我们要构建一个手写数字识别系统,输入是28x28像素的图片(展平为784维向量),输出是0-9的数字分类。这个过程中,哪些要素是我们已知的?哪些是需要学习的?
已知量:
未知量:
学习目标的核心是通过梯度下降法,自动调整这些权重参数,使得神经网络能够准确识别手写数字。具体来说,就是找到一组W和b,使得预测结果y与真实标签t之间的差异(用损失函数衡量)最小化。
关键理解:训练过程本质上是寻找高维参数空间中的最优解。对于我们的例子,需要在784×50+50×10=39,700维的空间中找到最佳参数组合。
单层感知机(无隐藏层)本质上是一个线性分类器。想象在二维平面上,它就像用一条直线分隔不同类别的点。对于MNIST这样的复杂数据,单层感知机的识别准确率通常不会超过20%。
数学解释:
单层感知机的输出为:y = Wx + b
其中W是权重矩阵,b是偏置。这种线性变换只能解决线性可分问题,而手写数字识别需要更复杂的非线性决策边界。
假设我们使用三层网络,但所有层都是线性变换:
z1 = W1x + b1
z2 = W2z1 + b2
y = W3z2 + b3
这些线性变换可以合并为一个等效的线性变换:
y = W3(W2(W1x + b1) + b2) + b3 = (W3W2W1)x + (W3W2b1 + W3b2 + b3) = W'x + b'
这意味着无论叠加多少线性层,最终效果等同于单层网络。这就是为什么必须引入非线性激活函数。
常用激活函数对比:
| 函数类型 | 公式 | 特点 | 适用场景 |
|---|---|---|---|
| Sigmoid | 1/(1+e^-x) | 平滑,输出0-1 | 二分类输出层 |
| ReLU | max(0,x) | 计算简单,缓解梯度消失 | 隐藏层首选 |
| Tanh | (e^x-e^-x)/(e^x+e^-x) | 输出-1到1 | RNN等特定场景 |
神经网络的前向传播实际上是复合函数的逐层计算。以我们的两层网络为例:
这个过程中,每一层都在对数据进行逐步变换,从原始像素到边缘特征,再到数字部件,最后到完整数字的识别。
在分类问题中,输出层使用Softmax有三个关键原因:
概率解释:将输出转换为概率分布,满足:
梯度优化:Softmax与交叉熵损失配合使用时,梯度计算非常简洁:
∂L/∂a_i = y_i - t_i
这种线性形式极大提高了训练效率
类别竞争:通过指数运算拉大各类别间的差距,使模型预测更明确
Softmax计算示例:
假设最后一层的线性输出为[2.0, 1.0, 0.1]:
准确率作为指标存在三个根本问题:
相比之下,交叉熵损失:
在我们的MNIST示例中,学习率设为0.1。这个选择基于以下实验:
学习率对比实验:
| 学习率 | 训练表现 | 测试表现 | 收敛速度 | 稳定性 |
|---|---|---|---|---|
| 0.001 | 损失下降极慢 | 准确率低 | 极慢 | 稳定 |
| 0.01 | 损失平稳下降 | 92%准确率 | 慢 | 稳定 |
| 0.1 | 快速收敛 | 94%准确率 | 快 | 较稳定 |
| 1.0 | 损失震荡 | 无法收敛 | - | 不稳定 |
实用调参技巧:
python复制class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
self.params = {}
# 第一层权重初始化
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
# 第二层权重初始化
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
关键细节:
python复制def numerical_gradient(f, x):
h = 1e-4 # 微小变化量
grad = np.zeros_like(x)
# 对每个参数单独计算偏导数
for idx in range(x.size):
tmp_val = x[idx]
# 计算f(x+h)
x[idx] = tmp_val + h
fxh1 = f(x)
# 计算f(x-h)
x[idx] = tmp_val - h
fxh2 = f(x)
# 中心差分公式
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val # 恢复原值
return grad
注意事项:
python复制for i in range(iters_num):
# 随机选取batch
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
# 计算梯度
grad = network.numerical_gradient(x_batch, t_batch)
# 参数更新
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
# 记录损失
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
# 每个epoch评估一次
if i % iter_per_epoch == 0:
train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
print(f"epoch {i//iter_per_epoch}: train acc {train_acc:.4f}, test acc {test_acc:.4f}")
工程经验:
现象:
解决方案:
在MNIST示例中,我们观察到:
改进方法:
python复制loss = cross_entropy + 0.001*(np.sum(W1**2) + np.sum(W2**2))
python复制mask = np.random.rand(*a1.shape) > 0.5
z1 = sigmoid(a1) * mask
当模型表现不佳时,按以下步骤检查:
我们实现的数值梯度虽然直观,但效率极低。实际工程中采用反向传播算法:
反向传播优势:
关键公式(以我们的两层网络为例):
针对MNIST示例,我们可以系统优化:
网格搜索示例:
| 隐藏层大小 | 学习率 | Batch大小 | 测试准确率 |
|---|---|---|---|
| 30 | 0.01 | 50 | 93.2% |
| 50 | 0.1 | 100 | 94.1% |
| 100 | 0.05 | 200 | 94.3% |
| 50 | 0.2 | 100 | 93.8% |
更高效的搜索方法:
虽然我们的全连接网络能达到94%准确率,但卷积神经网络(CNN)更适合图像数据:
CNN改进方案:
预期可将准确率提升至99%以上,同时大幅减少参数量。