以机器人找金币的例子构建MDP框架。
网格中一共8个状态,其中状态6和8都是死亡区域,状态7是金币区域。
机器人的初始位置为网格世界中的任意一个状态,机器人从初始状态出发开始寻找金币,进入死亡区域或者找到金币时,本次探索结束。
将机器人找金币的例子建模为MDP。
状态空间:S={ 1,2,3,4,5,6,7,8}
动作空间:A={东,南,西,北}
状态转移概率为机器人的运动方程
回报函数为:找到金币回报为1,进入死亡区域回报为-1,机器人在状态1-5之间转换时回报为0。
构建机器人找金币的gym环境。
导入需要用的库
import logging
import numpy
import random
from gym import spaces
import gym
logger = logging.getLogger(__name__)
gym环境文件的主体是类,定义类名为GridEnv
class GridEnv(gym.Env):
metadata = {
'render.modes': ['human', 'rgb_array'],
'video.frames_per_second': 2
}
初始化为环境的基本参数
def __init__(self):
#状态空间
self.states = [1,2,3,4,5,6,7,8]
#机器人可能处的每个状态点的中心坐标x,y
self.x=[140,220,300,380,460,140,300,460]
self.y=[250,250,250,250,250,150,150,150]
#终止状态为字典格式
self.terminate_states = dict()
self.terminate_states[6] = 1
self.terminate_states[7] = 1
self.terminate_states[8] = 1
#动作空间
self.actions = ['n','e','s','w']
#回报函数,数据结构为字典
self.rewards = dict();
self.rewards['1_s'] = -1.0
self.rewards['3_s'] = 1.0
self.rewards['5_s'] = -1.0
#状态转移概率,数据格式为字典
self.t = dict();
self.t['1_s'] = 6
self.t['1_e'] = 2
self.t['2_w'] = 1
self.t['2_e'] = 3
self.t['3_s'] = 7
self.t['3_w'] = 2
self.t['3_e'] = 4
self.t['4_w'] = 3
self.t['4_e'] = 5
self.t['5_s'] = 8
self.t['5_w'] = 4
self.gamma = 0.8 #折扣因子
self.viewer = None
self.state = None
定义获取参数的函数
def getTerminal(self):
return self.terminate_states
def getGamma(self):
return self.gamma
def getStates(self):
return self.states
def getAction(self):
return self.actions
def getTerminate_states(self):
return self.terminate_states
def setAction(self,s):
self.state=s
有了状态空间、动作空间和状态转移函数,就可以写step(a)函数了。step()函数的输入是动作,输出时下一个时刻的状态、回报、是否终止和调试信息。对于调试信息,可以为空,但不能缺少,否则会报错,常用{}来代替。
def _step(self, action):
#系统当前状态
state = self.state
#判断系统当前状态是否为终止状态
if state in self.terminate_states:
return state, 0, True, {}
key = "%d_%s"%(state, action)
#将状态和动作组成字典的键值
#状态转移
if key in self.t:
next_state = self.t[key]
else:
next_state = state
self.state = next_state
is_terminal = False
if next_state in self.terminate_states:
is_terminal = True
if key not in self.rewards:
r = 0.0
else:
r = self.rewards[key]
return next_state, r,is_terminal,{}
用随机的方法初始化机器人的状态
def _reset(self):
self.state = self.states[int(random.random() * len(self.states))]
return self.state
构建一个render()函数实现图形化表达,可以调用rendering中的画图函数来绘制图像
def render(self, mode='human', close=False):
if close:
if self.viewer is not None:
self.viewer.close()
self.viewer = None
return
screen_width = 600
screen_height = 400
if self.viewer is None:
#创建一个600*400的窗口
from gym.envs.classic_control import rendering
self.viewer = rendering.Viewer(screen_width, screen_height)
#创建网格世界,11条线
self.line1 = rendering.Line((100,300),(500,300))
self.line2 = rendering.Line((100, 200), (500, 200))
self.line3 = rendering.Line((100, 300), (100, 100))
self.line4 = rendering.Line((180, 300), (180, 100))
self.line5 = rendering.Line((260, 300), (260, 100))
self.line6 = rendering.Line((340, 300), (340, 100))
self.line7 = rendering.Line((420, 300), (420, 100))
self.line8 = rendering.Line((500, 300), (500, 100))
self.line9 = rendering.Line((100, 100), (180, 100))
self.line10 = rendering.Line((260, 100), (340, 100))
self.line11 = rendering.Line((420, 100), (500, 100))
#创建死亡区域,用黑色实习圆代表死亡区域
#创建第一个骷髅
self.kulo1 = rendering.make_circle(40)
self.circletrans = rendering.Transform(translation=(140,150))
self.kulo1.add_attr(self.circletrans)
self.kulo1.set_color(0,0,0)
#创建第二个骷髅
self.kulo2 = rendering.make_circle(40)
self.circletrans = rendering.Transform(translation=(460, 150)) self.kulo2.add_attr(self.circletrans)
self.kulo2.set_color(0, 0, 0)
#创建金币区域,用浅色的圆圈来表示
#创建金币
self.gold = rendering.make_circle(40)
self.circletrans = rendering.Transform(translation=(300, 150))
self.gold.add_attr(self.circletrans)
self.gold.set_color(1, 0.9, 0)
#创建机器人,用和死亡区域和金币区域不同颜色的圆圈。
self.robot= rendering.make_circle(30)
self.robotrans = rendering.Transform()
self.robot.add_attr(self.robotrans)
self.robot.set_color(0.8, 0.6, 0.4)
#给11条直线设置颜色,并把创建的对象添加到几何中
self.line1.set_color(0, 0, 0)
self.line2.set_color(0, 0, 0)
self.line3.set_color(0, 0, 0)
self.line4.set_color(0, 0, 0)
self.line5.set_color(0, 0, 0)
self.line6.set_color(0, 0, 0)
self.line7.set_color(0, 0, 0)
self.line8.set_color(0, 0, 0)
self.line9.set_color(0, 0, 0)
self.line10.set_color(0, 0, 0)
self.line11.set_color(0, 0, 0)
self.viewer.add_geom(self.line1)
self.viewer.add_geom(self.line2)
self.viewer.add_geom(self.line3)
self.viewer.add_geom(self.line4)
self.viewer.add_geom(self.line5)
self.viewer.add_geom(self.line6)
self.viewer.add_geom(self.line7)
self.viewer.add_geom(self.line8)
self.viewer.add_geom(self.line9)
self.viewer.add_geom(self.line10)
self.viewer.add_geom(self.line11)
self.viewer.add_geom(self.kulo1)
self.viewer.add_geom(self.kulo2)
self.viewer.add_geom(self.gold)
self.viewer.add_geom(self.robot)
#设置机器人当前的圆心坐标
if self.state is None: return None
self.robotrans.set_translation(self.x[self.state-1], self.y[self.state- 1])
#返回语句
return self.viewer.render(return_rgb_array=mode == 'rgb_array')
1、将环境文件(笔者创建的文件名为 grid_mdp.py) 拷贝到自己的gym安装目录/gym/envs/classic_control文件夹中。(拷贝在此文件夹中是因为要使用rendering模块)
2、打开该文件夹(第⼀步中的文件夹)下的__init__.py⽂件,在文件末尾加入语句
from gym.envs.classic_control.grid_mdp import GridEnv
3、进⼊⽂件夹的gym安装目录/gym/gym/envs,打开该文件夹下 的__init__.py文件
添加代码:
第⼀个参数id就是调用gym.make(‘id’)时的id,这个id可以随便选取,笔者取的名字是GridWorld-v0。
第⼆个参数是函数路口。
后⾯的参数原则上来说可以不写。
register (
id= 'GridWorld-v0',
entry_point='gym.envs.classic_control:GridEnv',
max_episode_steps=200, reward_threshold=100.0,
)
书写一个简单的demo来测试环境的效果。
书中的测试不太正确,这里我改成了适合win10系统的代码
import gym
env = gym.make('GridWorld-v0')
env. reset ()
env. render ()
AttributeError: ‘GridEnv’ object has no attribute ‘_seed’
问题分析:
在registration.py文件中找到如下代码:
env.reset = env._reset
env.step = env._step
env.seed = env._seed
对比其他的模型(如:CartPole-v0)我们发现书中的代码不存在seed函数。
所以在书里的模型中添加seed函数。
修改完之后继续运行,第二步不再报错,第三步显示机器人随机的初始位置为3。
运行第四步时又出现了错误。
错误提示为
AttributeError: ‘GridEnv’ object has no attribute ‘_render’
所以根据提示将registration.py中的render改为_render即可
修改之后第四步也运行正确,吃到了金币,显示true
图形显示多种结果如下