Играем в GTA V c Python. Часть XII: тестируем нейронную сеть для автопилота

Предыдущая статья — Играем в GTA V c Python. Часть XI: Обучение нейронной сети для автопилота.

Добро пожаловать в двенадцатую часть серии статей про применение методов машинного обучения в игре Grand Theft Auto V. Мы продолжаем создавать наш беспилотный автомобиль в этой игре.

В предыдущей статье мы обучили нашу нейронную сеть на игровых данных и теперь мы готовы посмотреть, что из этого вышло. В процессе обучения нейронной сети мы сохраняли результаты в отдельном файле. Это позволит нам в любой момент вернуться к уже обученной модели и даже дообучить ее, если потребуется.

Чтобы загрузить эти данные из файла, нам нужно иметь уже заданную модель TensorFlow. Дело в том, что этот записанный файл содержит только сохраненные веса, соответствующие нашей модели нейронной сети, и ничего более. Поэтому, чтобы это все работало, модель нашей нейронной сети должна быть определена заранее.

Мы уже сохраняли эту модель в отдельный файл, но на всякий случай напомним еще раз:

# alexnet.py

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 

Далее давайте создадим новый файл под названием test_model.py:

# test_model.py

import numpy as np
from PIL import ImageGrab
import cv2
import time
from directkeys import PressKey,ReleaseKey, W, A, S, D
from alexnet import alexnet

WIDTH = 80
HEIGHT = 60
LR = 1e-3
EPOCHS = 8
MODEL_NAME = 'pygta5-car-{}-{}-{}-epochs.model'.format(LR, 'alexnetv2',EPOCHS) 

Затем наша модель будет выводить [1,0,0], [0,1,0] или [0,0,1]. Эти выходные данные соответствуют левому повороту, движению вперед или правому повороту, поэтому мы хотим, чтобы эти исходы были сопоставлены с некоторыми фактическими действиями, вот они:

def straight():
    PressKey(W)
    ReleaseKey(A)
    ReleaseKey(D)

def left():
    PressKey(W)
    PressKey(A)
    ReleaseKey(D)

def right():
    PressKey(W)
    PressKey(D)
    ReleaseKey(A) 

Эти сопоставления — только начало. Обратите внимание, что на самом деле мы всегда нажимаем «W», что означает нажатие на газ. Возможно, со временем нам потребуется настроить это по-другому, но на данный момент это так.

Теперь определим модель и загрузим наши обученные веса:

model = alexnet(WIDTH, HEIGHT, LR)
model.load(MODEL_NAME)

Теперь мы начинаем цикл движения, который очень похож на нашу предыдущую поездку с беспилотным автомобилем. Отличие состоит в том, что мы принимаем решения, исходящие от нашей модели, через метод model.predict:

def main():
    last_time = time.time()
    
    for i in list(range(4))[::-1]:
        print(i+1)
        time.sleep(1)
        
    while(True):
        # 800x600 windowed mode
        screen =  np.array(ImageGrab.grab(bbox=(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, (80,60))
        cv2.imshow('',screen)
        moves = list(np.around(model.predict([screen.reshape(80,60,1)])[0]))
        print(moves)

        if moves == [1,0,0]:
            left()
        elif moves == [0,1,0]:
            straight()
        elif moves == [0,0,1]:
            right()

        if cv2.waitKey(25) & 0xFF == ord('q'):
            cv2.destroyAllWindows()
            break 

Мы обнаружили, что в отличие от предыдущего сценария автономного вождения, этот, вероятно, следует сразу запускать на полной скорости. Вы должны запустить его, сразу же перейти к игре и начать действовать как можно быстрее.

В реальности, на скорости наша модель ведет себя весьма неплохо. Что довольно впечатляюще, так как после балансировки наш набор обучающих данных сократился до 20,000 фреймов.

Есть одна вещь, которая может всех раздражать — это запуск / остановка нашего AI в процессе тестирования. Сразу понятно, что эта штука требует доработки, посколько мы хотим иметь возможность быстро остановить автопилот и перейти к самостоятельному вождению. Поэтому давайте внесем небольшие изменения в наш файл test_model.py:

from getkeys import key_check

Теперь у нас есть возможность использовать определенную кнопку клавиатуры для приостановки работы автопилота. Далее давайте слегка изменим основную функцию программы:

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)))
            print('loop took {} seconds'.format(time.time()-last_time))
            last_time = time.time()
            screen = cv2.cvtColor(screen, cv2.COLOR_BGR2GRAY)
            screen = cv2.resize(screen, (80,60))
            moves = list(np.around(model.predict([screen.reshape(80,60,1)])[0]))
            if moves == [1,0,0]:
                left()
            elif moves == [0,1,0]:
                straight()
            elif moves == [0,0,1]:
                right()
   
        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) 

Для остановки автопилота мы произвольно выбрали клавишу ‘T‘. Мы могли бы взять и ‘P‘, но тогда на паузу ставилась бы вся игра, чего нам бы не хотелось. Мы хотим остановить AI, сбросить все данные и начать все сначала без остановки самой игры. Поэтому пусть будет ‘T‘. Мы не очень этим заморачиваемся, но иногда действительно нужно поставить все на паузу и затем начать сначала. Для продолжения достаточно просто нажать эту клавишу еще раз.

Как бы то ни было, наш искусственный интеллект конечно не самый лучший, но вполне справляется с управлением даже на затемненных дорогах с отчетливо видимой разметкой. Для начала неплохо: видно, что мы на правильном пути.

Следующая статья — Играем в GTA V c Python. Часть XIII: улучшаем наш автопилот.