{"id":28378648,"url":"https://github.com/oriyarden/path-finding-and-object-tracking-using-machine-learning-ai-from-scratch-numpy-only-in-python","last_synced_at":"2026-05-03T15:35:54.010Z","repository":{"id":177336053,"uuid":"656267775","full_name":"OriYarden/Path-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python","owner":"OriYarden","description":"Optimizing Path Finding using Machine Learning AI from Scratch (only numpy arrays) in Python","archived":false,"fork":false,"pushed_at":"2023-06-29T15:07:37.000Z","size":50,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-04T17:00:05.385Z","etag":null,"topics":["ai","algorithms","artificial-intelligence","from-scratch","google-colab-notebook","machine-learning","machine-learning-algorithms","numpy","numpy-arrays","object-detection","object-oriented-programming","oop","pathfinding","python","reinforcement-learning","reinforcement-learning-algorithms"],"latest_commit_sha":null,"homepage":"","language":"Jupyter Notebook","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/OriYarden.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-06-20T15:38:41.000Z","updated_at":"2023-07-18T15:58:57.000Z","dependencies_parsed_at":null,"dependency_job_id":"83d2482c-2423-426e-8bb1-f611f523da26","html_url":"https://github.com/OriYarden/Path-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python","commit_stats":null,"previous_names":["oriyarden/path-finding-and-object-tracking-using-machine-learning-ai-from-scratch-numpy-only-in-python"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/OriYarden/Path-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OriYarden%2FPath-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OriYarden%2FPath-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OriYarden%2FPath-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OriYarden%2FPath-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/OriYarden","download_url":"https://codeload.github.com/OriYarden/Path-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OriYarden%2FPath-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32575113,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T06:36:36.687Z","status":"ssl_error","status_checked_at":"2026-05-03T06:36:09.306Z","response_time":103,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","algorithms","artificial-intelligence","from-scratch","google-colab-notebook","machine-learning","machine-learning-algorithms","numpy","numpy-arrays","object-detection","object-oriented-programming","oop","pathfinding","python","reinforcement-learning","reinforcement-learning-algorithms"],"created_at":"2025-05-30T02:07:04.241Z","updated_at":"2026-05-03T15:35:53.990Z","avatar_url":"https://github.com/OriYarden.png","language":"Jupyter Notebook","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Path-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python\n\nHere we have a world (grid); its size is 50 by 50:\n\n![download (22)](https://github.com/OriYarden/Path-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python/assets/137197657/e77b5628-1ba8-4424-8fec-ebc233ad09b0)\n\nIncluding diagonal movements, the \"best path\" for the object (yellow) to reach the target (green) requires 49 actions:\n\n![download (23)](https://github.com/OriYarden/Path-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python/assets/137197657/0db1cd7d-1039-4c8e-99a5-06ae817572cd)\n\nIn this example, it took our Neural Network model 28 training iterations to find the \"best path\" to reach the target.\n\nWe can plot performance in terms of the number of actions taken to reach the target over training iterations, where each \"training iteration\" is a single path to the target on the x-axis:\n\n![download (24)](https://github.com/OriYarden/Path-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python/assets/137197657/301cc0c2-0c6f-4fa1-95ec-01551265688f)\n\nThe y-axis is the number of actions in each training path, and we can see a clear learning curve. The first training path included almost 1,000 actions to reach the target:\n\n![download (25)](https://github.com/OriYarden/Path-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python/assets/137197657/abd80ee1-4879-4728-9dc5-68d87a54f70c)\n\n\nand although there is a bit of noise, each subsequent training path required fewer actions to reach the target.\nHere we demonstrate machine learning or artificial intelligence in a world grid search example where the object learns the best path to reach the target in the fewest possible number of actions.\n\nWe'll go over in detail how we train our numpy weights matrix representing the possible actions that the object can make at every given location in the world (grid).\n\nLet's start off with the actions themselves (along with labeling the actions):\n\n![image](https://github.com/OriYarden/Path-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python/assets/137197657/4d55ac94-c673-4b31-8256-0d0a24acb4a1)\n\nIn the center is the \"object\" (or \"/\" in labels); in order for the object to move to a new position in the world (grid), we have to add the row-column actions to the object's current position (also represented as row-columns values). We can also randomize the starting positions of the object and target, but let's use [0, 0] and [49, 49] for this example.\nAt every location in the grid, there are 8 possible actions (labeled 0 through 7); we'll account for actions that would move the object outside of the world (grid) later.\n\nGiven the number of possible actions (8 including diagonal movements, 4 if not including diagonal movements), and a grid size of 50 (and world grid size 50 by 50), we can create a weights matrix by multiplying the square of the grid size and the number of possible actions:\n\n    def init_weights(self):\n        return np.reshape(self.add_noise(self.number_of_actions*self.grid_size**2)*0.01, [self.grid_size**2, self.number_of_actions])\n\nOur \"add_noise\" method will be used when initializing the weights matrix as well as in other methods; it just returns random value(s) or \"noise\":\n\n    @staticmethod\n    def add_noise(n=1):\n        return np.random.random(n) if n != 1 else np.random.random(n)[0]\n\nTo calculate the fewest number of actions required to reach the target, we'll use the following method:\n\n    def calc_minimum_actions_to_target(self, start, target):\n        if not self.diagonal_movements:\n            return abs(target[0] - start[0]) + abs(target[1] - start[1])\n        d = min(abs(target[1] - start[1]), abs(target[0] - start[0]))\n        _d = 0 if abs(target[1] - start[1]) == abs(target[0] - start[0]) else max(abs(target[1] - start[1]), abs(target[0] - start[0])) - d\n        return d + _d\n\nWhich (when including diagonal movements) is the minimum row-column distance between the starting position and the target position, plus the difference between the maximum row-column distance and the minimum if the row and column distances are not equal.\n\nPrior to the training loop(s), we'll create a paths dictionary which will store the positions of the object as it moves to the target for each training path:\n\n        self.paths = {}\n        \nAnd we'll use a method to store_paths:\n\n    def store_path(self, pos, new_path=False):\n        if new_path:\n            self.paths[str(len(self.paths))] = []\n        self.paths[str(len(self.paths) - 1)].append(pos)\n\nOur training iteration begins with a while loop; more specifically, while not best_path_to_target(paths, minimum_actions_to_target):\n\n    @staticmethod\n    def best_path_to_target(paths, minimum_actions_to_target):\n        return False if not paths or len(paths[str(len(paths) - 1)]) - 1 \u003e minimum_actions_to_target else True\n\nThis method returns False if the paths dictionary is empty (obviously), and it returns False if the length of the previous path is greater than the minimum number of actions to reach the target; otherwise, it returns True.\n\nOur outter training while loop creates the world (grid) and places the object at its starting position, and also stores that position:\n\n        while not self.best_path_to_target(self.paths, self.minimum_actions_to_target):\n            current_pos = [self.start_pos[0], self.start_pos[1]]\n            world_grid = self.create_world_grid(current_pos)\n\n            self.store_path(current_pos, new_path=True)\n\nNow the inner training while loop operates until the object reaches the target:\n\n            while current_pos != self.target_pos:\n                new_action, new_pos = self.decide_action(current_pos, world_grid)\n\n                feedback = self.get_feedback(current_pos, new_pos)\n                actions_feedback = np.zeros(self.number_of_actions).astype(float)\n                actions_feedback[new_action] = feedback + 0.5*self.add_noise()\n\n                weights_feedback = np.reshape(world_grid, [self.grid_size**2, 1])*actions_feedback.transpose()\n                self.weights += weights_feedback*learning_rate\n\n                current_pos, world_grid = new_pos, self.create_world_grid(new_pos)\n                self.store_path(current_pos)\n\nFor each iteration of this while loop, we'll decide a new action (i.e. decide_action method), get feedback from that new action (i.e. get_feedback method), create an actions feedback matrix (i.e. actions_feedback variable), create a weights feedback matrix (i.e. multiply the world grid by the actions feedback matrix), and update the weights matrix (i.e. the weights for actions).\nThen, we'll update the current position of the object as the new position along with the new world grid, and store the new position in the paths dictionary.\n\nThat was one iteration of the inner while loop, which corresponds to just one action; so our object moved once. It's still not at the target position, so the while loop will continue selecting new actions, updating the weights based on the actions, etc. until it reaches the target.\nOnce the object reaches the target, we have our first path; given that the actions are at least initially random, the object will begin a new path, and then another, etc. until the \"best path\" is found--and the outter while loop terminates.\n\nOur only weights are for actions themselves, not the locations of the object nor the location of the target position. We give positive feedback when the object's new position is closer to the target position compared to the previous position, and negative feedback if the object moves farther away from the target position.\nWe utilize some boolean logic (in list comprehensions) to calculate the error (i.e. feedback), which is negative if the object's actions aren't efficient (i.e. moving non-diagonally when diagonal movements are more appropriate):\n\n    def get_feedback(self, current_pos, new_pos):\n        error = sum([abs(self.target_pos[xy] - current_pos[xy]) - abs(self.target_pos[xy] - new_pos[xy]) for xy in range(2)])\n        if self.diagonal_movements:\n            error += 0.5*sum([self.target_pos[xy] == current_pos[xy] == new_pos[xy] for xy in range(2)]) - 2*sum([self.target_pos[xy] != current_pos[xy] == new_pos[xy] for xy in range(2)]) - 0.5*sum([abs(self.target_pos[xy] - current_pos[xy]) \u003c abs(self.target_pos[xy] - new_pos[xy]) for xy in range(2)])\n        return error\n\nIn our decide_action method:\n\n    def decide_action(self, current_pos, world_grid):\n        actions_weights = np.reshape(world_grid, [1, self.grid_size**2])[0]@self.weights + 0.5*self.add_noise(self.number_of_actions)\n        edges = self.check_boundaries(current_pos)\n        for _edge in edges if edges is not None else []:\n            actions_weights[_edge] -= 100000\n        new_action, _new_action = self.get_action(actions_weights)\n        new_pos = [current_pos[0] + _new_action[0], current_pos[1] + _new_action[1]]\n        return new_action, new_pos\n\nwe temporarily reduce the value(s) of the weights that correspond to actions that would move the object outside of the world grid using the check_boundaries method to avoid those actions:\n\n    def check_boundaries(self, current_pos, pos_and=[[0, 2, 4, 5, 6], [1, 3, 5, 6, 7], [0, 3, 4, 6, 7], [1, 2, 4, 5, 7]], pos_or=[[0, 4, 6], [1, 5, 7], [2, 4, 5], [3, 6, 7]], pos=[[0, 2], [1, 3], [0, 3], [1, 2]]):\n        for op in ['and', 'or']:\n            for e, edges in enumerate([[0, 0], [self.grid_size - 1, self.grid_size - 1], [0, self.grid_size - 1], [self.grid_size - 1, 0]]):\n                if eval(f'{current_pos[0]} == {edges[0]} {op} {current_pos[1]} == {edges[1]}'):\n                    if op == 'and':\n                        return pos_and[e] if self.diagonal_movements else pos[e]\n                    return (pos_or[e] if self.diagonal_movements else [e]) if current_pos[0] == edges[0] else (pos_or[e + 2] if self.diagonal_movements else [e + 2])\n\n\nBelow, I included the entire class and its methods:\n\n    import numpy as np\n    from matplotlib import pyplot as plt\n\n    class NN:\n        def __init__(self, grid_size=10, diagonal_movements=False):\n            self.grid_size = grid_size\n            self.diagonal_movements = diagonal_movements\n            self.number_of_actions = 8 if self.diagonal_movements else 4\n            self.weights = self.init_weights()\n\n        @staticmethod\n        def print_actions_diagram():\n            print('''Actions = [\n                [-1, -1], [-1, 0], [-1, 1],\n                [0,  -1], object,  [0,  1],\n                [1,  -1], [1,  0], [1,  1]\n                ]''')\n            print('''Labels = [\n                [4, 0, 6],\n                [2, /, 3],\n                [5, 1, 7]\n                ]''')\n\n        def train(self, start_pos=None, target_pos=None, random_positions=False, new_weights=False, iterations=None, learning_rate=1.0):\n            self.init_positions(start_pos, target_pos, random_positions)\n            if new_weights:\n                self.weights = self.init_weights()\n\n            self.paths = {}\n            while not self.best_path_to_target(self.paths, self.minimum_actions_to_target) if iterations is None else len(self.paths) \u003c iterations:\n                current_pos = [self.start_pos[0], self.start_pos[1]]\n                world_grid = self.create_world_grid(current_pos)\n\n                self.store_path(current_pos, new_path=True)\n                while current_pos != self.target_pos:\n                    new_action, new_pos = self.decide_action(current_pos, world_grid)\n\n                    feedback = self.get_feedback(current_pos, new_pos)\n                    actions_feedback = np.zeros(self.number_of_actions).astype(float)\n                    actions_feedback[new_action] = feedback + 0.5*self.add_noise()\n\n                    weights_feedback = np.reshape(world_grid, [self.grid_size**2, 1])*actions_feedback.transpose()\n                    self.weights += weights_feedback*learning_rate\n\n                    current_pos, world_grid = new_pos, self.create_world_grid(new_pos)\n                    self.store_path(current_pos)\n\n        @staticmethod\n        def add_noise(n=1):\n            return np.random.random(n) if n != 1 else np.random.random(n)[0]\n\n        def init_weights(self):\n            return np.reshape(self.add_noise(self.number_of_actions*self.grid_size**2)*0.01, [self.grid_size**2, self.number_of_actions])\n\n        def randomize_positions(self):\n            self.start_pos = [np.random.randint(self.grid_size), np.random.randint(self.grid_size)]\n            self.target_pos = [np.random.randint(self.grid_size), np.random.randint(self.grid_size)]\n            while self.start_pos == self.target_pos:\n                self.target_pos = [np.random.randint(self.grid_size), np.random.randint(self.grid_size)]\n\n        def calc_minimum_actions_to_target(self, start, target):\n            if not self.diagonal_movements:\n                return abs(target[0] - start[0]) + abs(target[1] - start[1])\n            d = min(abs(target[1] - start[1]), abs(target[0] - start[0]))\n            _d = 0 if abs(target[1] - start[1]) == abs(target[0] - start[0]) else max(abs(target[1] - start[1]), abs(target[0] - start[0])) - d\n            return d + _d\n\n        def init_positions(self, start_pos, target_pos, random_positions):\n            if not self.__dict__.__contains__('start_pos') or start_pos:\n                self.start_pos = start_pos if start_pos is not None and all([min(start_pos) \u003e= 0, max(start_pos) \u003c self.grid_size]) else [0, 0]\n            if not self.__dict__.__contains__('target_pos') or target_pos:\n                self.target_pos = target_pos if target_pos is not None and all([min(target_pos) \u003e= 0, max(target_pos) \u003c self.grid_size]) else [self.grid_size - 1, self.grid_size - 1]\n            if random_positions:\n                self.randomize_positions()\n            self.minimum_actions_to_target = self.calc_minimum_actions_to_target(self.start_pos, self.target_pos)\n\n        @staticmethod\n        def best_path_to_target(paths, minimum_actions_to_target):\n            return False if not paths or len(paths[str(len(paths) - 1)]) - 1 \u003e minimum_actions_to_target else True\n\n        @staticmethod\n        def get_action(actions_weights, actions=[[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [1, -1], [-1, 1], [1, 1]]):\n            return np.where(actions_weights == max(actions_weights))[0][0], actions[np.where(actions_weights == max(actions_weights))[0][0]]\n\n        def create_world_grid(self, pos):\n            _world_grid = np.zeros((self.grid_size, self.grid_size)).astype(float); _world_grid[pos[0], pos[1]] = 1\n            return _world_grid\n\n        def store_path(self, pos, new_path=False):\n            if new_path:\n                self.paths[str(len(self.paths))] = []\n            self.paths[str(len(self.paths) - 1)].append(pos)\n\n        def check_boundaries(self, current_pos, pos_and=[[0, 2, 4, 5, 6], [1, 3, 5, 6, 7], [0, 3, 4, 6, 7], [1, 2, 4, 5, 7]], pos_or=[[0, 4, 6], [1, 5, 7], [2, 4, 5], [3, 6, 7]], pos=[[0, 2], [1, 3], [0, 3], [1, 2]]):\n            for op in ['and', 'or']:\n                for e, edges in enumerate([[0, 0], [self.grid_size - 1, self.grid_size - 1], [0, self.grid_size - 1], [self.grid_size - 1, 0]]):\n                    if eval(f'{current_pos[0]} == {edges[0]} {op} {current_pos[1]} == {edges[1]}'):\n                        if op == 'and':\n                            return pos_and[e] if self.diagonal_movements else pos[e]\n                        return (pos_or[e] if self.diagonal_movements else [e]) if current_pos[0] == edges[0] else (pos_or[e + 2] if self.diagonal_movements else [e + 2])\n\n        def decide_action(self, current_pos, world_grid):\n            actions_weights = np.reshape(world_grid, [1, self.grid_size**2])[0]@self.weights + 0.5*self.add_noise(self.number_of_actions)\n            edges = self.check_boundaries(current_pos)\n            for _edge in edges if edges is not None else []:\n                actions_weights[_edge] -= 100000\n            new_action, _new_action = self.get_action(actions_weights)\n            new_pos = [current_pos[0] + _new_action[0], current_pos[1] + _new_action[1]]\n            return new_action, new_pos\n\n        def get_feedback(self, current_pos, new_pos):\n            error = sum([abs(self.target_pos[xy] - current_pos[xy]) - abs(self.target_pos[xy] - new_pos[xy]) for xy in range(2)])\n            if self.diagonal_movements:\n                error += 0.5*sum([self.target_pos[xy] == current_pos[xy] == new_pos[xy] for xy in range(2)]) - 2*sum([self.target_pos[xy] != current_pos[xy] == new_pos[xy] for xy in range(2)]) - 0.5*sum([abs(self.target_pos[xy] - current_pos[xy]) \u003c abs(self.target_pos[xy] - new_pos[xy]) for xy in range(2)])\n            return error\n\n        def plot_world(self, path=None, show_paths=True):\n            if path is None:\n                path = len(self.paths) - 1\n            _title = f'Training Path #{str(path + 1)}: {int(len(self.paths[str(path)]) - 1)} Actions'\n            if not show_paths:\n                path = 0\n                _title = f'''\n                Yellow = Object, Green = Target\n                Purple = World, Grid Size: {self.grid_size} x {self.grid_size}\n                Best Path to Target: {self.minimum_actions_to_target} Actions'''\n            world_grid = np.zeros((self.grid_size, self.grid_size)).astype(int)\n            for row, col in self.paths[str(path)] if show_paths else []:\n                world_grid[row, col] = 1\n            world_grid[self.start_pos[0], self.start_pos[1]] = 3\n            world_grid[self.target_pos[0], self.target_pos[1]] = 2\n            fig = plt.figure(figsize=(5, 5))\n            ax = plt.subplot(1, 1, 1)\n            ax.imshow(world_grid)\n            ax.set_title(_title)\n            ax.axis('off')\n            plt.show()\n\n        def plot_performance(self):\n            y = [len(v) - 1 for _, v in self.paths.items()]\n            fig = plt.figure(figsize=(5, 5))\n            ax = plt.subplot(1, 1, 1)\n            ax.plot(range(len(y)), y, ls='-', lw=1.5, color=[1, 0, 0])\n            ax.plot(range(len(y)), np.ones(len(y))*self.minimum_actions_to_target, ls='--', lw=0.75, color=[0, 0, 0, 0.5])\n            ax.set_ylim(-0.1*max(y), max(y) + 0.1*max(y))\n            xticks = [int(_xtick) for _xtick in ax.get_xticks() if _xtick == np.round(_xtick, decimals=0) and 0 \u003c= _xtick \u003c len(y)]\n            ax.set_xticks(xticks)\n            ax.set_xticklabels([str(int(_xtick) + 1) if xticks[0] == 0 else str(int(_xtick)) for _xtick in ax.get_xticks()])\n            _yticks = [int(_ytick) for _ytick in ax.get_yticks() if _ytick \u003e= 0 and int(_ytick) == _ytick]\n            _yticks = _yticks[:-abs(len(_yticks) - len(ax.get_yticks()))] if _yticks else np.arange(0, max(y), np.round(max(y) / 4, decimals=0)).astype(int)\n            yticks = []\n            [yticks.append(self.minimum_actions_to_target) if self.minimum_actions_to_target not in yticks else yticks.append(_ytick) for _ytick in _yticks if _ytick \u003e self.minimum_actions_to_target]\n            ax.set_yticks(yticks)\n            _ylabel = '''\n            Number of Actions\n            in Path to Target\n            '''\n            ax.set_ylabel(_ylabel, fontsize=15, fontweight='bold')\n            ax.set_xlabel('Training Paths', fontsize=15, fontweight='bold')\n            ax.xaxis.set_label_position('top')\n            for _axis in ['x', 'y']:\n                ax.tick_params(axis=_axis, which='both', bottom=False if _axis == 'x' else 'on', top=False if _axis == 'y' else 'on', color='gray', labelcolor='gray', labelbottom=False, labeltop=True)\n            for _axis in ['top', 'right', 'bottom', 'left']:\n                ax.spines[_axis].set_visible(False)\n            plt.show()\n\n        def plot_weights_heatmap(self, display_type='default', weights_map={'positive': '_weights[row, col, action] \u003e 0', 'negative': '_weights[row, col, action] \u003c 0', 'best path': '[row, col] in self.paths[str(len(self.paths) - 1)]', 'default': 'True'}, actions=[[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [1, -1], [-1, 1], [1, 1]], _title='Actions-Weights Heatmap'):\n            if not display_type.startswith('default'):\n                _title = f'''\n                Actions-Weights Heatmap\n                ({display_type} actions)\n                '''\n            _weights = np.reshape(self.weights, [self.grid_size, self.grid_size, self.number_of_actions])\n            weights = np.zeros((self.grid_size, self.grid_size)).astype(float)\n            for row in range(self.grid_size):\n                for col in range(self.grid_size):\n                    for action in range(self.number_of_actions):\n                        _row, _col = row + actions[action][0], col + actions[action][1]\n                        if 0 \u003c= _row \u003c self.grid_size and 0 \u003c= _col \u003c self.grid_size and eval(weights_map[display_type]):\n                            weights[_row, _col] += _weights[row, col, action] if display_type.startswith('default') else abs(_weights[row, col, action])\n            fig = plt.figure(figsize=(5, 5))\n            ax = plt.subplot(1, 1, 1)\n            ax.imshow(weights)\n            ax.set_title(_title, fontsize=15)\n            ax.axis('off')\n            plt.show()\n\n        def plot_paths_heatmap(self):\n            paths = np.zeros((self.grid_size, self.grid_size)).astype(int)\n            for path in self.paths.values():\n                for pos in path:\n                    paths[pos[0], pos[1]] += 1\n            fig = plt.figure(figsize=(5, 5))\n            ax = plt.subplot(1, 1, 1)\n            ax.imshow(paths)\n            ax.set_title('Paths Heatmap', fontsize=15)\n            ax.axis('off')\n            plt.show()\n\n    nn = NN(diagonal_movements=True, grid_size=50)\n    nn.print_actions_diagram()\n    nn.train()\n    nn.plot_world(show_paths=False)\n    nn.plot_world()\n    nn.plot_performance()\n\nThis includes features such as starting with randomized positions, including or not including diagonal movements, plots, etc.\n\nFor example, here is a heatmap of the paths the object took during training that I think looks pretty cool:\n\n![download (26)](https://github.com/OriYarden/Path-Finding-and-Object-Tracking-using-Machine-Learning-AI-from-Scratch-numpy-only-in-Python/assets/137197657/5a825a6b-baf2-4bad-91b8-294508a7ce23)\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foriyarden%2Fpath-finding-and-object-tracking-using-machine-learning-ai-from-scratch-numpy-only-in-python","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foriyarden%2Fpath-finding-and-object-tracking-using-machine-learning-ai-from-scratch-numpy-only-in-python","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foriyarden%2Fpath-finding-and-object-tracking-using-machine-learning-ai-from-scratch-numpy-only-in-python/lists"}