欢迎收藏Star我的Machine Learning Blog:https://github.com/purepisces/Wenqing-Machine_Learning_Blog。如果收藏star, 有问题可以随时与我交流, 谢谢大家!
优化器
在深度学习中,优化器被用来调整模型的参数。优化器的目的是调整模型权重以最小化损失函数。
回顾一下,我们使用线性类和激活函数类构建了自己的MLP模型,并且已经了解了如何对神经网络中使用的核心组件进行前向传播和反向传播。前向传播用于估计,而反向传播告诉我们参数变化如何影响损失。我们编写了一些损失函数,这些函数是我们用来评估模型估计质量的标准。最后一步是利用我们学到的关于参数变化如何影响损失的信息来改进我们的模型。
随机梯度下降(SGD)
在这一节中,我们将实现带动量的小批量随机梯度下降,我们在本文档中将其称为SGD。小批量SGD是一种通过使用训练数据的较小批次来近似梯度的SGD算法的版本,而动量是一种帮助加速SGD的方法,它通过将上一次更新的速度纳入当前更新来减少振荡。PyTorch库中的sgd
函数实际上是小批量随机梯度下降的实现。
你的任务是在文件sgd.py
中实现SGD
类的step
属性函数:
-
类属性:
- l l l: 模型层的列表
- L L L: 模型层数
- l r lr lr: 学习率,调节更新大小的可调超参数。
- μ \mu μ: 动量率 μ \mu μ,控制以前更新对当前更新方向的影响程度的可调超参数。 μ = 0 \mu = 0 μ=0 表示没有动量。
- v W v_W vW: 每层权重的速度列表
- v b v_b vb: 每层偏置的速度列表
-
类方法:
- step: 更新每个模型层的
W
W
W 和
b
b
b:
- 因为参数梯度告诉我们哪个方向使模型更糟,所以我们移动到梯度的相反方向来更新参数。
- 当动量不为零时,更新速度 v W v_W vW 和 v b v_b vb,它们是达到全局最小值的梯度变化。前次更新的速度按照超参数 μ \mu μ 进行缩放。
- step: 更新每个模型层的
W
W
W 和
b
b
b:
请考虑以下类结构:
class SGD:
def __init__(self, model, lr=0.1, momentum=0):
self.l = model.layers
self.L = len(model.layers)
self.lr = lr
self.mu = momentum
self.v_W = [np.zeros(self.l[i].W.shape) for i in range(self.L)]
self.v_b = [np.zeros(self.l[i].b.shape) for i in range(self.L)]
def step(self):
for i in range(self.L):
if self.mu == 0:
self.l[i].W = # TODO
self.l[i].b = # TODO
else:
self.v_W[i] = # TODO
self.v_b[i] = # TODO
self.l[i].W = # TODO
self.l[i].b = # TODO
代码名称 | 数学符号 | 类型 | 形状 | 含义 |
---|---|---|---|---|
model | - | 对象 | - | 具有层属性的模型 |
l l l | - | 对象 | - | 从模型中选择的层属性 |
L L L | - | 标量 | - | 模型中的层数 |
lr | λ \lambda λ | 标量 | - | 学习率超参数,用于缩放新梯度的影响 |
momentum | μ \mu μ | 标量 | - | 动量超参数,用于缩放以前梯度的影响 |
v_W | - | 列表 | L L L | 速度权重参数的列表,每层一个 |
v_b | - | 列表 | L L L | 速度偏置参数的列表,每层一个 |
v_W[i] | v W i v_{W_{i}} vWi | 矩阵 | C i + 1 × C i C_{i+1} \times C_i Ci+1×Ci | 第 i i i 层权重的速度 |
v_b[i] | v b i v_{b_{i}} vbi | 矩阵 | C i + 1 × 1 C_{i+1} \times 1 Ci+1×1 | 第 i i i 层偏置的速度 |
l[i].W | W i W_{i} Wi | 矩阵 | C i + 1 × C i C_{i+1} \times C_i Ci+1×Ci | 层的权重参数 |
l[i].b | b i b_{i} bi | 矩阵 | C i + 1 × 1 C_{i+1} \times 1 Ci+1×1 | 层的偏置参数 |
注意: 对于线性层中的 W W W,其形状为 C i + 1 × C i C_{i+1} \times C_i Ci+1×Ci,其中 C i C_i Ci 是该层的输入特征数, C i + 1 C_{i+1} Ci+1 是该层的输出特征数。
SGD 方程(无动量)
W : = W − λ ∂ L ∂ W W := W - \lambda \frac{\partial L}{\partial W} W:=W−λ∂W∂L
b : = b − λ ∂ L ∂ b b := b - \lambda \frac{\partial L}{\partial b} b:=b−λ∂b∂L
SGD 方程(带动量)
v W : = μ v W + ∂ L ∂ W v_W := \mu v_W + \frac{\partial L}{\partial W} vW:=μvW+∂W∂L
v b : = μ v b + ∂ L ∂ b v_b := \mu v_b + \frac{\partial L}{\partial b} vb:=μvb+∂b∂L
W : = W − λ v W W := W - \lambda v_W W:=W−λvW
b : = b − λ v b b := b - \lambda v_b b:=b−λvb
动量
振荡:
在优化和梯度下降的背景下,“振荡”是指参数更新(例如神经网络中的权重)重复超过损失函数的最小值的现象,导致参数的路径在损失曲面的斜率上来回振荡或曲折。更新不是平滑地收敛到最小值,而是不断地在其周围反弹,这可能会减慢收敛速度,使训练过程效率低下。
振荡示例:
想象一下,你正在试图将球滚动到碗的最深处,以找到表示损失函数最小值的最低点。如果碗的边缘很陡,球可能会在一侧迅速滚下来,获得过多的速度(由于陡峭的梯度),并超过底部向对面的一侧攀爬。然后,重力将其拉回,但它再次获得了太多的动量而超出范围,导致在底部周围来回振荡。
该插图描述了梯度下降过程中的两种情景,损失表面由同心椭圆表示,这些椭圆表示损失函数的水平。目标是到达由中心的红点表示的最低点,这是损失函数的全局最小值。
没有动量的SGD: 在左侧,标准SGD算法在没有动量的情况下采取的路径显示出蜿蜒曲折的模式。这是因为每一步仅基于局部梯度而采取,这可能导致过度校正,从而产生振荡。当损失表面具有陡峭的曲率时,这些调整是很大的,可能会导致更新超过最小值,从而导致在损失函数的斜坡上来回弹动。
带动量的SGD: 在右侧,动量的引入允许优化路径从先前步骤中累积方向性,这有助于平滑路径向最小值。动量项防止了优化器受到局部梯度变化的太大影响,从而导致较少的振荡和更直接的路径朝着目标前进。这通过更平滑和更稳定的曲线来展示,仍然朝着全局最小值前进,但途中的偏离较小。
关键的区别在于,动量有助于减弱振荡并提供更一致的行进方向,从而防止在没有动量的情况下看到的路径中出现的不稳定运动。这通常会导致更快的收敛速度和更有效的路径到达损失函数的最小值。
动量的例子:
让我们通过一个涉及球在陡坡上滚动的类比来可视化优化中动量的概念。
没有动量:
想象一下一个位于陡峭山顶的球,这个球代表我们的优化器。每次我们允许球滚下山一步,都代表着梯度下降的一次迭代。没有动量,球只是由于每一步上的重力作用于它而移动(这代表着我们损失函数在每个点的梯度)。当球滚下山时,它每一步只移动一小段距离,因为一旦坡度(梯度)变得平坦或者遇到上坡(损失函数增加),它就会停止。这种移动类似于基本的SGD,其中每次更新仅基于当前梯度,优化器可能会被卡在平坦区域或者因微小的上坡梯度而显著减慢,导致收敛速度慢。
带动量:
现在,想象同样的球,但这次它有能力保留来自先前滚动(步骤)的一些速度。这类似于在我们的优化器中添加一个动量项。当球开始滚下山时,它不仅因当前坡度(梯度)而增加速度,还保留了一些来自先前运动的速度。这些累积的速度帮助球在碰到平坦区域或轻微上坡时仍然保持移动,使其不太可能被卡住,并能更快地穿越地形。在穿越具有多个山丘和谷底(局部最小值)的复杂地形时,这特别有用,因为动量可以帮助球(优化器)摆脱浅谷并继续向最低点(全局最小值)前进。
速度
在梯度下降和深度学习优化算法的背景下,“速度”是与基于动量的方法(例如带动量的随机梯度下降)经常相关的一个术语。速度表示迭代中的累积梯度更新。它有助于平滑更新并提供更稳定和更快的收敛到损失函数的最小值。
下面是在带动量的随机梯度下降中速度是如何工作的:
在每一步中,带动量的随机梯度下降不直接使用当前梯度来更新模型参数,而是将当前梯度与上一步的速度结合起来。这种组合由一个动量系数控制,通常表示为 μ \mu μ。
速度的引入主要有两个方面的作用:
-
方向一致性: 通过累积梯度,它有助于保持更新的一致方向,这在损失函数表面不平坦或有陡峭坡度的情况下特别有益。
-
减震振荡: 当梯度方向快速变化时,它减少了振荡,这经常发生在损失函数陡峭的区域。这种减震效应使得更平滑地收敛到最小值。
本质上,像带动量的随机梯度下降中速度的概念允许优化器“记住”过去更新的方向和大小,从而使优化过程更稳定和高效。
注意: 对于减震振荡,让我们使用一个球的类比来说明优化算法中的动量项如何减少由于梯度方向快速变化而产生的振荡,特别是在损失函数的陡峭区域。
想象一下你在滑板公园玩耍,公园里有各种坡道和斜坡。现在,想象一下公园中的一个特定区域,那里有一条陡峭的斜坡通向一个平坦区域,然后立即又上了另一条陡峭的斜坡。如果你让球顺着第一个斜坡滚下去而没有任何控制,由于斜度很陡,它会获得很大的速度。当它到达平坦区域并开始爬上另一个斜坡时,它可能有足够的速度来上升然后下降第二个斜坡,来回往复,由于底部方向的快速变化而产生振荡。
现在,让我们给我们的球引入一个特殊的功能,类似于优化算法中的动量项。这一次,当你让球顺着第一个斜坡滚下去时,它有一种“记忆”,可以记录其以前的速度和方向。当它开始加速下坡时,这种“记忆”会略微抵制速度的突然增加,使球不太容易获得过多的速度。当球到达底部并开始爬上另一个斜坡时,它没有以前那么多的速度,所以它不会爬得那么高。动量的“记忆”帮助它快速调整速度和方向,平滑过渡斜坡之间,减少来回振荡。
在这个类比中,动量项充当了一个减震力,调节着球的速度和方向变化,防止它对陡峭的斜坡和突然的平坦区域反应过激。这导致了更加平稳和受控的运动,类似于优化算法中的动量项如何减少振荡,使收敛到损失函数最小值更加平滑。
代码实现
import numpy as np
class SGD:
def __init__(self, model, lr=0.1, momentum=0):
self.l = model.layers
self.L = len(model.layers)
self.lr = lr
self.mu = momentum
self.v_W = [np.zeros(self.l[i].W.shape, dtype="f") for i in range(self.L)]
self.v_b = [np.zeros(self.l[i].b.shape, dtype="f") for i in range(self.L)]
def step(self):
for i in range(self.L):
if self.mu == 0:
self.l[i].W -= self.lr * self.l[i].dLdW
self.l[i].b -= self.lr * self.l[i].dLdb
else:
self.v_W[i] = self.mu * self.v_W[i] + self.l[i].dLdW
self.v_b[i] = self.mu * self.v_b[i] + self.l[i].dLdb
self.l[i].W -= self.lr * self.v_W[i]
self.l[i].b -= self.lr * self.v_b[i]
测试
import numpy as np
class Linear:
def __init__(self, in_features, out_features, debug=False):
"""
Initialize the weights and biases with zeros
Checkout np.zeros function.
Read the writeup to identify the right shapes for all.
"""
self.W = np.zeros((out_features,in_features))
self.b = np.zeros((out_features,))
def forward(self, A):
"""
:param A: Input to the linear layer with shape (N, C0)
:return: Output Z of linear layer with shape (N, C1)
Read the writeup for implementation details
"""
self.A = A
self.N = A.shape[0]
self.Ones = np.ones((self.N, 1))
Z = self.A.dot(self.W.T) + self.Ones.dot(self.b.T)
return Z
def backward(self, dLdZ):
dLdA = dLdZ.dot(self.W)
self.dLdW = dLdZ.T.dot(self.A)
self.dLdb = dLdZ.T.dot(self.Ones)
return dLdA
class ReLU:
"""
On same lines as above:
Define 'forward' function
Define 'backward' function
Read the writeup for further details on ReLU.
"""
def forward(self, Z):
self.A = np.maximum(0, Z)
return self.A
def backward(self, dLdA):
dAdZ = np.where(self.A > 0, 1, 0)
dLdZ = dLdA * dAdZ
return dLdZ
class SGD:
def __init__(self, model, lr=0.1, momentum=0):
self.l = model.layers
self.L = len(model.layers)
self.lr = lr
self.mu = momentum
self.v_W = [np.zeros(self.l[i].W.shape, dtype="f") for i in range(self.L)]
self.v_b = [np.zeros(self.l[i].b.shape, dtype="f") for i in range(self.L)]
def step(self):
for i in range(self.L):
if self.mu == 0:
self.l[i].W -= self.lr * self.l[i].dLdW
self.l[i].b -= self.lr * self.l[i].dLdb
else:
self.v_W[i] = self.mu * self.v_W[i] + self.l[i].dLdW
self.v_b[i] = self.mu * self.v_b[i] + self.l[i].dLdb
self.l[i].W -= self.lr * self.v_W[i]
self.l[i].b -= self.lr * self.v_b[i]
"""
────────────────────────────────────────────────────────────────────────────────────
# SGD
────────────────────────────────────────────────────────────────────────────────────
"""
class PseudoModel:
def __init__(self):
self.layers = [Linear(3, 2),Linear(4, 2)]
self.f = [ReLU()]
def forward(self, A):
return NotImplemented
def backward(self):
return NotImplemented
# Create Example Model
pseudo_model = PseudoModel()
pseudo_model.layers[0].W = np.ones((2,3))
pseudo_model.layers[0].dLdW = np.ones((2,3)) / 10
pseudo_model.layers[0].b = np.ones((2,1))
pseudo_model.layers[0].dLdb = np.ones((2, 1)) / 10
pseudo_model.layers[1].W = np.ones((2, 4))
pseudo_model.layers[1].dLdW = np.ones((2, 4)) / 10
pseudo_model.layers[1].b = np.ones((2, 1))
pseudo_model.layers[1].dLdb = np.ones((2, 1)) / 10
print("\nInitialized Parameters:\n")
print("W11 =\n", pseudo_model.layers[0].W, "\n", sep="")
print("b11 =\n", pseudo_model.layers[0].b, "\n", sep="")
print("W12 =\n", pseudo_model.layers[1].W, "\n", sep="")
print("b12 =\n", pseudo_model.layers[1].b, "\n", sep="")
#Test Example Models
optimizer = SGD(pseudo_model, lr=0.9)
optimizer.step()
print("Parameters After SGD (Step=1)\n")
W_11 = pseudo_model.layers[0].W.copy()
b_11 = pseudo_model.layers[0].b.copy()
print("W11 =\n", W_11, "\n", sep="")
print("b11 =\n", b_11, "\n", sep="")
W_12 = pseudo_model.layers[1].W.copy()
b_12 = pseudo_model.layers[1].b.copy()
print("W12 =\n", W_12, "\n", sep="")
print("b12 =\n", b_12, "\n", sep="")
optimizer.step()
print("Parameters After SGD (Step=2)\n")
W_21 = pseudo_model.layers[0].W
b_21 = pseudo_model.layers[0].b
print("W21 =\n", W_21, "\n", sep="")
print("b21 =\n", b_21, "\n", sep="")
W_22 = pseudo_model.layers[1].W
b_22 = pseudo_model.layers[1].b
print("W22 =\n", W_22, "\n", sep="")
print("b22 =\n", b_22, "\n", sep="")
print("──────────────────────────────────────────")
print("SGD | SOLUTION OUTPUT")
print("──────────────────────────────────────────")
W_11_solution = np.array([
[0.91, 0.91, 0.91],
[0.91, 0.91, 0.91]], dtype="f")
b_11_solution = np.array([
[0.91],
[0.91]], dtype="f")
W_21_solution = np.array([
[0.82, 0.82, 0.82],
[0.82, 0.82, 0.82]], dtype="f")
b_21_solution = np.array([
[0.82],
[0.82]], dtype="f")
print("\nParameters After SGD (Step=1)\n")
print("W11 =\n", W_11_solution, "\n", sep="")
print("b11 =\n", b_11_solution, "\n", sep="")
print("Parameters After SGD (Step=2)\n")
print("W21 =\n", W_21_solution, "\n", sep="")
print("b21 =\n", b_21_solution, "\n", sep="")
print("\n──────────────────────────────────────────")
print("SGD | TEST RESULTS")
print("──────────────────────────────────────────")
print(" Pass?")
atol_threshold = 1e-4
TEST_sgd_W_21 = np.allclose(W_21.round(4), W_21_solution, atol=atol_threshold)
print("Test W (Step 2):", TEST_sgd_W_21)
TEST_sgd_b_21 = np.allclose(b_21.round(4), b_21_solution, atol=atol_threshold)
print("Test b (Step 2):", TEST_sgd_b_21)
"""
────────────────────────────────────────────────────────────────────────────────────
# SGD (With Momentum)
────────────────────────────────────────────────────────────────────────────────────
"""
class PseudoModel:
def __init__(self):
self.layers = [Linear(3, 2), Linear(4,2)]
self.f = [ReLU()]
def forward(self, A):
return NotImplemented
def backward(self):
return NotImplemented
# Create Example Model
pseudo_model = PseudoModel()
pseudo_model.layers[0].W = np.ones((2, 3))
pseudo_model.layers[0].dLdW = np.ones((2, 3)) / 10
pseudo_model.layers[0].b = np.ones((2, 1))
pseudo_model.layers[0].dLdb = np.ones((2, 1)) / 10
pseudo_model.layers[1].W = np.ones((2, 4))
pseudo_model.layers[1].dLdW = np.ones((2, 4)) / 10
pseudo_model.layers[1].b = np.ones((2, 1))
pseudo_model.layers[1].dLdb = np.ones((2, 1)) / 10
print("\nInitialized Parameters:\n")
print("W11 =\n", pseudo_model.layers[0].W, "\n", sep="")
print("b11 =\n", pseudo_model.layers[0].b, "\n", sep="")
print("W12 =\n", pseudo_model.layers[1].W, "\n", sep="")
print("b12 =\n", pseudo_model.layers[1].b, "\n", sep="")
# Test Example Models
optimizer = SGD(pseudo_model, lr=0.9, momentum=0.9)
print("optimizer.v_W =\n", optimizer.v_W)
print("optimizer.v_b =\n", optimizer.v_b)
print("optimizer.l =\n", optimizer.l)
print("optimizer.L =\n", optimizer.L)
optimizer.step()
print("Parameters After SGD (Step=1)\n")
W_11 = pseudo_model.layers[0].W.copy()
b_11 = pseudo_model.layers[0].b.copy()
print("W11 =\n", W_11, "\n", sep="")
print("b11 =\n", b_11, "\n", sep="")
W_12 = pseudo_model.layers[1].W.copy()
b_12 = pseudo_model.layers[1].b.copy()
print("W12 =\n", W_12, "\n", sep="")
print("b12 =\n", b_12, "\n", sep="")
optimizer.step()
print("Parameters After SGD (Step=2)\n")
W_21 = pseudo_model.layers[0].W
b_21 = pseudo_model.layers[0].b
print("W21 =\n", W_21, "\n", sep="")
print("b21 =\n", b_21, "\n", sep="")
W_22 = pseudo_model.layers[1].W
b_22 = pseudo_model.layers[1].b
print("W22 =\n", W_22, "\n", sep="")
print("b22 =\n", b_22, "\n", sep="")
print("──────────────────────────────────────────")
print("SGD with Momentum | SOLUTION OUTPUT")
print("──────────────────────────────────────────")
W_11_solution = np.array([
[0.91, 0.91, 0.91],
[0.91, 0.91, 0.91]], dtype="f")
b_11_solution = np.array([
[0.91],
[0.91]], dtype="f")
W_21_solution = np.array([
[0.739, 0.739, 0.739],
[0.739, 0.739, 0.739]], dtype="f")
b_21_solution = np.array([
[0.739],
[0.739]], dtype="f")
print("\nParameters After SGD (Step=1)\n")
print("W11 =\n", W_11_solution, "\n", sep="")
print("b11 =\n", b_11_solution, "\n", sep="")
print("Parameters After SGD (Step=2)\n")
print("W21 =\n", W_21_solution, "\n", sep="")
print("b21 =\n", b_21_solution, "\n", sep="")
print("\n──────────────────────────────────────────")
print("SGD with Momentum| TEST RESULTS")
print("──────────────────────────────────────────")
print(" Pass?")
TEST_sgd_W_m_21 = np.allclose(W_21.round(4), W_21_solution, atol=atol_threshold)
print("Test W (Step 2):", TEST_sgd_W_m_21)
TEST_sgd_b_m_21 = np.allclose(b_21.round(4), b_21_solution, atol=atol_threshold)
print("Test b (Step 2):", TEST_sgd_b_m_21)
测试结果
Initialized Parameters:
W11 =
[[1. 1. 1.]
[1. 1. 1.]]
b11 =
[[1.]
[1.]]
W12 =
[[1. 1. 1. 1.]
[1. 1. 1. 1.]]
b12 =
[[1.]
[1.]]
Parameters After SGD (Step=1)
W11 =
[[0.91 0.91 0.91]
[0.91 0.91 0.91]]
b11 =
[[0.91]
[0.91]]
W12 =
[[0.91 0.91 0.91 0.91]
[0.91 0.91 0.91 0.91]]
b12 =
[[0.91]
[0.91]]
Parameters After SGD (Step=2)
W21 =
[[0.82 0.82 0.82]
[0.82 0.82 0.82]]
b21 =
[[0.82]
[0.82]]
W22 =
[[0.82 0.82 0.82 0.82]
[0.82 0.82 0.82 0.82]]
b22 =
[[0.82]
[0.82]]
──────────────────────────────────────────
SGD | SOLUTION OUTPUT
──────────────────────────────────────────
Parameters After SGD (Step=1)
W11 =
[[0.91 0.91 0.91]
[0.91 0.91 0.91]]
b11 =
[[0.91]
[0.91]]
Parameters After SGD (Step=2)
W21 =
[[0.82 0.82 0.82]
[0.82 0.82 0.82]]
b21 =
[[0.82]
[0.82]]
──────────────────────────────────────────
SGD | TEST RESULTS
──────────────────────────────────────────
Pass?
Test W (Step 2): True
Test b (Step 2): True
Initialized Parameters:
W11 =
[[1. 1. 1.]
[1. 1. 1.]]
b11 =
[[1.]
[1.]]
W12 =
[[1. 1. 1. 1.]
[1. 1. 1. 1.]]
b12 =
[[1.]
[1.]]
optimizer.v_W =
[array([[0., 0., 0.],
[0., 0., 0.]], dtype=float32), array([[0., 0., 0., 0.],
[0., 0., 0., 0.]], dtype=float32)]
optimizer.v_b =
[array([[0.],
[0.]], dtype=float32), array([[0.],
[0.]], dtype=float32)]
optimizer.l =
[<__main__.Linear object at 0x7b96706546d0>, <__main__.Linear object at 0x7b965b14c750>]
optimizer.L =
2
Parameters After SGD (Step=1)
W11 =
[[0.91 0.91 0.91]
[0.91 0.91 0.91]]
b11 =
[[0.91]
[0.91]]
W12 =
[[0.91 0.91 0.91 0.91]
[0.91 0.91 0.91 0.91]]
b12 =
[[0.91]
[0.91]]
Parameters After SGD (Step=2)
W21 =
[[0.739 0.739 0.739]
[0.739 0.739 0.739]]
b21 =
[[0.739]
[0.739]]
W22 =
[[0.739 0.739 0.739 0.739]
[0.739 0.739 0.739 0.739]]
b22 =
[[0.739]
[0.739]]
──────────────────────────────────────────
SGD with Momentum | SOLUTION OUTPUT
──────────────────────────────────────────
Parameters After SGD (Step=1)
W11 =
[[0.91 0.91 0.91]
[0.91 0.91 0.91]]
b11 =
[[0.91]
[0.91]]
Parameters After SGD (Step=2)
W21 =
[[0.739 0.739 0.739]
[0.739 0.739 0.739]]
b21 =
[[0.739]
[0.739]]
──────────────────────────────────────────
SGD with Momentum| TEST RESULTS
──────────────────────────────────────────
Pass?
Test W (Step 2): True
Test b (Step 2): True
>
注意: layers属性的设计意图是仅包含具有可训练参数(例如线性层中的权重和偏置)的层。通常,像ReLU这样的激活函数在训练过程中不需要更新参数,因为它们是纯函数变换。
Python中的动态属性分配
这说明了我们为什么可以这样写:
pseudo_model.layers[0].dLdW = np.ones((2,3)) / 10
Python中的动态属性赋值允许您在运行时在对象上设置属性,这意味着您可以在执行过程中动态地向对象添加属性,即使这些属性在类定义中未定义。这个特性提供了灵活性,但应谨慎使用,以保持代码的可读性并避免意外行为。
示例
class Person:
def __init__(self, name):
self.name = name
# 创建Person类的一个实例
bob = Person("Bob")
# 动态地为实例'bob'分配一个新属性'age'
bob.age = 30
# 动态地为同一实例分配另一个新属性'occupation'
bob.occupation = "软件开发工程师"
# 访问动态分配的属性
print(f"{bob.name}今年 {bob.age} 岁,职业是 {bob.occupation}。")
参考:
- CMU_11785_深度学习入门
- https://paperswithcode.com/method/sgd-with-momentum