一直想找个强化学习的入门课,刚好看到了百度这个训练营,果断报名。7天下来,收获满满,再次感谢人美心善代码6的科老师。
本文做个简单的小结,方便以后复习。
1. 初识强化学习
1.1 基本概念
强化学习(Reinforcement Learning)是机器学习的一个领域,强调智能体如何基于环境而行动,已取得最大化的预期收益。
核心思想:智能体agent在环境environment中学习,根据环境的状态state(或观测到的observation),执行动作action,并根据环境的反馈 reward(奖励)来指导更好的动作。
1.2 对比其他机器学习方法
强化学习、监督学习、非监督学习是机器学习里的三个不同的领域,都跟深度学习有交集。
监督学习寻找输入到输出之间的映射,比如分类和回归问题。
非监督学习主要寻找数据之间的隐藏关系,比如聚类问题。
强化学习则需要在与环境的交互中学习和寻找最佳决策方案。
监督学习处理认知问题,强化学习处理决策问题。
1.3 代码环境
强化学习经典环境库GYM将环境交互接口规范化为:重置环境reset()、交互step()、渲染render()
强化学习框架库PARL将强化学习框架抽象为Model、Algorithm、Agent三层,使得强化学习算法的实现和调试更方便和灵活。
课程中使用的包有PaddlePaddle1.6.3,parl1.3.1和gym。
2. 基于表格的方法
2.1 Sarsa
Sarsa全称是state-action-reward-state'-action',目标是学习特定state下,特定action的价值,最终建立和优化一个Q表格。根据agent与环境交互所获得的reward来更新Q表格,更新公式为:
使用中可以将理解为,将理解为,则公式变为:
通过不断迭代,就可以得到优化后的Q表格。
训练时为了更好的探索环境,采用方式采样action。
2.1.1 关键代码
算法采样动作
# 根据输入观察值,采样输出的动作值,带探索
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
根据公式更新Q-table的方法
def learn(self, obs, action, reward, next_obs, next_action, done):
predict_Q = self.Q[obs, action]
target_Q = reward + (1-done) * self.gamma * self.Q[next_obs, next_action] #Sarsa
self.Q[obs, action] += self.lr * (target_Q - predict_Q) # 按学习率更新Q值
2.1.2 运行效果
在“CliffWalking-v0”环境上训练500个episode后的效果如下:
2.2 Q-Learning
Q-Learning和Sarsa一样都是采用Q表格存储Q值(状态动作价值),决策部分也是采用方式增加探索。
Q-Learning和Sarsa不一样的地方在于更新Q表格的方式。
Sarsa是on-policy的更新方式,先做出动作再更新。
Q-Learning是off-policy的更新方式,更新时无需获取下一步动作,而是假设下一步动作是Q值最大的动作。
Q-Learning的更新公式为:
2.2.1 关键代码
根据公式更新Q-table的方法
def learn(self, obs, action, reward, next_obs, done):
predict_Q = self.Q[obs, action]
target_Q = reward + (1-done) * self.gamma * np.max(self.Q[next_obx, :]) # Q-Learning
self.Q[obs, action] += self.lr * (target_Q - predict_Q) # 修正Q值
其它代码和Sarsa基本一致。
2.2.2 运行效果
同样在“CliffWalking-v0”环境上训练500个episode后的效果如下:
和Sarsa比较发现,Q-Learning训练后的可以沿着最短路径走到终点,而Sarsa选择的路线比较“保守”,没有紧贴着悬崖走。这是因为Sarsa在更新Q时,会考虑到下一步动作的结果,如果下一步动作Q值较低,就会拉低当前的Q值;而Q-Learning默认下一步动作总是选择Q值最大的动作,因此不会因为下一步可能掉到悬崖就降低当前Q值,最后表现就是比较“大胆”。
3. 基于神经网络的方法
3.1 DQN简介
基于表格型的方法,在面对状态数量巨大的环境时(如围棋,机器人控制等),存储和查找效率都受限,DQN的提出解决这一局限,通过神经网络来近视替代Q表格。
本质上DQN还是一个Q-Learning算法,更新方式一致,也同样采用方式训练。
在Q-Learning的基础上,DQN提出两个技巧使得Q网络的训练更稳定:
1、经验回放Experience Replay:使用一个经验池存储多条(s,a,r,s')经验,再随机抽取一部分数据送去训练。主要解决样本关联性和利用效率的问题。
2、固定Q目标Fixed-Q-target:复制一个和Q网络一个的Target Q网络,用于计算Q目标值。主要解决网络训练不稳定的问题。
3.2 关键代码
使用PARL框架,定义并创建model,algorithm,agent对象。agent负责和环境交互,并将产生的数据传给algorithm,algorithm根据收到的数据,结合model的预测值计算loss值,再使用SGD或其它优化器不断优化。
Model
用来定义计算Q值的网络。
class Model(parl.Model):
def __init__(self, act_dim):
self.fc1 = layers.fc(128, act='relu')
self.fc2 = layers.fc(128, act='relu')
self.fc3 = layers.fc(act_dim, act=None)
# 输入state,输出所有action对应的Q值(Q[s,a1], Q[s,a2], Q[s,a3]...)
def value(self, obs):
Q = self.fc3(self.fc2(self.fc1(obs)))
return Q
Algorithm
定义了具体的算法来更新Model,也就是通过定义损失函数来更新Model,和算法相关的计算都放在Algorithm。
class Algorithm(parl.Algorithm):
def __init__(self, model, act_dim, gamma, lr):
self.model = model
self.target_model = copy.deepcopy(model) # 复制Q网络
self.act_dim = act_dim
self.gamma = gamma
self.lr = lr
def predict(self, obs):
return self.model.value(obs)
def sysn_target(self):
# 同步model模型参数到target_model
self.model.sync_weights_to(self.target_model)
def learn(self, obs, action, reward, next_obs, terminal):
# 使用DQN算法(类似Q-Learning)更新model参数
# 从target_model中获取Q值,用于计算target_Q
target_pred_value = self.target_model.value(obs)
best_v = layers.max(target_pred_value, dim=1)
best_v.stop_gradient = True # 阻止梯度传递,防止更新target_model模型参数
terminal = layers.cast(terminal, dtype='float32')
target_Q = reward + (1.0-terminal) * self.gamma * best_v
# 预测当前obs下所有动作的Q值
pred_value = self.model.value(obs)
action_onehot = layers.one_hot(action, self.act_dim)
action_onehot = layers.cast(action_onehot, dtype='float32')
predict_Q = layers.reduce_sum(
layers.elementwise_mul(action_onehot, pred_value), dim=1)
# 使用MSE计算得到loss
cost = layers.square_error_loss(predict_Q, target_Q)
cost = layers.reduce_mean(cost)
# 使用Adam优化器
optimizer = fluid.optimizer.Adam(learning_rate=self.lr)
optimizer.minimize(cost)
return cost
Agent
用于和环境交互,并把生成的数据传给Algorithm。数据的处理流程一般也在这定义。
class Agent(parl.Agent):
def __init__(self, algorithm, obs_dim, act_dim, e_greed=0.1, e_greed_decrement=0):
略...
# 搭建计算图
def build_program(self):
略...
# 使用ϵ-greed方式探索环境
def sample(self, obs):
略...
return act
# 选择最优动作
def predict(self, obs):
略...
return act
def learn(self, obs, act, reward, next_obs, terminal):
# 每隔200个training steps同步一次model和target_model的参数
if self.global_step % self.update_target_steps == 0:
self.alg.sync_target()
self.global_step += 1
...略
cost = self.fluid_executor.run(
self.learn_program, feed=feed, fetch_list=[self.cost])[0] # 训练一次网络
return cost
另外在训练时,并非每个step后都调用learn,而是每5个step后learn一次。
3.3 运行效果
在"CartPole-v0"环境上,运行2000个episode后,reward能稳定在200。
4. 基于策略梯度的方法
4.1 Policy Gradient简介
在强化学习中,有两大类方法:一种基于值(Value-based),一种基于策略(Policy-based)。
Value-based算法的典型代表为Q-Learning和Sarsa,讲Q函数优化到最优,再根据Q函数选取最优策略。
Policy-based算法的典型代表为Policy Gradient,直接优化策略函数。
Policy Gradient采用神经网络拟合策略函数,需要计算策略梯度用于优化策略网络。
优化的目标是在策略的期望回报:所有轨迹获得的回报R和对应轨迹发生的概率P的加权和。当N足够大时,可以采样N个Episode的回报然求平均来近似。
优化目标:
策略梯度:
策略梯度的推导过程:
由于,因此
又由于
因此
因为,是由环境决定的,与无关,即对的导数为零,因此:
4.2 关键代码
同DQN一样,搭建Model,Algorithm,Agent。还是用来解决CartPole问题。
Model
class Model(parl.Model):
def __init__(self, act_dim):
act_dim = act_dim
hid1_size = act_dim * 10
self.fc1 = layers.fc(size=hid1_size, act='tanh')
self.fc2 = layers.fc(size=act_dim, act='softmax')
def forward(self, obs):
out = self.fc1(obs)
out = self.fc2(out)
return out
Algorithm
class PolicyGradient(parl.Algorithm):
# 其它代码略...
def learn(self, obs, action, reward):
""" 用policy gradient 算法更新policy model
"""
act_prob = self.model(obs) # 获取输出动作概率
# log_prob = layers.cross_entropy(act_prob, action) # 交叉熵
log_prob = layers.reduce_sum(
-1.0 * layers.log(act_prob) * layers.one_hot(
action, act_prob.shape[1]),
dim=1)
cost = log_prob * reward # 策略梯度公式
cost = layers.reduce_mean(cost)
optimizer = fluid.optimizer.Adam(self.lr)
optimizer.minimize(cost)
return cost
Agent
代码基本和DQN的Agent类似。
其它
在计算Reward时,需要累积当前步之后的所有Reward带衰减因子的累加和。
# 根据一个episode的每个step的reward列表,计算每一个Step的Gt
def calc_reward_to_go(reward_list, gamma=1.0):
for i in range(len(reward_list) - 2, -1, -1):
# G_t = r_t + γ·r_t+1 + ... = r_t + γ·G_t+1
reward_list[i] += gamma * reward_list[i + 1] # Gt
return np.array(reward_list)
4.3 运行效果
在"CartPole-v0"环境上,运行1000个episode后,reward能稳定在200。
5. 连续动作空间
待续...