百度强化学习7日训练营-小结

一直想找个强化学习的入门课,刚好看到了百度这个训练营,果断报名。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(s_{t},a_{t}) \leftarrow Q(s_{t},a_{t}) + \alpha\left[R_{t+1} + \gamma Q(s_{t+1},a_{t+1}) - Q(s_{t},a_{t})\right]

使用中可以将Q(s_{t},a_{t})理解为predict\_Q,将R_{t+1} + \gamma Q(s_{t+1},a_{t+1})理解为target\_Q,则公式变为:
Q(s_{t},a_{t}) \leftarrow predict\_Q + \alpha \left(target\_Q - predict\_Q\right)
通过不断迭代,就可以得到优化后的Q表格。
训练时为了更好的探索环境,采用\epsilon-greed方式采样action。

2.1.1 关键代码

\epsilon-greed算法采样动作

# 根据输入观察值,采样输出的动作值,带探索 
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后的效果如下:


sarsa效果.gif

2.2 Q-Learning

Q-Learning和Sarsa一样都是采用Q表格存储Q值(状态动作价值),决策部分也是采用\epsilon-greed方式增加探索。
Q-Learning和Sarsa不一样的地方在于更新Q表格的方式。

Sarsa是on-policy的更新方式,先做出动作再更新。
Q-Learning是off-policy的更新方式,更新时无需获取下一步动作,而是假设下一步动作是Q值最大的动作。

Q-Learning的更新公式为:
Q(s_{t},a_{t}) \leftarrow Q(s_{t},a_{t}) + \alpha\left[R_{t+1} + \gamma \max_{a}Q(s_{t+1},a) - Q(s_{t},a_{t})\right]

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后的效果如下:


q-learning效果.gif

和Sarsa比较发现,Q-Learning训练后的可以沿着最短路径走到终点,而Sarsa选择的路线比较“保守”,没有紧贴着悬崖走。这是因为Sarsa在更新Q时,会考虑到下一步动作的结果,如果下一步动作Q值较低,就会拉低当前的Q值;而Q-Learning默认下一步动作总是选择Q值最大的动作,因此不会因为下一步可能掉到悬崖就降低当前Q值,最后表现就是比较“大胆”。

3. 基于神经网络的方法

3.1 DQN简介

基于表格型的方法,在面对状态数量巨大的环境时(如围棋,机器人控制等),存储和查找效率都受限,DQN的提出解决这一局限,通过神经网络来近视替代Q表格。

本质上DQN还是一个Q-Learning算法,更新方式一致,也同样采用\epsilon-greed方式训练。

在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采用神经网络拟合策略函数,需要计算策略梯度用于优化策略网络。
优化的目标是在策略\pi(s,a)的期望回报:所有轨迹获得的回报R和对应轨迹发生的概率P的加权和。当N足够大时,可以采样N个Episode的回报然求平均来近似。

优化目标:\bar {R}_{\theta}=\sum_{\tau} R(\tau) p_{\theta}(\tau) \approx \frac{1}{N} \sum_{n=1}^{N} R(\tau)
策略梯度:\nabla \bar{R}_{\theta} \approx \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_{n}} R\left(\tau^{n}\right) \nabla \log \pi_{\theta}\left(a_{t}^{n} | s_{t}^{n}\right)

策略梯度的推导过程:
\nabla \bar{R}_{\theta}=\sum_{\tau} R(\tau) \nabla p_{\theta}(\tau) =\sum_{\tau} R(\tau) p_{\theta}(\tau) \frac{\nabla p_{\theta}(\tau)}{p_{\theta}(\tau)}
由于\nabla f(x)=f(x) \nabla \log f(x),因此

\qquad\nabla \bar{R}_{\theta}=\sum_{\tau} R(\tau) p_{\theta}(\tau) \nabla \log p_{\theta}(\tau)=\frac{1}{N} \sum_{n=1}^{N} R(\tau) \nabla \log p_{\theta}(\tau) \quad
又由于
p_{\theta}(\tau)=p\left(s_{1}\right) \pi_{\theta}\left(a_{1} | s_{1}\right) \cdot p\left(s_{2} | s_{1}, a_{1}\right) \pi_{\theta}\left(a_{2} | s_{2}\right) \cdot p\left(s_{3} | s_{2}, a_{2}\right)\pi_{\theta}\left(a_{3} | s_{3}\right) ...
因此
\qquad\nabla\bar{R}_{\theta} =\frac{1}{N} \sum_{n=1}^{N} R(\tau) \nabla \log \left[p(s1) \prod_{t=1}^{T} \pi_{\theta}\left(a_{t} | s_{t}\right) p\left({s}_{t+1} | s_{t}, a_{t}\right)\right]
因为p(s1),p\left({s}_{t+1} | s_{t}, a_{t}\right)是由环境决定的,与\theta无关,即对\theta的导数为零,因此:
\begin{array}{l} \nabla\bar {R}_{\theta} =\frac{1}{N} \sum_{n=1}^{N} R(\tau) \sum_{t=1}^{T} \nabla \log \pi_{\theta}\left(a_{t} | s_{t}\right) \\ \qquad\,\,=\frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T} R(\tau) \nabla \log \pi_{\theta}\left(a_{t} | s_{t}\right) \end{array}

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。


policy gradient效果.gif

5. 连续动作空间

待续...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。