Код доступен здесь.
В части 1 этой серии мы получили скрипт на Python, чтобы видеть мир и (несмотря на это) ехать прямо, как правило, в стены. В этой части мы заставим водителя действовать немного умнее.
Когда дело доходит до автономных гонок, есть масса вариантов, и в теории легко запутаться. Начнем с простой парадигмы машинного обучения: обучения с учителем. Это означает, что мы будем обучать водителя, сначала показывая ему, как мы водим, а затем позволяя ему повторно наблюдать за нашим вождением, чтобы он мог скопировать наш стиль вождения и применить его сам.
Прежде чем мы начнем, нам нужно выбрать модель. Простой ответ для любого классификатора изображений — сверточная нейронная сеть. Это тип модели машинного обучения, которая особенно хороша для классификации изображений, и это именно то, что мы делаем — мы берем изображение, а затем решаем, двигаться ли вперед, влево, вправо или тормозить/назад.
Когда вы создаете нейронную сеть, у вас есть много вариантов настройки модели: сколько слоев, сколько нейронов, сколько сверточных фильтров, размер шага… список можно продолжить. Для нашей первой попытки мы постараемся уменьшить количество параметров. В колледже меня учили, что вы всегда хотите чрезмерно ограничивать модель, то есть иметь больше обучающих примеров, чем параметров. Обучающим примером в нашем случае является один снимок экрана из игры. Количество параметров модели определяется архитектурой нейронной сети, и оно может быстро исчисляться миллионами даже для довольно простых архитектур. Давайте попробуем удержать нашу цифру примерно до 10 000. Наш игровой цикл работает со скоростью около 20 кадров в секунду, поэтому мы можем сделать как минимум 10 000 снимков экрана чуть более чем за 8 минут.
Это определяется с помощью keras:
model = keras.Sequential([ keras.Input(shape=input_shape), layers.Conv2D(2, kernel_size=(30, 30), activation="relu"), layers.Flatten(), layers.Dropout(0.2), layers.Dense(num_outputs, activation="sigmoid"), layers.Dense(num_outputs, activation="sigmoid"), ])
С уменьшенной входной формой (300, 400, 1), что соответствует изображению в градациях серого 400x300, это дает нам около 13 000 параметров. Довольно близко к нашему идеалу ~ 10 000.
Полный обучающий код доступен в файле train_model.py.
Вау, мы сильно опередили себя. У нас есть прекрасная модель, но нам все еще нужно собрать данные для обучения. Полный код доступен в gather_training_data.py, но я дам вам краткую идею.
Нам нужно захватить ввод модели (т.е. экран) и вывод модели (т.е. наши нажатия клавиш). Мы установим его в цикле, в котором мы опрашиваем экран и наш ввод и сохраняем каждый в массиве. Затем, когда скрипт завершит работу, мы сохраним оба массива в файлы, чтобы мы могли получить доступ к данным при обучении нашей модели.
Перед запуском цикла мы создадим два массива (технически списки Python, но идея та же):
screen_captures = [] labels = []
Затем внутри цикла мы захватим нужные нам данные и сохраним их в массивах:
while frames_processed < MAX_FRAMES: frame_number = frames_processed # grab screen screen = np.array(ImageGrab.grab(bbox=(0, 40, 800, 640))) # get user input, then store as labelled data user_input = utils.get_user_input() screen_captures.append(processed_screen) labels.append(user_input[:4])
utils.get_user_input() использует модуль с именем keyboard для захвата ввода пользователя, а затем кодирует его по той же схеме, что описана в части 1 этой серии, с добавлением пробела и c для приостановки и выхода из обучения.
import keyboard def get_user_input(): """ Returns np.array with boolean values for whether w, a, s, d, space are pressed. indices: 0=w, 1=a, 2=s, 3=d, 4=space, 5=c """ bool_list = [keyboard.is_pressed("w"), keyboard.is_pressed("a"), keyboard.is_pressed("s"), keyboard.is_pressed("d"), keyboard.is_pressed("space"), keyboard.is_pressed("c")] float_array = np.array([float(1.0) if k else float(0.0) for k in bool_list]) return float_array
Теперь просто запустите collect_training_data.py, пока катаетесь в своей видеоигре. Время от времени контролируйте вывод, чтобы убедиться, что все выглядит правильно. Теперь, когда у нас есть данные для обучения, мы вернемся и обучим модель с помощью train_model.py, прежде чем перейти к хорошей части: позволить гонщику участвовать в гонке!
Код этой части находится в run_supervised.py.
model = keras.models.load_model('models/basic_cnn') model.summary() # game loop while frames_processed < MAX_FRAMESt: frame_number = frames_processed # grab screen screen = np.array(ImageGrab.grab(bbox=(0, 40, 800, 640))) # process image and display resulting image processed_screen = utils.process_image(screen) cv2.imshow('window', processed_screen) user_input = utils.get_user_input() # get model prediction model_input = np.expand_dims(np.array(processed_screen), -1).reshape((1, 300, 400, 1)) prediction = model.predict(model_input)[0] prediction_str = " ".join([f"{p:2.2}" for p in prediction]) print(prediction_str) # send input utils.send_input(prediction)
И вот мы идем! Просто запустите скрипт, чтобы увидеть, как работает ваш водитель!
Благодаря этой архитектуре и примерно 15-минутным тренировочным данным я заставил ИИ в основном передвигаться по трассе в Grid Autosport. Это не сумасшедший быстрый гонщик, но обычно он некоторое время едет прямо и рулит, когда видит приближающийся поворот. Тем не менее, он часто застревает. В следующий раз мы рассмотрим трюки, чтобы заставить водителя водить машину немного умнее.