qlearning算法_强化学习代码记录一 sarsa和qlearning

本文介绍了Sarsa和Q-learning两种强化学习算法,它们都属于时间差分法TD,但Sarsa是on-policy,Q-learning是off-policy。在Baidu AI Studio的强化学习7日打卡营中,通过代码记录了Sarsa的训练和测试过程,以及Q-learning的表格更新方式。Sarsa在训练中学习避免危险,而Q-learning则倾向于贴着悬崖行走,这源于它们在更新Q值时的行为策略差异。
摘要由CSDN通过智能技术生成

sarsa和qlearning都属于时间差分法TD,是有偏估计,只用到了下一步的状态和动作估计Q。此外还有采用后续多步的TD(λ)。

以下来自对Baidu AI Studio - 人工智能学习与实训社区的强化学习7日打卡营的代码记录:

323aae622dddfe69ce263cf94a6b66b4.png

1. Sarsa

  • 目的是学习特定的state下,特定action的价值Q,最终建立和优化一个Q表格,以state为行,action为列,根据与环境交互得到的reward来更新Q表格,更新公式为:

d74b0f4654383fd397d309168edc0b14.png

85f0ef576c76a9af22b0b8efb3dace8b.png
  • Sarsa在训练中为了更好的探索环境,采用ε-greedy方式来训练,有一定概率随机选择动作输出。
  • Agent是和环境environment交互的主体。
  • predict()方法:输入观察值observation(或者说状态state),输出动作值
  • sample()方法:再predict()方法基础上使用ε-greedy增加探索
  • learn()方法:输入训练数据,完成一轮Q表格的更新
# agent.py 
class SarsaAgent(object):
    def __init__(self, obs_n, act_n, learning_rate=0.01, gamma=0.9, e_greed=0.1):
        self.act_n = act_n      # 动作维度,有几个动作可选
        self.lr = learning_rate # 学习率
        self.gamma = gamma      # reward的衰减率
        self.epsilon = e_greed  # 按一定概率随机选动作
        self.Q = np.zeros((obs_n, act_n))
        #生成二维得Q表格,状态为行,动作为列,所谓状态即obs观测者,在Q表格中表示为环境格子得总数
        #Q表格的行是环境格子的总数,比如CliffWalking的Q表格有4*12=48行,FrozenLake的Q表格有4*4=16行。
        #Q结构是“环境格子数”*“动作数”。Q值即对应的每个格子采用某个动作所获得的终极回报

    # 根据输入观察值,采样输出的动作值,带探索
    def sample(self, obs):
        #if条件是根据状态选择能输出最大Q值的动作,有90%的机会执行exploitation
        if np.random.uniform(0, 1) < (1.0 - self.epsilon): #根据table的Q值选动作
            action = self.predict(obs)
        else:#有10%的机会执行exploration探索
            action = np.random.choice(self.act_n) #有一定概率随机探索选取一个动作
        return action

    # 根据输入观察值,预测输出的动作值,输出能产生最大Q值得action
    def predict(self, obs):
        Q_list = self.Q[obs, :]
        maxQ = np.max(Q_list)
        action_list = np.where(Q_list == maxQ)[0]  # maxQ可能对应多个action
        action = np.random.choice(action_list)#多个action则随机选一个
        return action

    # 学习方法,也就是更新Q-table的方法
    def learn(self, obs, action, reward, next_obs, next_action, done):
        """ on-policy 即求target_Q时使用的self.Q[next_obs, next_action]是下一个obs-状态实际sample
                      到的action所对应的Q。即下一个obs和实际动作对应的Q
            obs: 交互前的obs, s_t
            action: 本次交互选择的action, a_t
            reward: 本次动作获得的奖励r
            next_obs: 本次交互后的obs, s_t+1
            next_action: 根据当前Q表格, 针对next_obs会选择的动作, a_t+1
            done: episode是否结束
        """
        predict_Q = self.Q[obs, action]#获取对应单元格在动作action下的Q值
        if done:
            target_Q = reward # 没有下一个状态了
        else:
            target_Q = reward + self.gamma * self.Q[next_obs, next_action] # Sarsa
        self.Q[obs, action] += self.lr * (target_Q - predict_Q) # 修正q

    # 保存Q表格数据到文件
    def save(self):
        npy_file = './q_table.npy'
        np.save(npy_file, self.Q)
        print(npy_file + ' saved.')
    
    # 从文件中读取Q值到Q表格中
    def restore(self, npy_file='./q_table.npy'):
        self.Q = np.load(npy_file)
        print(npy_file + ' loaded.')

Training && Test(训练&&测试)

  • run_episode()agent在一个episode中训练的过程,使用agent.sample()与环境交互,使用agent.learn()训练Q表格。
  • test_episode()agent在一个episode中测试效果,评估目前的agent能在一个episode中拿到多少总reward
def run_episode(env, agent, render=False):
    total_steps = 0 # 记录每个episode走了多少step
    total_reward = 0

    obs = env.reset() # 重置环境, 重新开一局(即开始新的一个episode)
    action = agent.sample(obs) # 根据算法选择一个动作

    while True:
        next_obs, reward, done, _ = env.step(action) # 与环境进行一个交互,由action->state与策略无关,
        #由环境决定,由环境的状态转移概率决定。
        next_action = agent.sample(next_obs) # 根据算法选择一个动作,
        #由策略决定,一般平衡exploration和exploitation
        # 训练 Sarsa 算法,更新Q值表
        agent.learn(obs, action, reward, next_obs, next_action, done)

        action = next_action  #该动作是下次实际采用动作
        obs = next_obs  # 存储上一个观察值
        total_reward += reward
        total_steps += 1 # 计算step数
        if render:
            env.render() #渲染新的一帧图形
        if done:
            break
    return total_reward, total_steps


def test_episode(env, agent):
    total_reward = 0
    obs = env.reset()
    while True:
        action = agent.predict(obs) # 测试阶段不需要探索,greedy
        next_obs, reward, done, _ = env.step(action)
        total_reward += reward
        obs = next_obs
        # time.sleep(0.5)
        # env.render()
        if done:
            break
    return total_reward

Step5 创建环境和Agent,启动训练

# 使用gym创建悬崖环境
env = gym.make("CliffWalking-v0")  # 0 up, 1 right, 2 down, 3 left

# 创建一个agent实例,输入超参数
agent = SarsaAgent(
        obs_n=env.observation_space.n,
        act_n=env.action_space.n,
        learning_rate=0.1,
        gamma=0.9,
        e_greed=0.1)


# 训练500个episode,打印每个episode的分数
for episode in range(500):
    ep_reward, ep_steps = run_episode(env, agent, False)
    print('Episode %s: steps = %s , reward = %.1f' % (episode, ep_steps, ep_reward))

# 全部训练结束,查看算法效果
test_reward = test_episode(env, agent)
print('test reward = %.1f' % (test_reward))

2.Q-learning

  • Q-learning也是采用Q表格的方式存储Q值(状态动作价值),决策部分与Sarsa是一样的,采用ε-greedy方式增加探索。
  • Q-learningSarsa不一样的地方是更新Q表格的方式。
    • Sarsaon-policy的更新方式,先做出动作再更新。
    • Q-learningoff-policy的更新方式,更新learn()时无需获取下一步实际做出的动作next_action,并假设下一步动作是取最大Q值的动作。
  • Q-learning的更新公式为:

d9a7b18d946750737afb51b8e083f010.png
class QLearningAgent(object):
    def __init__(self, obs_n, act_n, learning_rate=0.01, gamma=0.9, e_greed=0.1):
        self.act_n = act_n      # 动作维度,有几个动作可选
        self.lr = learning_rate # 学习率
        self.gamma = gamma      # reward的衰减率
        self.epsilon = e_greed  # 按一定概率随机选动作
        self.Q = np.zeros((obs_n, act_n))

    # 根据输入观察值,采样输出的动作值,带探索,与sarsa同
    def sample(self, obs):
        if np.random.uniform(0, 1) < (1.0 - self.epsilon): #根据table的Q值选动作
            action = self.predict(obs)
        else:
            action = np.random.choice(self.act_n) #有一定概率随机探索选取一个动作
        return action

    # 根据输入观察值,预测输出的动作值,与sarsa同
    def predict(self, obs):
        Q_list = self.Q[obs, :]
        maxQ = np.max(Q_list)
        action_list = np.where(Q_list == maxQ)[0]  # maxQ可能对应多个action
        action = np.random.choice(action_list)
        return action

    # 学习方法,也就是更新Q-table的方法,与sarsa不同之处是不需要next_action了来更新Q了,
    #取得是下一obs状态在不同动作下的最大Q:np.max(self.Q[next_obs, :]),而实际在下一obs状态
    #下采用的动作所对应的Q很可能不是这个maxQ
    def learn(self, obs, action, reward, next_obs, done):
        """ off-policy:更新learn()时无需获取下一步实际做出的动作next_action,
                        并假设下一步动作是取最大Q值的动作。但实际采用的可能不是
            obs: 交互前的obs, s_t
            action: 本次交互选择的action, a_t
            reward: 本次动作获得的奖励r
            next_obs: 本次交互后的obs, s_t+1
            done: episode是否结束
        """
        predict_Q = self.Q[obs, action]#即原先的Q值,用来更新
        if done:
            target_Q = reward # 没有下一个状态了
        else:
            target_Q = reward + self.gamma * np.max(self.Q[next_obs, :]) # Q-learning
        self.Q[obs, action] += self.lr * (target_Q - predict_Q) # 修正q

    # 把 Q表格 的数据保存到文件中
    def save(self):
        npy_file = './q_table.npy'
        np.save(npy_file, self.Q)
        print(npy_file + ' saved.')
    
    # 从文件中读取数据到 Q表格
    def restore(self, npy_file='./q_table.npy'):
        self.Q = np.load(npy_file)
        print(npy_file + ' loaded.')

Training && Test(训练&&测试)

def run_episode(env, agent, render=False):
    total_steps = 0 # 记录每个episode走了多少step
    total_reward = 0

    obs = env.reset() # 重置环境, 重新开一局(即开始新的一个episode)

    while True:
        action = agent.sample(obs) # 根据算法选择一个动作,注意sarsa代码中这行放在while前面,
        #因为sarsa后面有next_action = agent.sample(next_obs)语句,而qlearning代码没有。
        next_obs, reward, done, _ = env.step(action) # 与环境进行一个交互
        #与sarsa不同在于在此不需要采样next_action来训练Q
        # 训练 Q-learning算法,注意没有next_action参数了
        agent.learn(obs, action, reward, next_obs, done)
        
        #通过用next_obs更新obs状态,结合while的第一条语句就采样到了next_action,更新了action
        #那这个更新的action,有可能是探索到的action,也有可能是maxQ对应的action,看sample和predict方法
        #所以就是计算targetQ时采用的maxQ所对应的action和实际下一步采用的action可能不一致。也就是offpolicy
        #而sarsa是先通过sample到next_action之后再根据这个next_action来找到对应的Q:self.Q[next_obs, next_action]。
        obs = next_obs  # 存储上一个观察值
        total_reward += reward
        total_steps += 1 # 计算step数
        if render:
            env.render() #渲染新的一帧图形
        if done:
            break
    return total_reward, total_steps

def test_episode(env, agent):
    total_reward = 0
    obs = env.reset()
    while True:
        action = agent.predict(obs) # greedy
        next_obs, reward, done, _ = env.step(action)
        total_reward += reward
        obs = next_obs
        # time.sleep(0.5)
        # env.render()
        if done:
            break
    return total_reward

为什么最终测试时sarsa和qlearning表现不一样,sarsa是学到尽量远离危险行走而qlearning学到是贴着悬崖行走。测试阶段都一样通过采用maxQ走,而sarsa学到的maxQ是远离悬崖,qlearning学到的maxQ是贴着悬崖。因为sarsa是onpolicy在更新Q值时采用的动作和Q值是对应的,贴着悬崖的格子采用动作可能会掉到悬崖获得负的reward而Q值减小,而在远离悬崖处则不容易Q减小,越远离越不容易减小,最终学到是远离悬崖的Q最大。而qlearning则训练中采用的动作和Q值不对应,每次更新Q值都是用maxQ更新,不管行为策略,而贴着悬崖能获得maxQ。

d43abe3260add2ffd157b68bf89e1ef7.png

c2619f43fe13d9e6b5a2d1a7237fccb5.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值