【RL】Q-learning走迷宫案例及改进

【RL】Q-learning走迷宫案例及改进_第1张图片
【RL】Q-learning走迷宫案例及改进_第2张图片

假设我们用一个 [公式] 的表格表示 [公式] 值,称作 Q-table。就像训练神经网络时初始化模型系数一样,我们首先会初始化这个表格。然后开始与环境进行交互,以游戏为例。一局(一个 episode)内有很多个 step,我们会做一系列的动作,并最终获得胜利/失败(到达结束状态)。在每一个 step 中, 接收到当前状态 [公式] 后,我们就根据当前的 Q-table 选出 [公式] 值最大的动作 [公式] (也有可能用 [公式] 以一定概率随机选动作),执行该动作 [公式] 后,会从环境获得即时奖励 [公式] 和转移到的下一 step 的状态 [公式] 。

由于我们不知道真实的 [公式] 值是多少(不像监督学习那样有label),我们就需要构建一个估计出来的 Q-target(绿色框部分)。像 TD(0) 一样,Q-learning 每次只往前看一步,它会利用执行当前动作 [公式] 后收到的即时奖励 [公式] 和对转移到的状态 [公式] 的最大 [公式] 值来构造 Q-target。直观一点来说就是,马上能得到的,确定的奖励是 [公式] ,估计未来能得到的最大奖励则是 [公式] ,那定下一个小目标,就让当前估计的 [公式] 能往这两者之和靠拢吧!

创建迷宫环境

import numpy as np
import time
import sys
import tkinter as tk  
import random

UNIT = 40   # pixels 像素
MAZE_H = 4  # grid height 网格高度
MAZE_W = 4 # grid width  网格宽度
class Maze(tk.Tk, object):  # 继承
    def __init__(self):
        super(Maze, self).__init__()
        self.action_space = ['u', 'd', 'l', 'r']
        self.n_actions = len(self.action_space)  # 4
        self.title('maze')
        self._build_maze()

    def _build_maze(self):
        # 初始化画布
        self.canvas = tk.Canvas(self, bg='white', height=MAZE_H * UNIT, width=MAZE_W * UNIT)
        # 画网格
        for c in range(0, MAZE_W * UNIT, UNIT):
            x0, y0, x1, y1 = c, 0, c, MAZE_H * UNIT  # 先描两侧横线上点
            self.canvas.create_line(x0, y0, x1, y1)  # 将上述的点连成线
        for r in range(0, MAZE_H * UNIT, UNIT):
            x0, y0, x1, y1 = 0, r, MAZE_W * UNIT, r
            self.canvas.create_line(x0, y0, x1, y1)

        # 创建一个原点
        origin = np.array([20, 20])

        # 画一个黑色矩形表示陷阱
        hell1_center = origin + np.array([UNIT * 2, UNIT])  # 陷阱的中心点
        self.hell1 = self.canvas.create_rectangle(  # 根据矩形的两个对角的坐标画矩形
            hell1_center[0] - 15, hell1_center[1] - 15,
            hell1_center[0] + 15, hell1_center[1] + 15,
            fill='black')
        # 同上
        hell2_center = origin + np.array([UNIT, UNIT * 2])
        self.hell2 = self.canvas.create_rectangle(
            hell2_center[0] - 15, hell2_center[1] - 15,
            hell2_center[0] + 15, hell2_center[1] + 15,
            fill='black')

        # 画一个圆表示终点
        oval_center = origin + UNIT * 2
        self.oval = self.canvas.create_oval(  # 先根据两个对角点定好矩形,再在矩形里画一个内切圆
            oval_center[0] - 15, oval_center[1] - 15,
            oval_center[0] + 15, oval_center[1] + 15,
            fill='yellow')

        # 画一个红色矩阵表示agent
        self.rect = self.canvas.create_rectangle(
            origin[0] - 15, origin[1] - 15,
            origin[0] + 15, origin[1] + 15,
            fill='red')

        # 打包
        self.canvas.pack()

    def reset(self):  # 复位
        self.update()
        time.sleep(0.5)  # 0.5后复位
        self.canvas.delete(self.rect)
        origin = np.array([20, 20])
        self.rect = self.canvas.create_rectangle(
            origin[0] - 15, origin[1] - 15,
            origin[0] + 15, origin[1] + 15,
            fill='red')
        # 返回agent的坐标(即所处的state)
        #print(self.rect)
        return self.canvas.coords(self.rect)  # 返回的是一个list

    def step(self, action):
        s = self.canvas.coords(self.rect)  # 获取当前agent的位置
        base_action = np.array([0, 0])
        if action == 0:   # up
            if s[1] > UNIT:  # 判断这时候是否在上边界,因为要向上移动
                base_action[1] -= UNIT
        elif action == 1:   # down
            if s[1] < (MAZE_H - 1) * UNIT:  # 同上
                base_action[1] += UNIT
        elif action == 2:   # right
            if s[0] < (MAZE_W - 1) * UNIT:  # 同上
                base_action[0] += UNIT
        elif action == 3:   # left
            if s[0] > UNIT:  # 同上
                base_action[0] -= UNIT
        self.canvas.move(self.rect, base_action[0], base_action[1])  # move agent
        s_ = self.canvas.coords(self.rect)  # agent执行完动作后所处的位置

        # 奖励
        if s_ == self.canvas.coords(self.oval):  # 如果当前所处位置在圆上
            reward = 1
            done = True
            s_ = 'terminal'
        elif s_ in [self.canvas.coords(self.hell1), self.canvas.coords(self.hell2)]:
            reward = -1
            done = True
            s_ = 'terminal'
        else:
            reward = 0
            done = False
        return s_, reward, done

    def render(self):
        time.sleep(0.1)  # 时间步长
        self.update()

def update():
    for t in range(10):
        s = env.reset()
        while True:
            env.render()
            a = random.randint(0, 4)
            s, r, done = env.step(a)
            if done:
                break

if __name__ == '__main__':
    env = Maze()
    env.after(100, update)  # 每100毫秒调用一次函数
    env.mainloop()  # 该方法最后执行,将标签显示在屏幕,进入等待状态(若组件为打包,则不会再窗口中显示)
                    # 准备响应用户发起的GUI事件(图形用户界面)
 
 
 

智能体


```python
#!/usr/bin/python3
# -*- coding:utf-8 -*-
 
"""
This part of code is the Q learning brain, which is a brain of the agent.
All decisions are made in here.
View more on my tutorial page: https://morvanzhou.github.io/tutorials/
"""
 
from tkinter.constants import S
import numpy as np
import pandas as pd


 
class QLearningTable:
    def __init__(self, actions, learning_rate=0.01, reward_decay=0.9, e_greedy=0.95,e_greedy_max = 0.9):
        '''
        :param actions:    行为
        :param learning_rate: 学习率, 来决定这次的误差有多少是要被学习的
        :param reward_decay: 是折扣因子,表示时间的远近对回报的影响程度,为0表示之看当前状态采取行动的reward。 
        :param e_greedy: 是用在决策上的一种策略, 比如 epsilon = 0.9 时, 就说明有90% 的情况我会按照 Q 表的最优值选择行为, 10% 的时间使用随机选行为
        '''
        self.actions = actions  # a list
        self.lr = learning_rate
        self.gamma = reward_decay
        self.epsilon = e_greedy
        self.epsilon_max = e_greedy_max
        self.q_table = pd.DataFrame(columns=self.actions, dtype=np.float64)    #一般行是状态 列是行为
 
    def choose_action(self, observation, episode):
        self.check_state_exist(observation)  #首先检验传入的数据有没有在Q_table当中,如果没有则加入。当作新的索引值
        # action selection
        self.epsilon**=episode
        if self.epsilon < self.epsilon_max:
            pass
        else:
            self.epsilon = self.epsilon_max
        if np.random.uniform() > self.epsilon:   #如果随机数<0.9 
            # choose best action  根据最优结果选择action 
            state_action = self.q_table.loc[observation, :]  #索引  选出这个observation的action值   
            # some actions may have the same value, randomly choose on in these actions
            action = np.random.choice(state_action[state_action == np.max(state_action)].index)   #这句话的意思是打乱
            #action = state_action.idxmax()
        else:
            # choose random action   随机选择(10%的可能)
            action = np.random.choice(self.actions)
        return action
 
    def learn(self, s, a, r, s_):  #从这些状态 提升学习q_table 
        self.check_state_exist(s_)
        q_predict = self.q_table.loc[s, a]
        if s_ != 'terminal':
            q_target = r + self.gamma * self.q_table.loc[s_, :].max()  # next state is not terminal
        else:
            q_target = r  # next state is terminal  已经到达了最终状态
        self.q_table.loc[s, a] += self.lr * (q_target - q_predict)  # update   
 
    def check_state_exist(self, state):  #检查state
        if state not in self.q_table.index:
            # append new state to q table
            self.q_table = self.q_table.append(
                pd.Series(
                    [0]*len(self.actions),
                    index=self.q_table.columns,
                    name=state,
                )
            )

主程序流程

#!/usr/bin/python3
# -*- coding:utf-8 -*-
   
from maze_env import Maze  #调用环境
from RL_brain import QLearningTable  #调用主方法
import matplotlib.pyplot as plt
import pandas as pd
 
def update():
    reward_list = []  #记录每一个episode的奖励总值
    count_list= []
    for episode in range(50):   #运行100个回合
        # initial observation #环境的观测值
        observation = env.reset()  #初始值的信息 (1,1) (1,2) 观测到的信息
        # observation = env.reset()
        reward_sum = 0
        i = 0
        while True:
            # fresh env  
            i+=1
            env.render()  #刷新环境
            # RL choose action based on observation
            action = RL.choose_action(str(observation), episode)  #先挑选一个动作
            # RL take action and get next observation and reward
            observation_, reward, done = env.step(action)  #observation_下一个状态  #done 判断是否拿到奖励还是跳到坑
            # RL learn from this transition  学习
            RL.learn(str(observation), action, reward, str(observation_))  
            # swap observation
            observation = observation_  #循环进入下一个状态
            # break while loop when end of this episode
            reward_sum += reward
            if done:
                count_list.append(i)
                break
        reward_list.append(reward_sum)
        print(reward_list)
        print("第%d回合"%episode)
        print(RL.q_table)
        RL.q_table.to_csv("./1.csv")
    avg_list = []
    for i in range(len(reward_list)):
        avg_list.append(reward_list[i]/count_list[i])
    plt.plot(avg_list)
    plt.show()
    #print(reward_list)
    # end of game
    print('game over')
    env.destroy()
 
if __name__ == "__main__":
    env = Maze()  #Maze()环境
    RL = QLearningTable(actions=list(range(env.n_actions)))   #选择的学习方式
    #print(RL.q_table)
 
    env.after(100, update)
    #print("hahah")
    #print(RL.q_table)
    env.mainloop()  # 进入消息循环


#找出收敛的方法:

"""
设置了一个reward_list去记录每一episode的值,
画图可视化出来
根据图中-1 和1的分布情况
如果大部分是1的情况,则代表收敛了
"""

思考与改进

【RL】Q-learning走迷宫案例及改进_第3张图片

可视化结果:

迭代50次:
【RL】Q-learning走迷宫案例及改进_第4张图片
迭代100次:
【RL】Q-learning走迷宫案例及改进_第5张图片

你可能感兴趣的:(实战,python,强化学习,机器学习)