跳至主要内容

Adagrad 优化器详解:工作原理、实现与对比

学习 Adagrad 优化技术,包括其关键优势、局限性、在 PyTorch 中的实现方法,以及用于优化机器学习模型的应用场景。
· 12分钟 阅读

使用 AI 探索

想象您正下山行走,但地形不断变化——有的地方陡峭,有的地方平坦。如果您每一步的步幅都相同,可能会很吃力,或者花很长时间才能到达山脚。在机器学习中,梯度下降中也会遇到类似的挑战:如果对所有参数使用相同的学习率,学习过程可能会变慢。

这就是 Adagrad 派上用场的地方。它会根据参数在训练过程中变化的程度来调整每个参数的步长,从而帮助模型更快、更有效地学习,尤其是在不同特征具有不同尺度时。本文将介绍 Adagrad 的工作原理,并剖析其背后的数学。同时,我们还将展示如何使用PyTorch 实现它,并与其他优化器进行比较,帮助您为项目选择最合适的方案。

什么是 Adagrad?

Adagrad(Adaptive Gradient,自适应梯度)是一种用于机器学习的优化算法,特别适用于训练深度神经网络。它旨在根据每个参数的历史梯度为其自适应调整学习率。这种方法能够提高机器学习模型的训练效率,特别适合处理稀疏数据或不同参数具有不同收敛速度的场景。

Adagrad 会对不常见特征给予更高的学习率,对频繁出现的特征给予较低的学习率,因此非常适合处理稀疏数据。此外,它还能减少手动调整学习率的需求!

Adagrad 算法详解

我们来逐步拆解 Adagrad 算法:

步骤 1:初始化参数

首先,准备:

  • 参数的初始值,0,即您希望优化的那些参数。
  • 一个很小的常数 ϵ 用于避免除以 0(通常是一个很小的数,如 10-8)。
  • 初始学习率 η,用于决定一开始参数更新的步长大小。

步骤 2:计算梯度

对于每个参数 0t​ 在时间步 t,计算损失函数关于该参数的梯度。这个梯度告诉我们应当如何以及朝哪个方向更新参数以减小损失。将该梯度记为 gt,其中 t 是当前时间步。

步骤 3:累积平方梯度

Adagrad 并不直接更新参数,而是为每个参数跟踪其平方梯度的累积和。对每个参数 i,在每个时间步计算并累积平方梯度。这个累积量记为 Gt。

Gt =Gt-1 + gt2

其中:

  • Gt-1 是到上一个时间步为止的平方梯度之和。
  • gt2 是当前时间步该参数的梯度平方。

这个累积量帮助 Adagrad 根据每个参数被更新的程度,分别调整其学习率。

步骤 4:更新参数

现在我们有了累积的平方梯度 Gt​,即可更新每个参数 0t​。Adagrad 的更新规则如下:

具体含义如下:

  • η 是初始学习率
  • Gt 是截至时间步 t 的平方梯度累积。
  • ϵ 是用于避免除以 0 的小常数。
  • gt​ 为当前时间步的梯度。

这个项:

​

会有效降低累计梯度较大的参数的学习率;相反,累计梯度较小的参数会拥有更大的学习率。

Adagrad 的公式会自动自适应学习率,具体如下:

  • 更新频繁(累计梯度大):对于经常被更新的参数,Gt 会变大,从而使学习率变小。这能防止参数变化过于剧烈,稳定学习过程。
  • 更新不频繁(累计梯度小):对于较少被更新的参数,Gt 保持较小,学习率更大,从而在必要时进行更显著的更新。

在 PyTorch 中实现 Adagrad

PyTorch 提供了 Adagrad 的内置实现,可通过 torch.optim 模块访问。首先使用 pip 安装 PyTorch:

pip install torch torchvision

通过我们的免费课程学习如何构建您的第一个神经网络:Introduction to Deep Learning with PyTorch。

以下是在 PyTorch 中实现 Adagrad 优化器的分步指南:

步骤 1:导入必要的库

import torchimport torch.nn as nnimport torch.optim as optim

步骤 2:定义一个简单的神经网络

class SimpleNN(nn.Module):    def __init__(self):        super(SimpleNN, self).__init__()        self.fc1 = nn.Linear(10, 5)        self.fc2 = nn.Linear(5, 2)        def forward(self, x):        x = torch.relu(self.fc1(x))        x = self.fc2(x)        return x

步骤 3:初始化模型与优化器

model = SimpleNN()criterion = nn.CrossEntropyLoss()optimizer = optim.Adagrad(model.parameters(), lr=0.01)

步骤 4:训练循环

for epoch in range(10):  # loop over the dataset multiple times    inputs = torch.randn(1, 10)  # random input tensor    labels = torch.tensor([1])   # target labels    # Zero the parameter gradients    optimizer.zero_grad()    # Forward pass    outputs = model(inputs)    loss = criterion(outputs, labels)    # Backward pass and optimize    loss.backward()    optimizer.step()    print(f'Epoch {epoch+1}, Loss: {loss.item()}')
在这段代码中,我们使用 AdaGrad 优化器训练了一个简单的线性模型。学习率被设为 0.01,但 AdaGrad 会基于累积的平方梯度为每个参数分别调整学习率。

运行代码,您将看到类似这样的输出:

输出显示了每个 epoch 的损失,体现了模型的学习效果。初始时损失较高,但随着训练推进,损失通常会下降,表示模型在改进。使用随机输入时出现一些波动是正常的,但总体上,模型会逐步学到更好的预测。

如果您想深入了解深度学习,以及这一机器学习分支如何改变世界,请查看我们的课程Deep Learning in Python。

Adagrad 与其他优化器对比

我们将 Adagrad 与其他优化器进行比较,例如Adam、SGD 和 RMSProp。

Adagard 与 Adam

学习率自适应

Adagrad 和Adam 都会为每个参数自适应调整学习率,但方式不同。Adagrad 根据平方梯度之和缩放学习率,这可能导致学习率随时间衰减。相较之下,Adam 使用梯度的一阶与二阶矩,从而在训练过程中保持更稳定的学习率。

处理稀疏数据:

Adagrad 尤其适合处理稀疏数据,因为它能为不常见特征分配更大的学习率。这使其非常适用于自然语言处理等常见稀疏梯度的应用。虽然 Adam 也能处理稀疏数据,但由于其在更广泛应用中的稳健性与效率,往往更受青睐。

收敛与性能

在实践中,Adam 往往因更快的收敛和更优的性能而受到青睐,尤其在深度学习模型和大数据集上。它利用动量与偏置校正,有助于避免陷入局部极小值与鞍点,而这可能是 Adagrad 的局限所在。

了解用于图像与序列数据建模的基础深度学习架构,如 CNN、RNN、LSTM 和 GRU。请查看我们的课程Intermediate Deep Learning with PyTorch。

Adagrad 与其他梯度下降算法

我们将 Adagrad 与 SGD 和 RMSProp 进行比较

AdaGrad 与随机梯度下降(SGD)

随机梯度下降是一种基础优化算法,通过损失函数相对于参数的梯度来更新模型参数。它以简单易实现著称。然而,SGD 的学习率是常数,在处理稀疏数据或特征尺度不一致时可能存在局限。

AdaGrad 与 RMSProp

RMSProp 是对 AdaGrad 的改进,引入了衰减因子来缓解学习率不断减小的问题。借助这一衰减因子,RMSProp 能在整个训练过程中维持更稳定的学习率。

我们来总结一下 Adagrad、SGD 和 RMSProp 的优缺点:

优化器

优点

缺点

Adagrad

1. 自动为每个参数自适应学习率。

2. 对稀疏数据有效。

3. 无需手动调节学习率。

1. 学习率随时间衰减,可能导致过早收敛。

2. 训练后期可能过慢。

SGD

1. 简单易实现。

2. 由于随机性,有助于跳出局部极小值。

1. 更新方差高,可能导致不稳定。

2. 需要仔细调节学习率与动量。

RMSProp

1. 基于平方梯度的移动平均自适应学习率。

2. 适合非平稳环境。

1. 需要仔细调节超参数。

2. 本身不包含动量机制。

常见用例与局限

我们来探讨 AdaGrad 在不同场景下的用例与局限。

Adagrad 的用例

Adagrad 为每个参数自适应学习率的能力,使其非常适合以下情形:

  1. 自然语言处理(NLP): Adagrad 在自然语言处理(NLP)中的一大典型应用是优化词向量,通过为每个参数自适应调整学习率来进行优化。
  2. 推荐系统: 在推荐系统中,Adagrad 用于优化推荐模型的权重,以预测用户喜欢某个产品的可能性。
  3. 图像识别:在图像识别中,Adagrad 用于优化深度神经网络的权重。通过为每个参数自适应调整学习率,Adagrad 能确保权重针对任务进行恰当更新,从而提升图像识别模型的准确率。

Adagrad 的局限

虽然 Adagrad 在特定情境下非常强大,但也存在一些局限:

  1. 学习率衰减: Adagrad 的一个主要局限是学习率会随着时间过于激进地减小。由于该算法从训练一开始就累积平方梯度,有效学习率可能变得非常小,导致收敛缓慢,甚至使模型几乎不再学习。
  2. 内存需求: Adagrad 需要为每个参数存储历史梯度,这会消耗更多内存,从而在大数据集上效率较低。在大规模机器学习模型中,这可能是一个显著的劣势,因为内存效率至关重要。

缓解局限性的策略

您可以使用以下策略来应对 Adagrad 的局限:

  1. 使用 AdaDelta 与 RMSProp: 为了解决学习率衰减问题,AdaGrad 的扩展算法如 AdaDelta 与 RMSProp 应运而生。
  2. 结合动量: 另一种缓解 AdaGrad 局限的策略是与基于动量的方法结合使用。例如,Adam 优化器将 AdaGrad 的自适应学习率与动量思想结合,有助于加速收敛并在实践中提升性能。
  3. 超参数调节: 通过仔细调节初始学习率以及用于避免除以 0 的 epsilon 值,也能在一定程度上缓解 Adagrad 的一些局限。

动手练习:使用 Adagrad 优化模型

我们将构建一个简单的神经网络,使用 Adagrad 优化器进行训练,并与其他常用优化器的性能进行对比。

如果您想进一步学习 PyTorch,请查看我们的指南:PyTorch Tutorial: Building a Simple Neural Network From Scratch.

我们先在 PyTorch 中创建一个基础的神经网络模型。我们将设计一个适用于稀疏数据的二分类网络,Adagrad 在这类任务中表现突出。

import torchimport torch.nn as nnclass SparseNN(nn.Module):    def __init__(self, input_size):        super(SparseNN, self).__init__()        self.fc1 = nn.Linear(input_size, 50)        self.fc2 = nn.Linear(50, 1)        self.activation = nn.ReLU()        self.output_activation = nn.Sigmoid()    def forward(self, x):        x = self.activation(self.fc1(x))        x = self.output_activation(self.fc2(x))        return x

该网络包含一个输入层、一个包含 50 个神经元的隐藏层和一个单神经元的输出层。隐藏层使用 ReLU 激活函数,输出层使用 Sigmoid 激活,适用于二分类任务。

接下来,我们用 Adagrad 优化器来设置训练流程。我们会编写一个训练函数:

import torch.optim as optimfrom torch.utils.data import DataLoader, TensorDataset# Function to train the modeldef train_model(model, optimizer, criterion, train_loader, num_epochs=100):    losses = []    for epoch in range(num_epochs):  # loop for each training epoch        for inputs, targets in train_loader:            # Clear gradients before each batch to avoid accumulation            optimizer.zero_grad() # Reset gradients to zero            # Forward pass - get model predictions            outputs = model(inputs)            # Calculate loss based on predictions and targets            loss = criterion(outputs, targets)            # Backward pass - calculate gradients for parameter updates based on the loss            loss.backward()            # Update model parameters based on calculated gradients            optimizer.step()        # Track loss for monitoring training progress        losses.append(loss.item())    return losses# Setup for trainingX, y = generate_sparse_data()  # This function generates our sparse datasetinput_size = X.shape[1]train_size = int(0.8 * len(X))X_train, X_test = X[:train_size], X[train_size:]y_train, y_test = y[:train_size], y[train_size:]train_dataset = TensorDataset(X_train, y_train)train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)model = SparseNN(input_size)criterion = nn.BCELoss()  # Assuming binary classification with BCE lossoptimizer = optim.Adagrad(model.parameters(), lr=0.01)# Train the modellosses = train_model(model, optimizer, criterion, train_loader)

在此设置中,我们:

  1. 创建 SparseNN 模型。
  2. 定义损失函数(交叉熵 Binary Cross Entropy)。
  3. 以 0.01 的学习率初始化 Adagrad 优化器。
  4. 训练 100 个 epoch,并记录每个 epoch 的损失。

现在,为了评估模型表现,我们编写一个评估函数:

def evaluate_model(model, test_loader):    model.eval()    correct = 0    total = 0    with torch.no_grad():        for inputs, targets in test_loader:            outputs = model(inputs)            predicted = (outputs > 0.5).float()            total += targets.size(0)            correct += (predicted == targets).sum().item()    accuracy = correct / total    return accuracy# Evaluate the modeltest_dataset = TensorDataset(X_test, y_test)test_loader = DataLoader(test_dataset, batch_size=32)accuracy = evaluate_model(model, test_loader)print(f"Adagrad Accuracy: {accuracy:.4f}")

该函数用于计算模型在测试集上的准确率。

接下来,我们与其他常见优化器(SGD、Adam、RMSprop)进行对比。我们将创建一个函数来为每个优化器运行实验:

def run_experiment(optimizer_class, lr=0.01):    model = SparseNN(input_size)    criterion = nn.BCELoss()        if optimizer_class == optim.SGD:        optimizer = optimizer_class(model.parameters(), lr=lr, momentum=0.9)    elif optimizer_class == optim.RMSprop:        optimizer = optimizer_class(model.parameters(), lr=lr, alpha=0.99)    else:        optimizer = optimizer_class(model.parameters(), lr=lr)    losses = train_model(model, optimizer, criterion, train_loader)    accuracy = evaluate_model(model, test_loader)    return losses, accuracy# Compare optimizersoptimizers = {    'Adagrad': optim.Adagrad,    'SGD': optim.SGD,    'Adam': optim.Adam,    'RMSprop': optim.RMSprop}results = {}for name, opt_class in optimizers.items():    losses, accuracy = run_experiment(opt_class)    results[name] = {'losses': losses, 'accuracy': accuracy}print("Final accuracies:")for name, data in results.items():    print(f"{name}: {data['accuracy']:.4f}")

运行上述代码后,我们得到如下结果:

Final accuracies:Adagrad: 0.9350SGD: 0.8950Adam: 0.9300RMSprop: 0.8600

这些结果显示,在我们的稀疏数据任务中,Adagrad 表现最佳,准确率达到 95.00%。这说明 Adagrad 非常适合稀疏数据场景。Adam 紧随其后,而 SGD 与 RMSprop 在本例中的表现相对较弱。

为了可视化训练过程,我们可以绘制损失曲线:

import matplotlib.pyplot as pltplt.figure(figsize=(12, 6))for name, data in results.items():    plt.plot(data['losses'], label=f"{name} (Accuracy: {data['accuracy']:.4f})")plt.xlabel('Epochs')plt.ylabel('Loss')plt.title('Training Loss Comparison (Sparse Data)')plt.legend()plt.show()

我们将所有代码片段合并,完整代码如下。

import torchimport torch.nn as nnimport torch.optim as optimfrom torch.utils.data import DataLoader, TensorDatasetimport matplotlib.pyplot as plt# Step 1: Generate sparse synthetic datadef generate_sparse_data(n_samples=1000, n_features=100, sparsity=0.95):    X = torch.randn(n_samples, n_features)    mask = torch.rand(n_samples, n_features) < sparsity    X[mask] = 0    weights = torch.randn(n_features)    y = (torch.matmul(X, weights) > 0).float().view(-1, 1)    return X, y# Step 2: Define the neural networkclass SparseNN(nn.Module):    def __init__(self, input_size):        super(SparseNN, self).__init__()        self.fc1 = nn.Linear(input_size, 50)        self.fc2 = nn.Linear(50, 1)        self.activation = nn.ReLU()        self.output_activation = nn.Sigmoid()    def forward(self, x):        x = self.activation(self.fc1(x))        x = self.output_activation(self.fc2(x))        return x# Step 3: Train the modeldef train_model(model, optimizer, criterion, train_loader, num_epochs=100):    losses = []    for epoch in range(num_epochs):        for inputs, targets in train_loader:            optimizer.zero_grad()            outputs = model(inputs)            loss = criterion(outputs, targets)            loss.backward()            optimizer.step()        losses.append(loss.item())    return losses# Step 4: Evaluate the modeldef evaluate_model(model, test_loader):    model.eval()    correct = 0    total = 0    with torch.no_grad():        for inputs, targets in test_loader:            outputs = model(inputs)            predicted = (outputs > 0.5).float()            total += targets.size(0)            correct += (predicted == targets).sum().item()    accuracy = correct / total    return accuracy# Step 5: Main function to run the experimentdef run_experiment(optimizer_class, lr=0.01):    # Generate sparse data    X, y = generate_sparse_data()    input_size = X.shape[1]    train_size = int(0.8 * len(X))    X_train, X_test = X[:train_size], X[train_size:]    y_train, y_test = y[:train_size], y[train_size:]    # Create data loaders    train_dataset = TensorDataset(X_train, y_train)    test_dataset = TensorDataset(X_test, y_test)    train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)    test_loader = DataLoader(test_dataset, batch_size=32)    # Initialize model and training components    model = SparseNN(input_size)    criterion = nn.BCELoss()        # Use default hyperparameters for each optimizer    if optimizer_class == optim.SGD:        optimizer = optimizer_class(model.parameters(), lr=lr, momentum=0.9)    elif optimizer_class == optim.RMSprop:        optimizer = optimizer_class(model.parameters(), lr=lr, alpha=0.99)    else:        optimizer = optimizer_class(model.parameters(), lr=lr)    # Train the model    losses = train_model(model, optimizer, criterion, train_loader)    # Evaluate the model    accuracy = evaluate_model(model, test_loader)    return losses, accuracy# Step 6: Compare Adagrad with other optimizersoptimizers = {    'Adagrad': optim.Adagrad,    'SGD': optim.SGD,    'Adam': optim.Adam,    'RMSprop': optim.RMSprop}results = {}for name, opt_class in optimizers.items():    losses, accuracy = run_experiment(opt_class)    results[name] = {'losses': losses, 'accuracy': accuracy}# Step 7: Plot resultsplt.figure(figsize=(12, 6))for name, data in results.items():    plt.plot(data['losses'], label=f"{name} (Accuracy: {data['accuracy']:.4f})")plt.xlabel('Epochs')plt.ylabel('Loss')plt.title('Training Loss Comparison (Sparse Data)')plt.legend()plt.show()print("Final accuracies:")for name, data in results.items():    print(f"{name}: {data['accuracy']:.4f}")

结果如下:

Training loss comparison graph

结论

由于具备自适应学习率,Adagrad 在处理稀疏数据时非常有用。我们构建了一个基础神经网络并用 Adagrad 进行训练,随后与其他知名优化算法进行了对比。结果强烈支持 Adagrad,显示其在稀疏数据上更具优势。

我们还精选了一些关于 PyTorch、深度学习和其他优化算法的优质资源。欢迎查阅以获得更深入的学习:

Satyam Tripathi

作者

Satyam Tripathi

主题