Отчет о проекте Udacity 3 - Клонирование поведения

1. Цель

В третьем проекте Udacity Self-Driving Car nanodegree (www.udacity.com) нам предлагается разработать систему, которая автономно управляет автомобилем в смоделированной среде.

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

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

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

Легко?

No!

Посмотрите на мою первую попытку сделать этот проект. Машина упала в реку! Десятка других попыток тоже не удалась: машина съехала с дороги, разбилась о мост и так далее.

2. Введение: что должна делать система

То, что легко для человека (распознавание изображений и соответствующий угол поворота), очень и очень сложно для компьютера.

2.1 Распознавание изображений и определение угла поворота

Распознавание изображений неструктурированных файлов - сложная задача для компьютера.

Для нас практически невозможно не увидеть изображение и не распознать то, что внутри него. Но для компьютера изображение - это просто набор чисел. В частности, это массив чисел от 0 до 255 в трех каналах (красный, зеленый, синий) для каждого пикселя изображения.

Какие особенности изображения важны для определения угла поворота?

Актуальны ли облака на небе? Река важна? Дорожки важны? Или цвета дороги? Что происходит в той части схемы, где нет линий (например, грязный выход ниже?). Это тривиальные вопросы для людей, но машину нужно обучить учитывать правильные особенности.

Проблема сложная. Этот учебник также обширен. При необходимости будьте готовы к прыжкам.

Я подчеркнул также моменты, которые не работают. Мы учимся на ошибках больше, чем на успехах.

2.2 Подход и инструменты

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

Использовались следующие инструменты: Keras (оболочка, работающая над Tensor Flow), OpenCV (для выполнения некоторых преобразований изображений), numpy (матричные операции) и python (чтобы собрать все это вместе).

3. Анализ изображений.

Первый вход - это изображения камеры внутри автомобиля. Есть три камеры: центральная, левая и правая.

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

Угол поворота нормализован от -1 до 1, что в автомобиле соответствует - От 25 до 25 градусов.

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

Плохие новости. Просто передать необработанные изображения в сеть не получится.

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

Сеть не сойдется к хорошему решению. Мы должны предварительно обработать данные: сделать то, что называется «увеличением изображения».

4. Увеличение изображения

4.1 Центральные и боковые изображения

Итак, у нас есть данные с боковых камер. Но что с этим делать?

Следуя предложению великого форума carND (кстати, почти все эти советы оттуда), я добавил угол коррекции 0,10 к левому изображению и -0,10 к правому. Идея состоит в том, чтобы центрировать автомобиль, избегая границ.


4.2 Отражение изображений

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

if np.random.uniform()>0.5:
  X_in[i,:,:,:] = cv2.flip(X_in[i,:,:,:],1)
  Y_in[i] = -p[i] #Flipped images

Будьте осторожны, чтобы правильно использовать функцию. Флип с нулевым параметром поступит неправильно.

4.3 Случайное боковое возмущение

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

pix2angle = -0.05 #Opposed direction
latShift = random.randint(-5,5) 
M = np.float32([[1,0,latShift],[0,1,0]])
imgTranslated = cv2.warpAffine(img,M,(img.shape[1],img.shape[0]))

4.4 Изменить размер

Из-за вычислительных ограничений рекомендуется изменять размер изображения после его обрезки. При использовании размера бумаги NVIDIA (66 x 200 пикселей) у моего ноутбука закончилась память. Я пробовал (64 x 64), (64 x 96), (64 x 128)…

Я также уменьшил размер шага сверточных слоев. Поскольку изображение меньше, шаг также может быть меньше.

X_in[i,:,:,0] = cv2.resize(imgScreenshots[i].squeeze(), (size2,size1))

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

4.5 Обрезка

Изображение было обрезано, чтобы удалить ненужные части изображения, такие как небо или деревья. При этом мы предполагаем, что камера зафиксирована в устойчивом положении.

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

crop_img = imgRGB[40:160,10:310,:] #Throwing away to superior portion of the image and 10 pixels from each side of the original image = (160, 320)

Имеет смысл выполнить преобразования (например, боковой сдвиг) до кадрирования, поскольку мы теряем информацию. Поступая наоборот, мы накормим модель изображением с боковой черной полосой.

4.6 оттенки серого, YUV, HSV

Я пробовал оттенки серого, полноцветный, канал Y YUV, канал S HSV…

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

Some conversion commands:

 imgOut = cvtColor(img, cv2.COLOR_BGR2YUV) 
 imgOut = cvtColor(img, cv2.COLOR_BGR2HSV)

Но настоящая проблема заключалась в том, что OpenCV (команда cv2.imread) считывает изображение в BGR, а код в drive.py - в RGB.

Я использовал команду преобразования opencv для преобразования изображения в RGB.

imgOut = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

В модели использован полноцветный RGB. В дополнение к этому, я использовал первый слой NN, который просто преобразует 3 цветовых канала в 1 канал с размышлениями, которые выбирает Керас, вместо того, чтобы я выбирал правильный канал.

model.add(Convolution2D(1,1,1, border_mode = ‘same’,init =’glorot_uniform’,input_shape=(size1,size2,3)))

4.7 Нормализация
Нормализация выполняется, потому что нейронная сеть обычно работает с небольшими числами: сигмовидная активация имеет диапазон (0, 1), tanh имеет диапазон (-1,1).

Изображения имеют 3 канала (красный, зеленый, синий), каждый канал имеет значение от 0 до 255. Нормализованный массив имеет диапазон от -1 до 1.

X_norm = X_in/127.5–1

5. Нейронные сети

5.1 Архитектура

После того, как обработка и увеличение изображения завершены, пришло время разработать нейронную сеть (NN). Но существует бесконечное количество архитектур нейронных сетей.

В качестве отправной точки я использовал сквозную модель NVIDIA (https://arxiv.org/abs/1604.07316), конфигурация которой описана ниже.

В общих чертах задачи сети можно описать двумя способами: распознавать соответствующие особенности и прогнозировать угол поворота.

Распознавание признаков осуществляется сверточным слоем

Что такое свертка?
Операция свертки похожа на окно, которое скользит по изображению и производит скалярное произведение. Он работает как распознаватель какой-то функции. Например, при свертке треугольника часть изображения, которая имеет этот треугольник, будет иметь максимальное значение, в то время как некоторое полностью некоррелированное изображение будет иметь минимальное значение.

Почему так много сверточных слоев?

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

Важно подчеркнуть, что мы не даем явных характеристик, которые изображение должно распознать. Это автоматически выполняется обратным распространением нейронной сети. У него есть свои плюсы и минусы. Плюс в том, что мы просто питаем сеть. Минус в том, что если он не работает, мы не понимаем, почему он не работает - сеть слишком мала? Слишком большой?

Учитывая особенности, как определить угол поворота?

Это делается полносвязными слоями после сверточных.

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

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

5.2 Функция ошибки потери

Как сравнить прогнозируемые и фактические углы?

Опять же, множество вариантов: средняя абсолютная ошибка, среднеквадратическая ошибка. Чтобы не сильно усложнять и потому что это не имеет большого значения, я использовал среднеквадратичную ошибку. Это значение - всего лишь индикатор. Настоящая проверка - действительно ли машина едет по трассе.

model.compile(optimizer=Adam(lr=LEARNING_RATE), loss=’mse’)

5.3 Большие / Меньшие сети

Поскольку модель NVIDIA тяжелая, я попробовал сеть меньшего размера. Насколько маленький или большой - вопрос в миллион (а может и больше). Нет точного подхода, чтобы решить это. Просто некоторые общие индикаторы: слишком маленькая сеть не научится решать, слишком большая сеть подойдет. Есть диапазон, который хорошо работает. А способ найти модель - это испытать бесконечное количество комбинаций…



5.4 Выпадение

Слой выпадения с вероятностью 50% использовался после первого полностью связанного слоя.

Идея выпадающего слоя очень интересна. Сеть должна быть достаточно устойчивой, чтобы делать прогноз (или клонировать поведение) даже без некоторых из этих подключений. Это похоже на попытку распознать изображение, просто глядя на его части. 50% говорят, что только случайная половина нейронов в этом слое будет оставаться на шаге подгонки. Если бы это было 20%, он сохранит 80% нейронов в Керасе. В TensorFlow все наоборот, останется 20%.

Лучше избегать переобучения. Сеть может запоминать неправильные особенности изображения. Запоминать деревья на дороге - плохое обобщение. Пропуск помогает избежать этого.

Почему выпадает только первый полностью связанный слой? А почему 50%? Собственно, есть несколько других возможных решений. Нет однозначного правильного ответа, многое из этого эмпирически.

5.5 Скорость обучения

Использовалась скорость обучения 0,001 с оптимизатором Adam. Оптимизатор Adam - хороший выбор в целом, потому что он автоматически настраивает скорость обучения.

Слишком малая скорость обучения, и модель не будет учиться. Слишком большой, и он разойдется. Мне вспоминается отрывок из Сунь-Цзы, примерно такой: Если передние войска пойдут слишком быстро, тыловые войска будут разделены. Если все пойдут слишком медленно, мы не доберемся до места назначения.

5.6 Функция активации

Я попробовал tanh, потому что он имеет диапазон вывода от -1 до 1. Но были проблемы с конвергенцией. Доказательством этого было то, что у автомобиля был нулевой градус поворота. Или только выход 25 градусов. Это явление исчезающего градиента, когда градиент в процессе обратного распространения достигает нуля, и нейрон умирает.

Функция активации ELU оказалась превосходной. В этой статье обсуждается, что ELU лучше, чем RELU, функция, которая избегает исчезающего градиента, но сохраняет нелинейность сети. (Http://www.picalike.com/blog/2015/11/28/relu-was-yesterday-tomorrow-comes-elu/).

Есть и другие функции активации, которые могут работать, но я тестировал только эти две.

6. Запись данных

Нейронным сетям нужны данные. Высокое качество и большой объем данных. В этом случае Udacity предоставил набор данных для работы. Но в дополнение к этому мы могли также собирать новые данные, играя на симуляторе как в видеоигру и записывая ее.

Управлять автомобилем в симуляторе можно двумя способами: клавиатурой или джойстиком.

- Клавиатура: не рекомендуется, если вы не очень хороший игрок. Это потому, что углы резче, чем при использовании джойстика. В наборе данных будет много нулей и много высоких углов. И НС будет сложно это обобщить. Мусор на входе, мусор на выходе.

- Джойстик: я одолжил джойстик PS3 и попытался записать свои данные. Добавил собранные данные к данным, предоставленным Udacity. Но я настолько ужасный игрок, что мои данные были бесполезны. Это дало худший результат. Я выбросил его и использовал только данные Udacity. Но в целом, если вы сможете собрать хорошие данные, это упростит задачу NN.

7. Еще полезные советы.

Несколько практических и полезных советов для этого проекта

Генератор данных 7.1

Обычно для обучения NN требуется большое количество данных. Загрузка всего этого в память может занять много времени и исчерпать ресурсы.
У меня есть ноутбук с 8 Гб оперативной памяти. Если я попытался загрузить 24 000 цветных изображений, ему не хватило памяти.

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

def generator(nbatch):
  #Load nbatch images and do the processing
  yield X_norm, Y_norm #Yield is the “return” for generators

Использование генератора:

n_epoch =15
for e in range(n_epoch):
  print(“ETAPA %d” % e)
  for X_train, Y_train in generator(2000):
  model.fit(X_train, Y_train, batch_size=200, nb_epoch=1, verbose=1, validation_split=0.20)
  model.save_weights(“model_” + str(e) + “.h5”)

7.2 Активация на последнем уровне

Наивная ошибка - вставлять функцию активации в последний слой. Поскольку каждый слой имеет нелинейную активацию, почему не последний?

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

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

7.3 Графические процессоры

Гиперпараметров так много, что проверить все комбинации становится экспоненциально невозможно.

Поскольку это сложная проблема и требует дорогостоящих вычислительных ресурсов, настоятельно рекомендуется использовать мощность графических процессоров (графических процессоров).

Из базовой компьютерной архитектуры мы обнаруживаем, что есть ЦП, устройства ввода (мышь, клавиатура) и устройства вывода (монитор, принтер). С развитием компьютеров, в основном из-за видеоигр, графика, отображаемая на компьютере, становилась все более сложной и с большим разрешением. Одним из решений, разработанных для такой тяжелой графики, был GPUS.

Процессор похож на машину. Он движется очень быстро и переносит мало данных. Графический процессор похож на поезд. Он медленнее, чем CPU, но переносит в несколько раз больше данных. Если вам нужно перевезти миллионы людей, лучше иметь метро, ​​чем перевезти их на машине.

Установить драйверы для работы с графическим процессором было непросто. Несколько дней занимаюсь этим. В первый раз я застрял на странице входа, которая больше не входила. Сначала какая-то паника, потом несколько часов поисков в Google для решения проблемы, и я вернулся к нулю… На следующий день я попробовал другой подход. Теперь все прошло успешно.

Использование графических процессоров было разницей между разумным временем для решения проблемы и невозможным требованием времени.
Один единственный прогон модели занимал 40 минут в CPU по сравнению с чуть более 10 минутами в GPU. Огромная разница, когда вы работаете полный рабочий день, имеете двух маленьких дочерей и несколько драгоценных часов в сутки, чтобы работать над чем-то другим.

Помимо увеличения скорости есть выигрыш в памяти. У моего ноутбука 8 Гб оперативной памяти. Видеокарта (NVIVIA GeForce 920M), 2 Гб. Как будто мне дали дополнительно 2 Гб памяти. Имея только ЦП, я не мог работать ни с чем, кроме модели, потому что 100% ресурсов было выделено на нее. С помощью графического процессора я могу запускать модель и одновременно заниматься другими делами.

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

7.4 Сохранение эпох и загрузка сохраненных

Хороший совет - спасать эпохи. Не каждую эпоху, а что-то вроде каждых 3 эпох, как в приведенном ниже коде. Поскольку подгонка NN занимает так много времени, сложно потратить полчаса на запуск модели, чтобы обнаружить, что она не работает.


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

if e % 3 ==0: #Save each 3 epochs
 model.save_weights(“model_” + str(e) + “.h5”)

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

model.load_weights(“model.h5”)

7.5 RGB, BGR, размеры OpenCV

Я уже приводил путаницу с RGB BGR. Другая проблема - это размеры изображений в OpenCV. Я ошибочно путал строки и столбцы. Это хороший совет - сделать двойную проверку.

В общем, каждый шаг в OpenCV необходимо проверять визуально, например в блокноте Jupyter. Так много деталей, и одна-единственная неправильная деталь может все испортить.

7.6 Различия между model.py и drive.py

Поскольку существует один файл для создания модели, а другой - для запуска на симуляторе, каждое преобразование изображения, выполненное в одном, должно выполняться в другом. Это очевидно, но я потерял несколько дней, потому что файлы выполняли разные преобразования, а результаты не имели смысла.

7.7 Форум, Github

Udacity - это онлайн-платформа. Так что здесь нет настоящих одноклассников, которым можно было бы помочь или обсудить. Общаться с другими можно с помощью форумов, групп в фейсбуке и изучения чужих проектов на Github.

Конечно, не рекомендуется копировать чей-то проект, но идея состоит в том, чтобы понять разные подходы и реализовать свою работу.

В техникуме, где я закончил, никто не обманул. Это был кодекс чести студентов. Вот ссылка с объяснением этого (на португальском языке): https://ideiasesquecidas.com/2016/04/05/confesso-que-colei/

8. Заключение

Вот ссылка на этот проект на Github. Https://github.com/asgunzi/CarND-Simulator

А вот видео, на котором машина проезжает круг. Это все еще немного зигзагообразно.

Научить машину водить даже в управляемой, очень упрощенной среде - непростая задача. Представленные инструменты и методы очень новые. Например, TensorFlow был выпущен в декабре 2015 года.

Возможно, задача вождения - непростая задача даже для человека. Почти год у меня ушло на то, чтобы научиться водить: привыкнуть к машине, выучить правила и так далее.

Проблемы: нейронные сети по-прежнему слишком тяжелые, требуют слишком больших вычислительных мощностей и обработки. Дизайн и обучение сети - болезненный процесс. Хорошая сторона этого состоит в том, что есть много вещей, которые нужно улучшить: впереди еще целое будущее!

Я хотел бы поблагодарить Udacity за предоставленный курс такого высокого уровня в этой развивающейся области и такой отличный проект.

Этот текст был написан во время прослушивания A Felicidade - Carminho canta Tom Jobim. Https://www.vituga.com/watch_video.php?v=YN56xhy6wRes

Арнальдо Гунци - консультант и инженер

Февраль / 2017

Проекты на срок 1:

Другие статьи: https://medium.com/@arnaldogunzi

Главный блог: https://ideiasesquecidas.com/

9. Ссылки

Мой личный блог: http://ideiasesquecidas.com

Udacity: https://www.udacity.com/

Тензорный поток: https://www.tensorflow.org/

Керас: http://keras.io/

Numpy, Scikit Learn: http://scikit-learn.github.io/stable

NVIDIA End-to-End: https://arxiv.org/abs/1604.07316

Статья Ле Куна: http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf

ELU: http://www.picalike.com/blog/2015/11/28/relu-was-yesterday-tomorrow-comes-elu/

Исключение: https://www.cs.toronto.edu/~hinton/absps/JMLRdropout.pdf