1. 程式人生 > >Reinforcement Learning強化學習系列之二:MC prediction

Reinforcement Learning強化學習系列之二:MC prediction

引言

這幾個月一直在忙找工作和畢業論文的事情,部落格擱置了一段時間,現在稍微有點空閒時間,又啃起了強化學習的東西,今天主要介紹的是強化學習的免模型學習free-model learning中的最基礎的部分蒙特卡洛方法(Monte Carlo),並以21點遊戲作為說明。
本文主要參考的文獻是[1]參考的主要程式碼是這位斯坦福大神的課程程式碼,本系列的文章均不作為商用,如有侵權請聯絡我的郵箱

Monte Carlo Learning

在前一篇文章中介紹了基於模型的強化學習方法,對於很多現實問題,其實環境的state和狀態轉移概率是未知的,因此在計算Value的時候不能按照基於模型的方法進行全概率展開,這也是免模型學習的難點所在。很自然的對於很多數學問題,如果不能直接求解,取樣的方法是個替代的方法,比如重要性取樣。

在蒙特卡洛(MC)的強化學習中,MC並不是特指某個具體的方法,只是單純指的是基於隨機取樣的方法進行計算學習。本節主要講的是通過MC計算Value的方法。

對於某種策略π,我們從起始狀態s0出發,根據該策略獲取狀態的軌跡:

s0,a0,r1,s1,a1,r2...,sT1,aT1,rT,sT
這裡at表示t時刻的動作,rt表示的t時刻的reward。我們把每一個這種取樣的序列成為一個時期episode,表示一個事件從開始到結束,通過取樣多個episode,我們可以得到多個狀態序列。

在MC prediction中,計算Value函式有兩種方法,第一種是first-visit,第二種是evert-visit,其中first-visit在計算

Value(st)值的時候是對於在每個episode中,選取該episode第一次出現狀態st以後的序列reward值來計算value(st),然後對所有的episode中的value(st)按照出現次數取平均值,就可以得到value(st),演算法可以表示如下:
這裡寫圖片描述
對應的every-visit則返回的是每次st以後的序列reward值,然後在全部的episode裡面取平均值,本文中使用的是first-visit MC prediction.

21點遊戲

這一小節打算[1]書中使用21點遊戲為背景,對於兩個人,遊戲規則是這樣:

  • 有兩種角色,莊家和玩家
  • 撲克有1-10,J,Q,K,其中J,Q,K表示數值10,卡牌1(Ace)可以表示1也可以表示11
  • 在最開始,玩家發兩張牌,莊家發兩張牌,其中莊家的牌一張是公開的,玩家兩張牌都是不公開的,只有玩家自己才能看到。
  • 玩家可以選擇要牌,可以決定手中的1表示的是1還是表示11,如果玩家在開始的時候是一張1一種10(10,J,Q,K)那麼表示是natural,此時如果莊家也是natural那麼表示的是平局,反之則玩家勝,如果在玩家要牌的過程中手中牌的總數大於21,那麼就爆了,玩家輸,如果玩家沒要爆並停止要牌,那麼莊家開始要牌,莊家在點數小於17的時候必須要牌,如果超過了17那麼就要停止要牌,莊家在要牌的過程中爆了則莊家輸,如果莊家停牌並沒有爆,那麼這個時候莊家和玩家開牌,誰的點數更靠近21點便勝利,如果相等則平局。

    遊戲分析:

  • 在最開始,如果玩家手中的點數小於11那麼必然會要牌直到超過11點

  • 每個人手中如果1可以在不爆的情況下可以表示為11,那麼必然會當做11
    根據以上分析,那麼在遊戲過程中,玩家手中的總和應該在12-21之間,而莊家公開的牌是1-10,不管莊家的1表示的11還是1,而玩家手中的1可以表示1或者11,當玩家表示為1的時候表示是no usable,當玩家表示為11的時候表示為usable,那麼顯然所有的state狀態又200個。

遊戲建模假設:

  • 牌是無限發的,因此玩家和莊家不可以通過桌面上公開的牌進行猜測剩下的牌的概率

遊戲建模

對於以上分析,我們可以建立一個21點遊戲的背景程式碼:

#coding=utf-8
import numpy as np


deck = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10]

np.random.seed(3)

#random choice a card from deck
def draw_card():
    choice = np.random.randint(0,len(deck))
    return deck[choice]

#choose two card at the begining
def draw_hand():
    return [draw_card(),draw_card()]

#define whether player or dealer has a usable ace
def usable_ace(hand):
    return (1 in hand) and (sum(hand)+10<=21)

#define the total sum of hand
def sum_hand(hand):
    if usable_ace(hand):
        return sum(hand)+10
    else:
        return sum(hand)
#whether bust
def is_bust(hand):
    return sum_hand(hand)>21

def score(hand):
    return 0 if is_bust(hand) else sum_hand(hand)

#whether natural
def is_natural(hand):
    return hand==[1,10]



def cmp(a,b):
    return int(a>b)-int(a<b)



class Blackjack(object):
    def __init__(self,natural=False):
        self.natural = natural
        self.nA=2
        self._reset()



    def observation(self):
        return (sum_hand(self.player),self.dealer[0],usable_ace(self.player))

    def _reset(self):

        self.dealer = draw_hand()
        self.player = draw_hand()

        while sum_hand(self.player)<12:
            self.player.append(draw_card())

        return self.observation()

    def _step(self,action):
        """
        :param action: 1 means hit, 0 means stick
        :return:
        """
        done=False
        if action:
            self.player.append(draw_card())
            if is_bust(self.player):
                done=True
                reward=-1
            else:
                done = False
                reward = 0
        else:
            done=True
            while(sum_hand(self.dealer)<17):
                self.dealer.append(draw_card())
            reward = cmp(score(self.player), score(self.dealer))
            if self.natural and is_natural(self.player) and reward == 1:
                reward = 1.5
        return self.observation(),reward,done



假設我們選取的策略是當玩家點數超過20的時候就停止要牌,反之則要牌,那麼使用first-visit MC prediction的時候程式碼如下:

#coding=utf-8

from Blackjack import Blackjack
from collections import defaultdict
import sys
import matplotlib
import os

import plotting

matplotlib.style.use('ggplot')


def sample_policy(observation):

    player_sum,dealer_show_hand,usable_ace=observation
    if player_sum<20:
        return 1
    return 0

#
# episode.append(state)
#                 if return_sum.has_key(state):
#                     return_sum[state]+=reward
#                 else:
#                     return_sum[state]=reward
#                 if return_count.has_key(state):
#                     return_count[state]+=1
#                 else:
#                     return_count=1
#                 if V.has_key(state):
#                     V[state]
def mc_prediction(policy,env,num_episodes,discount=1.0):
    """
    first-visit mc prediction
    :param env: blackjack env
    :param policy: initial policy
    :param num_episodes:
    :param discount:
    :return:
    """


    return_sum = defaultdict(float)
    return_count = defaultdict(float)
    V = defaultdict(float)

    for i_episode in range(1,1+num_episodes):

        if i_episode % 1000 == 0:
            print("\rEpisode {}/{}.".format(i_episode, num_episodes))
            sys.stdout.flush()

        env._reset()
        state = env.observation()

        episode=[]

        #sample using given policy
        for i in range(100):
            action = policy(state)

            next_state,reward,done = env._step(action)
            episode.append((state,action,reward))
            if done:
                break
            else:
                state=next_state

        seperate_episode = set([tuple(eps[0]) for eps in episode])

        for s_eps in seperate_episode:
            #find the first visit state

            for i,x in enumerate(episode):
                if x[0] == s_eps:
                    first_visit_pos=i
            G = sum([e[2]*discount**idx for idx,e in enumerate(episode[first_visit_pos:])])

            return_sum[s_eps]+=G
            return_count[s_eps]+=1.0
            V[s_eps] = return_sum[s_eps]*1.0/return_count[s_eps]

    return V

env = Blackjack()

V_10k = mc_prediction(sample_policy, env, num_episodes=10000)
plotting.plot_value_function(V_10k, title="10,000 Steps")

V_500k = mc_prediction(sample_policy, env, num_episodes=500000)
plotting.plot_value_function(V_500k, title="500,000 Steps")

當使用10000個episode的時候可以得到的是如下圖:
這裡寫圖片描述
這裡寫圖片描述
當使用500000個episode的時候可以得到的是如下圖:
這裡寫圖片描述
這裡寫圖片描述
可以看出的是,手中有usable的整個Value值還是要高一點。

程式碼可以在這裡獲取

後記

今天主要介紹的是MC prediction,以21點遊戲為例,下面的文章將會介紹MC control以及 on-policy以及off-policy的一些內容,記於北京。