人工智能技术基础系列之:深度强化学习

发布于:2023-10-25 ⋅ 阅读:(91) ⋅ 点赞:(0)

作者:禅与计算机程序设计艺术

1.背景介绍

深度强化学习(Deep Reinforcement Learning)作为强化学习领域最前沿的研究热点,近年来已经引起了越来越多的关注。它借助于深度神经网络和强化学习的理论方法来解决复杂、模糊的决策过程。其代表性算法包括DQN、DDPG等,可以应用在各种场景中,如机器人控制、自然语言处理、游戏AI等。 随着现代计算机的普及和云计算服务的逐渐普及,深度强化学习也正在成为更多领域的研究热点。尤其是在金融、保险、医疗、环保、交通、制造等领域都有相关的研究。由于这些领域涉及的任务高度复杂、环境多变、动作反馈不确定性强、状态空间大等特点,使得传统的基于值函数的RL方法难以有效解决这些问题。深度强化学习正逐渐成为新一代机器学习、强化学习算法的基石。

2.核心概念与联系

2.1 马尔可夫决策过程

在强化学习中,马尔可夫决策过程(Markov Decision Process,简称MDP),是一种描述由状态转移和奖励组成的强化学习问题的数学模型。一个马尔可夫决策过程由初始状态S开始,在给定的一系列动作A之后会到达新的状态S‘,同时还会得到奖励R。下一步的动作可以从所有可能的动作集合A中进行选择。马尔可夫决策过程可以用贝叶斯公式表示如下: 其中,r(s,a)表示从状态s到状态s’通过行为a获得的奖励;π(a|s),p(s'r|s,a)是状态转移概率和奖励概率。γt是一个衰减因子,用来衡量下一步行为的影响程度。Q函数用来描述预期收益,由目标状态T和当前状态-行为序列τ决定。

2.2 时序差分学习

时序差分学习(Temporal Difference Learning,简称TD Learning)是指用样本来更新RL策略的一类算法。它的基本思想是用当前样本(s_t, a_t, r_t+1, s_t+1)和预测出的下一个状态的价值函数V(s_t+1)之间的差异来评估当前行为a_t的好坏。这个差异等于预测出的下一个状态的期望收益加上当前状态的价值函数减去预测出的下一个状态的实际收益。如果这个差异比之前更大,那么就认为当前行为是好的;否则就认为是坏的。TD Learning的更新规则如下所示: 注意:上述更新规则只是一种更新方式,其他的方式也可以使用。另外,对于同一个状态下的不同行为,TD Learning可以采用时间差分的方式对不同的行为进行不同的更新。

2.3 值函数近似

值函数近似(Value Function Approximation,简称VF)是一种通过逼近真实值的函数来求解某些值函数的方法。一般来说,在强化学习中,大部分的基于值函数的RL方法都需要通过参数化的方法对值函数进行拟合。这里的参数通常是一个神经网络,输入是当前状态,输出是对应的值。具体的过程可以使用梯度下降法来优化参数。值函数近似的主要优势在于:

  1. 可扩展性:通过神经网络,可以实现大规模并行运算,可以处理复杂的环境和状态空间。
  2. 鲁棒性:相较于值函数直接求解,通过逼近,可以简化问题,提高求解效率。
  3. 泛化能力:神经网络参数可以通过训练数据进行微调,从而达到泛化能力。

2.4 深度强化学习算法

目前,深度强化学习算法主要分为两大类:

  1. 基于价值迭代(Value Iteration)的算法:这种方法将值函数迭代地进行估计,直到收敛。典型的算法包括SARSA、Q-Learning、Actor-Critic等。
  2. 基于策略迭代(Policy Iteration)的算法:这种方法首先求解策略,然后再用这一策略进行迭代。典型的算法包括策略梯度算法、强化学习的最大熵原理等。

除此之外,还有一些更复杂的算法,比如基于模型的算法(Model-Based)、混合强化学习算法(Hybrid RL)等。不过,为了便于区分,本文只讨论基于值函数的深度强化学习算法。

3.核心算法原理和具体操作步骤以及数学模型公式详细讲解

3.1 Q-learning

Q-learning是一种通过最大化未来奖励来学习策略的算法。它基于动态规划的思想,用更新的价值函数来进行迭代更新。Q-learning的公式如下所示: 其中,Q(s,a)表示状态s下执行动作a的期望收益;α是学习速率;π是行为空间的策略;δ是割舍系数,用来平衡长期和短期奖励。在每个时间步t,Q-learning都会更新两个价值函数:Q(s,a)和Q'(s',argmax_a(Q(s',a))); 然后使用Q'(s')作为Q(s,a)的修正值。

3.2 Double Q-learning

Double Q-learning是一种改进的Q-learning算法,主要目的是减少动作的方差。它引入了一个新函数Q''(s',argmax_a(Q'(s',a))),用来代替当前时间步的Q函数。这样做的目的是防止由于函数值过分一致导致的方差增大。其更新公式如下:

3.3 Dueling network architecture

Dueling Network Architecture是另一种值函数近似方法。它将状态的价值函数和状态-动作的价值函数分开进行计算。状态-动作的价值函数是基于状态和动作的价值函数之和,而状态的价值函数则是基于状态的特征的线性组合。其更新公式如下:

3.4 Deep Q-network (DQN)

Deep Q-Network(DQN)是一种结合深度神经网络的强化学习方法。它提出了一种基于目标值网络的深度结构,用目标值网络来预测目标状态的Q值,然后用Q值来训练策略网络。DQN的主要优点是可以利用强大的神经网络进行快速准确的预测,并且可以在不依赖于物理代理的情况下训练智能体。其更新规则如下所示:

3.5 Deep Deterministic Policy Gradient (DDPG)

DDPG是一种用于连续控制的深度强化学习方法。它在训练过程中同时优化策略网络和目标值网络,来保证策略网络能够在给定状态下选取正确的动作。其更新规则如下所示:

3.6 Asynchronous Advantage Actor Critic (A3C)

Asynchronous Advantage Actor Critic(A3C)是一种并行、异步的深度强化学习方法。它使用多个actor-critic线程来收集并行的梯度信息,并更新共享的神经网络参数。其更新规则如下所示:

4.具体代码实例和详细解释说明

4.1 使用Pytorch构建DQN网络

以下是Pytorch实现DQN的一个简单示例。代码如下所示:

import torch
import torch.nn as nn


class Net(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim):
        super().__init__()

        self.layers = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, output_dim)
        )

    def forward(self, x):
        return self.layers(x)

# 实例化网络
net = Net(input_dim=env.observation_space.shape[0],
          hidden_dim=128,
          output_dim=env.action_space.n)

if USE_CUDA:
    net = net.cuda()
else:
    raise ValueError('USE_CUDA must be True.')

optimizer = optim.Adam(params=net.parameters())
loss_func = nn.MSELoss()

def train():
    # sample some transitions from the replay buffer

    state, action, reward, next_state, done = zip(*batch)

    if USE_CUDA:
        state = Variable(torch.cat(state)).cuda().float()
        action = Variable(torch.cat(action)).cuda().long()
        reward = Variable(torch.cat(reward)).cuda().float()
        next_state = Variable(torch.cat(next_state)).cuda().float()
        done = Variable(torch.cat(done).byte()).cuda().bool()
    else:
        state = Variable(torch.cat(state)).float()
        action = Variable(torch.cat(action)).long()
        reward = Variable(torch.cat(reward)).float()
        next_state = Variable(torch.cat(next_state)).float()
        done = Variable(torch.cat(done).byte()).bool()

    # compute target value function
    target_value = critic(next_state)

    y_expected = reward + gamma * target_value * (~done).float()

    # update the critic network with loss
    predicted_value = critic(state)[range(len(predicted_value)), action]
    loss = F.mse_loss(y_expected, predicted_value)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    # update the actor network using policy gradient
    policy_loss = -critic(state).mean()
    optimizer.zero_grad()
    policy_loss.backward()
    optimizer.step()

# use the trained model to play in the environment
for episode in range(num_episodes):
    state = env.reset()
    step = 0
    total_reward = 0
    while True:
        step += 1

        # select an action based on current observation
        action = agent.get_action(state)

        # perform the selected action and get new state and reward
        next_state, reward, done, _ = env.step(action)

        # add transition to replay buffer
        memory.push((state, action, reward, next_state, float(done)))

        # train the agent after collecting enough samples for training
        if len(memory) >= batch_size:
            train()

        state = next_state
        total_reward += reward

        if done or step == max_steps:
            break

env.close()

这里的代码片段展示了如何使用Pytorch搭建DQN网络。首先,定义了一个包含三个层的神经网络。每一层都是全连接层,其中第一层和第二层的激活函数均为ReLU,第三层的激活函数为None。然后,实例化这个网络并在GPU上进行训练,或者在CPU上进行训练。接着,定义了一个MSE损失函数,用以衡量预测值和目标值之间的误差。最后,定义了train函数,用于更新两个网络的参数,即状态-动作的价值函数critic和策略网络actor。在train函数内部,先获取批量的状态、动作、奖励、下一状态和终止信号,并在GPU上创建变量。然后,使用critic网络来预测下一状态对应的Q值。使用这些Q值来构造预测值和目标值之间的误差,并用Adam优化器对critic网络进行训练。类似地,policy_loss函数用于更新策略网络actor,但在本例中不需要修改优化器。

整个训练过程可以被抽象为一个Episode循环,在Episode内,按照定义的更新规则,选择动作,记录经验,并在获得足够数量的经验后更新网络。每次Episode结束后,将总奖励和剩余步数打印出来。

4.2 使用Tensorflow构建DDPG网络

以下是Tensorflow实现DDPG的一个简单示例。代码如下所示:

import tensorflow as tf


class DDPGAgent:
    def __init__(self, sess, num_actions, actor_lr=0.0001, critic_lr=0.001, gamma=0.99, tau=0.001,
                 replay_buffer_size=1000000, minibatch_size=64, exploration_noise=0.1, 
                 noise_reduction_factor=0.999):

        self.sess = sess
        self.num_actions = num_actions

        # Create actor network 
        self.actor = self._build_actor_model()
        self.target_actor = self._build_actor_model()

        # Create critic network 
        self.critic = self._build_critic_model()
        self.target_critic = self._build_critic_model()

        # Initialize target networks parameters with source networks
        self.update_target_networks()

        # Set hyperparameters
        self.actor_lr = actor_lr
        self.critic_lr = critic_lr
        self.gamma = gamma
        self.tau = tau
        self.replay_buffer_size = replay_buffer_size
        self.minibatch_size = minibatch_size
        self.exploration_noise = exploration_noise
        self.noise_reduction_factor = noise_reduction_factor

        # Define placeholders
        self.states_pl = None
        self.actions_pl = None
        self.rewards_pl = None
        self.next_states_pl = None
        self.dones_pl = None
        self.is_training_pl = None

        # Define operations
        self.train_op_actor, self.train_op_critic = self._create_training_ops()
        self.actor_gradients_ph, self.apply_gradient_op_actor = self._create_actor_training_op()
        self.critic_gradients_ph, self.apply_gradient_op_critic = self._create_critic_training_op()
        self.actor_weights, self.actor_biases = self._get_actor_weights_and_biases()


    def act(self, states):
        """ Returns actions for given observations"""

        action = self.actor.predict(np.expand_dims(states, axis=0))[0]
        action += np.random.normal(scale=self.exploration_noise)

        return np.clip(action, -self.action_bound, self.action_bound)


    def store_experience(self, states, actions, rewards, next_states, dones):
        """ Stores experience in replay buffer """

        self.replay_buffer.append([states, actions, rewards, next_states, dones])

        if len(self.replay_buffer) > self.replay_buffer_size:
            self.replay_buffer.popleft()


    def train(self):
        """ Trains agent by sampling from replay buffer and updating models """

        # Sample mini-batches from replay buffer
        states, actions, rewards, next_states, dones = self.sample_mini_batches()

        # Calculate target values
        target_values = self.target_critic.predict(np.concatenate((next_states, 
                                                                   self.target_actor.predict(next_states))),
                                                    batch_size=len(next_states))
        target_values = rewards + (1.0 - dones) * self.gamma * target_values

        # Update critic
        _, summary = self.sess.run([self.train_op_critic, self.summary_op],
                                    feed_dict={
                                        self.states_pl: states,
                                        self.actions_pl: actions,
                                        self.targets_pl: target_values,
                                        self.is_training_pl: False})

        self.writer.add_summary(summary, self.global_step)

        # Update actor
        gradients = self.sess.run(self.actor_gradients_ph,
                                  feed_dict={
                                      self.states_pl: states,
                                      self.is_training_pl: False})

        grads = [g for g, v in gradients]
        self.sess.run(self.apply_gradient_op_actor,
                      feed_dict={
                          self.actor_weights: weights,
                          self.actor_biases: biases,
                          self.actor_gradients_ph: grads}
                        })

        # Update target networks
        self.sess.run(self.update_target_ops)


    def initialize(self, obs_dim, act_dim):
        """ Initializes TF variables and replay buffer """

        self.obs_dim = obs_dim
        self.act_dim = act_dim
        self.action_bound = abs(env.action_space.high[0])

        # Build placeholders
        self.states_pl = tf.placeholder(tf.float32, shape=(None,) + tuple(obs_dim), name='states_pl')
        self.actions_pl = tf.placeholder(tf.float32, shape=(None,) + (act_dim,), name='actions_pl')
        self.targets_pl = tf.placeholder(tf.float32, shape=(None,), name='targets_pl')
        self.is_training_pl = tf.placeholder(tf.bool, (), 'is_training_pl')

        # Build replay buffer
        self.replay_buffer = deque(maxlen=self.replay_buffer_size)

        # Build summaries
        self.summary_op = tf.summary.merge_all()
        self.writer = tf.summary.FileWriter("logs", graph=tf.get_default_graph())
        self.global_step = 0

        # Start session and initialize variables
        init_op = tf.global_variables_initializer()
        self.sess.run(init_op)


    def _build_actor_model(self):
        """ Builds actor model using Keras API """

        inputs = layers.Input(shape=tuple(self.obs_dim))
        x = layers.Dense(units=64, activation='relu')(inputs)
        x = layers.Dense(units=64, activation='relu')(x)
        outputs = layers.Dense(units=self.act_dim, activation='tanh')(x)

        model = keras.models.Model(inputs=inputs, outputs=outputs)
        model.compile(optimizer=keras.optimizers.Adam(lr=self.actor_lr), loss='mse')

        return model


    def _build_critic_model(self):
        """ Builds critic model using Keras API """

        states_input = layers.Input(shape=tuple(self.obs_dim), name="states_input")
        actions_input = layers.Input(shape=(self.act_dim,), name="actions_input")
        concat_input = layers.Concatenate()([states_input, actions_input])

        x = layers.Dense(units=64, activation='relu')(concat_input)
        x = layers.Dense(units=64, activation='relu')(x)
        outputs = layers.Dense(units=1, activation=None)(x)

        model = keras.models.Model(inputs=[states_input, actions_input], outputs=outputs)
        model.compile(optimizer=keras.optimizers.Adam(lr=self.critic_lr), loss='mse')

        return model


    def _create_training_ops(self):
        """ Creates ops for updating policies and value functions """

        def create_training_op(network, weight_decay=0.01):

            params = network.trainable_weights
            gradients = tf.gradients(self.actor.total_loss, params)
            gradients = list(zip(gradients, params))

            # Clip gradients by L2 norm
            clipped_gradients = [(tf.clip_by_norm(grad, 0.5), var) for grad, var in gradients]

            # Add weight decay term to gradients
            gradients_with_decay = []
            for grad, var in gradients:
                if 'kernel' in var.name:
                    gradients_with_decay.append((grad + weight_decay * var, var))
                else:
                    gradients_with_decay.append((grad, var))

            op = tf.group(*[var.assign_sub(self.tau * d_p) for p, d_p in clipped_gradients])
            return op, gradients_with_decay

        # Create operations for updating policies and value functions
        train_op_actor, gradients_actor = create_training_op(self.actor, weight_decay=0.0001)
        train_op_critic, gradients_critic = create_training_op(self.critic)

        return train_op_actor, train_op_critic


    def _create_actor_training_op(self):
        """ Defines operation for applying gradients to actor network """

        # Get weights and biases of actor network
        weights, biases = self._get_actor_weights_and_biases()

        # Define operation for applying gradients to actor network
        optimizer = tf.train.AdamOptimizer(learning_rate=self.actor_lr)
        gradients = optimizer.compute_gradients(-self.actor.output, var_list=self.actor.trainable_weights)
        apply_gradient_op = optimizer.apply_gradients(gradients)

        return gradients, apply_gradient_op


    def _create_critic_training_op(self):
        """ Defines operation for applying gradients to critic network """

        optimizer = tf.train.AdamOptimizer(learning_rate=self.critic_lr)
        gradients = optimizer.compute_gradients(self.critic.total_loss)
        apply_gradient_op = optimizer.apply_gradients(gradients)

        return gradients, apply_gradient_op


    def _get_actor_weights_and_biases(self):
        """ Returns weights and biases of actor network """

        weights = self.sess.run(self.actor.weights)
        biases = self.sess.run(self.actor.biases)

        return weights, biases


    def update_target_networks(self):
        """ Updates target networks with soft update method """

        theta = self.actor.get_weights()
        target_theta = self.target_actor.get_weights()
        updated_target_theta = [self.tau*new_val + (1-self.tau)*old_val
                                 for old_val, new_val in zip(target_theta, theta)]
        self.target_actor.set_weights(updated_target_theta)

        theta = self.critic.get_weights()
        target_theta = self.target_critic.get_weights()
        updated_target_theta = [self.tau*new_val + (1-self.tau)*old_val
                                 for old_val, new_val in zip(target_theta, theta)]
        self.target_critic.set_weights(updated_target_theta)

这里的代码片段展示了如何使用Tensorflow搭建DDPG网络。首先,定义了DDPGAgent类,该类的对象封装了DDPG算法的各种功能。如定义的成员变量actor、critic、target_actor、target_critic、actor_lr、critic_lr、gamma、tau、replay_buffer_size、minibatch_size、exploration_noise、noise_reduction_factor等。该类提供了两个核心方法:act()和train(). 在act()方法中,根据输入的状态,返回对应的动作。在train()方法中,从记忆库中采样mini-batches,并计算目标值,使用这些目标值来更新critic网络,并使用actor网络来更新策略。初始化方法initialize()用来初始化TF变量和replay缓冲区。

DDPG算法是基于 actor-critic 框架的,即构建一个策略网络actor和一个价值函数网络critic。本质上,actor负责选择动作,而critic负责评估动作的好坏。DDPG算法是一种基于模型的算法,因此,需要训练一个actor网络和一个critic网络。在本例中,使用的神经网络是全连接网络,每一层使用ReLU作为激活函数。

在实现了上面所示的DDPGAgent类之后,就可以创建训练实例并进行训练了。例如:

agent = DDPGAgent(sess, env.action_space.shape[0], **kwargs)
episode_count = kwargs['num_episodes']
start_time = time.time()

for i in range(episode_count):
    print("Starting episode {}/{}.".format(i+1, episode_count))
    ep_reward = 0
    step = 0

    state = env.reset()
    done = False

    while not done:
        step += 1

        # Select action according to current policy and exploration noise
        action = agent.act(state)
        # Perform action and observe reward and new state
        next_state, reward, done, _ = env.step(action)

        # Store transition in replay buffer
        agent.store_experience(state, action, reward, next_state, int(done))

        # Train agent after collecting sufficient samples
        if len(agent.replay_buffer) >= agent.minibatch_size:
            agent.train()

        state = next_state
        ep_reward += reward

    # Reduce exploration noise over time
    agent.exploration_noise *= agent.noise_reduction_factor

    print("Episode finished with reward {}, steps {}".format(ep_reward, step))

print("Training took {} seconds".format(time.time()-start_time))

本例中的代码创建了DDPGAgent实例,并在训练过程中调用act()和train()方法。在act()方法中,选择动作时添加噪声。在train()方法中,训练网络。episode_count是一个超参,控制训练的次数。主循环遍历训练的episode数。在主循环中,每个episode开始时,重置环境,执行episode的时间步数,以及累积的奖励。在episode内,agent选择动作,进行动作,观察奖励和下一个状态。然后把transition添加到记忆库中,并训练网络。当记忆库中收集到足够的样本时,才会进行训练。训练结束后,通过乘法改变探索噪声的大小。

训练结束后,可以保存模型,或对模型进行测试。