Welcome to part 13 of the self-driving GTA 5 AI with Python series. I was finding the scooter to be far too boring to create training data for, so I have decided to upgrade to a sportbike instead. Also, after seeing the results of the scooter after such a small amount of training data, I was too certain it would work just fine to train the AI to stay in between the lanes. With certain success, that's too boring as well, we want a challenge. So, instead, I am bringing back in traffic, and I have decided to make an AI that can navigate traffic at high speeds. That's a bit more of a challenge.
This time, I trained the AI with over 300,000 training samples after balancing, and the AI appears to be okay. There are a lot of little issues, but, overall, I am pleased. The main issue I have right now is with the controls themselves, I really want PID control, so this is going to be the next thing I put my focus towards. I think making this change will produce much better results, but we'll see. For now, the code used:
# create_training_data.py
import numpy as np
from grabscreen import grab_screen
import cv2
import time
from getkeys import key_check
import os
def keys_to_output(keys):
'''
Convert keys to a ...multi-hot... array
[A,W,D] boolean values.
'''
output = [0,0,0]
if 'A' in keys:
output[0] = 1
elif 'D' in keys:
output[2] = 1
else:
output[1] = 1
return output
file_name = 'training_data.npy'
if os.path.isfile(file_name):
print('File exists, loading previous data!')
training_data = list(np.load(file_name))
else:
print('File does not exist, starting fresh!')
training_data = []
def main():
for i in list(range(4))[::-1]:
print(i+1)
time.sleep(1)
paused = False
while(True):
if not paused:
# 800x600 windowed mode
screen = grab_screen(region=(0,40,800,640))
last_time = time.time()
screen = cv2.cvtColor(screen, cv2.COLOR_BGR2GRAY)
screen = cv2.resize(screen, (160,120))
# resize to something a bit more acceptable for a CNN
keys = key_check()
output = keys_to_output(keys)
training_data.append([screen,output])
if len(training_data) % 1000 == 0:
print(len(training_data))
np.save(file_name,training_data)
keys = key_check()
if 'T' in keys:
if paused:
paused = False
print('unpaused!')
time.sleep(1)
else:
print('Pausing!')
paused = True
time.sleep(1)
main()
The create_training_data.py file is used to record your actions and frames from the game, saving them to a numpy file.
# balance_data.py
import numpy as np
import pandas as pd
from collections import Counter
from random import shuffle
train_data = np.load('training_data.npy')
df = pd.DataFrame(train_data)
print(df.head())
print(Counter(df[1].apply(str)))
lefts = []
rights = []
forwards = []
shuffle(train_data)
for data in train_data:
img = data[0]
choice = data[1]
if choice == [1,0,0]:
lefts.append([img,choice])
elif choice == [0,1,0]:
forwards.append([img,choice])
elif choice == [0,0,1]:
rights.append([img,choice])
else:
print('no matches')
forwards = forwards[:len(lefts)][:len(rights)]
lefts = lefts[:len(forwards)]
rights = rights[:len(forwards)]
final_data = forwards + lefts + rights
shuffle(final_data)
np.save('training_data.npy', final_data)
The balance_data.py file is used to take the training data, and balance it evenly across actions to take. Every time file sizes were ~100MB, I saved the balanced file with an ID appended to it, and started a new one. This is so we can just load in chunks of 100MB at a time when training the network.
# train_model.py
import numpy as np
from alexnet import alexnet
WIDTH = 160
HEIGHT = 120
LR = 1e-3
EPOCHS = 10
MODEL_NAME = 'pygta5-car-fast-{}-{}-{}-epochs-300K-data.model'.format(LR, 'alexnetv2',EPOCHS)
model = alexnet(WIDTH, HEIGHT, LR)
hm_data = 22
for i in range(EPOCHS):
for i in range(1,hm_data+1):
train_data = np.load('training_data-{}-balanced.npy'.format(i))
train = train_data[:-100]
test = train_data[-100:]
X = np.array([i[0] for i in train]).reshape(-1,WIDTH,HEIGHT,1)
Y = [i[1] for i in train]
test_x = np.array([i[0] for i in test]).reshape(-1,WIDTH,HEIGHT,1)
test_y = [i[1] for i in test]
model.fit({'input': X}, {'targets': Y}, n_epoch=1, validation_set=({'input': test_x}, {'targets': test_y}),
snapshot_step=500, show_metric=True, run_id=MODEL_NAME)
model.save(MODEL_NAME)
# tensorboard --logdir=foo:C:/path/to/log
This trains the model for us, assuming you have the alexnet model. You can use whatever model you want, however. If you don't have the alexnet model file:
# alexnet.py
""" AlexNet.
Applying 'Alexnet' to Oxford's 17 Category Flower Dataset classification task.
References:
- Alex Krizhevsky, Ilya Sutskever and Geoffrey E. Hinton. ImageNet
Classification with Deep Convolutional Neural Networks. NIPS, 2012.
Links:
- [AlexNet Paper](http://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf)
"""
import tflearn
from tflearn.layers.conv import conv_2d, max_pool_2d
from tflearn.layers.core import input_data, dropout, fully_connected
from tflearn.layers.estimator import regression
from tflearn.layers.normalization import local_response_normalization
def alexnet(width, height, lr):
network = input_data(shape=[None, width, height, 1], name='input')
network = conv_2d(network, 96, 11, strides=4, activation='relu')
network = max_pool_2d(network, 3, strides=2)
network = local_response_normalization(network)
network = conv_2d(network, 256, 5, activation='relu')
network = max_pool_2d(network, 3, strides=2)
network = local_response_normalization(network)
network = conv_2d(network, 384, 3, activation='relu')
network = conv_2d(network, 384, 3, activation='relu')
network = conv_2d(network, 256, 3, activation='relu')
network = max_pool_2d(network, 3, strides=2)
network = local_response_normalization(network)
network = fully_connected(network, 4096, activation='tanh')
network = dropout(network, 0.5)
network = fully_connected(network, 4096, activation='tanh')
network = dropout(network, 0.5)
network = fully_connected(network, 3, activation='softmax')
network = regression(network, optimizer='momentum',
loss='categorical_crossentropy',
learning_rate=lr, name='targets')
model = tflearn.DNN(network, checkpoint_path='model_alexnet',
max_checkpoints=1, tensorboard_verbose=2, tensorboard_dir='log')
return model
Once trained, you can test the model with:
# test_model.py
import numpy as np
from grabscreen import grab_screen
import cv2
import time
from directkeys import PressKey,ReleaseKey, W, A, S, D
from alexnet import alexnet
from getkeys import key_check
import random
WIDTH = 160
HEIGHT = 120
LR = 1e-3
EPOCHS = 10
MODEL_NAME = 'pygta5-car-fast-{}-{}-{}-epochs-300K-data.model'.format(LR, 'alexnetv2',EPOCHS)
t_time = 0.09
def straight():
## if random.randrange(4) == 2:
## ReleaseKey(W)
## else:
PressKey(W)
ReleaseKey(A)
ReleaseKey(D)
def left():
PressKey(W)
PressKey(A)
#ReleaseKey(W)
ReleaseKey(D)
#ReleaseKey(A)
time.sleep(t_time)
ReleaseKey(A)
def right():
PressKey(W)
PressKey(D)
ReleaseKey(A)
#ReleaseKey(W)
#ReleaseKey(D)
time.sleep(t_time)
ReleaseKey(D)
model = alexnet(WIDTH, HEIGHT, LR)
model.load(MODEL_NAME)
def main():
last_time = time.time()
for i in list(range(4))[::-1]:
print(i+1)
time.sleep(1)
paused = False
while(True):
if not paused:
# 800x600 windowed mode
#screen = np.array(ImageGrab.grab(bbox=(0,40,800,640)))
screen = grab_screen(region=(0,40,800,640))
print('loop took {} seconds'.format(time.time()-last_time))
last_time = time.time()
screen = cv2.cvtColor(screen, cv2.COLOR_BGR2GRAY)
screen = cv2.resize(screen, (160,120))
prediction = model.predict([screen.reshape(160,120,1)])[0]
print(prediction)
turn_thresh = .75
fwd_thresh = 0.70
if prediction[1] > fwd_thresh:
straight()
elif prediction[0] > turn_thresh:
left()
elif prediction[2] > turn_thresh:
right()
else:
straight()
keys = key_check()
# p pauses game and can get annoying.
if 'T' in keys:
if paused:
paused = False
time.sleep(1)
else:
paused = True
ReleaseKey(A)
ReleaseKey(W)
ReleaseKey(D)
time.sleep(1)
main()
If you would like the training data and the trained model, you're in luck, here you go! Self-driving training data and a trained model