gym中toy text——Q-learning实现

描述

使用Q-leaning算法实现gym中的toy text

toy text环境

gym中的toy text环境包含很多中,这里介绍三种带渲染环境的

  • FrozenLake-v0

    冬天来了。你和你的朋友在公园里玩飞盘的时候,你疯狂地把飞盘扔在了湖中央。水大部分是冻结的,但有几个洞的冰已经融化。如果你踏进其中一个洞,你就会掉进冰冷的水里。在这个时候,有一个国际飞盘短缺,所以你绝对有必要航行到湖的另一边并取回飞盘。然而,冰面很滑,所以你不能总是按照你想要的方向移动。
    在这里插入图片描述

    表面用网格描述如下:
    S: 起始点,安全
    F: 冰冻表面,安全
    H: 冰洞, 会掉下去
    G: 目标,飞盘的位置

    action动作为:
    0 left, 1 down, 2 right, 3 up

    state状态为:
    从左上角到右下角,依次是0,1,…,15

    agent在网格世界中控制角色的移动。网格的一些瓦片是可行走的,而另一些则导致agent落入水中。此外,agent的移动方向是不确定的,只部分依赖于所选择的方向。

  • FrozenLake8x8-v0
    更大的湖面
    在这里插入图片描述

  • Taxi-v3
    这个任务在[Dietterich2000]中被介绍来说明层次强化学习中的一些问题。有4个地点(用不同的字母标记),你的工作是在一个地点接乘客,然后在另一个地点让他下车。成功的掉落会得到+20分,每走一步会损失1分。违法上下车也会被扣10分。
    在这里插入图片描述
    RGBY是四个点,紫色和蓝色会随机涂在它们身上,蓝色点代表乘客叫车的位置,紫色点代表乘客的目的地,小车会随机初始化在表格中

代码

通用的Q-leaning代码

import gym
import numpy as np
import time

class QLearningAgent(object):
    # 参数初始化
    def __init__(self, stateSpace_size, actionSpace_size, learning_rate=0.01, gamma=0.9, epsilon=0.1):
        self.stateSpace_size = stateSpace_size  # 状态空间大小
        self.actionSpace_size = actionSpace_size  # 动作空间大小
        self.learning_rate = learning_rate  # 学习率
        self.gamma = gamma  # reward的衰减率
        self.epsilon = epsilon  # 按一定概率随机选动作
        self.QTable = np.zeros((stateSpace_size, actionSpace_size))  # Q表

        self.trainTimes = 100
        self.runTimes = 10

    # 设置训练的迭代次数
    def setTrainTimes(self, train_times):
        self.trainTimes = train_times

    # 设置运行的次数
    def setRunTimes(self, run_times):
        self.runTimes = run_times

    # 设置
    def setTrust(self, trust_ratio):
        self.epsilon = trust_ratio

    # 选择state下的动作
    def getAction(self, state):
        sand = np.random.uniform(0, 1)
        if sand <= self.epsilon:
            action = np.random.choice(self.actionSpace_size)
        else:
            action = self.chooseActionFromQTable(state)
        return action

    # 依据Q表选择动作
    def chooseActionFromQTable(self, state):
        Q_max = np.max(self.QTable[state, :])
        action_list = np.where(self.QTable[state, :] == Q_max)[0]
        action = np.random.choice(action_list)
        return action

    # 更新Q表
    def updateQTable(self, state, action, reward, next_state, done):
        if done:
            Q_future = 0
        else:
            Q_future = np.max(self.QTable[next_state, :])
        updateValue = self.learning_rate * (reward + self.gamma * Q_future - self.QTable[state, action])
        self.QTable[state, action] += updateValue

    # 显示Q表
    def showQTable(self):
        print(self.QTable)

    # 从文件中读取数据到Q表格中
    def loadQTable(self, path):
        self.QTable = np.load(path)
        print(path + ' loaded.')

    # 保存Q表格数据到文件
    def saveQTable(self, path):
        np.save(path, self.QTable)
        print(path + ' saved.')

    # 训练代码
    def train(self, env, agent, render):
        total_steps = 0  # 训练次数
        while True:
            state = env.reset()  # 重置环境, 重新开一局(即开始新的一个episode)
            episode_reward = 0
            episode_steps = 0
            if total_steps >= self.trainTimes:
                break
            while True:
                action = self.getAction(state)  # 根据算法选择一个动作
                next_state, reward, done, info = env.step(action)  # 与环境进行一个交互
                agent.updateQTable(state, action, reward, next_state, done)  # 更新Q表
                state = next_state  # 存储上一个观察值
                episode_reward += reward
                episode_steps += 1  #  记录每个episode走了多少step
                if render:
                    env.render()  # 渲染新的一帧图形
                if done:
                    total_steps += 1
                    print("episode "+str(total_steps)+", train steps "+str(episode_steps)+ ", reward "+str(
                        episode_reward))
                    break

    # 测试代码
    def run(self, env, agent, render):
        total_steps = 0
        while True:
            state = env.reset()  # 重置环境, 重新开一局(即开始新的一个episode)
            episode_reward = 0
            episode_steps = 0
            if total_steps >= self.runTimes:
                break
            while True:
                action = self.chooseActionFromQTable(state)  # 根据算法选择一个动作
                next_state, reward, done, info = env.step(action)  # 与环境进行一个交互
                state = next_state  # 存储上一个观察值
                episode_reward += reward
                episode_steps += 1  # 记录每个episode走了多少step
                if render:
                    time.sleep(0.5)
                    env.render()  # 渲染新的一帧图形
                if done:
                    total_steps += 1
                    print("episode " + str(total_steps) + ", run steps " + str(episode_steps) + ", reward " + str(
                        episode_reward))
                    break



render = True  # 是否要显示环境
env = gym.make('Taxi-v3')

agent = QLearningAgent(
        stateSpace_size=env.observation_space.n,
        actionSpace_size=env.action_space.n,
        learning_rate=0.1,
        gamma=0.9,
        epsilon=0.3)  # 定义一个agent

#agent.loadQTable('./q_table.npy')  # 加载已保存的Q表,初次执行请注释
agent.setTrainTimes(1000)
agent.train(env, agent, False)  # 训练可以不用看界面
agent.showQTable()

agent.setRunTimes(10)
agent.run(env, agent, render)  # 测试时可以看看界面
agent.saveQTable('./q_table.npy')

代码中你可以改变env = gym.make('Taxi-v3'),变成env = gym.make('FrozenLake-v0')env = gym.make('FrozenLake8x8-v0'),代码可以正常运行。

因为我写的这段代码,是Q-leaning基本框架,gym环境返回的state和action等的接口也是统一的。

问题及改进

1. FrozenLake-v0的打滑问题

在实际运行过程中,会发现FrozenLake-v0训练500次甚至5000次,效果都不好,实际运行时发现都不能良好的运行到终点

原因是,gym环境中也说了,FrozenLake-v0环境不是严格按照agent想法来走的(冰面是滑的),因此带来的问题是,old state+action不等于new state。

解决办法是is_slippery=False将冰面设置为不滑的,这样只训练500次就能得到很好的结果了

env = gym.make('FrozenLake-v0', is_slippery=False)

当然我们还有另外一种解决办法,就是改变算法的设置。我们可以这样设置,

  • 当在state下执行动作action后,由于环境简单我们已经知道了一个estimated state(估计的状态)
  • 如果new state并不是estimated state,这一次的经验我们就不要学习了

在冰面滑的情况下去学习

# 更新Q表
def updateQTable(self, state, action, reward, next_state, done):
	# 只需要在原代码的基础上多加这一句话
	# 意思是,只学习没有打滑的经验
    if (action == 0 and next_state == state - 1) or (action == 1 and next_state == state + 4) \
            or (action == 2 and next_state == state + 1) or (action == 0 and next_state == state - 4):
        if done:
            Q_future = 0
        else:
            Q_future = np.max(self.QTable[next_state, :])
        updateValue = self.learning_rate * (reward + self.gamma * Q_future - self.QTable[state, action])
        self.QTable[state, action] += updateValue

由于增加了冰面滑的特性,不滑时500次可能你就已经完全掌握了湖面,现在你不得不多训练一些,因为你有几率打滑然后结果会很糟糕。
为了看你的经验学习是不是成功,你测试时还是要把冰面设置成不滑is_slippery=False。挺好理解的,你即使完全会走了,湖面硬让你滑进洞,你也没办法

agent.setTrainTimes(5000)
agent.train(env, agent, False)  # 训练可以不用看界面
agent.showQTable()
env = gym.make('FrozenLake-v0', is_slippery=False)
agent.setRunTimes(10)
agent.run(env, agent, False) 

输出结果会是:

episode 1, run steps 6, reward 1.0
episode 2, run steps 6, reward 1.0
episode 3, run steps 6, reward 1.0
episode 4, run steps 6, reward 1.0
episode 5, run steps 6, reward 1.0
episode 6, run steps 6, reward 1.0
episode 7, run steps 6, reward 1.0
episode 8, run steps 6, reward 1.0
episode 9, run steps 6, reward 1.0
episode 10, run steps 6, reward 1.0

2. FrozenLake8x8-v0的训练次数

FrozenLake8x8-v0和FrozenLake-v0一样,都存在上述的打滑问题,这一点上一节已经说明了

但当我们设置好打不打滑后,发现FrozenLake8x8-v0训练500次并没有达到预期的目标,agent不能很好的达到终点。

原因也比较好理解,FrozenLake8x8-v0湖面是FrozenLake-v0面积的四倍。Q-learning本身可以简单理解为一个枚举算法(我自己的理解啊,讨论时可以这么说,公开专业的场合可不能这么说),你所有状态需要一个更高的迭代次数去遍历。

因此设置一个更高的迭代次数,能有效的解决这个问题

agent.setTrainTimes(50000)

同理,Taxi-v3的训练次数也存在这个问题,解决办法是一样的

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值