И снова здравствуйте,
Сегодняшняя тема… ну, такая же, как и предыдущая. Q Learning и Deep Q Networks. В прошлый раз мы объяснили, что такое Q Learning и как использовать уравнение Беллмана для нахождения Q-значений и, как следствие, оптимальной политики. Позже мы представили Deep Q Networks и то, как вместо того, чтобы вычислять все значения Q-таблицы, мы позволили Deep Neural Network научиться их аппроксимировать.
Сети Deep Q принимают в качестве входных данных состояние среды и выводят значение Q для каждого возможного действия. Максимальное значение Q определяет, какое действие выполнит агент. Обучение агентов использует в качестве потери Ошибка ТД, который представляет собой разницу между максимально возможным значением для следующего состояния и текущим прогнозом значения Q (как предлагает уравнение Беллмана). В результате нам удается аппроксимировать Q-таблицы с помощью нейронной сети.
Все идет нормально. Но, конечно, возникает несколько проблем. Просто так продвигаются научные исследования. И, конечно же, мы придумали несколько отличных решений.
Перемещение Q-целей
Первая проблема заключается в том, что называется движущимися Q-мишенями. Как мы видели, первым компонентом ошибки TD (TD означает Temporal Difference) является Q-Target, и он рассчитывается как немедленное вознаграждение плюс дисконтированное максимальное Q-значение для следующего состояния. Когда мы обучаем нашего агента, мы обновляем веса в соответствии с ошибкой TD. Но одни и те же веса применяются как к целевому, так и к прогнозируемому значению. Вы видите проблему?
Мы перемещаем вывод ближе к цели, но мы также перемещаем цель. Итак, мы в конечном итоге преследуем цель, и мы получаем сильно колеблющийся тренировочный процесс. Было бы неплохо оставить цель фиксированной, пока мы обучаем сеть. Что ж, DeepMind сделал именно это.
Вместо того, чтобы использовать одну нейронную сеть, он использует две. Да, вы не ослышались! (вроде бы одного уже не хватило).
Одна в качестве основной сети Deep Q, а вторая (называется Целевая сеть) для исключительного и периодического обновления весов цели. Эта техника называется Фиксированные Q-цели. На самом деле веса фиксированы для большей части тренировки и обновляются только время от времени.
class DQNAgent:
def __init__(self, state_size, action_size):
self.model = self._build_model()
self.target_model = self._build_model()
self.update_target_model()
def update_target_model(self):
self.target_model.set_weights(self.model.get_weights())
Предвзятость максимизации
Предвзятость максимизации — это склонность Deep Q Networks переоценивать функции ценности и действия-ценности (Q). Почему это происходит? Подумайте, что если по какой-то причине сеть завышает значение Q для действия, это действие будет выбрано в качестве действия для перехода к следующему шагу, и такое же завышенное значение будет использоваться в качестве целевого значения. Другими словами, невозможно оценить, действительно ли действие с максимальным значением является лучшим. Как это решить? Ответ очень интересный метод и называется:
Двойная глубокая сеть Q
Чтобы устранить предвзятость максимизации, мы используем две сети Deep Q.
-
С одной стороны, DQN отвечает за выбор следующего действия (с максимальным значением) как всегда.
-
С другой стороны, целевая сеть отвечает за оценка
этого действия.
Хитрость заключается в том, что целевое значение автоматически создается не максимальным значением Q, а целевой сетью. Другими словами, мы вызываем целевую сеть для вычисления целевого значения Q для выполнения этого действия в следующем состоянии. И как побочный эффект, мы также решаем проблему с движущейся мишенью. Аккуратно, верно? Две птицы с одним камнем. Отделив выбор действия от генерации целевого значения Q, мы можем существенно уменьшить переоценку и тренироваться быстрее и надежнее.
def train_model(self):
if len(self.memory) < self.train_start:
return
batch_size = min(self.batch_size, len(self.memory))
mini_batch = random.sample(self.memory, batch_size)
update_input = np.zeros((batch_size, self.state_size))
update_target = np.zeros((batch_size, self.state_size))
action, reward, done = (), (), ()
for i in range(batch_size):
update_input(i) = mini_batch(i)(0)
action.append(mini_batch(i)(1))
reward.append(mini_batch(i)(2))
update_target(i) = mini_batch(i)(3)
done.append(mini_batch(i)(4))
target = self.model.predict(update_input)
target_next = self.model.predict(update_target)
target_val = self.target_model.predict(update_target)
for i in range(self.batch_size):
if done(i):
target(i)(action(i)) = reward(i)
else:
a = np.argmax(target_next(i))
target(i)(action(i)) = reward(i) + self.discount_factor * (target_val(i)(a))
self.model.fit(update_input, target, batch_size=self.batch_size,epochs=1, verbose=0)
Думаешь, это все? Извините, что подвел вас. Мы собираемся пойти еще дальше. Что теперь? Собираетесь ли вы добавить третью нейронную сеть? Ха-ха!!
Ну типа. Кто смеется сейчас?
Дуэли сетей Deep Q
Давайте сначала обновим основу Q Learning. Значения Q соответствуют метрике того, насколько хорошо действие для определенного состояния, верно? Вот почему это функция действия-ценности. Метрика — это не что иное, как ожидаемая отдача этого действия от состояния. Фактически значения Q можно разбить на две части: функцию ценности состояния V(s) и значение преимущества A(s, a). И да, мы просто вводим еще одну функцию:
Функция преимущества фиксирует, насколько лучше действие по сравнению с другими в данном состоянии, в то время как, как мы знаем, функция ценности фиксирует, насколько хорошо быть в этом состоянии. Вся идея Dueling Q Networks основана на представление функции Q в виде суммы ценности и функции преимущества. У нас просто есть две сети для изучения каждой части суммы, а затем мы объединяем их результаты.
Дуэль сетевых архитектур для глубокого обучения с подкреплением
Зарабатываем ли мы этим что-то? Конечно, мы делаем. Теперь агенты могут оценивать состояние, не заботясь о последствиях каждого действия из этого состояния. Это означает, что функции, определяющие, является ли состояние хорошим или нет, не обязательно совпадают с функциями, оценивающими действие. И, возможно, ему вообще не нужно заботиться о действиях. Нередки случаи, когда действия из состояния никак не влияют на окружающую среду. Так зачем их учитывать?
* Краткое примечание: если вы внимательно посмотрите на изображение, вы увидите, что для объединения выходных данных двух сетей мы не просто добавляем их. Причиной этого является проблема идентификации. Если у нас есть Q, мы не можем найти V и A. Таким образом, мы не можем выполнить обратное распространение. Вместо этого мы предпочитаем использовать среднее преимущество в качестве исходного уровня (вычитаемый термин).
def build_model(self):
input = Input(shape=self.state_size)
shared = Conv2D(32, (8, 8), strides=(4, 4), activation='relu')(input)
shared = Conv2D(64, (4, 4), strides=(2, 2), activation='relu')(shared)
shared = Conv2D(64, (3, 3), strides=(1, 1), activation='relu')(shared)
flatten = Flatten()(shared)
advantage_fc = Dense(512, activation='relu')(flatten)
advantage = Dense(self.action_size)(advantage_fc)
advantage = Lambda(lambda a: a(:, :) - K.mean(a(:, :), keepdims=True),
output_shape=(self.action_size,))(advantage)
value_fc = Dense(512, activation='relu')(flatten)
value = Dense(1)(value_fc)
value = Lambda(lambda s: K.expand_dims(s(:, 0), -1),
output_shape=(self.action_size,))(value)
q_value = merge((value, advantage), mode='sum')
model = Model(inputs=input, outputs=q_value)
model.summary()
return model
И последнее, но не менее важное: у нас есть еще одна проблема для обсуждения, и она связана с оптимизацией воспроизведения опыта.
Повтор приоритетного опыта
Вы помните, что воспроизведение опыта — это когда мы время от времени проигрываем агенту случайные прошлые переживания, чтобы он не забыл их? Если вы этого не сделаете, теперь вы это сделаете. Но некоторые переживания могут быть более значительными, чем другие. В результате мы должны расставить приоритеты для их воспроизведения. Для этого вместо случайной выборки (из равномерного распределения) мы используем выборку с приоритетом. В качестве приоритета мы определяем величину ошибки TD (плюс некоторая константа, чтобы избежать нулевой вероятности выбора опыта).
Центральная идея: Чем выше ошибка между прогнозом и целью, тем актуальнее узнать об этом.
И чтобы гарантировать, что мы не всегда будем воспроизводить один и тот же опыт, мы добавляем некоторую стохастичность, и все готово. Кроме того, для снижения сложности мы сохраняем опыт в бинарном дереве под названием SumTree.
from SumTree import SumTree
class PER:
e = 0.01
a = 0.6
def __init__(self, capacity):
self.tree = SumTree(capacity)
def _getPriority(self, error):
return (error + self.e) ** self.a
def add(self, error, sample):
p = self._getPriority(error)
self.tree.add(p, sample)
def sample(self, n):
batch = ()
segment = self.tree.total() / n
for i in range(n):
a = segment * i
b = segment * (i + 1)
s = random.uniform(a, b)
(idx, p, data) = self.tree.get(s)
batch.append((idx, data))
return batch
def update(self, idx, error):
p = self._getPriority(error)
self.tree.update(idx, p)
Это было много. Много новой информации, много новых улучшений. Но только подумайте, что мы можем объединить их все вместе. И мы делаем это.
Я попытался дать краткий обзор самых важных недавних усилий в этой области, опираясь на некоторые интуитивные мысли и немного математики. Вот почему обучение с подкреплением так важно для изучения. Существует так много потенциала и так много возможностей для улучшений, что вы просто не можете игнорировать тот факт, что он станет крупным игроком в области ИИ (если это уже не так). Но именно поэтому так трудно учиться и идти в ногу с этим.
Если вам нужно погрузиться глубже, я настоятельно рекомендую Продвинутый ИИ: курс глубокого обучения на Python на Удеми.
В следующий раз мы представим градиенты политик и алгоритм REINFORCE.
Нам остается только учиться…
* Раскрытие информации: Обратите внимание, что некоторые из приведенных выше ссылок могут быть партнерскими ссылками, и без дополнительной оплаты для вас мы будем получать комиссию, если вы решите совершить покупку после перехода по ссылке.