Перейти к содержимому
ПереводыHugging Face

Deep RL Course, юнит 2, практика: Q-learning на FrozenLake и Taxi

Перевод практического занятия второго юнита курса Hugging Face: пишем агента Q-learning с нуля на NumPy, обучаем его на FrozenLake-v1 и Taxi, оцениваем, публикуем на Hub и загружаем чужие Q-таблицы.

Это перевод практического занятия юнита 2 курса Hugging Face Deep RL Course (ноутбук notebooks/unit2/unit2.ipynb). Теория — на странице «Юнит 2: Q-learning», карта курса — в путеводителе.

Мы изучили алгоритм Q-learning — теперь реализуем его с нуля и обучим агента Q-learning в двух средах:

  1. Frozen-Lake-v1 (нескользкая и скользкая версии) ☃️: агенту нужно дойти от старта (S) до цели (G), ступая только по льду (F) и обходя полыньи (H).
  2. Автономное такси 🚖: агенту нужно научиться ездить по городу, чтобы довозить пассажиров из точки A в точку B.

Благодаря лидерборду вы сможете сравнить результаты с однокурсниками и обменяться приёмами, улучшающими счёт агента. Кто выиграет состязание юнита 2?

Чтобы это занятие засчиталось для сертификата, нужно выложить обученную модель для Taxi на Hub и получить результат ≥ 4,5. Чтобы узнать результат, найдите свою модель на лидерборде: результат = средняя награда − стандартное отклонение награды. Прогресс можно проверить здесь.

Чтобы начать, нажмите кнопку «Open In Colab»: ноутбук unit2.ipynb в Colab.

Мы настоятельно рекомендуем выполнять практические задания в Google Colab, а не на личном компьютере. С Colab вы сосредоточитесь на учёбе и экспериментах и не будете отвлекаться на техническую настройку окружения.

В этом ноутбуке вы с нуля напишете своего первого агента обучения с подкреплением, который играет во FrozenLake ❄️ с помощью Q-learning, поделитесь им с сообществом и поэкспериментируете с разными настройками.

⬇️ Вот пример того, чего вы добьётесь всего за пару минут (в оригинале — анимация). ⬇️

🎮 Среды: FrozenLake-v1, Taxi-v3.

📚 Библиотеки RL: Python и NumPy, Gymnasium.

Мы постоянно улучшаем наши руководства, поэтому, если найдёте проблемы в этом ноутбуке, откройте issue в репозитории на GitHub.

К концу ноутбука вы сможете:

  • пользоваться Gymnasium — библиотекой сред;
  • написать агента Q-learning с нуля;
  • выложить обученного агента и код на Hub с красивым видео игры и оценкой качества 🔥.

В этом бесплатном курсе вы:

  • 📖 изучите глубокое обучение с подкреплением в теории и на практике;
  • 🧑‍💻 научитесь пользоваться известными библиотеками Deep RL: Stable Baselines3, RL Baselines3 Zoo, CleanRL и Sample Factory 2.0;
  • 🤖 обучите агентов в необычных средах.

И многое другое — смотрите 📚 программу курса. Лучший способ оставаться на связи — присоединиться к нашему Discord-серверу: discord.gg/ydHrjt3WP5.

🔲 📚 Изучить Q-learning по юниту 2 🤗 (перевод).

Q-learning — это алгоритм RL, который:

  • обучает Q-функцию — функцию ценности действия, которая во внутренней памяти хранится как Q-таблица, содержащая ценности всех пар «состояние — действие»;
  • по состоянию и действию ищет соответствующее значение в Q-таблице;
  • когда обучение закончено, у нас есть оптимальная Q-функция, а значит, оптимальная Q-таблица;
  • а если есть оптимальная Q-функция, есть и оптимальная политика: для каждого состояния мы знаем лучшее действие.

Но поначалу Q-таблица бесполезна: в ней произвольные значения для каждой пары «состояние — действие» (чаще всего Q-таблицу заполняют нулями). По мере того как мы исследуем среду и обновляем Q-таблицу, её приближения становятся всё лучше.

В оригинале здесь картинка с псевдокодом Q-learning — тем же, что разобран по шагам 1–4 в теории юнита 2: инициализировать Q-таблицу; выбрать действие ε-жадно; выполнить действие, получить награду Rt+1R_{t+1} и состояние St+1S_{t+1}; обновить Q(St,At)Q(S_t, A_t):

Q(St,At)←Q(St,At)+α[Rt+1+γmax⁡aQ(St+1,a)−Q(St,At)]Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha\left[R_{t+1} + \gamma \max_a Q(S_{t+1}, a) - Q(S_t, A_t)\right]

Напишем наш первый алгоритм обучения с подкреплением 🚀

Заголовок раздела «Напишем наш первый алгоритм обучения с подкреплением 🚀»

Устанавливаем зависимости и создаём виртуальный дисплей 🔽

Заголовок раздела «Устанавливаем зависимости и создаём виртуальный дисплей 🔽»

В ноутбуке нам понадобится записать видео игры. В Colab для этого нужен виртуальный экран, чтобы отрисовывать среду (и записывать кадры). Следующая ячейка ставит библиотеки, создаёт и запускает виртуальный экран 🖥

Ставим несколько пакетов:

  • gymnasium — содержит среды FrozenLake-v1 ⛄ и Taxi-v3 🚕;
  • pygame — для отрисовки FrozenLake-v1 и Taxi-v3;
  • numpy — для работы с Q-таблицей.

Hugging Face Hub 🤗 — центральное место, где любой может делиться моделями и датасетами и искать их. В нём есть версии, метрики, визуализации и другие возможности для совместной работы. Все модели глубокого RL на Hub, использующие Q-learning, — здесь.

Окно терминала
pip install -r https://raw.githubusercontent.com/huggingface/deep-rl-class/main/notebooks/unit2/requirements-unit2.txt
Окно терминала
sudo apt-get update
sudo apt-get install -y python3-opengl
apt install ffmpeg xvfb
pip3 install pyvirtualdisplay

Чтобы новые библиотеки точно подхватились, иногда нужно перезапустить среду выполнения ноутбука. Следующая ячейка намеренно «роняет» среду выполнения: переподключитесь и продолжайте с этого места. Благодаря этому трюку виртуальный экран заработает.

import os
os.kill(os.getpid(), 9)
# Виртуальный дисплей
from pyvirtualdisplay import Display
virtual_display = Display(visible=0, size=(1400, 900))
virtual_display.start()

Кроме установленных библиотек, используем:

  • random — для случайных чисел (пригодится для ε-жадной политики);
  • imageio — для записи видео игры.
import numpy as np
import gymnasium as gym
import random
import imageio
import os
import tqdm
import pickle5 as pickle
from tqdm.notebook import tqdm

Теперь всё готово, чтобы написать алгоритм Q-learning 🔥

Создаём среду FrozenLake ⛄ и разбираемся в ней

Заголовок раздела «Создаём среду FrozenLake ⛄ и разбираемся в ней»

💡 Хорошая привычка, когда начинаете работать со средой, — заглянуть в её документацию.

Мы обучим агента Q-learning дойти от старта (S) до цели (G), ступая только по льду (F) и обходя полыньи (H).

Размер среды бывает двух видов:

  • map_name="4x4" — сетка 4×4;
  • map_name="8x8" — сетка 8×8.

У среды два режима:

  • is_slippery=False: агент всегда идёт в выбранном направлении, потому что лёд не скользкий (детерминированная среда);
  • is_slippery=True: агент может пойти не туда, куда хотел, потому что лёд скользкий (стохастическая среда).

Пока возьмём простой вариант: карта 4×4 без скольжения. Добавим параметр render_mode, который задаёт, как отрисовывать среду. Поскольку в конце мы хотим записать видео, нужно задать render_mode="rgb_array".

Как сказано в документации, «rgb_array» возвращает один кадр — текущее состояние среды. Кадр — массив np.ndarray формы (x, y, 3) со значениями RGB для картинки x на y пикселей.

# Создайте среду FrozenLake-v1 с картой 4x4, без скольжения и с render_mode="rgb_array"
env = gym.make() # TODO: подставьте правильные параметры
env = gym.make("FrozenLake-v1", map_name="4x4", is_slippery=False, render_mode="rgb_array")

Можно создать свою карту, например так:

desc=["SFFF", "FHFH", "FFFH", "HFFG"]
gym.make('FrozenLake-v1', desc=desc, is_slippery=True)

но пока будем пользоваться стандартной средой.

# Среду мы создали через gym.make("<название_среды>")
print("_____OBSERVATION SPACE_____ \n")
print("Observation Space", env.observation_space)
print("Sample observation", env.observation_space.sample()) # случайное наблюдение

По Observation Space Shape Discrete(16) видно, что наблюдение — целое число, обозначающее текущую позицию агента как current_row * ncols + current_col (строки и столбцы нумеруются с 0).

Например, позиция цели на карте 4×4 — это 3⋅4+3=153 \cdot 4 + 3 = 15. Число возможных наблюдений зависит от размера карты. Например, на карте 4×4 их 16. В оригинале здесь картинка: так выглядит состояние 0 — агент в левом верхнем углу.

print("\n _____ACTION SPACE_____ \n")
print("Action Space Shape", env.action_space.n)
print("Action Space Sample", env.action_space.sample()) # случайное действие

Пространство действий (множество действий, доступных агенту) дискретное, действий 4 🎮:

  • 0: ИДТИ ВЛЕВО;
  • 1: ИДТИ ВНИЗ;
  • 2: ИДТИ ВПРАВО;
  • 3: ИДТИ ВВЕРХ.

Функция награды 💰:

  • дойти до цели: +1;
  • попасть в полынью: 0;
  • ступить на лёд: 0.

(👀 Шаг 1 псевдокода.)

Пора инициализировать Q-таблицу! Чтобы знать, сколько в ней строк (состояний) и столбцов (действий), нужно знать пространства действий и наблюдений. Мы уже знаем их размеры, но хотим получать их программно, чтобы алгоритм работал и в других средах. Gym даёт для этого env.action_space.n и env.observation_space.n.

state_space =
print("There are ", state_space, " possible states")
action_space =
print("There are ", action_space, " possible actions")
# Создадим Q-таблицу размера (state_space, action_space) и заполним её нулями через np.zeros. np.zeros нужен кортеж (a, b)
def initialize_q_table(state_space, action_space):
Qtable =
return Qtable
Qtable_frozenlake = initialize_q_table(state_space, action_space)
state_space = env.observation_space.n
print("There are ", state_space, " possible states")
action_space = env.action_space.n
print("There are ", action_space, " possible actions")
# Создадим Q-таблицу размера (state_space, action_space) и заполним её нулями через np.zeros
def initialize_q_table(state_space, action_space):
Qtable = np.zeros((state_space, action_space))
return Qtable
Qtable_frozenlake = initialize_q_table(state_space, action_space)

Вспомните: политик у нас две, потому что Q-learning — алгоритм off-policy. Значит, для действий и для обновления функции ценности используются разные политики:

  • ε-жадная политика (политика действий);
  • жадная политика (политика обновления).

Жадная политика будет и итоговой политикой агента Q-learning после обучения. Жадная политика выбирает действие по Q-таблице.

def greedy_policy(Qtable, state):
# Использование: выбираем действие с наибольшей ценностью пары «состояние — действие»
action =
return action
def greedy_policy(Qtable, state):
# Использование: выбираем действие с наибольшей ценностью пары «состояние — действие»
action = np.argmax(Qtable[state][:])
return action

ε-жадная политика — это политика обучения, которая решает компромисс между исследованием и использованием.

Идея ε-жадной политики:

  • с вероятностью 1 − ε мы используем знания (агент выбирает действие с наибольшей ценностью пары «состояние — действие»);
  • с вероятностью ε мы исследуем (пробуем случайное действие).

По ходу обучения мы постепенно уменьшаем ε, потому что исследование нужно всё меньше, а использование — всё больше.

def epsilon_greedy_policy(Qtable, state, epsilon):
# Случайное число от 0 до 1
random_num =
# если random_num больше epsilon --> использование
if random_num > epsilon:
# Выбираем действие с наибольшей ценностью в этом состоянии
# здесь может пригодиться np.argmax
action =
# иначе --> исследование
else:
action = # случайное действие
return action
def epsilon_greedy_policy(Qtable, state, epsilon):
# Случайное число от 0 до 1
random_num = random.uniform(0, 1)
# если random_num больше epsilon --> использование
if random_num > epsilon:
# Выбираем действие с наибольшей ценностью в этом состоянии
# здесь может пригодиться np.argmax
action = greedy_policy(Qtable, state)
# иначе --> исследование
else:
action = env.action_space.sample()
return action

Гиперпараметры, связанные с исследованием, — одни из самых важных.

  • Нужно, чтобы агент исследовал достаточно большую часть пространства состояний и выучил хорошее приближение ценностей. Для этого ε должно убывать постепенно.
  • Если уменьшать ε слишком быстро (слишком большой decay_rate), вы рискуете, что агент застрянет: он недостаточно исследовал пространство состояний и потому не сможет решить задачу.
# Параметры обучения
n_training_episodes = 10000 # всего эпизодов обучения
learning_rate = 0.7 # скорость обучения
# Параметры оценки
n_eval_episodes = 100 # всего тестовых эпизодов
# Параметры среды
env_id = "FrozenLake-v1" # название среды
max_steps = 99 # максимум шагов в эпизоде
gamma = 0.95 # коэффициент дисконтирования
eval_seed = [] # seed среды для оценки
# Параметры исследования
max_epsilon = 1.0 # вероятность исследования в начале
min_epsilon = 0.05 # минимальная вероятность исследования
decay_rate = 0.0005 # скорость экспоненциального убывания вероятности исследования

Цикл обучения устроен так:

Для каждого эпизода из общего числа эпизодов обучения:
Уменьшить epsilon (исследование нужно всё меньше)
Сбросить среду
Для каждого шага до максимального числа шагов:
Выбрать действие A_t ε-жадной политикой
Выполнить действие (a) и получить новое состояние (s') и награду (r)
Обновить Q-значение Q(s,a) по уравнению Беллмана: Q(s,a) + lr [R(s,a) + gamma * max Q(s',a') - Q(s,a)]
Если эпизод закончился, завершить его
Следующее состояние — новое состояние
def train(n_training_episodes, min_epsilon, max_epsilon, decay_rate, env, max_steps, Qtable):
for episode in tqdm(range(n_training_episodes)):
# Уменьшаем epsilon (исследование нужно всё меньше)
epsilon = min_epsilon + (max_epsilon - min_epsilon)*np.exp(-decay_rate*episode)
# Сбрасываем среду
state, info = env.reset()
step = 0
terminated = False
truncated = False
# повторяем
for step in range(max_steps):
# Выбираем действие A_t ε-жадной политикой
action =
# Выполняем действие A_t и получаем R_t+1 и S_t+1
# Выполняем действие (a) и получаем новое состояние (s') и награду (r)
new_state, reward, terminated, truncated, info =
# Обновляем Q(s,a):= Q(s,a) + lr [R(s,a) + gamma * max Q(s',a') - Q(s,a)]
Qtable[state][action] =
# Если эпизод закончился или оборван — завершаем его
if terminated or truncated:
break
# Следующее состояние — новое состояние
state = new_state
return Qtable
def train(n_training_episodes, min_epsilon, max_epsilon, decay_rate, env, max_steps, Qtable):
for episode in tqdm(range(n_training_episodes)):
# Уменьшаем epsilon (исследование нужно всё меньше)
epsilon = min_epsilon + (max_epsilon - min_epsilon) * np.exp(-decay_rate * episode)
# Сбрасываем среду
state, info = env.reset()
step = 0
terminated = False
truncated = False
# повторяем
for step in range(max_steps):
# Выбираем действие A_t ε-жадной политикой
action = epsilon_greedy_policy(Qtable, state, epsilon)
# Выполняем действие A_t и получаем R_t+1 и S_t+1
# Выполняем действие (a) и получаем новое состояние (s') и награду (r)
new_state, reward, terminated, truncated, info = env.step(action)
# Обновляем Q(s,a):= Q(s,a) + lr [R(s,a) + gamma * max Q(s',a') - Q(s,a)]
Qtable[state][action] = Qtable[state][action] + learning_rate * (
reward + gamma * np.max(Qtable[new_state]) - Qtable[state][action]
)
# Если эпизод закончился или оборван — завершаем его
if terminated or truncated:
break
# Следующее состояние — новое состояние
state = new_state
return Qtable
Qtable_frozenlake = train(n_training_episodes, min_epsilon, max_epsilon, decay_rate, env, max_steps, Qtable_frozenlake)

Посмотрим, как теперь выглядит Q-таблица 👀

Заголовок раздела «Посмотрим, как теперь выглядит Q-таблица 👀»
Qtable_frozenlake

Определим метод оценки, которым будем проверять агента Q-learning.

def evaluate_agent(env, max_steps, n_eval_episodes, Q, seed):
"""
Оценить агента на ``n_eval_episodes`` эпизодах и вернуть среднюю награду и её стандартное отклонение.
:param env: среда для оценки
:param n_eval_episodes: число эпизодов для оценки агента
:param Q: Q-таблица
:param seed: массив seed для оценки (для taxi-v3)
"""
episode_rewards = []
for episode in tqdm(range(n_eval_episodes)):
if seed:
state, info = env.reset(seed=seed[episode])
else:
state, info = env.reset()
step = 0
truncated = False
terminated = False
total_rewards_ep = 0
for step in range(max_steps):
# Выбираем действие (индекс) с наибольшей ожидаемой будущей наградой в этом состоянии
action = greedy_policy(Q, state)
new_state, reward, terminated, truncated, info = env.step(action)
total_rewards_ep += reward
if terminated or truncated:
break
state = new_state
episode_rewards.append(total_rewards_ep)
mean_reward = np.mean(episode_rewards)
std_reward = np.std(episode_rewards)
return mean_reward, std_reward
  • Обычно средняя награда должна быть 1,0.
  • Среда довольно простая: пространство состояний совсем маленькое (16). Можно попробовать заменить её скользкой версией: она вносит случайность и делает среду сложнее.
# Оцениваем агента
mean_reward, std_reward = evaluate_agent(env, max_steps, n_eval_episodes, Qtable_frozenlake, eval_seed)
print(f"Mean_reward={mean_reward:.2f} +/- {std_reward:.2f}")

Мы получили хорошие результаты — теперь можно одной строкой кода опубликовать модель на Hub 🤗. В оригинале здесь пример карточки модели.

Внутри Hub использует репозитории на основе git (не страшно, если вы не знаете, что такое git). Это значит, что по мере экспериментов и улучшения агента можно выкладывать новые версии модели.

from huggingface_hub import HfApi, snapshot_download
from huggingface_hub.repocard import metadata_eval_result, metadata_save
from pathlib import Path
import datetime
import json
def record_video(env, Qtable, out_directory, fps=1):
"""
Записать видео игры агента
:param env
:param Qtable: Q-таблица агента
:param out_directory
:param fps: сколько кадров в секунду (для taxi-v3 и frozenlake-v1 мы берём 1)
"""
images = []
terminated = False
truncated = False
state, info = env.reset(seed=random.randint(0, 500))
img = env.render()
images.append(img)
while not terminated or truncated:
# Выбираем действие (индекс) с наибольшей ожидаемой будущей наградой в этом состоянии
action = np.argmax(Qtable[state][:])
state, reward, terminated, truncated, info = env.step(
action
) # сразу пишем next_state в state — для записи этого достаточно
img = env.render()
images.append(img)
imageio.mimsave(out_directory, [np.array(img) for i, img in enumerate(images)], fps=fps)
def push_to_hub(repo_id, model, env, video_fps=1, local_repo_path="hub"):
"""
Оценить модель, записать видео и загрузить модель на Hugging Face Hub.
Метод выполняет весь конвейер:
- оценивает модель
- создаёт карточку модели
- записывает видео игры агента
- отправляет всё на Hub
:param repo_id: id репозитория модели на Hugging Face Hub
:param env
:param video_fps: сколько кадров в секунду записывать в видео
(для taxi-v3 и frozenlake-v1 мы берём 1)
:param local_repo_path: где лежит локальный репозиторий
"""
_, repo_name = repo_id.split("/")
eval_env = env
api = HfApi()
# Шаг 1: создаём репозиторий
repo_url = api.create_repo(
repo_id=repo_id,
exist_ok=True,
)
# Шаг 2: скачиваем файлы
repo_local_path = Path(snapshot_download(repo_id=repo_id))
# Шаг 3: сохраняем модель
if env.spec.kwargs.get("map_name"):
model["map_name"] = env.spec.kwargs.get("map_name")
if env.spec.kwargs.get("is_slippery", "") == False:
model["slippery"] = False
# Сериализуем модель через pickle
with open((repo_local_path) / "q-learning.pkl", "wb") as f:
pickle.dump(model, f)
# Шаг 4: оцениваем модель и собираем JSON с метриками оценки
mean_reward, std_reward = evaluate_agent(
eval_env, model["max_steps"], model["n_eval_episodes"], model["qtable"], model["eval_seed"]
)
evaluate_data = {
"env_id": model["env_id"],
"mean_reward": mean_reward,
"n_eval_episodes": model["n_eval_episodes"],
"eval_datetime": datetime.datetime.now().isoformat(),
}
# Пишем файл results.json с результатами оценки
with open(repo_local_path / "results.json", "w") as outfile:
json.dump(evaluate_data, outfile)
# Шаг 5: создаём карточку модели
env_name = model["env_id"]
if env.spec.kwargs.get("map_name"):
env_name += "-" + env.spec.kwargs.get("map_name")
if env.spec.kwargs.get("is_slippery", "") == False:
env_name += "-" + "no_slippery"
metadata = {}
metadata["tags"] = [env_name, "q-learning", "reinforcement-learning", "custom-implementation"]
# Добавляем метрики
eval = metadata_eval_result(
model_pretty_name=repo_name,
task_pretty_name="reinforcement-learning",
task_id="reinforcement-learning",
metrics_pretty_name="mean_reward",
metrics_id="mean_reward",
metrics_value=f"{mean_reward:.2f} +/- {std_reward:.2f}",
dataset_pretty_name=env_name,
dataset_id=env_name,
)
# Объединяем оба словаря
metadata = {**metadata, **eval}
model_card = f"""
# **Q-Learning** Agent playing1 **{env_id}**
This is a trained model of a **Q-Learning** agent playing **{env_id}** .
## Usage
model = load_from_hub(repo_id="{repo_id}", filename="q-learning.pkl")
# Don't forget to check if you need to add additional attributes (is_slippery=False etc)
env = gym.make(model["env_id"])
"""
evaluate_agent(env, model["max_steps"], model["n_eval_episodes"], model["qtable"], model["eval_seed"])
readme_path = repo_local_path / "README.md"
readme = ""
print(readme_path.exists())
if readme_path.exists():
with readme_path.open("r", encoding="utf8") as f:
readme = f.read()
else:
readme = model_card
with readme_path.open("w", encoding="utf-8") as f:
f.write(readme)
# Сохраняем метрики в метаданные README
metadata_save(readme_path, metadata)
# Шаг 6: записываем видео
video_path = repo_local_path / "replay.mp4"
record_video(env, model["qtable"], video_path, video_fps)
# Шаг 7: отправляем всё на Hub
api.upload_folder(
repo_id=repo_id,
folder_path=repo_local_path,
path_in_repo=".",
)
print("Your model is pushed to the Hub. You can view your model here: ", repo_url)

Текст карточки модели оставлен по-английски: он попадает на Hub. Перевод: «Агент Q-Learning играет в {env_id}. Использование: model = load_from_hub(...); не забудьте проверить, нужны ли дополнительные атрибуты (is_slippery=False и т. п.), env = gym.make(model["env_id"])».

С push_to_hub вы оцениваете агента, записываете видео игры, создаёте карточку модели и отправляете всё на Hub. Так вы можете:

  • показать свою работу 🔥;
  • посмотреть, как играет агент 👀;
  • поделиться с сообществом агентом, которым могут пользоваться другие 💾;
  • попасть на лидерборд 🏆 и сравнить агента с однокурсниками: Deep RL Leaderboard.

Чтобы поделиться моделью с сообществом, нужно сделать ещё три шага.

1️⃣ (Если ещё нет) создайте аккаунт на HF: huggingface.co/join.

2️⃣ Войдите и сохраните токен авторизации с сайта Hugging Face: создайте новый токен (huggingface.co/settings/tokens) с правом записи (write).

from huggingface_hub import notebook_login
notebook_login()

Если вы не пользуетесь Google Colab или Jupyter Notebook, используйте вместо этого команду huggingface-cli login (или login).

3️⃣ Теперь всё готово, чтобы выложить обученного агента на Hub 🤗 🔥 функцией push_to_hub().

  • Создадим словарь модели, содержащий гиперпараметры и Q-таблицу.
model = {
"env_id": env_id,
"max_steps": max_steps,
"n_training_episodes": n_training_episodes,
"n_eval_episodes": n_eval_episodes,
"eval_seed": eval_seed,
"learning_rate": learning_rate,
"gamma": gamma,
"max_epsilon": max_epsilon,
"min_epsilon": min_epsilon,
"decay_rate": decay_rate,
"qtable": Qtable_frozenlake,
}

Заполним аргументы функции push_to_hub:

  • repo_id — название репозитория на Hugging Face Hub, который будет создан или обновлён (repo_id = {username}/{repo_name}); 💡 хорошее имя — {username}/q-{env_id};
  • model — словарь модели с гиперпараметрами и Q-таблицей;
  • env — среда;
  • commit_message — сообщение коммита.
model
username = "" # ЗАПОЛНИТЕ
repo_name = "q-FrozenLake-v1-4x4-noSlippery"
push_to_hub(repo_id=f"{username}/{repo_name}", model=model, env=env)

Поздравляем 🥳, вы только что реализовали с нуля, обучили и выложили своего первого агента обучения с подкреплением. FrozenLake-v1 без скольжения — очень простая среда; попробуем посложнее 🔥.

💡 Хорошая привычка, когда начинаете работать со средой, — заглянуть в её документацию.

В Taxi-v3 🚕 на клетчатом поле есть четыре отмеченные точки: R (красная), G (зелёная), Y (жёлтая) и B (синяя).

В начале эпизода такси стоит в случайной клетке, а пассажир — в случайной точке. Такси едет к пассажиру, сажает его, везёт к месту назначения (другой из четырёх точек) и высаживает пассажира. Как только пассажир высажен, эпизод заканчивается.

env = gym.make("Taxi-v3", render_mode="rgb_array")

Состояний 500, потому что у такси 25 возможных клеток, у пассажира 5 возможных мест (включая случай, когда он в такси), а мест назначения 4 (25×5×4=50025 \times 5 \times 4 = 500).

state_space = env.observation_space.n
print("There are ", state_space, " possible states")
action_space = env.action_space.n
print("There are ", action_space, " possible actions")

Пространство действий (множество действий, доступных агенту) дискретное, действий 6 🎮:

  • 0: ехать на юг;
  • 1: ехать на север;
  • 2: ехать на восток;
  • 3: ехать на запад;
  • 4: посадить пассажира;
  • 5: высадить пассажира.

Функция награды 💰:

  • −1 за каждый шаг, если не сработала другая награда;
  • +20 за доставку пассажира;
  • −10 за попытку посадить или высадить пассажира не там, где можно.
# Создаём Q-таблицу из state_size строк и action_size столбцов (500x6)
Qtable_taxi = initialize_q_table(state_space, action_space)
print(Qtable_taxi)
print("Q-table shape: ", Qtable_taxi.shape)

⚠ НЕ МЕНЯЙТЕ EVAL_SEED: массив eval_seed позволяет оценивать агентов всех однокурсников на одних и тех же начальных позициях такси.

# Параметры обучения
n_training_episodes = 25000 # всего эпизодов обучения
learning_rate = 0.7 # скорость обучения
# Параметры оценки
n_eval_episodes = 100 # всего тестовых эпизодов
# НЕ МЕНЯЙТЕ EVAL_SEED
eval_seed = [
16, 54, 165, 177, 191, 191, 120, 80, 149, 178, 48, 38, 6, 125, 174, 73, 50, 172, 100, 148,
146, 6, 25, 40, 68, 148, 49, 167, 9, 97, 164, 176, 61, 7, 54, 55, 161, 131, 184, 51,
170, 12, 120, 113, 95, 126, 51, 98, 36, 135, 54, 82, 45, 95, 89, 59, 95, 124, 9, 113,
58, 85, 51, 134, 121, 169, 105, 21, 30, 11, 50, 65, 12, 43, 82, 145, 152, 97, 106, 55,
31, 85, 38, 112, 102, 168, 123, 97, 21, 83, 158, 26, 80, 63, 5, 81, 32, 11, 28, 148,
] # seed для оценки: так агентов всех однокурсников оценивают на одних и тех же начальных позициях такси
# Каждому seed соответствует своё начальное состояние
# Параметры среды
env_id = "Taxi-v3" # название среды
max_steps = 99 # максимум шагов в эпизоде
gamma = 0.95 # коэффициент дисконтирования
# Параметры исследования
max_epsilon = 1.0 # вероятность исследования в начале
min_epsilon = 0.05 # минимальная вероятность исследования
decay_rate = 0.005 # скорость экспоненциального убывания вероятности исследования

(В оригинале числа eval_seed записаны по одному на строку; здесь — по двадцать в строке, порядок тот же.)

Qtable_taxi = train(n_training_episodes, min_epsilon, max_epsilon, decay_rate, env, max_steps, Qtable_taxi)
Qtable_taxi

Создаём словарь модели 💾 и публикуем обученную модель на Hub 🔥

Заголовок раздела «Создаём словарь модели 💾 и публикуем обученную модель на Hub 🔥»

Создадим словарь модели со всеми гиперпараметрами обучения (для воспроизводимости) и Q-таблицей.

model = {
"env_id": env_id,
"max_steps": max_steps,
"n_training_episodes": n_training_episodes,
"n_eval_episodes": n_eval_episodes,
"eval_seed": eval_seed,
"learning_rate": learning_rate,
"gamma": gamma,
"max_epsilon": max_epsilon,
"min_epsilon": min_epsilon,
"decay_rate": decay_rate,
"qtable": Qtable_taxi,
}
username = "" # ЗАПОЛНИТЕ
repo_name = "" # ЗАПОЛНИТЕ
push_to_hub(repo_id=f"{username}/{repo_name}", model=model, env=env)

Теперь модель на Hub, и результаты своего Taxi-v3 можно сравнить с однокурсниками на лидерборде 🏆.

Чем замечателен Hugging Face Hub 🤗 — с него легко загружать сильные модели сообщества.

Загрузить сохранённую модель с Hub очень просто:

  1. Откройте список всех сохранённых моделей Q-learning.
  2. Выберите модель и скопируйте её repo_id.
  3. Вызовите load_from_hub, передав:
    • repo_id;
    • filename — имя сохранённой модели в репозитории.
from urllib.error import HTTPError
from huggingface_hub import hf_hub_download
def load_from_hub(repo_id: str, filename: str) -> str:
"""
Скачать модель с Hugging Face Hub.
:param repo_id: id репозитория модели на Hugging Face Hub
:param filename: имя файла модели в репозитории
"""
# Берём модель с Hub, скачиваем и кэшируем её на локальном диске
pickle_model = hf_hub_download(repo_id=repo_id, filename=filename)
with open(pickle_model, "rb") as f:
downloaded_model_file = pickle.load(f)
return downloaded_model_file
model = load_from_hub(repo_id="ThomasSimonini/q-Taxi-v3", filename="q-learning.pkl") # попробуйте другую модель
print(model)
env = gym.make(model["env_id"])
evaluate_agent(env, model["max_steps"], model["n_eval_episodes"], model["qtable"], model["eval_seed"])
model = load_from_hub(
repo_id="ThomasSimonini/q-FrozenLake-v1-no-slippery", filename="q-learning.pkl"
) # попробуйте другую модель
env = gym.make(model["env_id"], is_slippery=False)
evaluate_agent(env, model["max_steps"], model["n_eval_episodes"], model["qtable"], model["eval_seed"])

Лучший способ учиться — пробовать самому! Как вы видели, нынешний агент играет не блестяще. Для начала можно учить дольше: с 1 000 000 шагов мы видели отличные результаты!

На лидерборде есть ваши агенты. Сможете пробиться наверх? Несколько идей:

  • учить дольше;
  • попробовать другие гиперпараметры, посмотрев, что сделали однокурсники;
  • выложить новую обученную модель на Hub 🔥.

Ходить по льду и водить такси наскучило? Попробуйте сменить среду: почему бы не взять скользкую версию FrozenLake-v1? Посмотрите, как устроены среды, в документации Gymnasium, и развлекайтесь 🎉.


Поздравляем 🥳, вы только что реализовали, обучили и выложили своего первого агента обучения с подкреплением.

Понять Q-learning — важный шаг к пониманию методов на основе ценности.

В следующем юните, о Deep Q-Learning, мы увидим, что создавать и обновлять Q-таблицу было хорошей стратегией, — однако она не масштабируется. Представьте, например, агента, который учится играть в Doom. Doom — большая среда с огромным пространством состояний (миллионы разных состояний). Создавать и обновлять Q-таблицу для такой среды было бы неэффективно.

Поэтому в следующем юните мы изучим Deep Q-Learning — алгоритм, в котором нейросеть по состоянию приближает Q-значения каждого действия (перевод юнита 3).

Увидимся в юните 3! 🔥 Продолжайте учиться и оставайтесь крутыми 🤗

EnglishРусский
Q-table, initialize_q_tableQ-таблица, инициализация Q-таблицы
greedy / epsilon-greedy policyжадная / ε-жадная политика
acting policy / updating policyполитика действий / политика обновления
exploration / exploitationисследование / использование
decay rateскорость убывания (ε)
slippery / non-slipperyскользкий / нескользкий (лёд)
frozen tile, hole, goalлёд, полынья, цель
pickup / drop off passengerпосадить / высадить пассажира
evaluation seedseed для оценки (фиксированные начальные состояния)
model dictionary, pickleсловарь модели, сериализация pickle