使用 ViT 训练 Cifar10 数据集

主要用来记录学习 ViT 中的问题帖子


nn.LayerNorm 是 PyTorch 中的一个标准化层,它在神经网络中用于对输入数据进行层归一化。层归一化的主要作用是提高模型的训练稳定性,加快收敛速度,并有助于防止梯度消失和梯度爆炸的问题。下面是详细解释:

层归一化的原理

层归一化 (Layer Normalization) 对每个输入样本的特征维度进行归一化。与批归一化 (Batch Normalization) 不同,层归一化是对每个样本单独进行归一化操作,而批归一化是对一个小批次样本的统计量进行归一化。

公式

假设输入为 (\mathbf{x}),其中 (\mathbf{x}) 是一个形状为 ((N, C)) 的张量,(N) 是批大小,(C) 是特征数量。对于每个样本 (i),层归一化的计算公式为:

$\hat{x}_i = \frac{x_i - \mu_i}{\sqrt{\sigma_i^2 + \epsilon}}$

其中:

归一化后,通常会有可学习的参数 (\gamma) 和 (\beta),用于恢复归一化后的输出:

[ y_i = \gamma \hat{x}_i + \beta ]

nn.LayerNorm 在代码中的使用

在 PyTorch 中,可以使用 nn.LayerNorm 来实现层归一化。以下是一些示例:

示例代码

import torch
from torch import nn

# 创建一个 LayerNorm 层
layer_norm = nn.LayerNorm(normalized_shape=10)

# 创建一个输入张量
x = torch.randn(2, 10)

# 应用 LayerNorm
output = layer_norm(x)
print(output)

在这个示例中:

作用

  1. 稳定训练过程:通过减小内部协变量的变化,层归一化可以使得训练过程更加稳定。
  2. 加快收敛速度:在某些情况下,层归一化可以加快模型的收敛速度。
  3. 增强模型泛化能力:层归一化有助于提高模型在测试集上的表现。

何时使用

层归一化通常在以下情况下使用:

总体来说,nn.LayerNorm 是一种强大的工具,可以显著改善模型的训练过程和性能。


einops.repeat 函数简介

einops.repeat 函数用于扩展张量的维度。它的语法如下:

repeat(tensor, pattern, **axes_lengths)

代码解释

代码 1

cls_tokens = repeat(self.cls_token, '() n d -> b n d', b = b)

这个模式扩展了 self.cls_token 的第一个维度,使其形状从 (1, d) 变为 (b, n, d)。其中,n 是保持不变的第二维度,b 是新增加的批次维度。

代码 2

cls_tokens = repeat(self.cls_token, '1 1 d -> b 1 d', b=b)

这个模式扩展了 self.cls_token 的第一个维度,使其形状从 (1, 1, d) 变为 (b, 1, d)。其中,1 是保持不变的第二维度(单个元素的维度),b 是新增加的批次维度。

区别

  1. 输入形状

    • 第一段代码假设 self.cls_token 的形状为 (1, d)
    • 第二段代码假设 self.cls_token 的形状为 (1, 1, d)
  2. 扩展方式

    • 第一段代码的模式 '() n d -> b n d' 用于从 (1, d) 扩展到 (b, n, d),其中 n 保持不变。
    • 第二段代码的模式 '1 1 d -> b 1 d' 用于从 (1, 1, d) 扩展到 (b, 1, d)
  3. 最终形状

    • 第一段代码的最终形状为 (b, n, d)
    • 第二段代码的最终形状为 (b, 1, d)

示例

假设 self.cls_token 的初始形状为 (1, d)

import torch
from einops import repeat

# 初始化 cls_token
self.cls_token = torch.randn(1, 768)

# 扩展后的 cls_tokens
b = 32
cls_tokens1 = repeat(self.cls_token, '() n d -> b n d', b = b)
cls_tokens2 = repeat(self.cls_token.unsqueeze(1), '1 1 d -> b 1 d', b = b)

print(cls_tokens1.shape)  # 形状 (32, 1, 768)
print(cls_tokens2.shape)  # 形状 (32, 1, 768)

在这个示例中,尽管两种方式的最终输出形状相同,但第一种方式更适用于 self.cls_token 的形状为 (1, d) 的情况,而第二种方式假设 self.cls_token 的形状已经是 (1, 1, d) 或通过 unsqueeze 方法调整过来。


Adam 和 AdamW 优化器的区别

  1. 优化算法

    • Adam(Adaptive Moment Estimation):Adam 是一种自适应学习率优化算法,结合了 RMSProp 和动量方法的优点。它通过计算一阶和二阶动量来调整每个参数的学习率。
    • AdamW(Adam with Weight Decay):AdamW 是 Adam 优化器的变种,专门为解决 L2 正则化(权重衰减)在 Adam 优化器中的不正确实现问题。在标准的 Adam 中,权重衰减实际上是通过梯度更新过程中的学习率缩放实现的,而在 AdamW 中,权重衰减是直接应用于权重的。
  2. 权重衰减(L2 正则化)

    • Adam 优化器中,权重衰减通常通过 L2 正则化项实现。这个正则化项会影响梯度的计算过程。
    • AdamW 优化器中,权重衰减直接作用于权重更新步骤,这样可以更好地控制模型的泛化能力。

参数设置的区别

optimizer = optim.AdamW(net.parameters(), lr=0.0001, weight_decay=1e-4)

optimizer = torch.optim.Adam(model.parameters(), lr=3e-4)

具体区别和影响

  1. 优化器选择

    • AdamW 更适合处理大型神经网络的训练,因为它对权重衰减的实现更合理,有助于避免过拟合。
    • Adam 是一个通用的优化器,适用于大多数深度学习任务,但其权重衰减实现不如 AdamW 有效。
  2. 学习率

    • AdamW 优化器的学习率设置为 0.0001,比 Adam 的 0.0003 小,这意味着每一步更新的幅度更小,可能会导致训练速度较慢但更加稳定。
    • Adam 的学习率较大,训练速度更快,但如果设置不当,可能会导致不稳定的训练过程。
  3. 权重衰减

    • AdamW 中明确设置了权重衰减参数 weight_decay=1e-4,这有助于正则化模型并防止过拟合。
    • Adam 中未指定权重衰减参数,因此不进行权重衰减,可能会在某些情况下导致过拟合。

示例代码

import torch
from torch import nn, optim

# 定义一个简单的模型
model = nn.Sequential(nn.Linear(10, 10))

# 使用 AdamW 优化器
optimizer_adamw = optim.AdamW(model.parameters(), lr=0.0001, weight_decay=1e-4)

# 使用 Adam 优化器
optimizer_adam = torch.optim.Adam(model.parameters(), lr=3e-4)

print(optimizer_adamw)
print(optimizer_adam)

通过以上分析,可以看出 AdamW 优化器和 Adam 优化器在实现和使用上的关键区别。具体选择哪个优化器和参数设置,取决于你的任务需求和模型性能调优的经验。


学习率设置为太小的话,更新后的准确率上升不明显容易触发早停机制导致收敛提前结束:

学习率设置过小确实会导致模型训练过程中参数更新幅度过小,从而导致训练速度变慢,甚至可能触发早停机制,使得训练提前结束,导致模型未能充分收敛。

为了在使用优化器时更好地平衡训练稳定性和收敛速度,通常需要进行一些学习率和其他超参数的调优。以下是一些可能的解决方案和调整策略:

调整学习率和早停策略

  1. 逐步调整学习率

    • 可以从较高的学习率开始训练,然后逐步减小学习率。例如,可以使用学习率调度器(如 StepLRReduceLROnPlateau 等)在训练过程中动态调整学习率。
  2. 早停策略的耐心度(patience)

    • 适当增加早停策略中的 patience 参数,使得模型有更多的机会继续训练,从而避免过早停止。
  3. 使用学习率热身(Warm-up)策略

    • 在训练开始时逐步增加学习率,避免学习率过大导致训练不稳定,然后在达到最大学习率后逐步减小。

示例代码

以下是一个使用学习率调度器和早停策略的示例代码:

import torch
from torch import nn, optim
from torch.optim.lr_scheduler import StepLR, ReduceLROnPlateau

# 定义一个简单的模型
model = nn.Sequential(nn.Linear(10, 10))

# 选择优化器
optimizer = optim.AdamW(model.parameters(), lr=0.0001, weight_decay=1e-4)

# 定义学习率调度器
scheduler = StepLR(optimizer, step_size=10, gamma=0.1)

# 或者使用 ReduceLROnPlateau
# scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=5, verbose=True)

# 定义损失函数
criterion = nn.CrossEntropyLoss()

# 模拟训练和验证过程
for epoch in range(100):
    model.train()
    # 训练过程(示例)
    for inputs, targets in trainloader:
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()
    
    model.eval()
    val_loss = 0.0
    correct = 0
    total = 0
    with torch.no_grad():
        for inputs, targets in valloader:
            outputs = model(inputs)
            loss = criterion(outputs, targets)
            val_loss += loss.item()
            _, predicted = outputs.max(1)
            total += targets.size(0)
            correct += predicted.eq(targets).sum().item()
    
    val_loss /= len(valloader)
    val_acc = 100. * correct / total

    # 调整学习率
    scheduler.step()

    # 或者使用 ReduceLROnPlateau
    # scheduler.step(val_loss)

    print(f'Epoch {epoch}: Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.2f}%')

    # 早停机制(示例)
    if early_stopping_criteria:
        early_stopping_counter += 1
        if early_stopping_counter > patience:
            print("Early stopping")
            break
    else:
        early_stopping_counter = 0

调整学习率和早停策略的总结

通过这些调整,可以有效提高模型的训练效率和最终性能。具体参数的选择仍然需要根据具体的任务和数据集进行实验和调优。


AdamW 中的权重衰减:

权重衰减(Weight Decay)

权重衰减是指在优化过程中对模型的权重施加L2正则化,以防止过拟合。这是通过在损失函数中添加一个正则化项来实现的,该正则化项与权重的平方成正比。权重衰减的公式如下:

[ L_{total} = L_{data} + \lambda \sum_i w_i^2 ]

其中:

权重衰减的作用是鼓励模型的权重趋向于零,从而减少模型复杂度,防止过拟合。

应用权重衰减

在 PyTorch 中,权重衰减可以在定义优化器时通过 weight_decay 参数来设置。默认情况下,weight_decay 为 0,即不进行权重衰减。

示例代码

import torch
import torch.optim as optim
from torch import nn

# 定义一个简单的模型
model = nn.Sequential(nn.Linear(10, 10))

# 使用 Adam 优化器,不进行权重衰减
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 使用 AdamW 优化器,进行权重衰减
optimizer_adamw = optim.AdamW(model.parameters(), lr=0.001, weight_decay=1e-4)

在上面的代码中,optimizer 不进行权重衰减,而 optimizer_adamw 进行权重衰减,系数为 1e-4

学习率调整

Adam 和 AdamW 都可以结合学习率调度器来自动调整学习率。常用的学习率调度器有:

  1. StepLR:每隔一定的 epochs 将学习率乘以一个因子。
  2. ReduceLROnPlateau:当验证集上的指标不再改善时,降低学习率。
  3. ExponentialLR:每个 epoch 将学习率乘以一个因子。
  4. CosineAnnealingLR:使用余弦退火调整学习率。

StepLR 示例

from torch.optim.lr_scheduler import StepLR

# 使用 AdamW 优化器
optimizer = optim.AdamW(model.parameters(), lr=0.001, weight_decay=1e-4)

# 定义 StepLR 调度器,每 10 个 epoch 将学习率乘以 0.1
scheduler = StepLR(optimizer, step_size=10, gamma=0.1)

for epoch in range(100):
    train()  # 训练模型的函数
    validate()  # 验证模型的函数
    scheduler.step()  # 更新学习率

ReduceLROnPlateau 示例

from torch.optim.lr_scheduler import ReduceLROnPlateau

# 使用 AdamW 优化器
optimizer = optim.AdamW(model.parameters(), lr=0.001, weight_decay=1e-4)

# 定义 ReduceLROnPlateau 调度器,当验证集上的损失不再改善时,降低学习率
scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=5, verbose=True)

for epoch in range(100):
    train()  # 训练模型的函数
    val_loss = validate()  # 验证模型的函数,返回验证集上的损失
    scheduler.step(val_loss)  # 根据验证集上的损失更新学习率

总结

通过合理设置权重衰减和学习率调度器,可以有效提升模型的性能和训练稳定性。


动态(理想情况下)学习率调整方法:

好的,以下是 ReduceLROnPlateauCosineAnnealingLR 学习率调整方法的具体实例。

ReduceLROnPlateau 示例

ReduceLROnPlateau 是一种学习率调度器,当验证集上的指标(如损失)不再改善时,降低学习率。这种方法适用于当你希望在验证集上的性能达到一个平稳期时调整学习率。

示例代码

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
import torchvision.datasets as datasets

# 定义一个简单的模型
class SimpleModel(nn.Module):
    def __init__(self):
        super(SimpleModel, self).__init__()
        self.fc = nn.Linear(784, 10)
    
    def forward(self, x):
        return self.fc(x.view(x.size(0), -1))

# 加载数据集
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)

val_dataset = datasets.MNIST(root='./data', train=False, transform=transform, download=True)
val_loader = DataLoader(val_dataset, batch_size=1000, shuffle=False)

# 初始化模型和优化器
model = SimpleModel()
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)

# 定义 ReduceLROnPlateau 调度器
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=5, verbose=True)

# 定义损失函数
criterion = nn.CrossEntropyLoss()

# 训练和验证函数
def train(model, loader, optimizer, criterion):
    model.train()
    total_loss = 0
    correct = 0
    for inputs, targets in loader:
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
        _, predicted = outputs.max(1)
        correct += predicted.eq(targets).sum().item()
    return total_loss / len(loader), correct / len(loader.dataset)

def validate(model, loader, criterion):
    model.eval()
    total_loss = 0
    correct = 0
    with torch.no_grad():
        for inputs, targets in loader:
            outputs = model(inputs)
            loss = criterion(outputs, targets)
            total_loss += loss.item()
            _, predicted = outputs.max(1)
            correct += predicted.eq(targets).sum().item()
    return total_loss / len(loader), correct / len(loader.dataset)

# 训练和验证过程
best_val_acc = 0
patience = 10
early_stopping_counter = 0

for epoch in range(100):
    train_loss, train_acc = train(model, train_loader, optimizer, criterion)
    val_loss, val_acc = validate(model, val_loader, criterion)
    
    # 使用 ReduceLROnPlateau 更新学习率
    scheduler.step(val_loss)
    
    print(f'Epoch {epoch+1}: Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}, Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.2f}')
    
    # 早停策略
    if val_acc > best_val_acc:
        best_val_acc = val_acc
        early_stopping_counter = 0
        # 保存最好的模型
        torch.save(model.state_dict(), 'best_model.pth')
    else:
        early_stopping_counter += 1
        if early_stopping_counter >= patience:
            print("Early stopping")
            break

CosineAnnealingLR 示例

CosineAnnealingLR 使用余弦退火调整学习率,使学习率在训练过程中逐渐减小,然后周期性地恢复。这种方法适用于当你希望学习率在训练过程中有周期性变化,以跳出局部最优解。

示例代码

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
import torchvision.datasets as datasets

# 定义一个简单的模型
class SimpleModel(nn.Module):
    def __init__(self):
        super(SimpleModel, self).__init__()
        self.fc = nn.Linear(784, 10)
    
    def forward(self, x):
        return self.fc(x.view(x.size(0), -1))

# 加载数据集
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)

val_dataset = datasets.MNIST(root='./data', train=False, transform=transform, download=True)
val_loader = DataLoader(val_dataset, batch_size=1000, shuffle=False)

# 初始化模型和优化器
model = SimpleModel()
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)

# 定义 CosineAnnealingLR 调度器
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50, eta_min=0)

# 定义损失函数
criterion = nn.CrossEntropyLoss()

# 训练和验证函数
def train(model, loader, optimizer, criterion):
    model.train()
    total_loss = 0
    correct = 0
    for inputs, targets in loader:
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
        _, predicted = outputs.max(1)
        correct += predicted.eq(targets).sum().item()
    return total_loss / len(loader), correct / len(loader.dataset)

def validate(model, loader, criterion):
    model.eval()
    total_loss = 0
    correct = 0
    with torch.no_grad():
        for inputs, targets in loader:
            outputs = model(inputs)
            loss = criterion(outputs, targets)
            total_loss += loss.item()
            _, predicted = outputs.max(1)
            correct += predicted.eq(targets).sum().item()
    return total_loss / len(loader), correct / len(loader.dataset)

# 训练和验证过程
best_val_acc = 0
patience = 10
early_stopping_counter = 0

for epoch in range(100):
    train_loss, train_acc = train(model, train_loader, optimizer, criterion)
    val_loss, val_acc = validate(model, val_loader, criterion)
    
    # 使用 CosineAnnealingLR 更新学习率
    scheduler.step()
    
    print(f'Epoch {epoch+1}: Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}, Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.2f}')
    
    # 早停策略
    if val_acc > best_val_acc:
        best_val_acc = val_acc
        early_stopping_counter = 0
        # 保存最好的模型
        torch.save(model.state_dict(), 'best_model.pth')
    else:
        early_stopping_counter += 1
        if early_stopping_counter >= patience:
            print("Early stopping")
            break

总结

通过结合这些学习率调整方法,可以更有效地优化模型训练过程,达到更好的性能。


具体余弦退火学习率降低算法:
这行代码使用的是 CosineAnnealingLR 学习率调度器。CosineAnnealingLR 是一种基于余弦退火的学习率调整方法。其主要思想是学习率在每个周期内以余弦曲线的形式从初始值逐渐减小到最低值,然后周期性地恢复。

代码解释

scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=200)

在这个例子中,T_max=200 表示每经过 200 个 epoch,学习率从初始值逐渐减小到最低值。

余弦退火的学习率调整方法

余弦退火的学习率调整方法可以帮助模型在训练过程中跳出局部最优解,从而找到更好的全局最优解。其调整过程是一个周期性的余弦曲线,从初始学习率逐渐减小到最低学习率,再回到初始学习率,如此反复。

公式

学习率的变化公式为:

[ \eta_t = \eta_{min} + \frac{1}{2} (\eta_{max} - \eta_{min}) \left(1 + \cos\left(\frac{T_{cur}}{T_{max}} \pi\right)\right) ]

其中:

示例代码

下面是一个使用 CosineAnnealingLR 的完整训练示例:

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
import torchvision.datasets as datasets

# 定义一个简单的模型
class SimpleModel(nn.Module):
    def __init__(self):
        super(SimpleModel, self).__init__()
        self.fc = nn.Linear(784, 10)
    
    def forward(self, x):
        return self.fc(x.view(x.size(0), -1))

# 加载数据集
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)

val_dataset = datasets.MNIST(root='./data', train=False, transform=transform, download=True)
val_loader = DataLoader(val_dataset, batch_size=1000, shuffle=False)

# 初始化模型和优化器
model = SimpleModel()
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)

# 定义 CosineAnnealingLR 调度器
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=200)

# 定义损失函数
criterion = nn.CrossEntropyLoss()

# 训练和验证函数
def train(model, loader, optimizer, criterion):
    model.train()
    total_loss = 0
    correct = 0
    for inputs, targets in loader:
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
        _, predicted = outputs.max(1)
        correct += predicted.eq(targets).sum().item()
    return total_loss / len(loader), correct / len(loader.dataset)

def validate(model, loader, criterion):
    model.eval()
    total_loss = 0
    correct = 0
    with torch.no_grad():
        for inputs, targets in loader:
            outputs = model(inputs)
            loss = criterion(outputs, targets)
            total_loss += loss.item()
            _, predicted = outputs.max(1)
            correct += predicted.eq(targets).sum().item()
    return total_loss / len(loader), correct / len(loader.dataset)

# 训练和验证过程
best_val_acc = 0
patience = 10
early_stopping_counter = 0

for epoch in range(100):
    train_loss, train_acc = train(model, train_loader, optimizer, criterion)
    val_loss, val_acc = validate(model, val_loader, criterion)
    
    # 使用 CosineAnnealingLR 更新学习率
    scheduler.step()
    
    print(f'Epoch {epoch+1}: Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}, Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.2f}')
    
    # 早停策略
    if val_acc > best_val_acc:
        best_val_acc = val_acc
        early_stopping_counter = 0
        # 保存最好的模型
        torch.save(model.state_dict(), 'best_model.pth')
    else:
        early_stopping_counter += 1
        if early_stopping_counter >= patience:
            print("Early stopping")
            break

总结