【莫烦强化学习】视频笔记(二)2.编写一个Q学习的小例子

第5节 编写一个Q学习的小例子


本节使用python编写一个简单的Q学习例子,来体会Q学习中各种参数及更新过程。

最后的效果是什么样的呢?

这次要编写的是机器人寻宝,是一个线性的环境,o代表机器人,T代表终点,也就是宝藏所在处,当然也可以使用自己的符号表示。环境(只是外表,奖励值也是环境的一部分,但是是不可见的)长下面这样:

-o---T

这只是一个示例,空间长度是6,也可以自定义环境,更好的也可以是手动输入环境。
最终会以命令行的形式不断更新显示环境,到达终点算作一次过程(episode),每挪动一个位置算一步,结果应该是随着过程数目增加,机器人找到宝藏的速度越来越快。
每次episode结束显示所走步数。


哪些准备?

首先需要明确什么是状态s和动作a,状态即机器人所处的位置,动作即向左或向右。(当然撞墙了就不能走了,即两端的状态是不同的)
接下来是Q表了,一个二维数组,大小为 s i z e ( s ) ∗ s i z e ( a ) size(s)*size(a) size(s)size(a)。每一个状态 s i s_i si对应所有的动作 a 0 , a 1 … a_0,a_1… a0,a1,每一个格子对应着一个Q值,如下表所示:

a 1 a_1 a1
s 1 s_1 s1 Q ( s 1 , a 1 ) Q(s_1,a_1) Q(s1,a1)
s 2 s_2 s2 Q ( s 2 , a 1 ) Q(s_2,a_1) Q(s2,a1)

接下来是如何选择的动作,也就是之前说到的 ϵ − G r e e d y \epsilon-Greedy ϵGreedy方法,可以作为一个单独的函数模块。
最后就是创建环境,环境就是上面所述的线性空间,但是一般情况下,环境都很复杂,比如倒立摆模型就涉及到物理原理等,所以这里的环境是极为简单的。


需要哪些库?

Numpy、Pandas,基本的用法也可以参考莫烦的教程,教程链接:Numpy&Pandas教程系列
Time模块是用来控制动画速度的,可以进行时间有关的操作。导入Python库的代码如下:

import numpy as np  # 进行运算
import pandas as pd  # 表格操作
import time  # 时间控制

开始编写

首先设置随机种子,保证每次运行时,每一步生成的随机数都相同,能够实现过程的重现。

np.random.seed(2)  # 设置随机种子,保证生成随机数相同

接下来,设置全局变量,变量的解释在代码中,当然不是一下子全都先列出来放着,可能是过程中用到什么就列在全局变量的位置。

N_STATES = 6   # 状态数,即世界的长度,本例共有6格
ACTIONS = ['left', 'right']     # 动作空间,有几个可以选择的动作
EPSILON = 0.9   # ε-greedy中的ε,数值越小,随机性越强
ALPHA = 0.1     # 学习率
GAMMA = 0.9    # 衰减因子,对于未来的Reward的关注程度
MAX_EPISODES = 13   # 最大episode数,完成一局游戏是一个episode
FRESH_TIME = 0.3    # 刷新动画时间间隔

下一步建立Q表,使用pandas库建立表格:

def create_Q_table(state_num, actions):  # 输入状态数和动作空间
    table = pd.DataFrame(np.zeros((state_num, len(actions))), columns=actions)  # 创建一个表格,规格为状态数*动作数,列名为动作空间名称
    print("创建的Q表如下:")
    print(table)  # 输出表格观察
    return table

可以尝试运行以下,观察Q表的结构,有6格状态,2个动作:“向左走”、“向右走”。结果如下:

创建的Q表如下:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0

然后是动作选择函数,随机生成一个数,如果小于 ϵ \epsilon ϵ(假如说为0.9)说明有0.9的可能性进行纯贪婪选择,0.1的可能性随机选择动作,代码如下:

# 动作选择函数,根据ε和Q值选择状态s下的最佳动作
def choose_action(state, q_table):
    state_actions = q_table.iloc[state, :]  # 选择表格对应s行的所有列值
    if (np.random.uniform() > EPSILON) or (state_actions.all() == 0):  # 生成随机数,如果大于0.9也就是0.1的几率,或者两个动作Q值都为0
        action_name = np.random.choice(ACTIONS)   # 随机选择一个动作,返回动作名称"left" 或 "right"
    else:
        action_name = state_actions.argmax()  # 返回最大Q值对应的动作名称
    return action_name  # 返回名称,可能因为pandas用列名访问

接下来,是环境部分,这里输入的是当前状态 s s s动作 a a a,输出的是环境的反馈,即回报Reward,以及该动作后转移到的状态 s ′ s' s。(这里不是固定不变的,可以人为规定,比如说获得的奖励值、下一个动作是什么等等):

# 环境反馈函数
def get_env_feedback(state, action):  # 输入一个状态和动作
    if action == 'right':  # 向右走
        if state == N_STATES - 2:  # 下一步就是终态,现在在倒数第二个格
            state = 'terminal'  # 标记为终点
            R = 1  # 获得奖励
        else:
            state = state + 1  # 往右走一格
            R = 0  # 普通格子无奖励
    else:  # 向左走
        R = 0  # 这里的意思是往左走不可能到达终态
        if state == 0:  # 走到最左边,再走下一步撞墙了
            state = state  # 原地不动,下一状态还是原地
        else:
            state = state - 1  # 向左走一格
    return state, R

渲染环境的函数,输入的纯粹是状态,只渲染该状态下的环境图像,以及到达终态时输出的总结信息:

# 更新环境动画界面函数,纯粹根据当前状态绘制图像(命令行)
def update_env(state, episode, step_counter):
    env_list = ['-']*(N_STATES - 1) + ['T']  # 一开始的环境列表是['-', '-', '-', '-', '-', 'T']
    if state == 'terminal':  # 到达终态
        interaction = 'Episode {0}: total_steps = {1}'.format(episode, step_counter)  # 输出本次进程的信息,第几次游戏,一共走了几步到终点
        print('\r{}'.format(interaction), end='')  # \r是光标回到前一行,应该是原地刷新
        time.sleep(2)  # 暂停
        print('\r                                ', end='')  # 输出空行,视觉暂留
    else:
        env_list[state] = 'o'  # 标记机器人位置
        interaction = ''.join(env_list)  # 把列表转成字符串
        print('\r{}'.format(interaction), end='')
        time.sleep(FRESH_TIME)  # 暂停

最后是Q学习的主循环函数,返回的最后的Q表,可以看一下最优的策略是如何的,伪代码已经给出:
在这里插入图片描述

# 主循环函数
def rl():
    q_table = create_Q_table(N_STATES, ACTIONS)  # 创建Q表
    for episode in range(MAX_EPISODES):  # 游戏局数,episode数(1~MAX_EPISODES-1)
        step_counter = 0  # 计数器,计算本次游戏步数
        S = 0  # 本次游戏状态初始化为第一个格子
        is_terminal = False  # 是否达到终态的标记
        while not is_terminal:  # 未到达终态
            A = choose_action(S, q_table)  # ε-greedy选择动作,一开始随机选择,因为都是0
            S_, R = get_env_feedback(S, A)  # 与环境交互获得下一状态状态和回报值
            q_predict = q_table.loc[S, A]  # 估计Q值,也就是当前的Q值
            if S_!= 'terminal':
                q_target = R + GAMMA * q_table.iloc[S_, :].max()  # 最大Q值对应的动作,真实值估计
            	is_terminal = True
            else:
                q_target = R  # 终态的话,直接得到真实的回报值

            q_table.loc[S, A] += ALPHA * (q_target - q_predict)  # 更新Q表
            S = S_  # 状态转移
            update_env(S, episode, step_counter)  # 渲染状态下的环境
            step_counter += 1  # 加一步
            
    return q_table

最后是主函数,打印出最后收敛的Q表:

if __name__ == '__main__':
    # 主函数
    q_table = rl()  # 返回Q表
    print("\r最终Q表:")
    print(q_table)

全代码一览
import numpy as np
import pandas as pd
import time

np.random.seed(2)  # 设置随机种子,保证生成随机数相同


N_STATES = 6   # 状态数,即世界的长度,本例共有6格
ACTIONS = ['left', 'right']     # 动作空间,有几个可以选择的动作
EPSILON = 0.9   # ε-greedy中的ε,数值越小,随机性越强
ALPHA = 0.1     # 学习率
GAMMA = 0.9    # 衰减因子,对于未来的Reward的关注程度
MAX_EPISODES = 13   # 最大episode数,完成一局游戏是一个episode
FRESH_TIME = 0.3    # 刷新动画时间间隔


# 建立Q表函数
def create_Q_table(state_num, actions):  # 输入状态数和动作空间
    table = pd.DataFrame(np.zeros((state_num, len(actions))), columns=actions)  # 创建一个表格,规格为状态数*动作数,列名为动作空间名称
    print("创建的Q表如下:")
    print(table)  # 输出表格观察
    return table


# 动作选择函数,根据ε和Q值选择状态s下的最佳动作
def choose_action(state, q_table):
    state_actions = q_table.iloc[state, :]  # 选择表格对应s行的所有列值
    if (np.random.uniform() > EPSILON) or (state_actions.all() == 0):  # 生成随机数,如果大于0.9也就是0.1的几率,或者两个动作Q值都为0
        action_name = np.random.choice(ACTIONS)   # 随机选择一个动作,返回动作名称"left" 或 "right"
    else:
        action_name = state_actions.argmax()  # 返回最大Q值对应的动作名称
    return action_name  # 返回名称,可能因为pandas用列名访问


# 环境反馈函数
def get_env_feedback(state, action):  # 输入一个状态和动作
    if action == 'right':  # 向右走
        if state == N_STATES - 2:  # 下一步就是终态,现在在倒数第二个格
            state = 'terminal'  # 标记为终点
            R = 1  # 获得奖励
        else:
            state = state + 1  # 往右走一格
            R = 0  # 普通格子无奖励
    else:  # 向左走
        R = 0  # 这里的意思是往左走不可能到达终态
        if state == 0:  # 走到最左边,再走下一步撞墙了
            state = state  # 原地不动,下一状态还是原地
        else:
            state = state - 1  # 向左走一格
    return state, R


# 更新环境动画界面函数,纯粹根据当前状态绘制图像(命令行)
def update_env(state, episode, step_counter):
    env_list = ['-']*(N_STATES - 1) + ['T']  # 一开始的环境列表是['-', '-', '-', '-', '-', 'T']
    if state == 'terminal':  # 到达终态
        interaction = 'Episode {0}: total_steps = {1}'.format(episode, step_counter)  # 输出本次进程的信息,第几次游戏,一共走了几步到终点
        print('\r{}'.format(interaction), end='')  # \r是光标回到前一行,应该是原地刷新
        time.sleep(2)  # 暂停
        print('\r                                ', end='')  # 输出空行,视觉暂留
    else:
        env_list[state] = 'o'  # 标记机器人位置
        interaction = ''.join(env_list)  # 把列表转成字符串
        print('\r{}'.format(interaction), end='')
        time.sleep(FRESH_TIME)  # 暂停


# 主循环函数
def rl():
    q_table = create_Q_table(N_STATES, ACTIONS)  # 创建Q表
    for episode in range(MAX_EPISODES):  # 游戏局数,episode数(1~MAX_EPISODES-1)
        step_counter = 0  # 计数器,计算本次游戏步数
        S = 0  # 本次游戏状态初始化为第一个格子
        is_terminal = False  # 是否达到终态的标记
        while not is_terminal:  # 未到达终态
            A = choose_action(S, q_table)  # ε-greedy选择动作,一开始随机选择,因为都是0
            S_, R = get_env_feedback(S, A)  # 与环境交互获得下一状态状态和回报值
            q_predict = q_table.loc[S, A]  # 估计Q值,也就是当前的Q值
            if S_ != 'terminal':
                q_target = R + GAMMA * q_table.iloc[S_, :].max()  # 最大Q值对应的动作,真实值估计
            else:
                q_target = R  # 终态的话,直接得到真实的回报值
                is_terminal = True

            q_table.loc[S, A] += ALPHA * (q_target - q_predict)  # 更新Q表
            S = S_  # 状态转移
            update_env(S, episode, step_counter)  # 渲染状态下的环境
            step_counter += 1  # 加一步

    return q_table


if __name__ == '__main__':
    # 主函数
    q_table = rl()  # 返回Q表
    print("\r最终Q表:")
    print(q_table)


最终Q表
       left     right
0  0.000001  0.005728
1  0.000271  0.032612
2  0.002454  0.111724
3  0.000073  0.343331
4  0.000810  0.745813
5  0.000000  0.000000

上一篇:【莫烦强化学习】视频笔记(二)1. 什么是Q-Learning?
下一篇:【莫烦强化学习】视频笔记(二)3.Q_Learning算法实现走迷宫

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值