You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ML-For-Beginners/translations/ru/8-Reinforcement/2-Gym
leestott eaff2e5814
🌐 Update translations via Co-op Translator
2 weeks ago
..
solution 🌐 Update translations via Co-op Translator 3 weeks ago
README.md 🌐 Update translations via Co-op Translator 2 weeks ago
assignment.md 🌐 Update translations via Co-op Translator 3 weeks ago
notebook.ipynb 🌐 Update translations via Co-op Translator 3 weeks ago

README.md

Предварительные требования

В этом уроке мы будем использовать библиотеку OpenAI Gym для моделирования различных сред. Вы можете запустить код этого урока локально (например, из Visual Studio Code), в этом случае симуляция откроется в новом окне. При запуске кода онлайн, возможно, потребуется внести некоторые изменения в код, как описано здесь.

OpenAI Gym

В предыдущем уроке правила игры и состояние задавались классом Board, который мы определили самостоятельно. Здесь мы будем использовать специальную среду симуляции, которая будет моделировать физику балансирующего шеста. Одна из самых популярных сред симуляции для обучения алгоритмов с подкреплением называется Gym, которая поддерживается OpenAI. С помощью Gym мы можем создавать различные среды — от симуляции CartPole до игр Atari.

Примечание: Вы можете увидеть другие доступные среды OpenAI Gym здесь.

Сначала установим Gym и импортируем необходимые библиотеки (блок кода 1):

import sys
!{sys.executable} -m pip install gym 

import gym
import matplotlib.pyplot as plt
import numpy as np
import random

Упражнение — инициализация среды CartPole

Чтобы работать с задачей балансировки CartPole, нам нужно инициализировать соответствующую среду. Каждая среда связана с:

  • Пространством наблюдений, которое определяет структуру информации, получаемой из среды. Для задачи CartPole мы получаем положение шеста, скорость и некоторые другие значения.

  • Пространством действий, которое определяет возможные действия. В нашем случае пространство действий дискретное и состоит из двух действий — влево и вправо. (блок кода 2)

  1. Для инициализации введите следующий код:

    env = gym.make("CartPole-v1")
    print(env.action_space)
    print(env.observation_space)
    print(env.action_space.sample())
    

Чтобы понять, как работает среда, давайте запустим короткую симуляцию на 100 шагов. На каждом шаге мы предоставляем одно из действий, которое нужно выполнить — в этой симуляции мы просто случайным образом выбираем действие из action_space.

  1. Запустите код ниже и посмотрите, к чему это приведет.

    Помните, что предпочтительно запускать этот код на локальной установке Python! (блок кода 3)

    env.reset()
    
    for i in range(100):
       env.render()
       env.step(env.action_space.sample())
    env.close()
    

    Вы должны увидеть что-то похожее на это изображение:

    небалансирующий CartPole

  2. Во время симуляции нам нужно получать наблюдения, чтобы решать, как действовать. На самом деле, функция step возвращает текущие наблюдения, функцию вознаграждения и флаг завершения, который указывает, имеет ли смысл продолжать симуляцию: (блок кода 4)

    env.reset()
    
    done = False
    while not done:
       env.render()
       obs, rew, done, info = env.step(env.action_space.sample())
       print(f"{obs} -> {rew}")
    env.close()
    

    В результате вы увидите что-то похожее на это в выводе ноутбука:

    [ 0.03403272 -0.24301182  0.02669811  0.2895829 ] -> 1.0
    [ 0.02917248 -0.04828055  0.03248977  0.00543839] -> 1.0
    [ 0.02820687  0.14636075  0.03259854 -0.27681916] -> 1.0
    [ 0.03113408  0.34100283  0.02706215 -0.55904489] -> 1.0
    [ 0.03795414  0.53573468  0.01588125 -0.84308041] -> 1.0
    ...
    [ 0.17299878  0.15868546 -0.20754175 -0.55975453] -> 1.0
    [ 0.17617249  0.35602306 -0.21873684 -0.90998894] -> 1.0
    

    Вектор наблюдений, возвращаемый на каждом шаге симуляции, содержит следующие значения:

    • Положение тележки
    • Скорость тележки
    • Угол шеста
    • Скорость вращения шеста
  3. Получите минимальное и максимальное значение этих чисел: (блок кода 5)

    print(env.observation_space.low)
    print(env.observation_space.high)
    

    Вы также можете заметить, что значение вознаграждения на каждом шаге симуляции всегда равно 1. Это связано с тем, что наша цель — выжить как можно дольше, то есть удерживать шест в достаточно вертикальном положении максимально долго.

    На самом деле симуляция CartPole считается решенной, если нам удается получить среднее вознаграждение 195 за 100 последовательных попыток.

Дискретизация состояния

В Q-Learning нам нужно построить Q-таблицу, которая определяет, что делать в каждом состоянии. Чтобы это сделать, состояние должно быть дискретным, точнее, оно должно содержать конечное количество дискретных значений. Таким образом, нам нужно каким-то образом дискретизировать наши наблюдения, сопоставляя их с конечным набором состояний.

Есть несколько способов сделать это:

  • Разделение на интервалы. Если мы знаем интервал определенного значения, мы можем разделить этот интервал на несколько интервалов, а затем заменить значение номером интервала, к которому оно принадлежит. Это можно сделать с помощью метода numpy digitize. В этом случае мы точно будем знать размер состояния, так как он будет зависеть от количества интервалов, которые мы выберем для дискретизации.

Мы можем использовать линейную интерполяцию, чтобы привести значения к некоторому конечному интервалу (например, от -20 до 20), а затем преобразовать числа в целые, округляя их. Это дает нам немного меньше контроля над размером состояния, особенно если мы не знаем точных диапазонов входных значений. Например, в нашем случае 2 из 4 значений не имеют верхних/нижних границ, что может привести к бесконечному количеству состояний.

В нашем примере мы выберем второй подход. Как вы можете заметить позже, несмотря на неопределенные верхние/нижние границы, эти значения редко выходят за пределы определенных конечных интервалов, поэтому состояния с экстремальными значениями будут очень редкими.

  1. Вот функция, которая будет принимать наблюдение из нашей модели и возвращать кортеж из 4 целых значений: (блок кода 6)

    def discretize(x):
        return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))
    
  2. Давайте также исследуем другой метод дискретизации с использованием интервалов: (блок кода 7)

    def create_bins(i,num):
        return np.arange(num+1)*(i[1]-i[0])/num+i[0]
    
    print("Sample bins for interval (-5,5) with 10 bins\n",create_bins((-5,5),10))
    
    ints = [(-5,5),(-2,2),(-0.5,0.5),(-2,2)] # intervals of values for each parameter
    nbins = [20,20,10,10] # number of bins for each parameter
    bins = [create_bins(ints[i],nbins[i]) for i in range(4)]
    
    def discretize_bins(x):
        return tuple(np.digitize(x[i],bins[i]) for i in range(4))
    
  3. Теперь запустим короткую симуляцию и наблюдаем эти дискретные значения среды. Попробуйте использовать как discretize, так и discretize_bins, чтобы увидеть разницу.

    discretize_bins возвращает номер интервала, который начинается с 0. Таким образом, для значений входной переменной около 0 он возвращает число из середины интервала (10). В discretize мы не заботились о диапазоне выходных значений, позволяя им быть отрицательными, поэтому значения состояния не смещены, и 0 соответствует 0. (блок кода 8)

    env.reset()
    
    done = False
    while not done:
       #env.render()
       obs, rew, done, info = env.step(env.action_space.sample())
       #print(discretize_bins(obs))
       print(discretize(obs))
    env.close()
    

    Раскомментируйте строку, начинающуюся с env.render, если хотите увидеть, как выполняется среда. В противном случае вы можете выполнить ее в фоновом режиме, что быстрее. Мы будем использовать это "невидимое" выполнение во время процесса Q-Learning.

Структура Q-таблицы

В предыдущем уроке состояние было простой парой чисел от 0 до 8, и поэтому было удобно представлять Q-таблицу в виде тензора numpy с формой 8x8x2. Если мы используем дискретизацию с интервалами, размер нашего вектора состояния также известен, поэтому мы можем использовать тот же подход и представлять состояние в виде массива формы 20x20x10x10x2 (здесь 2 — это размерность пространства действий, а первые измерения соответствуют количеству интервалов, которые мы выбрали для каждого из параметров в пространстве наблюдений).

Однако иногда точные размеры пространства наблюдений неизвестны. В случае функции discretize мы никогда не можем быть уверены, что наше состояние остается в определенных пределах, потому что некоторые из исходных значений не ограничены. Таким образом, мы будем использовать немного другой подход и представлять Q-таблицу в виде словаря.

  1. Используйте пару (state,action) в качестве ключа словаря, а значение будет соответствовать значению записи Q-таблицы. (блок кода 9)

    Q = {}
    actions = (0,1)
    
    def qvalues(state):
        return [Q.get((state,a),0) for a in actions]
    

    Здесь мы также определяем функцию qvalues(), которая возвращает список значений Q-таблицы для данного состояния, соответствующего всем возможным действиям. Если запись отсутствует в Q-таблице, мы будем возвращать 0 по умолчанию.

Начнем Q-Learning

Теперь мы готовы научить Питера балансировать!

  1. Сначала установим некоторые гиперпараметры: (блок кода 10)

    # hyperparameters
    alpha = 0.3
    gamma = 0.9
    epsilon = 0.90
    

    Здесь alpha — это скорость обучения, которая определяет, в какой степени мы должны корректировать текущие значения Q-таблицы на каждом шаге. В предыдущем уроке мы начинали с 1, а затем уменьшали alpha до более низких значений во время обучения. В этом примере мы будем держать его постоянным для простоты, а вы можете экспериментировать с настройкой значений alpha позже.

    gamma — это коэффициент дисконтирования, который показывает, в какой степени мы должны отдавать предпочтение будущему вознаграждению над текущим.

    epsilon — это фактор исследования/эксплуатации, который определяет, должны ли мы предпочитать исследование или эксплуатацию. В нашем алгоритме мы будем в epsilon процентах случаев выбирать следующее действие в соответствии со значениями Q-таблицы, а в оставшихся случаях выполнять случайное действие. Это позволит нам исследовать области пространства поиска, которые мы никогда раньше не видели.

    В терминах балансировки — выбор случайного действия (исследование) будет действовать как случайный толчок в неправильном направлении, и шесту придется научиться восстанавливать баланс после этих "ошибок".

Улучшение алгоритма

Мы также можем внести два улучшения в наш алгоритм из предыдущего урока:

  • Рассчитать среднее накопленное вознаграждение за ряд симуляций. Мы будем выводить прогресс каждые 5000 итераций и усреднять наше накопленное вознаграждение за этот период времени. Это означает, что если мы получим более 195 очков, мы можем считать задачу решенной, с качеством даже выше требуемого.

  • Рассчитать максимальный средний накопленный результат, Qmax, и мы будем сохранять Q-таблицу, соответствующую этому результату. Когда вы запустите обучение, вы заметите, что иногда средний накопленный результат начинает снижаться, и мы хотим сохранить значения Q-таблицы, которые соответствуют лучшей модели, наблюдаемой во время обучения.

  1. Соберите все накопленные вознаграждения на каждой симуляции в вектор rewards для дальнейшего построения графика. (блок кода 11)

    def probs(v,eps=1e-4):
        v = v-v.min()+eps
        v = v/v.sum()
        return v
    
    Qmax = 0
    cum_rewards = []
    rewards = []
    for epoch in range(100000):
        obs = env.reset()
        done = False
        cum_reward=0
        # == do the simulation ==
        while not done:
            s = discretize(obs)
            if random.random()<epsilon:
                # exploitation - chose the action according to Q-Table probabilities
                v = probs(np.array(qvalues(s)))
                a = random.choices(actions,weights=v)[0]
            else:
                # exploration - randomly chose the action
                a = np.random.randint(env.action_space.n)
    
            obs, rew, done, info = env.step(a)
            cum_reward+=rew
            ns = discretize(obs)
            Q[(s,a)] = (1 - alpha) * Q.get((s,a),0) + alpha * (rew + gamma * max(qvalues(ns)))
        cum_rewards.append(cum_reward)
        rewards.append(cum_reward)
        # == Periodically print results and calculate average reward ==
        if epoch%5000==0:
            print(f"{epoch}: {np.average(cum_rewards)}, alpha={alpha}, epsilon={epsilon}")
            if np.average(cum_rewards) > Qmax:
                Qmax = np.average(cum_rewards)
                Qbest = Q
            cum_rewards=[]
    

Что вы можете заметить из этих результатов:

  • Близко к нашей цели. Мы очень близки к достижению цели получения 195 накопленных вознаграждений за 100+ последовательных запусков симуляции, или мы могли бы фактически достичь ее! Даже если мы получаем меньшие числа, мы все равно не знаем, потому что усредняем за 5000 запусков, а формальный критерий требует только 100 запусков.

  • Вознаграждение начинает снижаться. Иногда вознаграждение начинает снижаться, что означает, что мы можем "разрушить" уже изученные значения в Q-таблице новыми, которые ухудшают ситуацию.

Это наблюдение становится более очевидным, если мы построим график прогресса обучения.

Построение графика прогресса обучения

Во время обучения мы собрали значение накопленного вознаграждения на каждой итерации в вектор rewards. Вот как это выглядит, если построить график зависимости от номера итерации:

plt.plot(rewards)

сырой прогресс

На этом графике невозможно ничего сказать, потому что из-за природы стохастического процесса обучения длина обучающих сессий сильно варьируется. Чтобы придать этому графику больше смысла, мы можем рассчитать скользящее среднее за серию экспериментов, скажем, 100. Это можно удобно сделать с помощью np.convolve: (блок кода 12)

def running_average(x,window):
    return np.convolve(x,np.ones(window)/window,mode='valid')

plt.plot(running_average(rewards,100))

прогресс обучения

Изменение гиперпараметров

Чтобы сделать обучение более стабильным, имеет смысл корректировать некоторые из наших гиперпараметров во время обучения. В частности:

  • Для скорости обучения, alpha, мы можем начать с значений, близких к 1, а затем постепенно уменьшать параметр. Со временем мы будем получать хорошие вероятностные значения в Q-таблице, и поэтому мы должны корректировать их слегка, а не полностью перезаписывать новыми значениями.

  • Увеличить epsilon. Мы можем постепенно увеличивать epsilon, чтобы меньше исследовать и больше использовать. Вероятно, имеет смысл начать с низкого значения epsilon и двигаться к почти 1.

Задача 1: Попробуйте изменить значения гиперпараметров и посмотрите, сможете ли вы достичь более высокого суммарного вознаграждения. Удается ли вам превысить 195? Задача 2: Чтобы формально решить задачу, необходимо достичь среднего вознаграждения в 195 за 100 последовательных запусков. Измеряйте это во время обучения и убедитесь, что задача формально решена!

Просмотр результата в действии

Было бы интересно увидеть, как ведет себя обученная модель. Давайте запустим симуляцию и будем следовать той же стратегии выбора действий, что и во время обучения, выбирая действия в соответствии с вероятностным распределением в Q-таблице: (блок кода 13)

obs = env.reset()
done = False
while not done:
   s = discretize(obs)
   env.render()
   v = probs(np.array(qvalues(s)))
   a = random.choices(actions,weights=v)[0]
   obs,_,done,_ = env.step(a)
env.close()

Вы должны увидеть что-то вроде этого:

балансирующая тележка


🚀Испытание

Задача 3: Здесь мы использовали финальную копию Q-таблицы, которая может быть не самой лучшей. Помните, что мы сохранили лучшую Q-таблицу в переменной Qbest! Попробуйте тот же пример с лучшей Q-таблицей, скопировав Qbest в Q, и посмотрите, заметите ли вы разницу.

Задача 4: Здесь мы не выбирали лучшее действие на каждом шаге, а выбирали действия на основе соответствующего вероятностного распределения. Было бы логичнее всегда выбирать лучшее действие с наибольшим значением в Q-таблице? Это можно сделать с помощью функции np.argmax, чтобы определить номер действия, соответствующий наибольшему значению в Q-таблице. Реализуйте эту стратегию и посмотрите, улучшит ли она балансировку.

Тест после лекции

Задание

Обучите модель для Mountain Car

Заключение

Теперь мы научились обучать агентов достигать хороших результатов, просто предоставляя им функцию вознаграждения, которая определяет желаемое состояние игры, и давая им возможность разумно исследовать пространство поиска. Мы успешно применили алгоритм Q-Learning в случаях дискретных и непрерывных сред, но с дискретными действиями.

Важно также изучить ситуации, когда пространство действий является непрерывным, а пространство наблюдений гораздо более сложным, например, изображение с экрана игры Atari. В таких задачах часто требуется использовать более мощные методы машинного обучения, такие как нейронные сети, чтобы достичь хороших результатов. Эти более сложные темы будут рассмотрены в нашем следующем продвинутом курсе по искусственному интеллекту.


Отказ от ответственности:
Этот документ был переведен с использованием сервиса автоматического перевода Co-op Translator. Хотя мы стремимся к точности, пожалуйста, имейте в виду, что автоматические переводы могут содержать ошибки или неточности. Оригинальный документ на его исходном языке следует считать авторитетным источником. Для получения критически важной информации рекомендуется профессиональный перевод человеком. Мы не несем ответственности за любые недоразумения или неправильные интерпретации, возникшие в результате использования данного перевода.