Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Create a Self-Playing AI Chess Engine from Scratch with Imitation Learning

This is an article on how I created an AI Chess engine, starting completely from scratch to building my very own AI chess engine. Because creating an AI chess engine from scratch is a relatively complex task, this will be a long article, but stay tuned, as the product you will end up with will be a cool project to showcase!This article will explain most concepts in detail. However, there are some recommended prerequisites to follow the tutorial. You should be familiar with the following:I will also use the following tools:In this part, I will use Stockfish to generate a large dataset of moves from different positions. This data can then be used later on to train the chess AI.The most important component of my chess engine is Stockfish, so I will therefore show you how to install it. Go to the Stockfish website download page and download the version for you. I am using Windows myself, so I chose the Windows (faster) version:After downloading, extract the zip file to whatever location on your PC you want your chess engine to be. Remember where you place it as you need the path for the next step.Now you also need to incorporate the engine into Python. You could manually do this, but I found it easier to use the Python Stockfish package as it has all the functions you need. First install the package from pip (preferably in your virtual environment):You can then import it using the following command:Note that you need to give your own path to the Stockfish executable file:You can copy the file path from the folder structure, or if you are on Windows 11 you can press ctrl + shift + c to automatically copy the file path.Great! Now you have Stockfish available in Python!Now you need a dataset so you can train the AI chess engine! You can do this by making Stockfish play games and remembering each position and the moves you could take from there. Those moves will be along the best possible moves, considering Stockfish is a strong chess engine.First, install a chess package and NumPy (there are plenty to choose from, but I will be using the one below). Enter each line (individually) in the terminal:Then import packages (remember to also import Stockfish as shown earlier in this article):You also need some helper functions here:Remember to change the file path in the findNextIdx function, as this is personal for your computer. Create a data folder within the folder you are coding, and copy the path (but still keep the *.npy at the end)The checkEndCondition function uses functions from the Chess pip package to check if the game is to be ended. The saveData function saves a game to npy files which is a highly optimized way of storing arrays. The function uses the findNextIdx function to save to a new file (remember here to create a new folder called data to store all data in). Finally, the runGame function makes it so you can run a game that you saved to check the positions after numMoves number of moves.Then you can finally get to the function that mines the chess games:Here you first set a max limit so a game does not last infinitely long. Then, you run the number of games you want to run and make sure both Stockfish and the Chess pip package are reset to the starting position. Next, you get the top 3 moves suggested by Stockfish and choose one of them to play (80 % change for the best move, 15 % change for the second best move, 5 % change for the third best move). The reason you are not always choosing the best move is for the move selection to be more stochastic. Then, you choose a move (making sure no error occurs even if there are less than three possible moves), save the board position using FEN (a way of encoding a chess position), as well as the move done from that position. If the game is done, you break the loop and store all positions and the moves made from those positions. If the game is not done, you continue making moves until the game is over.You can then mine one game with:Remember to create a data folder here, as this is where I store the games!Run the mineGames function to mine one game using the following command:You can access this game with a helper function shown earlier using the following command:Assuming there have been 12 moves in the game, you will then see something like this:And that’s it, you can now mine as many games as you would like. It is going to take some time, and there are potentials for optimizing this mining process, such as parallelizing the game simulations (since each game is completely separate from the other). For the full code from part 1, you can check out the full code on my GitHub.In this part, you will encode chess moves and positions in the same way DeepMind did with AlphaZero!I will use the data you gathered in part 1 of this series. As a reminder, you installed Stockfish and made sure you could access it on the computer. You then made it play games against itself, while you stored all moves and positions. You now have a supervised learning problem, since the input is the current position, and the label (the correct move from the positions) is the move that Stockfish decided was the best.First, you need to install and import all required packages, some of which you may already have if you followed part 1 of this series. All imports are below – remember to only input one line at a time when installing via pip:Additionally, you need to make a small change in one of the files in the gym-chess package since np.int was used, which is now deprecated. In the file with the relative path (from the virtual environment) venv\Lib\site-packages\gym_chess\alphazero\board_encoding.py where venv is the name of my virtual environment, you have to search for "np.int" and replace them with "int". If you don't, you will see an error message stating that np.int is deprecated. I also had to restart VS Code after replacing "np.int" with "int", to make it work.All imports you need are below:And then you also need to create the gym environment to encode and decode moves:Encoding is an important element within AI, as it allows us to represent problems in a readable way for the AI. Instead of an image of a chess board, or a string representing a chess move like "d2d4", you instead represent this using arrays (lists of numbers). Finding out how to do this manually is quite challenging, but luckily for us, the gym-chess Python package has already solved this problem for us.I am not going to go into more details on how they encoded it, but you can see using the code below that a position is represented with an (8,8,119) shaped array, and all possible moves are given with a (4672) array (1 column with 4672 values). If you want to read more about this, you can check out the AlphaZero paper, though this is quite a complicated paper to fully understand.Which outputs:You can also check out the encoding of a move. From string notation to encoded notation. Make sure to reset the environment as it may give an error if you do not:With this, you can now have functions to encode the moves and positions you stored from part 1 where you generated a dataset.These functions are copied from the Gym-Chess package, but with small tweaks so it is not dependent on a class. I manually changed these functions so that it was easier to encode. I would not worry too much about understanding these functions fully, as they are quite complicated. Just know that they are a way of making sure moves that humans understand, are converted to a way that computers can understand.So now you can give in a move as a string (for example: "e2e4" for the move from e2 to e4), and it outputs a number (the encoded version of the move).Encoding the positions is a bit more difficult. I took a function from the gym-chess package ("encodeBoard") since I had some issues using the package directly. The function I copied is below:I also added the encodeBoardFromFen function, since the copied function required a chess board represented using the Python Chess package, so I first convert from FEN-notation (a way of encoding chess positions to a string – you cannot use this as you need the encoding to be in numbers) to a chess board given in that package.Then you have all you need to encode all your files.Now that you can encode moves and positions, you will automate this process for all files in your folder that you generated from part 1 of this series. This involves finding all files in which you have to encode the data and saving these to new files.Note that from part 1 I changed the folder structure slightly. I now have a parent Data folder, and within this folder, I have the rawData, which is the moves in string format and positions in FEN-format (from part 1).I also have the preparedData folder under the data folder, where the encoded moves and positions will be stored. Note that the encoded moves and positions will be stored in separate files since the encodings have different dimensions.I first create the environment and reset it. Then, I open all raw data files made from part 1 and encode this. I also do it in a try/catch statement, as I sometimes see errors with move encodings. The first except statement is for if a move is skipped (so the program thinks it’s the wrong turn). If this happens, the encoding will not work, so the except statement changes the turn and tries again. This is not the most optimal code, but the encoding is a minor part of the total runtime to creating an AI chess engine, and it is therefore acceptable.Make sure you have the correct folder structure and have created all the different folders. If not, you will receive an error.You have now encoded your chess board and moves. If you want to, you can check out the full code from this part on my GitHub.This is the third and last part in the for creating your own AI chess engine! In part 1 you learned how to create a dataset, and in part 2 you looked at encoding the dataset so that it could be used for an AI. You will now use this encoded dataset to train your own AI using PyTorch!As always, you have all the imports that will be used in the tutorial. Most are straightforward, but you need to install PyTorch, which I recommend installing using this website. Here you can scroll down a bit, where you see some options for which build and operating system you are on. After selecting the options that apply to you, you will get some code you can paste into the terminal to install PyTorch. You can see the options I chose in the image below, but in general, I recommend using the stable build and choosing your own operating system. Then, select what package you are most used to (Conda or pip is probably the easiest as you can just paste it into the terminal). Select CUDA 11.7/11.8 (does not matter which one), and install using the given command at the bottom.You can then import all your packages with the code below:This is an optional step, that allows you to utilize your GPU to train your model much faster. It is not required, but will save you some time when training your AI. The way you install CUDA varies depending on your operating system, but I am using Windows and followed this tutorial.If you are on MacOS or Linux, then you can find a tutorial by googling: “installing CUDA Mac/Linux”.To check if you have CUDA available (your GPU is available), you can use this code:Which outputs True if your GPU is available. If you do not have a GPU available however, do not worry, the only downside here is training the model takes longer, which is not that big of a deal when doing hobby projects like this one.I then define some helper methods for encoding and decoding from the Python Gym-Chess package. I had to make some modifications to the package, to make it work. Most of the code is copied from the package, with just a few small tweaks making the code not dependent on a class and so forth. Note that you do not have to understand all the code below, as the way Deepmind encodes all moves in chess is complicated.In part 1, you mined some chess games, and then in part 2, you encoded it so that it could be used to train a model. You now load this data in PyTorch data loader objects, so it’s available for the model to train on. In case you have not done part 1 or 2 of this tutorial, you can find some ready-made training files in this Google Drive folder.First, define some hyperparameters:The FRACTION_OF_DATA variable, is there just in case you want to train the model fast and do not want to train it on the full dataset. Make sure this value is > 0 and ≤ 1. The BATCH_SIZE variable decides the batch size the model trains on. In general, a higher batch size means the model can train faster, but your batch size is limited by the power of your GPU. I recommend testing with a low batch size of 4 and then trying to increase it and see if training still works as it should. If you get a memory error of some sort, try decreasing the batch size again.You then load the data with the code below. Make sure your folder structure and file naming is correct here. You should have an initial data folder in the same place where your code is. Then inside this data folder, you should have a preparedData folder, that contains the files you want to train on. These files have to be named moves{i}.npy and positions{i}.npy, where i is the index of the file. If you encoded the files as I did earlier, everything should be correct.You can then define the model architecture:You are free to change the architecture however you like. Here, I have just chosen some simple parameters that worked decently, though there is room for improvement. Some examples of changes you can make are:In addition to the model architecture and forward functions, which are obligatory when creating a deep model,  I also defined a predict() function, to make it easier to give a chess position to the model, and then it outputs the move it recommends.When you have all the required data and the model is defined, you can begin training the model. First, you define a function to train one epoch and save the best model:Note that this function is essentially copied from the PyTorch docs, with a slight change by importing the model, optimizer, and loss function as function parameters.You then define the hyperparameters like below. Note that this is something you can tune, to further improve your model.Run the training with the code below:This code is also heavily inspired by the PyTorch docs.Depending on the number of layers in your model, the number of neurons in the layers, the number of epochs, if you are using GPU or not, and several other factors, your time to train the model can take anywhere from seconds, to several hours. As you can see below, the estimated time to train my model here was about 2 minutes.Testing your model is a vital part of checking if what you created works. I have implemented two ways of checking the model:The first way is to play yourself against the AI. Here you decide a move, then you let the AI decide the move, and so on. I recommend doing this in a notebook, so you can run different cells for different actions.First, load a model that was saved from training. Here, I get the path to the file from the file created when running training, that stores the path to your best model. You can of course also manually change the path to the model you prefer to use.Then, define the chess board:Then you can make a move by running the code in the cell below by changing the string in the first line. Make sure it is a legal move:Then you can let the AI decide the next move with the cell below:This will also print the board state so you can decide your own move more easily:Continue making every other move, let the AI play every other move, and see who wins!If you want to regret a move, you can use:You can also automate the testing process, by setting Stockfish to a specific ELO, and letting your AI play against it:First, load your model (make sure to change the model_path to your own model):Then import Stockfish, and set it to a specific ELO. Remember to change the path to the Stockfish engine to your own path where you have the Stockfish program):A 100 ELO rating is quite bad, and something your engine will hopefully beat.Then play the game with this script, which will run:Which will print the board position after the game is over.I tried training the model on about 100k positions and moves and discovered that the performance of the model still is not enough to beat a low-level (500 ELO) chess bot. There could be several reasons for this. Chess is a highly complicated game, that probably requires a lot more moves and positions for a decent bot to be developed.Furthermore, there are several elements of the bot you change potentially change to improve it. The architecture can be improved, for example by adding a CNN at the beginning of the forward function, so that the bot takes in spatial information. You can also change the number of hidden layers in the fully connected layers, or the amount of neurons in each layer. A safe way to further improve the model is to feed it more data, as you have access to an infinite amount of data by using the mining code in this article. Additionally, I think this shows that an imitation learning chess engine either needs a lot of data or training a chess engine solely from imitation learning might not be an optimal idea. Still, imitation learning can be used as part of a chess engine, for example, if you also implement traditional searching methods, and add imitation learning on top of it.Congrats! You have now made your own AI chess engine from scratch, and I hope you learned something along the way. You can constantly make this engine better if you want to improve it, and make sure it beats better and better competition.If you want to full code, check out my GitHub.This tutorial was originally written part by part on my Medium, you can check out each part here:If you are interested and want to learn more about similar topics, you can find me on:CS Student at Norwegian University of Science and Technology If you read this far, tweet to the author to show them you care. Tweet a thanks Learn to code for free. freeCodeCamp's open source curriculum has helped more than 40,000 people get jobs as developers. Get started freeCodeCamp is a donor-supported tax-exempt 501(c)(3) charity organization (United States Federal Tax Identification Number: 82-0779546)Our mission: to help people learn to code for free. We accomplish this by creating thousands of videos, articles, and interactive coding lessons - all freely available to the public. We also have thousands of freeCodeCamp study groups around the world.Donations to freeCodeCamp go toward our education initiatives, and help pay for servers, services, and staff. You can make a tax-deductible donation here.



This post first appeared on VedVyas Articles, please read the originial post: here

Share the post

Create a Self-Playing AI Chess Engine from Scratch with Imitation Learning

×

Subscribe to Vedvyas Articles

Get updates delivered right to your inbox!

Thank you for your subscription

×