\documentclass{article}

\input{../common-preamble}
\input{../bmstu-preamble}
\input{../fancy-listings-preamble}

\numerationTop

\begin{document}
\fontsize{14pt}{14pt}\selectfont
\pagestyle{empty}
\makeBMSTUHeader

\makeReportTitle{лабораторной}{3}{Разработка программы глубокого обучения мультиагентной системы}{Мультиагентные интеллектуальные системы}{}{Большаков В.Э.}
\newpage
\pagestyle{fancy}
\tableofcontents
\newpage
\sloppy
\section{Цель работы}
Разработать лучшей программы глубокого обучения мультиагентной системы.

\section{Задачи}
\begin{enumerate}
\item Создать команду, придумать или адаптировать стратегию игры, разработать и обучить бота используя технологии Deep Reinforcement Learning и библиотеки глубокого обучения Keras, TensorFlow или PyTorch.
\item Для создания проекта использовать виртуальное окружение (virtual environment), а для защиты проекта предоставить версии
использованных библиотек.
\item Обучить агентов и набрать как можно больше очков в мультиагентной среде.
\end{enumerate}

\section{Теоретическая часть}
Описание среды в приложениях \hrf{appendix:MPE} и \hrf{appendix:adversary}

\subsection{Многоагентные среды}
Большинство разработок в области обучения с подкреплением относятся к одноагентным областям. Однако существует ряд важных примеров, когда несколько агентов одновременно обучаются для координации своих действий. В многоагентных средах из-за постоянно меняющейся политики принятия решений агентами существует одна серьезная проблема -- нестационарность среды с точки зрения каждого отдельного агента.

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

\subsection{Централизованная и децентрализованная архитектуры}
При децентрализованном обучении агенты обучаются отдельно друг от друга. Каждый агент обучает свою сеть политик, используя в качестве входных данных локальные наблюдения. Чтобы справиться с нестационарностью, обычно используется некоторая форма самовоспроизведения, когда агенты играют со своими текущими или предыдущими версиями, чтобы выучить оптимальную политику.

При централизованном обучении агенты коллективно моделируются определенным образом. В 2017 предложили модель MADDPG, многоагентную централизованную архитектуру, использующую детерминированный алгоритм градиента политики. Этот алгоритм, основанный на архитектуре "актор-критик", имеет централизованного критика и децентрализованного агента. Централизованный критик советует, как обновить политику агента, глядя на глобальную информацию. Поскольку каждый агент имеет доступ к наблюдениям и действиям всех других агентов через критика, оценка градиента политики обусловлена политикой других агентов, и, таким образом, решается проблема нестационарности. Во время тестирования централизованный критик удаляется, остаются только агенты, их политики и локальные наблюдения.

Общий алгоритм Q-обучения выглядит следующим образом:
\[ Q^{new}(s_t, a_t) \gets (1-\alpha) \cdot Q(s_t, a_t) + \alpha (r_t + \gamma \cdot max_a(Q(s_{t+1}, a)))\]

То есть агент делает следующий шаг (переходит в новое состояние) исходя не только из локально оптимального решения (жадный алгоритм), но и потенциальной будущей общей возможной награды. На каждый новый шаг агента, кроме коэффициента обучаемости, также накладывается дисконтирующий фактор и выдаётся непосредственная награда.

\subsection{MADDPG}
Multi-Agent Deep Deterministic Policy Gradient

\begin{figure}[H]
  \centering
  \includegraphics[width=6cm]{03-mas-lab-03-maddpg.png}
  \caption{Мультагентное централизованное градиентное поведение}
  \label{pic:}
\end{figure}

В то время как в DDPG у нас есть только один агент. Здесь у нас есть несколько агентов со своими собственными сетями агентов и критиков. Актор-сеть принимает на вход локальное наблюдение агента и выдает рекомендуемое действие, так что ничего нового по сравнению с актор-сетью DDPG. Напротив, сеть критика принимает на вход глобальное наблюдение среды, поэтому она принимает на вход не только состояние среды и действия агента, но и локальные наблюдения и действия других агентов. Выходом критической сети по-прежнему будет оценка Q-значения. Таким образом, критическая сеть используется только в обучающей части, чтобы дать агенту глобальное видение для адаптации к глобальной среде и действиям других агентов. Таким образом, агенты могут научиться стратегии сотрудничества или конкуренции. Далее, поскольку обучение каждого агента обусловлено наблюдением и действиями всех других агентов, каждый агент воспринимает окружающую среду как стационарную.

\section{Выполнение}
На первом этапе обучения была выбрана сеть MADDPG со следующими гиперпараметрами обучения:
\begin{itemize}
\item Эпизодов обучения -- 1100000
\item шум -- 0.1
\item batch-size -- 256
\item learn rate actor = 1e-4
\item learn rate critic = 1e-3
\item $\tau =  0.01$
\item $\varepsilon = 0.1$
\item $\gamma = 0.95$
\end{itemize}

И актор и критик каждого агента обучались с одинаковой нейросетью внутри:
\begin{itemize}
\item 3 слоя
\item 64 нейрона на слое
\item Adam
\end{itemize}

В результате обучения наилучий коэффициент $-6.469$ (усреднённый по тысяче эпизодов) по сумме агентов  был получен на миллионном эпизоде обучения. Графики обучения представлены на рис. \hrf{pic:first}. Полный график на рис. \hrf{pic:first-full}, также представлен график обучения (\hrf{pic:first-top}), на котором убраны явно случайные действия агентов в начале обучения.
\begin{figure}[H]
  \centering
  \begin{subfigure}[b]{0.95\textwidth}
    \centering
    \resizebox{\textwidth}{!}{\input{pics/03-mas-lab-03-simplefull.pgf}}    
    \caption{Полный}
    \label{pic:first-full}
  \end{subfigure}

  \begin{subfigure}[b]{0.95\textwidth}
    \centering
    \resizebox{\textwidth}{!}{\input{pics/03-mas-lab-03-simple.pgf}}    
    \caption{Без случайных ходов}
    \label{pic:first-top}
  \end{subfigure}
  \caption{Первый вариант обучения}
  \label{pic:first}
\end{figure}

Далее были опробованы несколько вариантов:
\begin{enumerate}
\item
  \begin{itemize}
  \item Эпизодов обучения -- 1000000
  \item шум -- 0.1
  \item batch-size -- 256
  \item learn rate actor = 1e-4
  \item learn rate critic = 1e-3
  \item $\tau =  0.01$
  \item $\varepsilon = 0.1$
  \item $\gamma = 0.9$
  \end{itemize}

  Актор и критик каждого агента обучались со следующими настройками нейросетей:
  \begin{itemize}
  \item Актор: 4 слоя, 256 нейронов, Adam
  \item Критик: 5 слоёв, 128 нейронов, Adamax
  \end{itemize}

  В результате обучения наилучий коэффициент $-4.569$ (усреднённый по тысяче эпизодов) по сумме агентов был получен на миллионном эпизоде обучения. Графики обучения представлены на рис. \hrf{pic:me2}.

  \begin{figure}[H]
    \centering
    \resizebox{\textwidth}{!}{\input{pics/03-mas-lab-03-me2.pgf}}
    \caption{ }
    \label{pic:me2}
  \end{figure}

\item Далее был проведён эксперимент со следующими параметрами: 
  \begin{itemize}
  \item шум -- 0.1
  \item batch-size -- 256
  \item learn rate actor = 1e-4
  \item learn rate critic = 1e-3
  \item $\tau =  0.02$
  \item $\varepsilon = 0.1$
  \item $\gamma = 0.95$
  \end{itemize}

  Актор и критик каждого агента обучались со следующими настройками нейросетей:
  \begin{itemize}
  \item Актор: 4 слоя, 128 нейронов, AdamW
  \item Критик: 3 слоя, 256 нейронов, RMSprop
  \end{itemize}

  В первой части обучения было проведено 200000 эпизодов обучения, на 160000 эпизодах был достигнут локальный максимум $-19.613$ (рис. \hrf{tanya}), настройки сети были изменены (дисконтирующий множитель уменьшен до 0.9, а размер пакета batch-size увеличен до значения 512) и проведено ещё 460000 циклов обучения.

  \begin{figure}[H]
    \centering
    \resizebox{\textwidth}{!}{\input{pics/03-mas-lab-03-tanya.pgf}}
    \caption{ }
    \label{pic:tanya}
  \end{figure}
  

  Эксперимент показал, что значительно снижать дисконтирующий фактор даже для неплохо действующей нейросети -- плохая идея, агенты начинают вести себя почти случайным образом, видимо, надеясь на то, что уже достаточно хорошо изучили среду и за них всю награду заработает другой агент (рис. \hrf{tanya-after})

  \begin{figure}[H]
    \centering
    \resizebox{\textwidth}{!}{\input{pics/03-mas-lab-03-tanya-after.pgf}}
    \caption{ }
    \label{pic:tanya-after}
  \end{figure}
  
\item
  \begin{itemize}
  \item Эпизодов обучения -- 1100000
  \item шум -- 0.1
  \item batch-size -- 256
  \item learn rate actor = 1e-4
  \item learn rate critic = 1e-3
  \item $\tau =  0.01$
  \item $\varepsilon = 0.1$
  \item $\gamma = 0.95$
  \end{itemize}

  Актор и критик каждого агента обучались с одинаковыми настройками нейросетей: 4 слоя, 128 нейронов, Adamax.

  Заданные настройки показали наилучший результат ($-1.965$), приведённый на графике \hrf{pic:lama} для уточнения, из графика были удалены начальные этапы обучения со случайными действиями агентов, финальный этап обучения представлен на рис \hrf{pic:lamashort}.
  \begin{figure}[H]
  \centering
  \begin{subfigure}[b]{0.95\textwidth}
    \centering
    \resizebox{\textwidth}{!}{\input{pics/03-mas-lab-03-Lama.pgf}}    
    \caption{Полный}
    \label{pic:lama}
  \end{subfigure}

  \begin{subfigure}[b]{0.95\textwidth}
    \centering
    \resizebox{\textwidth}{!}{\input{pics/03-mas-lab-03-Lamashort.pgf}}    
    \caption{Без случайных ходов}
    \label{pic:lamashort}
  \end{subfigure}
  \caption{Вариант обучения с алгоритмом Adamax}
  \label{pic:first}
  \end{figure}
\end{enumerate}

%теоретическая часть, графики экспериментов, описание стратегии мультиагентной игры, листинг программы с комментариями, список использованной литературы) + видеофайл.

\section{Выводы}
В результате достаточно долгого обучения агенты «понимают», какие действия приводят, если не к награде, то хотя бы к отсутствию наказания, и если не пытаются добраться до «зелёной точки», то хотя бы разделяются так, чтобы красный агент не преследовал их. Все стандартные сети, проверенные в этой работе обучаются до «приемлемых» коэффициентов $\approx < 20$, в то время как попытка обучения с постепенно увеличивающимся множителем коэффициента обучения (для актора 1е-3 через 50000, и 150000 эпизодов был повышен до 1,5е-3 и 1е-4, соответственно),  к ощутимым результатам не привели, график общей оценки продолжил носить псевдослучайный характер и не был приведён в этом отчёте.

К отчёту приложен архив с двумя видео наиболее обученных агентов и архив приложения с Jupyter Notebook (Google Colab) для обучения и запуска среды на обучение и непосредственную игру. Начальный вариант возможно найти в репозитории \href{https://git.iovchinnikov.ru/ivan-igorevich/bmstu-marl}{https://git.iovchinnikov.ru/ivan-igorevich/bmstu-marl}.

\printbibliography

\newpage
\appendix
\setcounter{secnumdepth}{3}
\section*{Приложения}
\addcontentsline{toc}{section}{Приложения}
\renewcommand{\thesubsection}{\Asbuk{subsection}}

\subsection{MADDPG настройки и классы актора, критика}
\label{appendix:maddpg}
\begin{lstlisting}[language=Python,style=PyCodeStyle]
import torch
import torch.nn as nn
import torch.nn.functional as F


# define the actor network
class Actor(nn.Module):
    def __init__(self, args, agent_id):
        super(Actor, self).__init__()
        self.max_action = args.high_action
        self.fc1 = nn.Linear(args.obs_shape[agent_id], 128)
        self.fc2 = nn.Linear(128, 128)
        self.fc3 = nn.Linear(128, 128)
        self.fc4 = nn.Linear(128, 128)
        self.action_out = nn.Linear(128, args.action_shape[agent_id])

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.relu(self.fc3(x))
        x = F.relu(self.fc4(x))
        actions = self.max_action * torch.tanh(self.action_out(x))

        return actions


class Critic(nn.Module):
    def __init__(self, args):
        super(Critic, self).__init__()
        self.max_action = args.high_action
        self.fc1 = nn.Linear(sum(args.obs_shape) + sum(args.action_shape), 128)
        self.fc2 = nn.Linear(128, 128)
        self.fc3 = nn.Linear(128, 128)
        self.fc4 = nn.Linear(128, 128)
        self.q_out = nn.Linear(128, 1)

    def forward(self, state, action):
        state = torch.cat(state, dim=1)
        for i in range(len(action)):
            action[i] /= self.max_action
        action = torch.cat(action, dim=1)
        x = torch.cat([state, action], dim=1)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.relu(self.fc3(x))
        x = F.relu(self.fc4(x))
        q_value = self.q_out(x)
        return q_value

        import torch
import os
from maddpg.actor_critic import Actor, Critic


class MADDPG:
    def __init__(self, args, agent_id):
        self.args = args
        self.agent_id = agent_id
        self.train_step = 0

        # create the network
        self.actor_network = Actor(args, agent_id)
        self.critic_network = Critic(args)

        # build up the target network
        self.actor_target_network = Actor(args, agent_id)
        self.critic_target_network = Critic(args)

        # load the weights into the target networks
        self.actor_target_network.load_state_dict(self.actor_network.state_dict())
        self.critic_target_network.load_state_dict(self.critic_network.state_dict())

        # create the optimizer
        self.actor_optim = torch.optim.Adamax(self.actor_network.parameters(), lr=self.args.lr_actor)
        self.critic_optim = torch.optim.Adamax(self.critic_network.parameters(), lr=self.args.lr_critic)

        # create the dict for store the model
        if not os.path.exists(self.args.save_dir):
            os.mkdir(self.args.save_dir)
        # path to save the model
        self.model_path = self.args.save_dir + '/' + self.args.scenario_name
        if not os.path.exists(self.model_path):
            os.mkdir(self.model_path)
        self.model_path = self.model_path + '/' + 'agent_%d' % agent_id
        if not os.path.exists(self.model_path):
            os.mkdir(self.model_path)

        if os.path.exists(self.model_path + '/actor_params.pkl'):
            self.actor_network.load_state_dict(torch.load(self.model_path + '/actor_params.pkl'))
            self.critic_network.load_state_dict(torch.load(self.model_path + '/critic_params.pkl'))
            print('Agent {} successfully loaded actor_network: {}'.format(self.agent_id,
                                                                          self.model_path + '/actor_params.pkl'))
            print('Agent {} successfully loaded critic_network: {}'.format(self.agent_id,
                                                                           self.model_path + '/critic_params.pkl'))

    # soft update
    def _soft_update_target_network(self):
        for target_param, param in zip(self.actor_target_network.parameters(), self.actor_network.parameters()):
            target_param.data.copy_((1 - self.args.tau) * target_param.data + self.args.tau * param.data)

        for target_param, param in zip(self.critic_target_network.parameters(), self.critic_network.parameters()):
            target_param.data.copy_((1 - self.args.tau) * target_param.data + self.args.tau * param.data)

    # update the network
    def train(self, transitions, other_agents):
        for key in transitions.keys():
            transitions[key] = torch.tensor(transitions[key], dtype=torch.float32)
        r = transitions['r_%d' % self.agent_id]  # reward
        o, u, o_next = [], [], []  # agent
        for agent_id in range(self.args.n_agents):
            o.append(transitions['o_%d' % agent_id])
            u.append(transitions['u_%d' % agent_id])
            o_next.append(transitions['o_next_%d' % agent_id])

        # calculate the target Q value function
        u_next = []
        with torch.no_grad():
            index = 0
            for agent_id in range(self.args.n_agents):
                if agent_id == self.agent_id:
                    u_next.append(self.actor_target_network(o_next[agent_id]))
                else:
                    # other_agents
                    u_next.append(other_agents[index].policy.actor_target_network(o_next[agent_id]))
                    index += 1
            q_next = self.critic_target_network(o_next, u_next).detach()

            target_q = (r.unsqueeze(1) + self.args.gamma * q_next).detach()

        # the q loss
        q_value = self.critic_network(o, u)
        critic_loss = (target_q - q_value).pow(2).mean()

        # the actor loss 
        u[self.agent_id] = self.actor_network(o[self.agent_id])
        actor_loss = - self.critic_network(o, u).mean()
        # if self.agent_id == 0:
        #     print('critic_loss is {}, actor_loss is {}'.format(critic_loss, actor_loss))
        # update the network
        self.actor_optim.zero_grad()
        actor_loss.backward()
        self.actor_optim.step()
        self.critic_optim.zero_grad()
        critic_loss.backward()
        self.critic_optim.step()

        self._soft_update_target_network()
        if self.train_step > 0 and self.train_step % self.args.save_rate == 0:
            self.save_model(self.train_step)
        self.train_step += 1

    def save_model(self, train_step):
        num = str(train_step // self.args.save_rate)
        model_path = os.path.join(self.args.save_dir, self.args.scenario_name)
        if not os.path.exists(model_path):
            os.makedirs(model_path)
        model_path = os.path.join(model_path, 'agent_%d' % self.agent_id)
        if not os.path.exists(model_path):
            os.makedirs(model_path)
        torch.save(self.actor_network.state_dict(), model_path + '/' + num + '_actor_params.pkl')
        torch.save(self.critic_network.state_dict(),  model_path + '/' + num + '_critic_params.pkl')
\end{lstlisting}


\subsection{Описание PettingZoo MPE}
\label{appendix:MPE}
\nocite{mordatch2017emergence}
\nocite{lowe2017multi}
Среды с несколькими частицами (MPE) — это набор коммуникационно-ориентированной среды, в которой агенты частиц могут (иногда) перемещаться, общаться, видеть друг друга, толкать друг друга и взаимодействовать с фиксированными ориентирами.

Эти среды взяты из кодовой базы OpenAI MPE с несколькими незначительными исправлениями, в основном связанными с дискретным пространством действий по умолчанию, обеспечением согласованности вознаграждений и очисткой пространства наблюдения определенных сред.

\paragraph{Типы сред} Среды Simple Adversary, Simple Crypto, Simple Push, Simple Tag и Simple World Comm являются враждебными («хороший» агент, получающий вознаграждение, означает, что «противник» наказывается, и наоборот, хотя и не всегда с нулевой суммой). В большинстве таких сред «хорошие» агенты отображаются зеленым цветом, а команда «противников» — красным. Среды Simple Reference, Simple Speaker Listener и Simple Spread носят кооперативный характер (агенты должны работать вместе для достижения своих целей и получать различные вознаграждения, основанные на их собственном успехе и успехах других агентов).

\paragraph{Ключевые идеи}
\begin{itemize}
\item Ориентиры: ориентиры — это статические круглые элементы окружающей среды, которые нельзя контролировать. В некоторых средах, таких как Simple, это места назначения, которые влияют на вознаграждение агентов в зависимости от того, насколько близко к ним находятся агенты. В других средах они могут быть препятствиями, блокирующими движение агентов. Более подробно они описаны в документации для каждой среды.
\item Видимость: когда агент виден другому агенту, наблюдение этого другого агента содержит относительное положение первого агента (а в Simple World Comm и Simple Tag — скорость первого агента). Если агент временно скрыт (возможно только в Simple World Comm), тогда позиция и скорость агента обнуляются.
\item Коммуникация: некоторые агенты в некоторых средах могут транслировать сообщение как часть своего действия (дополнительную информацию см. в области действия), которое будет передано каждому агенту, которому разрешено видеть это сообщение. В Simple Crypto это сообщение используется, чтобы сигнализировать о том, что Боб и Ева реконструировали сообщение.
\item Цвет: поскольку все агенты отображаются в виде кругов, агенты могут быть идентифицированы человеком только по их цвету, поэтому цвет агентов описывается в большинстве сред. Агенты не наблюдают цвет.
\item Расстояния: Ориентиры и агенты обычно начинаются с равномерного случайного размещения на карте от -1 до 1. Это означает, что они обычно находятся на расстоянии около 1-2 единиц друг от друга. Это важно иметь в виду, рассуждая о шкале наград (которая часто зависит от расстояния) и пространстве наблюдения, которое содержит относительные и абсолютные положения.
\end{itemize}

\paragraph{Прекращение} Игра завершается после выполнения количества циклов, указанного аргументом среды \code{max_cycles}. По умолчанию для всех сред установлено значение 25 циклов, как и в исходном коде OpenAI.

\paragraph{Пространство наблюдения} Пространство наблюдения агента представляет собой вектор, обычно состоящий из положения и скорости агента, относительных положений и скоростей других агентов, относительных положений ориентиров, типов ориентиров и агентов, а также сообщений, полученных от других агентов. Точная форма этого подробно описана в документации по средам.

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

\paragraph{Пространство действий} Примечание. MPE OpenAI по умолчанию использует непрерывные пространства действий.

Дискретное пространство действий (по умолчанию):

Пространство действий — это дискретное пространство действий, представляющее комбинации движений и коммуникаций, которые может выполнять агент. Агенты, которые могут двигаться, могут выбирать между 4 основными направлениями или ничего не делать. Агенты, которые могут общаться, выбирают от 2 до 10 опций связи в зависимости от среды, которые передают сообщение всем агентам, которые могут его услышать.

Пространство непрерывного действия (установлено параметром \code{Continuous_actions=True}):

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

\paragraph{Рендеринг} Рендеринг отображает сцену в окне, которое автоматически увеличивается, если агенты выходят за его границы. Общение отображается в нижней части сцены. Метод render() также возвращает карту пикселей визуализируемой области.

\subsection{Описание Simple Adversary}
\label{appendix:adversary}
В этой среде есть 1 противник (красный), N хороших агентов (зеленый), N ориентиров (по умолчанию N=2). Все агенты наблюдают за положением ориентиров и других агентов. Одним из ориентиров является «целевой ориентир» (зеленый цвет). Хорошие агенты вознаграждаются в зависимости от того, насколько близко один из них находится к целевому ориентиру, но отрицательно вознаграждаются в зависимости от того, насколько близко противник находится к целевому ориентиру. Противник получает вознаграждение в зависимости от расстояния до цели, но он не знает, какой ориентир является целевым ориентиром. Все награды представляют собой немасштабированное евклидово расстояние (см. основную документацию MPE для среднего расстояния). Это означает, что хорошие агенты должны научиться «разделяться» и охватывать все ориентиры, чтобы обмануть противника.

Пространство наблюдения агента: [\code{self_pos}, \code{self_vel}, \code{goal_rel_position}, \code{landmark_rel_position}, \code{other_agent_rel_positions}]

Зона наблюдения противника: [\code{landmark_rel_position}, \code{other_agents_rel_positions}]

Пространство действий агента: [\code{no_action}, \code{move_left}, \code{move_right}, \code{move_down}, \code{move_up}]

Пространство действий противника: [\code{no_action}, \code{move_left}, \code{move_right}, \code{move_down}, \code{move_up}]

Аргументы
\code{simple_adversary_v2.env(N=2, max_cycles=25, Continuous_actions=False)}
N: количество хороших агентов и ориентиров

\code{max_cycles}: количество кадров (шаг для каждого агента) до завершения игры

\code{Continuous_actions}: являются ли пространства действий агента дискретными (по умолчанию) или непрерывными.

\end{document}
