强化学习Q-Learning算法
:(
实验是在百度的Ai Studio上学习操作的
核心思想
智能体agent在环境environment中学习,根据环境的状态state(或观测到的observation),执行动作action,并根据环境的反馈reward(奖励)来指导更好的动作。强化学习通过不断的试错探索,吸取经验和教训,持续不断地优化策略,以从环境中拿到更好的反馈。
实验原理
- Q-learning学习特定的state下,特定action的价值Q,采用Q表格的方式存储Q值(状态动作价值)。在训练中为了更好的探索环境,采用ε-greedy方式来训练,有一定概率随机选择动作输出。
- Q-learning是离线(off-policy)的更新方式,更新learn()时无需获取下一步实际做出的动作next_action,并假设下一步动作是取最大Q值的动作。其具体更新公式如下:
- 1、建立Q表,行是每种状态,列是每种状态的行为,值是某状态下某行为估计获得的奖励
2.每次进行状态转移时有e_greedy概率选当前状态最优方法,有 1- e_greedy选随机方法
3.选完之后就更新当前状态下对应所选行为的Q值(估计值)
4.更新方法:其自身= 其自身+学习率*(估计-现实) —> (实际更新 = 实际未更新+学习率*(预测-实际发生))
实验流程图
实验分析
理解Q-Learning算法
GYM库
- GYM是强化学习中经典的环境库,它将环境交互接口规范化为:
重置环境reset()、交互step()、渲染render()。 - gym.make()生成环境。在示例中,CliffWalking-v0生成悬崖环境。o代表正常可通行路径,x代表受控制的人物,C代表悬崖,T代表目标。
reset()重置(初始化)环境。 - step()控制进行一次动作。在示例中,传入参数:0代表上,1代表右,2代表下,3代表左;返回结果:obs观测结果,此处为所处方格编号;reward环境反馈,此处为了最优化路径,每走一步反馈-1,跌落峡谷反馈-100;down是否结束,此处为是否走到终点,布尔变量;info额外信息。
- render()绘制当前环境
- predict()方法:输入观察值observation(或者说状态state),输出动作值
- sample()方法:在predict()方法基础上使用ε-greedy增加探索
- learn()方法:输入训练数据,完成一轮Q表格的更新
- run_episode():agent在一个episode中训练的过程,使用agent.sample()与环境交互,使用agent.learn()训练Q表格。
- test_episode():agent在一个episode中测试效果,评估目前的agent能在一个episode中拿到多少总reward。
- 在悬崖环境中,目的是寻找最快路径从左下角起点到右下角终点,跌落悬崖会返回起点。Agent是和环境environment交互的主体。
更换实验环境
- 将CliffWalking悬崖环境更换为FrozenLake-v0冰面行走
- 使用gym的FrozenLake-V0环境进行训练,F为frozen lake,H为hole,S为起点,G为终点,掉到hole里就游戏结束,可以有上每一步可以有上下左右四个方向的走法,只有走到终点G才能得1分。
实验代码
Q-Learning:
import gym
import numpy as np
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))
# 根据输入观察值,采样输出的动作值,带探索
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
# 根据输入观察值,预测输出的动作值
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的方法
def learn(self, obs, action, reward, next_obs, done):
""" off-policy
obs: 交互前的obs, s_t
action: 本次交互选择的action, a_t
reward: 本次动作获得的奖励r
next_obs: 本次交互后的obs, s_t+1
done: episode是否结束
"""
predict_Q = self.Q[obs, action]
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.')
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) # 根据算法选择一个动作
next_obs, reward, done, _ = env.step(action) # 与环境进行一个交互
# 训练 Q-learning算法
agent.learn(obs, action, reward, next_obs, done)
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, render=False):
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
if render:
env.render()
if done:
break
return total_reward
# 使用gym创建悬崖环境
env = gym.make("CliffWalking-v0") # 0 up, 1 right, 2 down, 3 left
# 创建一个agent实例,输入超参数
agent = QLearningAgent(
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
print('Training...')
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)) # 取消注释以展示训练过程
print('Done.')
# 查看算法效果,输出行走路径
test_reward = test_episode(env, agent, True)
print('test reward = %.1f' % (test_reward))
Sarsa代码
import gym
import numpy as np
import time
# 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.')
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
# 使用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))