动态规划Dynamic Programming
如果环境的动态特性完全已知,那么可以直接求解出状态价值函数,但是求解过程较为繁琐,因此一般采用迭代法求解。
假设有一个近似的价值函数序列,$v_0,v_1,v_2,\dots,$从$S^+$映射到实数集$\Reals$,初始的近似值$v_0$除了终止状态的值必须为0外可以任意选取,然后下一轮的迭代使用$v_{\pi}$的贝尔曼方程进行更新,对应任意的$s \isin S$有:
在保证$v_{\pi}$存在的前提下,序列{${v_k}$} 在$k \to \infty$时收敛到$v_{\pi}$
这就是迭代策略评估算法。
This algorithm is called iterative policy evaluation.
网格寻路示例
现有一个$4 \times 4$的网格地图,左上角与右下角的位置是终止状态,现在要求解出某个智能体在地图上移动到终止状态的最优策略,智能体可以向上下左右四个方向移动。
非终止状态集合为$S = { 1,2,\dots ,14 }$
每种状态都对应有4种可能的动作$A = { up,down,right,left }$
每个动作会导致状态转移,但若超过网格边界则状态保持不变,如:$P((6,-1)|5,right) = 1$,$P((7,-1)|7,right) = 1$,对任意的$r \isin R$,都有$P((10,r)|5,right) = 0$.
这是一个无折扣的分幕式任务。在到达终止状态之前,所有动作的收益均为-1。对于所有的状态$s,s'$和动作$a$,期望的收益函数均为$r(s,a,s') =-1$。
假设智能体采取等概率的随机策略,即:所有动作的执行概率相等,均为$0.25$.
计算过程如下图,注意图中保留了一位小数,$k=2$时$-1.7$实际上是$-1.75$
$k=2$时:
$(-1) + 0.25 * (-1)+0.25*(-1)+0.25*(-1) + 0.25*0 =-1.75$
$k=3$时:
$(-1.7) + 0.25*(-1)+0.25*(-1)+0.25*(-1) + 0.25*0 =-2.45$
以此类推,得到下图:
第三次迭代后,所有策略都是最优的。
蒙特卡洛Monte Carlo Methods
“蒙特卡洛”泛指任何包含大量随机成分的估计方法。
经典的蒙特卡洛方法解释是在正方形中求圆形面积的示例,这里不再赘述。
在《图灵的大教堂:数字宇宙开启智能时代》 [美] 乔治·戴森一书中有关于蒙特卡洛方法诞生历史的一些叙述。
在绝大多数实际场景中,我们并不清楚完整的环境知识,因此不适用动态规划的方法。蒙特卡洛方法仅需采样的经验就可以推理出最优的行为。
前面的多臂赌博机问题中,曾经提到这个通用公式:
蒙特卡洛方法的公式:
蒙特卡洛方法必须等到一幕的结尾才能确定对$Q(S_{t} A_t)$的增量。
算法:
井字棋实例
《Reinforcement Learning: An Introduction》提供了随书代码
https://github.com/ShangtongZhang/reinforcement-learning-an-introduction
其中第一章的代码就是井字棋程序,下面的程序在某些地方参考了例程的代码,同时思路也参考了B站up主PenicillinLP的教程。
import numpy as np
import random
# 输入3*3数组,输出元组
def get_tuple(numpy_array):
return tuple(map(tuple, numpy_array))
def print_board(board):
for i in range(3):
print('-------------')
out = '| '
for j in range(3):
if board[i, j] == 1:
token = '*'
elif board[i, j] == -1:
token = 'x'
else:
token = '0'
out += token + ' | '
print(out)
print('-------------')
class Agent_my():
# 参数:玩家索引号,玩家训练参数(Epsilon、Learning_rate)
# 玩家需要有游戏策略,策略表,after_state,q,r
def __init__(self,ooxx_index,Epsilon=0.1,Learning_rate=0.1):
self.index = ooxx_index
self.epsilon = Epsilon
self.alpha = Learning_rate
self.q_values = {} # Q值表,创建字典
self.outcomes = []
self.isepsilon = [] # 记录该步骤是否探索了
# 字典的键是棋盘可能的状态,值是状态对应的估计收益值
def reset(self):
self.outcomes =[]
self.isepsilon = []
# 输入当前棋盘状态
# 选择行动,根据Q值表选择,搜索当前状态下的所有可能的键,
# 返回下一棋局的状态,即可
def choose_action(self, current_board):
valid_moves=[]
# 先计算所有的outcome
for row in range(3):
for col in range(3):
outc = current_board.copy()
if outc[row, col] == 0:
# 返回outcome
outc[row, col] = self.index
valid_moves.append(get_tuple(outc))
if random.uniform(0, 1) < self.epsilon:
self.isepsilon.append(0)
# 随机选择动作
next_board = random.choice(valid_moves)
else:
self.isepsilon.append(1)
# 选择具有最大Q值的动作
actions ={}
max_q = 0
for action in valid_moves:
temp_q = self.q_values.setdefault(action, 0)
actions[action] = temp_q
if temp_q > max_q:
max_q = temp_q
# 得到了最大的q值和actions列表
# keys_with_value = [key for key, value in my_dict.items() if value == target_value]
best_actions = [action for action, q_value in actions.items() if q_value == max_q]
next_board = random.choice(best_actions)
# 将选取的outcome加入列表
self.outcomes.append(next_board)
return np.array(next_board)
# 更新Q值
# 输入是reward,current_board
# 和棋奖励是0.5,赢棋奖励是1,输棋奖励0
def update_q_value(self, reward):
# 先单独设置T时刻
self.q_values[self.outcomes[-1]] = reward
# 然后设置T-1,T-2...
for i in reversed(range(len(self.outcomes) - 1)):
current_t = self.outcomes[i]
last_T = self.outcomes[i+1]
# 下面就是程序的关键,计算公式,蒙特卡洛法的计算公式
# 这里setdefault是为了避免某个键尚不存在
self.q_values[current_t] = self.q_values.setdefault(current_t,0) + self.alpha * self.isepsilon[i] * (self.q_values[last_T] - self.q_values.setdefault(current_t,0))
# 返回true,false
def board_over(board,player_index):
# Check rows, columns, and diagonals
for i in range(3):
# all()函数用于判断整个数组中的元素的值是否全部满足条件,如果满足条件返回True,否则返回False
if np.all(board[i, :] == player_index) or \
np.all(board[:, i] == player_index):
return True
if np.all(np.diag(board) == player_index) or \
np.all(np.diag(np.fliplr(board)) == player_index):
return True
return False
class Environment_my():
def __init__(self):
self.board = np.zeros((3,3))
self.winner = None
self.r1 = 0
self.r2 = 0
# 记录胜率
self.win1 =0
self.win2 = 0
self.win0 =0
def reset(self):
self.board = np.zeros((3, 3))
self.winner = None
self.r1 = 0
self.r2 = 0
# 检查游戏是否结束或是和棋
# 返回true 结束,false未结束,返回1,1号玩家胜利,-1,2号玩家胜利
def is_game_over(self):
if board_over(self.board,1) == True:
self.winner = 1
self.r1 = 1
self.win1 +=1
self.r2 = 0
return True
if board_over(self.board,-1) == True:
self.winner = -1
self.win2 +=1
self.r1 = 0
self.r2 = 1
return True
if (np.all(self.board != 0) and self.winner==None):
self.win0 +=1
self.r1 = 0.5
self.r2 = 0.5
return True
return False
# human interface
# input a number to put a chessman
# | q | w | e |
# | a | s | d |
# | z | x | c |
class HumanPlayer:
def __init__(self, **kwargs):
self.symbol = None
self.keys = ['q', 'w', 'e', 'a', 's', 'd', 'z', 'x', 'c']
self.state = None
def reset(self):
pass
def choose_action(self, current_board):
print_board(current_board)
key = input("Input your position:")
data = self.keys.index(key)
i = data // 3
j = data % 3
next_board = current_board.copy()
next_board[i,j] = -1
return next_board
def human_test(ai_agent,human_player):
env = Environment_my()
while True:
ai_agent.reset()
env.reset()
while True:
env.board = agent1.choose_action(env.board)
# print(env.board)
# 检查游戏是否结束
if env.is_game_over() == True:
break
env.board = human_player.choose_action(env.board)
if env.is_game_over() == True:
break
if env.winner == 1:
print("You lose!")
elif env.winner == -1:
print("You win!")
else:
print("It is a tie!")
def test_ai(agent1,agent2,testi=1000):
agent1.epsilon =0
agent2.epsilon =0
env_pk =Environment_my()
for i in range(testi):
env_pk.reset()
agent1.reset()
agent2.reset()
while True:
env_pk.board = agent1.choose_action(env_pk.board)
if env_pk.is_game_over() == True:
# agent1.update_q_value(env_pk.r1)
# agent2.update_q_value(env_pk.r2)
break
env_pk.board = agent2.choose_action(env_pk.board)
if env_pk.is_game_over() == True:
# agent1.update_q_value(env_pk.r1)
# agent2.update_q_value(env_pk.r2)
break
print(f"Agent 1 Wins: {env_pk.win1 / testi}, Agent 2 Wins: {env_pk.win2 / testi}, Draws: {env_pk.win0 / testi}")
if __name__ == "__main__":
agent1 = Agent_my(1,0.1,0.1)
agent2 = Agent_my(-1,0.1,0.1)
env = Environment_my()
print("start")
# 落子
all_e =100000
z_e= 1000
for i in range(all_e):
env.reset()
agent1.reset()
agent2.reset()
while True:
env.board = agent1.choose_action(env.board)
# 检查游戏是否结束
if env.is_game_over() == True:
agent1.update_q_value(env.r1)
agent2.update_q_value(env.r2)
break
env.board = agent2.choose_action(env.board)
if env.is_game_over() == True:
agent1.update_q_value(env.r1)
agent2.update_q_value(env.r2)
break
if (i + 1) % z_e == 0:
print(f"Agent 1 Wins: {env.win1 / i}, Agent 2 Wins: {env.win2 / i}, Draws: {env.win0 / i}")
test_ai(agent1,agent2)
# 训练完成后,与人类玩家竞赛,看看效果。这里设置了人类玩家后手,机器人先手。
p2 = HumanPlayer()
human_test(agent1,p2)
运行效果类似于下图:
测试效果还是蛮好的,我基本上不能赢,只能和棋。
Reference
- https://www.davidsilver.uk/teaching/
- https://github.com/ShangtongZhang/reinforcement-learning-an-introduction
- 【强化学习】一小时完全入门 PenicillinLP