12.8. RMSProp¶
Одной из ключевых проблем, рассмотренных в Section 12.7, является то, что скорость обучения уменьшается по заранее заданному графику, что по сути эквивалентно \(\mathcal{O}(t^{-\frac{1}{2}})\). Хотя такой подход обычно подходит для выпуклых задач, он может оказаться неоптимальным для невыпуклых, с которыми мы сталкиваемся в глубоком обучении. Тем не менее, координатная адаптивность Adagrad крайне желательна в качестве предобуславливателя.
Tieleman and Hinton (2012) предложил алгоритм RMSProp в качестве простого решения, позволяющего отделить планирование скорости обучения от адаптивных к координатам скоростей обучения. Проблема заключается в том, что Adagrad накапливает квадраты градиента \(\mathbf{g}_t\) в векторе состояния \(\mathbf{s}_t = \mathbf{s}_{t-1} + \mathbf{g}_t^2\). В результате \(\mathbf{s}_t\) продолжает неограниченно расти из-за отсутствия нормализации, увеличиваясь практически линейно по мере сходимости алгоритма.
Одним из способов решения этой проблемы было бы использование \(\mathbf{s}_t / t\). Для разумных распределений \(\mathbf{g}_t\) это обеспечит сходимость. К сожалению, может потребоваться очень много времени, прежде чем предельное поведение начнет оказывать влияние, поскольку процедура запоминает всю траекторию значений. Альтернативой является использование затухающего среднего так же, как мы использовали его в методе импульса, то есть \(\mathbf{s}_t \leftarrow \gamma \mathbf{s}_{t-1} + (1-\gamma) \mathbf{g}_t^2\) для некоторого параметра \(\gamma > 0\). Сохранение всех остальных частей без изменений дает алгоритм RMSProp.
12.8.1. Алгоритм¶
Давайте подробно запишем уравнения.
Константа \(\epsilon > 0\) обычно устанавливается равной \(10^{-6}\), чтобы избежать деления на ноль или слишком больших шагов. Благодаря этому разложению мы теперь можем независимо управлять скоростью обучения \(\eta\) от масштабирования, применяемого для каждой координаты. С точки зрения затухающих средних мы можем применить те же рассуждения, что и ранее в случае метода импульса. Раскрытие определения \(\mathbf{s}_t\) даёт
Как и ранее в Section 12.6, мы используем \(1 + \gamma + \gamma^2 + \ldots, = \frac{1}{1-\gamma}\). Следовательно, сумма весов нормируется до \(1\) при времени полураспада наблюдения, равном \(\gamma^{-1}\). Визуализируем веса за последние 40 временных шагов для различных значений \(\gamma\).
import math
import torch
from d2l import torch as d2l
d2l.set_figsize()
gammas = [0.95, 0.9, 0.8, 0.7]
for gamma in gammas:
x = torch.arange(40).detach().numpy()
d2l.plt.plot(x, (1-gamma) * gamma ** x, label=f'gamma = {gamma:.2f}')
d2l.plt.xlabel('time');
12.8.2. Реализация с нуля¶
Как и прежде, мы используем квадратичную функцию \(f(\mathbf{x})=0.1x_1^2+2x_2^2\), чтобы наблюдать за траекторией RMSProp. Напомним, что в Section 12.7 при использовании Adagrad со скоростью обучения 0.4 переменные двигались очень медленно на поздних этапах алгоритма, поскольку скорость обучения уменьшалась слишком быстро. Поскольку \(\eta\) контролируется отдельно, этого не происходит в RMSProp.
def rmsprop_2d(x1, x2, s1, s2):
g1, g2, eps = 0.2 * x1, 4 * x2, 1e-6
s1 = gamma * s1 + (1 - gamma) * g1 ** 2
s2 = gamma * s2 + (1 - gamma) * g2 ** 2
x1 -= eta / math.sqrt(s1 + eps) * g1
x2 -= eta / math.sqrt(s2 + eps) * g2
return x1, x2, s1, s2
def f_2d(x1, x2):
return 0.1 * x1 ** 2 + 2 * x2 ** 2
eta, gamma = 0.4, 0.9
d2l.show_trace_2d(f_2d, d2l.train_2d(rmsprop_2d))
Далее мы реализуем RMSProp для использования в глубокой сети. Это делается столь же просто.
def init_rmsprop_states(feature_dim):
s_w = torch.zeros((feature_dim, 1))
s_b = torch.zeros(1)
return (s_w, s_b)
def rmsprop(params, states, hyperparams):
gamma, eps = hyperparams['gamma'], 1e-6
for p, s in zip(params, states):
with torch.no_grad():
s[:] = gamma * s + (1 - gamma) * torch.square(p.grad)
p[:] -= hyperparams['lr'] * p.grad / torch.sqrt(s + eps)
p.grad.data.zero_()
Мы устанавливаем начальную скорость обучения равной 0,01, а весовой коэффициент \(\gamma\) равным 0,9. То есть \(\mathbf{s}\) усредняет квадрат градиента по прошлым \(1/(1-\gamma) = 10\) наблюдениям.
data_iter, feature_dim = d2l.get_data_ch11(batch_size=10)
d2l.train_ch11(rmsprop, init_rmsprop_states(feature_dim),
{'lr': 0.01, 'gamma': 0.9}, data_iter, feature_dim);
12.8.3. Краткая реализация¶
Поскольку RMSProp является довольно популярным алгоритмом, он также
доступен в объекте Trainer. Всё, что нам нужно сделать, — это
создать его объект, используя алгоритм с именем rmsprop, присвоив
\(\gamma\) параметру gamma1.
trainer = torch.optim.RMSprop
d2l.train_concise_ch11(trainer, {'lr': 0.01, 'alpha': 0.9},
data_iter)
12.8.4. Итог¶
RMSProp очень похож на Adagrad в том смысле, что оба алгоритма используют квадрат градиента для масштабирования коэффициентов.
RMSProp, как и метод импульса, использует затухающее усреднение. Однако в RMSProp эта техника применяется для настройки поэлементного преусловителя.
На практике скорость обучения необходимо настраивать экспериментатору.
Коэффициент \(\gamma\) определяет длительность истории при настройке масштаба для каждой координаты.
12.8.5. Упражнения¶
Что происходит на практике, если мы установим \(\gamma = 1\)? Почему?
Поверните задачу оптимизации, чтобы минимизировать \(f(\mathbf{x}) = 0.1 (x_1 + x_2)^2 + 2 (x_1 - x_2)^2\). Что произойдёт со сходимостью?
Попробуйте исследовать, как ведет себя алгоритм RMSProp на реальной задаче машинного обучения, например, при обучении на наборе данных Fashion-MNIST. Экспериментируйте с различными стратегиями настройки скорости обучения.
Хотели бы вы корректировать \(\gamma\) по мере продвижения процесса оптимизации? Насколько чувствителен RMSProp к этому?