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 в двух средах:
- Frozen-Lake-v1 (нескользкая и скользкая версии) ☃️: агенту нужно дойти от старта (S) до цели (G), ступая только по льду (F) и обходя полыньи (H).
- Автономное такси 🚖: агенту нужно научиться ездить по городу, чтобы довозить пассажиров из точки A в точку B.
Благодаря лидерборду вы сможете сравнить результаты с однокурсниками и обменяться приёмами, улучшающими счёт агента. Кто выиграет состязание юнита 2?
Чтобы это занятие засчиталось для сертификата, нужно выложить обученную модель для Taxi на Hub и получить результат ≥ 4,5. Чтобы узнать результат, найдите свою модель на лидерборде: результат = средняя награда − стандартное отклонение награды. Прогресс можно проверить здесь.
Чтобы начать, нажмите кнопку «Open In Colab»: ноутбук unit2.ipynb в Colab.
Мы настоятельно рекомендуем выполнять практические задания в Google Colab, а не на личном компьютере. С Colab вы сосредоточитесь на учёбе и экспериментах и не будете отвлекаться на техническую настройку окружения.
Юнит 2: Q-learning на FrozenLake-v1 ⛄ и Taxi-v3 🚕
Заголовок раздела «Юнит 2: Q-learning на FrozenLake-v1 ⛄ и Taxi-v3 🚕»В этом ноутбуке вы с нуля напишете своего первого агента обучения с подкреплением, который играет во FrozenLake ❄️ с помощью Q-learning, поделитесь им с сообществом и поэкспериментируете с разными настройками.
⬇️ Вот пример того, чего вы добьётесь всего за пару минут (в оригинале — анимация). ⬇️
🎮 Среды: FrozenLake-v1, Taxi-v3.
📚 Библиотеки RL: Python и NumPy, Gymnasium.
Мы постоянно улучшаем наши руководства, поэтому, если найдёте проблемы в этом ноутбуке, откройте issue в репозитории на GitHub.
Цели ноутбука 🏆
Заголовок раздела «Цели ноутбука 🏆»К концу ноутбука вы сможете:
- пользоваться Gymnasium — библиотекой сред;
- написать агента Q-learning с нуля;
- выложить обученного агента и код на Hub с красивым видео игры и оценкой качества 🔥.
Этот ноутбук — из курса Deep Reinforcement Learning
Заголовок раздела «Этот ноутбук — из курса Deep Reinforcement Learning»В этом бесплатном курсе вы:
- 📖 изучите глубокое обучение с подкреплением в теории и на практике;
- 🧑💻 научитесь пользоваться известными библиотеками Deep RL: Stable Baselines3, RL Baselines3 Zoo, CleanRL и Sample Factory 2.0;
- 🤖 обучите агентов в необычных средах.
И многое другое — смотрите 📚 программу курса. Лучший способ оставаться на связи — присоединиться к нашему Discord-серверу: discord.gg/ydHrjt3WP5.
Что нужно заранее 🏗️
Заголовок раздела «Что нужно заранее 🏗️»🔲 📚 Изучить Q-learning по юниту 2 🤗 (перевод).
Краткое повторение Q-learning
Заголовок раздела «Краткое повторение Q-learning»Q-learning — это алгоритм RL, который:
- обучает Q-функцию — функцию ценности действия, которая во внутренней памяти хранится как Q-таблица, содержащая ценности всех пар «состояние — действие»;
- по состоянию и действию ищет соответствующее значение в Q-таблице;
- когда обучение закончено, у нас есть оптимальная Q-функция, а значит, оптимальная Q-таблица;
- а если есть оптимальная Q-функция, есть и оптимальная политика: для каждого состояния мы знаем лучшее действие.
Но поначалу Q-таблица бесполезна: в ней произвольные значения для каждой пары «состояние — действие» (чаще всего Q-таблицу заполняют нулями). По мере того как мы исследуем среду и обновляем Q-таблицу, её приближения становятся всё лучше.
В оригинале здесь картинка с псевдокодом Q-learning — тем же, что разобран по шагам 1–4 в теории юнита 2: инициализировать Q-таблицу; выбрать действие ε-жадно; выполнить действие, получить награду и состояние ; обновить :
Напишем наш первый алгоритм обучения с подкреплением 🚀
Заголовок раздела «Напишем наш первый алгоритм обучения с подкреплением 🚀»Устанавливаем зависимости и создаём виртуальный дисплей 🔽
Заголовок раздела «Устанавливаем зависимости и создаём виртуальный дисплей 🔽»В ноутбуке нам понадобится записать видео игры. В 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.txtsudo apt-get updatesudo apt-get install -y python3-openglapt install ffmpeg xvfbpip3 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 npimport gymnasium as gymimport randomimport imageioimport osimport tqdm
import pickle5 as picklefrom tqdm.notebook import tqdmТеперь всё готово, чтобы написать алгоритм Q-learning 🔥
Часть 1: FrozenLake ⛄ (нескользкая версия)
Заголовок раздела «Часть 1: FrozenLake ⛄ (нескользкая версия)»Создаём среду 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 — это . Число возможных наблюдений зависит от размера карты. Например, на карте 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.
Создаём и инициализируем Q-таблицу 🗄️
Заголовок раздела «Создаём и инициализируем Q-таблицу 🗄️»(👀 Шаг 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 QtableQtable_frozenlake = initialize_q_table(state_space, action_space)Решение
Заголовок раздела «Решение»state_space = env.observation_space.nprint("There are ", state_space, " possible states")
action_space = env.action_space.nprint("There are ", action_space, " possible actions")# Создадим Q-таблицу размера (state_space, action_space) и заполним её нулями через np.zerosdef initialize_q_table(state_space, action_space): Qtable = np.zeros((state_space, action_space)) return QtableQtable_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Обучаем агента Q-learning 🏃
Заголовок раздела «Обучаем агента Q-learning 🏃»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Оцениваем агента Q-learning 📈
Заголовок раздела «Оцениваем агента Q-learning 📈»- Обычно средняя награда должна быть 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 🔥»Мы получили хорошие результаты — теперь можно одной строкой кода опубликовать модель на Hub 🤗. В оригинале здесь пример карточки модели.
Внутри Hub использует репозитории на основе git (не страшно, если вы не знаете, что такое git). Это значит, что по мере экспериментов и улучшения агента можно выкладывать новые версии модели.
Этот код не меняйте
Заголовок раздела «Этот код не меняйте»from huggingface_hub import HfApi, snapshot_downloadfrom huggingface_hub.repocard import metadata_eval_result, metadata_save
from pathlib import Pathimport datetimeimport jsondef 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— сообщение коммита.
modelusername = "" # ЗАПОЛНИТЕrepo_name = "q-FrozenLake-v1-4x4-noSlippery"push_to_hub(repo_id=f"{username}/{repo_name}", model=model, env=env)Поздравляем 🥳, вы только что реализовали с нуля, обучили и выложили своего первого агента обучения с подкреплением. FrozenLake-v1 без скольжения — очень простая среда; попробуем посложнее 🔥.
Часть 2: Taxi-v3 🚖
Заголовок раздела «Часть 2: Taxi-v3 🚖»Создаём среду Taxi-v3 🚕 и разбираемся в ней
Заголовок раздела «Создаём среду Taxi-v3 🚕 и разбираемся в ней»💡 Хорошая привычка, когда начинаете работать со средой, — заглянуть в её документацию.
В Taxi-v3 🚕 на клетчатом поле есть четыре отмеченные точки: R (красная), G (зелёная), Y (жёлтая) и B (синяя).
В начале эпизода такси стоит в случайной клетке, а пассажир — в случайной точке. Такси едет к пассажиру, сажает его, везёт к месту назначения (другой из четырёх точек) и высаживает пассажира. Как только пассажир высажен, эпизод заканчивается.
env = gym.make("Taxi-v3", render_mode="rgb_array")Состояний 500, потому что у такси 25 возможных клеток, у пассажира 5 возможных мест (включая случай, когда он в такси), а мест назначения 4 ().
state_space = env.observation_space.nprint("There are ", state_space, " possible states")action_space = env.action_space.nprint("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_SEEDeval_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 записаны по одному на строку; здесь — по двадцать в строке, порядок тот же.)
Обучаем агента Q-learning 🏃
Заголовок раздела «Обучаем агента Q-learning 🏃»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 можно сравнить с однокурсниками на лидерборде 🏆.
Часть 3: загружаем модель с Hub 🔽
Заголовок раздела «Часть 3: загружаем модель с Hub 🔽»Чем замечателен Hugging Face Hub 🤗 — с него легко загружать сильные модели сообщества.
Загрузить сохранённую модель с Hub очень просто:
- Откройте список всех сохранённых моделей Q-learning.
- Выберите модель и скопируйте её
repo_id. - Вызовите
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_filemodel = 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_table | Q-таблица, инициализация 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 seed | seed для оценки (фиксированные начальные состояния) |
| model dictionary, pickle | словарь модели, сериализация pickle |