強化學習實戰 | 表格型Q-Learning玩井字棋(一)

埠默笙聲聲聲脈發表於2021-12-07

強化學習實戰 | 自定義Gym環境之井子棋 中,我們構建了一個井字棋環境,並進行了測試。接下來我們可以使用各種強化學習方法訓練agent出棋,其中比較簡單的是Q學習,Q即Q(S, a),是狀態動作價值,表示在狀態s下執行動作a的未來收益的總和。Q學習的演算法如下:

可以看到,當agent在狀態S,執行了動作a之後,得到了環境給予的獎勵R,並進入狀態S'。同時,選擇最大的Q(S', a),更新Q(S, a)。所謂表格型Q學習,就是構建一個Q(S, a)的表格,維護所有的狀態動作價值。 一個很好的示例來自 Q學習玩Flappy Bird,隨著遊戲的不斷進行,Q表格中記錄的狀態越來越多,狀態動作價值也越來越準確,於是小鳥也飛得越來越好。

我們也要構建這樣的Q表格,並希望通過Q_table[state][action] 的檢索方式訪問其儲存的狀態動作價值,我們可以用字典實現: 

'[1, 0, -1, 0, 0, 0, 1, -1, 0]' {'(0,1)':0, '(1,0)':0, '(1,1)':0, '(1,2)':0, '(2,2)':0}
'[0, 1, 0, -1, 0, 0, -1, 0, 1]' ......

在本文中我們要做到如下的目標:

  • 改寫 強化學習實戰 | 自定義Gym環境之井子棋 中的測試程式碼,要更有邏輯,更能凸顯強化學習中 agent 和環境的概念。
  • agent 隨機選擇空格進行動作,每次動作前,更新Q表格:若表格中不存在當前狀態,則將當前狀態及其動作價值新增至Q表格中。
  • 玩50000次遊戲,檢視Q表格中的狀態數

步驟1:建立檔案

在任意目錄新建檔案 Table QLearning play TicTacToe.py

步驟2:建立類 Agent()

Agent() 類 需要有(1)隨機落子的動作生成函式(2)Q表格(3)更新Q表格的函式,且新增表格中全部狀態動作價值設為0。程式碼如下:

class Agent():
    def __init__(self):
        self.Q_table = {}
    
    def getEmptyPos(self, env_): # 返回空位的座標
        action_space = []
        for i, row in enumerate(env_.state):
            for j, one in enumerate(row):
                if one == 0: action_space.append((i,j)) 
        return action_space
        
    def randomAction(self, env_, mark): # 隨機選擇空格動作
        actions = self.getEmptyPos(env_)
        action_pos = random.choice(actions)
        action = {'mark':mark, 'pos':action_pos}
        return action
    
    def updateQtable(self, env_): # 更新Q表格
        state = env_.state
        if str(state) not in self.Q_table: # 新增狀態
            self.Q_table[str(state)] = {}
            actions = self.getEmptyPos(env_)
            for action in actions:
                self.Q_table[str(state)][str(action)] = 0 # 新增的狀態動作價值為0

步驟3:建立類 Game()

Game() 類需要有(1)是/否顯示遊戲過程、更改行動時間間隔的屬性(2)開局隨機先後手(3)切換行動方的函式(4)遊戲結束時,可以新建遊戲。程式碼如下:

class Game():
    def __init__(self, env):
        self.INTERVAL = 0 # 行動間隔
        self.RENDER = False # 是否顯示遊戲過程
        self.first = 'blue' if random.random() > 0.5 else 'red' # 隨機先後手
        self.currentMove = self.first # 當前行動方
        self.env = env
        self.agent = Agent()
    
    def switchMove(self): # 切換行動玩家
        move = self.currentMove
        if move == 'blue': self.currentMove = 'red'
        elif move == 'red': self.currentMove = 'blue'
    
    def newGame(self): # 新建遊戲
        self.first = 'blue' if random.random() > 0.5 else 'red'
        self.currentMove = self.first
        self.env.reset()
    
    def run(self): # 玩一局遊戲
        self.env.reset() # 在第一次step前要先重置環境 不然會報錯
        while True:
            if self.currentMove == 'blue': self.agent.updateQtable(self.env) # 只記錄藍方視角下的局面
            action = self.agent.randomAction(self.env, self.currentMove)
            state, reward, done, info = self.env.step(action)
            if self.RENDER: self.env.render()
            self.switchMove()
            time.sleep(self.INTERVAL)
            if done:
                self.newGame()
                if self.RENDER: self.env.render()
                time.sleep(self.INTERVAL)
                break

步驟4:測試

(1)玩一局遊戲,顯示Q表格,及Q表格中儲存的狀態數:

env = gym.make('TicTacToeEnv-v0')
game = Game(env)
for i in range(1):
    game.run()

for state in game.agent.Q_table:
    print(state)
    for action in game.agent.Q_table[state]:
        print(action, ': ', game.agent.Q_table[state][action])
    print('--------------')

print('dim of state: ', len(game.agent.Q_table))

輸出:

(2) 玩50000局遊戲,檢視Q表格中儲存的狀態數:

env = gym.make('TicTacToeEnv-v0')
game = Game(env)
for i in range(50000):
    game.run()

print('dim of state: ', len(game.agent.Q_table))

輸出:

整體程式碼如下:

強化學習實戰 | 表格型Q-Learning玩井字棋(一)
import gym
import random
import time

# 檢視所有已註冊的環境
# from gym import envs
# print(envs.registry.all()) 

class Game():
    def __init__(self, env):
        self.INTERVAL = 0 # 行動間隔
        self.RENDER = False # 是否顯示遊戲過程
        self.first = 'blue' if random.random() > 0.5 else 'red' # 隨機先後手
        self.currentMove = self.first
        self.env = env
        self.agent = Agent()
    
    def switchMove(self): # 切換行動玩家
        move = self.currentMove
        if move == 'blue': self.currentMove = 'red'
        elif move == 'red': self.currentMove = 'blue'
    
    def newGame(self): # 新建遊戲
        self.first = 'blue' if random.random() > 0.5 else 'red'
        self.currentMove = self.first
        self.env.reset()
    
    def run(self): # 玩一局遊戲
        self.env.reset() # 在第一次step前要先重置環境 不然會報錯
        while True:
            if self.currentMove == 'blue': self.agent.updateQtable(self.env) # 只記錄藍方視角下的局面
            action = self.agent.randomAction(self.env, self.currentMove)
            state, reward, done, info = self.env.step(action)
            if self.RENDER: self.env.render()
            self.switchMove()
            time.sleep(self.INTERVAL)
            if done:
                self.newGame()
                if self.RENDER: self.env.render()
                time.sleep(self.INTERVAL)
                break
                    
class Agent():
    def __init__(self):
        self.Q_table = {}
    
    def getEmptyPos(self, env_): # 返回空位的座標
        action_space = []
        for i, row in enumerate(env_.state):
            for j, one in enumerate(row):
                if one == 0: action_space.append((i,j)) 
        return action_space
        
    def randomAction(self, env_, mark): # 隨機選擇空格動作
        actions = self.getEmptyPos(env_)
        action_pos = random.choice(actions)
        action = {'mark':mark, 'pos':action_pos}
        return action
    
    def updateQtable(self, env_):
        state = env_.state
        if str(state) not in self.Q_table:
            self.Q_table[str(state)] = {}
            actions = self.getEmptyPos(env_)
            for action in actions:
                self.Q_table[str(state)][str(action)] = 0
            

env = gym.make('TicTacToeEnv-v0')
game = Game(env)
for i in range(1):
    game.run()

for state in game.agent.Q_table:
    print(state)
    for action in game.agent.Q_table[state]:
        print(action, ': ', game.agent.Q_table[state][action])
    print('--------------')

print('dim of state: ', len(game.agent.Q_table))
View Code

 

相關文章