J'ai entendu dire que Chainer RL Beta a été publié, donc je l'ai utilisé immédiatement. Ici, référez-vous à la source du Guide de démarrage rapide et changez-le pour la troisième ligne (○ × jeu). Je suis.
Tout d'abord, installez ChainerRL.
pip install chainerrl
cmake est requis, donc si vous ne l'avez pas installé, veuillez l'installer à l'avance.
brew install cmake
Mon environnement est le suivant.
Quel que soit le type de joueur (DQN, coup aléatoire, humain, etc.), vous aurez besoin d'un plateau de jeu pour jouer au jeu ○ ×, créez-le d'abord. Cette fois, j'écrirai toutes les sources dans un fichier sans diviser le fichier, alors importez les bibliothèques nécessaires au début.
dqn.py
import chainer
import chainer.functions as F
import chainer.links as L
import chainerrl
import numpy as np
#Plateau de jeu
class Board():
def reset(self):
self.board = np.array([0] * 9, dtype=np.float32)
self.winner = None
self.missed = False
self.done = False
def move(self, act, turn):
if self.board[act] == 0:
self.board[act] = turn
self.check_winner()
else:
self.winner = turn*-1
self.missed = True
self.done = True
def check_winner(self):
win_conditions = ((0,1,2),(3,4,5),(6,7,8),(0,3,6),(1,4,7),(2,5,8),(0,4,8),(2,4,6))
for cond in win_conditions:
if self.board[cond[0]] == self.board[cond[1]] == self.board[cond[2]]:
if self.board[cond[0]]!=0:
self.winner=self.board[cond[0]]
self.done = True
return
if np.count_nonzero(self.board) == 9:
self.winner = 0
self.done = True
def get_empty_pos(self):
empties = np.where(self.board==0)[0]
if len(empties) > 0:
return np.random.choice(empties)
else:
return 0
def show(self):
row = " {} | {} | {} "
hr = "\n-----------\n"
tempboard = []
for i in self.board:
if i == 1:
tempboard.append("○")
elif i == -1:
tempboard.append("×")
else:
tempboard.append(" ")
print((row + hr + row + hr + row).format(*tempboard))
Les cinq fonctions suivantes. Je suis désolé pour la source peu claire pour les débutants en Python, mais vous pouvez voir ce qu'ils font.
--reset Initialise le plateau de jeu. Courir avant le début de chaque épisode --move Effectue le placement des mains. Jugement de victoire ou de défaite, erreur (placement dans une case qui ne peut être placée) et fin de la partie après placement --check_winner Jugement de la victoire --get_empty_pos Récupère l'un des index des cellules qui peuvent être placés au hasard. Comme cela sera décrit plus loin, il est utilisé lors des frappes aléatoires. --show Affiche l'état de la carte. Pour jouer contre les humains
Il semble qu'il soit bon de faire une aventure de temps en temps pour ne pas tomber dans une solution locale, et Quickstart avait également une telle implémentation, donc cela suit ici aussi. J'ai utilisé celui de gym dans Quickstart, mais ici je n'ai pas d'autre choix que de le faire moi-même, alors ajoutez le code suivant à la fin.
dqn.py
#Objet de fonction aléatoire pour l'explorateur
class RandomActor:
def __init__(self, board):
self.board = board
self.random_count = 0
def random_action_func(self):
self.random_count += 1
return self.board.get_empty_pos()
random_action_func est la clé de cet objet. Appelez get_empty_pos du tableau créé précédemment pour obtenir l'espace qui peut être placé et le renvoyer à l'appelant. Il incrémente également le compteur afin que vous puissiez voir plus tard à quel point cette fonction a été utilisée comme information statistique (si DQN l'a renvoyée au hasard au lieu de réfléchir). Pourquoi avez-vous pris la peine de faire de quelque chose comme celui-ci un objet séparé? Sera expliqué plus tard.
C'est le point principal pour DQN, et Chainer RL entre en jeu.
dqn.py
#Fonction Q
class QFunction(chainer.Chain):
def __init__(self, obs_size, n_actions, n_hidden_channels=81):
super().__init__(
l0=L.Linear(obs_size, n_hidden_channels),
l1=L.Linear(n_hidden_channels, n_hidden_channels),
l2=L.Linear(n_hidden_channels, n_hidden_channels),
l3=L.Linear(n_hidden_channels, n_actions))
def __call__(self, x, test=False):
#-Parce qu'il traite de 1, qui fuit_relu
h = F.leaky_relu(self.l0(x))
h = F.leaky_relu(self.l1(h))
h = F.leaky_relu(self.l2(h))
return chainerrl.action_value.DiscreteActionValue(self.l3(h))
···c'est tout. C'est assez simple pour battre un peu. C'est presque la même chose que de définir normalement un NN.
Maintenant que nous avons quelque chose à construire, tout ce que nous devons faire est de préparer l'environnement et l'agent et de construire la progression du jeu. D'abord de l'environnement et de l'agent.
dqn.py
#Préparation du conseil
b = Board()
#Préparation d'un objet de fonction aléatoire pour l'explorateur
ra = RandomActor(b)
#Nombre de dimensions de l'environnement et du comportement
obs_size = 9
n_actions = 9
# Q-configuration des fonctions et de l'optimiseur
q_func = QFunction(obs_size, n_actions)
optimizer = chainer.optimizers.Adam(eps=1e-2)
optimizer.setup(q_func)
#Taux d'actualisation de la récompense
gamma = 0.95
# Epsilon-Aventure occasionnelle avec gourmand. Terminer en 50000 étapes_devenir epsilon
explorer = chainerrl.explorers.LinearDecayEpsilonGreedy(
start_epsilon=1.0, end_epsilon=0.3, decay_steps=50000, random_action_func=ra.random_action_func)
#Tampon utilisé dans la méthode d'apprentissage utilisée dans DQN appelée Experience Replay
replay_buffer = chainerrl.replay_buffer.ReplayBuffer(capacity=10 ** 6)
#Génération d'agent (replay)_Deux tampons de partage, etc.)
agent_p1 = chainerrl.agents.DoubleDQN(
q_func, optimizer, replay_buffer, gamma, explorer,
replay_start_size=500, update_frequency=1,
target_update_frequency=100)
agent_p2 = chainerrl.agents.DoubleDQN(
q_func, optimizer, replay_buffer, gamma, explorer,
replay_start_size=500, update_frequency=1,
target_update_frequency=100)
À propos, chez Epsilon-gourmand, l'acteur aléatoire que j'ai créé plus tôt apparaîtra. Il est nécessaire de passer une référence à la fonction à utiliser lors de l'aventure à l'explorateur à l'avance, mais il semble que vous ne pouvez pas passer un argument à cette fonction? Donc, j'ai passé une référence au plateau de jeu à la variable membre de l'objet RandomActor qui a été instancié à l'avance, et dans le traitement interne de l'explorateur, j'ai demandé à random_action_func d'être appelé sans argument et je l'ai fait. Je pense qu'il existe un moyen plus intelligent, alors faites-le moi savoir. ..
De plus, la méthode de ε-gourmand est changée en une méthode (LinearDecayEpsilonGreedy) qui réduit progressivement la valeur au lieu d'en faire une valeur constante. Commencez par 1,0 = toujours aléatoire, puis réduisez à 0,3 sur 50 000 pas. Je ne sais pas si ce numéro est également valide, il peut donc être bon de le changer de différentes manières.
L'agent crée P1 et P2 qui partagent un optimiseur et un replay_buffer pour se battre.
Je pense que c'est un peu fastidieux parce que je veux déjà le faire, mais si j'ajoute cela, je peux le faire, alors soyez patient.
dqn.py
#Nombre de jeux d'apprentissage
n_episodes = 20000
#Déclaration de compteur
miss = 0
win = 0
draw = 0
#Épisodes répétés
for i in range(1, n_episodes + 1):
b.reset()
reward = 0
agents = [agent_p1, agent_p2]
turn = np.random.choice([0, 1])
last_state = None
while not b.done:
#Acquisition de masse de placement
action = agents[turn].act_and_train(b.board.copy(), reward)
#Effectuer le placement
b.move(action, 1)
#À la suite du placement, à la fin, définissez des valeurs dans la récompense et le compteur et apprenez
if b.done == True:
if b.winner == 1:
reward = 1
win += 1
elif b.winner == 0:
draw += 1
else:
reward = -1
if b.missed is True:
miss += 1
#Apprenez en terminant l'épisode
agents[turn].stop_episode_and_train(b.board.copy(), reward, True)
#L'autre partie termine également l'épisode et apprend. N'apprends pas les erreurs de ton adversaire comme une victoire
if agents[1 if turn == 0 else 0].last_state is not None and b.missed is False:
#Dernière sauvegarde au tour précédent_Passer l'état comme état après l'exécution de l'action
agents[1 if turn == 0 else 0].stop_episode_and_train(last_state, reward*-1, True)
else:
#Enregistrer le dernier état du tour pour l'apprentissage
last_state = b.board.copy()
#Inverser la valeur sur la carte lors de la poursuite
b.board = b.board * -1
#Changer de tour
turn = 1 if turn == 0 else 0
#Affichage de la progression sur la console
if i % 100 == 0:
print("episode:", i, " / rnd:", ra.random_count, " / miss:", miss, " / win:", win, " / draw:", draw, " / statistics:", agent_p1.get_statistics(), " / epsilon:", agent_p1.explorer.epsilon)
#Initialisation du compteur
miss = 0
win = 0
draw = 0
ra.random_count = 0
if i % 10000 == 0:
#Enregistrer le modèle tous les 10000 épisodes
agent_p1.save("result_" + str(i))
print("Training finished.")
20000 Se compose d'une instruction for imbriquée qui répète le jeu et d'une instruction while qui répète le tour en jeu. En fait, la première et la deuxième attaque sont l'agent lui-même. Dans ce jeu, au lieu de ○ ×, vous placerez votre propre main comme 1 et la main de votre adversaire comme -1 sur le plateau, mais puisque vous voulez apprendre l'environnement et les actions des première et deuxième attaques, dans la progression du jeu Mettez toujours un 1 sur le tableau au lieu de diviser les signes.
#Effectuer le placement
b.move(action, 1)
Bien sûr, si elle est laissée telle quelle, la planche sera pleine de 1, donc le code de la planche est inversé lorsque le tour est changé.
#Inverser la valeur sur la carte lors de la poursuite
else:
b.board = b.board * -1
Et enfin, nous sauvegardons le modèle entraîné. ChainerRL semble créer ce répertoire même s'il n'existe pas, j'ai donc essayé de sauvegarder l'historique dans le répertoire avec le nombre d'épisodes à la fin pour 10 000 épisodes. Puisque nous nous entraînons avec la même expérience, nous enregistrons uniquement agent_p1.
Faisons-le maintenant ...! Étant donné que la valeur de epsilon est grande au début, la plupart d'entre eux sont des résultats aléatoires (le nombre de fois où la valeur rnd est frappée au hasard). Par conséquent, il y a peu d'erreurs, mais si le nombre de coups aléatoires diminue progressivement, les chances de toucher avec les mains que DQN pense augmenteront, de sorte que le nombre d'erreurs augmentera temporairement, mais à mesure que l'apprentissage progresse, il convergera également à 15000 fois. Quand il a dépassé, il est devenu presque la première moitié du chiffre unique.
episode: 100 / rnd: 761 / miss: 1 / win: 85 / draw: 14 / statistics: [('average_q', 0.11951273068342624), ('average_loss', 0.09235552993858538)] / epsilon: 0.994778
episode: 200 / rnd: 722 / miss: 3 / win: 85 / draw: 12 / statistics: [('average_q', 0.35500590929140996), ('average_loss', 0.12790488153218765)] / epsilon: 0.9895
episode: 300 / rnd: 756 / miss: 6 / win: 82 / draw: 12 / statistics: [('average_q', 0.6269444783473722), ('average_loss', 0.12164947750267516)] / epsilon: 0.984278
: (Omis)
episode: 19800 / rnd: 212 / miss: 1 / win: 69 / draw: 30 / statistics: [('average_q', 0.49387913595157096), ('average_loss', 0.07891365175610675)] / epsilon: 0.3
episode: 19900 / rnd: 229 / miss: 1 / win: 61 / draw: 38 / statistics: [('average_q', 0.49195677296191365), ('average_loss', 0.07796313042393459)] / epsilon: 0.3
episode: 20000 / rnd: 216 / miss: 0 / win: 70 / draw: 30 / statistics: [('average_q', 0.509864846571749), ('average_loss', 0.07866546801090374)] / epsilon: 0.3
Training finished.
Il semble que vous frappez sans faire d'erreurs, et même si vous frappez au hasard de temps en temps, ce sera tout un match nul, donc je jouerai contre moi-même pour vérifier la force.
Tout d'abord, créez un objet appelé HumanPlayer en tant qu'interface pour les humains.
dqn.py
#Joueur humain
class HumanPlayer:
def act(self, board):
valid = False
while not valid:
try:
act = input("Please enter 1-9: ")
act = int(act)
if act >= 1 and act <= 9 and board[act-1] == 0:
valid = True
return act-1
else:
print ("Invalid move")
except Exception as e:
print (act + " is invalid")
C'est la partie du progrès. En fixant l'agent DQN à 1 et l'humain à -1, les première et seconde attaques décident "si l'agent DQN est la première attaque" avant le début de l'épisode et contrôlent s'il faut ou non sauter la première fois. Je suis. À cet égard, les agents sont toujours ○ et les humains sont toujours ×, qu'ils soient premiers ou seconds.
dqn.py
#Vérification
human_player = HumanPlayer()
for i in range(10):
b.reset()
dqn_first = np.random.choice([True, False])
while not b.done:
#DQN
if dqn_first or np.count_nonzero(b.board) > 0:
b.show()
action = agent_p1.act(b.board.copy())
b.move(action, 1)
if b.done == True:
if b.winner == 1:
print("DQN Win")
elif b.winner == 0:
print("Draw")
else:
print("DQN Missed")
agent_p1.stop_episode()
continue
#Humain
b.show()
action = human_player.act(b.board.copy())
b.move(action, -1)
if b.done == True:
if b.winner == -1:
print("HUMAN Win")
elif b.winner == 0:
print("Draw")
agent_p1.stop_episode()
print("Test finished.")
Le fait est que l'agent n'apprend pas ici, il est donc censé utiliser act () et stop_episode (). C'est aussi comme Quickstart.
Maintenant que nous sommes prêts pour le match, il est stérile de s'entraîner à nouveau 20 000 fois, alors chargez l'agent sauvé. En fait, il est judicieux de basculer entre l'apprentissage avec le paramètre d'exécution de dqn.py et le chargement d'un modèle existant, mais comme je veux jouer plus rapidement, je vais sauter le processus d'apprentissage en définissant le nombre d'épisodes d'apprentissage sur 0 comme suit.
dqn.py
#Nombre de jeux d'apprentissage
n_episodes = 0
Ensuite, une fois le processus de formation terminé, ajoutez le code suivant pour charger le modèle.
dqn.py
print("Training finished.")
agent_p1.load("result_20000") #← Ajouter ceci
Lorsque vous êtes prêt, il est temps de jouer!
Training finished.
| |
-----------
| |
-----------
| |
| |
-----------
| |
-----------
○ | |
Please enter 1-9: 1
× | |
-----------
| |
-----------
○ | |
× | |
-----------
| |
-----------
○ | | ○
Please enter 1-9: 8
Tu peux jouer! Yay! !!
Merci de me familiariser avec DQN et Python. Je suis très heureux qu'il ait grandi au point qu'il puisse presque certainement frapper la pierre sans apprendre les règles. De plus, c'est beaucoup plus propre que d'implémenter DQN en utilisant Chainer tel quel. ChainerRL est incroyable! !! Avec de meilleures perspectives, il semble possible d'éviter de mélanger des bogues pour tenter d'améliorer diverses choses.
Je pense qu'il y a beaucoup de choses qui ne vont pas, comme "ceci devrait être fait", "l'apprentissage progresse de cette façon", et "je ne peux pas apprendre avec ça", donc j'apprécierais que vous puissiez souligner diverses choses. Merci de votre collaboration.
Ce qui me préoccupe particulièrement, c'est que la façon dont l'agent est touché semble être la même presque à chaque fois. Dois-je le rendre plus aventureux? Si vous entraînez 350 000 épisodes, il frappera comme d'habitude, donc ce sera fort, et si vous définissez ε à 0, ce sera Draw presque à chaque fois, donc c'est une bonne chose. .. De 150 000 à 200 000 épisodes, les résultats et les pertes sont devenus constants.
Pour le moment, je publierai la source entière. Si l'environnement est complet, vous pouvez le copier et le coller et le déplacer immédiatement.
dqn.py
import chainer
import chainer.functions as F
import chainer.links as L
import chainerrl
import numpy as np
#Plateau de jeu
class Board():
def reset(self):
self.board = np.array([0] * 9, dtype=np.float32)
self.winner = None
self.missed = False
self.done = False
def move(self, act, turn):
if self.board[act] == 0:
self.board[act] = turn
self.check_winner()
else:
self.winner = turn*-1
self.missed = True
self.done = True
def check_winner(self):
win_conditions = ((0,1,2),(3,4,5),(6,7,8),(0,3,6),(1,4,7),(2,5,8),(0,4,8),(2,4,6))
for cond in win_conditions:
if self.board[cond[0]] == self.board[cond[1]] == self.board[cond[2]]:
if self.board[cond[0]]!=0:
self.winner=self.board[cond[0]]
self.done = True
return
if np.count_nonzero(self.board) == 9:
self.winner = 0
self.done = True
def get_empty_pos(self):
empties = np.where(self.board==0)[0]
if len(empties) > 0:
return np.random.choice(empties)
else:
return 0
def show(self):
row = " {} | {} | {} "
hr = "\n-----------\n"
tempboard = []
for i in self.board:
if i == 1:
tempboard.append("○")
elif i == -1:
tempboard.append("×")
else:
tempboard.append(" ")
print((row + hr + row + hr + row).format(*tempboard))
#Objet de fonction aléatoire pour l'explorateur
class RandomActor:
def __init__(self, board):
self.board = board
self.random_count = 0
def random_action_func(self):
self.random_count += 1
return self.board.get_empty_pos()
#Fonction Q
class QFunction(chainer.Chain):
def __init__(self, obs_size, n_actions, n_hidden_channels=81):
super().__init__(
l0=L.Linear(obs_size, n_hidden_channels),
l1=L.Linear(n_hidden_channels, n_hidden_channels),
l2=L.Linear(n_hidden_channels, n_hidden_channels),
l3=L.Linear(n_hidden_channels, n_actions))
def __call__(self, x, test=False):
#-Parce qu'il traite de 1, qui fuit_relu
h = F.leaky_relu(self.l0(x))
h = F.leaky_relu(self.l1(h))
h = F.leaky_relu(self.l2(h))
return chainerrl.action_value.DiscreteActionValue(self.l3(h))
#Préparation du conseil
b = Board()
#Préparation d'un objet de fonction aléatoire pour l'explorateur
ra = RandomActor(b)
#Nombre de dimensions de l'environnement et du comportement
obs_size = 9
n_actions = 9
# Q-configuration des fonctions et de l'optimiseur
q_func = QFunction(obs_size, n_actions)
optimizer = chainer.optimizers.Adam(eps=1e-2)
optimizer.setup(q_func)
#Taux d'actualisation de la récompense
gamma = 0.95
# Epsilon-Aventure occasionnelle avec gourmand. Terminer en 50000 étapes_devenir epsilon
explorer = chainerrl.explorers.LinearDecayEpsilonGreedy(
start_epsilon=1.0, end_epsilon=0.3, decay_steps=50000, random_action_func=ra.random_action_func)
#Tampon utilisé dans la méthode d'apprentissage utilisée dans DQN appelée Experience Replay
replay_buffer = chainerrl.replay_buffer.ReplayBuffer(capacity=10 ** 6)
#Génération d'agent (replay)_Deux tampons de partage, etc.)
agent_p1 = chainerrl.agents.DoubleDQN(
q_func, optimizer, replay_buffer, gamma, explorer,
replay_start_size=500, update_frequency=1,
target_update_frequency=100)
agent_p2 = chainerrl.agents.DoubleDQN(
q_func, optimizer, replay_buffer, gamma, explorer,
replay_start_size=500, update_frequency=1,
target_update_frequency=100)
#Nombre de jeux d'apprentissage
n_episodes = 20000
#Déclaration de compteur
miss = 0
win = 0
draw = 0
#Épisodes répétés
for i in range(1, n_episodes + 1):
b.reset()
reward = 0
agents = [agent_p1, agent_p2]
turn = np.random.choice([0, 1])
last_state = None
while not b.done:
#Acquisition de masse de placement
action = agents[turn].act_and_train(b.board.copy(), reward)
#Effectuer le placement
b.move(action, 1)
#À la suite du placement, à la fin, définissez des valeurs dans la récompense et le compteur et apprenez
if b.done == True:
if b.winner == 1:
reward = 1
win += 1
elif b.winner == 0:
draw += 1
else:
reward = -1
if b.missed is True:
miss += 1
#Apprenez en mettant fin à l'épisode
agents[turn].stop_episode_and_train(b.board.copy(), reward, True)
#L'autre partie termine également l'épisode et apprend. N'apprends pas les erreurs de ton adversaire comme une victoire
if agents[1 if turn == 0 else 0].last_state is not None and b.missed is False:
#Dernière sauvegarde au tour précédent_Passer l'état comme état après l'exécution de l'action
agents[1 if turn == 0 else 0].stop_episode_and_train(last_state, reward*-1, True)
else:
#Enregistrer le dernier état du tour pour l'apprentissage
last_state = b.board.copy()
#Inverser la valeur sur la carte lors de la poursuite
b.board = b.board * -1
#Changer de tour
turn = 1 if turn == 0 else 0
#Affichage de la progression sur la console
if i % 100 == 0:
print("episode:", i, " / rnd:", ra.random_count, " / miss:", miss, " / win:", win, " / draw:", draw, " / statistics:", agent_p1.get_statistics(), " / epsilon:", agent_p1.explorer.epsilon)
#Initialisation du compteur
miss = 0
win = 0
draw = 0
ra.random_count = 0
if i % 10000 == 0:
#Enregistrer le modèle tous les 10000 épisodes
agent_p1.save("result_" + str(i))
print("Training finished.")
#Joueur humain
class HumanPlayer:
def act(self, board):
valid = False
while not valid:
try:
act = input("Please enter 1-9: ")
act = int(act)
if act >= 1 and act <= 9 and board[act-1] == 0:
valid = True
return act-1
else:
print("Invalid move")
except Exception as e:
print(act + " is invalid")
#Vérification
human_player = HumanPlayer()
for i in range(10):
b.reset()
dqn_first = np.random.choice([True, False])
while not b.done:
#DQN
if dqn_first or np.count_nonzero(b.board) > 0:
b.show()
action = agent_p1.act(b.board.copy())
b.move(action, 1)
if b.done == True:
if b.winner == 1:
print("DQN Win")
elif b.winner == 0:
print("Draw")
else:
print("DQN Missed")
agent_p1.stop_episode()
continue
#Humain
b.show()
action = human_player.act(b.board.copy())
b.move(action, -1)
if b.done == True:
if b.winner == -1:
print("HUMAN Win")
elif b.winner == 0:
print("Draw")
agent_p1.stop_episode()
print("Test finished.")
Recommended Posts