DQN(1)

深度强化学习DQN简介及实现
本文介绍了DQN(深度Q学习)的基本思想,针对q-table在状态和动作空间过大时的挑战,提出使用神经网络替代,并通过经验回放缓冲区解决数据相关性和稳定性问题。文中还给出了DQN的伪代码,展示了训练过程中的代价函数曲线和实际应用效果。

DQN(1)

资料

莫烦PYTHON
DeepMind
《强化学习精要》
Deep Reinforcement Learning 基础知识(DQN方面)
用Tensorflow基于Deep Q Learning DQN 玩Flappy Bird
Human-level control through deep reinforcement learning

为什么需要DQN

q-learning需要一张q table来表示q value,如果state和action数量巨大,那么这张q table就会很大,导致存储查找都极其耗费时间和空间。解决的思路就是,用一个神经网络来代替这张q table。可以将这个神经网络想象成一个函数,即 q_values,a=f(state,action) q _ v a l u e s , a = f ( s t a t e , a c t i o n ) ,或者更近一步,我们只需要输入state,因为q learning在选择action的时候比较激进,直接找最大的q value对应的action。给网络输入state,网络输出所有的action对应的q value,我们自己再在这个输出的tensor中
找到q value最大的对应的action,完成action的选择。
那么怎么更新q value的值呢?怎么训练网络。一个问题是监督学习需要大量数据,一个问题是强化学习的数据是有顺序的序列,而神经网络需要的是独立同分布的数据。
解决办法是,设置一个样本回放缓冲区replay buffer,也就是存储bot跟环境交互产生的(s, a, r, s_)。其容量很大,当replay buffer满了以后会用新数据覆盖老数据。每次训练的时候都从replay buffer中随机的抽取一批数据。这样一来,打乱了数据的相关性,向独立同分布靠近。同时也提高了数据的使用效率。
另外一个要解决的问题是不稳定的问题。在q-learning中,本次更新由上次的q-value和q target决定。

qT(s,a)=(1α)qT1(s,a)+γ[r(s)+maxa(qT1(s,a))] q T ( s , a ) = ( 1 − α ) q T − 1 ( s , a ) + γ [ r ( s ′ ) + m a x a ′ ( q T − 1 ( s ′ , a ′ ) ) ]

那么如果用一个神经网络来完成更新的时候,由于样本之间的差异会造成一定的波动性,数据本身的这种不稳定性,在迭代的时候可能会有波动,如果有波动,会随着迭代传递下去。我们无法得到一个稳定的模型。
为了增加模型的稳定性,要将更新的两部分拆分开,解耦。
那么增加一个一模一样的网络target network。target network和behavior network模型一模一样,初始化参数也相同。但是behavior network和环境交互去获取(s, a, r, s_),同时决定action的产生。target network仅仅一个作用,就是替代上边公式中的一部分。

伪代码

depp Q-learning with experience replay:

Initialize replay memory D D to capacity N.
Initialize action-value function Q Q with random weights θ
Initialize target action-value function Q̂  Q ^ with weights θ=θ θ − = θ
For episode=1,M e p i s o d e = 1 , M do:
- Initialize sequense s1={x1} s 1 = { x 1 } and preprocessed sequence ϕ1=ϕ(s1) ϕ 1 = ϕ ( s 1 )
- For t=1,T t = 1 , T do:
- With probability ϵ ϵ select a random aciton at a t , otherwise select at=argmaxaQ(ϕt,a;θ) a t = a r g m a x a Q ( ϕ t , a ; θ )
- Execute action at a t in emulator and observe reward rt r t and image xt+1 x t + 1
- Set st+1=xt+1, s t + 1 = x t + 1 , and preprecess ϕt+1=ϕ(st+1) ϕ t + 1 = ϕ ( s t + 1 )
- Store transition (ϕt,at,rt,ϕt+1) ( ϕ t , a t , r t , ϕ t + 1 ) in D
- Sample random minibatch of transitions (ϕj,aj,rj,ϕj+1) ( ϕ j , a j , r j , ϕ j + 1 )
- Set:

yj={rjrj+γmaxaQ̂ (ϕj+1,a;θ)(ifepisodeterminatesatstepj+1)(otherwise) y j = { r j ( i f e p i s o d e t e r m i n a t e s a t s t e p j + 1 ) r j + γ ∗ m a x a ′ Q ^ ( ϕ j + 1 , a ′ ; θ − ) ( o t h e r w i s e )

- Perform a gradient descent step on (yjQ(ϕj,aj;θ))2 ( y j − Q ( ϕ j , a j ; θ ) ) 2 with respect to the network parameters \theta
- Set st=st+1 s t = s t + 1
- Every C steps reset Q̂ =Q Q ^ = Q
- End for
- End for
改正了论文中的错误。论文中的算法如下:
这里写图片描述

需要

一个容量为N的容器D,存储大量的( s, a,r,s’ ),同时设置从容器抽取样本的数量batch_size;
两个相同结构的网络,Q、Q‘,并且初始化的参数相同。

复现莫烦PYTHON的核心代码

# coding:utf-8
import os
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'


class DeepQNet:

    def __init__(
            self,
            n_features,
            n_actions,
            learning_rate=0.01,
            reward_decay=0.9,
            e_greedy_max=0.9,
            e_greedy_increment=None,
            replace_target_iter=300,
            memory_pool_size=500,
            batch_size=32,
            output_graph=False,
    ):
        # 强化学习需要的部分
        self.gamma = reward_decay  # 折扣因子
        self.e_greedy_max = e_greedy_max  # =0.9,90%利用,10%探索 e 即: epsilon
        self.e_greedy_increment = e_greedy_increment  # epsilon greedy的增量
        self.e_greedy = 0 if e_greedy_increment is not None else e_greedy_max  # 即有增量就从100%探索开始到100%利用,无就固定一个值

        # 神经网络需要的部分
        self.lr = learning_rate  # 学习率alpha
        self.n_features = n_features  # state,state_的特征数量
        self.n_actions = n_actions  # action的数量

        self.replace_target_iter = replace_target_iter  # 每隔replace_target_iter个action以后更新一次q target网络的参数,更新q target的步数
        self.learn_step_counter = 0  # 记录学习的步数,便于进行更新q target的参数更新

        # 记忆池
        self.memory_pool_size = memory_pool_size  # 记忆池的容量,一般比较大,比如100万
        self.memory_pool_counter = 0
        self.memory_pool = np.zeros((memory_pool_size, n_features * 2 + 2))  # 全零初始化记忆池
        self.batch_size = batch_size  # 每次从记忆池取出数据的数量

        self._build_net()  # 建立网络q target net, q evaluate net

        target_params = tf.get_collection('target_net_params')  # 从collection中提取q target的参数
        eval_params = tf.get_collection('eval_net_params')  # 提取q eval的参数
        self.replace_q_target_op_params = [tf.assign(t, e) for t, e in zip(target_params, eval_params)]

        self.cost_history = []  # cost的更改数据,用来监测网络学习的结果

        self.sess = tf.Session()

        if output_graph:
            # 需要从根目录开始写完整路径# FIXME 可改进仅写一次,不要每次运行都生成一个图,if图存在,则不生成
            # tensorboard --logdir=name1:/Users/tu/PycharmProjects/myFirstPythonDir/DQN/logs
            tf.summary.FileWriter('logs/', self.sess.graph)

        self.sess.run(tf.global_variables_initializer())

    # 建立神经网络
    def _build_net(self):
        self.state = tf.placeholder(tf.float32, [None, self.n_features], name='state')
        self.state_ = tf.placeholder(tf.float32, [None, self.n_features], name='state_')
        self.q_target = tf.placeholder(tf.float32, [None, self.n_actions], name='Q_target')  # target net 的输出值

        w_initializer, b_initializer = tf.random_normal_initializer(0., 0.3), tf.constant_initializer(0.1)

        with tf.variable_scope('eval_net'):
            my_collections = ['eval_net_params', tf.GraphKeys.GLOBAL_VARIABLES]

            with tf.variable_scope('l1'):
                w1 = tf.get_variable('w1', [self.n_features, 20], initializer=w_initializer, collections=my_collections)
                b1 = tf.get_variable('b1', [1, 20], initializer=b_initializer, collections=my_collections)
                l1 = tf.nn.relu(tf.matmul(self.state, w1) + b1)

            with tf.variable_scope('l2'):
                w2 = tf.get_variable('w2', [20, self.n_actions], initializer=w_initializer, collections=my_collections)
                b2 = tf.get_variable('b2', [1, self.n_actions], initializer=w_initializer, collections=my_collections)
                self.q_eval = tf.matmul(l1, w2) + b2  # 输出某一个state的actions value

        with tf.variable_scope('loss'):
            self.loss = tf.reduce_mean(tf.squared_difference(self.q_target, self.q_eval))

        with tf.variable_scope('train'):
            self.train_op = tf.train.RMSPropOptimizer(self.lr).minimize(self.loss)

        with tf.variable_scope('target_net'):  # fixme 初始化的时候两个网络的参数不同,目标是相同
            my_collections = ['target_net_params', tf.GraphKeys.GLOBAL_VARIABLES]

            with tf.variable_scope('l1'):
                w1 = tf.get_variable('w1', [self.n_features, 20], initializer=w_initializer, collections=my_collections)
                b1 = tf.get_variable('b1', [1, 20], initializer=b_initializer, collections=my_collections)
                l1 = tf.nn.relu(tf.matmul(self.state_, w1) + b1)

            with tf.variable_scope('l2'):
                w2 = tf.get_variable('w2', [20, self.n_actions], initializer=w_initializer, collections=my_collections)
                b2 = tf.get_variable('b2', [1, self.n_actions], initializer=b_initializer, collections=my_collections)
                self.q_next = tf.matmul(l1, w2) + b2

    # 存储记忆
    def store_memory(self, state, action, reward, state_):
        transition = np.hstack((state, action, reward, state_))
        index = self.memory_pool_counter % self.memory_pool_size
        self.memory_pool[index, :] = transition
        self.memory_pool_counter += 1

    # 选择行为
    def choose_action(self, observation):
        observation = observation[np.newaxis, :]

        if np.random.uniform() < self.e_greedy:
            actions_value = self.sess.run(self.q_eval, feed_dict={self.state: observation})
            action = np.argmax(actions_value)
        else:
            action = np.random.randint(0, self.n_actions)
        return action

    # 更新q网络
    def learn(self):
        # 从memory poll 中随机获取一批数据
        if self.memory_pool_counter >= self.memory_pool_size:
            sample_index = np.random.choice(self.memory_pool_size, self.batch_size)
        else:
            sample_index = np.random.choice(self.memory_pool_counter, self.batch_size)
        batch_memory = self.memory_pool[sample_index, :]

        # 计算出实际值
        q_eval, q_next = self.sess.run([self.q_eval, self.q_next],
                                       feed_dict={self.state: batch_memory[:, :self.n_features],
                                                  self.state_: batch_memory[:, -self.n_features:]})
        q_target = q_eval.copy()
        batch_index = np.arange(self.batch_size, dtype=np.int32)
        eval_action_index = batch_memory[:, self.n_features].astype(int)
        reward = batch_memory[:, self.n_features + 1]
        q_target[batch_index, eval_action_index] = reward + self.gamma * np.max(q_next, axis=1)  # axis = 1才为行向

        # 实际值与预测值构成lose,更新q eval参数
        _, cost = self.sess.run([self.train_op, self.loss], feed_dict={self.state: batch_memory[:, :self.n_features],
                                                                       self.q_target: q_target})

        # 到达一定局数,更新q target网络
        if (self.learn_step_counter % self.replace_target_iter) == 0:
            self.sess.run(self.replace_q_target_op_params)
            print('q target net has updated')

        # 添加loss
        self.cost_history.append(cost)

        # epsilon
        self.e_greedy = self.e_greedy + self.e_greedy_increment \
            if self.e_greedy < self.e_greedy_max else self.e_greedy_max

        self.learn_step_counter += 1

    # 代价函数下降线
    def plot_cost(self):
        plt.plot(np.arange(len(self.cost_history)), self.cost_history)
        plt.xlabel('my training steps')
        plt.ylabel('my cost')
        plt.show()

效果

代价函数的曲线:
这里写图片描述
可以看到确实是有波动性的。
胜利和失败情况:
这里写图片描述
玩的游戏比较简单:
这里写图片描述

下一步任务

flappy bird

<think>好的,我现在要帮用户找关于使用DQN算法解决pendulum-v1问题的资料。首先,我需要确认用户的需求是什么。用户提到的是OpenAI Gym的Pendulum-v1环境,以及Deep Q-Network(DQN)的实现。可能用户对强化学习有一定的了解,但需要具体的实现步骤或代码示例。 首先,我应该回忆一下Pendulum-v1环境的特点。这个环境是一个倒立摆问题,目标是通过施加扭矩让摆杆保持直立。状态空间包括摆杆的角度和角速度,动作空间是连续的扭矩值。然而,DQN原本是为离散动作空间设计的,所以直接应用可能有问题。这时候可能需要考虑如何将连续动作空间离散化,或者使用改进的算法如DDPG,但用户明确提到的是DQN,所以可能需要调整动作空间的处理方式。 接下来,我需要思考如何将连续动作离散化。例如,把扭矩的范围[-2, 2]分成几个离散的动作,比如-2, -1, 0, 1, 2,或者更细的粒度。这样DQN就可以在每个时间步选择一个离散的动作。但离散化可能会导致动作不够精细,影响性能,这也是需要考虑的。 然后,构建DQN网络结构。输入应该是状态空间的维度,Pendulum-v1的状态是3维的(cosθ, sinθ, θ的角速度)。输出层的节点数对应离散后的动作数量。中间的隐藏层可能需要实验,比如使用两个全连接层,每层128个节点,并使用ReLU激活函数。还需要经验回放机制和目标网络,这两个是DQN的经典组件,用来稳定训练。 关于奖励函数,Pendulum-v1的奖励函数是负的,因为目标是最大化奖励,所以需要最小化角度和速度带来的损失。奖励函数具体形式可能需要查看OpenAI Gym的文档,但通常包括角度θ的平方、角速度的平方和施加扭矩的平方,组合成一个负值。 然后是训练过程,需要设定总回合数、epsilon-greedy策略的参数、经验回放的容量、批量大小等超参数。在训练过程中,每个步骤选择动作,与环境交互,存储转移,并定期从经验回放中采样进行训练,更新目标网络的参数。 最后,用户可能需要代码示例。根据这些思考,可以编写一个简单的PyTorch实现,包括网络定义、经验回放缓冲区、训练循环等部分。同时需要提醒用户注意的问题,比如离散化对性能的影响,可能需要调整离散动作的数量或考虑其他算法如DDPG。 另外,用户可能对DQN在连续动作空间中的限制不太清楚,所以需要在回答中说明这一点,并建议可能的改进方法。同时,生成的相关问题可以帮助用户进一步学习,比如DQN与DDPG的区别,如何改进DQN在Pendulum-v1中的性能,以及如何调整超参数。 需要验证一下经验回放的实现是否正确,以及目标网络的更新频率。代码中的选择动作部分是否正确处理了epsilon-greedy策略,尤其是在训练和评估模式下的不同。另外,奖励的归一化是否需要处理,因为Pendulum的奖励范围可能较大,影响训练稳定性,可以考虑适当缩放。 最后,确保回答结构清晰,分步骤说明实现要点,代码正确,并且引用了相关文献,比如DQN的原始论文,以及OpenAI Gym的文档作为参考。</think>以下是使用Deep Q-Network(DQN)算法解决OpenAI Gym Pendulum-v1环境问题的实现要点和示例: ### 1. 环境特性分析 Pendulum-v1是连续控制问题,状态空间包含3个维度:$\cos\theta$, $\sin\theta$, $\dot\theta$,动作空间是连续扭矩值$[-2, 2]$。由于DQN原生支持离散动作空间,需要将连续动作离散化处理[^1]。 ### 2. DQN实现关键步骤 #### (1) 动作空间离散化 将扭矩范围$[-2, 2]$离散化为N个等间距值: ```python ACTION_SPACE = np.linspace(-2.0, 2.0, num=5) # 离散为5个动作 ``` #### (2) 网络架构(PyTorch实现) ```python class DQN(nn.Module): def __init__(self, state_dim, action_dim): super(DQN, self).__init__() self.fc1 = nn.Linear(state_dim, 128) self.fc2 = nn.Linear(128, 128) self.fc3 = nn.Linear(128, action_dim) def forward(self, x): x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) return self.fc3(x) ``` #### (3) 经验回放机制 ```python class ReplayBuffer: def __init__(self, capacity): self.buffer = deque(maxlen=capacity) def push(self, state, action, reward, next_state, done): self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size): return random.sample(self.buffer, batch_size) ``` ### 3. 训练流程 ```python env = gym.make('Pendulum-v1') state_dim = env.observation_space.shape[0] action_dim = len(ACTION_SPACE) policy_net = DQN(state_dim, action_dim) target_net = DQN(state_dim, action_dim) target_net.load_state_dict(policy_net.state_dict()) optimizer = optim.Adam(policy_net.parameters(), lr=1e-3) buffer = ReplayBuffer(10000) for episode in range(500): state = env.reset() total_reward = 0 while True: # Epsilon-greedy动作选择 if random.random() < epsilon: action_idx = random.randint(0, action_dim-1) else: with torch.no_grad(): q_values = policy_net(torch.FloatTensor(state)) action_idx = q_values.argmax().item() # 执行动作并存储转移 next_state, reward, done, _ = env.step([ACTION_SPACE[action_idx]]) buffer.push(state, action_idx, reward, next_state, done) # 网络更新 if len(buffer) >= 128: transitions = buffer.sample(128) # ... 实现DQN更新逻辑 ... state = next_state total_reward += reward if done: break ``` ### 4. 关键挑战与解决方案 - **连续状态处理**:直接使用原始状态向量作为输入 - **奖励归一化**:原始奖励范围$[-16.27, 0]$,建议进行线性缩放 - **探索策略**:采用衰减ε-greedy策略,初始ε=0.9,最终ε=0.05 - **目标网络更新**:每100步进行硬更新[^2] [^1]: 参见OpenAI Gym官方文档对Pendulum-v1的说明 [^2]: 参考DQN原始论文《Human-level control through deep reinforcement learning》
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值