Summary
https://github.com/lucasdino/AI-Driving
<aside>
š I built a racing game and trained a neural network to drive a course and grab coins
</aside>
<aside>
ā Motivation: 2-for-1 teaching myself reinforcement learning and larger coding projects (Python)
</aside>
S/o CodeBullet for giving me the idea for what to build in my first project. This was inspired by his video but improved upon by adding the ability to play multiple courses and have the AI generalize fairly well against courses it had not yet seen before. I think his memorized the map but not shamingā¦this stuff is FINICKY.
šŗCouple GIFs
Track the car was trained on - performs fairly well on this one!
Letās make this a bit harderā¦
Found this racetrack on Google Maps. A lot more windy than the othersā¦this AI is lacking on the āIā.
Unseen track - slightly more difficult. Itās not bad but a bit of a downgrade.
Took a ton of different prompts to get a closed circuit top-down course from DALL-E 3 but itās pretty groovy! At least I think it is. The AI doesnāt š«
Major Takeaways
- Coding: Relearned how to code larger projects after my 2-year banking hiatus. Iām happy with how the code turned out though there are obvious improvements I could make.
- Reinforcement Learning: If I had a dollar for the number of times I wanted to throw my laptop out the window I wouldnāt hesitate to put guac on my burrito bowl at Chipotle. Learned a lot by having to build the physics and data pipeline from the ground up. More on that below.
Coding
Only thing worthy of sharing here - initially I was getting around 40 FPS on training runs.
Note ā all these measurements refer to when training my Neural Net (i.e., backprop is running and the model is updating). When my game starts and it hasnāt accumulated enough training data yet, I was clocking in over 200 FPS after all of these š¤
Thatās kind of slow. Like I needed this program to be Level 100 Pacer Test and it was just getting a passing grade.
So I stepped in an made a couple of improvementsā¦
- Smart Collision / Vision Line Detection: (~40 FPS to ~80FPS)
- I knew this was going to be the biggest improvement but was definitely surprised by the results. I used a gridding approach mixed with an RTree (spatial data structure) to only check for intersections with the nearest racetrack lines. Previously it was just checking every line on the racetrack.
- Gridding: When initializing my game, I store the index of each line that exists in each āgridā in the map. When the car is in a grid, I would check the current grid the car is in as well as the neighboring 8 grids - then return a set of the lines that are in those grids and pass that into my intersection function.
- RTree: I used an RTree to quickly calculate which grid my car was in based on the center point of the sprite.
- Concept of a Session: (~80 FPS to ~100FPS)
- I implemented a hierarchy for my game that went as follows:
- Session
- Race Game
- Racecar / Track / Rewards / Etc.
- The improvement was in migrating as much code as possible into the āsessionā since every time the car crashes, it resets the āRaceGameā. Loading images, importing racetrack lines / rewards from a CSV, calculating the gridding, etc. - these are all things that only need to be done once so I moved them up in hierarchy to reduce unnecessary computation
- In doing all of this, I also refactored my code a ton which led to finding a few other areas that I could improve performance
- Numpy / I actually now know linear algebra: (~100 FPS to ~120FPS)
- It was kind of cute. When I first started this, I didnāt know linear algebra. So I had these long functions that I calculated using raw trigonometry. It worked but it was uglyā¦
- Cue me learning linear algebra and Numpy a month or so back. So I was able to convert all my code into Numpy / matrices where possible and got a nice performance boosts here