Hier werden die Unterschiede zwischen zwei Versionen gezeigt.
Nächste Überarbeitung | Vorhergehende Überarbeitung | ||
some:06.02.2020 [2020/03/27 16:12] benbaute angelegt |
some:06.02.2020 [2020/03/27 16:53] (aktuell) benbaute |
||
---|---|---|---|
Zeile 1: | Zeile 1: | ||
===Protokoll=== | ===Protokoll=== | ||
- | Bei diesem Termin haben wir angefangen uns mit dem Vier gewinnt Spiel auseinaderzusetzen. Dabei konnten wir Teile des Codes vom Tic Tac Toe Spiel weiter verwenden, wie z.B. der Grundaufbau des Codes. Andere Methoden mussten wir überarbeiten oder ganz neu schreiben, wie z.B. die Funktion, die überprüft, ob schon jemand gewonnen hat, da ein Vier gewinnt Feld deutlich größer ist, als Tic Tac Toe und somit alles mit for-Schleifen durchzulaufen unter Umständen sehr Zeitaufwendig ist. Dabei haben wir uns überlegt die Funktion aufzuteilen und zwar in eine, die einen Array übergeben bekommt und prüft ob Vier in einer Reihe sind und in eine andere, die das Vier gewinnt Feld in Arrays aufteilt und diese der anderen Funktion übergibt. Für die erste Funktion haben wir uns folgenden Code überlegt: | + | Heute haben wir den kompletten Algorithmus eins zu eins übertragen auf unser Vier gewinnt Spiel. So funktioniert jetzt die Funktion zur Überprüfung, ob jemand gewonnen hat oder nicht auch. Dabei sind wir jedoch auf ein einfaches Problem gestoßen, was man im Vorhinein hätte bedanken sollen. Und zwar ist der Zustandsraum von Vier gewinnt sehr viel größer, als der Zustandsraum von Tic Tac Toe. So gibt es über 10 hoch 20 mögliche Zustände, (wenn man nicht zwischen legalen und nicht möglichen Zuständen unterscheidet). Somit kann nicht so einfach wie zuvor eine Tabelle benutzt werden, in der jeder möglicher Zustand steht und der zugehörige Wert. Also haben wir ein Programm geschrieben, was zwar theoretisch fertig ist, aber praktisch nicht umsetztbar. Das heißt, wir müssen uns einen anderen Weg überlegen, unsere Ai gegen uns Vier gewinnt spielen zu lassen. |
+ | Momentan sieht unser Trainingsprogramm für unsere Ai so aus: | ||
<code python> | <code python> | ||
- | def Summe(self, zeile, spalte): #muss noch mit der umgebung verknüpft werden | ||
- | for i in range(len(a)-3): | ||
- | b=a[i:i+4] | ||
- | if (sum(b)== 4 or sum(b)==-4): | ||
- | return sum(b) | ||
- | </code> | ||
- | **Protokoll für den 06.02.2020** | + | #Training AI vs AI |
- | Training Algorithmus fertig | + | import numpy as np |
- | Funktion fertig - Wissen ob 4 in einer Reihe sind | + | import itertools |
- | + | ||
- | Problem: Anzahl möglicher Zustände über 10 hoch 20 | + | |
- | D.h. das Programm läuft so noch nicht | + | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | Kommentierte Version des Codes mit dem NN: | + | |
- | + | ||
- | <code python> | + | |
import random | import random | ||
- | import gym | ||
- | import numpy as np | ||
- | from collections import deque | ||
- | from keras.models import Sequential | ||
- | from keras.layers import Dense, Dropout | ||
- | from keras.optimizers import Adam | ||
- | import keras | ||
+ | game_state = np.zeros_like(np.arange(42).reshape(6,7))#4Gewinnt Feld mit Nullen | ||
+ | players = ['Blue','Red']#Blau fängt an / Blau ist 1 und Rot ist -1 | ||
- | def ViergewinntAnfangsmatrix(): | + | def play_move(state, player, block_num): |
- | #game_state[x,0,0] greift auf das zu benutzende Feld zu (x=0/1 sind Spiele; x=2 zeigt leere Felder) | + | if(state[int((block_num-1)/6)][(block_num-1)%7] == 0): |
- | #game_state[0,x,0] greift auf die Zeile zu | + | state[int((block_num-1)/6)][(block_num-1)%7] = player |
- | #game_state[0,0,x] greift auf die Spalte zu | + | else: |
- | game_state = np.zeros_like(np.arange(42*3).reshape(3,6,7)) | + | print("Fehler")#Dürfte eigentlich beim Training nie passieren |
- | game_state[2]=1 | + | |
- | return game_state | + | def copy_game_state(state): |
+ | new_state = np.zeros_like(np.arange(42).reshape(6,7))#4Gewinnt Feld mit Nullen | ||
+ | for i in range(6): | ||
+ | for j in range(7): | ||
+ | new_state[i][j] = state[i][j] | ||
+ | return new_state | ||
def check_array(a): | def check_array(a): | ||
- | #Prüft ob in einem Array 4 in einer Reihe sind | ||
for i in range(len(a)-3): | for i in range(len(a)-3): | ||
b=sum(a[i:i+4]) | b=sum(a[i:i+4]) | ||
- | if (b== 4): return 4 | + | if (b== 4 or b==-4): return b |
- | + | ||
- | def checkgewonnen(game_state): | + | def check_current_state(game_state):#Überprüfung ob jemand Gewonnen hat oder das Spiel unentschieden ausgegangen ist |
- | #Prüft ob jemand gewonnen hat | + | |
- | Winstate = game_state[0] | + | |
- | Losestate = game_state[1] | + | |
- | Drawstate = game_state[2] | + | |
#Spalte | #Spalte | ||
for i in range(7): | for i in range(7): | ||
- | a=check_array(Winstate[:6, i:i+1].reshape(6,)) | + | a=check_array(game_state[:6, i:i+1].reshape(6,)) |
- | b=check_array(Losestate[:6, i:i+1].reshape(6,)) | + | |
if(a==4):return "Win" | if(a==4):return "Win" | ||
- | if(b==4):return "Lose" | + | if(a==-4):return "Lose" |
| | ||
#Diagonalen: | #Diagonalen: | ||
for i in range(3): | for i in range(3): | ||
- | a=check_array(np.diagonal(Winstate, i+1)) | + | a=check_array(np.diagonal(game_state, i+1)) |
- | b=check_array(np.diagonal(Losestate, i+1)) | + | |
if(a==4):return "Win" | if(a==4):return "Win" | ||
- | if(b==4):return "Lose" | + | if(a==-4):return "Lose" |
- | a=check_array(np.diagonal(Winstate, -i)) | + | a=check_array(np.diagonal(game_state, -i)) |
- | b=check_array(np.diagonal(Losestate, -i)) | + | |
if(a==4):return "Win" | if(a==4):return "Win" | ||
- | if(b==4):return "Lose" | + | if(a==-4):return "Lose" |
- | a=check_array(np.fliplr(Winstate).diagonal(i+1)) | + | a=check_array(np.fliplr(game_state).diagonal(i+1)) |
- | b=check_array(np.fliplr(Losestate).diagonal(i+1)) | + | |
if(a==4):return "Win" | if(a==4):return "Win" | ||
- | if(b==4):return "Lose" | + | if(a==-4):return "Lose" |
- | a=check_array(np.fliplr(Winstate).diagonal(-i)) | + | a=check_array(np.fliplr(game_state).diagonal(-i)) |
- | b=check_array(np.fliplr(Losestate).diagonal(-i)) | + | |
if(a==4):return "Win" | if(a==4):return "Win" | ||
- | if(b==4):return "Lose" | + | if(a==-4):return "Lose" |
| | ||
#Zeile: | #Zeile: | ||
for i in range(6): | for i in range(6): | ||
- | a=check_array(Winstate[i:i+1].reshape(7,)) | + | a=check_array(game_state[i:i+1].reshape(7,)) |
- | b=check_array(Losestate[i:i+1].reshape(7,)) | + | |
if(a==4):return "Win" | if(a==4):return "Win" | ||
- | if(b==4):return "Lose" | + | if(a==-4):return "Lose" |
# Check if draw | # Check if draw | ||
for i in range(7): | for i in range(7): | ||
- | if(Drawstate[0,i] == 1): | + | if(game_state[0,i] == 0): |
return None | return None | ||
return "Draw" | return "Draw" | ||
+ | | ||
+ | | ||
+ | # Initialize state values | ||
+ | player = [1,-1,0] | ||
+ | states_dict = {} | ||
+ | all_possible_states = [[list(i[0:7]),list(i[7:14]),list(i[14:21]),list(i[21:28]),list(i[28:35]),list(i[35:42])] for i in itertools.product(player, repeat = 42)] | ||
+ | n_states = len(all_possible_states) # 2 players, 42 spaces | ||
+ | n_actions = 42 # 42 spaces | ||
+ | state_values_for_AI_Blue = np.full((n_states),0.0) | ||
+ | state_values_for_AI_Red = np.full((n_states),0.0) | ||
+ | #print("n_states = %i \nn_actions = %i"%(n_states, n_actions)) | ||
+ | # State values for AI 'Red', (fängt nicht an) | ||
+ | for i in range(n_states): | ||
+ | states_dict[i] = all_possible_states[i] | ||
+ | winner = check_current_state(states_dict[i]) | ||
+ | if winner == 'Lose': # AI won | ||
+ | state_values_for_AI_Red[i] = 1 | ||
+ | elif winner == 'Win': # AI lost | ||
+ | state_values_for_AI_Red[i] = -1 | ||
+ | | ||
+ | # State values for AI 'Blue', (fängt an) | ||
+ | for i in range(n_states): | ||
+ | winner = check_current_state(states_dict[i]) | ||
+ | if winner == 'Lose': # AI lost | ||
+ | state_values_for_AI_Blue[i] = -1 | ||
+ | elif winner == 'Win': # AI won | ||
+ | state_values_for_AI_Blue[i] = 1 | ||
- | class Agent: | + | def update_state_value_Red(curr_state_idx, next_state_idx, learning_rate): |
- | def __init__(self, state_size, action_size): | + | new_value = state_values_for_AI_Red[curr_state_idx] + learning_rate*(state_values_for_AI_Red[next_state_idx] - state_values_for_AI_Red[curr_state_idx]) |
- | self.state_size = state_size | + | state_values_for_AI_Red[curr_state_idx] = new_value |
- | self.action_size = action_size | + | |
- | self.memory = deque(maxlen=2000) #Erinnerungen | + | def update_state_value_Blue(curr_state_idx, next_state_idx, learning_rate): |
- | self.gamma = 1.0 # discount rate | + | new_value = state_values_for_AI_Blue[curr_state_idx] + learning_rate*(state_values_for_AI_Blue[next_state_idx] - state_values_for_AI_Blue[curr_state_idx]) |
- | self.epsilon = 1.0 # exploration rate | + | state_values_for_AI_Blue[curr_state_idx] = new_value |
- | self.epsilon_min = 0.01 | + | |
- | self.epsilon_decay = 0.999 | + | |
- | self.learning_rate = 0.001 | + | |
- | self.model = self._build_model() #NN wird gebaut | + | |
- | def _build_model(self): | + | def getBestMove(state, player, epsilon): |
- | # Einfaches NN | + | ''' |
- | #Müssen weitere Layer hinzugefügt werden, da 4gewinnt komplexer ist? | + | Reinforcement Learning Algorithm |
- | model = Sequential() #Standard komplett vernetztes NN | + | ''' |
- | model.add(Dense(48, input_dim=self.state_size, activation='relu', | + | moves = [] |
- | kernel_regularizer=keras.regularizers.l2(0.00001)))#Input muss angepasst werden | + | curr_state_values = [] |
- | model.add(Dropout(0.3)) # Soll "Overfitting" vermeiden | + | empty_cells = [] |
- | model.add(Dense(24, activation='relu', kernel_regularizer=keras.regularizers.l2(0.00001)))#Hidden-Layer | + | for i in range(7): |
- | model.add(Dense(self.action_size, activation='linear'))#Output im Bezug auf mögliche Aktionen (hier 7) | + | if(state[0,i] == 0): |
- | model.compile(loss='mse', | + | a = 5 |
- | optimizer=Adam(lr=self.learning_rate)) #Erstellung | + | while(state[a,i] != 0): |
- | return model | + | a = a-1 |
+ | empty_cells.append(a*6 + (i+1)) | ||
+ | |||
+ | for empty_cell in empty_cells: | ||
+ | moves.append(empty_cell) | ||
+ | new_state = copy_game_state(state) | ||
+ | play_move(new_state, player, empty_cell) | ||
+ | next_state_idx = list(states_dict.keys())[list(states_dict.values()).index(new_state)] | ||
+ | if player == 'Blue': | ||
+ | curr_state_values.append(state_values_for_AI_Blue[next_state_idx]) | ||
+ | else: | ||
+ | curr_state_values.append(state_values_for_AI_Red[next_state_idx]) | ||
+ | |||
+ | best_move_idx = np.argmax(curr_state_values) | ||
+ | |||
+ | if np.random.uniform(0,1) <= epsilon: # Exploration | ||
+ | best_move = random.choice(empty_cells) | ||
+ | epsilon *= 0.99 | ||
+ | else: #Exploitation | ||
+ | best_move = moves[best_move_idx] | ||
- | def remember(self, state, action, reward, next_state, done):#Erinnerungsmöglichkeit | + | return best_move |
- | # merkt sich alle bisher durchlaufenen Zustände | + | |
- | self.memory.append((state, action, reward, next_state, done)) | + | |
- | + | ||
- | def act(self, state): | + | |
- | # epsilon-greedy: off-policy oder policy | + | |
- | + | ||
- | if np.random.rand() <= self.epsilon:#Zufällige Aktion | + | |
- | return random.randrange(self.action_size) | + | |
- | act_values = self.model.predict(state)#Vorhersage des NN | + | |
- | return np.argmax(act_values[0]) # returns action | + | |
- | def replay(self, batch_size): | + | # PLaying |
- | # baut den Vektor der Q-Werte aus | + | |
- | # als reward zum Zeitpunkt t + gamma*max(moegliche rewards zum Zeitpunkt t+1) | + | |
- | #prüft ob ein Zug gut war? | + | |
- | minibatch = random.sample(self.memory, batch_size) | + | |
- | states, targets_f = [], [] | + | |
- | for state, action, reward, next_state, done in minibatch: | + | |
- | target = reward #Reward-Anpassung | + | |
- | if not done: | + | |
- | target = (reward + self.gamma * | + | |
- | np.amax(self.model.predict(next_state)[0])) | + | |
- | target_f = self.model.predict(state) | + | |
- | target_f[0][action] = target | + | |
- | # Filtering out states and targets for training | + | |
- | states.append(state[0]) | + | |
- | targets_f.append(target_f[0]) | + | |
- | history = self.model.fit(np.array(states), np.array(targets_f), epochs=1, verbose=0) | + | |
- | # Keeping track of loss | + | |
- | loss = history.history['loss'][0] | + | |
- | if self.epsilon > self.epsilon_min: | + | |
- | self.epsilon *= self.epsilon_decay | + | |
- | return loss | + | |
- | #laden und speichern des NN | + | #LOAD TRAINED STATE VALUES |
- | def load(self, name): | + | state_values_for_AI_Blue = np.loadtxt('trained_state_values_Blue.txt', dtype=np.float64) |
- | self.model.load_weights(name) | + | state_values_for_AI_Red = np.loadtxt('trained_state_values_Red.txt', dtype=np.float64) |
- | def save(self, name): | + | learning_rate = 0.2 |
- | self.model.save_weights(name) | + | epsilon = 0.2 |
+ | num_iterations = 1 | ||
+ | for iteration in range(num_iterations): | ||
+ | game_state = np.zeros_like(np.arange(42).reshape(6,7))#4Gewinnt Feld mit Nullen | ||
+ | winner = None | ||
+ | current_player_idx = random.choice([0,1]) | ||
| | ||
+ | while winner == None: | ||
+ | curr_state_idx = list(states_dict.keys())[list(states_dict.values()).index(game_state)] | ||
+ | if current_player_idx == 0: # AI_Blue's turn | ||
+ | block_choice = getBestMove(game_state, players[current_player_idx], epsilon) | ||
+ | play_move(game_state ,players[current_player_idx], block_choice) | ||
+ | new_state_idx = list(states_dict.keys())[list(states_dict.values()).index(game_state)] | ||
+ | | ||
+ | else: # AI_Red's turn | ||
+ | block_choice = getBestMove(game_state, players[current_player_idx], epsilon) | ||
+ | play_move(game_state ,players[current_player_idx], block_choice) | ||
+ | new_state_idx = list(states_dict.keys())[list(states_dict.values()).index(game_state)] | ||
| | ||
+ | update_state_value_Red(curr_state_idx, new_state_idx, learning_rate) | ||
+ | update_state_value_Blue(curr_state_idx, new_state_idx, learning_rate) | ||
+ | winner = check_current_state(game_state) | ||
+ | if(winner == None): | ||
+ | current_player_idx = (current_player_idx + 1)%2 | ||
| | ||
- | # Lernen: | ||
- | # EPISODES mal durchspielen, bis der Mast umfaellt | ||
+ | # Save state values for future use | ||
+ | np.savetxt('trained_state_values_Blue.txt', state_values_for_AI_Blue, fmt = '%.6f') | ||
+ | np.savetxt('trained_state_values_Red.txt', state_values_for_AI_Red, fmt = '%.6f') | ||
+ | print('Training Complete!') | ||
+ | </code> | ||
- | EPISODES = 10 | ||
+ | Vorheriger Termin: [[some:23.01.2020]]\\ Nächster Termin: [[some:Blocktermin]] | ||
- | env = gym.make('CartPole-v1') | ||
- | #4gewinnt einfuegen | ||
- | #state_size = env.observation_space.shape[0] | ||
- | state_size = 7 #Für die 7 Möglichkeiten die es gibt einen Stein zu platzieren | ||
- | #Spielfeld? | ||
- | action_size = env.action_space.n | ||
- | #Wird 7 sein, solange keine Spalte voll ist | ||
- | |||
- | |||
- | agent = Agent(state_size, action_size) | ||
- | done = False | ||
- | batch_size = 32#Muss eventuell auch angepasst werden | ||
- | |||
- | for e in range(EPISODES): | ||
- | state = env.reset() | ||
- | #eigene Reset Funktion | ||
- | | ||
- | state = np.reshape(state, [1, state_size])#Erzeugt einen Array, der den Zustand repräsentiert | ||
- | cum_reward = 0 | ||
- | for time in range(500):#Zeit braucht man nicht bei 4 gewinnt, ersetzen mit max Spiellänge | ||
- | #env.render() | ||
- | action = agent.act(state)#Auswahl der möglichen Aktionen | ||
- | next_state, reward, done, _ = env.step(action)#Ausführung dieser Aktion | ||
- | reward = reward/(abs(next_state[0])+1.)**2 if not done else -10 #Anpssung des rewards-System | ||
- | cum_reward += reward | ||
- | next_state = np.reshape(next_state, [1, state_size])#nächsten Zustand herbei führen | ||
- | agent.remember(state, action, reward, next_state, done)#merken | ||
- | state = next_state | ||
- | if done: | ||
- | print(("episode: {}/{}, score: {}, e: {:.2}"#Score sollte nicht abhängig von Anzahl Züge, sondern von gewonenen/verloren sein | ||
- | .format(e, EPISODES, time, agent.epsilon))) | ||
- | break | ||
- | if len(agent.memory) > batch_size: | ||
- | loss = agent.replay(batch_size) | ||
- | # Logging training loss and actual reward every 10 timesteps | ||
- | if time % 10 == 0: | ||
- | print(("episode: {}/{}, time: {}, cumulative reward: {:.4f}, loss: {:.4f}".format(e, EPISODES, time, cum_reward, loss))) | ||
- | |||
- | agent.save("qlearning_cartpole.weights") | ||
- | |||
- | |||
- | # Testen des gelernten Modells mit leichten Störungen, um die Stabilität zu sehen. Mit Visualisierung | ||
- | #Funktioniert hier nicht so richtig | ||
- | |||
- | #2 Ai's gegeneinander spielen lassen? | ||
- | |||
- | agent.load("qlearning_cartpole.weights")#gemerkte Entscheidungen laden, um damit gute Züge machen zu können | ||
- | |||
- | import time as ti #Zeit braucht man nicht | ||
- | for e in range(1000):#Von hier | ||
- | state = env.reset() | ||
- | state[0] = state[0] + np.random.randn()*0.1 | ||
- | state[1] = state[1] + np.random.randn()*0.1 | ||
- | state[2] = state[2] + np.random.randn()*0.1 | ||
- | state[3] = state[3] + np.random.randn()*0.1 | ||
- | env.env.state = state | ||
- | state = np.reshape(state, [1, state_size]) | ||
- | for time in range(2000):#bis hier muss komplett ersetzt werden | ||
- | | ||
- | env.render()#Nicht Zeit abhängig, braucht man also nicht | ||
- | agent.epsilon = 0# Setzt Zufallshandlung auf 0 | ||
- | action = agent.act(state) | ||
- | next_state, reward, done, _ = env.step(action) | ||
- | next_state = np.reshape(next_state, [1, state_size]) | ||
- | state = next_state#Spielt einen Zug durch | ||
- | if done: | ||
- | print("Duration: ", time) | ||
- | break | ||
- | | ||
- | else: | ||
- | print("Volle Zeit")#Hier wichtig ob Gewonnen/Verloren/Unentschieden? | ||
- | </code> |