(11-3)TensorFlow强化学习实战:推车子游戏

本项目的功能是使用强化学习(Reinforcement Learning, RL)的一个经典算法(Q-Learning),玩转 OpenAI gym game。本项目的游戏跟本章前面实例的相同,也是MountainCar-v0,这个游戏很简单,将车往不同的方向推,最终让车爬到山顶。本游戏主要包含4个概念,具体说明如表11-1所示。

表11-1  CartPole游戏中的4个概念

概念

解释

例子

State

list: 状态,[位置,速度]

[0.5,-0.01]

Action

int: 动作(0向左推,1不动,2向右推)

2

Reward

float: 每回合-1分

-1

Done

bool: 是否爬到山顶(True/False),上限200回合

-1

如果购物车在200回合还没到达山顶,说明游戏失败,-200是最低分。每个回合得-1,分数越高,说明尝试回合数越少,意味着越早地到达山顶。比如得分-100分,表示仅经过了100回合就到达了山顶。

(1)初始化 Q-Table(Q表)

如果有如下11-8所示的一张表,告诉我在某个状态(State)下, 执行每一个动作(Action)产生的价值(Value),那就可以通过查询表格,选择产生价值最大的动作。

11-8  状态说明表

State

Action 0

Action 1

Action 2

[0.2, -0.01]

10

-20

-30

[-0.3, 0.01]

100

0

0

[-0.1, -0.01]

0

-10

20

应该如何计算价值(Value)呢?游戏的最终目标是爬到山顶,爬到山顶前的每一个动作都为最终的目标贡献了价值,因此每一个动作的价值计算,和最终的结果,也就是与未来(Future)有关。这就是强化学习的经典算法 Q-Learning 设计的核心。Q-Learning中的Q,代表的是 Action-Value,也可以理解为 Quality。而上面这张表,就称之为 Q表(Q-Table)

Q-Learning的目的是创建Q-Table。有了Q-Table,自然能知道选择哪一个Action了。编写程序文件q_learning.py,先初始化一张Q表(Q-Table)。

# 默认将Action 0,1,2的价值初始化为0

Q = defaultdict(lambda: [0, 0, 0])

2连续状态映射

但是此时Q-Table有一个问题,用字典来表示Q-Table,State中的值是浮点数,是连续的,意味着有无数种状态,这样更新Q-Table的值是不可能实现。因此需要对State进行线性转换,实现归一化处理。即将State中的值映射到[0, 40]的空间中。这样,就将无数种状态映射到40x40种状态了。在文件q_learning.py中的代码如下:

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


def transform_state(state):
    """将 position, velocity 通过线性转换映射到 [0, 40] 范围内"""
    pos, v = state
    pos_low, v_low = env.observation_space.low
    pos_high, v_high = env.observation_space.high

    a = 40 * (pos - pos_low) / (pos_high - pos_low)
    b = 40 * (v - v_low) / (v_high - v_low)

    return int(a), int(b)

# print(transform_state([-1.0, 0.01]))
# eg: (4, 22)

3更新 Q-Table

究竟应该更新Q-Table呢?请看下面这个简化版的公式:

Q[s][a] = (1 - lr) * Q[s][a] + lr * (reward + factor * max(Q[next_s]))

上述公式的具体说明如表11-2所示。

表11-2  公式的具体说明

表达式

含义

简介

s, a,next_s

-

当前状态,当前动作,下一个状态

reward

奖励

执行a动作的奖励

Q[s][a]

价值

状态s下,动作a产生的价值

max(Q[next_s])

最大价值

下一个状态下,所有动作价值的最大值

lr

学习速率(learning_rate)

lr越大,保留之前训练效果越少。lr为0,Q[s, a]值不变;lr为1时,完全抛弃了原来的值。

factor

折扣因子(discount_factor)

factor 越大,表示越重视历史的经验; factor 为0时,只关心当前利益(reward)

为什么是max(Q[next_s]),而不是min(Q[next_s])呢?在Q-Table中,状态 next_s 有3个动作可选,即[0, 1, 2],对应价值 **Q[next_s][0],Q[next_s][1],Q[next_s][2]**。Q[s][a]的值应由产生的最大价值的动作决定。

假如我们想象成一个极端场景:在五子棋的最后一步,下在X位置赢,100分;其他位置输,0分。那怎么衡量倒数第二步的价值呢?当然是由最后一步的最大价值决定,不能因为最后一步走错了,就否定前面动作的价值。

(4)训练并保存模型

接下来开始训练,把上面的这个公式嵌入到OpenAI gym中。训练完成后,保存这个模型。

lr, factor = 0.7, 0.95
episodes = 10000  # 训练10000次
score_list = []  # 记录所有分数
for i in range(episodes):
    s = transform_state(env.reset())
    score = 0
    while True:
        a = np.argmax(Q[s])
        # 训练刚开始,多一点随机性,以便有更多的状态
        if np.random.random() > i * 3 / episodes:
            a = np.random.choice([0, 1, 2])
        # 执行动作
        next_s, reward, done, _ = env.step(a)
        next_s = transform_state(next_s)
        # 根据上面的公式更新Q-Table
        Q[s][a] = (1 - lr) * Q[s][a] + lr * (reward + factor * max(Q[next_s]))
        score += reward
        s = next_s
        if done:
            score_list.append(score)
            print('episode:', i, 'score:', score, 'max:', max(score_list))
            break
env.close()

# 保存模型
with open('MountainCar-v0-q-learning.pickle', 'wb') as f:
    pickle.dump(dict(Q), f)
    print('model saved')

因为Q表的状态比较多,当训练到3000次的时候,仍旧没能成功到达山顶。最终训练结束的时候,分数保持在-150左右,最大分数达到-119。代码中的参数都是随便选取的,如果进一步优化的话,会得到更好的结果。执行后会输出:

$ python q_learning.py
episode: 3080 score: -200.0 max: -200
episode: 3081 score: -200.0 max: -200
...
episode: 9996 score: -169.0 max: -119.0
episode: 9997 score: -141.0 max: -119.0
episode: 9998 score: -160.0 max: -119.0
episode: 9999 score: -161.0 max: -119.0
model saved

(5)测试模型

编写测试文件test_q_learning.py,加载上面训练的模型,展示推车子游戏的执行效果。

def transform_state(state):
    """将 position, velocity 通过线性转换映射到 [0, 40] 范围内"""
    pos, v = state
    pos_low, v_low = env.observation_space.low
    pos_high, v_high = env.observation_space.high

    a = 40 * (pos - pos_low) / (pos_high - pos_low)
    b = 40 * (v - v_low) / (v_high - v_low)

    return int(a), int(b)


# 加载模型
with open('MountainCar-v0-q-learning.pickle', 'rb') as f:
    Q = pickle.load(f)
    print('model loaded')

env = gym.make('MountainCar-v0')
s = env.reset()
score = 0
while True:
    env.render()
    time.sleep(0.01)
    # transform_state函数 与 训练时的一致
    s = transform_state(s)
    a = np.argmax(Q[s]) if s in Q else 0
    s, reward, done, _ = env.step(a)
    score += reward
    if done:
        print('score:', score)
        break
env.close()

执行后的效果如图11-2所示。

11-2  执行效果

  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值