J'ai créé un programme pour résoudre le contrôle classique d'OpenAI Gym avec Deep Q Network, communément appelé DQN, qui combine apprentissage en profondeur et apprentissage intensif. Cette fois, je voudrais présenter la mise en œuvre.
À propos de DQN lui-même
L'article est très facile à comprendre et je l'ai implémenté en référence aux articles et au code GitHub présentés ici. Si vous souhaitez connaître la théorie de l'apprentissage par renforcement et le DQN, veuillez vous reporter ici.
Comme vous pouvez le voir à partir du nom Deep Q Network, DQN se rapproche fonctionnellement de l'apprentissage Q, qui est l'un des apprentissages par renforcement, avec un réseau neuronal multicouche. En plus de cela, il semble qu'il ne puisse être appelé DQN qu'après avoir incorporé les trois méthodes suivantes.
La méthode que j'ai implémentée cette fois n'était que de 1 et 2, et je n'ai pas coupé la récompense de 3. Donc, pour être précis, ce n'est pas DQN. J'ai donc choisi DQN "Modoki".
Une plate-forme open source qui facilite la création d'un environnement pour un apprentissage amélioré. C'est une bibliothèque python
$ pip install gym
Facile à installer avec. Pour plus de détails, veuillez consulter le Site officiel.
Je voudrais maintenant présenter le code implémenté. Une partie du code affiché ici est omis, veuillez donc vérifier le tout depuis ici.
Je l'ai implémenté en utilisant Chainer. 100 unités ont 3 couches et la fonction d'activation est Leaky ReLU.
class Neuralnet(Chain):
def __init__(self, n_in, n_out):
super(Neuralnet, self).__init__(
L1 = L.Linear(n_in, 100),
L2 = L.Linear(100, 100),
L3 = L.Linear(100, 100),
Q_value = L.Linear(100, n_out, initialW=np.zeros((n_out, 100), dtype=np.float32))
)
def Q_func(self, x):
h = F.leaky_relu(self.L1(x))
h = F.leaky_relu(self.L2(h))
h = F.leaky_relu(self.L3(h))
h = self.Q_value(h)
return h
Agent
Implémentation de la partie agent de l'apprentissage amélioré.
Il définit les paramètres pour effectuer l'apprentissage par renforcement. Le réseau de neurones introduit précédemment est également défini en fonction du nombre d'états de lecture et du nombre d'actions. Et pour Fixed Target Q-Network, faites une copie complète de la fonction Q créée. En d'autres termes, il existe deux fonctions Q. Au début, j'ai eu du mal à comprendre cette partie ...
class Agent():
def __init__(self, n_st, n_act, seed):
self.n_act = n_act
self.model = Neuralnet(n_st, n_act)
self.target_model = copy.deepcopy(self.model)
self.optimizer = optimizers.Adam()
self.optimizer.setup(self.model)
self.memory = deque()
self.loss = 0
self.step = 0
self.gamma = 0,99 # taux d'actualisation
self.mem_size = 1000 #Experience Nombre d'expériences à retenir pour la relecture
self.batch_size = 100 # Taille du mini-lot pour Experience Replay
self.train_freq = 10 # Intervalle d'entraînement du réseau neuronal
self.target_update_freq = 20 # Intervalle de synchronisation du réseau cible
# ε-greedy
self.epsilon = 1 # valeur initiale de ε
self.epsilon_decay = 0,005 # Valeur de décroissance de ε
self.epsilon_min = 0 # valeur minimale de ε
self.exploration = 1000 # Nombre d'étapes jusqu'à ce que ε commence à décroître (cette fois jusqu'à ce que la mémoire soit accumulée)
Pour la relecture d'expérience
Les cinq éléments de sont exploités comme une expérience et stockés en mémoire. S'il dépasse la taille de la mémoire définie au début, il sera supprimé dans un style Tokoroten à partir de celui mis en premier. Au début, la mémoire n'était qu'une liste, mais j'ai entendu dire qu'il existe un deque qui peut lier les appendices et les pop aux deux extrémités, alors j'ai utilisé cela.
def stock_experience(self, st, act, r, st_dash, ep_end):
self.memory.append((st, act, r, st_dash, ep_end))
if len(self.memory) > self.mem_size:
self.memory.popleft()
Experience Replay
Il s'agit de la partie implémentation de Experience Replay, qui est l'une des techniques importantes de DQN. Mélangez la mémoire stockée, découpez-la dans la taille de mini-lot définie et apprenez.
def suffle_memory(self):
mem = np.array(self.memory)
return np.random.permutation(mem)
def parse_batch(self, batch):
st, act, r, st_dash, ep_end = [], [], [], [], []
for i in xrange(self.batch_size):
st.append(batch[i][0])
act.append(batch[i][1])
r.append(batch[i][2])
st_dash.append(batch[i][3])
ep_end.append(batch[i][4])
st = np.array(st, dtype=np.float32)
act = np.array(act, dtype=np.int8)
r = np.array(r, dtype=np.float32)
st_dash = np.array(st_dash, dtype=np.float32)
ep_end = np.array(ep_end, dtype=np.bool)
return st, act, r, st_dash, ep_end
def experience_replay(self):
mem = self.suffle_memory()
perm = np.array(xrange(len(mem)))
for start in perm[::self.batch_size]:
index = perm[start:start+self.batch_size]
batch = mem[index]
st, act, r, st_d, ep_end = self.parse_batch(batch)
self.model.zerograds()
loss = self.forward(st, act, r, st_d, ep_end)
loss.backward()
self.optimizer.update()
Il s'agit de la partie mise à jour de la fonction Q utilisant le réseau neuronal. Il est important d'utiliser la fonction Q copiée (self.target_model.Q_func) dans la partie qui calcule la valeur Q maximale dans l'état suivant (st_dash).
def forward(self, st, act, r, st_dash, ep_end):
s = Variable(st)
s_dash = Variable(st_dash)
Q = self.model.Q_func(s)
tmp = self.target_model.Q_func(s_dash)
tmp = list(map(np.max, tmp.data))
max_Q_dash = np.asanyarray(tmp, dtype=np.float32)
target = np.asanyarray(copy.deepcopy(Q.data), dtype=np.float32)
for i in xrange(self.batch_size):
target[i, act[i]] = r[i] + (self.gamma * max_Q_dash[i]) * (not ep_end[i])
loss = F.mean_squared_error(Q, Variable(target))
return loss
Lors du calcul de la perte ici, il semble que l'apprentissage sera plus rapide si la différence entre la valeur Q et la cible est réduite de -1 à 1, mais je n'ai pas pu l'implémenter car je ne pouvais pas comprendre la théorie en raison d'un manque d'étude (je suis désolé ...
C'est la partie qui renvoie l'action à entreprendre lorsqu'elle est saisie selon la fonction Q apprise. La méthode de sélection d'action utilise ε-gourmand.
def get_action(self, st):
if np.random.rand() < self.epsilon:
return np.random.randint(0, self.n_act)
else:
s = Variable(st)
Q = self.model.Q_func(s)
Q = Q.data[0]
a = np.argmax(Q)
return np.asarray(a, dtype=np.int8)
C'est la partie pour procéder à l'apprentissage lorsque suffisamment de mémoire est accumulée. L'étape est cochée à chaque fois et la fonction Q de la cible est synchronisée à intervalles réguliers. De plus, après avoir terminé la recherche dans une certaine mesure, ε diminuera à chaque étape.
def reduce_epsilon(self):
if self.epsilon > self.epsilon_min and self.exploration < self.step:
self.epsilon -= self.epsilon_decay
def train(self):
if len(self.memory) >= self.mem_size:
if self.step % self.train_freq == 0:
self.experience_replay()
self.reduce_epsilon()
if self.step % self.target_update_freq == 0:
self.target_model = copy.deepcopy(self.model)
self.step += 1
J'ai essayé de faire en sorte que si vous entrez le nom de l'environnement du contrôle classique, il jugera le nombre d'états et le nombre d'actions sans autorisation C'est peut-être devenu un peu brouillon et difficile à comprendre ^^;
def main(env_name):
env = gym.make(env_name)
view_path = "./video/" + env_name
n_st = env.observation_space.shape[0]
if type(env.action_space) == gym.spaces.discrete.Discrete:
# CartPole-v0, Acrobot-v0, MountainCar-v0
n_act = env.action_space.n
action_list = range(0, n_act)
elif type(env.action_space) == gym.spaces.box.Box:
# Pendulum-v0
action_list = [np.array([a]) for a in [-2.0, 2.0]]
n_act = len(action_list)
agent = Agent(n_st, n_act, seed)
env.monitor.start(view_path, video_callable=None, force=True, seed=seed)
for i_episode in xrange(1000):
observation = env.reset()
for t in xrange(200):
env.render()
state = observation.astype(np.float32).reshape((1,n_st))
act_i = agent.get_action(state)
action = action_list[act_i]
observation, reward, ep_end, _ = env.step(action)
state_dash = observation.astype(np.float32).reshape((1,n_st))
agent.stock_experience(state, act_i, reward, state_dash, ep_end)
agent.train()
if ep_end:
break
env.monitor.close()
J'ai téléchargé les résultats sur OpenAI Gym
Je pense qu'Acrobot et Pendulum sont de très bons résultats, mais Cart Pole est subtil. Il semble que les résultats varieront en fonction de la fréquence de mise à jour de la fonction Q pour la cible, de l'amplitude de l'atténuation ε et de la méthode d'optimisation. intéressant!
Je veux l'essayer avec les jeux Atari à l'avenir. A ce moment, il semble nécessaire d'envisager de couper la récompense. Dois-je envisager la normalisation et DropOut?
Recommended Posts