From a2b56742da7b30afa00f33c9a806fa6031be68a5 Mon Sep 17 00:00:00 2001 From: bd-912 Date: Sun, 12 Nov 2023 20:10:57 -0700 Subject: Added initial files --- GameEngine/GoalCollection.py | 33 ++ GameEngine/PlayersCollection.py | 158 ++++++++ GameEngine/__init__.py | 0 GameEngine/multiplayer.py | 170 ++++++++ QNetwork/__init__.py | 0 QNetwork/neuralnetwork_regression.py | 332 ++++++++++++++++ QNetwork/optimizers.py | 116 ++++++ QTable/__init__.py | 0 QTable/qtsnake.py | 102 +++++ inferior_qt.npy | Bin 0 -> 384 bytes revised_snake_q_network.ipynb | 591 ++++++++++++++++++++++++++++ revised_snake_q_table.ipynb | 743 +++++++++++++++++++++++++++++++++++ revised_snake_q_table_noise.ipynb | 57 +++ superior_qt.npy | Bin 0 -> 384 bytes 14 files changed, 2302 insertions(+) create mode 100644 GameEngine/GoalCollection.py create mode 100644 GameEngine/PlayersCollection.py create mode 100644 GameEngine/__init__.py create mode 100644 GameEngine/multiplayer.py create mode 100644 QNetwork/__init__.py create mode 100644 QNetwork/neuralnetwork_regression.py create mode 100644 QNetwork/optimizers.py create mode 100644 QTable/__init__.py create mode 100755 QTable/qtsnake.py create mode 100644 inferior_qt.npy create mode 100644 revised_snake_q_network.ipynb create mode 100644 revised_snake_q_table.ipynb create mode 100644 revised_snake_q_table_noise.ipynb create mode 100644 superior_qt.npy diff --git a/GameEngine/GoalCollection.py b/GameEngine/GoalCollection.py new file mode 100644 index 0000000..17e9721 --- /dev/null +++ b/GameEngine/GoalCollection.py @@ -0,0 +1,33 @@ +#+AUTHOR: bdunahu +#+TITLE: multiplayer.py +#+DESCRIPTION: goal object for multiagent snake + +import pygame as pg +from random import randint +from collections import namedtuple + +Point = namedtuple('Point', 'x, y') +GREEN = (0,128,43) + +class Goal(): + def __init__(self, display, window_width=640, window_height=480, game_units=40): + ''' create initial location ''' + self.location = None + + self.display = display + self.window_width = window_width + self.window_height = window_height + self.game_units = game_units + + def reset(self, hazards=[]): + ''' generate new coordinates for goal ''' + x = randint(0, (self.window_width-self.game_units )//self.game_units )*self.game_units + y = randint(0, (self.window_height-self.game_units )//self.game_units )*self.game_units + self.location = Point(x, y) + if self.location in hazards: + self.reset(hazards) + + def draw(self): + ''' draw rectangle directly on field ''' + pg.draw.rect(self.display, GREEN, pg.Rect(self.location.x, self.location.y, + self.game_units, self.game_units)) diff --git a/GameEngine/PlayersCollection.py b/GameEngine/PlayersCollection.py new file mode 100644 index 0000000..10fa990 --- /dev/null +++ b/GameEngine/PlayersCollection.py @@ -0,0 +1,158 @@ +#+AUTHOR: bdunahu +#+TITLE: PlayersCollection.py +#+DESCRIPTION: methods for handling and querying snake objects + +import pygame as pg +from random import randint +from collections import namedtuple +from enum import Enum + +class Direction(Enum): + UP = 0 + RIGHT = 1 + DOWN = 2 + LEFT = 3 + +Point = namedtuple('Point', 'x, y') + +YELLOW = (255,255,0) +RED = (255,0,0) +PURPLE = (204,51,255) +WHITE = (255,255,255) +PLAYER_COLOR = [YELLOW, RED, PURPLE, WHITE] + +WINDOW_WIDTH = 640 +WINDOW_HEIGHT = 480 +GAME_UNITS = 40 +DISPLAY = None + +class Players(): + def __init__(self, snake_size, num_players, display, window_width=640, window_height=480, game_units=40): + ''' define array list of new Snake objects ''' + global WINDOW_WIDTH + global WINDOW_HEIGHT + global GAME_UNITS + global DISPLAY + + WINDOW_WIDTH = window_width + WINDOW_HEIGHT = window_height + GAME_UNITS = game_units + DISPLAY = display + + self._index = 0 + self.num_players = num_players + + self.players = [Snake(snake_size, player_id) + for player_id in range(num_players)] + + def __iter__(self): + return iter(self.players) + + def __getitem__(self, index): + return self.players[index] + + def full_reset(self): + ''' reset every snake position ''' + # map(lambda player:player.reset(), self.players) + for player in self.players: + player.reset() + + def move_all(self): + ''' move all snakes ''' + # map(lambda player:player.move(), self.players) + for player in self.players: + player.move() + + def reward_killer(self, player): + ''' split play length up against killing snakes ''' + killer = self._point_lookup(player.head) + if not killer == None and not killer == player: + rewards = player.score.curr_score + player.size + killer.deficit += rewards + killer.score.curr_score += rewards + killer.score.total_score += rewards + + def _point_lookup(self, head): + for player in self.players: + if head in player.snake: + return player + return None + + def draw(self): + ''' draw all snakes ''' + # map(lambda player:player.draw(), self.players) + for player in self.players: + player.draw() + +class Snake(): + def __init__(self, initial_size, player_id): + ''' define initial size (length), direction, and position ''' + self.player_id = player_id + self.size = initial_size + self.direction = None + self.head = None + self.snake = [] + self.score = Score(self.player_id) + self.deficit = 0 # for how many moves does this snake need to grow? + + def reset(self): + self.score.reset() + self.deficit = 0 + self.direction = Direction.RIGHT.value + x = randint(0, (WINDOW_WIDTH-GAME_UNITS )//GAME_UNITS )*GAME_UNITS + y = randint(0, (WINDOW_HEIGHT-GAME_UNITS )//GAME_UNITS )*GAME_UNITS + self.head = Point(x,y) + self.snake = [self.head] + for seg in range(self.size-1): + self.snake.append(Point(self.head.x-(seg*GAME_UNITS), self.head.y)) + + def move(self): + ''' update snake coordinates by inserting new head ''' + x = self.head.x + y = self.head.y + if self.direction == Direction.RIGHT.value: + x += GAME_UNITS + if self.direction == Direction.LEFT.value: + x -= GAME_UNITS + if self.direction == Direction.DOWN.value: + y += GAME_UNITS + if self.direction == Direction.UP.value: + y -= GAME_UNITS + + self.head = Point(x, y) + self.snake.insert(0,self.head) + + def in_wall(self): + return True if (self.head.x > WINDOW_WIDTH - GAME_UNITS or + self.head.x < 0 or + self.head.y > WINDOW_HEIGHT - GAME_UNITS or + self.head.y < 0) else False + + def draw(self): + ''' draw rectangle(s) directly on field ''' + for seg in self.snake: # see explanation in engine.org + pg.draw.rect(DISPLAY, PLAYER_COLOR[self.player_id], pg.Rect(seg.x, seg.y, + GAME_UNITS, GAME_UNITS)) + self.score.draw() + +class Score(): + def __init__(self, player_id): + ''' initialize score counter ''' + self.player_id = player_id + self.font = pg.font.SysFont("monospace", 16) + self.curr_score = 0 + self.total_score = 0 + self.deaths = 0 + self.kills = 0 + + def scored(self): + self.curr_score += 1 + self.total_score += 1 + + def reset(self): + self.curr_score = 0 + + def draw(self): + ''' draw score on top left ''' + score_surf = self.font.render(f'Current: {self.curr_score} Total: {self.total_score}', True, PLAYER_COLOR[self.player_id]) + DISPLAY.blit(score_surf, (0, 0+24*self.player_id)) diff --git a/GameEngine/__init__.py b/GameEngine/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/GameEngine/multiplayer.py b/GameEngine/multiplayer.py new file mode 100644 index 0000000..9f6b651 --- /dev/null +++ b/GameEngine/multiplayer.py @@ -0,0 +1,170 @@ +#+AUTHOR: bdunahu +#+TITLE: multiplayer.py +#+DESCRIPTION game engine for multiagent snake + +from enum import Enum +import pygame as pg +import numpy as np + +from GameEngine import PlayersCollection +from GameEngine import GoalCollection + +BLACK = (0, 0, 0) + +class CollisionType(Enum): # each of these means different outcome for model + DEATH = 0 + GOAL = 1 + NONE = 2 + + +class Playfield: + def __init__(self, window_width=640, window_height=480, units=40, g_speed=25, s_size=3): + ''' initialize pygame modules, snake, goal, and score objects ''' + global DISPLAY + + self._g_speed = g_speed # game speed + self._s_size = s_size # initial snake size + self._units = units + self._window_width = window_width + self._window_height = window_height + self._player_count = -1 # number of registered players + ''' for human feedback ''' + self._game_state = False # false is game over + self._draw_on = True + self._clock = pg.time.Clock() + + ''' objects ''' + pg.init() + self.display = pg.display.set_mode( + [self._window_width, self._window_height], + pg.HWSURFACE) # display object (see explanation in engine.org) + self._players = None + self._goal = None + + def add_player(self): + ''' + Returns the player's number to the callee. + If the player count is over four, returns None + ''' + if self._player_count < 4 or self._game_state == True: + self._player_count += 1 + return self._player_count + return None + + def get_heads_tails_and_goal(self): + ''' + Returns an array of heads, an array of tail positions, + and the goal position + ''' + heads = [] + for player in self._players.players: + heads.append(player.head) + return heads, self._get_player_bodies(), self._goal.location + + def get_viable_actions(self, player_id): + ''' + Given a player's id, + returns a list of actions that does + not result in immediate death + ''' + head = self._players.players[player_id].head + tail = self._players.players[player_id].snake + danger_array = np.array([ + head.y-self._units < 0 or PlayersCollection.Point(head.x, head.y-self._units) in tail[1:], # up + head.x+self._units >= self._window_width or PlayersCollection.Point(head.x+self._units, head.y) in tail[1:], # right + head.y+self._units >= self._window_height or PlayersCollection.Point(head.x, head.y+self._units) in tail[1:], # down + head.x-self._units < 0 or PlayersCollection.Point(head.x-self._units, head.y) in tail[1:], # left + ]) + + return np.where(danger_array == False)[0] + + def start_game(self): + ''' + Initializes player objects, starts the game + ''' + self._players = PlayersCollection.Players(self._s_size, self._player_count+1, self.display, game_units=self._units) + self._goal = GoalCollection.Goal(self.display, game_units=self._units) + self._reset() + print(f'Game starting with {self._player_count+1} players.') + + def stop_game(self): + ''' + Restarts the game, allows adding/removing players + ''' + self._game_state = False + print(f'Game over!') + + def cleanup(self): + ''' end game session ''' + pg.quit() + + def player_advance(self, actions, noise=0.0): + ''' given a list of snake actions ''' + ''' return a list of results, and ''' + ''' update game state ''' + + for player_id, action in enumerate(actions): + if np.random.uniform < noise: + # random action (noise) + random_choice = [0, 1, 2, 3] + random_choice.remove(self._players[player_id].direction) + self._players[player_id].direction = np.random.choice(random_choice) + else: + self._players[player_id].direction = action + + self._players.move_all() + collisions = self._check_player_collisions() + if self._draw_on: + self._update_ui() + return collisions + + def toggle_draw(self): + ''' turns off and on UI ''' + self._draw_on = not self._draw_on + print(f'Draw is now {self._draw_on}.') + + def _check_player_collisions(self): + results = [] + for player in self._players: + ''' determine what obstacle was hit ''' + hazards = self._get_player_bodies() + hazards.remove(player.head) + if (player.head in hazards or player.in_wall()): + self._players.reward_killer(player) + player.reset() + results.append(CollisionType.DEATH) + elif player.head == self._goal.location: + player.score.curr_score += 1 + player.score.total_score += 1 + self._goal.reset(hazards) + results.append(CollisionType.GOAL) + else: + if player.deficit <= 0: + player.snake.pop() + else: + player.deficit -= 1 + results.append(CollisionType.NONE) + return results + + def _get_player_bodies(self): + ''' return an array of all tail coordinates ''' + tails = [0] + for player in self._players: + tails += player.snake + return tails + + def _update_ui(self): + ''' flush new positions to screen ''' + self.display.fill(BLACK) + self._players.draw() + self._goal.draw() + + pg.display.flip() # full screen update + self._clock.tick(self._g_speed) + + def _reset(self): + ''' reset game state ''' + ''' required by training ''' + self._game_state = True # false is game over + self._goal.reset() + self._players.full_reset() diff --git a/QNetwork/__init__.py b/QNetwork/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/QNetwork/neuralnetwork_regression.py b/QNetwork/neuralnetwork_regression.py new file mode 100644 index 0000000..b26be5e --- /dev/null +++ b/QNetwork/neuralnetwork_regression.py @@ -0,0 +1,332 @@ +import numpy as np +from QNetwork import optimizers +import sys # for sys.float_info.epsilon +import matplotlib.pyplot as plt +import matplotlib.patches as pltpatch # for Arc +import matplotlib.collections as pltcoll +import math + +###################################################################### +## class NeuralNetwork() +###################################################################### + +class NeuralNetwork(): + + + def __init__(self, n_inputs, n_hiddens_per_layer, n_outputs, activation_function='tanh'): + self.n_inputs = n_inputs + self.n_outputs = n_outputs + self.activation_function = activation_function + + # Set self.n_hiddens_per_layer to [] if argument is 0, [], or [0] + if n_hiddens_per_layer == 0 or n_hiddens_per_layer == [] or n_hiddens_per_layer == [0]: + self.n_hiddens_per_layer = [] + else: + self.n_hiddens_per_layer = n_hiddens_per_layer + + # Initialize weights, by first building list of all weight matrix shapes. + n_in = n_inputs + shapes = [] + for nh in self.n_hiddens_per_layer: + shapes.append((n_in + 1, nh)) + n_in = nh + shapes.append((n_in + 1, n_outputs)) + + # self.all_weights: vector of all weights + # self.Ws: list of weight matrices by layer + self.all_weights, self.Ws = self.make_weights_and_views(shapes) + + # Define arrays to hold gradient values. + # One array for each W array with same shape. + self.all_gradients, self.dE_dWs = self.make_weights_and_views(shapes) + + self.trained = False + self.total_epochs = 0 + self.error_trace = [] + self.Xmeans = None + self.Xstds = None + self.Tmeans = None + self.Tstds = None + + def setup_standardization(self, Xmeans, Xstds, Tmeans, Tstds): + self.Xmeans = np.array(Xmeans) + self.Xstds = np.array(Xstds) + self.Tmeans = np.array(Tmeans) + self.Tstds = np.array(Tstds) + + def make_weights_and_views(self, shapes): + # vector of all weights built by horizontally stacking flatenned matrices + # for each layer initialized with uniformly-distributed values. + all_weights = np.hstack([np.random.uniform(-1, 1, size=shape).flat / np.sqrt(shape[0]) + for shape in shapes]) + # Build list of views by reshaping corresponding elements from vector of all weights + # into correct shape for each layer. + views = [] + start = 0 + for shape in shapes: + size =shape[0] * shape[1] + views.append(all_weights[start:start + size].reshape(shape)) + start += size + return all_weights, views + + + # Return string that shows how the constructor was called + def __repr__(self): + return f'{type(self).__name__}({self.n_inputs}, {self.n_hiddens_per_layer}, {self.n_outputs}, \'{self.activation_function}\')' + + + # Return string that is more informative to the user about the state of this neural network. + def __str__(self): + result = self.__repr__() + if len(self.error_trace) > 0: + return self.__repr__() + f' trained for {len(self.error_trace)} epochs, final training error {self.error_trace[-1]:.4f}' + + + def train(self, X, T, n_epochs, learning_rate, method='sgd', verbose=True): + ''' +train: + X: n_samples x n_inputs matrix of input samples, one per row + T: n_samples x n_outputs matrix of target output values, one sample per row + n_epochs: number of passes to take through all samples updating weights each pass + learning_rate: factor controlling the step size of each update + method: is either 'sgd' or 'adam' + ''' + + # Setup standardization parameters + if self.Xmeans is None: + self.Xmeans = X.mean(axis=0) + self.Xstds = X.std(axis=0) + self.Xstds[self.Xstds == 0] = 1 # So we don't divide by zero when standardizing + self.Tmeans = T.mean(axis=0) + self.Tstds = T.std(axis=0) + + # Standardize X and T + X = (X - self.Xmeans) / self.Xstds + T = (T - self.Tmeans) / self.Tstds + + # Instantiate Optimizers object by giving it vector of all weights + optimizer = optimizers.Optimizers(self.all_weights) + + # Define function to convert value from error_f into error in original T units, + # but only if the network has a single output. Multiplying by self.Tstds for + # multiple outputs does not correctly unstandardize the error. + if len(self.Tstds) == 1: + error_convert_f = lambda err: (np.sqrt(err) * self.Tstds)[0] # to scalar + else: + error_convert_f = lambda err: np.sqrt(err)[0] # to scalar + + + if method == 'sgd': + + error_trace = optimizer.sgd(self.error_f, self.gradient_f, + fargs=[X, T], n_epochs=n_epochs, + learning_rate=learning_rate, + verbose=verbose, + error_convert_f=error_convert_f) + + elif method == 'adam': + + error_trace = optimizer.adam(self.error_f, self.gradient_f, + fargs=[X, T], n_epochs=n_epochs, + learning_rate=learning_rate, + verbose=verbose, + error_convert_f=error_convert_f) + + else: + raise Exception("method must be 'sgd' or 'adam'") + + self.error_trace = error_trace + + # Return neural network object to allow applying other methods after training. + # Example: Y = nnet.train(X, T, 100, 0.01).use(X) + return self + + def relu(self, s): + s[s < 0] = 0 + return s + + def grad_relu(self, s): + return (s > 0).astype(int) + + def forward_pass(self, X): + '''X assumed already standardized. Output returned as standardized.''' + self.Ys = [X] + for W in self.Ws[:-1]: + if self.activation_function == 'relu': + self.Ys.append(self.relu(self.Ys[-1] @ W[1:, :] + W[0:1, :])) + else: + self.Ys.append(np.tanh(self.Ys[-1] @ W[1:, :] + W[0:1, :])) + last_W = self.Ws[-1] + self.Ys.append(self.Ys[-1] @ last_W[1:, :] + last_W[0:1, :]) + return self.Ys + + # Function to be minimized by optimizer method, mean squared error + def error_f(self, X, T): + Ys = self.forward_pass(X) + mean_sq_error = np.mean((T - Ys[-1]) ** 2) + return mean_sq_error + + # Gradient of function to be minimized for use by optimizer method + def gradient_f(self, X, T): + '''Assumes forward_pass just called with layer outputs in self.Ys.''' + error = T - self.Ys[-1] + n_samples = X.shape[0] + n_outputs = T.shape[1] + delta = - error / (n_samples * n_outputs) + n_layers = len(self.n_hiddens_per_layer) + 1 + # Step backwards through the layers to back-propagate the error (delta) + for layeri in range(n_layers - 1, -1, -1): + # gradient of all but bias weights + self.dE_dWs[layeri][1:, :] = self.Ys[layeri].T @ delta + # gradient of just the bias weights + self.dE_dWs[layeri][0:1, :] = np.sum(delta, 0) + # Back-propagate this layer's delta to previous layer + if self.activation_function == 'relu': + delta = delta @ self.Ws[layeri][1:, :].T * self.grad_relu(self.Ys[layeri]) + else: + delta = delta @ self.Ws[layeri][1:, :].T * (1 - self.Ys[layeri] ** 2) + return self.all_gradients + + def use(self, X): + '''X assumed to not be standardized''' + # Standardize X + X = (X - self.Xmeans) / self.Xstds + Ys = self.forward_pass(X) + Y = Ys[-1] + # Unstandardize output Y before returning it + return Y * self.Tstds + self.Tmeans + + def draw(self, input_names=None, output_names=None, scale='by layer', gray=False): + plt.title('{} weights'.format(sum([Wi.size for Wi in self.Ws]))) + + def isOdd(x): + return x % 2 != 0 + + n_layers = len(self.Ws) + + Wmax_overall = np.max(np.abs(np.hstack([w.reshape(-1) for w in self.Ws]))) + + # calculate xlim and ylim for whole network plot + # Assume 4 characters fit between each wire + # -0.5 is to leave 0.5 spacing before first wire + xlim = max(map(len, input_names)) / 4.0 if input_names else 1 + ylim = 0 + + for li in range(n_layers): + ni, no = self.Ws[li].shape #no means number outputs this layer + if not isOdd(li): + ylim += ni + 0.5 + else: + xlim += ni + 0.5 + + ni, no = self.Ws[n_layers-1].shape #no means number outputs this layer + if isOdd(n_layers): + xlim += no + 0.5 + else: + ylim += no + 0.5 + + # Add space for output names + if output_names: + if isOdd(n_layers): + ylim += 0.25 + else: + xlim += round(max(map(len, output_names)) / 4.0) + + ax = plt.gca() + + # changes from Jim Jazwiecki (jim.jazwiecki@gmail.com) CS480 student + character_width_factor = 0.07 + padding = 2 + if input_names: + x0 = max([1, max(map(len, input_names)) * (character_width_factor * 3.5)]) + else: + x0 = 1 + y0 = 0 # to allow for constant input to first layer + # First Layer + if input_names: + y = 0.55 + for n in input_names: + y += 1 + ax.text(x0 - (character_width_factor * padding), y, n, horizontalalignment="right", fontsize=20) + + patches = [] + for li in range(n_layers): + thisW = self.Ws[li] + if scale == 'by layer': + maxW = np.max(np.abs(thisW)) + else: + maxW = Wmax_overall + ni, no = thisW.shape + if not isOdd(li): + # Even layer index. Vertical layer. Origin is upper left. + # Constant input + ax.text(x0 - 0.2, y0 + 0.5, '1', fontsize=20) + for i in range(ni): + ax.plot((x0, x0 + no - 0.5), (y0 + i + 0.5, y0 + i + 0.5), color='gray') + # output lines + for i in range(no): + ax.plot((x0 + 1 + i - 0.5, x0 + 1 + i - 0.5), (y0, y0 + ni + 1), color='gray') + # cell "bodies" + xs = x0 + np.arange(no) + 0.5 + ys = np.array([y0 + ni + 0.5] * no) + for x, y in zip(xs, ys): + patches.append(pltpatch.RegularPolygon((x, y - 0.4), 3, 0.3, 0, color ='#555555')) + # weights + if gray: + colors = np.array(['black', 'gray'])[(thisW.flat >= 0) + 0] + else: + colors = np.array(['red', 'green'])[(thisW.flat >= 0) + 0] + xs = np.arange(no) + x0 + 0.5 + ys = np.arange(ni) + y0 + 0.5 + coords = np.meshgrid(xs, ys) + for x, y, w, c in zip(coords[0].flat, coords[1].flat, + np.abs(thisW / maxW).flat, colors): + patches.append(pltpatch.Rectangle((x - w / 2, y - w / 2), w, w, color=c)) + y0 += ni + 1 + x0 += -1 ## shift for next layer's constant input + else: + # Odd layer index. Horizontal layer. Origin is upper left. + # Constant input + ax.text(x0 + 0.5, y0 - 0.2, '1', fontsize=20) + # input lines + for i in range(ni): + ax.plot((x0 + i + 0.5, x0 + i + 0.5), (y0, y0 + no - 0.5), color='gray') + # output lines + for i in range(no): + ax.plot((x0, x0 + ni + 1), (y0 + i+ 0.5, y0 + i + 0.5), color='gray') + # cell 'bodies' + xs = np.array([x0 + ni + 0.5] * no) + ys = y0 + 0.5 + np.arange(no) + for x, y in zip(xs, ys): + patches.append(pltpatch.RegularPolygon((x - 0.4, y), 3, 0.3, -math.pi / 2, color ='#555555')) + # weights + if gray: + colors = np.array(['black', 'gray'])[(thisW.flat >= 0) + 0] + else: + colors = np.array(['red', 'green'])[(thisW.flat >= 0) + 0] + xs = np.arange(ni) + x0 + 0.5 + ys = np.arange(no) + y0 + 0.5 + coords = np.meshgrid(xs, ys) + for x, y, w, c in zip(coords[0].flat, coords[1].flat, + np.abs(thisW / maxW).flat, colors): + patches.append(pltpatch.Rectangle((x - w / 2, y - w / 2), w, w, color=c)) + x0 += ni + 1 + y0 -= 1 ##shift to allow for next layer's constant input + + collection = pltcoll.PatchCollection(patches, match_original=True) + ax.add_collection(collection) + + # Last layer output labels + if output_names: + if isOdd(n_layers): + x = x0 + 1.5 + for n in output_names: + x += 1 + ax.text(x, y0 + 0.5, n, fontsize=20) + else: + y = y0 + 0.6 + for n in output_names: + y += 1 + ax.text(x0 + 0.2, y, n, fontsize=20) + ax.axis([0, xlim, ylim, 0]) + ax.axis('off') diff --git a/QNetwork/optimizers.py b/QNetwork/optimizers.py new file mode 100644 index 0000000..7d28f92 --- /dev/null +++ b/QNetwork/optimizers.py @@ -0,0 +1,116 @@ +import numpy as np + +###################################################################### +## class Optimizers() +###################################################################### + +class Optimizers(): + + def __init__(self, all_weights): + '''all_weights is a vector of all of a neural networks weights concatenated into a one-dimensional vector''' + + self.all_weights = all_weights + + # The following initializations are only used by adam. + # Only initializing m, v, beta1t and beta2t here allows multiple calls to adam to handle training + # with multiple subsets (batches) of training data. + self.mt = np.zeros_like(all_weights) + self.vt = np.zeros_like(all_weights) + self.beta1 = 0.9 + self.beta2 = 0.999 + self.beta1t = 1 + self.beta2t = 1 + + + def sgd(self, error_f, gradient_f, fargs=[], n_epochs=100, learning_rate=0.001, verbose=True, error_convert_f=None): + ''' +error_f: function that requires X and T as arguments (given in fargs) and returns mean squared error. +gradient_f: function that requires X and T as arguments (in fargs) and returns gradient of mean squared error + with respect to each weight. +error_convert_f: function that converts the standardized error from error_f to original T units. + ''' + + error_trace = [] + epochs_per_print = n_epochs // 10 + + for epoch in range(n_epochs): + + error = error_f(*fargs) + grad = gradient_f(*fargs) + + # Update all weights using -= to modify their values in-place. + self.all_weights -= learning_rate * grad + + if error_convert_f: + error = error_convert_f(error) + error_trace.append(error) + + if verbose and ((epoch + 1) % max(1, epochs_per_print) == 0): + print(f'sgd: Epoch {epoch+1:d} Error={error:.5f}') + + return error_trace + + def adam(self, error_f, gradient_f, fargs=[], n_epochs=100, learning_rate=0.001, verbose=True, error_convert_f=None): + ''' +error_f: function that requires X and T as arguments (given in fargs) and returns mean squared error. +gradient_f: function that requires X and T as arguments (in fargs) and returns gradient of mean squared error + with respect to each weight. +error_convert_f: function that converts the standardized error from error_f to original T units. + ''' + + alpha = learning_rate # learning rate called alpha in original paper on adam + epsilon = 1e-8 + error_trace = [] + epochs_per_print = n_epochs // 10 + + for epoch in range(n_epochs): + + error = error_f(*fargs) + grad = gradient_f(*fargs) + + self.mt[:] = self.beta1 * self.mt + (1 - self.beta1) * grad + self.vt[:] = self.beta2 * self.vt + (1 - self.beta2) * grad * grad + self.beta1t *= self.beta1 + self.beta2t *= self.beta2 + + m_hat = self.mt / (1 - self.beta1t) + v_hat = self.vt / (1 - self.beta2t) + + # Update all weights using -= to modify their values in-place. + self.all_weights -= alpha * m_hat / (np.sqrt(v_hat) + epsilon) + + if error_convert_f: + error = error_convert_f(error) + error_trace.append(error) + + if verbose and ((epoch + 1) % max(1, epochs_per_print) == 0): + print(f'Adam: Epoch {epoch+1:d} Error={error:.5f}') + + return error_trace + +if __name__ == '__main__': + + import matplotlib.pyplot as plt + plt.ion() + + def parabola(wmin): + return ((w - wmin) ** 2)[0] + + def parabola_gradient(wmin): + return 2 * (w - wmin) + + w = np.array([0.0]) + optimizer = Optimizers(w) + + wmin = 5 + optimizer.sgd(parabola, parabola_gradient, [wmin], + n_epochs=500, learning_rate=0.1) + + print(f'sgd: Minimum of parabola is at {wmin}. Value found is {w}') + + w = np.array([0.0]) + optimizer = Optimizers(w) + optimizer.adam(parabola, parabola_gradient, [wmin], + n_epochs=500, learning_rate=0.1) + + print(f'adam: Minimum of parabola is at {wmin}. Value found is {w}') diff --git a/QTable/__init__.py b/QTable/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/QTable/qtsnake.py b/QTable/qtsnake.py new file mode 100755 index 0000000..4b1eac7 --- /dev/null +++ b/QTable/qtsnake.py @@ -0,0 +1,102 @@ +#+AUTHOR: bdunahu +#+TITLE: qtsnake.py +#+DESCRIPTION qtable lookup, training, and handling for multiagent snake + +import numpy as np +from GameEngine import multiplayer +from collections import namedtuple + +WINDOW_WIDTH = None +WINDOW_HEIGHT = None +GAME_UNITS = None + +Point = namedtuple('Point', 'x, y') + + +def sense_goal(head, goal): + ''' + maps head and goal location onto an + integer corresponding to approx location + ''' + diffs = Point(goal.x - head.x, goal.y - head.y) + + if diffs.x == 0 and diffs.y < 0: + return 0 + if diffs.x > 0 and diffs.y < 0: + return 1 + if diffs.x > 0 and diffs.y == 0: + return 2 + if diffs.x > 0 and diffs.y > 0: + return 3 + if diffs.x == 0 and diffs.y > 0: + return 4 + if diffs.x < 0 and diffs.y > 0: + return 5 + if diffs.x < 0 and diffs.y == 0: + return 6 + return 7 + +def load_q(filename): + ''' loads np array from given file ''' + if not filename.endswith('.npy'): + exit(1) + return np.load(filename) + +class QSnake: + def __init__(self, game_engine): + ''' initialize fields required by model ''' + self.game_engine = game_engine + + def index_actions(self, q, pid): + ''' + given q, player_id, an array of heads, + and the goal position, + indexes into the corresponding expected + reward of each action + ''' + heads, tails, goal = self.game_engine.get_heads_tails_and_goal() + state = sense_goal(heads[pid], goal) + return state, q[state, :] + + def argmin_gen(self, rewards): + ''' + Given an array of rewards indexed by actions, + yields actions in order from most rewarding to + least rewarding + ''' + rewards = rewards.copy() + for i in range(rewards.size): + best_action = np.argmin(rewards) + rewards[best_action] = float("inf") + yield best_action + + def pick_greedy_action(self, q, pid, epsilon): + ''' + given a q table, the id of the player + taking action, and a randomization factor, + returns the most rewarding non-lethal action + or a non-lethal random action. + ''' + viable_actions = self.game_engine.get_viable_actions(pid) + state, rewards = self.index_actions(q, pid) + + if np.random.uniform() < epsilon: + return (state, np.random.choice(viable_actions)) if viable_actions.size > 0 else (state, 0) + for action in self.argmin_gen(rewards): + if action in viable_actions: + return (state, action) + return (state, 0) # death + + def update_q(self, q, old_state_action, new_state_action, outcome, lr=0.05): + ''' + given a q table, the previous state/action pair, + the new state/action pair, the outcome of the last + action, and the learning rate + updates q with the temporal difference. + ''' + if outcome == multiplayer.CollisionType.GOAL: + q[new_state_action[0], new_state_action[1]] = 0 + else: + td_error = -1 + q[new_state_action[0], new_state_action[1]] - q[old_state_action[0], old_state_action[1]] + q[old_state_action[0], old_state_action[1]] += lr * td_error + diff --git a/inferior_qt.npy b/inferior_qt.npy new file mode 100644 index 0000000..551a537 Binary files /dev/null and b/inferior_qt.npy differ diff --git a/revised_snake_q_network.ipynb b/revised_snake_q_network.ipynb new file mode 100644 index 0000000..40952d7 --- /dev/null +++ b/revised_snake_q_network.ipynb @@ -0,0 +1,591 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "73c6d255-0c32-4895-9a22-e95eadb25103", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pygame 2.5.1 (SDL 2.28.2, Python 3.11.5)\n", + "Hello from the pygame community. https://www.pygame.org/contribute.html\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from collections import namedtuple\n", + "from IPython.core.debugger import Pdb\n", + "from IPython.display import display, clear_output\n", + "\n", + "from QNetwork import neuralnetwork_regression as nn\n", + "from GameEngine import multiplayer\n", + "from QTable import qtsnake\n", + "\n", + "Point = namedtuple('Point', 'x, y')" + ] + }, + { + "cell_type": "markdown", + "id": "b3aab739-e016-4700-89c9-41f3c2f536cf", + "metadata": {}, + "source": [ + "### New Game Implementation\n", + "\n", + "I have an improved game implementation which allows for multiplayer snake games, as well as simplified training. This notebook will go over training of a simple q-network, which maps a total of 32 different combinations of states and actions onto rewards, much like the previous q-table implementation from ***revised_snake_q_table.ipynb***.\n", + "\n", + "Please read that notebook first if interested in a more complete description of the new game engine. As usual, we have some game-setup to do:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "682a7036-4f0d-4f3d-b147-6355c0a2f93e", + "metadata": {}, + "outputs": [], + "source": [ + "# defines game window size and block size, in pixels\n", + "WINDOW_WIDTH = 640\n", + "WINDOW_HEIGHT = 480\n", + "GAME_UNITS = 80" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "41cfbec9-e14e-4c58-95dd-2e3fb1788e72", + "metadata": {}, + "outputs": [], + "source": [ + "game_engine = multiplayer.Playfield(window_width=WINDOW_WIDTH,\n", + " window_height=WINDOW_HEIGHT,\n", + " units=GAME_UNITS,\n", + " g_speed=35,\n", + " s_size=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "804a13dc-7dd4-43f0-bc47-e781bc022075", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Game starting with 1 players.\n" + ] + }, + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p1 = game_engine.add_player()\n", + "game_engine.start_game()\n", + "p1" + ] + }, + { + "cell_type": "markdown", + "id": "34efdb66-7a8e-4b48-a015-d1eb8a029915", + "metadata": {}, + "source": [ + "Training thousands of steps is a little bit slow with the graphics on. It makes only a small difference here, but it provides little information anyways:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b94f16d4-65bb-4150-bdc0-6cc648e3cb7e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Draw is now False.\n" + ] + } + ], + "source": [ + "game_engine.toggle_draw()" + ] + }, + { + "cell_type": "markdown", + "id": "43cefedf-e005-4910-9b4c-953697aa3f26", + "metadata": {}, + "source": [ + "### State-sensing methods, defining reinforcement and greedy-action selector\n", + "\n", + "I have also imported the aforementioned q_table implementation as qtsnake. It will come back in the end of the notebook when I pair the q_table and q_network against each other, but to make the game fair, I'll use the exact same state-sensing method:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "71c97804-74d3-4248-bdb7-5519aa02b556", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qtsnake.sense_goal" + ] + }, + { + "cell_type": "markdown", + "id": "e065f223-9e19-4f21-ba75-8d44fc62d353", + "metadata": {}, + "source": [ + "Even though I plan to only call it when selecting a greedy_action, I'll wrap it in a neat 'query_state' function:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "26b8f8bf-ad08-40f8-847f-88351e262c1d", + "metadata": {}, + "outputs": [], + "source": [ + "def query_state(id):\n", + " '''\n", + " given a player's id,\n", + " returns their state\n", + " '''\n", + " heads, _, goal = game_engine.get_heads_tails_and_goal()\n", + " return np.array(qtsnake.sense_goal(heads[id], goal))" + ] + }, + { + "cell_type": "markdown", + "id": "7d61e508-0661-4893-a720-f0a511c52809", + "metadata": {}, + "source": [ + "And a reinforcement function. Because I took the requirement to sense danger away, we only need two outputs from the reinforcement function.\n", + "\n", + "The output of this function was chosen due to being the best-performing. It is possible the reward for GOAL should be higher or lower. In actuality, the reinforcement for non-goals will never be used. I prefer the simplicity of using the discount factor to force agents to the goal quickly." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "0af0a115-83b9-498a-8228-dc79580131f1", + "metadata": {}, + "outputs": [], + "source": [ + "def reinforcement(outcome):\n", + " '''\n", + " given an outcome of an action,\n", + " returns associated reward\n", + " '''\n", + " if outcome == multiplayer.CollisionType.GOAL:\n", + " return -3\n", + " return 0" + ] + }, + { + "cell_type": "markdown", + "id": "45e6040c-9aae-4f9e-8ef6-cf23b4043622", + "metadata": {}, + "source": [ + "Here is the first real interesting function. It takes its implementation largely from the marble example, but it accepts and returns parameters as closely to the previous q-table version.\n", + "\n", + "In essence, I ask the game the viable actions for a player, take into account our current state, and choose the action with the greatest expected reward, or a random action. This is called epsilon greedy selection.\n", + "\n", + "When calling use on the network, it maps a state and action onto a reward, just the same as indexing the q-table. We return the expected reward for this action in addition, because it is needed later for learning with discounted rewards." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "a76fd63a-478a-43ad-91ce-df1dff03e565", + "metadata": {}, + "outputs": [], + "source": [ + "def pick_greedy_action(q_net, id, epsilon):\n", + " '''\n", + " given a q network, the id of the player\n", + " taking action, and a randomization factor,\n", + " returns the most rewarding non-lethal action\n", + " or a non-lethal random action and expected reward\n", + " '''\n", + " viable_actions = game_engine.get_viable_actions(id)\n", + " state = query_state(id)\n", + "\n", + " if viable_actions.size < 1:\n", + " best_action = 0\n", + " elif np.random.uniform() < epsilon:\n", + " best_action = np.random.choice(viable_actions)\n", + " else:\n", + " qs = [q_net.use(np.hstack(\n", + " (state, action)).reshape((1, -1))) for action in viable_actions]\n", + " best_action = viable_actions[np.argmin(qs)]\n", + "\n", + " X = np.hstack((state, best_action))\n", + " q = q_net.use(X.reshape((1, -1)))\n", + "\n", + " return X, q" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "06cd085e-77f4-4a22-9b1f-ec364b7737c5", + "metadata": {}, + "outputs": [], + "source": [ + "def update_q(q, old_X, new_X, new_q, outcome, n_epochs, discount=0.9, lr=0.2):\n", + " '''\n", + " given a q network, the previous state/action pair,\n", + " the new state/action pair, the expected next reward,\n", + " the outcome of the last action, the number of epochs,\n", + " a discount factor (gamma), and the learning rate\n", + " updates q with discounted rewards.\n", + " '''\n", + " reward = reinforcement(outcome)\n", + " if outcome == multiplayer.CollisionType.GOAL:\n", + " q.train(np.array([new_X]),\n", + " np.array([reward]) + np.array([[reward]]),\n", + " n_epochs, lr, method='sgd', verbose=False)\n", + " else:\n", + " q.train(np.array([old_X]),\n", + " discount * np.array([new_q]), n_epochs,\n", + " lr, method='sgd', verbose=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "f51c3238-c918-40a5-bf38-1456f4ed4ff5", + "metadata": {}, + "outputs": [], + "source": [ + "gamma = 0.9\n", + "n_epochs = 10\n", + "learning_rate = 0.015\n", + "\n", + "hidden_layers = [15]\n", + "q = nn.NeuralNetwork(2, hidden_layers, 1)\n", + "q.setup_standardization([5, 3.5], [4, np.sqrt(5.25)], [-.1], [0.2])" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "072ef9b7-86ec-4cbf-a315-dd6b4019fce6", + "metadata": {}, + "outputs": [], + "source": [ + "n_steps = 25000\n", + "epsilon = 1\n", + "final_epsilon = 0.05\n", + "epsilon_decay = np.exp(np.log(final_epsilon) / (n_steps))\n", + "epsilon_trace = np.zeros(n_steps)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "720a04aa-b53f-42d7-adf8-7c1a0958ff04", + "metadata": {}, + "outputs": [], + "source": [ + "class Scoreboard():\n", + " ''' tracks game statistics '''\n", + " def __init__(self):\n", + " self.all_goals = 0\n", + " self._deaths = 0\n", + " self._goals = 0\n", + " self._max_goals = 0\n", + "\n", + " self.goals = []\n", + " self.deaths = []\n", + " self.max_goals = []\n", + "\n", + " def track_outcome(self, outcome):\n", + " if outcome == multiplayer.CollisionType.GOAL:\n", + " self._goals += 1\n", + " self.all_goals += 1\n", + " if self._goals > self._max_goals:\n", + " self._max_goals = self._goals\n", + " elif outcome == multiplayer.CollisionType.DEATH:\n", + " self._deaths += 1\n", + " self._goals = 0\n", + "\n", + " def flush(self):\n", + " self.goals.append(self._goals)\n", + " self.deaths.append(self._deaths)\n", + " self.max_goals.append(self._max_goals)\n", + "\n", + " self._reset()\n", + "\n", + " def _reset(self):\n", + " self._deaths = 0\n", + " self._goals = 0\n", + " self._max_goals = 0" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "c86cea77-c3b9-44fa-becd-2d04d49b92cc", + "metadata": {}, + "outputs": [], + "source": [ + "def plot_status(q, step, epsilon_trace, r_trace):\n", + " \n", + " plt.subplot(4, 3, 1)\n", + " plt.plot(epsilon_trace[:step + 1])\n", + " plt.ylabel('Random Action Probability ($\\epsilon$)')\n", + " plt.ylim(0, 1)\n", + "\n", + " plt.subplot(4, 3, 2)\n", + " plt.plot(scoreboard.deaths)\n", + " plt.ylabel('Deaths')\n", + "\n", + " plt.subplot(4, 3, 3)\n", + " plt.plot(scoreboard.goals)\n", + " plt.ylabel('Goals')\n", + "\n", + " plt.subplot(4, 3, 4)\n", + " plt.plot(scoreboard.max_goals)\n", + " plt.ylabel('Max Score')\n", + "\n", + " plt.subplot(4, 3, 5)\n", + " plt.plot(r_trace[:step + 1], alpha=0.5)\n", + " binSize = 20\n", + " if step+1 > binSize:\n", + " # Calculate mean of every bin of binSize reinforcement values\n", + " smoothed = np.mean(r_trace[:int(step / binSize) * binSize].reshape((int(step / binSize), binSize)), axis=1)\n", + " plt.plot(np.arange(1, 1 + int(step / binSize)) * binSize, smoothed)\n", + " plt.ylabel('Mean reinforcement')\n", + "\n", + " plt.subplot(4, 3, 6)\n", + " q.draw(['$o$', '$a$'], ['q'])\n", + "\n", + " plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "00ca3585-8a11-4fd5-93d7-8e73bfc31e81", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "ename": "ValueError", + "evalue": "cannot reshape array of size 25 into shape (50,20)", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[16], line 30\u001b[0m\n\u001b[1;32m 28\u001b[0m scoreboard\u001b[38;5;241m.\u001b[39mflush()\n\u001b[1;32m 29\u001b[0m fig\u001b[38;5;241m.\u001b[39mclf()\n\u001b[0;32m---> 30\u001b[0m \u001b[43mplot_status\u001b[49m\u001b[43m(\u001b[49m\u001b[43mq\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstep\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mepsilon_trace\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mr_trace\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 31\u001b[0m scoreboard\u001b[38;5;241m.\u001b[39mall_goals \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0\u001b[39m\n\u001b[1;32m 32\u001b[0m clear_output(wait\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n", + "Cell \u001b[0;32mIn[15], line 25\u001b[0m, in \u001b[0;36mplot_status\u001b[0;34m(q, step, epsilon_trace, r_trace)\u001b[0m\n\u001b[1;32m 22\u001b[0m binSize \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m20\u001b[39m\n\u001b[1;32m 23\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m step\u001b[38;5;241m+\u001b[39m\u001b[38;5;241m1\u001b[39m \u001b[38;5;241m>\u001b[39m binSize:\n\u001b[1;32m 24\u001b[0m \u001b[38;5;66;03m# Calculate mean of every bin of binSize reinforcement values\u001b[39;00m\n\u001b[0;32m---> 25\u001b[0m smoothed \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mmean(\u001b[43mr_trace\u001b[49m\u001b[43m[\u001b[49m\u001b[43m:\u001b[49m\u001b[38;5;28;43mint\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mstep\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m/\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mbinSize\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mbinSize\u001b[49m\u001b[43m]\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mreshape\u001b[49m\u001b[43m(\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mint\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mstep\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m/\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mbinSize\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbinSize\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m, axis\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m)\n\u001b[1;32m 26\u001b[0m plt\u001b[38;5;241m.\u001b[39mplot(np\u001b[38;5;241m.\u001b[39marange(\u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m1\u001b[39m \u001b[38;5;241m+\u001b[39m \u001b[38;5;28mint\u001b[39m(step \u001b[38;5;241m/\u001b[39m binSize)) \u001b[38;5;241m*\u001b[39m binSize, smoothed)\n\u001b[1;32m 27\u001b[0m plt\u001b[38;5;241m.\u001b[39mylabel(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mMean reinforcement\u001b[39m\u001b[38;5;124m'\u001b[39m)\n", + "\u001b[0;31mValueError\u001b[0m: cannot reshape array of size 25 into shape (50,20)" + ] + }, + { + "data": { + "image/png": "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vMjzoinvn2I8L3KKpGrz7bffCs2bNxdsbGyEbt266Uw5ZEoAlPrYvHmzIAiC8PDhQyEgIEBo0qSJYG1tLXh6egpTpkwR0tLSdNbz6NEjYe7cuUKjRo0EOzs7YcSIESX61ITieWatra0Fd3d34aWXXhIuXryofV2tVguffPKJ4OrqKojFYuG5554Tzp8/r7MOU9mWYn/88Yd2jron1YZ9Uzy1wL8fU6ZMEQTBcPsjOztbeO211wQHBwfBwcFBeO2118qdzoAMp7xz3ZQpU4R+/frp9D98+LDQtWtXwcbGRmjRooWwdu3aEuv86aefhLZt2wrW1tZCu3btdL5sVLfKbE+/fv3K/fctCIIQHBwseHp6CjY2NkKTJk2EgIAAIS4uziS3Z9myZUKrVq0EW1tboWHDhsKzzz4r/Pe//y2xztqyfwRBM82anZ2dsGHDhlLXZ8z987TzY208fkiD50WeF011/wgCz4v67h+RIDy+25iIiIiIiIiojuM9wURERERERGQ2mAQTERERERGR2WASTERERERERGaDSTARERERERGZDSbBREREREREZDaYBBMREREREZHZYBJMREREREREZoNJMBEREREREZkNJsFERERERERkNpgEExERERERkdlgEkxERERERERmg0kwERERERERmQ0mwURERERERGQ2mAQTERERERGR2WASTERERERERGaDSTARERERERGZDSbBREREREREZDaYBBMREREREZHZYBJMREREREREZoNJMBEREREREZkNJsFERERERERkNpgEExERERERkdlgEkxERERERERmg0kwERERERERmQ0mwURERERERGQ2mAQTERERERGR2WASTERERERERGaDSTARERERERGZDSbBREREREREZDaYBBMREREREZHZYBJMREREREREZoNJMBEREREREZkNJsFERERERERkNpgEExERERERkdlgEkxERERERERmw8rYARibWq3GnTt34ODgAJFIZOxwiAiAIAjIy8uDu7s7LCz4W11N43mRyPTwvEhEZDhmnwTfuXMHHh4exg6DiEqRnp6OZs2aGTsMs8PzIpHp4nmRiKjqzD4JdnBwAKD5T8XR0dHI0RARAMjlcnh4eGiPT6pZPC8SmR6eF4mIDMfsk+DiS/0cHR35ZY/IxPBSXOPgeZHIdPG8SERUdbyphIiIiIiI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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "old_X, old_q = pick_greedy_action(q, p1, epsilon)\n", + "game_engine.player_advance([old_X[1]])\n", + "\n", + "fig = plt.figure(figsize=(10, 10))\n", + "scoreboard = Scoreboard()\n", + "plot_spacing = 1000\n", + "plotted_steps = 0\n", + "\n", + "R = np.zeros((plot_spacing, 1))\n", + "r_trace = np.zeros(n_steps // plot_spacing)\n", + "\n", + "for step in range(n_steps):\n", + " new_X, new_q = pick_greedy_action(q, p1, epsilon)\n", + " outcomes = game_engine.player_advance([new_X[1]])\n", + " scoreboard.track_outcome(outcomes[p1])\n", + "\n", + " update_q(q, old_X, new_X, new_q, outcomes[p1], n_epochs, lr=learning_rate)\n", + "\n", + " epsilon *= epsilon_decay\n", + " epsilon_trace[step] = epsilon\n", + " R[step % plot_spacing, 0] = reinforcement(outcomes[p1])\n", + " old_X = new_X\n", + " old_q = new_q\n", + "\n", + " if step >= plotted_steps:\n", + " r_trace[plotted_steps // plot_spacing] = np.mean(R)\n", + " plotted_steps += plot_spacing\n", + " scoreboard.flush()\n", + " fig.clf()\n", + " plot_status(q, step, epsilon_trace, r_trace)\n", + " scoreboard.all_goals = 0\n", + " clear_output(wait=True)\n", + " display(fig)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "269ac824-1568-49aa-a020-9a57ee59ae49", + "metadata": {}, + "outputs": [], + "source": [ + "game_engine.toggle_draw()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36a2d897-15a8-47a4-953b-a159af0ad881", + "metadata": {}, + "outputs": [], + "source": [ + "epsilon = 0\n", + "for step in range(500):\n", + " new_X, _ = pick_greedy_action(q, p1, epsilon)\n", + " game_engine.player_advance([new_X[1]])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b77b2db1-e928-4cd8-ae98-7f8ac9b1326f", + "metadata": {}, + "outputs": [], + "source": [ + "inferior_table = qtsnake.load_q('inferior_qt.npy')\n", + "superior_table = qtsnake.load_q('superior_qt.npy')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1022bbdf-c68d-4e02-89e0-9d71470d9b8e", + "metadata": {}, + "outputs": [], + "source": [ + "epsilon = 0\n", + "n_steps = 1500" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d67ba96c-9b42-47d2-a88f-a94335bd6967", + "metadata": {}, + "outputs": [], + "source": [ + "game_engine = multiplayer.Playfield(window_width=WINDOW_WIDTH,\n", + " window_height=WINDOW_HEIGHT,\n", + " units=10,\n", + " g_speed=100,\n", + " s_size=1)\n", + "t1 = game_engine.add_player()\n", + "t2 = game_engine.add_player()\n", + "n1 = game_engine.add_player()\n", + "game_engine.start_game()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c5be5beb-e92c-42ad-9076-c28394560122", + "metadata": {}, + "outputs": [], + "source": [ + "q_table = qtsnake.QSnake(game_engine)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "314d0836-5c99-4de3-91c8-e563fed61e6c", + "metadata": {}, + "outputs": [], + "source": [ + "for step in range(n_steps):\n", + " # table 1\n", + " _, t1_action = q_table.pick_greedy_action(inferior_table, t1, epsilon)\n", + "\n", + " # table 2\n", + " _, t2_action = q_table.pick_greedy_action(superior_table, t2, epsilon)\n", + "\n", + " # network 1\n", + " n1_state_action, _ = pick_greedy_action(q, n1, epsilon)\n", + " game_engine.player_advance([t1_action,\n", + " t2_action,\n", + " n1_state_action[1]])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c75448e-3216-48f1-b649-938711cd4870", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/revised_snake_q_table.ipynb b/revised_snake_q_table.ipynb new file mode 100644 index 0000000..3d41eaf --- /dev/null +++ b/revised_snake_q_table.ipynb @@ -0,0 +1,743 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "85da5df2-c926-417c-bd7b-d214ad31ebe1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pygame 2.5.1 (SDL 2.28.2, Python 3.11.5)\n", + "Hello from the pygame community. https://www.pygame.org/contribute.html\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "from collections import namedtuple\n", + "from IPython.core.debugger import Pdb\n", + "\n", + "from GameEngine import multiplayer\n", + "Point = namedtuple('Point', 'x, y')" + ] + }, + { + "cell_type": "markdown", + "id": "d264ae4a-380c-47b6-9b29-c6fe16c6399c", + "metadata": {}, + "source": [ + "### New Game Implementation\n", + "\n", + "I have an improved game implementation which allows for multiplayer snake games, as well as simplified training. This notebook will go over both of these, including an implementation of q-table learning, as well as a match between a manually filled out q-table and a learned one.\n", + "\n", + "Let's start by initializing the engine object:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2a382240-906d-474f-94c0-9af1a5de97ae", + "metadata": {}, + "outputs": [], + "source": [ + "# defines game window size and block size, in pixels\n", + "WINDOW_WIDTH = 640\n", + "WINDOW_HEIGHT = 480\n", + "GAME_UNITS = 80" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "976eff80-c50a-492e-a49b-975d9905e274", + "metadata": {}, + "outputs": [], + "source": [ + "game_engine = multiplayer.Playfield(window_width=WINDOW_WIDTH,\n", + " window_height=WINDOW_HEIGHT,\n", + " units=GAME_UNITS,\n", + " g_speed=35,\n", + " s_size=1)" + ] + }, + { + "cell_type": "markdown", + "id": "23de2fca-d31a-497f-9a0c-845c568e5df7", + "metadata": {}, + "source": [ + "Here is a run-down of the current functions available to programs utilizing the game engine:\n", + "\n", + "**add_player**: Returns the player's number to the callee (between 0-3, for a total of 4 players). This number can be used with other functions to index that player's state.\n", + "\n", + "**get_heads_tails_and_goal**: Returns an array of player heads (in order of player number) the locations of all snake tails, as well as the goal location. Each is stored in an array of named tuples.\n", + "\n", + "**get_viable_actions**: Given a player's id, returns a list of integers corresponding to actions which will not immediately result in the snake's death.\n", + "0 = UP\n", + "1 = RIGHT\n", + "2 = DOWN\n", + "3 = LEFT\n", + "\n", + "**start_game**: Initializes goal, player, score, and playfield objects. Disables the ability to add new players. Enables use of player_advance function.\n", + "\n", + "**stop_game**: Sets the game_state to false, allowing new players to be added.\n", + "\n", + "**cleanup**: Quits the pygame window.\n", + "\n", + "**player_advance**: Given an array corresponding to each player's action (integers), returns a list of collision results, and updates the internal game state.\n", + "\n", + "**toggle_draw**: Turns off/on the game UI for faster training.\n", + "\n", + "Let's do a test of these functions:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "64470c1a-ddc6-4ce1-b6d4-f1e17e3d0176", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Game starting with 1 players.\n" + ] + }, + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p1 = game_engine.add_player()\n", + "game_engine.start_game()\n", + "p1" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "28d7fa43-4419-4940-9d4f-d6bd5ccc11ec", + "metadata": {}, + "outputs": [], + "source": [ + "viable_actions = game_engine.get_viable_actions(p1)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "ba45b03a-9c42-48e9-a259-5d01e667157d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "game_engine.player_advance([np.random.choice(viable_actions)])" + ] + }, + { + "cell_type": "markdown", + "id": "df7170ae-e95d-404b-b632-e8e9de46d69e", + "metadata": {}, + "source": [ + "If you looked at the UI for this last statement, you should have seen that the game moved the snake (yellow) in a random direction away from immediate death." + ] + }, + { + "cell_type": "markdown", + "id": "144bc5ff-756c-4e07-a855-4020a4474d52", + "metadata": {}, + "source": [ + "### State-sensing methods, creating and reading a q-table\n", + "Now, we can start redesigning some functions used to allow the snake to play intelligently. We'll use a multi-dimensional numpy array to store the rewards corresponding to each state and action. This is called a q-function, or a q-table in this case.\n", + "\n", + "How many states do I need? Seeing how the new **get_viable_actions** method already prevents the snake from choosing life-ending moves, the snake is no longer tasked with learning or memorizing it.\n", + "\n", + "The snake doesneed to be able to interpret progress towards the goal, so I will reinclude one state-sensing to sense the goal direction. I need only 8 states (with entries for each four actions) to represent our game now." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "88a69876-fbc7-4dc1-a471-d8449fada4e4", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.]])" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "goal_relations = 8\n", + "actions = 4\n", + "q = np.zeros((goal_relations,\n", + " actions))\n", + "q" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3e15744e-1251-4315-8a4c-ed5788d04478", + "metadata": {}, + "outputs": [], + "source": [ + "def sense_goal(head, goal):\n", + " '''\n", + " maps head and goal location onto an\n", + " integer corresponding to approx location\n", + " '''\n", + " diffs = Point(goal.x - head.x, goal.y - head.y)\n", + "\n", + " if diffs.x == 0 and diffs.y < 0:\n", + " return 0\n", + " if diffs.x > 0 and diffs.y < 0:\n", + " return 1\n", + " if diffs.x > 0 and diffs.y == 0:\n", + " return 2\n", + " if diffs.x > 0 and diffs.y > 0:\n", + " return 3\n", + " if diffs.x == 0 and diffs.y > 0:\n", + " return 4\n", + " if diffs.x < 0 and diffs.y > 0:\n", + " return 5\n", + " if diffs.x < 0 and diffs.y == 0:\n", + " return 6\n", + " return 7" + ] + }, + { + "cell_type": "markdown", + "id": "addf716b-892c-4f7f-b71f-c6af8779dff7", + "metadata": {}, + "source": [ + "I will use the getter provided by my engine, which queries various statistics about all agents in the game:\n", + "1. An array of head positions\n", + "2. An array of all tail locations\n", + "3. The goal location" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "7b5f0d57-8e26-4f95-b7ea-a6810936ad5d", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "([Point(x=0, y=160)], [0, Point(x=0, y=160)], Point(x=0, y=320))" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "game_engine.get_heads_tails_and_goal()" + ] + }, + { + "cell_type": "markdown", + "id": "d3fd47ce-55fe-4d2f-9147-8848193f7ca1", + "metadata": {}, + "source": [ + "Now to use sense_goal as an index into our q table:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "85e2bab1-c98b-400e-be41-a47ccd4bc163", + "metadata": {}, + "outputs": [], + "source": [ + "def index_actions(q, id):\n", + " '''\n", + " given q, player_id, an array of heads,\n", + " and the goal position,\n", + " indexes into the corresponding expected\n", + " reward of each action\n", + " '''\n", + " heads, tails, goal = game_engine.get_heads_tails_and_goal()\n", + " state = sense_goal(heads[id], goal)\n", + " return state, q[state, :]" + ] + }, + { + "cell_type": "markdown", + "id": "33ae53a8-989a-410c-a04f-2ac76561ce21", + "metadata": {}, + "source": [ + "Returning state here simplifies some logic later when I train the agent. It will be passed along to my next function, but it can be ignored for now." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "3808c200-2f67-43c4-a80f-c4c17dcfeacf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0., 0., 0., 0.])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "_, rewards = index_actions(q, p1)\n", + "rewards" + ] + }, + { + "cell_type": "markdown", + "id": "6a2ef7f7-f6f7-4610-8e98-1d389327f3e8", + "metadata": {}, + "source": [ + "In our learning agent, these actions will obviously be associated with different expected rewards. But it is not enough to take the best reward, because the positions of the hazards have not been accounted for. I chose to implement a replacement argmin/max function to select actions from this table, which generates new actions in order from highest expected reward to lowest expected reward." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a172e347-75b7-4b0a-8dcc-07a6ba04f77e", + "metadata": {}, + "outputs": [], + "source": [ + "def argmin_gen(rewards):\n", + " rewards = rewards.copy()\n", + " for i in range(rewards.size):\n", + " best_action = np.argmin(rewards)\n", + " rewards[best_action] = float(\"inf\")\n", + " yield best_action" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "9674f225-e7df-4551-baa8-29eb23fcc1d5", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "2\n", + "3\n" + ] + } + ], + "source": [ + "for action in argmin_gen(rewards):\n", + " print(action)" + ] + }, + { + "cell_type": "markdown", + "id": "b9bed101-661c-4e61-b8ad-94bbc1900e03", + "metadata": {}, + "source": [ + "How will we use this? If the action generated is not a viable action, we will take the next best action.\n", + "\n", + "What if no actions are viable? Then the agent has boxed itself in, and it doesn't matter what action we choose.\n", + "\n", + "Previously, I reset the snake if it got stuck in a learning-loop. I will instead use epsilon, as it gives me a bit more control over training. Here is my greedy-action selector function, combining the work of all the previous code:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d3d65397-62a9-47b6-9c84-808282656f80", + "metadata": {}, + "outputs": [], + "source": [ + "def pick_greedy_action(q, id, epsilon):\n", + " viable_actions = game_engine.get_viable_actions(id)\n", + " state, rewards = index_actions(q, id)\n", + "\n", + " if np.random.uniform() < epsilon:\n", + " return (state, np.random.choice(viable_actions)) if viable_actions.size > 0 else (state, 0)\n", + " for action in argmin_gen(rewards):\n", + " if action in viable_actions:\n", + " return (state, action)\n", + " return (state, 0) # death" + ] + }, + { + "cell_type": "markdown", + "id": "2169ac2b-df83-4f53-8a83-b35a2d1a521a", + "metadata": {}, + "source": [ + "We'll set up epsilon to decay over our 500-step test..." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "a5932d47-adbe-46de-b91e-582e40faf369", + "metadata": {}, + "outputs": [], + "source": [ + "n_steps = 200\n", + "epsilon = 1\n", + "final_epsilon = 0.001\n", + "epsilon_decay = np.exp(np.log(final_epsilon) / (n_steps))" + ] + }, + { + "cell_type": "markdown", + "id": "ee7b066a-330d-4cdd-bbb2-9d2a7ad2ceb4", + "metadata": {}, + "source": [ + "And watch the snake explore:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "6beff583-e32a-4c15-8fcb-f5b5d45ad548", + "metadata": {}, + "outputs": [], + "source": [ + "for step in range(n_steps):\n", + " _, p1_action = pick_greedy_action(q, p1, epsilon)\n", + " game_engine.player_advance([p1_action])\n", + " epsilon *= epsilon_decay" + ] + }, + { + "cell_type": "markdown", + "id": "4ee2c2e6-933d-460e-b2fc-f5bf1f22e381", + "metadata": {}, + "source": [ + "This snake obviously has no prior knowledge of how to earn the most reward, but it still does remarkably well because it is not allowed to die. It behaves as expected, favoring up and right when it is not forced to choose a random action.\n", + "\n", + "Our q_table only has 32 values as a result of removing the 16 danger states... It would be incredibly easy to manually select reward values to fill our q_table with..." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "7af51359-872c-4d1b-b178-e27cf86eb3cc", + "metadata": {}, + "outputs": [], + "source": [ + "set_q = np.array([[-10., -2., 0., -2.],\n", + " [-5., -5., 0., 0.],\n", + " [-2., -10., 2., 0.],\n", + " [0., -5., -5., 0.],\n", + " [0., -2., -10., -2.],\n", + " [0., 0., -5., -5.],\n", + " [-2., 0., -2., -10.],\n", + " [-5., 0., 0., -5.]])" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "4da4d318-f7e0-412b-8545-8fd346e167b3", + "metadata": {}, + "outputs": [], + "source": [ + "epsilon = 0\n", + "for step in range(n_steps):\n", + " _, p1_action = pick_greedy_action(set_q, p1, epsilon)\n", + " game_engine.player_advance([p1_action])\n", + " epsilon *= epsilon_decay" + ] + }, + { + "cell_type": "markdown", + "id": "5c36ab97-2ca0-4468-8d4c-ebd1e4deec23", + "metadata": {}, + "source": [ + "And the snake already plays optimally, no learning required.\n", + "\n", + "Now that we have these methods, I will create functions to allow the snake to learn by its own, and then pair it off against the q-table I just built." + ] + }, + { + "cell_type": "markdown", + "id": "0b9968af-0ec2-4b92-a19d-50912703dd4a", + "metadata": {}, + "source": [ + "### Learning and Temporal Difference" + ] + }, + { + "cell_type": "markdown", + "id": "ce537e44-ac8c-4f09-b89d-a330f13277da", + "metadata": {}, + "source": [ + "I will be using the temporal difference equation as the key learning element of my reinforcement function. In theory, it allows me adjust the expected reward of a state to agree with its observed successor. In practice, it will allow out agent to take actions that previously led it closer to the goal.\n", + "\n", + "In order to use this equation, I simply need to create a function that takes the current state/action/outcome, and the previous state/action, as this will be updated in cases where the agent did not reach the goal.\n", + "\n", + "When the agent does reach the goal, I will manually set that state and action to the best reward, 0. Remember that the q-table is initialized with zeros, meaning untravelled actions are pre-assigned good rewards. Both this and epsilon will encourage exploration.\n", + "\n", + "Here is the complete function:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "a0d3942a-af6a-41f3-be74-167e3abaae0b", + "metadata": {}, + "outputs": [], + "source": [ + "def update_q(q, old_state_action, new_state_action, outcome, lr=0.05):\n", + " if outcome == multiplayer.CollisionType.GOAL:\n", + " q[new_state_action[0], new_state_action[1]] = 0\n", + " else:\n", + " td_error = -1 + q[new_state_action[0], new_state_action[1]] - q[old_state_action[0], old_state_action[1]]\n", + " q[old_state_action[0], old_state_action[1]] += lr * td_error" + ] + }, + { + "cell_type": "markdown", + "id": "01b21e01-174e-4fdd-ad70-dcc1e6483fb2", + "metadata": {}, + "source": [ + "Now all that is needed is the training loop. I have high expectations for this agent, so I will only allow it 1500 moves to train itself! Here is where the outputs of pick_greedy_action come in handy:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "95a2ec72-e30c-4730-a876-21f054d3727f", + "metadata": {}, + "outputs": [], + "source": [ + "n_steps = 1500\n", + "epsilon = 1\n", + "final_epsilon = 0.001\n", + "epsilon_decay = np.exp(np.log(final_epsilon) / (n_steps))" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "a71dc022-5e51-46f8-bfed-37b95243fc5e", + "metadata": {}, + "outputs": [], + "source": [ + "p1_old_s_a = pick_greedy_action(q, p1, epsilon) # state, action\n", + "game_engine.player_advance([p1_old_s_a[1]])\n", + "\n", + "for step in range(n_steps):\n", + " p1_new_s_a = pick_greedy_action(q, p1, epsilon) # state, action\n", + " outcome = game_engine.player_advance([p1_new_s_a[1]])\n", + "\n", + " update_q(q, p1_old_s_a, p1_new_s_a, outcome)\n", + "\n", + " epsilon *= epsilon_decay\n", + " p1_old_s_a = p1_new_s_a" + ] + }, + { + "cell_type": "markdown", + "id": "c6cbf429-c790-4bb9-8775-ed067844ab4e", + "metadata": {}, + "source": [ + "The results look promising. Here is everything it learned:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "ad12d31a-a1ec-45af-89b0-f66ca375b524", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[-5.61286955, -0.92562893, -0.14868602, -0.77936816],\n", + " [-4.87612819, -3.84302796, -0.9194685 , -0.46585524],\n", + " [-0.65546246, -4.77095018, -0.49342682, 0. ],\n", + " [-1.58528747, -6.27145881, -2.59719179, -0.51455945],\n", + " [-0.48715079, -1.43421385, -6.12413612, -0.7260854 ],\n", + " [-0.78394669, -1.70799532, -3.10876847, -6.45896201],\n", + " [-0.798533 , -0.41896323, -1.30809359, -3.63055269],\n", + " [-2.96333643, -0.98546563, -2.05542179, -6.07365687]])" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q" + ] + }, + { + "cell_type": "markdown", + "id": "b2414728-c36c-45d6-8f2d-18e78e482054", + "metadata": {}, + "source": [ + "### Multiplayer Demonstration, Saving Tables" + ] + }, + { + "cell_type": "markdown", + "id": "24c73f20-2853-4ba7-a24e-22c5a4b8da5e", + "metadata": {}, + "source": [ + "The most entertaining way to test the success of my implementation is pair the agents q and set_q against each other. I will first stop and set up a new game:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "a93f0fe5-2bc4-45d7-ba31-2306efaa9806", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Game over!\n", + "Game starting with 2 players.\n" + ] + } + ], + "source": [ + "game_engine.stop_game()\n", + "p2 = game_engine.add_player()\n", + "game_engine.start_game()" + ] + }, + { + "cell_type": "markdown", + "id": "47a1adde-d95d-444e-9688-1290764f8cfa", + "metadata": {}, + "source": [ + "Now, I can simply call player advance with both player's actions in order, and the engine will handle the rest. I will define a new game loop, similar to the previous one:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "d5b5d089-cb66-47de-b352-36c13fd7dead", + "metadata": {}, + "outputs": [], + "source": [ + "epsilon = 0" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "4de326e6-82dc-48df-8920-d28d0154fbd9", + "metadata": {}, + "outputs": [], + "source": [ + "for step in range(n_steps):\n", + " # p1\n", + " _, p1_action = pick_greedy_action(set_q, p1, epsilon)\n", + "\n", + " # p2\n", + " p2_new_s_a = pick_greedy_action(q, p2, epsilon) # state, action\n", + " \n", + " game_engine.player_advance([p1_action, p2_new_s_a[1]])\n", + "\n", + " epsilon *= epsilon_decay\n", + " p2_old_s_a = p2_new_s_a" + ] + }, + { + "cell_type": "markdown", + "id": "820d7e5f-c3e9-4dae-82ce-3c9188d8a8d8", + "metadata": {}, + "source": [ + "The learned agent normally plays significantly worse than the artificially-learned one, which is okay given I hardly spent time optimizing the number of steps and learning rate. I plan to compare both of the agents again my neural-network approach, so the last this I will do is save the q_tables to a file." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "95227c89-e7db-4923-9160-dacfa1cf4af8", + "metadata": {}, + "outputs": [], + "source": [ + "np.save('superior_qt.npy', set_q)\n", + "np.save('inferior_qt.npy', q)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9eec33d8-9a65-426a-8ad5-8ffb2cbe2541", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/revised_snake_q_table_noise.ipynb b/revised_snake_q_table_noise.ipynb new file mode 100644 index 0000000..695875e --- /dev/null +++ b/revised_snake_q_table_noise.ipynb @@ -0,0 +1,57 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "85da5df2-c926-417c-bd7b-d214ad31ebe1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pygame 2.5.1 (SDL 2.28.2, Python 3.11.5)\n", + "Hello from the pygame community. https://www.pygame.org/contribute.html\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "from collections import namedtuple\n", + "from IPython.core.debugger import Pdb\n", + "\n", + "from GameEngine import multiplayer\n", + "Point = namedtuple('Point', 'x, y')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9eec33d8-9a65-426a-8ad5-8ffb2cbe2541", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/superior_qt.npy b/superior_qt.npy new file mode 100644 index 0000000..8c81fe1 Binary files /dev/null and b/superior_qt.npy differ -- cgit v1.2.3