目录
- 摘要
- Abstract
- [1. 引言](#1. 引言)
- [2. 参数范数惩罚](#2. 参数范数惩罚)
- [3. 显式约束和重投影](#3. 显式约束和重投影)
-
- [3.1 显式约束](#3.1 显式约束)
- [3.2 重投影](#3.2 重投影)
- [4. 数据集增强](#4. 数据集增强)
- [5. 多任务学习](#5. 多任务学习)
- [6. 提前终止](#6. 提前终止)
- [7. 参数绑定和共享](#7. 参数绑定和共享)
- [8. 稀疏表示](#8. 稀疏表示)
- [9. Bagging和其他集成方法](#9. Bagging和其他集成方法)
- [10. 对抗训练](#10. 对抗训练)
- 参考
- 总结
摘要
本周,我学习了神经网络中的正则化方法。
Abstract
This week, I studied regularization methods in neural networks.
1. 引言
前面介绍了机器学习中的正则化,在机器学习正则化的基础上介绍深度学习的正则化。
在探究正则化之前,需要说明的是在神经网络中参数包括每一层仿射变换的权重和偏置,通常只对权重正则化而不对偏置正则化。不对偏置进行正则化也不会导致太大的方差,相反正则化偏置可能导致欠拟合。
在神经网络中希望对网络的每一层使用单独的惩罚,并分配不同的正则化系数。但是如果这样做的话,寻找多个合适超参数的代价很大,为了减少搜索空间,尽量在所有层使用相同的权重衰减。
2. 参数范数惩罚
参数范数惩罚是一种通过在代价函数中增加额外项来限制模型复杂度的方法,从而防止过拟合。它通过对模型参数进行约束,鼓励模型在学习过程中保持简单,避免过多的自由度。常见的参数范数惩罚方法包括L1范数惩罚和L2范数惩罚。
。L2正则化(也称为权重衰减)通过对权重矩阵的平方和进行惩罚,迫使权重变得尽可能小,从而避免模型的参数过大,避免过拟合。神经网络的所有参数为 θ \theta θ,损失函数为 L ( θ ) L(\theta) L(θ),所有层的权重为 W W W, L 2 L^2 L2正则化后的损失函数为
J ( θ ) = L ( θ ) + λ ∥ W ∥ 2 2 . \Large J(\theta)=L(\theta)+\lambda\|W\|_2^2. J(θ)=L(θ)+λ∥W∥22.
L1正则化通过对参数的绝对值进行惩罚,鼓励模型的某些权重变为零,从而实现稀疏性。稀疏表示有助于特征选择,因为只有少数特征会对最终的预测产生重要影响。 L 1 L^1 L1正则化后的损失函数为
J ( θ ) = L ( θ ) + λ ∥ W ∥ 1 . \Large J(\theta)=L(\theta)+\lambda\|W\|_1. J(θ)=L(θ)+λ∥W∥1.
PyTorch中优化器实现了 L 2 L^2 L2正则化,只需要设置优化器的weight_decay参数即可,这个参数是正则化系数; L 1 L^1 L1正则化需要手动实现。
python
from torch import optim
optimizer = optim.SGD(lr=1e-2, momentum=0.9, weight_decay=0.5)
3. 显式约束和重投影
3.1 显式约束
显式约束和重投影是一种对模型参数施加限制的方法,用于控制模型的训练过程和参数空间,以满足特定的条件或约束。这些技术主要用于提高模型的稳定性、泛化能力,或者让模型满足某些特定的结构化需求。
带有参数范数约束 Ω \Omega Ω的损失函数为
J ( θ ) = L ( θ ) + λ ( Ω ( θ ) − k ) . \Large J(\theta)=L(\theta)+\lambda(\Omega(\theta)-k). J(θ)=L(θ)+λ(Ω(θ)−k).
显式约束可以确保模型的参数满足预定的结构或范围,有助于提高模型的解释性和稳定性。但是直接施加显式约束可能会影响模型的优化过程,尤其是在梯度下降时,强制性的约束可能会导致训练效率下降。这一问题可以重投影技术解决。
3.2 重投影
2. 重投影(Reprojection)
重投影是一种优化方法,用于在训练过程中将不满足约束条件的模型参数投影回满足约束的可行域中。这种方法通常与显式约束结合使用,用于确保训练过程中参数始终满足约束条件。
工作机制:假设在训练过程中,某些权重 W W W在更新后不满足给定的约束条件 C C C。此时,重投影步骤会将 W W W投影回满足 C C C的空间中: W ← P r o j C ( W ) W\leftarrow Proj_C(W) W←ProjC(W),其中 P r o j C Proj_C ProjC是投影操作,将 W W W调整到最近的满足 C C C的点。
重投影方法确保了模型参数始终在可行域内,从而满足显式约束的要求,有助于提高模型的稳定性。但是重投影操作可能增加计算开销,尤其是在高维参数空间或复杂约束下,计算投影可能比较耗时。
4. 数据集增强
让模型泛化得更好的最好方法是是使用更多的数据进行训练。在实践中,可获取的数据是有限的,解决这一问题的一种方法是创建假数据并添加到训练集中。
对于分类任务来说这种方法最容易实现。分类器需要一个复杂的高维输入 x x x,并用单个类别标识 y y y概括 x x x,这意味着分类需要对各种各样的变换保持不变,因此可以通过转换训练集中的 x x x来生成新的 ( x , y ) (x, y) (x,y)对。对于图片分类任务来说,可以通过旋转图片等操作来增强数据集,但是如果增强操作会改变图片的类别,就不能使用这种增强操作。
此外,数据集增强在语音识别任务也是有效的。这里不详细介绍各种领域都有哪些数据增强操作。
5. 多任务学习
多任务学习是通过在一个模型中同时训练多个任务,以提高模型的泛化能力。与传统的单任务学习不同,多任务学习 将多个相关的任务联合学习,利用不同任务之间的共享信息和特征,从而提高每个任务的表现。
特点:
1. 共享表示:在多任务学习中,所有任务共享模型的某些参数(通常是网络的前几层),这样可以让模型在不同任务之间共享有用的信息。通过共享低层次特征,模型能够学到更加通用的表示。
2. 同时训练多个任务:多个任务的损失函数被合并,模型在训练过程中同时优化这些任务的目标函数。通常,这些任务会有不同的权重来平衡每个任务的贡献。
3. 提高泛化能力:通过学习多个相关任务,模型有可能避免过拟合,因为它被迫学会共享不同任务的知识,而不仅仅是针对某个任务优化。
6. 提前终止
当训练有足够的表示能力甚至会过拟合的模型时,可以观察到训练误差随着时间的推移逐渐降低但验证集的误差会再次上升。这意味着只要返回使验证集误差最低的参数设置,就可以获得验证集误差最低的模型,使模型具备最好的泛化能力。这种策略就是提前终止,是深度学习中最常用的正则化形式。
提前终止算法的过程:
p 为 p a t i e n c e , 观察到较坏的验证集表现 p 次后终止。 θ 0 为模型的初始参数。 θ = θ 0 i = 0 ( 训练轮数 ) j = 0 ( 坏验证集表现出现的次数 ) v = ∞ ( 评估的验证集表现 ) θ ∗ = θ ( 使验证集表现更好的参数 ) i ∗ = i ( 使验证集表现更好的步数 ) w h i l e j < p d o 训练算法在训练集上训练一遍 , 更新 θ 。 i = i + 1 v ′ = V a l i d a t i o n S e t E r r o r ( θ ) i f v ′ < v t h e n j = 0 θ ∗ = θ i ∗ = i v = v ′ e l s e j = j + 1 e n d i f e n d w h i l e 最佳参数为 θ ∗ , 最佳训练步数 ( 轮数 ) 为 i ∗ 。 \begin{aligned} &p为patience,观察到较坏的验证集表现p次后终止。\\ &\theta_0为模型的初始参数。\\ &\theta=\theta_0\\ &i=0(训练轮数)\\ &j=0(坏验证集表现出现的次数)\\ &v=\infty(评估的验证集表现)\\ &\theta^*=\theta(使验证集表现更好的参数)\\ &i^*=i(使验证集表现更好的步数)\\ &while \quad j \lt p \quad do\\ &\quad 训练算法在训练集上训练一遍,更新\theta。\\ &\quad i=i+1\\ &\quad v'=ValidationSetError(\theta)\\ &\quad if \quad v' \lt v \quad then\\ &\quad\quad j=0\\ &\quad\quad \theta^*=\theta\\ &\quad\quad i^*=i\\ &\quad\quad v=v'\\ &\quad else\\ &\quad\quad j=j+1\\ &\quad end\quad if\\ &end \quad while 最佳参数为\theta^*,最佳训练步数(轮数)为i^*。 \end{aligned} p为patience,观察到较坏的验证集表现p次后终止。θ0为模型的初始参数。θ=θ0i=0(训练轮数)j=0(坏验证集表现出现的次数)v=∞(评估的验证集表现)θ∗=θ(使验证集表现更好的参数)i∗=i(使验证集表现更好的步数)whilej<pdo训练算法在训练集上训练一遍,更新θ。i=i+1v′=ValidationSetError(θ)ifv′<vthenj=0θ∗=θi∗=iv=v′elsej=j+1endifendwhile最佳参数为θ∗,最佳训练步数(轮数)为i∗。
提前终止需要验证集,这意味着某些训练数据不能被送到模型中。为了更好地利用这些额外的数据,可以在完成提前终止的某次训练后进行额外的训练。在第二轮中,所有的训练数据都被包括在内。
额外训练有二种方式:1. 再次初始化模型,然后使用所有数据训练,训练步数为第一轮提前终止确定的最佳步数。2. 保存从第一轮训练获得的最佳参数,然后使用全部数据继续训练,直到在第一轮的验证集上的平均损失函数值低于第一轮提前终止时的值。
下面给出模型训练过程中如何使用提前终止的例子(需要使用ignite)。
python
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
from torchvision.models import resnet18
from torchvision.transforms.v2 import Compose, Normalize, ToImage, ToDtype
from ignite.engine import Engine, Events, create_supervised_trainer, create_supervised_evaluator
from ignite.metrics import Accuracy, Loss
from ignite.handlers import ModelCheckpoint, EarlyStopping
from ignite.contrib.handlers import TensorboardLogger, global_step_from_engine
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.model = resnet18(num_classes=10)
self.model.conv1 = self.model.conv1 = nn.Conv2d(
1, 64, kernel_size=3, padding=1, bias=False
)
def forward(self, x):
return self.model(x)
model = Net().to(device)
data_transform = Compose([
ToImage(),
ToDtype(torch.float32, scale=True),
Normalize((0.1307,), (0.3081,))
])
train_loader = DataLoader(
MNIST(download=True, root=".", transform=data_transform, train=True), batch_size=128, shuffle=True
)
val_loader = DataLoader(
MNIST(download=True, root=".", transform=data_transform, train=False), batch_size=256, shuffle=False
)
optimizer = torch.optim.RMSprop(model.parameters(), lr=0.005)
criterion = nn.CrossEntropyLoss()
trainer = create_supervised_trainer(model, optimizer, criterion, device)
val_metrics = {
"accuracy": Accuracy(),
"loss": Loss(criterion)
}
train_evaluator = create_supervised_evaluator(model, metrics=val_metrics, device=device)
val_evaluator = create_supervised_evaluator(model, metrics=val_metrics, device=device)
log_interval = 100
@trainer.on(Events.ITERATION_COMPLETED(every=log_interval))
def log_training_loss(engine):
print(f"Epoch[{engine.state.epoch}], Iter[{engine.state.iteration}] Loss: {engine.state.output:.2f}")
@trainer.on(Events.EPOCH_COMPLETED)
def log_training_results(trainer):
train_evaluator.run(train_loader)
metrics = train_evaluator.state.metrics
print(f"Training Results - Epoch[{trainer.state.epoch}] Avg accuracy: {metrics['accuracy']:.2f} Avg loss: {metrics['loss']:.2f}")
@trainer.on(Events.EPOCH_COMPLETED)
def log_validation_results(trainer):
val_evaluator.run(val_loader)
metrics = val_evaluator.state.metrics
print(f"Validation Results - Epoch[{trainer.state.epoch}] Avg accuracy: {metrics['accuracy']:.2f} Avg loss: {metrics['loss']:.2f}")
def score_function(engine):
return -engine.state.metrics["loss"]
model_checkpoint = ModelCheckpoint(
"checkpoint",
n_saved=1,
filename_prefix="best",
score_function=score_function, #默认是越大越好,因此需要添加负号
score_name="val_loss",
global_step_transform=global_step_from_engine(trainer),
)
early_stopping = EarlyStopping(patience=5, score_function=score_function, trainer=trainer) #默认是越大越好
val_evaluator.add_event_handler(Events.COMPLETED, early_stopping)
val_evaluator.add_event_handler(Events.COMPLETED, model_checkpoint, {"model": model})
trainer.run(train_loader, max_epochs=50)
7. 参数绑定和共享
在神经网络中,参数绑定和共享可以帮助减少模型的复杂性、降低计算和存储成本,同时增加模型的泛化能力,防止过拟合。它们在不同的神经网络架构中被广泛使用,尤其是在卷积神经网络和递归神经网络中。
在卷积神经网络中,所有卷积核在整个输入图像上共享相同的参数。在这种情况下,卷积层的参数在所有位置上都是相同的,这就是参数共享的一种形式。卷积操作通过滑动卷积核并对输入的不同区域应用相同的权重,来学习特征。
8. 稀疏表示
在深度学习中,稀疏表示(Sparse Representation) 是一种将数据表示为在某个基下大部分系数为零或接近零的形式。简单来说,稀疏表示希望找到一种方式,使用少量的非零系数来近似或表示输入数据,尤其在高维数据处理(如图像、语音、文本等)中,这种表示方式能够显著减少数据的复杂性和冗余。
稀疏表示的优势
1. 降低计算复杂度:由于大多数系数为零,稀疏表示可以显著减少计算量,特别是在大规模数据处理时。
2. 提高模型的解释性:稀疏表示有助于突出输入数据中最重要的特征,使模型的行为更具可解释性。
3. 防止过拟合:通过减少冗余的参数和特征,稀疏表示有助于提高模型的泛化能力,避免过拟合。
9. Bagging和其他集成方法
Bagging是通过结合几个模型降低泛化误差的技术,主要想法是分别训练几个不同的模型,然后让所有模型表决测试样例的输出。
Bagging的过程:1. 从训练集 D D D中有放回抽样,生成 k k k个子集 D 1 D_1 D1, D 2 D_2 D2, ⋯ \cdots ⋯, D k D_k Dk。2. 在每个训练子集 D i D_i Di上训练一个模型 h i h_i hi。3. 对于新样本,Bagging 将
k k k个模型的结果通过投票或平均进行融合,得到最终预测结果。
还有其他集成方法如Boosting也可用于神经网络。
下面给出使用Bagging进行预测的例子。
python
import torch
import numpy as np
from torch import nn, optim
from torch.utils.data import DataLoader, SubsetRandomSampler
from torchvision.datasets import MNIST
from torchvision.transforms.v2 import Compose, ToImage, ToDtype, Normalize
from ignite.engine import Events, create_supervised_trainer, create_supervised_evaluator
from ignite.metrics import Accuracy, Loss
transform = Compose([
ToImage(),
ToDtype(torch.float32, scale=True),
Normalize((0,5,), (0.5,))
])
train_dataset = MNIST(root="data", train=True, download=True, transform=transform)
test_dataset = MNIST(root="data", train=False, download=True, transform=transform)
class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(2, 32, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.pool = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(64 * 7 * 7, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = self.pool(torch.relu(self.conv1(x)))
x = self.pool(torch.relu(self.conv2(x)))
x = x.view(-1, 64 * 7 * 7) # Flatten layer
x = torch.relu(self.fc1(x))
x = self.fc2(x)
return x
device = "cuda"
class BaggingModel:
def __init__(self, model, num, batch_size, learning_rate, epochs):
super().__init__()
self.model = model
self.num = num
self.batch_size = batch_size
self.lr = learning_rate
self.epochs = epochs
self.models = nn.ModuleList()
def train(self, train_dataset, valid_dataset):
for i in range(self.num):
model = self.model().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), self.lr)
indices = torch.randperm(len(train_dataset))
subset_sampler = SubsetRandomSampler(indices[: len(train_dataset) // 2])
train_loader = DataLoader(
train_dataset, batch_size=self.batch_size, sampler=subset_sampler
)
valid_loader = DataLoader(
valid_dataset, batch_size=self.batch_size, shuffle=False
)
trainer = create_supervised_trainer(model, optimizer, criterion, device)
val_metrics = {"accuracy": Accuracy(), "loss": Loss(criterion)}
train_evaluator = create_supervised_evaluator(
model, metrics=val_metrics, device=device
)
val_evaluator = create_supervised_evaluator(
model, metrics=val_metrics, device=device
)
log_interval = 100
@trainer.on(Events.ITERATION_COMPLETED(every=log_interval))
def log_training_loss(engine):
print(
f"Model[{i+1}], Epoch[{engine.state.epoch}], Iter[{engine.state.iteration}] Loss: {engine.state.output:.2f}"
)
@trainer.on(Events.EPOCH_COMPLETED)
def log_training_results(trainer):
train_evaluator.run(train_loader)
metrics = train_evaluator.state.metrics
print(
f"Training Results of Model[{i+1}] - Epoch[{trainer.state.epoch}] Avg accuracy: {metrics['accuracy']:.2f} Avg loss: {metrics['loss']:.2f}"
)
@trainer.on(Events.EPOCH_COMPLETED)
def log_validation_results(trainer):
val_evaluator.run(valid_loader)
metrics = val_evaluator.state.metrics
print(
f"Validation Results of Model[{i+1}] - Epoch[{trainer.state.epoch}] Avg accuracy: {metrics['accuracy']:.2f} Avg loss: {metrics['loss']:.2f}"
)
trainer.run(train_loader, self.epochs)
self.models.append(model)
def predict(self, test_dataset):
all_preds = []
all_labels = []
test_loader = DataLoader(
test_dataset, batch_size=self.batch_size, shuffle=False
)
for X, y in test_loader:
X, y = X.to(device), y.to(device)
all_labels.append(y.cpu().numpy())
pred = torch.zeros(X.shape[0], 10).to(device)
for model in self.models:
model.eval()
with torch.no_grad():
pred += nn.Softmax(dim=1)(model(X))
pred /= self.num
all_preds.append(torch.argmax(pred, dim=1).cpu().numpy())
return np.concatenate(all_preds), np.concatenate(all_labels)
def evaluate(self, test_dataset):
y_pred, y_true = self.predict(test_dataset)
accuracy = np.sum(y_pred == y_true) / len(y_pred)
return accuracy
bagging_model = BaggingModel(SimpleCNN, 5, 64, 1e-2, 5)
bagging_model.train(train_dataset, test_dataset)
accuracy = bagging_model.evaluate(test_dataset)
print(f"Bagging模型的准确率: {accuracy:.4f}")
10. 对抗训练
对抗训练是一种通过引入对抗样本来增强模型泛化能力的方法。对抗样本是通过对原始数据进行微小扰动(通常通过梯度下降等方法)而生成的,这些扰动足够小,以至于对人类来说仍然难以察觉,但却足以让机器学习模型产生错误预测。
对抗训练的核心思想是在训练过程中,除了使用正常的数据样本,还要使用这些对抗样本来"挑战"模型。通过在训练集中加入对抗样本,模型不仅学习正常数据的特征,还学习如何应对由对抗样本引起的扰动,从而提升其对潜在攻击或不确定输入的鲁棒性。
训练过程:
1. 生成对抗样本:使用某种方法(如快速梯度符号法 FGSM、迭代法等)生成对抗样本。
2. 结合对抗样本与正常样本进行训练:将这些对抗样本与原始样本一起输入到模型中,并计算总损失函数,优化模型的参数。
参考
[美]伊恩·古德费洛(lan Goodfellow)[加]约书亚·本吉奥(Yoshua Bengio)[加]亚伦·库维尔(Aaron Courville) 深度学习(中文翻译版)
PyTorch-Ignite Getting Started
总结
本周,我学习了一部分神经网络中的正则化方法。
下周,我将学习Dropout,BatchNormalization,LayerNormalization。