Qu’est-ce que l’A3C ? Le calcul de l’A3C a été déchargé par le groupe DeepMind de Google récemment, et il a fait un peu de bruit en… rendant obsolète le DQN. Il était plus rapide, plus facile, de plus en plus vigoureux, et prêt à réaliser de bien meilleurs scores sur la batterie standard des entreprises de RL Profound. Dans l’ensemble, il pouvait fonctionner dans des espaces d’activité aussi cohérents que discrets. Compte tenu de cela, il est devenu la plongée vers le calcul de Deep RL pour les nouveaux problèmes de test avec des espaces d’état et d’activité complexes. À vrai dire, OpenAI vient de sortir une version d’A3C en tant que “spécialiste général du démarrage” pour travailler avec leur nouvel ensemble (et extrêmement différent) de situations de l’Univers.

Les 3 A partir de A3C

Nonconcurrent Bit of leeway Entertainer Pundit est une pièce sérieuse. Et si nous commencions par décharger le nom, et à partir de là, nous commencerions à décharger la mécanique du calcul lui-même.

Non-concurrent : pas du tout comme le DQN, où un opérateur solitaire auquel parle un système neuronal solitaire collabore avec une situation solitaire, A3C utilise différentes manifestations de ce qui précède afin de s’adapter de manière d’autant plus productive. Dans A3C, il existe un système mondial et différents opérateurs spécialisés qui ont chacun leur propre arrangement des paramètres du système. Chacun de ces opérateurs se connecte simultanément à son propre double de la terre, tandis que les différents spécialistes communiquent avec leur environnement. L’explication de ce fonctionnement supérieur à celui d’un spécialiste solitaire (au-delà de l’accélération de l’accomplissement de plus de travail), est que l’expérience de chaque opérateur est libre de l’expérience des autres. Dans cette optique, l’expérience générale accessible pour la préparation s’avère progressivement assortie.

Animateur Pundit : Jusqu’à présent, cet arrangement s’est concentré sur les stratégies de mise en valeur de l’estime, par exemple, le Q-learning, ou les techniques de cycle d’approche, par exemple, la pente stratégique. Le personnage à l’écran Pundit consolide les avantages des deux méthodologies. Grâce à A3C, notre système évaluera à la fois une capacité de valeur V(s) (à quel point un état spécifique doit être dans un état donné) et une stratégie π(s) (beaucoup de rendements de probabilité d’activité). Chacun de ces éléments sera constitué de couches indépendantes et complètement associées, situées au point le plus élevé du système. Fondamentalement, l’opérateur utilise la jauge de valeur (l’expert) pour rafraîchir la stratégie (l’amuseur) de manière plus intelligente que les stratégies d’inclinaison d’arrangement conventionnelles.

Mise en œuvre de l’algorithme

Pendant le temps passé à structurer cet usage du calcul A3C, j’ai utilisé comme référence les exécutions de qualité de DennyBritz et d’OpenAI. Deux que je prescris profondément dans le cas où vous souhaiteriez voir des options en contraste avec mon code ici. Chaque segment inséré ici est pris en dehors de tout lien pertinent avec la question traitée à des fins pédagogiques, et ne fonctionnera pas seul. Pour voir et exécuter l’exécution complète et utile d’A3C, consultez mon coffre-fort Github.

Le schéma général de l’ingénierie du code est le suivant :

AC_Network – Cette classe contient toutes les opérations de Tensorflow pour réaliser les systèmes eux-mêmes.

Spécialiste – Cette classe contient un double d’AC_Network, une classe de domaine, tout comme toutes les raisons de coopérer avec la nature et de rafraîchir le système mondial.

Code de niveau élevé pour la construction des événements spécialisés et leur exécution en parallèle.

Le calcul de l’A3C commence par le développement du système mondial. Ce système comprendra des couches convolutionnelles pour traiter les conditions spatiales, suivies d’une couche LSTM pour traiter les conditions fugitives, enfin, des couches de valeur et de rendement stratégique. Voici le code modèle pour la mise en place de la carte du système lui-même.

classe AC_Network() :

    def __init__(self,s_size,a_size,scope,trainer) :

        avec tf.variable_scope(scope) :

            #Couches d’entrée et d’encodage visuel

            self.inputs = tf.placeholder(shape=[None,s_size],dtype=tf.float32)

            self.imageIn = tf.reshape(self.inputs,shape=[-1,84,84,1])

            self.conv1 = slim.conv2d(activation_fn=tf.nn.elu,

                inputs=self.imageIn,num_outputs=16,

                kernel_size=[8,8],stride=[4,4],padding=’VALID’)

            self.conv2 = slim.conv2d(activation_fn=tf.nn.elu,

                inputs=self.conv1,num_outputs=32,

                kernel_size=[4,4],stride=[2,2],padding=’VALID’)

            caché = slim.fully_connected(slim.flatten(self.conv2),256,activation_fn=tf.nn.elu)

            #Réseau récurrent pour les dépendances temporelles

            lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(256,state_is_tuple=True)

            c_init = np.zeros((1, lstm_cell.state_size.c), np.float32)

            h_init = np.zeros((1, lstm_cell.state_size.h), np.float32)

            self.state_init = [c_init, h_init]

            c_in = tf.placeholder(tf.float32, [1, lstm_cell.state_size.c])

            h_in = tf.placeholder(tf.float32, [1, lstm_cell.state_size.h])

            self.state_in = (c_in, h_in)

            rnn_in = tf.expand_dims(hidden, [0])

            step_size = tf.shape(self.imageIn)[:1]

            state_in = tf.nn.rnn_cell.LSTMStateTuple(c_in, h_in)

            lstm_outputs, lstm_state = tf.nn.dynamic_rnn(

                lstm_cell, rnn_in, initial_state=state_in, sequence_length=step_size,

                time_major=False)

            lstm_c, lstm_h = lstm_state

            self.state_out = (lstm_c[:1, :], lstm_h[:1, :])

            rnn_out = tf.reshape(lstm_outputs, [-1, 256])

            #Couches de sortie pour les estimations de politiques et de valeurs

            self.policy = slim.fully_connected(rnn_out,a_size,

                activation_fn=tf.nn.softmax,

                weights_initializer=normalized_columns_initializer(0.01),

                biases_initializer=None)

            self.value = slim.fully_connected(rnn_out,1,

                activation_fn=None,

                weights_initializer=normalized_columns_initializer(1.0),

                biases_initializer=None)

Ensuite, de nombreux opérateurs ouvriers, chacun avec son propre système et sa propre condition, sont fabriqués. Chacun de ces ouvriers fonctionne sur une chaîne de processeur différente, de sorte qu’il ne devrait pas y avoir un plus grand nombre d’ouvriers qu’il n’y a de chaînes sur votre CPU.

avec tf.device(“/cpu:0”) :

    master_network = AC_Network(s_size,a_size, ‘global’,None) # Générer un réseau global

    num_workers = multiprocessing.cpu_count() # Définir les travailleurs ou le nombre de threads CPU disponibles

    travailleurs = []

    # Créer des classes de travailleurs

    pour i dans la gamme(num_workers) :

        workers.append(Worker(DoomGame(),i,s_size,a_size,trainer,saver,model_path))

avec tf.Session() comme sess :

    coord = tf.train.Coordinateur()

    si load_model == Vrai :

        Imprimer “Modèle de chargement…

        ckpt = tf.train.get_checkpoint_state(model_path)

        saver.restore(sess,ckpt.model_checkpoint_path)

    d’autre part :

        sess.run(tf.global_variables_initializer())

    # C’est ici que la magie asynchrone se produit.

    # Démarrer le processus de “travail” pour chaque travailleur dans une menace distincte.

    worker_threads = []

    pour les travailleurs dans les travailleurs :

        worker_work = lambda : worker.work(max_episode_length,gamma,master_network,sess,coord)

        t = filetage.filetage(target=(travailleur_travail))

        t.start()

        worker_threads.append(t)

    coord.join(fils_travailleurs)

Chaque travailleur commence par régler les paramètres de son système sur ceux du système mondial. Nous pouvons y parvenir en mettant au point une opération Tensorflow qui fixe chaque facteur du système spécialisé de quartier à la même variable d’incitation dans le système mondial.

Chaque spécialiste à ce stade s’associe à son propre double de la nature et recueille la compréhension. Chacun d’entre eux conserve une liste de ses expériences (perception, activité, rémunération, travail, estime) qui est continuellement enrichie par les communications avec la terre.

lass Worker() :

      ….

      ….

      ….

      def work(self,max_episode_longueur,gamma,global_AC,sess,coord) :

        compte_épisode = 0

        total_step_count = 0

        imprimer “Travailleur débutant” + str(self.number)

        avec sess.as_default(), sess.graph.as_default() :

            alors que non coord.should_stop() :

                sess.run(self.update_local_ops)

                episode_buffer = []

                episode_values = []

                episode_frames = []

                épisode_reward = 0

                episode_step_count = 0

                d = Faux

                self.env.new_episode()

                s = self.env.get_state().screen_buffer

                episode_frames.append(s)

                s = process_frame(s)

                rnn_state = self.local_AC.state_init

                while self.env.is_episode_finished() == Faux :

                    #Prendre une mesure en utilisant les probabilités des résultats du réseau politique.

                    a_dist,v,rnn_state = sess.run([self.local_AC.policy,self.local_AC.value,self.local_AC.state_out],

                        feed_dict={self.local_AC.inputs :[s],

                        self.local_AC.state_in[0]:rnn_state[0],

                        self.local_AC.state_in[1]:rnn_state[1]})

                    a = np.random.choice(a_dist[0],p=a_dist[0])

                    a = np.argmax(a_dist == a)

                    r = self.env.make_action(self.actions[a]) / 100.0

                    d = self.env.is_episode_finished()

                    si d == Faux :

                        s1 = self.env.get_state().screen_buffer

                        episode_frames.append(s1)

                        s1 = process_frame(s1)

                    d’autre part :

                        s1 = s

                    episode_buffer.append([s,a,r,s1,d,v[0,0])

                    episode_values.append(v[0,0])

                    episode_reward += r

                    s = s1 

                    total_steps += 1

                    episode_step_count += 1

                    #Spécifique à VizDoom. Nous dormons le jeu pendant un temps déterminé.

                    si self.sleep_time>0 :

                        sleep(self.sleep_time)

                    # Si l’épisode n’est pas terminé, mais que le tampon de l’expérience est plein, alors nous

                    # faire une étape de mise à jour en utilisant le déploiement de cette expérience.

                    if len(episode_buffer) == 30 et d != True et episode_step_count != max_episode_longueur – 1 :

                        # Comme nous ne savons pas quel est le véritable retour final, nous “démarrons” de notre

                        # estimation de la valeur.

                        v1 = sess.run(self.local_AC.value,

                            feed_dict={self.local_AC.inputs :[s],

                            self.local_AC.state_in[0]:rnn_state[0],

                            self.local_AC.state_in[1]:rnn_state[1]})[0,0]

                        v_l,p_l,e_l,g_n,v_n = self.train(global_AC,episode_buffer,sess,gamma,v1)

                        episode_buffer = []

                        sess.run(self.update_local_ops)

                    si d == Vrai :

                        pause

                self.episode_rewards.append(episode_reward)

                self.episode_lengths.append(episode_step_count)

                self.episode_mean_values.append(np.mean(episode_values))

                # Mettre à jour le réseau en utilisant le tampon d’expérience à la fin de l’épisode.

                v_l,p_l,e_l,g_n,v_n = self.train(global_AC,episode_buffer,sess,gamma,0.0)

Lorsque l’histoire de l’expérience de l’ouvrier est assez énorme, nous l’utilisons pour décider d’un rendement limité et d’une certaine marge de manœuvre, et nous nous en servons pour calculer la valeur et aborder les malheurs. Nous déterminons en outre une entropie (H) de l’approche. Celle-ci est comparée à la dispersion des probabilités d’activité. Si, par hasard, la stratégie produit des activités avec des probabilités comparatives modérées, l’entropie sera alors élevée, mais si, par hasard, l’arrangement recommande une activité solitaire avec une probabilité énorme, l’entropie sera alors faible. Nous utilisons l’entropie comme une méthode pour améliorer l’investigation, en incitant le modèle à être traditionaliste en ce qui concerne la certitude de la bonne activité.

Perte de valeur : L = Σ(R – V(s))²

Perte de la police : L = -log(π(s)) * A(s) – β*H(π)

Un ouvrier à ce moment-là utilise ces malheurs pour acquérir des angles concernant les paramètres de son système. Chacun de ces angles est couramment coupé afin d’anticiper des rafraîchissements de paramètres trop importants qui peuvent déstabiliser l’approche.

Un spécialiste à ce stade utilise les inclinaisons pour rafraîchir les paramètres du système mondial. Dans cette optique, le système mondial est continuellement rafraîchi par chacun des spécialistes, au fur et à mesure qu’ils collaborent avec leur état.

classe AC_Network() :

    def __init__(self,s_size,a_size,scope,trainer) :

        ….

        ….

        ….

        si scope != “global” :

            self.actions = tf.placeholder(shape=[None],dtype=tf.int32)

            self.actions_onehot = tf.one_hot(self.actions,a_size,dtype=tf.float32)

            self.target_v = tf.placeholder(shape=[None],dtype=tf.float32)

            self.advantages = tf.placeholder(shape=[None],dtype=tf.float32)

            self.responsible_outputs = tf.reduce_sum(self.policy * self.actions_onehot, [1])

            #Fonctions de perte

            self.value_loss = 0,5 * tf.reduce_sum(tf.square(self.target_v – tf.reshape(self.value,[-1]))

            self.entropy = – tf.reduce_sum(self.policy * tf.log(self.policy))

            self.policy_loss = -tf.reduce_sum(tf.log(self.responsible_outputs)*self.advantages)

            perte.personnelle = 0,5 * perte.de.valeur.personnelle + perte.politique.personnelle – entropie.personnelle * 0,01

            #Obtenir des gradients à partir d’un réseau local en utilisant les pertes locales

            local_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope)

            self.gradients = tf.gradients(self.loss,local_vars)

            self.var_norms = tf.global_norm(local_vars)

            grads,self.grad_norms = tf.clip_by_global_norm(self.gradients,40.0)

            #Appliquer les gradients locaux au réseau mondial

            global_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, ‘global’)

            self.apply_grads = trainer.apply_gradients(zip(grads,global_vars))

classe Ouvrier() :

      ….

      ….

      ….

      def train(self,global_AC,rollout,sess,gamma,bootstrap_value) :

        rollout = np.array(rollout)

        observations = déploiement [ :,0]

        actions = déploiement[ :,1]

        récompenses = mise en place [ :,2]

        observations_suivantes = déploiement[ :,3]

        valeurs = déploiement[ :,5]

        # Ici, nous prenons les récompenses et les valeurs du déploiement, et nous les utilisons pour

        # générer l’avantage et les rendements escomptés.

        # La fonction d’avantage utilise “l’estimation d’avantage généralisé”.

        self.rewards_plus = np.asarray(rewards.tolist() + [bootstrap_value])

        discounted_rewards = discount(self.rewards_plus,gamma)[:-1]

        self.value_plus = np.asarray(values.tolist() + [bootstrap_value])

        avantages = récompenses + gamma * self.value_plus [1 :] – self.value_plus[:-1]

        avantages = réduction (avantages, gamma)

        # Mettre à jour le réseau mondial en utilisant les gradients de perte

        # Générer des statistiques de réseau pour sauvegarder périodiquement

        rnn_state = self.local_AC.state_init

        feed_dict = {self.local_AC.target_v:discounted_rewards,

            self.local_AC.inputs:np.vstack(observations),

            self.local_AC.actions:actions,

            self.local_AC.advantages:avantages,

            self.local_AC.state_in[0]:rnn_state[0],

            self.local_AC.state_in[1]:rnn_state[1]}

        v_l,p_l,e_l,g_n,v_n,_ = sess.run([self.local_AC.value_loss,

            self.local_AC.policy_loss,

            self.local_AC.entropy,

            self.local_AC.grad_norms,

            self.local_AC.var_norms,

            self.local_AC.apply_grads],

            feed_dict=feed_dict)

        return v_l / len(rollout),p_l / len(rollout),e_l / len(rollout), g_n,v_n

Lorsqu’une mise à jour efficace est effectuée dans le système mondial, toute la procédure se répète ! L’ouvrier remet alors ses propres paramètres à ceux du système mondial, et la procédure recommence.