Играем в GTA V c Python. Часть X: балансировка данных для обучения нейронной сети

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

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

Перед тем как перейти к обучению нашей нейронной сети, нам надо подумать про один вопрос. Дело в том, что скорей всего большая часть наших данных соответствует движению вперед (то есть правильным действием будет движение вперед). Если мы закинем данные в нейронную сеть в таком виде, например если 80% данных будут соответствовать движению вперед, то она обучится всегда предсказывать именно этот класс данных, за исключением совсем очевидных случаев. Проблема в том, что такая сеть практически наверняка переобучится. То есть на тренировочной и проверочной выборке точность будет в районе 99%, а когда вы возьмете произвольные данные или вообще запустите ее на реальную игру, результат вас сильно разочарует.

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

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

# 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))) 
                                                   0          1
0  [[50, 62, 173, 200, 188, 135, 131, 131, 125, 1...  [1, 0, 0]
1  [[38, 38, 163, 183, 175, 160, 129, 129, 128, 1...  [1, 0, 0]
2  [[82, 41, 93, 147, 191, 160, 138, 134, 112, 12...  [1, 0, 0]
3  [[80, 32, 28, 85, 61, 138, 191, 191, 158, 140,...  [0, 1, 0]
4  [[49, 31, 70, 89, 111, 61, 29, 52, 56, 83, 194...  [0, 1, 0]
Counter({'[0, 1, 0]': 70365, '[0, 0, 1]': 6708, '[1, 0, 0]': 6427})

Мы даже еще не рассмотрели все 100К наших данных, но уже хорошо видно, насколько они разбалансированны. Если бы мы обучали на них нашу нейронную сеть, она бы научилась только движиению вперед.

Теперь просто заметим, что обычно нейронная сеть работает следующим образом: она имеет некий выходной слой, к которому программист применяет функцию argmax() , чтобы получить искомый результат. Но, фактически, выходной слой почти никогда не выдает идеальне значения 1 или 0, это будут числа вроде 0,85521 или 0,1241, и тому подобные. Таким образом, чтобы с большей вероятностью обнаруживать повороты, мы можем задать для них другие пороговые значения. Cледовательно, даже если результат функции argmax() велит нам ехать прямо, мы все равно можем настроить нейронную сеть на поворот. Все это очень гибко. Многие часто не учитывают, что они могут применить дополнительный алгоритм к выходным данным своей нейронной сети, чтобы получить то, что им на самом деле нужно.

Но мы все равно будем балансировать данные!

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_v2.npy', final_data) 

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