精华内容
下载资源
问答
  • 【深度学习】DQN网络代码详解

    千次阅读 2018-12-03 18:10:10
    前面的话 代码作者是 莫烦 大佬。在github上可以找到代码原文。同时在视频网站上可以找到 莫烦 大佬的系列教学视频。...This part of code is the DQN brain, which is a brain of the agent. All de...

    前面的话

    代码作者是 莫烦 大佬。在github上可以找到代码原文。同时在视频网站上可以找到 莫烦 大佬的系列教学视频。
    这里我用代码注释的形式标注了所有代码的含义,同时还有部分函数方法的简单用法。各位可以看情况细细研究或者知其大概。

    代码部分

    """
    This part of code is the DQN brain, which is a brain of the agent.
    All decisions are made in here.
    Using Tensorflow to build the neural network.
    
    View more on my tutorial page: https://morvanzhou.github.io/tutorials/
    
    Using:
    Tensorflow: 1.0
    gym: 0.7.3
    """
    
    import numpy as np
    import pandas as pd
    import tensorflow as tf
    
    # 设置随机数seed
    np.random.seed(1)
    tf.set_random_seed(1)
    
    
    # Deep Q Network off-policy
    class DeepQNetwork:
        def __init__(
                self,
                n_actions,
                n_features,
                learning_rate=0.01,
                reward_decay=0.9,
                e_greedy=0.9,
                replace_target_iter=300,
                memory_size=500,
                batch_size=32,
                e_greedy_increment=None,
                output_graph=False,
        ):
            self.n_actions = n_actions  # action num
            self.n_features = n_features  # state num
            self.lr = learning_rate  # 学习率
            self.gamma = reward_decay  # 折扣因子
            self.epsilon_max = e_greedy  # 贪婪决策概率
            self.replace_target_iter = replace_target_iter  # target和eval的参数更新间隔步
            self.memory_size = memory_size  # 记忆库大小
            self.batch_size = batch_size  # 批量大小
            self.epsilon_increment = e_greedy_increment  # greedy变化
            self.epsilon = 0 if e_greedy_increment is not None else self.epsilon_max
    
            # total learning step
            self.learn_step_counter = 0  # 计步器
    
            # initialize zero memory [s, a, r, s_]
            self.memory = np.zeros((self.memory_size,
                                    n_features * 2 + 2))  # 初始化记忆库
    
            # consist of [target_net, evaluate_net]
            self._build_net()
            t_params = tf.get_collection('target_net_params')
            e_params = tf.get_collection('eval_net_params')
            #zip() 函数用于将可迭代对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象。
            self.replace_target_op = [
                tf.assign(t, e) for t, e in zip(t_params, e_params)
            ]
    
            self.sess = tf.Session()
    
            if output_graph:
                # $ tensorboard --logdir=logs
                # tf.train.SummaryWriter soon be deprecated, use following
                tf.summary.FileWriter("E:/Code/logs", self.sess.graph)
    
            self.sess.run(tf.global_variables_initializer())
            self.cost_his = []
    
        def _build_net(self):
            # ------------------ build evaluate_net ------------------
            ### eva-Network的输入s 类比 图片输入 是一个 列矩阵 形式
            ### tf.placeholder(s类型为32位浮点,[行向量],标签名字为s)
            ### 这里的 None 与 minibatch 的 size 相同
            self.s = tf.placeholder(
                tf.float32, [None, self.n_features], name='s')  # 输入 s
            ### 用于计算loss的Q现实值。
            ### 并不是本结构的输出,但需要扩展作用域
            self.q_target = tf.placeholder(
                tf.float32, [None, self.n_actions],
                name='Q_target')  # 输入 用于计算 loss function
    
            ### tf.variable_scope 创建eval_net的相关变量
            ### c_names是数组,中间层神经元个数10,正态分布随机数作为权值w,常量0.1作为偏置b
            with tf.variable_scope('eval_net'):
                c_names, n_l1, w_initializer, b_initializer = \
                    ['eval_net_params', tf.GraphKeys.GLOBAL_VARIABLES], 10, \
                    tf.random_normal_initializer(0., 0.3), tf.constant_initializer(0.1)
    
                # 第一层,w1 b1 以及输出 l1 = 激活函数( input s * w1 + b1 )
                # first layer. collections is used later when assign to target net
                with tf.variable_scope('l1'):
                    ## get_variable('name',[shape],<initializer>)
                    ## 权值矩阵大小为 features行 * n_l1列
                    ## 偏置矩阵大小为 1行 * n_l1列
                    w1 = tf.get_variable(
                        'w1', [self.n_features, n_l1],
                        initializer=w_initializer,
                        collections=c_names)
                    b1 = tf.get_variable(
                        'b1', [1, n_l1],
                        initializer=b_initializer,
                        collections=c_names)
                    l1 = tf.nn.relu(tf.matmul(self.s, w1) + b1)
                # 上一层输出对应输入,这一层输出对应 action,输出层
                # 第二层,w2 b2 以及输出 q_eval = l1 * w2 + b2
                # second layer. collections is used later when assign to target net
                with tf.variable_scope('l2'):
                    ## 权值矩阵大小为 n_l1行 * actions列
                    ## 偏置矩阵大小为 1行 * actions列
                    w2 = tf.get_variable(
                        'w2', [n_l1, self.n_actions],
                        initializer=w_initializer,
                        collections=c_names)
                    b2 = tf.get_variable(
                        'b2', [1, self.n_actions],
                        initializer=b_initializer,
                        collections=c_names)
                    self.q_eval = tf.matmul(l1, w2) + b2
            ### 定义evl_Network的更新依据 loss函数
            ### reduce_mean默认对所有数求平均,squared_difference最小二乘
            ### 最小二乘的结果是1*features,平均之后是1*1
            with tf.variable_scope('loss'):
                self.loss = tf.reduce_mean(
                    tf.squared_difference(self.q_target, self.q_eval))
            ### 定义evl_Network训练方法
            with tf.variable_scope('train'):
                self._train_op = tf.train.RMSPropOptimizer(self.lr).minimize(
                    self.loss)
    
            # ------------------ build target_net ------------------
            ### target net是旧版的evl_Net。需要给出 q_target 的计算依据 q_next
            ### 输入为 s_ 及下一时刻的state
            ### 网络结构与evl_Net相同
            self.s_ = tf.placeholder(
                tf.float32, [None, self.n_features], name='s_')  # 输入 下一时刻状态
            with tf.variable_scope('target_net'):
                c_names = ['target_net_params', tf.GraphKeys.GLOBAL_VARIABLES]
                # 结构与evl_Net相同
                # first layer. collections is used later when assign to target net
                with tf.variable_scope('l1'):
                    w1 = tf.get_variable(
                        'w1', [self.n_features, n_l1],
                        initializer=w_initializer,
                        collections=c_names)
                    b1 = tf.get_variable(
                        'b1', [1, n_l1],
                        initializer=b_initializer,
                        collections=c_names)
                    l1 = tf.nn.relu(tf.matmul(self.s_, w1) + b1)
    
                # second layer. collections is used later when assign to target net
                with tf.variable_scope('l2'):
                    w2 = tf.get_variable(
                        'w2', [n_l1, self.n_actions],
                        initializer=w_initializer,
                        collections=c_names)
                    b2 = tf.get_variable(
                        'b2', [1, self.n_actions],
                        initializer=b_initializer,
                        collections=c_names)
                    self.q_next = tf.matmul(l1, w2) + b2
    
        # 定义样本池
        def store_transition(self, s, a, r, s_):
            ### hasattr(object, name) 检查对象是否包含对应属性
            if not hasattr(self, 'memory_counter'):
                self.memory_counter = 0
            ### np.hastack 水平合并 数组
            transition = np.hstack((s, [a, r], s_))
            ### 样本池按列存放样本,每个样本为一个行向量
            ### 循环存储每个行动样本,index用于循环覆盖的起始行数
            index = self.memory_counter % self.memory_size
            self.memory[index, :] = transition
    
            self.memory_counter += 1
    
        def choose_action(self, observation):
            ### e-greedy 策略实现
            observation = observation[np.newaxis, :]
    
            if np.random.uniform() < self.epsilon:
                ### feed 与 placeholder 搭配使用 ,传递值
                actions_value = self.sess.run(
                    self.q_eval, feed_dict={self.s: observation})
                ### np.argmax 取出最大值的索引
                action = np.argmax(actions_value)
            else:
                action = np.random.randint(0, self.n_actions)
            return action
    
        def learn(self):
            # 如需更新target参数,则更新
            if self.learn_step_counter % self.replace_target_iter == 0:
                self.sess.run(self.replace_target_op)
                print('\ntarget_params_replaced\n')
    
            # 随机抽取minibatch
            ## 当样本池未满的时候,依照计数器值随机选取,否则按照池大小选取。
            if self.memory_counter > self.memory_size:
                ### numpy.random.choice(a, size=None, replace=True, p=None)
                ### 从 a 中以概率 p 随机抽取 size 个。replace表示是否放回。
                sample_index = np.random.choice(
                    self.memory_size, size=self.batch_size)
            else:
                sample_index = np.random.choice(
                    self.memory_counter, size=self.batch_size)
            # 选择出需要训练的样本 batch_memory
            batch_memory = self.memory[sample_index, :]
    
            q_next, q_eval = self.sess.run(
                [self.q_next, self.q_eval],
                feed_dict={
                ### batch_memory格式为行向量,s(features),a,r,s_(feature)
                ### 分别取出 s 和 s_ 作为网络输入
                ### np矩阵切片 a[:,a:b:c] 从a到b列步长为c切片
                ### 下方对batch_memory切片取出s_ 和 s
                    self.s_: batch_memory[:, -self.n_features:],  # 旧参数
                    self.s: batch_memory[:, :self.n_features],  # 新参数
                })
    
            # 加上奖励和折扣因子作为q实际
            q_target = q_eval.copy()
    
            batch_index = np.arange(self.batch_size, dtype=np.int32)
            ### numpy 数组的四个方法:ndim返回维度数,shape返回维度数组,dtype返回元素类型,astype类型转换
            ### 对 batch_memroy 切片,取出 action / reward
            eval_act_index = batch_memory[:, self.n_features].astype(int)
            reward = batch_memory[:, self.n_features + 1]
    
            q_target[batch_index, eval_act_index] = reward + self.gamma * np.max(
                q_next, axis=1)
            """
            For example in this batch I have 2 samples and 3 actions:
            q_eval =
            [[1, 2, 3],
             [4, 5, 6]]
    
            q_target = q_eval =
            [[1, 2, 3],
             [4, 5, 6]]
    
            Then change q_target with the real q_target value w.r.t the q_eval's action.
            For example in:
                sample 0, I took action 0, and the max q_target value is -1;
                sample 1, I took action 2, and the max q_target value is -2:
            q_target =
            [[-1, 2, 3],
             [4, 5, -2]]
    
            So the (q_target - q_eval) becomes:
            [[(-1)-(1), 0, 0],
             [0, 0, (-2)-(6)]]
    
            We then backpropagate this error w.r.t the corresponding action to network,
            leave other action as error=0 cause we didn't choose it.
            """
    
            # 训练Eval_Net
            _, self.cost = self.sess.run([self._train_op, self.loss],
                                         feed_dict={
                                             self.s:
                                                 batch_memory[:, :self.n_features],
                                             self.q_target:
                                                 q_target
                                         })
            ### 记录每一步更新的 cost 用于训练后画图分析
            self.cost_his.append(self.cost)
    
            # increasing epsilon
            self.epsilon = self.epsilon + self.epsilon_increment if self.epsilon < self.epsilon_max else self.epsilon_max
            self.learn_step_counter += 1
    
        def plot_cost(self):
            import matplotlib.pyplot as plt
            plt.plot(np.arange(len(self.cost_his)), self.cost_his)
            plt.ylabel('Cost')
            plt.xlabel('training steps')
            plt.show()
    
    
    if __name__ == '__main__':
        DQN = DeepQNetwork(3, 4, output_graph=True)
    
    

    结语

    最近事情太多,来不及上传笔记。关于numpy和tensorflow的使用会在有时间的时候更新总结。还有一些关于Machine Learning和CNN的笔记也会上传。

    展开全文
  • Apply DQN in gym environment in MountainCar-v0 文章目录Apply DQN in gym environment in MountainCar-v0一、Gym Environment1.1 ACTION SPACE1.2 STATE SPACE1.3 REWARD1.4 DONE二、Deep Q-learning2.1 ...

    Apply DQN in gym environment in MountainCar-v0

    一、Gym Environment

    首先启动环境,采取随机的动作后会返回几个变量,简单的代码过程如下:

    env = gym.make('MountainCar-v1') # 打开一个环境,这个环境是修改后的后面会讲
    env.reset() # 重置环境
    action = env.action_space.sample() # 从动作空间里随机采样一个动作
    state, reward, done, info = env.step(action) # 采取动作后,获得了状态,奖励,是否完成和info额外信息。
    

    1.1 ACTION SPACE

    动作空间有三个,分别是左,原地不动和右,离散的形式为action=[0,1,2]

    1.2 STATE SPACE

    原本的状态是两个,分别是车辆的位置和速度,离散的形式为state=[position,velocity]

    其中,position=[-0.6,0.6],velocity=[-0.1,0.1]

    传统的方法是通过确定的状态来更新Q-value,本实验将不同的图片帧作为状态,通过卷积神经网络输出一个Q-value,进一步再选择动作。

    1.3 REWARD

    奖励为-1 / time step

    1.4 DONE

    一般是DONE=FALSE,当完成任务后,DONE=TRUE,然而内置版本设定为200个episode没有到达终点,则环境重置env.reset()

    ❗️由于重置后Done会重新设置为True,而到达终点时候的Done也是True,多数情况下,很难在200个episode中收敛,不利于训练,因此在注册函数中更改一下信息。

    位置为:XXX/anaconda3/envs/py36/lib/python3.6/site-packages/gym/envs/__init__.py

    打开后可以看到有如下两个版本,分别是离散连续的两种情况。

    复制粘贴第一个环境,修改最大episode为100000后,注册为MountainCar-v1并且保存。

    register(
        id='MountainCar-v0',
        entry_point='gym.envs.classic_control:MountainCarEnv',
        max_episode_steps=200,
        reward_threshold=-110.0,
    )
    register(
        id='MountainCarContinuous-v0',
        entry_point='gym.envs.classic_control:Continuous_MountainCarEnv',
        max_episode_steps=999,
        reward_threshold=90.0,
    )
    # 新注册一个环境
    register(
        id='MountainCar-v1',
        entry_point='gym.envs.classic_control:MountainCarEnv',
        max_episode_steps=100000,
        reward_threshold=-110.0,
    )
    

    2021.3.26 补充一下另一种修改gym最大episode的方式

    import gym
    env = gym.make("CartPole-v0")
    print("The default max episode is",env._max_episode_steps)
    env._max_episode_steps = 500
    print("After changing, the max episode is",env._max_episode_steps)
    

    The default max episode is 200
    After changing, the max episode is 500

    二、Deep Q-learning

    2.1 Preprocess Frame

    事实上,在训练时候并不需要那么大的图片,主要分为以下操作过程:

    • RGB的图片转换成黑白图像
    • (可选择)剪裁掉没有用的区域,例如游戏的计分板区域
    • 归一化,将像素值转换为[0,1]之间,减少计算量
    • 再次改变大小,将图片转换成[84,84]的区域。
    def preprocess_frame(frame):
        gray = rgb2gray(frame)
       #crop the frame
       #cropped_frame = gray[:,:]
        normalized_frame = gray/255.0
        preprocessed_frame = transform.resize(normalized_frame, [84,84])
        return preprocessed_frame
    

    2.2 Stack Frames

    将每四帧图片堆叠在一起,因此得到的图像大小为[84,84,4]

    此时需要分两种情况讨论,首先建立一个双端队列deque,每个位置的大小为[84,84],最大队列长度为4

    • 新的episode(DONE=True

      • 初始化,将第一帧复制四份填满这个队列
    • 当前episode还未结束(DONE=False

      • 将最新的帧添加到队列中,并且旧的帧自动移除队列

    def stack_frames(stacked_frames, state, is_new_episode):
        # Preprocess frame
        frame = preprocess_frame(state)
        
        if is_new_episode:
            # Clear our stacked_frames
            stacked_frames = deque([np.zeros((84,84), dtype=np.int) for i in range(stack_size)], maxlen=4)
            
            # Because we're in a new episode, copy the same frame 4x
            stacked_frames.append(frame)
            stacked_frames.append(frame)
            stacked_frames.append(frame)
            stacked_frames.append(frame)
            
            # Stack the frames
            stacked_state = np.stack(stacked_frames, axis=2)
            
        else:
            # Append frame to deque, automatically removes the oldest frame
            stacked_frames.append(frame)
    
            # Build the stacked state (first dimension specifies different frames)
            stacked_state = np.stack(stacked_frames, axis=2) 
        
        return stacked_state, stacked_frames
    

    在存储图片后,也可以通过读取的方式显示这些图片。

    batch_size = 64
    # 调用DQN类,创建一个memory
    memory = DQNetwork()
    
    replay_batch = memory.sample(batch_size) 
    
    s_batch = [replay[0] for replay in replay_batch][np.random.randint(0,batch_size)] 
    # print("the shape of s_batch is",s_batch.shape)
    # (s_batch[63]).shape       84*84*4 
    next_s_batch = [replay[3] for replay in replay_batch][np.random.randint(0,batch_size)] # (s_batch[63]).shape       84*84*4 
    # print("the shape of next_s_batch is",next_s_batch.shape)
    # 然后可以输出对应的图片,需要乘以255并且选择通道。
    
    plt.imshow(255*s_batch[:,:,3])
    

    2.3 Replay Buffer

    • 在预测Q值的时候,用的不是当前的状态,而是经验回放池中抽取的状态
    • 依次定义三个过程
      • 添加经验
      • 从经验中采样
      • 再从中采样出一组经验
    # 定义 batch_size,每次从Replay Buffer中抽取多少batch
        def add(self, experience):
            self.buffer.append(experience)
            
        def sample(self, batch_size):
            buffer_size = len(self.buffer) 
            index = np.random.choice(np.arange(buffer_size),size = batch_size,replace = True) #如果开始的时候都是0,采样会重复,因此改为True。
            return [self.buffer[i] for i in index]
        
    	def train(self, batch_size=64):
            replay_batch = self.sample(batch_size)
            #简化一个过程,抽取了若干batch后,先随机抽取一个batch
            batch_number = np.random.randint(0,batch_size)
            s_batch = [replay[0] for replay in replay_batch][batch_number]
    		next_s_batch = [replay[0] for replay in replay_batch][batch_number]
            
    

    此时来分析一下维度:

    • replay_batch:大小为64×564\times 5,包含了64个batch,并且每个batch中由5个部分组成
    • replay:大小为1×51\times 5,而后面的batch_number决定了这个replay是这64中的哪一组。
      • replay由5部分组成,里面的内容为[(84,84,4),action=[0,1,2],reward=-1,(84,84,4),False or True]
    • s_batch:即这个replay中的replay[0],大小为84×84×484\times 84 \times 4,由连续四帧堆叠而成。
    • next_s_batch:即这个replay中的replay[3],大小为84×84×484\times 84 \times 4,由连续四帧堆叠而成。
    • actionreplay[1],动作为[0,1,2]中的一个随机动作
    • rewardreplay[2],表示当前时间步的奖励
    • Donereplay[5]True或者False,表示当前episode是否结束。

    2.4 Q-target network and Q network

    class DQNetwork:
        def __init__(self):
            self.step = 0
            self.update_freq = 50  # 模型更新频率
        # 省略一些函数,看一下关键语句
        # 每 update_freq 步,将 model 的权重赋值给 target_model
        def train(self):
        	self.step += 1
            if self.step % self.update_freq == 0:
                self.target_model.set_weights(self.model.get_weights())
        # 省略一些语句,看一下关键语句
            Q = self.model.predict(s_batch.reshape(-1, 84, 84, 4))
            Q_next = self.target_model.predict(next_s_batch.reshape(-1, 84, 84, 4))
    
    • Q-target network的参数每间隔一定步数,将Q network的权重复制过去。
    • Q-target network的网络结构和Q network完全一样

    这里reshape(-1,84,84,4)是将其增加了一个维度,由(84,84,4)变成了(1,84,84,4),因为在输入网络的时候是batch=1,输入的格式为(batch_size,length,width,channels),因此需要改变输入大小。

    2.5 Network Model

        def create_model(self):
    
            # 这个网络是原版Atari的网络架构
            inputs = layers.Input(shape=(84, 84, 4,))
    
            # Convolutions on the frames on the screen
            layer1 = layers.Conv2D(32, 8, strides=4, activation="relu")(inputs)
            layer2 = layers.Conv2D(64, 4, strides=2, activation="relu")(layer1)
            layer3 = layers.Conv2D(64, 3, strides=1, activation="relu")(layer2)
    
            layer4 = layers.Flatten()(layer3)
    
            layer5 = layers.Dense(512, activation="relu")(layer4)
            action = layers.Dense(3, activation="linear")(layer5)
    
            model=keras.Model(inputs=inputs, outputs=action)
    
            return model
    
    

    使用model.summary()后可以看到它的结构:

    Model: "model_18"
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    input_19 (InputLayer)        [(None, 84, 84, 4)]       0         
    _________________________________________________________________
    conv2d_54 (Conv2D)           (None, 20, 20, 32)        8224      
    _________________________________________________________________
    conv2d_55 (Conv2D)           (None, 9, 9, 64)          32832     
    _________________________________________________________________
    conv2d_56 (Conv2D)           (None, 7, 7, 64)          36928     
    _________________________________________________________________
    flatten_18 (Flatten)         (None, 3136)              0         
    _________________________________________________________________
    dense_36 (Dense)             (None, 512)               1606144   
    _________________________________________________________________
    dense_37 (Dense)             (None, 3)                 1539      
    =================================================================
    Total params: 1,685,667
    Trainable params: 1,685,667
    Non-trainable params: 0
    

    2.6 Update Q-Value

    其主要公式为:
    Qnew=(1α)Qold+α(reward+γmaxQfuture) Q_{new}=(1-\alpha)Q_{old}+\alpha(reward+\gamma maxQ_{future})
    其中,α\alpha表示学习率,γ\gamma表示折扣系数。

    • QfutureQ_{future}用的是Q-target network
    • QoldQ_{old}用的是Q network

    更新后,将QnewQ_{new}的值给Q network

    Q[0][a] = ( 1 - lr) * Q[0][a] + lr * (reward + factor * np.amax(Q_next[0]))
    
    • Q的值为:
    Q = array([[ 0.2  , -0.525,  0.4  ]])
    

    总结一下,更新过程如下:

    1. 初始化 Q=Q target

      repeat

      1. 通过公式,用Q和Q target更新 Q
      2. 每隔一段时间,Q target复制Q的权重更新Q target

    2.7 Save and Load Weights

    class DQNetwork:
        def save_model(self, file_path='MountainCar-v1-dqn.h5'):
            print('model saved')
            self.model.save(file_path)
    
    agent_test = DQNetwork()
    agent_test.model.load_weights(r'/home/shy/桌面/MountainCar-v1-dqn.h5')
    

    如何确认权重是否读取成功呢?在TensorFlow 1中,读取后会显示权重对应存储的列表。

    而TensorFlow 2中,可能没有显示这些信息,读取后使用get_weights()方法,如果再次创建一个实体发现权重一样,说明读取进去了,否则每次随机初始化的权重不同,说明读取失败。

    三、Some Details

    3.1 Save Frames

    • 在gym中,使用env.render(mode='rgb_array')便可以渲染出当前的状态图片,但是这种方式消耗CPU过大,在这里也看到了很多人提出了这个问题https://github.com/openai/gym/issues/659,也许可以利用内存回收机制,节约一些内存。

    经典控制的游戏并没有提供不渲染就能返回图片的接口,也就是这种写法env.render(mode='rgb_array',close=True),而在一些Atari的部分游戏中,可以采用此方法节约内存空间。

    • 如果不将图片保存到本地,将env.render(mode='rgb_array')返回给一个变量,然后经过了动作后,再次env.render(mode='rgb_array')返回另外一个变量,显示图片发现指向了同一个空间,而这两个变量的图片是一样的。
    • 因此采用了将图片保存到本地,再次从本地读取的办法处理这个问题。
    def save_gym_state(env,i):
    
        next_state = env.render(mode='rgb_array')
    
        plt.imsave('/home/shy/state/state{}.png'.format(i),preprocess_frame(next_state)) 
        next_state = plt.imread('/home/shy/state/state{}.png'.format(i))
        
        return next_state
    

    3.2 Train and Test Agent

    这里分为三个步骤,

    • 没有权重时,初始化权重训练出Q network
    • 将学习好的权重读入,再次训练Q network
    • Q network测试,直接返回Q值最大对应的动作

    由于内存空间不足,第二个步骤可以将episode设为1000,反复训练多次可以拟合出更准的Q network

    四、Full Code

    4.1 Import

    import matplotlib.pyplot as plt
    import tensorflow as tf
    import gym
    import scipy
    import numpy as np
    from skimage import transform # Help us to preprocess the frames
    from skimage.color import rgb2gray # Help us to gray our frames
    from collections import deque
    from tensorflow import keras
    from tensorflow.keras import layers
    from tensorflow.keras import models
    from tensorflow.compat.v1 import ConfigProto
    from tensorflow.compat.v1 import InteractiveSession
    import os
    os.environ["CUDA_VISIBLE_DEVICES"] = "0"
    os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
    tf.compat.v1.logging.set_verbosity(tf.compat.v1.logging.ERROR)
    config = ConfigProto()
    config.allow_soft_placement=True
    config.gpu_options.per_process_gpu_memory_fraction=0.8
    config.gpu_options.allow_growth = True
    session = InteractiveSession(config=config)
    

    4.2 Defined Function and Class

    # 构造函数,输入episode,结果保存图片或返回处理好的数组
    def preprocess_frame(frame):
        gray = rgb2gray(frame)
       #crop the frame
       #cropped_frame = gray[:,:]
        normalized_frame = gray/255.0
        preprocessed_frame = transform.resize(normalized_frame, [84,84])
        return preprocessed_frame
    
    
    stack_size = 4 # We stack 4 frames
    
    # Initialize deque with zero-images one array for each image
    stacked_frames  =  deque([np.zeros((84,84), dtype=np.int) for i in range(stack_size)], maxlen=4)
    
    def stack_frames(stacked_frames, state, is_new_episode):
        # Preprocess frame
        frame = preprocess_frame(state)
        
        if is_new_episode:
            # Clear our stacked_frames
            stacked_frames = deque([np.zeros((84,84), dtype=np.int) for i in range(stack_size)], maxlen=4)
            
            # Because we're in a new episode, copy the same frame 4x
            stacked_frames.append(frame)
            stacked_frames.append(frame)
            stacked_frames.append(frame)
            stacked_frames.append(frame)
            
            # Stack the frames
            stacked_state = np.stack(stacked_frames, axis=2)
            
        else:
            # Append frame to deque, automatically removes the oldest frame
            stacked_frames.append(frame)
    
            # Build the stacked state (first dimension specifies different frames)
            stacked_state = np.stack(stacked_frames, axis=2) 
        
        return stacked_state, stacked_frames
    
    def save_gym_state(env,i):
    
        next_state = env.render(mode='rgb_array')
    
        plt.imsave('/home/shy/state/state{}.png'.format(i),preprocess_frame(next_state)) 
        next_state = plt.imread('/home/shy/state/state{}.png'.format(i))
        
        return next_state
    
    class DQNetwork:
        def __init__(self):
            self.step = 0
            self.update_freq = 50  # 模型更新频率
            
            self.buffer = deque(maxlen = 200)
            self.model = self.create_model()
    
            self.target_model = self.create_model()
        def add(self, experience):
            self.buffer.append(experience)
        
        def sample(self, batch_size ):
            buffer_size = len(self.buffer) #agent.buffer=0??这里有问题
            
            index = np.random.choice(np.arange(buffer_size),
                                    size = batch_size,
                                    replace = True) #如果开始的时候都是0,不然会出现采样重复的状况,因此改称True
            
            return [self.buffer[i] for i in index]
            
        def create_model(self):
    
            # 这个网络是原版Atari的网络架构
            inputs = layers.Input(shape=(84, 84, 4,))
    
            # Convolutions on the frames on the screen
            layer1 = layers.Conv2D(32, 8, strides=4, activation="relu")(inputs)
            layer2 = layers.Conv2D(64, 4, strides=2, activation="relu")(layer1)
            layer3 = layers.Conv2D(64, 3, strides=1, activation="relu")(layer2)
    
            layer4 = layers.Flatten()(layer3)
    
            layer5 = layers.Dense(512, activation="relu")(layer4)
            action = layers.Dense(3, activation="linear")(layer5)
    
            model=keras.Model(inputs=inputs, outputs=action)
    
            return model
    
        
        def act(self, state, epsilon=0.1):
            """预测动作"""
            # 刚开始时,加一点随机成分,产生更多的状态
            if np.random.uniform() < epsilon - self.step * 0.0002:
                return np.random.choice([0, 1, 2])
            return np.argmax(self.model.predict(state.reshape(-1, 84, 84, 4)))
                             
        def save_model(self, file_path='MountainCar-v0-dqn.h5'):
            print('model saved')
            self.model.save(file_path)
                             
        def train(self, batch_size=64, lr=0.1, factor=0.95):
    
            self.step += 1
            # 每 update_freq 步,将 model 的权重赋值给 target_model
            if self.step % self.update_freq == 0:
                self.target_model.set_weights(self.model.get_weights())
            
            replay_batch = self.sample(batch_size) 
            
           #num = np.random.randint(0,batch_size) # 举例子,一个最基本的情况,从该batch中随机抽取一个样本作为输入
            
            s_batch = [replay[0] for replay in replay_batch][np.random.randint(0,batch_size)] 
            #print("the shape of s_batch is",np.array(s_batch).shape)
            # (s_batch[63]).shape       84*84*4   
            next_s_batch = [replay[3] for replay in replay_batch][np.random.randint(0,batch_size)] # 84,84,4 正确
            #print("the shape of next_s_batch is",np.array(next_s_batch).shape)
    
            Q = self.model.predict(s_batch.reshape(-1, 84, 84, 4))
            Q_next = self.target_model.predict(next_s_batch.reshape(-1, 84, 84, 4))
    
            # 使用公式更新训练集中的Q值 这里的句子还没有改,这里现打印一下吧
            for i, replay in enumerate(replay_batch):
    #             print("the shape of replay_batch is",np.array(replay_batch).shape)
    #             print("the shape of replay is ",replay[0].shape)
    #             print("the action of replay is",replay[1])
    #             print("the reward of replay is ",replay[2])
    #             print("the shape of next_replay is ",replay[3].shape)
    #             print("the last replay is ",replay[4])
                a = replay[1]
                Q[0][a] = ( 1 - lr) * Q[0][a] + lr * (reward + factor * np.amax(Q_next[0]))
            
            # 传入网络进行训练
            self.model.compile(loss='mean_squared_error',
                               optimizer=keras.optimizers.Adam(0.001))
            self.model.fit(s_batch.reshape(-1, 84, 84, 4), Q, verbose=0) 
    

    4.3 Main Function (Random Initialization)

    ###########################
    #这个是初次训练时候的主函数####
    ###########################
    env = gym.make('MountainCar-v1')
    episodes = 1000  # 训练1000次
    score_list = [] 
    agent = DQNetwork()
    env.reset()
    ##########
    # 1.修改注册后,要重启内核
    # 2.env.render(mode='rgb_array',close=True) 可以节约内存,但这个环境不适用
    # 3.在注册里修改里面的max变量后,增加episode,否则默认的episode是200的时候自动结束开启新的周期
    ###########
    
    #state = env.render(mode='rgb_array')
    #plt.imsave('/home/shy/state/state.png',preprocess_frame(state))
    #state = plt.imread('/home/shy/state/state.png')
    
    state = save_gym_state(env,'init')
    
    stacked_frames  =  deque([np.zeros((84,84), dtype=np.int) for i in range(stack_size)], maxlen=4)
    score = 0
    for i in range(episodes):
        #action = env.action_space.sample() # 初始化,随机从环境中选取一个动作 
        action = agent.act(state)
        _ , reward, done, _ = env.step(action)
    
        if i % 200 == 0:
            print("# It has finished {} episodes".format(i))
        
        #next_state = env.render(mode='rgb_array')
        #plt.imsave('/home/shy/state/state{}.png'.format(i),preprocess_frame(next_state)) 
        #next_state = plt.imread('/home/shy/state/state{}.png'.format(i))
    	next_state = save_gym_state(env,i)
        
        next_state, stacked_frames = stack_frames(stacked_frames, next_state, False)
        if done: # 说明这个游戏暂时结束了
            next_state = np.zeros([84,84,4]) #那么上个周期的最后没有未来记忆,因此需要把最后一个的记忆给成1
            score += reward
            score_list.append(score)
            agent.add((state, action, reward, next_state, done))
            print("# 第{}个episode的reward为".format(i),score)
            env.reset() 
            state = env.render(mode='rgb_array') 
            score = 0 # 分数置为0
            #plt.imsave('/home/shy/state/state{}.png'.format(i),preprocess_frame(state))
            #state = plt.imread('/home/shy/state/state{}.png'.format(i))
      		state =  save_gym_state(env,i)
            
            state, stacked_frames = stack_frames(stacked_frames, state, True)
        else:
            #print("the shape of state is ",state.shape)
            #print("the shape of next_state is ",next_state.shape)
            agent.add((state, action, reward, next_state, done)) # 将获得的状态添加到记忆memroy中
            agent.train() ## 这里要用同一个类,不然添加不上记忆
            score += reward
            score_list.append(score)
            state = next_state
    agent.save_model(r'/home/shy/桌面/MountainCar-v1-dqn.h5')
    

    4.4 Main Function (Trained Weights Initialization)

    #########################################
    #这个是得到了训练权重后,再次训练时候的主函数####
    #########################################
    env = gym.make('MountainCar-v1')
    episodes = 1000  # 训练1000次
    score_list = [] 
    agent_train = DQNetwork()
    agent_train.model.load_weights(r'/home/shy/桌面/MountainCar-v1-dqn.h5')
    env.reset()
    ##########
    # 1.修改注册后,要重启内核
    # 2.env.render(mode='rgb_array',close=True) 可以节约内存,但这个环境不适用
    # 3.在注册里修改里面的max变量后,增加episode,否则默认的episode是200的时候自动结束开启新的周期
    ###########
    
    #state = env.render(mode='rgb_array')
    #plt.imsave('/home/shy/state/state.png',preprocess_frame(state))
    #state = plt.imread('/home/shy/state/state.png')
    
    state =  save_gym_state(env,'init')
    stacked_frames  =  deque([np.zeros((84,84), dtype=np.int) for i in range(stack_size)], maxlen=4)
    score = 0
    for i in range(episodes):
        #action = env.action_space.sample() # 初始化,随机从环境中选取一个动作 
        action = agent.act(state)
        _ , reward, done, _ = env.step(action)
    
        if i % 200 == 0:
            print("# It has finished {} episodes".format(i))
        
        #next_state = env.render(mode='rgb_array')
        #plt.imsave('/home/shy/state/state{}.png'.format(i),preprocess_frame(next_state)) 
        #next_state = plt.imread('/home/shy/state/state{}.png'.format(i))
        next_state = save_gym_state(env,i)
        next_state, stacked_frames = stack_frames(stacked_frames, next_state, False)
        if done: # 说明这个游戏暂时结束了
            next_state = np.zeros([84,84,4]) #那么上个周期的最后没有未来记忆,因此需要把最后一个的记忆给成1
            score += reward
            score_list.append(score)
            agent_train.add((state, action, reward, next_state, done))
            print("# 第{}个episode的reward为".format(i),score)
            env.reset() 
            state = env.render(mode='rgb_array') 
            score = 0 # 分数置为0
            #plt.imsave('/home/shy/state/state{}.png'.format(i),preprocess_frame(state))
            #state = plt.imread('/home/shy/state/state{}.png'.format(i))
            state =  save_gym_state(env,i)
            
            state, stacked_frames = stack_frames(stacked_frames, state, True)
        else:
            #print("the shape of state is ",state.shape)
            #print("the shape of next_state is ",next_state.shape)
            agent_train.add((state, action, reward, next_state, done)) # 将获得的状态添加到记忆memroy中
            agent_train.train() ## 这里要用同一个类,不然添加不上记忆
            score += reward
            score_list.append(score)
            state = next_state
    agent_train.save_model(r'/home/shy/桌面/MountainCar-v1-dqn_weights.h5')
    

    4.4 Main Function (Test)

    # 测试时候的改动
         action = agent_weight_train.act(state) # 直接选择Q值最大的动作
    # 取消训练过程和添加记忆的过程,只保留将4帧图片保存到队列的过程,作为网络的输入
    

    演示效果:Bilibili

    展开全文
  • 策略学习:学习action 价值学习:最大的回报 转载于:https://www.cnblogs.com/koocn/p/7763270.html

    策略学习:学习action

    价值学习:最大的回报

     

    转载于:https://www.cnblogs.com/koocn/p/7763270.html

    展开全文
  • 【导读】本文主要介绍今日头条推出的强化学习应用在推荐的最新论文[1],首次改进DQN网络解决推荐中的在线广告投放问题。 背景介绍 随着最近RL研究的火热,在推荐平台上在线广告投放策略中如何利用RL引起了大家极大的...
        

    640?wx_fmt=jpeg

    (图片付费下载自视觉中国)

    作者 | 深度传送门

    来源 | 深度传送门(ID:gh_5faae7b50fc5)

    【导读】本文主要介绍今日头条推出的强化学习应用在推荐的最新论文[1],首次改进DQN网络解决推荐中的在线广告投放问题。


    背景介绍

    随着最近RL研究的火热,在推荐平台上在线广告投放策略中如何利用RL引起了大家极大的兴趣。然而,大部分基于RL的在线广告投放算法只聚焦于如何使广告收益最大化,却忽略了广告对推荐列表的用户体验可能会带来的负面影响。在推荐列表中不适当地插入广告或者插入广告太频繁都会损害推荐列表的用户体验,与此同时插入太少的广告又会减少广告收入。

    因此本文提出了一种全新的广告投放策略来平衡推荐用户体验以及广告的收入。在给定推荐列表前提下,本文提出了一种基于DQN的创新架构来同时解决三个任务:是否插入广告;如果插入,插入哪一条广告;以及插入广告在推荐列表的哪个位置。实验也在某短视频平台上验证了本文算法的效果。

    640?wx_fmt=png


    DQN架构

    在深入本文具体的算法架构前,我们先来简单回顾下DQN的两种经典结构:
    • 图a的DQN接受的输入是state,输出是所有可能action对应的Q-value;
    • 图b的DQN接受的输入是state以及某一个action,输出是对应的Q-value。

    这两种经典架构的最主要的问题是只能将action定义为插入哪一条广告,或者插入广告在列表的哪个位置,无法同时解决上述提到的三个任务。

    640?wx_fmt=png


    当然,从某种程度上来说将插入位置与插入哪一条广告通过某种表示形式譬如one-hot编码来建模action是一种使用上述经典DQN的方式,这样的话action的空间会变成O(A*L),其中A是广告的空间,L是插入列表的位置空间。这样的复杂度对于实际线上的广告系统是不太能够接受的。


    改进的DEAR架构

    因此,本文提出了一种改进的DQN框架DEAR用来解决上述推荐系统中在线广告投放问题。该框架试图同时解决上述提到的三个任务。也就是说,本框架会同时针对所有可能的插入位置的Q-value进行预估。

    如下左图所示,其实是融合了上述提到了两种经典DQN结构的结合,输入层包含State以及Action(插入哪条广告),输出层则是广告插入推荐列表的L+1位置对应的Q-value(假设推荐列表长度为L,则可以插入广告的位置为L+1种可能)。与此同时,使用一个特殊插入位置0用来表示不进行广告插入,因此输出层的长度扩展成为L+2。

    DEAR框架详细的架构如下右图所示,输出层Q函数被拆解成两部分:只由state决定的V函数;以及由state和action同时决定的A函数。其中,
    • state包含了使用GRU针对推荐列表和广告进行用户序列偏好建模的p;当前用户请求的上下文信息c;以及当前请求展示的推荐列表item的特征进行拼接转换形成的低维稠密向量rec;
    • action则包含两部分:一部分是候选插入广告ad的特征;另一部分则是广告插入的位置;其中这里的前半部分会被当做输入层。

    640?wx_fmt=png

    • reward函数。Reward函数也包含两部分:一部分是广告的的收入r^ad;另一部分则是用户是否继续往下刷的奖励。基于下图的reward函数,最优的Q函数策略便可以通过Bellman等式求得。

    640?wx_fmt=png

    Off-Policy训练

    本文基于用户交互历史的离线日志,采用 Off-policy的方式进行训练得到最优的投放策略。如下图所示,针对每一次迭代训练:
    • (第6行)针对用户请求构建state;
    • (第7行)根据标准的off-policy执行action,也就是选取特定ad;
    • (第8行)根据设计好的reward函数,计算reward;
    • (第10行)将状态转移信息(s_t,a_t,r_t,s_t+1)存储到replay      buffer;
    • (第11行)从replay buffer中取出mini-batch的状态转移信息,来训练得到最优的Q函数参数。

    640?wx_fmt=png

    实验

    由于没有同时包含推荐列表和广告item的公开数据集,本文基于从某短视频网站获取的自2019年3月的数据集训练得到模型,该数据集包含两种视频:正常推荐列表的视频和广告视频。正常视频的特征包含:id、点赞数、播放完成率、评论数等;广告视频的特征包含:id、图片大小、定价等。

    实验对比上本文主要挑选了如下的几个代表性的baseline进行效果对比,为了实验对比的公正性,所有对比算法使用的特征完全一致。
    • W&D。本文稍微针对W&D进行了扩展来预估是否插入广告以及预估插入广告的CTR。
    • DFM。DeepFM是在W&D基础上改进而来的一种可以额外学习特征间低阶交互的一种架构。本文的实验也表明DFM的表现好于W&D。
    • GRU。GRU4Rec使用GRU来建模用户的历史行为针对用户是否点击进行预估,本文同样也进行了扩展支持实验场景。本文的实验表明GRU4Rec效果好于W&D和DFM。
    • HDQN。HQN是一个层级DQN结构,高阶DQN决定插入位置;低阶DQN选择特定ad进行插入。本文的实验表明HDQN效果好于GRU,因为GRU只是最大化当前请求的immediate奖励,而HDQN则是最大化长期收益。
    • DEAR。本文提出的DEAR框架效果好于HDQN,因为层级的RL架构在使用off-policy方式进行联合训练时有稳定性问题。详细的效果对比,如下图所示。

    640?wx_fmt=png

    参考

    1. Deep Reinforcement Learning for Online Advertising in Recommender Systems,https://arxiv.org/abs/1909.03602

    (*本文为 AI科技大本营转载文章,载请联系作者


    精彩推荐



    早鸟票倒计时最后2天,扫码购票立减2600元

    2019 中国大数据技术大会(BDTC)再度来袭!豪华主席阵容及百位技术专家齐聚,15 场精选专题技术和行业论坛,超强干货+技术剖析+行业实践立体解读,深入解析热门技术在行业中的实践落地。

    640?wx_fmt=png

    推荐阅读

    640?wx_fmt=png

    你点的每个“在看”,我都认真当成了喜欢

    展开全文
  • 强化学习实战视频培训教程概况:强化学习是当下爆火的机器学习经典模型,系列课程从实例出发,形象解读强化学习如何完整一个实际任务。由基本概念过度到马尔科夫决策过程,通过实例演示如何通过迭代求解来得出来好的...
  • DQN神经网络学习

    2021-05-01 07:09:41
    这是DQN神经网络的简单代码 用于分享
  • dqn 深层神经网络
  • DQN

    2020-12-29 20:55:50
    文章目录前言强化学习与神经网络更新神经网络DQN 两大利器参考 前言 今天我们会来说说强化学习中的一种强大武器, Deep Q Network 简称为 DQN. Google Deep mind 团队就是靠着这 DQN 使计算机玩电动玩得比我们还厉害....
  • 所有数据集均来自DQN网络与游戏之间的实时互动。 通过使用此DQN网络,您可以与游戏中想要的任何首领作战。 还有一些你需要知道的: 为了缩短重新启动游戏的时间,我们需要游戏修改器。 游戏修改器的链接: : ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 636
精华内容 254
关键字:

dqn网络