強化學習的發展歷經了從單智慧體(single agent)到多智慧體(multi-agent)的蛻變,其中2019年是MARL演算法爆發的一年,這一年先後誕生了AlphaStar、OpenAI Five、Pluribus Poker等標誌性的成果。這些標誌性的成果是由一個個基礎演算法逐步發展而來的,今天我們將分別介紹MADDPG演算法(

2017,Ryan Lowe et al。

)和QMIX演算法(

2018,Tabish Rashid et al。

),這兩種演算法對多智慧體的解決思路都是基於一個普遍的框架:集中式訓練,分散式執行。

背景

在多智慧體情形,每個智慧體同單智慧體情形下類似,也是透過與環境互動的試錯過程來進行序貫決策,一個很大的不同之處則是在於智慧體之間會相互影響,因此環境狀態和回報函式其實是取決於所有智慧體的聯合動作(joint actions)。一個直觀的idea是對於其中某個智慧體而言,將其他智慧體的狀態動作等也建模成環境,然後運用單智慧體領域的演算法。然而,將單智慧體系統較為成功的演算法直接引入多智慧體系統時存在很多問題,並不適用,一個較為突出的問題則是,隨著訓練的進行,每個智慧體的策略都在變化,從任何一個獨立的智慧體看來環境都是非靜態的(non-stastionary),這與MDP的靜態性相違背。

多智慧體強化學習問題的一般建模方式是隨機博弈(Stochastic Game),或稱馬爾科夫博弈(Markov Game),用數學語言描述是

(N,\mathcal{S},\{a_i\}_{i=1...N},\mathcal{T},\gamma,\{r_i\}_{i=1...N})

,其中

N

表示智慧體數目

\mathcal{S}

表示所有智慧體共享的環境狀態集

a_i

表示第

i

個智慧體的動作集

\mathcal{T}

表示狀態轉移機率,

\mathcal{T}: \mathcal{S}\times a_1 \times ...\times a_n \to \mathcal{S}

r_i

表示第

i

個智慧體的立即回報

MADDPG

MADDPG,全稱是Multi-Agent Deep Deterministic Policy Gradient,顧名思義,它是DDPG演算法的多智慧體版,其與DDPG的區別,也即它的獨到核心思想之處在於利用全域性資訊學習Critic,利用區域性資訊學習Actor,具體到如下的示意圖:

兩種經典的多智慧體強化學習演算法

MADDPG示意圖

對於每個智慧體而言,都需要維護自身的Actor網路

\pi_i

和Critic網路

Q_i

,所謂的區域性資訊是指

\pi_i

的輸入只需要智慧體自身周圍觀察到的資訊

o_i

,而全域性資訊是指

Q_i

的輸入除了包括環境所有的狀態

x

以外,還要輸入所有智慧體的動作

\{a_i\}_{i=1...N}

。反映到梯度公式,更新Actor網路時:

兩種經典的多智慧體強化學習演算法

其中,

\mathcal{D}

是多智慧體情形下的replay buffer,格式為

(x,x

更新Critic網路時,

兩種經典的多智慧體強化學習演算法

其中,

\mu

是一系列引數為

\theta

的目標網路。

除此之外,在MADDPG原文中還提出了推斷其他策略網路(Inferring Policies of Other Agents)和策略整合(Agents withPolicy Ensemble)兩種技巧,消融實驗結果表明,這兩種技巧對於模型效果的提升都有一定貢獻。

總結起來,演算法偽碼大致如下:

兩種經典的多智慧體強化學習演算法

演算法的一系列實驗是在OpenAI自己開發的multi-agent particle環境下進行的,該環境中既有合作環境,也有競爭環境。以競爭環境Predator-Prey為例,紅色點是三個速度較慢的追捕者,它們需要透過合作抓到更快的綠色小球,每當紅色小球碰到綠色小球,紅色小球就會獲得獎勵,而綠色小球獲得懲罰。演算法最終實現的納什均衡狀態如下:

兩種經典的多智慧體強化學習演算法

跑程式碼的話推薦該演算法一個比較簡潔的pytorch版本倉庫:

QMIX

同MADDPG一樣,QMIX也是集中式訓練,分散式執行的典範,不同之處在於MADDPG是基於DDPG的,不需要考慮如何從總的Q函式中提取分散式策略,而QMIX是基於Q-learning的。QMIX的提出主要是針對星際爭霸II遊戲,由於對一個聯合動作-狀態只有一個總獎勵值,而不是每個智慧體得到一個自己的獎勵值,因此只能用於合作環境,而不能用於競爭對抗環境。

具體來看,在部分可觀測的馬爾科夫環境中,定義單個智慧體的動作-觀測軌跡為

\tau_i=(a_{i,0},o_{i,1},...a_{i,t-1},o_{i,t})

,聯合的動作-觀測軌跡為

\tau=(\tau_1,\tau_2,...,\tau_n)

,對於單個智慧體的區域性值函式為

Q_i(\tau_i,a_i;\theta_i)

,總的聯合動作值函式為

Q_{tot}

QMIX是對VDN(Value Decomposition Network)方法的改進,在VDN方法中,認為聯合動作值函式是區域性值函式的線性相加,即

Q_{tot}=\sum_{i=1}^{n}{Q_i(\tau_i,a_i;\theta_i)}

,這樣簡單的線性相加很好地滿足了聯合值函式與區域性值函式之間的單調性,從而可以提取分散式策略,即:

\arg\max_a Q_{tot}=\begin{pmatrix} \arg\max_{a_1} Q_{1}(\tau_1,a_1) \\.\\.\\ \arg\max_{a_n} Q_{n}(\tau_n,a_n)  \end{pmatrix}

但線性結構的值函式表達能力有限,在有的任務上表現也不盡人意。於是QMIX的作者提出用非線性結構來表徵聯合值函式,把上述的連加符號換成多層感知機,

Q_{tot}=MLP({Q_i(\tau_i,a_i;\theta_i)})

,多層感知機的具體結構如圖(a):狀態向量

s_t

透過超網路(hybernetwork)和絕對值啟用函式生成非負的權重向量和偏重向量,從而保證單調性。在Mixing網路的輸入Q值向量是由DRQN網路輸出的(如圖c),這種帶LSTM/GRU的網路結構在處理部分可觀的馬爾科夫問題具有一定的優勢。

兩種經典的多智慧體強化學習演算法

總的來說,QMIX演算法的創新性在於設計了特定的神經網路結構來保證聯合值函式與區域性值函式之間的單調性,我們再從程式碼的角度來看看每個部分的結構。

Mixing Network:

import

torch。nn

as

nn

import

torch

import

torch。nn。functional

as

F

class

QMixNet

nn

Module

):

def

__init__

self

args

):

super

QMixNet

self

__init__

()

self

args

=

args

# 因為生成的hyper_w1需要是一個矩陣,而pytorch神經網路只能輸出一個向量,

# 所以就先輸出長度為需要的 矩陣行*矩陣列 的向量,然後再轉化成矩陣

# args。n_agents是使用hyper_w1作為引數的網路的輸入維度,args。qmix_hidden_dim是網路隱藏層引數個數

# 從而經過hyper_w1得到(經驗條數,args。n_agents * args。qmix_hidden_dim)的矩陣

if

args

two_hyper_layers

self

hyper_w1

=

nn

Sequential

nn

Linear

args

state_shape

args

hyper_hidden_dim

),

nn

ReLU

(),

nn

Linear

args

hyper_hidden_dim

args

n_agents

*

args

qmix_hidden_dim

))

# 經過hyper_w2得到(經驗條數, 1)的矩陣

self

hyper_w2

=

nn

Sequential

nn

Linear

args

state_shape

args

hyper_hidden_dim

),

nn

ReLU

(),

nn

Linear

args

hyper_hidden_dim

args

qmix_hidden_dim

))

else

self

hyper_w1

=

nn

Linear

args

state_shape

args

n_agents

*

args

qmix_hidden_dim

# 經過hyper_w2得到(經驗條數, 1)的矩陣

self

hyper_w2

=

nn

Linear

args

state_shape

args

qmix_hidden_dim

*

1

# hyper_w1得到的(經驗條數,args。qmix_hidden_dim)矩陣需要同樣維度的hyper_b1

self

hyper_b1

=

nn

Linear

args

state_shape

args

qmix_hidden_dim

# hyper_w2得到的(經驗條數,1)的矩陣需要同樣維度的hyper_b1

self

hyper_b2

=

nn

Sequential

nn

Linear

args

state_shape

args

qmix_hidden_dim

),

nn

ReLU

(),

nn

Linear

args

qmix_hidden_dim

1

def

forward

self

q_values

states

):

# states的shape為(episode_num, max_episode_len, state_shape)

# 傳入的q_values是三維的,shape為(episode_num, max_episode_len, n_agents)

episode_num

=

q_values

size

0

q_values

=

q_values

view

-

1

1

self

args

n_agents

# (episode_num * max_episode_len, 1, n_agents) = (1920,1,5)

states

=

states

reshape

-

1

self

args

state_shape

# (episode_num * max_episode_len, state_shape)

w1

=

torch

abs

self

hyper_w1

states

))

# (1920, 160)

b1

=

self

hyper_b1

states

# (1920, 32)

w1

=

w1

view

-

1

self

args

n_agents

self

args

qmix_hidden_dim

# (1920, 5, 32)

b1

=

b1

view

-

1

1

self

args

qmix_hidden_dim

# (1920, 1, 32)

hidden

=

F

elu

torch

bmm

q_values

w1

+

b1

# (1920, 1, 32)

w2

=

torch

abs

self

hyper_w2

states

))

# (1920, 32)

b2

=

self

hyper_b2

states

# (1920, 1)

w2

=

w2

view

-

1

self

args

qmix_hidden_dim

1

# (1920, 32, 1)

b2

=

b2

view

-

1

1

1

# (1920, 1, 1)

q_total

=

torch

bmm

hidden

w2

+

b2

# (1920, 1, 1)

q_total

=

q_total

view

episode_num

-

1

1

# (32, 60, 1)

return

q_total

Agent Network:

import

torch。nn

as

nn

import

torch。nn。functional

as

f

class

RNN

nn

Module

):

# Because all the agents share the same network, input_shape=obs_shape+n_actions+n_agents

def

__init__

self

input_shape

args

):

super

RNN

self

__init__

()

self

args

=

args

self

fc1

=

nn

Linear

input_shape

args

rnn_hidden_dim

self

rnn

=

nn

GRUCell

args

rnn_hidden_dim

args

rnn_hidden_dim

self

fc2

=

nn

Linear

args

rnn_hidden_dim

args

n_actions

def

forward

self

obs

hidden_state

):

x

=

f

relu

self

fc1

obs

))

h_in

=

hidden_state

reshape

-

1

self

args

rnn_hidden_dim

h

=

self

rnn

x

h_in

q

=

self

fc2

h

return

q

h

程式碼是來自這個倉庫: