学习笔记:强化学习之A3C代码详解

写在前面:我是根据莫烦的视频学习的Reinforce learning,具体代码实现包括Q-learning,SARSA,DQN,Policy-Gradient,Actor-Critic以及A3C。(莫凡老师的网站:https://morvanzhou.github.io/tutorials/machine-learning/reinforcement-learning/)
今天先发表最后一个也是最复杂的一个,将来会应用到自己课设中的A3C算法,以后会把前面几个代码和注解一起发布。

算法理解:首先说一下A3C算法的个人理解,他是基于Actor-Critic的一种改进算法,主要是利用多线程维持多个agent学习并分享各自的经验,达到更快的学习速率和更好的训练效用。类似于三个工人和一个管理者共同解决一个难题,三个工人在解决过程中向管理者汇报自己的解题经验,而管理者汇总三个人的经验再将总结后的经验发放给三个工人,让他们获得集体的经验并继续解决问题。
具体到A3C算法上,这个算法维持着一个中央大脑global,以及n个独立个体worker(n一般取cpu数量,有多少个cpu就有多少个worker)。global和worker各自维持着结构完全相同的网络,Actor网络和Critic网络。worker通过actor—critic网络获取近几个回合所获得的经验并上传到global网络中,global汇集几个worker的经验后下发给每一个worker,这里所说的经验,其实就是actor和critic网络的参数。
需要着重注意的两点,首先是这个上传经验,上传的是损失loss对神经网络参数求导所得到的梯度,global从worker中获取到这个梯度并更新自己的网络参数。下载经验,是worker直接将global中的神经网络参数全盘接受,并覆盖当前自己的神经网络actor和critic。
另外,相较于普通的Actor-critic网络,每一个worker没有自我学习的过程,在Actor-critic中,worker通过optimizer最小化损失loss并更新自己的网络参数。而在A3C算法中,这一过程被global-net取代了,更新自己的网络参数只需从global中获取最新的参数即可(集体的智慧)。

具体代码
环境:开发环境python3.7。 游戏环境:gym中的 ‘CartPole-v0’ 一级倒立摆
游戏环境:一级倒立摆

import multiprocessing  #多线程模块
import threading  #线程模块
import tensorflow as tf
import numpy as np
import gym
import os
import shutil  #拷贝文件用
import matplotlib.pyplot as plt

Game='CartPole-v0'
N_workers=multiprocessing.cpu_count()    #独立玩家个体数为cpu数
MAX_GLOBAL_EP=2000  #中央大脑最大回合数
GLOBALE_NET_SCOPE='Globale_Net' #中央大脑的名字
UPDATE_GLOBALE_ITER=10   #中央大脑每N次提升一次
GAMMA=0.9    #衰减度
LR_A=0.0001   #Actor网络学习率
LR_C=0.001    #Critic 网络学习率

GLOBALE_RUNNING_R=[]   #存储总的reward
GLOBALE_EP=0   #中央大脑步数

env=gym.make(Game)   #定义游戏环境


N_S=env.observation_space.shape[0]  #观测值个数
N_A=env.action_space.n              #行为值个数


class ACnet(object):     #这个class即可用于生产global net,也可生成 worker net,因为结构相同
    def __init__(self,scope,globalAC=None):   #scope 用于确定生成什么网络
        if scope==GLOBALE_NET_SCOPE:   #创建中央大脑
            with tf.variable_scope(scope):
                self.s=tf.placeholder(tf.float32,[None,N_S],'S')   #初始化state,None代表batch,N—S是每个state的观测值个数
                self.build_net(scope)                               #建立中央大脑神经网络
                self.a_params=tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,scope=scope+'/actor')
                self.c_params = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope=scope + '/critic')
                #定义中央大脑actor和critic的参数


        else:                        #创建worker两个网络的具体步骤
            with tf.variable_scope(scope):    #这里的scope传入的是worker的名字
                self.s=tf.placeholder(tf.float32,[None,N_S],'S')  #初始化state
                self.a_his = tf.placeholder(tf.int32, [None,1], 'A_his')         #初始化action,是一个[batch,1]的矩阵,第二个维度为1,
                                                                                  #格式类似于[[1],[2],[3]]
                self.v_target=tf.placeholder(tf.float32,[None,1],'Vtarget')     #初始化v现实(V_target),数据格式和上面相同


                self.acts_prob,self.v=self.build_net(scope)   #建立神经网络,acts_prob为返回的概率值,v为返回的评价值
                self.a_params = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope=scope + '/actor')
                self.c_params = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope=scope + '/critic')


                td=tf.subtract(self.v_target,self.v,name='TD_error')  #计算td—error即v现实和v估计之差
                                                                      #v—target和v都是一串值,v-target(现实)已经计算好并传入了,v估计由传入的
                                                                      #一系列state送入critic网络确定

                with tf.name_scope('c_loss'):    #计算Critic网络的loss
                    self.c_loss=tf.reduce_mean(tf.square(td))  #Critic的loss就是td—error加平方避免负数


                with tf.name_scope('a_loss'):    #计算actor网络的损失
                    log_prob = tf.reduce_sum(tf.log(self.acts_prob +1e-5)*tf.one_hot(self.a_his,N_A,dtype=tf.float32),axis=1,keep_dims=True)
                    #这里是矩阵乘法,目的是筛选出本batch曾进行的一系列选择的概率值,acts—prob类似于一个向量[0.3,0.8,0.5],
                    #one—hot是在本次进行的的操作置位1,其他位置置为0,比如走了三次a—his为[1,0,3],N—A是4,则one—hot就是[[0,1,0,0],[1,0,0,0],[0,0,0,1]]
                    #相乘以后就是[[0,0.3,0,0],[0.8,0,0,0],[0,0,0,0.5]],log_prob就是计算这一系列选择的log值。

                    self.exp_v = log_prob * td     #td决定梯度下降的方向
                    self.a_loss=tf.reduce_mean(-self.exp_v)    #计算actor网络的损失a-loss


                with tf.name_scope('local_grad'):
                    self.a_grads=tf.gradients(self.a_loss,self.a_params)   #实现a_loss对a_params每一个参数的求导,返回一个list
                    self.c_grads=tf.gradients(self.c_loss,self.c_params)   #实现c_loss对c_params每一个参数的求导,返回一个list


            with tf.name_scope('sync'):   #worker和global的同步过程
                with tf.name_scope('pull'):   #获取global参数,复制到local—net
                    self.pull_a_params_op=[l_p.assign(g_p) for l_p,g_p in zip(self.a_params,globalAC.a_params)]
                    self.pull_c_params_op=[l_p.assign(g_p) for l_p,g_p in zip(self.c_params,globalAC.c_params)]
                with tf.name_scope('push'):   #将参数传送到gloabl中去
                    self.update_a_op=OPT_A.apply_gradients(zip(self.a_grads,globalAC.a_params))
                    self.update_c_op = OPT_C.apply_gradients(zip(self.c_grads, globalAC.c_params))
                    #其中传送的是local—net的actor和critic的参数梯度grads,具体计算在上面定义
                    #apply_gradients是tf.train.Optimizer中自带的功能函数,将求得的梯度参数更新到global中


    def build_net(self,scope): #建立神经网络过程
        w_init=tf.random_normal_initializer(0.,.1)  #初始化神经网络weights
        with tf.variable_scope('actor'):            #actor神经网络结构
            l_a=tf.layers.dense(inputs=self.s,units=200,activation=tf.nn.relu6,
                                kernel_initializer=w_init,bias_initializer=tf.constant_initializer(0.1),name='la')  #建立第一层神经网络
            acts_prob=tf.layers.dense(inputs=l_a,units=N_A,activation=tf.nn.softmax,
                               kernel_initializer=w_init,bias_initializer=tf.constant_initializer(0.1),name='act_prob')  #第二层神经网络其中之一输出为动作的均值

        with tf.variable_scope('critic'):     #critic神经网络结构,输入为位置的观测值,输出为评价值v
            l_c=tf.layers.dense(self.s,20,tf.nn.relu6,kernel_initializer=w_init,bias_initializer=tf.constant_initializer(0.1),name='lc')  #建立第一层神经网络
            v=tf.layers.dense(l_c,1,kernel_initializer=w_init,bias_initializer=tf.constant_initializer(0.1),name='v')   #第二层神经网络

        return acts_prob,v    #建立神经网络后返回的是输入当前state得到的actor网络的动作概率和critic网络的v估计


    def update_global(self,feed_dict):    #定义更新global参数函数
        SESS.run([self.update_a_op,self.update_c_op],feed_dict)    #分别更新actor和critic网络

    def pull_global(self):   #定义更新local参数函数
        SESS.run([self.pull_a_params_op,self.pull_c_params_op])

    def choose_action(self,s):   #定义选择动作函数
        s=s[np.newaxis, :]
        probs=SESS.run(self.acts_prob, feed_dict={self.s: s})
        return np.random.choice(range(probs.shape[1]), p=probs.ravel())   #从probs中按概率选取出某一个动作



class Worker(object):
    def __init__(self,name,globalAC):    #传入的name是worker的名字,globalAC是已经建立好的中央大脑GLOBALE—AC
        self.env=gym.make(Game).unwrapped
        self.name=name                   #worker的名字
        self.AC=ACnet(name,globalAC)     #第二个参数当传入的是已经建立好的GLOBALE—AC时创建的是local net
                                         #建立worker的AC网络

    def work(self):   #定义worker运行的的具体过程
        global  GLOBALE_RUNNING_R,GLOBALE_EP   #两个全局变量,R是所有worker的总reward,ep是所有worker的总episode
        total_step=1                            #本worker的总步数
        buffer_s,buffer_a,buffer_r=[],[],[]    #state,action,reward的缓存

        while not COORD.should_stop() and GLOBALE_EP<MAX_GLOBAL_EP:   #停止本worker运行的条件
                                                                     #本循环一次是一个回合

            s=self.env.reset()       #初始化环境
            ep_r=0                   #本回合总的reward

            while True:      #本循环一次是一步
                if self.name=='W_0':    #只有worker0才将动画图像显示
                    self.env.render()

                a=self.AC.choose_action(s)    #将当前状态state传入AC网络选择动作action

                s_,r,done,info=self.env.step(a)   #行动并获得新的状态和回报等信息

                if done:r=-5    #如果结束了,reward给一个惩罚数

                ep_r+=r              #记录本回合总体reward
                buffer_s.append(s)   #将当前状态,行动和回报加入缓存
                buffer_a.append(a)
                buffer_r.append(r)


                if total_step % UPDATE_GLOBALE_ITER==0 or done:  #每iter步完了或者或者到达终点了,进行同步sync操作
                    if done:
                        v_s_=0   #如果结束了,设定对未来的评价值为0
                    else:
                        v_s_=SESS.run(self.AC.v,feed_dict={self.AC.s:s_[np.newaxis,:]})[0,0]   #如果是中间步骤,则用AC网络分析下一个state的v评价

                    buffer_v_target=[]
                    for r in buffer_r[::-1]:    #将下一个state的v评价进行一个反向衰减传递得到每一步的v现实
                        v_s_=r + GAMMA* v_s_
                        buffer_v_target.append(v_s_)  #将每一步的v现实都加入缓存中
                    buffer_v_target.reverse()    #反向后,得到本系列操作每一步的v现实(v-target)

                    buffer_s,buffer_a,buffer_v_target=np.vstack(buffer_s),np.vstack(buffer_a),np.vstack(buffer_v_target)

                    feed_dict={
                        self.AC.s:buffer_s,                 #本次走过的所有状态,用于计算v估计
                        self.AC.a_his:buffer_a,             #本次进行过的所有操作,用于计算a—loss
                        self.AC.v_target:buffer_v_target    #走过的每一个state的v现实值,用于计算td
                    }

                    self.AC.update_global(feed_dict)  #update—global的具体过程在AC类中定义,feed-dict如上

                    buffer_s,buffer_a,buffer_r=[],[],[]   #清空缓存

                    self.AC.pull_global()    #从global—net提取出参数赋值给local—net

                s=s_   #跳转到下一个状态
                total_step+=1  #本回合总步数加1


                if done:   #如果本回合结束了
                    if len(GLOBALE_RUNNING_R)==0:  #如果尚未记录总体running
                        GLOBALE_RUNNING_R.append(ep_r)
                    else:
                        GLOBALE_RUNNING_R.append(0.9*GLOBALE_RUNNING_R[-1]+0.1*ep_r)

                    print(self.name,'EP:',GLOBALE_EP)
                    GLOBALE_EP+=1       #加一回合
                    break   #结束本回合



if __name__=='__main__':
    SESS=tf.Session()

    with tf.device('/cpu:0'):
        OPT_A=tf.train.RMSPropOptimizer(LR_A,name='RMSPropA')    #定义actor训练过程,后续主要是使用该optimizer中的apply—gradients操作
        OPT_C = tf.train.RMSPropOptimizer(LR_C, name='RMSPropC')  #定义critic训练过程
        GLOBALE_AC=ACnet(GLOBALE_NET_SCOPE)  #创建中央大脑GLOBALE_AC,只创建结构(A和C的参数)
        workers=[]
        for i in range(N_workers):    #N—workers等于cpu数量
            i_name='W_%i'%i   #worker name
            workers.append(Worker(name=i_name,globalAC=GLOBALE_AC))   #创建独立的worker

        COORD=tf.train.Coordinator()    #多线程
        SESS.run(tf.global_variables_initializer())   #初始化所有参数

        worker_threads=[]
        for worker in workers:    #并行过程
            job= lambda:worker.work()   #worker的工作目标,此处调用Worker类中的work
            t=threading.Thread(target=job)  #每一个线程完成一个worker的工作目标
            t.start()                 # 启动每一个worker
            worker_threads.append(t)   #每一个worker的工作都加入thread中
        COORD.join(worker_threads)     #合并几个worker,当每一个worker都运行完再继续后面步骤

        plt.plot(np.arange(len(GLOBALE_RUNNING_R)),GLOBALE_RUNNING_R)   #绘制reward图像
        plt.xlabel('step')
        plt.ylabel('Total moving reward')
        plt.show()

写在后面:这是A3C算法的实现,也算是做过里面最复杂的一个了,需要Actor-critic的基础(后面还会贴上其他几个RL算法)。当中也遇到了很多问题,首先是tensorflow运用不熟练,由于是从RL才开始真正使用tensorflow框架,所以对其中很多东西理解不深,比如张量的数据类型,由于不像numpy等可以直接输出,张量的数据类型全靠理解,占位符的各种运算也基于对张量的类型理解,因此比较吃力。其次是TF中众多的函数还需要加深理解,像本代码中的tf.train.Optimizer中地apply-gradient这些函数过去都没有遇到过。另外是面向对象的思想,由于java是我们的选修课,尽管放假时再次学习了java基础,但对于面向对象的程序设计使用的不多,而这样的程序设计又是现在软件开发的主流思想,因此也需要多多学习。
在RL的学习过程中,万分感谢莫烦老师的教程,相比于其他尚不成熟的RL学习书籍,莫烦老师的教程更加清晰明了,配合代码能够理解的较为深入。其次是学长聪哥的答疑解惑,很多问题都是在聪哥的点拨下才理解清楚,在这里表示感谢!另外有任何指正或者建议意见都可以留在下面一起讨论!谢谢!

  • 14
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
以下是一个简单的 A3C 实现代码,仅供参考: ```python import gym import numpy as np import tensorflow as tf import threading global_episode = 0 global_rewards = [] global_episodes = 10000 episode_rewards = tf.keras.metrics.Mean('episode_rewards', dtype=tf.float32) class A3C(tf.keras.Model): def __init__(self, state_size, action_size): super(A3C, self).__init__() self.state_size = state_size self.action_size = action_size self.dense1 = tf.keras.layers.Dense(64, activation='relu') self.dense2 = tf.keras.layers.Dense(64, activation='relu') self.policy_logits = tf.keras.layers.Dense(action_size) self.values = tf.keras.layers.Dense(1) def call(self, inputs): x = self.dense1(inputs) x = self.dense2(x) logits = self.policy_logits(x) values = self.values(x) return logits, values class Agent: def __init__(self, state_size, action_size): self.state_size = state_size self.action_size = action_size self.global_model = A3C(state_size, action_size) self.global_model(tf.keras.Input(shape=(state_size,))) self.opt = tf.optimizers.Adam(learning_rate=.0001, clipnorm=1.0) self.gamma = 0.99 self.tau = .125 def train(self, state, action, reward, next_state, done): with tf.GradientTape() as tape: logits, value = self.global_model(tf.convert_to_tensor(state[None, :], dtype=tf.float32)) next_logits, next_value = self.global_model(tf.convert_to_tensor(next_state[None, :], dtype=tf.float32)) advantage = reward + self.gamma * next_value[0] * (1 - int(done)) - value[0] value_loss = advantage ** 2 policy = tf.nn.softmax(logits) entropy = tf.reduce_sum(policy * tf.math.log(policy)) policy_loss = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=action, logits=logits) total_loss = tf.reduce_mean(.5 * value_loss + policy_loss - .01 * entropy) grads = tape.gradient(total_loss, self.global_model.trainable_variables) self.opt.apply_gradients(zip(grads, self.global_model.trainable_variables)) def get_action(self, state): logits, _ = self.global_model(tf.convert_to_tensor(state[None, :], dtype=tf.float32)) probs = tf.nn.softmax(logits) action = np.random.choice(self.action_size, p=probs.numpy()[0]) return action def sync(self, local_model): for local, global_ in zip(local_model.trainable_variables, self.global_model.trainable_variables): global_.assign(self.tau * local + (1 - self.tau) * global_) def test(env, agent): state = env.reset() done = False total_reward = 0 while not done: action = agent.get_action(state) next_state, reward, done, _ = env.step(action) state = next_state total_reward += reward return total_reward def train(global_agent, num_episodes, lock): global global_episode, global_rewards env = gym.make('CartPole-v0') agent = Agent(env.observation_space.shape[0], env.action_space.n) for ep in range(num_episodes): state = env.reset() done = False episode_reward = 0 while not done: action = agent.get_action(state) next_state, reward, done, _ = env.step(action) agent.train(state, action, reward, next_state, done) state = next_state episode_reward += reward with lock: global_rewards.append(episode_reward) global_episode += 1 episode_rewards(episode_reward) print("Episode: {}, Reward: {}".format(global_episode, episode_reward)) agent.sync(agent) if global_episode % 100 == 0: test_reward = test(env, agent) print("Test Reward: {}".format(test_reward)) if __name__ == '__main__': lock = threading.Lock() global_agent = Agent(4, 2) threads = [] for i in range(4): t = threading.Thread(target=train, args=(global_agent, global_episodes//4, lock)) threads.append(t) for thread in threads: thread.start() for thread in threads: thread.join() ``` 在这个实现中,我们首先定义了一个 A3C 模型和一个 Agent 类,其中 A3C 模型有两个输出:一个是策略 logits,一个是状态值估计。Agent 类负责在环境中与模型进行交互,以及使用梯度下降更新模型。 我们使用了一个简单的 CartPole 环境来测试模型。在训练过程中,我们创建了四个线程来并行地训练模型,每个线程都有自己的 local 模型。每个 episode 结束时,local 模型的参数会同步到 global 模型中。 此外,我们还定义了一个 test 函数来测试模型的性能。在每个训练周期的末尾,我们都会调用这个函数来评估模型在测试集上的表现。 请注意,这只是一个简单的实现,无法保证在所有环境中都能正常运行。如果你想要在自己的项目中使用 A3C,建议参考一些开源的实现,如 Tensorflow 的官方实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值