一个深度学习可视化的网址:A Neural Network Playground
深度学习的正则化
正则化 (Regularization )是指修改学习算法,使其降低泛化误差而非训练误差。
参数范数惩罚
L 2 L^2 L 2 参数正则化
L 2 L^2 L 2 正则化通过在损失函数中添加权重向量的欧几里得范数平方项实现,其目标函数可表示为:
J ~ ( w ) = J ( w ) + λ 2 ∥ w ∥ 2 2 \begin{align}
\tilde{J}(\boldsymbol{w}) = J(\boldsymbol{w}) + \frac{\lambda}{2} \|\boldsymbol{w}\|_2^2
\end{align}
J ~ ( w ) = J ( w ) + 2 λ ∥ w ∥ 2 2
其中λ \lambda λ 为正则化强度超参数。该正则化项对权重w \boldsymbol{w} w 的梯度为λ w \lambda \boldsymbol{w} λ w ,在梯度下降更新中表现为权重衰减:w ← w − η ( ∇ L + λ w ) \boldsymbol{w} \leftarrow \boldsymbol{w} - \eta(\nabla L + \lambda \boldsymbol{w}) w ← w − η ( ∇ L + λ w ) ,其中η \eta η 为学习率。L 2 L^2 L 2 正则化使权重向量更接近原点 ,降低模型复杂度,同时保持权重分量间平衡,避免某些特征过度主导。
L 1 L^1 L 1 参数正则化
L 1 L^1 L 1 正则化使用权重向量的L 1 L^1 L 1 范数作为惩罚项,目标函数为:
J ~ ( w ) = J ( w ) + λ ∥ w ∥ 1 = J ( w ) + λ ∑ i ∣ w i ∣ \begin{align}
\tilde{J}(\boldsymbol{w}) = J(\boldsymbol{w}) + \lambda \|\boldsymbol{w}\|_1 = J(\boldsymbol{w}) + \lambda \sum_i |w_i|
\end{align}
J ~ ( w ) = J ( w ) + λ ∥ w ∥ 1 = J ( w ) + λ i ∑ ∣ w i ∣
其梯度在w i > 0 w_i > 0 w i > 0 时为+ λ +\lambda + λ ,在w i < 0 w_i < 0 w i < 0 时为− λ -\lambda − λ 。更新规则为:
w i ← w i − η ( ∂ J ∂ w i + λ ⋅ sign ( w i ) ) w_i \leftarrow w_i - \eta \left( \frac{\partial J}{\partial w_i} + \lambda \cdot \text{sign}(w_i) \right)
w i ← w i − η ( ∂ w i ∂ J + λ ⋅ sign ( w i ) )
L 1 L^1 L 1 正则化产生稀疏解:当权重w i w_i w i 的绝对值小于阈值η λ 2 \frac{\eta\lambda}{2} 2 η λ 时,梯度更新会将其置零。这种特征选择特性使模型仅保留最显著的特征权重,显著提高解释性。
作为约束的范数惩罚
在约束优化视角下,正则化可视为对参数空间施加显式约束。考虑带约束的优化问题:
min w L ( w ) s.t. Ω ( w ) ≤ k \begin{equation}
\begin{aligned}
\min_{\boldsymbol{w}} & \quad L(\boldsymbol{w})\\
\text{s.t.} & \quad \Omega(\boldsymbol{w}) \leq k
\end{aligned}
\end{equation}
w min s.t. L ( w ) Ω ( w ) ≤ k
其中Ω ( w ) \Omega(\boldsymbol{w}) Ω ( w ) 为范数惩罚项,k k k 为约束半径。该问题通过拉格朗日乘子法可转化为等价的无约束形式:
min w max λ ≥ 0 ( L ( w ) + λ ( Ω ( w ) − k ) ) \min_{\boldsymbol{w}} \max_{\lambda \geq 0} \left( L(\boldsymbol{w}) + \lambda (\Omega(\boldsymbol{w}) - k) \right)
w min λ ≥ 0 max ( L ( w ) + λ ( Ω ( w ) − k ) )
当约束激活时(Ω ( w ) = k \Omega(\boldsymbol{w}) = k Ω ( w ) = k ),其效果等价于参数范数惩罚。
提前终止
在训练过程中,如果训练误差一直在下降,但是在验证集上的误差到达某个水平后不降反升,那么就可以认为此时模型已经拟合了训练数据,如果继续训练则可能导致模型过拟合,此刻应该停止训练,这就是提前终止。提前终止也是一种正则化方法。
Dropout
4.6. 暂退法(Dropout) — 动手学深度学习 2.0.0 documentation
Dropout 提供了正则化一大类模型的方法,计算方便但功能强大。具体而言,Dropout训练的集成包括所有从基础网络除去非输出单元后形成的子网络,如图所示。最先进的神经网络基于一系列仿射变换和非线性变换,我们只需将一些单元的输出乘零就能有效地删除一个单元。
Dropout训练由所有子网络组成的集成,其中子网络通过从基本网络中删除非输出单元构建。我们从具有两个可见单元和两个隐藏单元的基本网络开始。这四个单元有十六个可能的子集。 右图展示了从原始网络中丢弃不同的单元子集而形成的所有十六个子网络。在这个小例子中,所得到的大部分网络没有输入单元或没有从输入连接到输出的路径。当层较宽时,丢弃所有从输入到输出的可能路径的概率变小,所以这个问题不太可能在出现层较宽的网络中。
数学上,设某层神经元输出为 h ∈ R d \boldsymbol{h} \in \mathbb{R}^d h ∈ R d ,Dropout 可表示为:
h drop = m ⊙ h \begin{align}
\boldsymbol{h}_{\text{drop}} = \boldsymbol{m} \odot \boldsymbol{h}
\end{align}
h drop = m ⊙ h
其中
m i ∼ Bernoulli ( 1 − p ) \begin{align}
m_i \sim \text{Bernoulli}(1-p)
\end{align}
m i ∼ Bernoulli ( 1 − p )
即以概率p p p 的伯努利分布生成0,1向量,也就是说训练时根据概率p p p 来决定是否保留该神经元。在推断阶段,所有神经元均被保留,但需对权重进行缩放(乘以 1 − p 1-p 1 − p )以匹配训练时的期望输出。
计算方便是Dropout的一个优点。训练过程中使用Dropout产生 n n n 个随机二进制 数与状态相乘,每个样本每次更新只需 O ( n ) O(n) O ( n ) 的计算复杂度。Dropout的另一个显著优点是不怎么限制适用的模型或训练过程。几乎在所有 使用分布式表示且可以用随机梯度下降训练的模型上都表现很好。
虽然Dropout在特定模型上每一步的代价是微不足道的,但在一个完整的系统上使用Dropout的代价可能非常显著。因为Dropout是一个正则化技术,它减少了模型的有效容量。为了抵消这种影响,我们必须增大模型规模。不出意外的话,使用Dropout时最佳验证集的误差会低很多,但这是以更大的模型和更多训练算法的迭代次数为代价换来的。对于非常大的数据集,正则化带来的泛化误差减少得很小。在这些情况下,使用Dropout和更大模型的计算代价可能超过正则化带来的好处。
深度学习的归一化
归一化 (Normalization )技术在深度学习中扮演着至关重要的角色,其核心目标是通过标准化数据分布来优化学习过程。对输入数据归一化的作用是为了统一量纲,对网络中的输出进行归一化则是防止误差的过度累积,总而言之都是为了提高反向传播的效率。
简单的归一化方法
min-max归一化
min-max归一化通过线性变换将原始数据映射到[ 0 , 1 ] [0,1] [ 0 , 1 ] 区间:
x norm = x − x min x max − x min x_{\text{norm}} = \frac{x - x_{\min}}{x_{\max} - x_{\min}}
x norm = x m a x − x m i n x − x m i n
其中x min x_{\min} x m i n 和x max x_{\max} x m a x 分别为数据集的最小值和最大值。该方法保留原始数据分布形状,但对异常值敏感:当存在极端离群值时,x max − x min x_{\max}-x_{\min} x m a x − x m i n 显著增大导致有效数据被压缩到狭窄区间。
z-score归一化
z-score归一化将数据转换为均值为0、标准差为1的标准正态分布:
x norm = x − μ σ x_{\text{norm}} = \frac{x - \mu}{\sigma}
x norm = σ x − μ
其中μ \mu μ 为数据均值,σ \sigma σ 为标准差。
批量归一化
批量归一化 (Batch Normalization )是对前馈神经网络的每一层(除输出层外)的净输入或输入在每一个批量的样本上进行归一化,在其基础上训练神经网络的方法。这个方法将特征尺度变换应用到神经网络学习,本质上改变了神经网络的结构。主要作用是防止内部协变量偏移,加快学习收敛速度,在一定程度上防止梯度消失和梯度爆炸。
\begin{algorithm}
\caption{批量归一化}
\begin{algorithmic}
\STATE \textbf{输入:} 神经网络结构 $f(x; \theta)$,训练集,测试样本
\STATE \textbf{输出:} 对测试样本的预测值
\STATE \textbf{超参数:} 批量容量大小 $n$
\STATE 初始化参数 $\theta, \phi$,其中 $\phi = \{ \gamma^{(t)}, \beta^{(t)} \}_{t=1}^{s-1}$
\FOR{每个批量 $b$}
\FOR{$t = 1, 2, \cdots, s-1$}
\STATE 计算第 $t$ 层净输入的均值 $\mu^{(t)}$ 和方差 $\sigma^{2(t)}$
\STATE 执行批量归一化:$z_j^{(t)} \leftarrow \gamma^{(t)} \cdot \dfrac{z_j^{(t)} - \mu^{(t)}}{\sqrt{\sigma^{2(t)} + \epsilon}} + \beta^{(t)}$,其中 $j = 1, 2, \cdots, n$
\ENDFOR
\ENDFOR
\STATE 构建训练神经网络 $f_{Tr}(x; \theta, \phi)$
\STATE 使用随机梯度下降法训练 $f_{Tr}(x; \theta, \phi)$,估计参数 $\theta, \phi$
\FOR{$t = 1, 2, \cdots, s-1$}
\STATE 计算期望均值 $E_b(\mu^{(t)})$ 和期望方差 $E_b(\sigma^{2(t)})$
\STATE 对测试样本执行批量归一化:$z_j^{(t)} \leftarrow \gamma^{(t)} \cdot \dfrac{z_j^{(t)} - E_b(\mu^{(t)})}{\sqrt{E_b(\sigma^{2(t)}) + \epsilon}} + \beta^{(t)}$,其中 $j = 1, 2, \cdots, n$
\ENDFOR
\STATE 构建推理神经网络 $f_{Inf}(x; \theta, \phi)$
\STATE 输出 $f_{Inf}(x; \theta, \phi)$ 对测试样本的预测值
\end{algorithmic}
\end{algorithm}
层归一化
批归一化的效果取决于小批量的大小,且在循环神经网络中的应用受到明显的限制。同时,批归一化也不能应用于在线学习任务或小批量必须很小的极大分布式模型。
层归一化 (Layer Normalization )是针对循环神经网络和Transformer等序列模型设计的归一化方法。与批量归一化不同,层归一化在单个样本的所有特征维度上进行归一化,其计算不依赖于批量大小,特别适用于小批量或变长序列场景。
层归一化算法伪代码
\begin{algorithm}
\caption{层归一化}
\begin{algorithmic}
\STATE \textbf{输入:} 输入向量 $\mathbf{h} = [h_1, h_2, \cdots, h_d]^T$,可学习参数 $\gamma, \beta$
\STATE \textbf{输出:} 归一化输出 $\mathbf{h}'$
\STATE 计算层内均值:$\mu = \dfrac{1}{d}\sum_{i=1}^d h_i$
\STATE 计算层内方差:$\sigma^2 = \dfrac{1}{d}\sum_{i=1}^d (h_i - \mu)^2$
\STATE 执行归一化:$\hat{h}_i = \dfrac{h_i - \mu}{\sqrt{\sigma^2 + \epsilon}} \quad \forall i=1,2,\cdots,d$
\STATE 仿射变换:$h_i' = \gamma \hat{h}_i + \beta \quad \forall i=1,2,\cdots,d$
\STATE \textbf{返回} $\mathbf{h}' = [h_1', h_2', \cdots, h_d']^T$
\end{algorithmic}
\end{algorithm}
深度学习实战#1 BP神经网络
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 import numpy as npimport pandas as pdfrom sklearn.model_selection import train_test_splitimport torchimport torch.nn as nnimport torch.optim as optimfrom torch.utils.data import DataLoader, TensorDatasetfrom torchvision import datasets, transformsdevice = torch.device("cuda" if torch.cuda.is_available() else "cpu" ) device random_seed = 42 torch.manual_seed(random_seed) def load_mnist_data (): transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5 ,), (0.5 ,))]) train_dataset = datasets.MNIST(root='./data' , train=True , download=True , transform=transform) train_dataset, _ = train_test_split(train_dataset, test_size=0.8 , random_state=random_seed) test_dataset = datasets.MNIST(root='./data' , train=False , download=True , transform=transform) return train_dataset, test_dataset class NeuralNetwork (nn.Module): def __init__ (self ): super (NeuralNetwork, self ).__init__() self .layers = nn.Sequential( nn.Linear(28 *28 , 128 ), nn.ReLU(), nn.Linear(128 , 64 ), nn.Dropout(0.2 ), nn.ReLU(), nn.Linear(64 , 10 ) ) def forward (self, x ): """ 前向传播 参数: x (torch.Tensor): 输入图像数据 返回: torch.Tensor: 网络输出,形状为[batch_size, 10] """ x = x.view(x.size(0 ), -1 ) return self .layers(x) def train_model (model, train_loader, criterion, optimizer, epochs=25 ): model.train() for epoch in range (epochs): train_loss = 0.0 correct = 0 total = 0 for images, labels in train_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) loss = criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() train_loss += loss.item() * images.size(0 ) _, predicted = torch.max (outputs.data, 1 ) total += labels.size(0 ) correct += (predicted == labels).sum ().item() epoch_loss = train_loss / total epoch_acc = 100 * correct / total print (f'Epoch [{epoch+1 } /{epochs} ] Loss: {epoch_loss:.4 f} Acc: {epoch_acc:.2 f} %' ) def test_model (model, test_loader ): """ 测试模型性能 参数: model (nn.Module): 要测试的模型 test_loader (DataLoader): 测试数据加载器 返回: tuple: (平均损失, 准确率) """ model.eval () test_loss = 0 correct = 0 total = 0 with torch.no_grad(): for data, target in test_loader: data, target = data.to(device), target.to(device) output = model(data) loss = nn.CrossEntropyLoss()(output, target) test_loss += loss.item() * data.size(0 ) _, predicted = torch.max (output.data, 1 ) total += target.size(0 ) correct += (predicted == target).sum ().item() average_loss = test_loss / len (test_loader.dataset) accuracy = 100 * correct / total print (f'Test Loss: {average_loss:.4 f} , Accuracy: {accuracy:.2 f} %' ) train_dataset, test_dataset = load_mnist_data() train_loader = DataLoader(train_dataset, batch_size=64 , shuffle=True ) test_loader = DataLoader(test_dataset, batch_size=64 , shuffle=False ) model = NeuralNetwork().to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001 ) train_model(model, train_loader, criterion, optimizer, epochs=15 ) test_model(model, test_loader)