論文學習「MDP」:馬爾可夫決策過程原理與代碼實現

最近在學習 RL ,不得不先接觸一下“ 馬爾可夫決策過程 ”,這裏找到了 David Silver 的課程: UCL Course on RL

http://www0.cs.ucl.ac.uk/staff/d.silver/web/Teaching.html),這裏我將按課程 PPT 中的順序講述我的理解已經如何用代碼實現相應的計算過程。

目錄

一、馬爾可夫過程(Markov Process)

(一)MDPs論述

(二)馬爾科夫特性

(三)狀態轉移矩陣

(四)馬爾可夫過程

(五)樣例

二、馬爾可夫報酬過程(Markov Reward Process)

(一)馬爾可夫報酬過程

(二)回報

(三)值函數

(四)樣例

(五)MRPs的Bellman等式

(六)Bellman等式

(七)Bellman等式的矩陣形式

三、馬爾可夫決策過程(Markov Decision Process)

(一)馬爾可夫決策過程

(二)策略

(三)值函數

(四)Bellman等式的矩陣形式

四、最優值函數(Optimal Value Function)


一、馬爾可夫過程(Markov Process)

(一)MDPs論述

馬爾可夫決策過程形式化地描述了強化學習的環境 ,這裏的環境是完全可見的,例如,當前的狀態完全描述了這個過程。幾乎所有的 RL 問題都可以形式化地表示爲 MDPs

(二)馬爾科夫特性

The future is independent of the past given the present ”,也就是說對當前而言,未來與過去是毫無關係的。馬爾可夫狀態定義如下圖1

圖1:馬爾科夫狀態

上訴定義的意思是:當前狀態的前提下,下個狀態發生的概率 = 當前狀態及其之前所有狀態的前提下,下一個狀態發生的概率。可以理解爲,當前狀態已經包含了歷史所有信息。

(三)狀態轉移矩陣

定義了一個從當前馬爾可夫狀態 s 到後續狀態 s^{'} 的 state transition probability (狀態轉移概率):

同時定義了一個從所有當前狀態 s 到後續狀態 s^{'} 的狀態轉移矩陣 P

 這個很好理解,比如 P_{ij} 表示從狀態 i 到狀態 j 的概率,注意可以存在從狀態 i 轉移到狀態 i 的可能性,而且矩陣 P 的每一行的和爲 1

(四)馬爾可夫過程

馬爾可夫過程是一個無記憶的隨機過程,例如一個滿足馬爾可夫特性的隨機狀態序列 S_{1},S_{2},... ,定義如下圖2

圖2:馬爾科夫過程

馬爾可夫過程(又叫馬爾可夫鏈)是一個元組 <S,P>  ,S 是一個有限狀態集, P 是上訴狀態轉移概率矩陣。簡單地理解就是給一個狀態,再給一個轉移概率,就會發生一個“ 過程 ” 。

(五)樣例

David Silver 給出了一個研究生的例子,如圖3

圖3:研究生馬爾科夫鏈

可以看出從 S_{1}=C_{1} 開始存在無數個馬爾可夫鏈。

  • C_{1}\: C_{2}\: C_{3}\: Pass\: Sleep
  • C_{1}\: FB\: FB\: C_{1}\: C_{2}\: Sleep
  • ...

同時,從圖3 中我們也可以得到狀態轉移概率矩陣 P

這裏需要注意的一點是,作者認爲 David Silver 的課程 PPT 這裏存在一點問題,即 P_{Sleep,Sleep} 應該爲 0 ,如果用 PPT 中的 1 ,在後續計算過程中存在十分明顯的錯誤,而且從圖3 看到,狀態 Sleep 到狀態 Sleep 之間並不存在轉移情況。

截止到這裏的內容比較容易理解,就不過多贅述了,下面開始詳述馬爾可夫報酬過程。

二、馬爾可夫報酬過程(Markov Reward Process)

(一)馬爾可夫報酬過程

馬爾可夫報酬過程是具有價值的馬爾可夫鏈,定義如下圖4

圖4:馬爾可夫報酬過程

這裏增加了報酬函數 R_{s} ,表示當前狀態 S_{t} = s 情況下,下一個階段的報酬 R_{t+1} 的期望。折扣因子 \gamma\in [0,1] 。研究生馬爾可夫報酬過程如下圖5 所示:

圖5:研究生馬爾可夫報酬過程

(二)回報

這裏我先用語言描述一下馬爾可夫決策過程的回報。首先我們設想一下,現在我們處於“ 大學生 ”狀態,之後可能的狀態有“ 讀研 ”和“ 找工作 ”,選擇不同的“ 狀態 ”將會有不同的“ 未來 ”,未來的“ 收入 ”當然也就有所區別了。現在我們假設選擇了“ 讀研 ”,以經濟學中“ 折現 ”的概念來理解未來的“ 收入 ”,我們現在的選擇其實已經一定程度上轉換成了“ 一定折扣的未來收入 ”。這就是所謂的回報,用公式表示如下圖6

圖6:回報

這裏的 return G_{t}  表示從 t 步開始全部的折扣報酬,其中折扣因子 \gamma 表示未來報酬的當前價值。 \gamma  越小表示“ 目標短淺 ”只專注於眼前, \gamma  越大表示“ 遠見 ”決策者更關注於對未來的展望。

(三)值函數

就上訴回報內容,值函數 v(s) 給出了狀態 s 的長期價值,定義如下圖7

圖7:值函數

G_{t}  表示確定的狀態序列下的未來報酬的折扣,但是我們得清楚,在實際決策過程中,狀態轉移是依據狀態轉移概率矩陣 P 來轉移的,所以從當前狀態 s 開始的 MRP 期望就得用上述定義來表示了。

(四)樣例

這裏以 \gamma =\frac{1}{2}S_{1}=C_{1} 爲例,計算 G_{1}

  • C_{1}\: C_{2}\: C_{3}\: Pass\: Sleep          -2-2*\frac{1}{2}-2*\frac{1}{4}+10*\frac{1}{8}=-2.25
  • C_{1}\: FB\: FB\: C_{1}\: C_{2}\: Sleep     -2-1*\frac{1}{2}-1*\frac{1}{4}-2*\frac{1}{8}-2*\frac{1}{16}=-3.125
  • ...

這裏讀者可能會想,每一個狀態的馬爾可夫鏈不是無數個嗎,給定一個馬爾可夫鏈求 G_{t} 容易,但是求值函數 v(s) 卻是幾乎不可能的,這裏藉助於 Bellman 等式能夠很好的解決這個問題。

(五)MRPs的Bellman等式

計算過程如下,讀者就自行理解吧!

然後給出了值函數 v(s) 的計算公式:

爲了理解這個公式,直接給出一張圖8

圖8:Bellman Equation for MRPs

這裏需要注意一點,作者認爲 David Silver 的課程 PPT r 的位置存在一點瑕疵,容易誤導讀者,正確的位置應該是箭頭所指方向。

解釋一下吧,圖8 中,狀態 s 對應 v(s) ,狀態 s^{'} 對應 v(s^{'})r 表示當前狀態 s 在下一階段的報酬。我們再看看值函數 v(s) 的計算公式,用白話解釋就是:某個狀態 s 的值函數 v(s)  = 當前狀態 s 在下一階段的報酬 R_{s} + 與當前狀態 s 相接的後續狀態 s^{'} 的值函數 v(s^{'})  \times 相應的狀態轉移概率 P_{ss^{'}} 的求和(後續狀態可能有一個或多個,這裏其實就是一個求期望的過程)的折扣。

下面給個例子來簡單描述一下這個計算過程,如圖9

圖9:值函數的計算

這張圖中描述了值函數 v(s) 是如何計算出來的。

讀者可能又有疑問,要計算狀態 s 的值函數v(s) ,得知道後續狀態 s^{'} 的值函數 v(s^{'}) ,這不是又陷入了一個閉環了嘛!

這裏給出兩種解決方法:

  1. Bellman等式
  2. Bellman等式的矩陣形式

(六)Bellman等式

迭代法就是我們令最初的值函數全部爲 0 ,通過多次迭代,使得迭代次數達到指定值或者值函數趨於穩定,這裏就直接放代碼啦!

"""這是MRP迭代過程"""


# 迭代
def next_v(_lambda, r_list, old_v_list, weight_list):
    new_v_list = []
    for j in range(len(old_v_list)):
        if j != len(old_v_list) - 1:
            j_sum = .0
            # 與當前狀態相接的後續狀態的值函數相應的狀態轉移概率的求和的折扣
            for k in range(len(weight_list[j])):
                j_sum += weight_list[j][k][0] * old_v_list[weight_list[j][k][1]]

            # 當前狀態在下一階段的報酬
            new_v_list.append(r_list[j] + _lambda * j_sum)

    # Sleep狀態無後續狀態,故直接賦值0
    new_v_list.append(0.0)
    return new_v_list


if __name__ == '__main__':
    # γ
    my_lambda = 1

    # 報酬順序:Class 1、Class 2、Class 3、Pass、Pub、Facebook、Sleep,分別對應0, 1, 2, 3, 4, 5, 6
    # 後續順序皆與此相同
    my_r_list = [-2., -2., -2., 10., 1., -1., 0.]

    # 初始化值函數
    my_old_v_list = [0, 0, 0, 0, 0, 0, 0]

    # 狀態轉移概率(這裏沒有用概率矩陣,方法有點笨,讀者可以用矩陣來表示)
    # 這裏以[[0.5, 1], [0.5, 5]]爲例解釋一下,該列表記錄Class 1的狀態:
    # [0.5, 1]表示以0.5的概率轉移到Class 2
    # [0.5, 5]表示以0.5的概率轉移到Facebook
    my_weight_list = [[[0.5, 1], [0.5, 5]],
                      [[0.8, 2], [0.2, 6]],
                      [[0.6, 3], [0.4, 4]],
                      [[1, 6]],
                      [[0.2, 0], [0.4, 1], [0.4, 2]],
                      [[0.1, 0], [0.9, 5]],
                      [[0, 0]]]

    my_new_v_list = []
    # 指定迭代次數
    for i in range(100):
        my_new_v_list = next_v(my_lambda, my_r_list, my_old_v_list, my_weight_list)

        # 用新生成的值函數列表替換舊的值函數列表
        my_old_v_list = my_new_v_list

    print(my_new_v_list)

運行結果與圖9 近似,通過增加迭代次數可提高結果準確性:

[-12.418287702397645, 1.4703089405374907, 4.3371337110593915, 10.0, 0.8410368812854547, -22.318009521720207, 0.0]

四捨五入:

[-12, 1.5, 4.3, 10, 0.8, -22, 0]

(七)Bellman等式的矩陣形式

Bellman 等式用矩陣形式表示出來,並進行線性變換:

我們可以看到值函數 v(s) 的計算不再需要後續狀態的值函數 v(s^{'}) ,只需要知道折扣因子 \gamma 、狀態概率轉移矩陣 P 和報酬 R

給出代碼:

import numpy as np


# γ
_lambda = 1

# 狀態轉移概率矩陣
p = [[0, 0.5, 0, 0, 0, 0.5, 0],
     [0, 0, 0.8, 0, 0, 0, 0.2],
     [0, 0, 0, 0.6, 0.4, 0, 0],
     [0, 0, 0, 0, 0, 0, 1],
     [0.2, 0.4, 0.4, 0, 0, 0, 0],
     [0.1, 0, 0, 0, 0, 0.9, 0],
     [0, 0, 0, 0, 0, 0, 0]]
# 報酬矩陣
r = [[-2],
     [-2],
     [-2],
     [10],
     [1],
     [-1],
     [0]]
# 單位矩陣
i = [[1, 0, 0, 0, 0, 0, 0],
     [0, 1, 0, 0, 0, 0, 0],
     [0, 0, 1, 0, 0, 0, 0],
     [0, 0, 0, 1, 0, 0, 0],
     [0, 0, 0, 0, 1, 0, 0],
     [0, 0, 0, 0, 0, 1, 0],
     [0, 0, 0, 0, 0, 0, 1]]

p_mat = np.matrix(p)
r_mat = np.matrix(r)
i_mat = np.matrix(i)

# Bellman等式的矩陣形式
v_mat = (i_mat - _lambda * p_mat).I * r_mat
# v_mat = np.dot(np.linalg.inv(i_mat - p_mat), r_mat)

print(v_mat)

通過這種方法計算得到的結果較第一種方法準確,結果如下:

[[-12.54320988]
 [  1.45679012]
 [  4.32098765]
 [ 10.        ]
 [  0.80246914]
 [-22.54320988]
 [  0.        ]]

四捨五入:

[[-13 ]
 [ 1.5]
 [ 4.3]
 [ 10 ]
 [ 0.8]
 [-23 ]
 [  0  ]]

三、馬爾可夫決策過程(Markov Decision Process)

(一)馬爾可夫決策過程

馬爾可夫決策過程是伴有決策的的馬爾可夫報酬過程,給出定義如下圖10

圖10:馬爾可夫決策過程

簡單來說,馬爾可夫決策過程就是比馬爾可夫報酬過程多了一個有限動作集 A ,這裏所說的決策其實可以理解爲決策者在某個狀態 s 下執行某個動作 a 的概率分佈。

這裏給出研究生馬爾可夫決策過程,如下圖11

圖11:研究生馬爾可夫決策過程

需要注意的一點是,作者認爲如上圖11 David Silver 的課程 PPT 中紅框標註的部分,應該標爲 Pass 。因爲原先狀態圖中,旁邊的狀態爲 Pass ,而且同樣都是 Study 動作,報酬不同有點不合理,“課程通過 ”可能才配得上報酬 10 吧,哈哈!

這裏結構上與圖3 存在明顯的不同就是取消了狀態 Pub ,變成了現在的動作 Pub ,然後該動作可以將 Class 3 狀態轉移到 Class 1 Class 2Class 3,這裏這樣修改是爲了下面更具普遍性的說明,可以說 David Silver 也是別有用心呀。

(二)策略

policy \pi 是給定狀態下動作的分佈,定義如下圖12

圖12:策略π

理解起來也很簡單, \pi (a|s) 就是在當前狀態 S_{t}=s  前提下,執行動作 A_{t} = a 的概率。

我們再來看看狀態轉移概率 P_{s,s^{'}}^{\pi }R_{s}^{\pi } 現在怎麼計算:

我用白話解釋一下:執行動作 Study,以 Class 1 狀態到Class 2 狀態爲例計算 P_{Class 1,Class 2}^{\pi} ,根據圖11 ,我們可以發現P_{Class 1,Class 2}^{\pi}=\pi (Study|Class1)P_{Class 1,Class 2}^{Study} ,解釋一下就是,從狀態 Class 1 到狀態 Class 2 只有執行動作 Study 纔行,而且執行動作 Study 後,可以百分之百從狀態 Class 1 轉移到狀態 Class 2 ,所以 P_{Class 1,Class 2}^{Study}=1 。同時我們可以看到狀態 Class 1 情況下,可執行的動作有兩個 Study Facebook ,所以這裏的 \pi (Study|Class1)=0.5 。從狀態 Class 3 執行動作 Pub 的情況比較特殊,讀者需要注意,這裏的 概率 P_{Class 3,Class 1}^{Pub}=0.2 ,剩下我的就靠讀者自己思考啦!

(三)值函數

現在就出現了兩種值函數:狀態值函數和動作值函數,定義如下圖13 ,就不過多解釋啦:

圖13:狀態值函數和動作值函數

展示一個使用 Bellman 等式計算的樣例,圖14

圖14:Bellman 等式計算的樣例

這裏直接給出 Bellman 等式的矩陣形式:

同樣的需要狀態轉移概率矩陣 P^{\pi } 和 報酬 R^{\pi } 。 

(四)Bellman等式的矩陣形式

根據圖11 ,可得到如下狀態轉移概率矩陣:

每個狀態轉移的平均報酬可如下計算:

可以計算出報酬 R

然後就可以計算值函數啦!這裏分別給出使用 Bellman 等式和 Bellman 等式的矩陣表示的代碼,建議讀者使用 Bellman 等式的矩陣表示代碼。

  • Bellman 等式
"""這是MDP迭代過程"""


def next_v(pi, r_list, old_v_list, weight_list):
    new_v_list = []
    for j in range(len(old_v_list)):
        if j != len(old_v_list) - 1:
            j_sum = .0
            for k in range(len(weight_list[j])):
                if type(weight_list[j][k][0]) is not list:
                    j_sum += pi * (r_list[weight_list[j][k][1]] + old_v_list[weight_list[j][k][0]])
                else:
                    m_sum = .0
                    for m in range(len(weight_list[j][k][0])):
                        m_sum += old_v_list[weight_list[j][k][0][m][0]] * weight_list[j][k][0][m][1]
                    j_sum += pi * (r_list[weight_list[j][k][1]] + m_sum)
            new_v_list.append(j_sum)

    new_v_list.append(0.0)
    return new_v_list


if __name__ == '__main__':
    my_pi = 0.5
    # 報酬順序:study pass pub facebook quit sleep
    my_r_list = [-2., 10., 1., -1., 0., 0.]
    my_old_v_list = [0, 0, 0, 0, 0]
    my_weight_list = [[[1, 0], [3, 3]],
                      [[2, 0], [4, 5]],
                      [[[[0, 0.2], [1, 0.4], [2, 0.4]], 2], [4, 1]],
                      [[0, 4], [3, 3]],
                      []]

    my_new_v_list = []
    # my_new_v_list = next_v(my_pi, my_r_list, my_old_v_list, my_weight_list)
    for i in range(100):
        my_new_v_list = next_v(my_pi, my_r_list, my_old_v_list, my_weight_list)
        my_old_v_list = my_new_v_list

    print(my_new_v_list)

結果:

[-1.3076923099959044, 2.6923076920317515, 7.384615384159404, -2.3076923112229597, 0.0]

四捨五入:

[-1.3, 2.7, 7.4, -2.3, 0.0]

  • Bellman 等式的矩陣表示
import numpy as np


# π、γ
_pi = 0.5
_lambda = 1

p = [[0, _pi, 0, _pi, 0],
     [0, 0, _pi, 0, _pi],
     [_pi*0.2, _pi*0.4, _pi*0.4, 0, _pi],
     [_pi, 0, 0, _pi, 0],
     [0, 0, 0, 0, 0]]
r = [[_pi*-2 + _pi*-1],
     [_pi*-2 + _pi*0],
     [_pi*1 + _pi*10],
     [_pi*0 + _pi*-1],
     [0]]
i = [[1, 0, 0, 0, 0],
     [0, 1, 0, 0, 0],
     [0, 0, 1, 0, 0],
     [0, 0, 0, 1, 0],
     [0, 0, 0, 0, 1]]

p_mat = np.matrix(p)
r_mat = np.matrix(r)
i_mat = np.matrix(i)

v_mat = (i_mat - _lambda * p_mat).I * r_mat
# v_mat = np.dot(np.linalg.inv(i_mat - p_mat), r_mat)

print(v_mat)

結果:

[[-1.30769231]
 [ 2.69230769]
 [ 7.38461538]
 [-2.30769231]
 [ 0.        ]]

四捨五入:

[[-1.3]
 [ 2.7]
 [ 7.4]
 [-2.3]
 [ 0.  ]]

四、最優值函數(Optimal Value Function)

注意,這裏求解的 MDP 狀態值函數,每個狀態的回報是根據轉移概率計算的平均回報,這稱爲貝爾曼期望方程(Bellman Expectation Equation)。而在強化學習中,通常是選取使得價值最大的動作執行,這種解法稱爲最優值函數(Optimal Value Function),得到的解不再是期望值函數,而是理論最大值函數。此時相當於策略 π 已經改變,且非線性,線性方程組不再適用,需要通過數值計算的方式求出。通常的做法是設置狀態值函數 v(s) 和狀態-動作值函數 q(s,a) 迭代求解。最終得到:

這裏給出計算 v^{*}(s) 的計算代碼:

"""這是OVF迭代過程"""


def next_v(pi, r_list, old_v_list, weight_list):
    new_v_list = []
    for j in range(len(old_v_list)):
        if j != len(old_v_list) - 1:
            max_list = []
            for k in range(len(weight_list[j])):
                if type(weight_list[j][k][0]) is not list:
                    max_list.append(pi * (r_list[weight_list[j][k][1]] + old_v_list[weight_list[j][k][0]]))
                else:
                    m_sum = .0
                    for m in range(len(weight_list[j][k][0])):
                        m_sum += old_v_list[weight_list[j][k][0][m][0]] * weight_list[j][k][0][m][1]
                    max_list.append(pi * (r_list[weight_list[j][k][1]] + m_sum))
            new_v_list.append(max(max_list))

    new_v_list.append(0.0)
    return new_v_list


if __name__ == '__main__':
    my_pi = 1
    # study pass pub facebook quit sleep
    my_r_list = [-2., 10., 1., -1., 0., 0.]
    my_old_v_list = [0, 0, 0, 0, 0]
    my_weight_list = [[[1, 0], [3, 3]],
                      [[2, 0], [4, 5]],
                      [[[[0, 0.2], [1, 0.4], [2, 0.4]], 2], [4, 1]],
                      [[0, 4], [3, 3]],
                      []]

    my_new_v_list = []
    # my_new_v_list = next_v(my_pi, my_r_list, my_old_v_list, my_weight_list)
    for i in range(100):
        my_new_v_list = next_v(my_pi, my_r_list, my_old_v_list, my_weight_list)
        my_old_v_list = my_new_v_list

    print(my_new_v_list)

結果:

[6.0, 8.0, 10.0, 6.0, 0.0]

結果如下圖15 所示:

圖15:最優值函數

寫到這裏有點累了,具體公式讀者就看 PPT 吧,這裏講一個簡單的計算最優動作值函數 q^{*}(a,s) 的方法,我們先看看下一張圖16 吧:

圖16:最優動作值函數

最優值函數我們已經用代碼算出來了,計算最優動作值函數 q^{*}(a,s) 就變得簡單多了:當前動作值函數 =  動作指向的值函數 + 當前動作的報酬(或者是期望)。

更多內容大家自己閱讀 PPT 吧,內容以上傳到我的博客。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章