赞
踩
当涉及不同的梯度下降算法时,每种算法都有其独特的特点和优化策略。下面对每种算法进行详细解释:
算法原理:在每次迭代中,随机选择一个训练样本来计算损失函数的梯度,并更新模型的参数。由于随机选择样本,梯度估计存在一定的噪声,导致优化路径不稳定,但收敛速度较快。
优点:收敛速度快,计算开销较小。
缺点:优化路径不稳定,可能会震荡,难以找到全局最优解。
算法原理:在每次迭代中,使用所有训练样本来计算损失函数的梯度,并更新模型的参数。由于使用了更多数据,梯度估计更准确,收敛路径较稳定。
优点:收敛路径稳定,梯度估计准确。
缺点:计算开销较大,内存要求高,不适用于大规模数据集。
以下是随机梯度下降的详细解释:
算法原理:
优点:
缺点:
改进方法:
假设我们有一个损失函数 J ( θ ) J(θ) J(θ)(参数 θ θ θ表示模型的权重和偏置),其中 θ θ θ是一个向量。我们希望找到使损失函数最小化的最优参数 θ ∗ θ* θ∗。
损失函数: J ( θ ) J(θ) J(θ)
随机梯度下降公式:
在随机梯度下降中,我们使用一个样本
(
x
,
y
)
(x, y)
(x,y) 来计算损失函数关于该样本的梯度,并更新参数。梯度表示损失函数在参数
θ
θ
θ处的变化率。
损失函数关于参数 θ θ θ的梯度(梯度向量): ∇ J ( θ ) = [ ∂ J ( θ ) / ∂ θ 1 , ∂ J ( θ ) / ∂ θ 2 , . . . , ∂ J ( θ ) / ∂ θ r ] ∇J(θ) = [∂J(θ)/∂θ₁, ∂J(θ)/∂θ₂, ..., ∂J(θ)/∂θᵣ] ∇J(θ)=[∂J(θ)/∂θ1,∂J(θ)/∂θ2,...,∂J(θ)/∂θr]
参数更新规则(学习率为α):
θ
←
θ
−
α
∗
∇
J
(
θ
)
θ ← θ - α * ∇J(θ)
θ←θ−α∗∇J(θ)
#include <iostream> #include <vector> #include <cmath> // 随机梯度下降函数 void stochasticGradientDescent(std::vector<std::vector<double>>& data, std::vector<double>& labels, std::vector<double>& weights, double learning_rate, int epochs) { int num_samples = data.size(); int num_features = data[0].size(); for (int epoch = 0; epoch < epochs; epoch++) { for (int i = 0; i < num_samples; i++) { double y_pred = predict(data[i], weights); double loss = lossFunction(y_pred, labels[i]); // 更新每个权重 for (int j = 0; j < num_features; j++) { double gradient = (y_pred - labels[i]) * data[i][j]; weights[j] -= learning_rate * gradient; } } } }
批量梯度下降(Batch Gradient Descent)是梯度下降算法的一种变体,在每次迭代中使用所有训练样本来计算损失函数关于参数的梯度,并更新模型的参数。与随机梯度下降(SGD)和小批量梯度下降(Mini-batch Gradient Descent)不同,它在每次迭代中使用全部训练样本,因此在计算梯度时具有更好的稳定性和准确性。
以下是批量梯度下降的详细解释:
算法原理:
优点:
缺点:
改进方法:
假设我们有一个损失函数 J ( θ ) J(θ) J(θ)(参数θ表示模型的权重和偏置),其中θ是一个向量。我们的目标是最小化该损失函数,找到使损失函数最小化的最优参数 θ ∗ θ* θ∗。
损失函数: J ( θ ) J(θ) J(θ)
批量梯度下降公式:
在批量梯度下降中,我们使用所有训练样本来计算损失函数关于参数θ的梯度,并更新模型的参数。
损失函数关于参数θ的梯度(梯度向量): ∇ J ( θ ) = [ ∂ J ( θ ) / ∂ θ 1 , ∂ J ( θ ) / ∂ θ 2 , . . . , ∂ J ( θ ) / ∂ θ r ] ∇J(θ) = [∂J(θ)/∂θ₁, ∂J(θ)/∂θ₂, ..., ∂J(θ)/∂θᵣ] ∇J(θ)=[∂J(θ)/∂θ1,∂J(θ)/∂θ2,...,∂J(θ)/∂θr]
参数更新规则(学习率为α):
θ
←
θ
−
α
∗
(
1
/
批大小
)
∗
∑
(
∇
J
(
θ
)
)
θ ← θ - α * (1 / 批大小) * ∑(∇J(θ))
θ←θ−α∗(1/批大小)∗∑(∇J(θ))
其中∇表示梯度运算符,α是学习率(learning rate),r是参数的数量,批大小是在每次迭代中使用的样本数量。
#include <iostream> #include <vector> #include <cmath> // 批量梯度下降函数 void batchGradientDescent(std::vector<std::vector<double>>& data, std::vector<double>& labels, std::vector<double>& weights, double learning_rate, int epochs) { int num_samples = data.size(); int num_features = data[0].size(); for (int epoch = 0; epoch < epochs; epoch++) { std::vector<double> y_pred = predict(data, weights); double loss = lossFunction(y_pred, labels); // 初始化梯度 std::vector<double> gradient(num_features, 0.0); // 计算梯度 for (int i = 0; i < num_samples; i++) { for (int j = 0; j < num_features; j++) { gradient[j] += (y_pred[i] - labels[i]) * data[i][j]; } } // 更新每个权重 for (int j = 0; j < num_features; j++) { weights[j] -= learning_rate * (1.0 / num_samples) * gradient[j]; } // 输出每次迭代的损失 std::cout << "Epoch " << epoch + 1 << ", Loss: " << loss << std::endl; } }
小批量梯度下降(Mini-batch Gradient Descent)是梯度下降算法的一种改进版本,它是批量梯度下降和随机梯度下降的折中方案。在每次迭代中,小批量梯度下降使用一小批(通常为 2 的幂次 2的幂次 2的幂次)训练样本来计算损失函数关于参数的梯度,并更新模型的参数。相比于批量梯度下降,它在计算梯度时具有更好的效率,而相比于随机梯度下降,它的梯度估计更稳定,从而更容易收敛到较好的解。
以下是小批量梯度下降的详细解释:
算法原理:
优点:
缺点:
改进方法:
假设我们有一个损失函数 J ( θ ) J(θ) J(θ)(参数θ表示模型的权重和偏置),其中 θ θ θ是一个向量。我们的目标是最小化该损失函数,找到使损失函数最小化的最优参数 θ ∗ θ* θ∗。
损失函数: J ( θ ) J(θ) J(θ)
小批量梯度下降公式:
在小批量梯度下降中,我们使用一小批(通常为2的幂次)训练样本来计算损失函数关于参数θ的梯度,并更新模型的参数。
损失函数关于参数θ的梯度(梯度向量): ∇ J ( θ ) = [ ∂ J ( θ ) / ∂ θ 1 , ∂ J ( θ ) / ∂ θ 2 , . . . , ∂ J ( θ ) / ∂ θ r ] ∇J(θ) = [∂J(θ)/∂θ₁, ∂J(θ)/∂θ₂, ..., ∂J(θ)/∂θᵣ] ∇J(θ)=[∂J(θ)/∂θ1,∂J(θ)/∂θ2,...,∂J(θ)/∂θr]
参数更新规则(学习率为α,批大小为b):
θ
←
θ
−
α
∗
(
1
/
b
)
∗
∑
(
∇
J
(
θ
)
)
θ ← θ - α * (1 / b) * ∑(∇J(θ))
θ←θ−α∗(1/b)∗∑(∇J(θ))
其中∇表示梯度运算符,α是学习率(learning rate),r是参数的数量,b是小批量的大小。
#include <iostream> #include <vector> #include <cmath> // 小批量梯度下降函数 void miniBatchGradientDescent(std::vector<std::vector<double>>& data, std::vector<double>& labels, std::vector<double>& weights, double learning_rate, int batch_size, int epochs) { int num_samples = data.size(); int num_features = data[0].size(); for (int epoch = 0; epoch < epochs; epoch++) { for (int i = 0; i < num_samples; i += batch_size) { int end_idx = std::min(i + batch_size, num_samples); std::vector<double> batch_labels(labels.begin() + i, labels.begin() + end_idx); std::vector<std::vector<double>> batch_data(data.begin() + i, data.begin() + end_idx); std::vector<double> y_pred = predict(batch_data, weights); double loss = lossFunction(y_pred, batch_labels); // 初始化梯度 std::vector<double> gradient(num_features, 0.0); // 计算梯度 for (int j = 0; j < batch_size; j++) { for (int k = 0; k < num_features; k++) { gradient[k] += (y_pred[j] - batch_labels[j]) * batch_data[j][k]; } } // 更新每个权重 for (int j = 0; j < num_features; j++) { weights[j] -= learning_rate * (1.0 / batch_size) * gradient[j]; } } // 输出每次迭代的损失 std::vector<double> y_pred = predict(data, weights); double loss = lossFunction(y_pred, labels); std::cout << "Epoch " << epoch + 1 << ", Loss: " << loss << std::endl; } }
动量优化(Momentum Optimization)是一种梯度下降算法的改进版本,它通过模拟物体在惯性作用下的运动来加速收敛,并且有助于在梯度更新时减少震荡。动量优化可以在训练过程中更快地达到收敛,并且在复杂的非凸优化问题中通常表现较好。
动量优化算法的核心思想是在更新参数时,利用之前的梯度信息来为当前的梯度方向提供一个“动量”。这样做可以在梯度在一个方向上连续增大或减小时,使得参数更新更加平滑,从而加快收敛速度。
以下是动量优化算法的详细解释:
算法原理:
动量的计算:
动量的计算类似于梯度的累积。我们引入一个动量系数β(通常取值为0.9或0.99),并维护一个动量向量v,初始化为0。在每次迭代中,根据当前梯度计算动量,并更新动量向量v。然后,用动量向量v来更新参数。
优点:
超参数选择:
假设我们有一个损失函数 J(θ)(参数θ表示模型的权重和偏置),其中θ是一个向量。我们的目标是最小化该损失函数,找到使损失函数最小化的最优参数θ*。
损失函数: J ( θ ) J(θ) J(θ)
动量向量的初始化:
初始化动量向量
v
=
0
v = 0
v=0,v的维度与θ相同。
动量的计算:
在每次迭代中,利用当前梯度和之前的动量来计算动量。
动量系数:β(通常取值为0.9或0.99)
更新动量向量: v = β ∗ v + ( 1 − β ) ∗ ∇ J ( θ ) v = β * v + (1 - β) * ∇J(θ) v=β∗v+(1−β)∗∇J(θ)
其中, ∇ J ( θ ) ∇J(θ) ∇J(θ)是损失函数关于参数θ的梯度(梯度向量)。
参数更新规则:
使用动量向量v来更新参数θ。
学习率: α α α
参数更新: θ ← θ − α ∗ v θ ← θ - α * v θ←θ−α∗v
在每次迭代中,我们计算梯度 ∇ J ( θ ) ∇J(θ) ∇J(θ)并更新动量向量 v v v,然后使用动量向量 v v v来更新参数 θ θ θ。动量向量v模拟了之前梯度的“动量”,在梯度的方向上引入了惯性,使得参数更新更加平滑,从而加快收敛速度。
当然!下面是在使用动量和L2正则化的参数更新中的具体数学公式。
首先,我们有L2正则化项的梯度:
∇
R
(
θ
)
=
2
λ
θ
\nabla R(\theta) = 2 \lambda \theta
∇R(θ)=2λθ
其中, λ \lambda λ是正则化强度。
然后,我们计算总梯度,该总梯度包括损失函数的梯度
∇
J
(
θ
)
\nabla J(\theta)
∇J(θ)和正则化项的梯度:
totalGradient
=
∇
J
(
θ
)
+
∇
R
(
θ
)
\text{totalGradient} = \nabla J(\theta) + \nabla R(\theta)
totalGradient=∇J(θ)+∇R(θ)
接下来,我们根据以下公式计算动量向量
v
v
v:
v
=
β
v
+
(
1
−
β
)
totalGradient
v = \beta v + (1 - \beta) \text{totalGradient}
v=βv+(1−β)totalGradient
其中, β \beta β 是动量系数。
最后,我们使用学习率
α
\alpha
α 和动量向量
v
v
v来更新参数
θ
\theta
θ:
θ
←
θ
−
α
v
\theta \leftarrow \theta - \alpha v
θ←θ−αv
这样,我们就将动量和L2正则化结合在了同一优化步骤中。这两个技术通常用于更快地训练神经网络,以及减少过拟合。
#include <iostream> #include <vector> #include <cmath> // 动量优化函数 void momentumOptimization(std::vector<std::vector<double>>& data, std::vector<double>& labels, std::vector<double>& weights, double learning_rate, double beta, int epochs) { int num_samples = data.size(); int num_features = data[0].size(); std::vector<double> velocity(num_features, 0.0); for (int epoch = 0; epoch < epochs; epoch++) { std::vector<double> y_pred = predict(data, weights); double loss = lossFunction(y_pred, labels); // 初始化梯度 std::vector<double> gradient(num_features, 0.0); // 计算梯度 for (int i = 0; i < num_samples; i++) { for (int j = 0; j < num_features; j++) { gradient[j] += (y_pred[i] - labels[i]) * data[i][j]; } } // 更新动量 for (int j = 0; j < num_features; j++) { velocity[j] = beta * velocity[j] + (1 - beta) * gradient[j]; } // 更新每个权重 for (int j = 0; j < num_features; j++) { weights[j] -= learning_rate * velocity[j]; } // 输出每次迭代的损失 std::cout << "Epoch " << epoch + 1 << ", Loss: " << loss << std::endl; } }
Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的梯度下降算法,它可以针对每个参数自适应地调整学习率,从而更有效地优化模型。Adagrad的主要思想是在训练过程中为每个参数维护一个学习率,根据参数的历史梯度信息来自适应地调整学习率大小。
以下是Adagrad算法的详细解释:
算法原理:
其中∇J(θ(t))是损失函数关于参数θ在第t次迭代的梯度,G(t)是累积梯度平方和,ε是一个小的常数(通常取较小的值,例如1e-8)以防止除以0。
累积梯度平方和的计算:
在每次迭代中,对参数的梯度进行平方,然后将平方梯度值加到之前的累积梯度平方和中。
优点:
缺点:
改进方法:
假设我们有一个损失函数 J ( θ ) J(θ) J(θ)(参数θ表示模型的权重和偏置),其中 θ θ θ是一个向量。我们的目标是最小化该损失函数,找到使损失函数最小化的最优参数 θ ∗ θ* θ∗。
损失函数: J ( θ ) J(θ) J(θ)
学习率: α α α
Adagrad的参数更新规则:
Adagrad算法为每个参数θ维护一个累积梯度平方和(sum of squared gradients),并使用该平方和来调整参数的学习率。
在第t次迭代时,我们计算参数θ的梯度∇J(θ(t)),并将其平方进行累加:
G
(
t
)
=
G
(
t
−
1
)
+
(
∇
J
(
θ
(
t
)
)
)
2
G(t) = G(t-1) + (∇J(θ(t)))^2
G(t)=G(t−1)+(∇J(θ(t)))2
然后,使用累积梯度平方和G(t)来更新参数θ:
θ
(
t
+
1
)
=
θ
(
t
)
−
(
α
/
s
q
r
t
(
G
(
t
)
+
ε
)
)
∗
∇
J
(
θ
(
t
)
)
θ(t+1) = θ(t) - (α / sqrt(G(t) + ε)) * ∇J(θ(t))
θ(t+1)=θ(t)−(α/sqrt(G(t)+ε))∗∇J(θ(t))
其中:
正则化通常通过在损失函数中添加一个与模型参数的大小有关的项来实现。对于L1或L2正则化,这个额外的项会考虑参数的绝对值或平方值。对于你提供的优化器公式,添加正则化需要考虑正则化项对梯度的影响。
以L2正则化为例,L2正则化项与参数的平方成正比,因此其对梯度的贡献是线性的。假设正则化强度为 λ \lambda λ,我们可以将L2正则化项的梯度表示为 2 λ θ ( t ) 2\lambda\theta(t) 2λθ(t)。
因此,损失函数的梯度将变为:
KaTeX parse error: {align*} can be used only in display mode.
然后,我们可以将这个新的梯度插入到原始的优化器更新公式中:
KaTeX parse error: {align*} can be used only in display mode.
其中
#include <iostream> #include <vector> #include <cmath> // Adagrad优化函数 void adagradOptimization(std::vector<std::vector<double>>& data, std::vector<double>& labels, std::vector<double>& weights, double learning_rate, double epsilon, int epochs) { int num_samples = data.size(); int num_features = data[0].size(); std::vector<double> sum_squared_gradients(num_features, 0.0); for (int epoch = 0; epoch < epochs; epoch++) { std::vector<double> y_pred = predict(data, weights); double loss = lossFunction(y_pred, labels); // 初始化梯度 std::vector<double> gradient(num_features, 0.0); // 计算梯度 for (int i = 0; i < num_samples; i++) { for (int j = 0; j < num_features; j++) { gradient[j] += (y_pred[i] - labels[i]) * data[i][j]; } } // 更新累积梯度平方和 for (int j = 0; j < num_features; j++) { sum_squared_gradients[j] += gradient[j] * gradient[j]; } // 更新每个权重 for (int j = 0; j < num_features; j++) { weights[j] -= (learning_rate / (std::sqrt(sum_squared_gradients[j]) + epsilon)) * gradient[j]; } // 输出每次迭代的损失 std::cout << "Epoch " << epoch + 1 << ", Loss: " << loss << std::endl; } }
RMSprop(Root Mean Square Propagation)是一种自适应学习率的梯度下降算法,它是Adagrad算法的一种改进版本。RMSprop通过引入一个衰减系数(decay rate)来解决Adagrad算法中累积梯度平方和过大导致学习率过度减小的问题。RMSprop在处理长时间训练的模型时表现更好,并且在深度学习中广泛应用。
以下是RMSprop算法的详细解释:
算法原理:
其中:
优点:
改进方法:
假设我们有一个损失函数 J ( θ ) J(θ) J(θ)(参数θ表示模型的权重和偏置),其中 θ θ θ是一个向量。我们的目标是最小化该损失函数,找到使损失函数最小化的最优参数 θ ∗ θ* θ∗。
损失函数: J ( θ ) J(θ) J(θ)
学习率: α α α
衰减系数: β β β
RMSprop的参数更新规则:
RMSprop算法为每个参数θ维护一个累积梯度平方和(sum of squared gradients),并使用该平方和来调整参数的学习率。
在第t次迭代时,我们计算参数θ的梯度∇J(θ(t)),并将其平方进行累加,然后利用衰减系数β对累积梯度平方和进行衰减:
G
(
t
)
=
β
∗
G
(
t
−
1
)
+
(
1
−
β
)
∗
(
∇
J
(
θ
(
t
)
)
)
2
G(t) = β * G(t-1) + (1 - β) * (∇J(θ(t)))^2
G(t)=β∗G(t−1)+(1−β)∗(∇J(θ(t)))2
然后,使用累积梯度平方和G(t)来更新参数θ:
θ
(
t
+
1
)
=
θ
(
t
)
−
(
α
/
s
q
r
t
(
G
(
t
)
+
ε
)
)
∗
∇
J
(
θ
(
t
)
)
θ(t+1) = θ(t) - (α / sqrt(G(t) + ε)) * ∇J(θ(t))
θ(t+1)=θ(t)−(α/sqrt(G(t)+ε))∗∇J(θ(t))
其中:
#include <iostream> #include <vector> #include <cmath> // RMSprop优化函数 void rmspropOptimization(std::vector<std::vector<double>>& data, std::vector<double>& labels, std::vector<double>& weights, double learning_rate, double beta, double epsilon, int epochs) { int num_samples = data.size(); int num_features = data[0].size(); std::vector<double> squared_gradients(num_features, 0.0); for (int epoch = 0; epoch < epochs; epoch++) { std::vector<double> y_pred = predict(data, weights); double loss = lossFunction(y_pred, labels); // 初始化梯度 std::vector<double> gradient(num_features, 0.0); // 计算梯度 for (int i = 0; i < num_samples; i++) { for (int j = 0; j < num_features; j++) { gradient[j] += (y_pred[i] - labels[i]) * data[i][j]; } } // 更新累积梯度平方和 for (int j = 0; j < num_features; j++) { squared_gradients[j] = beta * squared_gradients[j] + (1 - beta) * gradient[j] * gradient[j]; } // 更新每个权重 for (int j = 0; j < num_features; j++) { weights[j] -= (learning_rate / (std::sqrt(squared_gradients[j]) + epsilon)) * gradient[j]; } // 输出每次迭代的损失 std::cout << "Epoch " << epoch + 1 << ", Loss: " << loss << std::endl; } }
以下是Adam算法的详细解释:
算法原理:
假设我们有一个损失函数 J(θ)(参数θ表示模型的权重和偏置),其中θ是一个向量。我们的目标是最小化该损失函数,找到使损失函数最小化的最优参数θ*。
Adam算法为每个参数θ维护两个一阶矩估计(first moment estimate)m和二阶矩估计(second moment estimate)v,并使用它们来调整参数的学习率。
参数更新规则:对于第t次迭代,学习率为α,衰减系数为β1和β2,ε是一个小的常数(通常取较小的值,例如1e-8)以防止除以0,θ在第t+1次迭代的更新公式为:
m
(
t
)
=
β
1
∗
m
(
t
−
1
)
+
(
1
−
β
1
)
∗
∇
J
(
θ
(
t
)
)
m(t) = β1 * m(t-1) + (1 - β1) * ∇J(θ(t))
m(t)=β1∗m(t−1)+(1−β1)∗∇J(θ(t)) (一阶矩估计)
v
(
t
)
=
β
2
∗
v
(
t
−
1
)
+
(
1
−
β
2
)
∗
(
∇
J
(
θ
(
t
)
)
)
2
v(t) = β2 * v(t-1) + (1 - β2) * (∇J(θ(t)))^2
v(t)=β2∗v(t−1)+(1−β2)∗(∇J(θ(t)))2 (二阶矩估计)
m
h
a
t
(
t
)
=
m
(
t
)
/
(
1
−
β
1
t
)
m_hat(t) = m(t) / (1 - β1^t)
mhat(t)=m(t)/(1−β1t) (修正一阶矩估计)
v
h
a
t
(
t
)
=
v
(
t
)
/
(
1
−
β
2
t
)
v_hat(t) = v(t) / (1 - β2^t)
vhat(t)=v(t)/(1−β2t) (修正二阶矩估计)
θ ( t + 1 ) = θ ( t ) − ( α / ( s q r t ( v h a t ( t ) ) + ε ) ) ∗ m h a t ( t ) θ(t+1) = θ(t) - (α / (sqrt(v_hat(t)) + ε)) * m_hat(t) θ(t+1)=θ(t)−(α/(sqrt(vhat(t))+ε))∗mhat(t)
其中:
优点:
改进方法:
假设我们有一个损失函数 J ( θ ) J(θ) J(θ)(参数θ表示模型的权重和偏置),其中 θ θ θ是一个向量。我们的目标是最小化该损失函数,找到使损失函数最小化的最优参数 θ ∗ θ* θ∗。
损失函数: J ( θ ) J(θ) J(θ)
学习率: α α α
衰减系数: β 1 和 β 2 β1和β2 β1和β2
一阶矩估计(m)和二阶矩估计(v):
Adam算法为每个参数θ维护两个一阶矩估计和二阶矩估计,并使用它们来调整参数的学习率。
在第t次迭代时,我们计算参数θ的梯度∇J(θ(t)),然后分别计算一阶矩估计m和二阶矩估计v:
m
(
t
)
=
β
1
∗
m
(
t
−
1
)
+
(
1
−
β
1
)
∗
∇
J
(
θ
(
t
)
)
m(t) = β1 * m(t-1) + (1 - β1) * ∇J(θ(t))
m(t)=β1∗m(t−1)+(1−β1)∗∇J(θ(t))
v
(
t
)
=
β
2
∗
v
(
t
−
1
)
+
(
1
−
β
2
)
∗
(
∇
J
(
θ
(
t
)
)
)
2
v(t) = β2 * v(t-1) + (1 - β2) * (∇J(θ(t)))^2
v(t)=β2∗v(t−1)+(1−β2)∗(∇J(θ(t)))2
其中:
修正后的一阶矩估计(m_hat)和二阶矩估计(v_hat):
由于在算法初期估计偏向0,需要对一阶矩估计m和二阶矩估计v进行修正。
m
h
a
t
(
t
)
=
m
(
t
)
/
(
1
−
β
1
t
)
m_hat(t) = m(t) / (1 - β1^t)
mhat(t)=m(t)/(1−β1t)
v
h
a
t
(
t
)
=
v
(
t
)
/
(
1
−
β
2
t
)
v_hat(t) = v(t) / (1 - β2^t)
vhat(t)=v(t)/(1−β2t)
参数更新规则:
使用修正后的一阶矩估计m_hat和二阶矩估计v_hat来更新参数θ。
θ ( t + 1 ) = θ ( t ) − ( α / ( s q r t ( v h a t ( t ) ) + ε ) ) ∗ m h a t ( t ) θ(t+1) = θ(t) - (α / (sqrt(v_hat(t)) + ε)) * m_hat(t) θ(t+1)=θ(t)−(α/(sqrt(vhat(t))+ε))∗mhat(t)
其中:
#include <iostream> #include <vector> #include <cmath> // Adam优化函数 void adamOptimization(std::vector<std::vector<double>>& data, std::vector<double>& labels, std::vector<double>& weights, double learning_rate, double beta1, double beta2, double epsilon, int epochs) { int num_samples = data.size(); int num_features = data[0].size(); std::vector<double> m(num_features, 0.0); // 一阶矩估计 std::vector<double> v(num_features, 0.0); // 二阶矩估计 int t = 0; // 时间步数 for (int epoch = 0; epoch < epochs; epoch++) { std::vector<double> y_pred = predict(data, weights); double loss = lossFunction(y_pred, labels); // 初始化梯度 std::vector<double> gradient(num_features, 0.0); // 计算梯度 for (int i = 0; i < num_samples; i++) { for (int j = 0; j < num_features; j++) { gradient[j] += (y_pred[i] - labels[i]) * data[i][j]; } } // 更新时间步数 t++; // 更新一阶矩估计和二阶矩估计 for (int j = 0; j < num_features; j++) { m[j] = beta1 * m[j] + (1 - beta1) * gradient[j]; v[j] = beta2 * v[j] + (1 - beta2) * gradient[j] * gradient[j]; } // 进行修正后的一阶矩估计和二阶矩估计 std::vector<double> m_hat(num_features, 0.0); std::vector<double> v_hat(num_features, 0.0); for (int j = 0; j < num_features; j++) { m_hat[j] = m[j] / (1 - std::pow(beta1, t)); v_hat[j] = v[j] / (1 - std::pow(beta2, t)); } // 更新每个权重 for (int j = 0; j < num_features; j++) { weights[j] -= (learning_rate / (std::sqrt(v_hat[j]) + epsilon)) * m_hat[j]; } // 输出每次迭代的损失 std::cout << "Epoch " << epoch + 1 << ", Loss: " << loss << std::endl; } }
(Nesterov加速梯度法),简称NAG,是一种优化算法,用于训练神经网络和其他机器学习模型。Nesterov算法是梯度下降算法的一种变种,通过引入动量(momentum)的概念来加速收敛,并在损失函数梯度的计算中采用"look-ahead"策略来调整参数的更新方向,从而在训练过程中减少震荡和摆动。
Nesterov算法的特点在于在计算梯度时先对参数进行一次"look-ahead"更新,然后在该位置上计算梯度。这使得梯度计算的方向更加准确,能够更好地适应参数的变化情况。
以下是Nesterov算法的详细解释:
算法原理:
假设我们有一个损失函数 J(θ)(参数θ表示模型的权重和偏置),其中θ是一个向量。我们的目标是最小化该损失函数,找到使损失函数最小化的最优参数θ*。
Nesterov算法引入了动量(momentum)的概念。动量是过去梯度的加权平均,它在梯度的计算中增加了一个惯性项,从而加速收敛。
参数更新规则:对于第t次迭代,学习率为α,动量系数为γ,参数θ的一阶梯度为∇J(θ)。
v
(
t
)
=
γ
∗
v
(
t
−
1
)
+
α
∗
∇
J
(
θ
−
γ
∗
v
(
t
−
1
)
)
v(t) = γ * v(t-1) + α * ∇J(θ - γ * v(t-1))
v(t)=γ∗v(t−1)+α∗∇J(θ−γ∗v(t−1)) (计算动量)
θ
(
t
+
1
)
=
θ
(
t
)
−
v
(
t
)
θ(t+1) = θ(t) - v(t)
θ(t+1)=θ(t)−v(t) (更新参数)
其中:
优点:
改进方法:
假设我们有一个损失函数 J ( θ ) J(θ) J(θ)(参数θ表示模型的权重和偏置),其中 θ θ θ是一个向量。我们的目标是最小化该损失函数,找到使损失函数最小化的最优参数 θ ∗ θ* θ∗。
损失函数: J ( θ ) J(θ) J(θ)
学习率: α α α
动量系数: γ γ γ
动量(momentum)的累积向量(v):
Nesterov算法引入了动量的概念,动量是过去梯度的加权平均。
在第t次迭代时,我们首先进行动量的累积,然后计算梯度:
v
(
t
)
=
γ
∗
v
(
t
−
1
)
+
α
∗
∇
J
(
θ
−
γ
∗
v
(
t
−
1
)
)
v(t) = γ * v(t-1) + α * ∇J(θ - γ * v(t-1))
v(t)=γ∗v(t−1)+α∗∇J(θ−γ∗v(t−1))
其中:
参数更新规则:
使用动量的累积向量v来更新参数θ。
θ ( t + 1 ) = θ ( t ) − v ( t ) θ(t+1) = θ(t) - v(t) θ(t+1)=θ(t)−v(t)
#include <iostream> #include <vector> #include <cmath> // 第一种写法-Nesterov算法优化函数 void nesterovOptimization(std::vector<std::vector<double>>& data, std::vector<double>& labels, std::vector<double>& weights, double learning_rate, double momentum, int epochs) { int num_samples = data.size(); int num_features = data[0].size(); std::vector<double> v(num_features, 0.0); // 动量的累积向量 for (int epoch = 0; epoch < epochs; epoch++) { // 提前计算"look-ahead"位置的参数更新 std::vector<double> theta_ahead(num_features, 0.0); for (int j = 0; j < num_features; j++) { theta_ahead[j] = weights[j] - momentum * v[j]; } // 计算"look-ahead"位置的梯度 std::vector<double> gradient_ahead(num_features, 0.0); std::vector<double> y_pred_ahead = predict(data, theta_ahead); for (int i = 0; i < num_samples; i++) { for (int j = 0; j < num_features; j++) { gradient_ahead[j] += (y_pred_ahead[i] - labels[i]) * data[i][j]; } } // 更新动量的累积向量 for (int j = 0; j < num_features; j++) { v[j] = momentum * v[j] + learning_rate * gradient_ahead[j]; } // 更新参数 for (int j = 0; j < num_features; j++) { weights[j] -= v[j]; } // 计算损失并输出 std::vector<double> y_pred = predict(data, weights); double loss = lossFunction(y_pred, labels); std::cout << "Epoch " << epoch + 1 << ", Loss: " << loss << std::endl; } }
// 第二种写法-Nesterov算法优化函数(推荐) void nesterovOptimization(std::vector<std::vector<double>>& data, std::vector<double>& labels, std::vector<double>& weights, double learning_rate, double momentum, int epochs) { int num_samples = data.size(); int num_features = data[0].size(); std::vector<double> v(num_features, 0.0); // 动量的累积向量 for (int epoch = 0; epoch < epochs; epoch++) { std::vector<double> y_pred = predict(data, weights); double loss = lossFunction(y_pred, labels); // 初始化梯度 std::vector<double> gradient(num_features, 0.0); // 计算梯度 for (int i = 0; i < num_samples; i++) { for (int j = 0; j < num_features; j++) { gradient[j] += (y_pred[i] - labels[i]) * data[i][j]; } } // 更新动量的累积向量 for (int j = 0; j < num_features; j++) { v[j] = momentum * v[j] + learning_rate * gradient_ahead[j]; } // 更新参数 for (int j = 0; j < num_features; j++) { weights[j] -= (momentum * v[j] + learning_rate * gradient_ahead[j]); } // 计算损失并输出 std::vector<double> y_pred = predict(data, weights); double loss = lossFunction(y_pred, labels); std::cout << "Epoch " << epoch + 1 << ", Loss: " << loss << std::endl; } }
Nadam 是一种优化算法,结合了Nesterov Accelerated Gradient(NAG)和Adam两种优化算法的特点。Nadam在深度学习中广泛应用,是目前训练神经网络的优化算法之一。
Nadam算法在Adam的基础上进行了改进,主要是对Adam的动量部分进行了修改。Nadam的优点在于对于凸优化和非凸优化问题都具有较好的性能,并且对于稀疏梯度的处理也比较优秀。
以下是Nadam算法的详细解释:
算法原理:
假设我们有一个损失函数 J(θ)(参数θ表示模型的权重和偏置),其中θ是一个向量。我们的目标是最小化该损失函数,找到使损失函数最小化的最优参数θ*。
Nadam算法为每个参数θ维护两个一阶矩估计(first moment estimate)m和二阶矩估计(second moment estimate)v,并使用它们来调整参数的学习率。
参数更新规则:对于第t次迭代,学习率为α,衰减系数为β1和β2,ε是一个小的常数(通常取较小的值,例如1e-8)以防止除以0,β1_t和β2_t是β1和β2的时间步数的衰减校正。
m
(
t
)
=
β
1
∗
m
(
t
−
1
)
+
(
1
−
β
1
)
∗
∇
J
(
θ
(
t
)
)
m(t) = β1 * m(t-1) + (1 - β1) * ∇J(θ(t))
m(t)=β1∗m(t−1)+(1−β1)∗∇J(θ(t)) (一阶矩估计)
v
(
t
)
=
β
2
∗
v
(
t
−
1
)
+
(
1
−
β
2
)
∗
(
∇
J
(
θ
(
t
)
)
)
2
v(t) = β2 * v(t-1) + (1 - β2) * (∇J(θ(t)))^2
v(t)=β2∗v(t−1)+(1−β2)∗(∇J(θ(t)))2 (二阶矩估计)
m
h
a
t
(
t
)
=
m
(
t
)
/
(
1
−
β
1
t
)
m_hat(t) = m(t) / (1 - β1^t)
mhat(t)=m(t)/(1−β1t) (修正一阶矩估计)
v
h
a
t
(
t
)
=
v
(
t
)
/
(
1
−
β
2
t
)
v_hat(t) = v(t) / (1 - β2^t)
vhat(t)=v(t)/(1−β2t) (修正二阶矩估计)
θ
(
t
+
1
)
=
θ
(
t
)
−
(
α
/
(
s
q
r
t
(
v
h
a
t
(
t
)
)
+
ε
)
)
∗
(
β
1
t
∗
m
h
a
t
(
t
)
+
(
1
−
β
1
t
)
∗
∇
J
(
θ
(
t
)
)
)
θ(t+1) = θ(t) - (α / (sqrt(v_hat(t)) + ε)) * (β1_t * m_hat(t) + (1 - β1_t) * ∇J(θ(t)))
θ(t+1)=θ(t)−(α/(sqrt(vhat(t))+ε))∗(β1t∗mhat(t)+(1−β1t)∗∇J(θ(t)))
其中:
优点:
改进方法:
Nadam算法在深度学习中广泛应用,特别适用于训练复杂的深度神经网络和处理大规模数据集。它是目前训练神经网络的优化算法之一,并且在很多实际任务中表现优秀。
假设我们有一个损失函数 J(θ)(参数θ表示模型的权重和偏置),其中θ是一个向量。我们的目标是最小化该损失函数,找到使损失函数最小化的最优参数θ*。
损失函数: J ( θ ) J(θ) J(θ)
学习率: α α α
衰减系数: β 1 和 β 2 β1和β2 β1和β2
一阶矩估计(m)和二阶矩估计(v):
Nadam算法为每个参数θ维护两个一阶矩估计和二阶矩估计,并使用它们来调整参数的学习率。
在第t次迭代时,我们计算参数θ的梯度∇J(θ(t)),然后分别计算一阶矩估计m和二阶矩估计v:
m
(
t
)
=
β
1
∗
m
(
t
−
1
)
+
(
1
−
β
1
)
∗
∇
J
(
θ
(
t
)
)
m(t) = β1 * m(t-1) + (1 - β1) * ∇J(θ(t))
m(t)=β1∗m(t−1)+(1−β1)∗∇J(θ(t))
v
(
t
)
=
β
2
∗
v
(
t
−
1
)
+
(
1
−
β
2
)
∗
(
∇
J
(
θ
(
t
)
)
)
2
v(t) = β2 * v(t-1) + (1 - β2) * (∇J(θ(t)))^2
v(t)=β2∗v(t−1)+(1−β2)∗(∇J(θ(t)))2
其中:
时间步数的衰减校正:
Nadam算法引入了时间步数的衰减校正,用于修正动量的计算。
β
1
t
=
β
1
∗
(
1
−
0.5
∗
0.9
6
(
t
/
250
)
)
β1_t = β1 * (1 - 0.5 * 0.96^(t / 250))
β1t=β1∗(1−0.5∗0.96(t/250))
β
2
t
=
β
2
∗
(
1
−
0.9
9
t
)
β2_t = β2 * (1 - 0.99^t)
β2t=β2∗(1−0.99t)
修正后的一阶矩估计(m_hat)和二阶矩估计(v_hat):
由于在算法初期估计偏向0,需要对一阶矩估计m和二阶矩估计v进行修正。
m
h
a
t
(
t
)
=
m
(
t
)
/
(
1
−
β
1
t
)
m_hat(t) = m(t) / (1 - β1^t)
mhat(t)=m(t)/(1−β1t)
v
h
a
t
(
t
)
=
v
(
t
)
/
(
1
−
β
2
t
)
v_hat(t) = v(t) / (1 - β2^t)
vhat(t)=v(t)/(1−β2t)
参数更新规则:
使用修正后的一阶矩估计m_hat和二阶矩估计v_hat来更新参数θ。
θ ( t + 1 ) = θ ( t ) − ( α / ( s q r t ( v h a t ( t ) ) + ε ) ) ∗ ( β 1 t ∗ m h a t ( t ) + ( 1 − β 1 t ) ∗ ∇ J ( θ ( t ) ) ) θ(t+1) = θ(t) - (α / (sqrt(v_hat(t)) + ε)) * (β1_t * m_hat(t) + (1 - β1_t) * ∇J(θ(t))) θ(t+1)=θ(t)−(α/(sqrt(vhat(t))+ε))∗(β1t∗mhat(t)+(1−β1t)∗∇J(θ(t)))
其中:
Nadam算法通过引入Nesterov Accelerated Gradient(NAG)和Adam两种优化算法的特点,实现了对凸优化和非凸优化问题都有较好性能的优化算法。在深度学习中,Nadam广泛应用于训练复杂的深度神经网络和处理大规模数据集。
Nadam(Nesterov-accelerated Adaptive Moment Estimation)结合了Adam优化器的概念和Nesterov加速梯度的想法。在Nadam中添加正则化与在Adam中添加正则化非常相似,你只需要在计算梯度时包括正则化项。
以下是将L2正则化与Nadam结合使用的步骤:
计算损失函数的梯度(不包括正则化项): ∇ J ( θ ( t ) ) \nabla J(\theta(t)) ∇J(θ(t))
计算正则化项的梯度: 2 λ θ ( t ) 2\lambda\theta(t) 2λθ(t)(对于L2正则化)
结合梯度: ∇ J ( θ ( t ) ) + 2 λ θ ( t ) \nabla J(\theta(t)) + 2\lambda\theta(t) ∇J(θ(t))+2λθ(t)
更新一阶矩估计:
m
(
t
)
=
β
1
m
(
t
−
1
)
+
(
1
−
β
1
)
(
∇
J
(
θ
(
t
)
)
+
2
λ
θ
(
t
)
)
m(t) = \beta_1 m(t-1) + (1-\beta_1)\left(\nabla J(\theta(t)) + 2\lambda\theta(t)\right)
m(t)=β1m(t−1)+(1−β1)(∇J(θ(t))+2λθ(t))
更新二阶矩估计:
v
(
t
)
=
β
2
v
(
t
−
1
)
+
(
1
−
β
2
)
(
∇
J
(
θ
(
t
)
)
+
2
λ
θ
(
t
)
)
2
v(t) = \beta_2 v(t-1) + (1-\beta_2)\left(\nabla J(\theta(t)) + 2\lambda\theta(t)\right)^2
v(t)=β2v(t−1)+(1−β2)(∇J(θ(t))+2λθ(t))2
修正一阶和二阶矩的偏差:
m
^
(
t
)
=
m
(
t
)
1
−
β
1
t
\hat{m}(t) = \frac{m(t)}{1-\beta_1^t}
m^(t)=1−β1tm(t)
v
^
(
t
)
=
v
(
t
)
1
−
β
2
t
\hat{v}(t) = \frac{v(t)}{1-\beta_2^t}
v^(t)=1−β2tv(t)
应用Nesterov修正:
m
Nesterov
(
t
)
=
β
1
m
^
(
t
)
+
(
1
−
β
1
)
(
∇
J
(
θ
(
t
)
)
+
2
λ
θ
(
t
)
)
m_{\text{Nesterov}}(t) = \beta_1 \hat{m}(t) + (1-\beta_1)\left(\nabla J(\theta(t)) + 2\lambda\theta(t)\right)
mNesterov(t)=β1m^(t)+(1−β1)(∇J(θ(t))+2λθ(t))
更新权重:
θ
(
t
+
1
)
=
θ
(
t
)
−
α
v
^
(
t
)
+
ϵ
m
Nesterov
(
t
)
\theta(t+1) = \theta(t) - \frac{\alpha}{\sqrt{\hat{v}(t)} + \epsilon} m_{\text{Nesterov}}(t)
θ(t+1)=θ(t)−v^(t)
+ϵαmNesterov(t)
代码实现与Adam类似,主要区别在于加入了Nesterov修正。你可以按照上述步骤编写代码来实现Nadam优化器,并包括你选择的正则化项。
注意:Nesterov修正为动量项增加了一种"预览"效果,以更准确地预测下一个参数更新的方向。与Adam相比,这可能会在某些情况下提供更好的训练性能。
#include <iostream> #include <vector> #include <cmath> // 定义损失函数(假设为均方误差) double lossFunction(std::vector<double>& y_pred, std::vector<double>& y_actual) { double loss = 0.0; for (int i = 0; i < y_pred.size(); i++) { loss += 0.5 * std::pow(y_pred[i] - y_actual[i], 2); } return loss; } // 定义模型预测函数 std::vector<double> predict(std::vector<std::vector<double>>& data, std::vector<double>& weights) { std::vector<double> y_pred(data.size(), 0.0); for (int i = 0; i < data.size(); i++) { for (int j = 0; j < data[i].size(); j++) { y_pred[i] += data[i][j] * weights[j]; } } return y_pred; } // Nadam优化函数 void nadamOptimization(std::vector<std::vector<double>>& data, std::vector<double>& labels, std::vector<double>& weights, double learning_rate, double beta1, double beta2, double epsilon, int epochs) { int num_samples = data.size(); int num_features = data[0].size(); std::vector<double> m(num_features, 0.0); // 一阶矩估计 std::vector<double> v(num_features, 0.0); // 二阶矩估计 int t = 0; // 时间步数 for (int epoch = 0; epoch < epochs; epoch++) { std::vector<double> y_pred = predict(data, weights); double loss = lossFunction(y_pred, labels); // 初始化梯度 std::vector<double> gradient(num_features, 0.0); // 计算梯度 for (int i = 0; i < num_samples; i++) { for (int j = 0; j < num_features; j++) { gradient[j] += (y_pred[i] - labels[i]) * data[i][j]; } } // 更新时间步数 t++; // 时间步数的衰减校正 double beta1_t = beta1 * (1 - 0.5 * std::pow(0.96, t / 250)); double beta2_t = beta2 * (1 - std::pow(0.99, t)); // 更新一阶矩估计和二阶矩估计 for (int j = 0; j < num_features; j++) { m[j] = beta1 * m[j] + (1 - beta1) * gradient[j]; v[j] = beta2 * v[j] + (1 - beta2) * gradient[j] * gradient[j]; } // 进行修正后的一阶矩估计和二阶矩估计 std::vector<double> m_hat(num_features, 0.0); std::vector<double> v_hat(num_features, 0.0); for (int j = 0; j < num_features; j++) { m_hat[j] = m[j] / (1 - std::pow(beta1, t)); v_hat[j] = v[j] / (1 - std::pow(beta2, t)); } // 更新每个权重 for (int j = 0; j < num_features; j++) { weights[j] -= (learning_rate / (std::sqrt(v_hat[j]) + epsilon)) * (beta1_t * m_hat[j] + (1 - beta1_t) * gradient[j]); } // 输出每次迭代的损失 std::cout << "Epoch " << epoch + 1 << ", Loss: " << loss << std::endl; } } int main() { // 假设训练数据为2维特征 std::vector<std::vector<double>> data = { {1.0, 2.0}, {2.0, 3.0}, {3.0, 4.0}, {4.0, 5.0}, {5.0, 6.0} }; // 对应的标签 std::vector<double> labels = {3.0, 4.0, 5.0, 6.0, 7.0}; // 初始化权重 std::vector<double> weights = {0.0, 0.0}; double learning_rate = 0.1; double beta1 = 0.9; double beta2 = 0.999; double epsilon = 1e-8; int epochs = 1000; // 使用Nadam优化权重 nadamOptimization(data, labels, weights, learning_rate, beta1, beta2, epsilon, epochs); // 打印最终学到的权重 std::cout << "Weights: "; for (double w : weights) { std::cout << w << " "; } std::cout << std::endl; return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。