【五】gym搭建自己的环境之寻宝游戏,详细定义自己myenv.py文件以及算法实现

相关文章:

相关文章:

【一】gym环境安装以及安装遇到的错误解决

【二】gym初次入门一学就会-简明教程

【三】gym简单画图

【四】gym搭建自己的环境,全网最详细版本,3分钟你就学会了!

【五】gym搭建自己的环境____详细定义自己myenv.py文件

【六】gym搭建自己环境升级版设计,动态障碍------强化学习


下载丨汀/MyEnv  {目前暂未更新}

gym搭建自己的环境之详细定义自己myenv.py文件

1.模板化环境编程(统一环境代码框架)

通过上篇文章我们已经可以注册搭建自己环境了,下面开始详细构建自己的myenv.py文件,还有疑问请看文章【四】

  • 首先需要定义自己的环境myenv.py,其代码框架如下:
import gym
"""
 gym.Env是gym的环境基类,自定义的环境就是根据自己的需要重写其中的方法;
 必须要重写的方法有: 
  __init__():构造函数
  reset():初始化环境
  step():环境动作,即环境对agent的反馈
  render():如果要进行可视化则实现
"""
class MyEnv(gym.Env):
  1. 函数reset()作用:智能体需要一次次地尝试累积经验,然后从经验中学到好的动作。一次尝试称之为一条轨迹或一个episode. 每次尝试都要到达终止状态. 一次尝试结束后,智能体需要从头开始,这就需要智能体具有重新初始化的功能。
  2. 一个仿真环境必不可少的两部分是物理引擎图像引擎。物理引擎模拟环境中物体的运动规律;图像引擎用来显示环境中的物体图像

  · render()函数作用:起到图像引擎作用,对于强化学习算法渲染函数可以没有,但是加入图像引擎可以方便调试代码时直观显示当前环境中物体的状态。

  ·step()函数作用:起到物理引擎其输入是动作a,输出是:下一步状态,立即回报,是否终止,调试项;该函数中,一般利用智能体的运动学模型和动力学模型计算下一步的状态和立即回报,并判断是否达到终止状态。

from gym import spaces, core
 # core.Env是gym的环境基类,自定义的环境就是根据自己的需要重写其中的方法;
 #同上

class MyEnv(core.Env):
	def __init__(self):
		self.action_space = spaces.Box(low=-1, high=1, shape=(1, )) # 动作空间
		self.observation_space =  spaces.Box(low=-1, high=1, shape=(1, )) # 状态空间
		# 其他成员
	
	def reset(self):
		...
		obs = self.get_observation()
		return obs
	
	def step(self, action):
		...
		reward = self._get_reward()
		done = self._get_done()
		obs = self._get_observation(action)
		info = {} # 用于记录训练过程中的环境信息,便于观察训练状态
		return obs, reward, done, info
		# 根据需要设计相关辅助函数
	def _get_observation(self, action):
		...
		return obs
	
	def _get_reward(self):
		...
		return reward

	def _get_done(self):
		...
		return done

2.项目环境搭建

  • 背景介绍:机器人在一个二维迷宫中走动寻找电池🔋,迷宫中有障碍物、大山、电池。大山机器人是无法走的,游戏终止条件是:机器人设计障碍物里或者找到电池;如何最佳的策略,让机器人尽快地找到电池获得奖励呢。下面将进行解答:

2.1 状态空间代码:

       self.states = range(0,16) #状态空间
       
        self.terminate_states = dict()  #终止状态为字典格式
        self.terminate_states[11] = 1
        self.terminate_states[12] = 1
        self.terminate_states[15] = 1

        self.actions = ['n','e','s','w']

        self.rewards = dict();        #回报的数据结构为字典
        self.rewards['8_s'] = -1.0
        self.rewards['13_w'] = -1.0
        self.rewards['7_s'] = -1.0
        self.rewards['10_e'] = -1.0
        self.rewards['14_e'] = 1.0

        self.t = dict();             #状态转移的数据格式为字典
        self.t['1_s'] = 5
        self.t['1_e'] = 2
        self.t['2_w'] = 1
        self.t['2_e'] = 3
        self.t['3_s'] = 6
        self.t['3_w'] = 2
        self.t['3_e'] = 4
        self.t['4_w'] = 3
        self.t['4_s'] = 7
        self.t['5_s'] = 8
        self.t['5_n'] = 1
        self.t['6_n'] = 3
        self.t['6_s'] = 10
        self.t['6_e'] = 7
        self.t['7_w'] = 6
        self.t['7_n'] = 4
        self.t['7_s'] = 11
        self.t['8_n'] = 5
        self.t['8_e'] = 9
        self.t['8_s'] = 12
        self.t['9_w'] = 8
        self.t['9_e'] = 10
        self.t['9_s'] = 13
        self.t['10_w'] = 9
        self.t['10_n'] = 6
        self.t['10_e'] = 11
        self.t['10_s'] = 14
        self.t['10_w'] = 9
        self.t['13_n'] = 9
        self.t['13_e'] = 14
        self.t['13_w'] = 12
        self.t['14_n'] = 10
        self.t['14_e'] = 15
        self.t['14_w'] = 13

2.2 step函数创建:

动作空间:需要注意的是输出的顺序不要弄错了,对于调试信息,可以为空,但不能缺少,否则会报错,常用{}来代替。

简单阐释:状态转移根据当前状态和动作得到下一步状态,然后判断是否达到终止条件is_terminal决定游戏进程。reward只有到达目的或者障碍是才有其余情况为

   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,{}

2.3 render函数的建立:

    def render(self, mode='human'):      #可视化画图
        from gym.envs.classic_control import rendering
        screen_width = 600
        screen_height = 600

        if self.viewer is None:

            self.viewer = rendering.Viewer(screen_width, screen_height)#调用rendering中的画图函数,#创建600*600的窗口
# 创建网格世界,一共包括10条直线,事先算好每条直线的起点和终点坐标,然后绘制这些直线,代码如下:
            #创建网格世界
            self.line1 = rendering.Line((100,100),(500,100))
            self.line2 = rendering.Line((100, 200), (500, 200))
            self.line3 = rendering.Line((100, 300), (500, 300))
            self.line4 = rendering.Line((100, 400), (500, 400))
            self.line5 = rendering.Line((100, 500), (500, 500))
            self.line6 = rendering.Line((100, 100), (100, 500))
            self.line7 = rendering.Line((200, 100), (200, 500))
            self.line8 = rendering.Line((300, 100), (300, 500))
            self.line9 = rendering.Line((400, 100), (400, 500))
            self.line10 = rendering.Line((500, 100), (500, 500))

            #创建大山
            self.mountain = rendering.make_circle(40)
            self.circletrans = rendering.Transform(translation=(250,350))
            self.mountain.add_attr(self.circletrans)
            self.mountain.set_color(0,1,1)

            #创建第一个障碍物
            self.obstacle_1 = rendering.make_circle(35)
            self.circletrans = rendering.Transform(translation=(450, 250))
            self.obstacle_1.add_attr(self.circletrans)
            self.obstacle_1.set_color(0, 0, 0)

            #创建第二个障碍物
            self.obstacle_2 = rendering.make_circle(35)
            self.circletrans = rendering.Transform(translation=(150, 150))
            self.obstacle_2.add_attr(self.circletrans)
            self.obstacle_2.set_color(0, 0, 0)

            #创建电池
            self.Battery = rendering.make_circle(35)
            self.circletrans = rendering.Transform(translation=(450, 150))
            self.Battery.add_attr(self.circletrans)
            self.Battery.set_color(0, 1, 0.5)

            #创建机器人
            self.robot= rendering.make_circle(30)
            self.robotrans = rendering.Transform()
            self.robot.add_attr(self.robotrans)
            self.robot.set_color(1, 0.8, 0)
# 创建完之后,给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)
# 添加组件到Viewer中
            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.mountain)
            self.viewer.add_geom(self.obstacle_1)
            self.viewer.add_geom(self.obstacle_2)
            self.viewer.add_geom(self.Battery)
            self.viewer.add_geom(self.robot)
# 接下来,开始设置机器人的位置。机器人的位置根据其当前所处的状态不同,所在的位置不同。我们事先计算出每个状态处机器人位置的中心坐标,并存储到两个向量中,并在类初始化中给出
        self.x=[150,250,350,450] * 4
        self.y=[450] * 4 + [350] * 4 + [250] * 4 + [150] * 4
"""为了让结果可视化,我们需要自己渲染结果,比如我打算设置一个600×600的窗口,
那么,每一格的中心的横坐标为[150, 250, 350, 450]重复4次(因为是一个1×16的list,每4个为环境的一行),
相应地,纵坐标为150,250,350,450分别重复4次。"""

# 根据这两个向量和机器人当前的状态,我们就可以设置机器人当前的圆心坐标了即:
        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')

2.4 reset()函数的建立:

reset()函数常常用随机的方法初始化机器人的状态,即:

    def reset(self):
        self.state = self.states[int(random.random() * len(self.states))]  #随机初始化机器人状态在[1-16之间随便选]
        return self.state

关闭窗口:

    def close(self):
        if self.viewer:
            self.viewer.close()

下面对创建网格世界进行详细阐释:比如需要创建下面的往网格,则需要10条线,3行4列(本来需要7条),但是有空缺,则8,9,10,需要每个创建从开始到结束的坐标

2.5 环境生成效果图:

2.6 完整代码和文件创建位置:

通过上篇文章我们已经可以注册搭建自己环境了,下面开始详细构建自己的myenv.py文件,还有疑问请看文章【四】

  • 路径H:\Anaconda3-2020.02\envs\tf2\Lib\site-packages\gym\envs\classic_control下的__init__.py文件
  • 拷贝在这个文件夹中因为要使用rendering模块
from gym.envs.classic_control.cartpole import CartPoleEnv
from gym.envs.classic_control.mountain_car import MountainCarEnv
from gym.envs.classic_control.continuous_mountain_car import Continuous_MountainCarEnv
from gym.envs.classic_control.pendulum import PendulumEnv
from gym.envs.classic_control.acrobot import AcrobotEnv

#MyEnv_1:
from gym.envs.classic_control.myenv_1.myenv_1 import MyEnv_1
  • 路径H:\Anaconda3-2020.02\envs\tf2\Lib\site-packages\gym\envs下的__init__.py文件
register(
    id='MyEnv_1-v0',
    entry_point='gym.envs.classic_control:MyEnv_1',
    max_episode_steps=200,
    reward_threshold=195.0,
  • 测试程序
import gym
import time
env = gym.make('MyEnv_1-v0')
env.reset()
env.render()
time.sleep(10)
env.close()
sys.exit()
  • 完整的环境py文件

      见链接:丨汀/MyEnv - Gitee.com

3.基于Q-learning和Epsilon-greedy训练

理论知识见:【五】强化学习之Sarsa、Qlearing详细讲解----PaddlePaddlle【PARL】框架{飞桨}_丨汀、的博客-CSDN博客

这份代码没有考虑大山的存在。具体代码见码云:丨汀/MyEnv - Gitee.com

import gym
import numpy as np
import time
import sys


num_episodes = 5000# 共进行5000场游戏
max_number_of_steps = 10# 每场游戏最大步数


# 以栈的方式记录成绩
goal_average_steps = 100 # 平均分
num_consecutive_iterations = 100 # 栈的容量
last_time_steps = np.zeros(num_consecutive_iterations)  # 只存储最近100场的得分(可以理解为是一个容量为100的栈)

env = gym.make('GridWorld-v0')




# q_table是一个256*2的二维数组
# 离散化后的状态共有4^4=256中可能的取值,每种状态会对应一个行动
# q_table[s][a]就是当状态为s时作出行动a的有利程度评价值
# 我们的AI模型要训练学习的就是这个映射关系表
# 这里的4*4=16是棋盘上棋子的位置数量,第二个参数的4为每个位置对应的4个方向的可能操作。
# q_table的纵坐标是state可能出现的情况之和,横坐标为对应每种state可以做出的action
# 而取值是每种action对于每种state有利程度的评价值
# q_table = np.loadtxt("q_table.txt", delimiter=",")

q_table = np.random.uniform(low=-1, high=1, size=(4 * 4, 4))
#在这之间随机取值

# 根据本次的行动及其反馈(下一个时间步的状态),返回下一次的最佳行动
# epsilon_coefficient为贪心策略中的ε,取值范围[0,1],取值越大,行为越随机
# 当epsilon_coefficient取值为0时,将完全按照q_table行动。故可作为训练模型与运用模型的开关值。
def get_action(state, action, observation, reward, episode, epsilon_coefficient=0.0):
    # print(observation)
    next_state = observation
    epsilon = epsilon_coefficient * (0.99 ** episode)  # ε-贪心策略中的ε
    if epsilon <= np.random.uniform(0, 1):
        next_action = np.argmax(q_table[next_state])
    else:
        next_action = np.random.choice([0, 1, 2, 3])
    # -------------------------------------训练学习,更新q_table----------------------------------
    alpha = 0.2  # 学习系数α
    gamma = 0.99  # 报酬衰减系数γ
    q_table[state, action] = (1 - alpha) * q_table[state, action] + alpha * (
            reward + gamma * q_table[next_state, next_action])
    # -------------------------------------------------------------------------------------------
    return next_action, next_state


timer = time.time()#返回时间单位是秒
for episode in range(num_episodes):
    env.reset()  # 初始化本场游戏的环境
    episode_reward = 0  # 初始化本场游戏的得分
    q_table_cache = q_table # 创建q_table还原点,如若训练次数超次,则不作本次训练记录。

    for t in range(max_number_of_steps):
        env.render()    # 更新并渲染游戏画面
        state = env.state
        action = np.argmax(q_table[state])
        observation, reward, done, info = env.step(action)  # 进行活动,#并获取本次行动的反馈结果
        action, state = get_action(state, action, observation, reward, episode, 0.5)  # 作出下一次行动的决策
        episode_reward += reward

        if done:
            np.savetxt("q_table.txt", q_table, delimiter=",")
            print('已完成 %d 次训练,本次训练共进行 %d 步数。episode_reward:%d,平均分: %f' % (episode, t + 1, reward, last_time_steps.mean()))
            last_time_steps = np.hstack((last_time_steps[1:], [reward]))    # 更新最近100场游戏的得分stack
            break
    q_table = q_table_cache # 超次还原q_table

    
    print('已完成 %d 次训练,本次训练共进行 %d 步数。episode_reward:%d,平均分: %f' % (episode, t + 1, reward, last_time_steps.mean()))
    last_time_steps = np.hstack((last_time_steps[1:], [reward]))  # 更新最近100场游戏的得分stack;np.hstack():在水平方向上平铺

    if (last_time_steps.mean() >= goal_average_steps):
        np.savetxt("q_table.txt", q_table, delimiter=",")
        print('用时 %d s,训练 %d 次后,模型到达测试标准!' % (time.time() - timer, episode))

        env.close()

        sys.exit()


env.close()
sys.exit()

结果如下:

......
已完成 113 次训练,本次训练共进行 4 步数。episode_reward:100,平均分: 96.000000
已完成 113 次训练,本次训练共进行 4 步数。episode_reward:100,平均分: 96.000000
已完成 114 次训练,本次训练共进行 2 步数。episode_reward:100,平均分: 96.000000
已完成 114 次训练,本次训练共进行 2 步数。episode_reward:100,平均分: 96.000000
已完成 115 次训练,本次训练共进行 5 步数。episode_reward:100,平均分: 96.000000
已完成 115 次训练,本次训练共进行 5 步数。episode_reward:100,平均分: 98.000000

posted @ 2022-10-27 21:34  汀、人工智能  阅读(186)  评论(0编辑  收藏  举报