Научить автомобиль ездить самостоятельно при помощи машинного обучения

Перевод выступления технического директора Overleaf Джона Лиса-Миллера.

Введение

Этот проект был личным, но в некоторой степени он связан с моей работой. Я занимаю должность технического директора в Overleaf, онлайн-редакторе LaTeX. Сегодня платформа насчитывает 3 млн пользователей.

Я и мой коллега-сооснователь прежде занимались беспилотными автомобилями.

Научить автомобиль ездить самостоятельно при помощи машинного обучения

Overleaf мы запустили, одновременно проектируя Heathrow Pods, первую в мире систему беспилотных такси. Она открылась в 2011 году и до сих пор перевозит пассажиров.

Научить автомобиль ездить самостоятельно при помощи машинного обучения

Ещё в 2011 году нам удалось создать сеть беспилотников благодаря отдельной сети дорог. Heathrow Pods — закрытая система, поэтому мы полагались на традиционные методы разработки и проверки безопасности.

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

Один из пионеров этой области — Себастьян Трун, профессор Стэнфорда и победитель гонки DARPA Grand Challenge в 2005 году, во многом ознаменовавшей современную эпоху беспилотников. Позже Трун основал платформу онлайн-обучения Udacity. В 2016 году на сайте появился курс по беспилотным машинам. Я записался.

Себастьян Трун
Себастьян Трун

В основе моего выступления — одна из лабораторных работ курса, которая, в свою очередь, опирается на доклад Nvidia «Сквозное обучение в беспилотных автомобилях».

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

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

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

Наша задача на той лабораторной — воспроизвести эту магию. Пойдём по тому же пути:

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

Поехали.

Собираем данные для обучения

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

В правом верхнем углу видна иконка «Запись»: мы записываем изображение с камеры и углы поворота в каждом кадре десять раз в секунду.

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

Синие пики — нажатия кнопок; зелёные и красные кривые — результаты применения экспоненциального и Гауссового сглаживания соответственно; второе оказалось лучше
Синие пики — нажатия кнопок; зелёные и красные кривые — результаты применения экспоненциального и Гауссового сглаживания соответственно; второе оказалось лучше

Следующее препятствие. Если ехать только по центру, система не поймёт, что делать в случае смещения машины к краю. Дело решается записью возврата на курс: я останавливаю запись, подъезжаю к краю, возобновляю запись и возвращаюсь на середину.

Таким образом машина учится возвращаться в центр трассы (надеюсь, это не научит её съезжать).

Проехавшись по дороге несколько раз и записав возврат на курс, я получил 11 тысяч кадров:

Научить автомобиль ездить самостоятельно при помощи машинного обучения

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

Перенос обучения

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

Научить автомобиль ездить самостоятельно при помощи машинного обучения

В нашем проекте мы задействуем сеть Inception v3, обученную Google для состязания по распознаванию изображений. На входе — картинка, на выходе — класс изображения, вещь на нём. Если в систему загрузить фото слева, она скажет, что на нём сибирская хаски, если справа — эскимосская собака (не уверен, что сам бы их различил).

Сеть Inception огромна — более 25 млн параметров; на её обучение Google потратила немало сил и денег. Как же нам её адаптировать? Ответ прост: мы проведём «лоботомию» и вытащим лишь первые 44 слоя (отмечены красной рамкой).

Научить автомобиль ездить самостоятельно при помощи машинного обучения

Почему это работает? Дело в том, что вырезанные слои типичны для обработки изображений (например, детекторы границ). Разница между породами собак определяется позже, как и другие классы изображений нам не нужные. Мы дополним группу Inception тремя собственными слоями:

Научить автомобиль ездить самостоятельно при помощи машинного обучения

Нам придётся обучить лишь собственные доработки, не трогая секцию Inception. Будем думать, что в таком случае мы обойдёмся меньшим количеством данных, чем если бы начали с нуля.

Добавлю, что архитектура последних трёх слоёв выбрана методом проб и ошибок. Были варианты попроще, но этот оказался самым простым работающим.

Свёрточные нейронные сети

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

  • свёртка;
  • изменение размера (особенно в меньшую сторону);
  • функции активации.

Рассмотрим каждый по очереди. В качестве образца возьмём кадр ниже:

Научить автомобиль ездить самостоятельно при помощи машинного обучения

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

Первой идёт свёртка — простая, но универсальная операция. Для её выполнения нам потребуется ядро — небольшая числовая матрица. Мы задействуем ядро 3x3 пикселя.

Научить автомобиль ездить самостоятельно при помощи машинного обучения

Начнём с того, что совместим ядро такого же размера с частью изображения (левый верхний угол). Затем перемножим каждый пиксель этой области на соответствующее значение в матрице ядра и сложим произведения — первый пиксель выходного изображения готов. Так мы движемся от пикселя к пикселю, каждый раз прибавляя к изображению на выходе одну точку.

Как работает свёртка

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

Чтобы задействовать свёртку в нейросети, нужно не высчитывать ядра самостоятельно, а позволить системе самой проанализировать данные и вывести множество ядер. Используемые слои Inception располагают около 700 тысячами параметров, немало из которых — параметры ядер, обученные Google на 1,2 млн изображений.

Научить автомобиль ездить самостоятельно при помощи машинного обучения

Запуская свёртку с таким большим количеством ядер, мы из одного изображения на входе получаем сразу несколько — по одному на каждое ядро. Но продолжай мы добавлять картинки, память бы закончилась. Поэтому-то нам нужен второй блок — изменение размера.

Подвыборка
Подвыборка
Научить автомобиль ездить самостоятельно при помощи машинного обучения

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

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

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

Перейдём к функциям активации — в словосочетании «нейронная сеть» появляется слово «нейронный».

Научить автомобиль ездить самостоятельно при помощи машинного обучения

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

Так, у нейрона есть дендриты, «входы», соединённые с восходящими нейронами. Если «входы» в сумме преодолевают определённый порог, этот нейрон активируется и отсылает сигнал через аксон в нисходящие нейроны.

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

Вот и всё. Эти три операции мы повторяем снова и снова. Отмечу, однако, что свёртка (по крайней мере, тип используемый здесь) — линейная операция. Не будь изменение размера и функции активации нелинейными, композиция свёрток попросту схлопнулась бы в одну большую линейную функцию.

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

Сборка

С теорией покончено. Теперь посмотрим, на что способна наша сеть, используя картинку-образец. Пропустив её через 44 слоя сети Inception, мы получим 256 изображений в серых тонах; их стороны примерно в десять раз меньше сторон изображения на входе, но они гораздо глубже.

Вот выборка из девяти случайных изображений, начиная с 42-го. Каждая представляет собой один из откликов, который нейронная сеть даёт на тестовое изображение. Светлый пиксель означает, что в этой точке изображения нейрон активирован, то есть он отвечает на какую-то черту соответствующей части входного изображения. Тёмный пиксель — нейрон не активирован.

Научить автомобиль ездить самостоятельно при помощи машинного обучения

Истолковать ответ нейронов будет проще, если наложить его на входное изображение:

Научить автомобиль ездить самостоятельно при помощи машинного обучения

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

На 43-ем (сверху в середине), похоже, видна поверхность дороги — тоже может пригодиться. Кое-где, правда, проступает фон, но 48 изображение (справа в средней строке) почти полностью берёт его на себя. Комбинация этих изображений, судя по всему, даст нам полезную информацию.

Замечу: для этой части сети мы не указывали черты, необходимые в решении нашей задачи. На самом деле она обучена Google для классификации изображений.

Это переносит нас к фрагменту сети, который мы обучаем сами, — те три слоя, добавленные в конце.

Научить автомобиль ездить самостоятельно при помощи машинного обучения

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

Такой выбор архитектуры обусловлен выше — вместо изображения 42, 43 и 48 могут хорошо работать на распознавание дороги. Мы же хотим, чтобы сеть отбирала самые полезные комбинации, верно?

В этом примере мы выберем 64 линейные комбинации из 256 изображений. Ниже — изображения до и после свёртки с ядром 1х1. Они похожи, но вторая группа в целом ярче и сглаженнее.

Научить автомобиль ездить самостоятельно при помощи машинного обучения
Научить автомобиль ездить самостоятельно при помощи машинного обучения

И вновь на некоторых изображениях отчётливо проявляются края дороги, например на 45-м (на этот раз из 64 картинок).

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

Научить автомобиль ездить самостоятельно при помощи машинного обучения

Схема справа описывает одну из таких комбинаций — один нейрон — в одном полносвязном слое. Такой слой состоит из множества нейронов, соединнёных со всеми выходами предыдущего слоя. Здесь мы обучаем веса wi и выходы xi. И снова мы применяем функцию активации f к каждому нейрону — вносим нелинейность.

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

Обучение

Ну, почти готовы. Даже для последних трёх слоёв требуется выставить немало гиперпараметров, перед тем как полностью определить сеть и сценарии обучения. Сколько ядер необходимо в свёртке 1х1? Насколько велик должен быть каждый полносвязный слой? Какое сглаживание применить к углу поворота?

Научить автомобиль ездить самостоятельно при помощи машинного обучения

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

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

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

Layer (type) Output Shape Param # Connected to ==================================================================================================== convolution2d_1 (Convolution2D) (None, 17, 37, 64) 16448 convolution2d_input_1[0][0] ____________________________________________________________________________________________________ flatten_1 (Flatten) (None, 40256) 0 convolution2d_1[0][0] ____________________________________________________________________________________________________ dense_1 (Dense) (None, 32) 1288224 flatten_1[0][0] ____________________________________________________________________________________________________ dense_2 (Dense) (None, 1) 33 dense_1[0][0] ==================================================================================================== Total params: 1304705 ____________________________________________________________________________________________________ Epoch 1/30 27144/27144 [==============================] - 160s - loss: 79.0047 - val_loss: 0.0780 Epoch 2/30 27144/27144 [==============================] - 147s - loss: 25.4130 - val_loss: 0.0692 Epoch 3/30 27144/27144 [==============================] - 148s - loss: 8.4912 - val_loss: 0.0670 Epoch 4/30 27144/27144 [==============================] - 148s - loss: 2.9383 - val_loss: 0.0638 … Epoch 12/30 27144/27144 [==============================] - 148s - loss: 0.0851 - val_loss: 0.0572 Epoch 13/30 27144/27144 [==============================] - 148s - loss: 0.0785 - val_loss: 0.0568 Epoch 14/30 27144/27144 [==============================] - 148s - loss: 0.0802 - val_loss: 0.0546 Epoch 15/30 27144/27144 [==============================] - 147s - loss: 0.0769 - val_loss: 0.0569 Epoch 16/30 27144/27144 [==============================] - 147s - loss: 0.0793 - val_loss: 0.0560 Epoch 17/30 27144/27144 [==============================] - 148s - loss: 0.0832 - val_loss: 0.0574

Тут много всего интересного, но я прокомментирую следующее. В начале — краткое описание обучаемой модели, включающее несколько параметров, которым нужно соответствовать. Помните: слои Inception остаются неизменными, мы занимаемся обучением лишь блока на конце объединённой сети.

Во второй секции — процесс обучения, который Keras описывает по мере развития. Я разбил собранные данные для обучения на две части: обучающую (80%) и контрольную (20%). Каждую эпоху Keras записывает значение средней абсолютной ошибки в прогнозах сети по обучающей (loss) и контрольной (val_loss) группам.

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

Спустя 17 эпох потери в десятки раз ниже, около 0,08 пункта. Обучение заканчивается тогда, когда начинают расти потери в контрольной выборке (val_loss), — большее число эпох весьма вероятно приведёт к переобучению.

Запускаем модель с самыми низкими потерями в контрольной группе

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

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

Можно заметить, как машина виляет и возвращается на курс. В конечном счёте она переусердствует и съезжает с дороги. Что ж, старт неплохой: видно, как машина пыталась удержаться на пути.

Доработки

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

  • Различные функции потерь: как нам измерить ошибку? Применив формулы среднего квадрата ошибки и средней абсолютной ошибки, я остановился на втором варианте.
  • Различные функции активации: я попробовал сигмоиду, гиперболический тангенс и выпрямитель; тангенс показал себя лучше.
  • Различная регуляризация: отбраковка весов, чтобы они не становились слишком большими, — распространённая практика избежания переобучения; я перебрал несколько весов L2-регуляризации.
  • Различные распределения начальных весов решили пару проблем со сходимостью во время обучения.
  • Различные размеры слоёв: сколько нейронов в каждом скрытом слое?
  • Различные архитектуры сети — добавить ли слоёв? Или убрать?

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

Задержка контроллера — 0,35 секунды
Задержка контроллера — 0,35 секунды

Контроллер тратил слишком много времени на обработку каждого кадра, поэтому подруливать он мог лишь три раза в секунду.

Кроме того, дальнейшее расследование показало: система проводила большую часть времени на слоях Inception. С одной стороны, проблему можно было решить покупкой более быстрого ноутбука. С другой — количество слоёв Inception можно сократить с 44-х до 12-ти (на картинке семь: остальные невидимы).

Научить автомобиль ездить самостоятельно при помощи машинного обучения

Уменьшаем задержку

Посмотрим, как система ведёт себя при низкой задержке (около 0,1 секунды вместо 0,35 секунды):

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

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

Результат

Со всеми дополнениями сеть смогла проехать полный круг:

Я поменял настройки газа, чтобы машина разгонялась до 50 километров в час — максимальной скорости в этой версии симулятора. Кое-где она по-прежнему виляет, но уже не разбивается. Успех.

Расширение

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

Едет. И вновь автомобиль сбивают с толку деревья — на 24 секунде он чуть не врезался в ограждение, но дальше всё равно поехал не туда. Учитывая простоту нашей сети по меркам нейросетей и короткое обучение, факт того, что машина так хорошо едет по неизвестному пути, пусть и в том же симуляторе, — довольно неплохой результат.

Заключение

В этом выступлении мы:

  • взяли нейронную сеть, обученную распознаванию пород собак;
  • переделали её под управление автомобилем;
  • научили систему водить, располагая 35 минутами образцов;
  • посмотрели, как она едет по незнакомому пути.

Более того:

  • в 2015 году мало кто считал, что и это будет возможно;
  • в 2016 году тысячи людей вроде меня занимались обучением беспилотников в свободное время на онлайн-курсах;
  • в 2018 году я руководил (совсем немного) работой школьника Джоша, который проделал всё это сам на радиоуправляемой машинке с Raspberry Pi.

Код проекта находится в открытом доступе.

66
2 комментария

Не до конца понятно, но интересно :) Не знал, что можно резать готовые нейронки и переиспользовать для других задач. ЗЫ, вспомнился мем:

Ответить

Погружение в сон от статьи - 45 секунд. Спасибо!

Ответить