Q-Learning走迷宮
上文中我們瞭解了Q-Learning算法的思想,基於這種思想我們可以實現很多有趣的功能和小demo,本文讓我們通過Q-Learning算法來實現用計算機來走迷宮。
原理簡述
我們先從一個比較高端的例子說起,AlphaGo大家都聽說過,其實在AlphaGo的訓練過程中就使用了Q-Learning的思想,對於機器下錯棋和下對棋的時候給予一定的懲罰和獎勵,當經過無數次的訓練之後,機器自然就會直接向着獎勵前進,直接選擇對的位置進行下棋,久而久之在各種場景下都能選擇對的位置下棋的機器人就能夠把人類打敗了。
再回到我們今天要講的例子上來,走迷宮和下棋的原理其實類似,我們要走的路無非就是兩種:順暢的路和有障礙的路,那我們要做的事也就很明瞭了,當觸碰到障礙的時候給予一定的懲罰,走了正確的路時給予一定的獎勵,這樣經過不斷的訓練,機器就應該能夠知道該如何”走迷宮“了。我們要走的迷宮示意圖如下:
代碼實現
構建畫布
我們首先要做的就是來構建畫布並且畫出迷宮,Python中Tkinter就是一個很好的畫圖工具(相對於其他的庫來說,該庫運行快,且不容易卡死)
構建畫布的時候,我們除了需要構建基本的圖形和迷宮,還需要實現行動的方式,對於簡單的迷宮來說,我們只需要設定“上下左右”的行動方式就可以了,如果走到了黑塊就得到-1的懲罰並結束回合,走到黃塊得到1的獎勵並結束回合。其餘的解釋放在代碼註釋中,畫布代碼如下:
# coding: utf-8
import sys
import time
import numpy as np
if sys.version_info.major == 2:
import Tkinter as tk
else:
import tkinter as tk
class Maze(tk.Tk, object):
UNIT = 40 # 像素
MAZE_H = 6 # 網格高度
MAZE_W = 6 # 網格寬度
def __init__(self):
super(Maze, self).__init__()
self.action_space = ['U', 'D', 'L', 'R']
self.n_actions = len(self.action_space)
self.title('迷宮')
self.geometry('{0}x{1}'.format(self.MAZE_H * self.UNIT,
self.MAZE_W * self.UNIT))
self._build_maze()
# 畫矩形
# x y 格座標
# color 顏色
def _draw_rect(self, x, y, color):
center = self.UNIT / 2
w = center - 5
x_ = self.UNIT * x + center
y_ = self.UNIT * y + center
return self.canvas.create_rectangle(x_ - w,
y_ - w,
x_ + w,
y_ + w,
fill=color)
# 初始化迷宮
def _build_maze(self):
h = self.MAZE_H * self.UNIT
w = self.MAZE_W * self.UNIT
# 初始化畫布
self.canvas = tk.Canvas(self, bg='white', height=h, width=w)
# 畫線
for c in range(0, w, self.UNIT):
self.canvas.create_line(c, 0, c, h)
for r in range(0, h, self.UNIT):
self.canvas.create_line(0, r, w, r)
# 陷阱
self.hells = [
self._draw_rect(3, 2, 'black'),
self._draw_rect(3, 3, 'black'),
self._draw_rect(3, 4, 'black'),
self._draw_rect(3, 5, 'black'),
self._draw_rect(4, 5, 'black'),
self._draw_rect(1, 0, 'black'),
self._draw_rect(1, 1, 'black'),
self._draw_rect(1, 2, 'black'),
self._draw_rect(1, 4, 'black'),
self._draw_rect(1, 5, 'black')
]
self.hell_coords = []
for hell in self.hells:
self.hell_coords.append(self.canvas.coords(hell))
# 獎勵
self.oval = self._draw_rect(4, 5, 'yellow')
# 玩家對象
self.rect = self._draw_rect(0, 0, 'red')
self.canvas.pack() #執行畫
# 重新初始化(用於撞到黑塊後重新開始)
def reset(self):
self.update()
time.sleep(0.5)
self.canvas.delete(self.rect)
self.rect = self._draw_rect(0, 0, 'red')
self.old_s = None
#返回 玩家矩形的座標 [5.0, 5.0, 35.0, 35.0]
return self.canvas.coords(self.rect)
# 走下一步
def step(self, action):
s = self.canvas.coords(self.rect)
base_action = np.array([0, 0])
if action == 0: # up
if s[1] > self.UNIT:
base_action[1] -= self.UNIT
elif action == 1: # down
if s[1] < (self.MAZE_H - 1) * self.UNIT:
base_action[1] += self.UNIT
elif action == 2: # right
if s[0] < (self.MAZE_W - 1) * self.UNIT:
base_action[0] += self.UNIT
elif action == 3: # left
if s[0] > self.UNIT:
base_action[0] -= self.UNIT
# 根據策略移動紅塊
self.canvas.move(self.rect, base_action[0], base_action[1])
s_ = self.canvas.coords(self.rect)
# 判斷是否得到獎勵或懲罰
done = False
if s_ == self.canvas.coords(self.oval):
reward = 1
done = True
elif s_ in self.hell_coords:
reward = -1
done = True
#elif base_action.sum() == 0:
# reward = -1
else:
reward = 0
self.old_s = s
return s_, reward, done
def render(self):
time.sleep(0.01)
self.update()
實現算法
實現算法的過程就是按照我們上一文中所說到的Q-Learning的算法運行方式來進行,代碼實現如下:
# coding: utf-8
import pandas as pd
import numpy as np
class q_learning_model_maze:
def __init__(self,
actions,
learning_rate=0.01,
reward_decay=0.9,
e_greedy=0.99):
self.actions = actions
self.learning_rate = learning_rate
self.reward_decay = reward_decay
self.e_greedy = e_greedy
self.q_table = pd.DataFrame(columns=actions, dtype=np.float32)
# 檢查狀態是否存在
def check_state_exist(self, state):
if state not in self.q_table.index:
self.q_table = self.q_table.append(
pd.Series(
[0] * len(self.actions),
index=self.q_table.columns,
name=state,
))
# 選擇動作
def choose_action(self, s):
self.check_state_exist(s)
if np.random.uniform() < self.e_greedy:
state_action = self.q_table.loc[s, :]
state_action = state_action.reindex(
np.random.permutation(
state_action.index)) # 防止相同列值時取第一個列,所以打亂列的順序
action = state_action.idxmax()
else:
action = np.random.choice(self.actions)
return action
# 更新q表
def rl(self, s, a, r, s_):
self.check_state_exist(s_)
q_predict = self.q_table.loc[s, a] # q估計
if s_ != 'terminal':
q_target = r + self.reward_decay * self.q_table.loc[
s_, :].max() # q現實
else:
q_target = r
self.q_table.loc[s, a] += self.learning_rate * (q_target - q_predict)
訓練
訓練的時候我們需要做的就是選擇狀態並執行,最後把執行的結果更新到狀態表中。
# coding: utf-8
def update():
for episode in range(100):
s = env.reset()
while True:
env.render()
# 選擇一個動作
action = RL.choose_action(str(s))
# 執行這個動作得到反饋(下一個狀態s 獎勵r 是否結束done)
s_, r, done = env.step(action)
# 更新狀態表
RL.rl(str(s), action, r, str(s_))
s = s_
if done:
print(episode)
break
if __name__ == "__main__":
env = Maze()
RL = q_learning_model_maze(actions=list(range(env.n_actions)))
env.after(10, update) # 設置10ms的延遲
env.mainloop()