文章目录
简单概括参数更新:
1、更新方向不是简单地取为梯度
2、学习速率不是简单地取为常值
都是使用局部梯度delta来更新参数:
我们一般使用修正后的delta:
优化器
Vanilla Update:
和最普通的梯度下降法别无二致:
Vanilla 代码实现:
class MBGD(Optimizer):
def run(self, i, dw):
return self.lr * dw
Momentum Update:
其中梯度Δwt的物理意义即为“动力”、vt的物理意义即为第 t 步迭代中参数的“行进速度”、ρ的物理意义即为惯性,它描述了上一步的行进速度会在多大程度上影响到这一步的行进速度。易知当ρ=0时、Momentum Update等价于 Vanilla Update。
Momentum 代码实现:
class Momentum(Optimizer, metaclass=TimingMeta):
"""
初始化结构(Momentum Update版本)
self._momentum:记录“惯性”的属性
self._step:每一步迭代后“惯性”的增量
self._floor、self._ceiling:“惯性”的最小、最大值
self._cache:对于Momentum Update而言、该属性记录的就是“行进速度”
self._is_nesterov:处理Nesterov Momentum Update的属性,这里暂时按下不表
"""
def __init__(self, lr=0.01, cache=None, epoch=100, floor=0.5, ceiling=0.999):
Optimizer.__init__(self, lr, cache)
self._momentum = floor
self._step = (ceiling - floor) / epoch
self._floor, self._ceiling = floor, ceiling
self._is_nesterov = False
def run(self, i, dw):
dw *= self.lr
velocity = self._cache
velocity[i] *= self._momentum
velocity[i] += dw
return velocity[i]
def update(self):
if self._momentum < self._ceiling:
self._momentum += self._step
Nesterov Momentum Update:
Nesterov Momentum 代码实现:
class NAG(Momentum):
def __init__(self, lr=0.01, cache=None, epoch=100, floor=0.5, ceiling=0.999):
Momentum.__init__(self, lr, cache, epoch, floor, ceiling)
self._is_nesterov = True
def run(self, i, dw):
dw *= self.lr
velocity = self._cache
velocity[i] *= self._momentum
velocity[i] += dw
# 如果不是Nesterov Momentum Update、可以直接把当成更新步伐
if not self._is_nesterov:
return velocity[i]
# 否则、调用公式来计算更新步伐
return self._momentum * velocity[i] + dw
RMSProp Update:
RMSProp 方法与 Momentum 系的方法最根本的不同在于:Momentum 系算法是通过搜索更优的更新方向来进行优化、而 RMSProp 则是通过实时调整学习速率来进行优化。
如果徘徊回了原点自然需要奋发图强地开辟新天地、如果已经走了很远自然应该谨小慎微。
NRMSProp 代码实现:
class RMSProp(Optimizer):
"""
初始化结构(RMSProp版本)
self.decay_rate:记录的属性,一般会取0.9、0.99或0.999
self.eps:算法的平滑项、用于增强算法稳定性,通常取中的某个数
self._cache:对于RMSProp而言、该属性记录的就是中间变量
"""
def __init__(self, lr=0.01, cache=None, decay_rate=0.9, eps=1e-8):
Optimizer.__init__(self, lr, cache)
self.decay_rate, self.eps = decay_rate, eps
def run(self, i, dw):
self._cache[i] = self._cache[i] * self.decay_rate + (1 - self.decay_rate) * dw ** 2
return self.lr * dw / (np.sqrt(self._cache[i] + self.eps))
Adam Update:
从直观上来说、Adam 算法很像是 Momentum 系算法和 RMSProp 算法的结合(中间变量Δ的相关计算类似于 Momentum 系算法对更新方向的选取、中间变量∇的相关计算则类似于 RMSProp 算法对学习速率的调整)。
Adam 代码实现:
class Adam(Optimizer):
"""
初始化结构(Adam版本)
self.beta1、self.beta2:记录、的属性,一般会取、
self.eps:意义与RMSProp中的eps一致、常取
self._cache:对于Adam而言、该属性记录的就是中间变量和中间变量
"""
def __init__(self, lr=0.01, cache=None, beta1=0.9, beta2=0.999, eps=1e-8):
Optimizer.__init__(self, lr, cache)
self.beta1, self.beta2, self.eps = beta1, beta2, eps
def feed_variables(self, variables):
self._cache = [
[np.zeros(var.shape) for var in variables],
[np.zeros(var.shape) for var in variables],
]
def run(self, i, dw):
self._cache[0][i] = self._cache[0][i] * self.beta1 + (1 - self.beta1) * dw
self._cache[1][i] = self._cache[1][i] * self.beta2 + (1 - self.beta2) * (dw ** 2)
return self.lr * self._cache[0][i] / (np.sqrt(self._cache[1][i] + self.eps))
优化器工厂
我们可以实现一个简单的工厂来“生产”这些优化器:
class OptFactory:
# 将所有能用的优化器存进一个字典
available_optimizers = {
"MBGD": MBGD,
"Momentum": Momentum, "NAG": NAG,
"RMSProp": RMSProp, "Adam": Adam,
}
# 定义一个能通过优化器名字来获取优化器的方法
def get_optimizer_by_name(self, name, variables, lr, epoch):
try:
_optimizer = self.available_optimizers[name](lr)
if variables is not None:
_optimizer.feed_variables(variables)
if epoch is not None and isinstance(_optimizer, Momentum):
_optimizer.epoch = epoch
return _optimizer