В моем последнем посте я пытался классифицировать карты покемонов по их типу. Результаты были довольно хорошими, но я не совсем понимал, что делаю. Я тренировал нейронную сеть MLP, написанную на sklearn, чтобы проводить классификацию, но понятия не имел, что происходит за кулисами.

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

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

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

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

Сверточная нейронная сеть

Так выглядит типичная свёрточная сеть

Как видно из рисунка, сеть свертки состоит из нескольких этапов (свертка, функция активации, объединение, выпадение и вывод). На мой взгляд, все они в основном представляют собой шаги, которые имеют n-мерный массив на входе и m-мерный массив на выходе.

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

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

Слой свертки

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

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

Чтобы вычислить матрицу I * K, нам нужно иметь все подматрицы из матрицы I. Итерация начинается с верхнего левого значения матрицы I. Каждую итерацию мы собираемся пропускать n значений (n задается как шаг имени параметра) до значения для чтения.

Взяв карту Чаризарда, это один из слоев после свертки. Похоже, этот фильтр удаляет все, кроме фона Покемона.

Слой активации

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

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

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

Уровень объединения

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

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

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

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

Выходной слой

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

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

Например, если взять всех животных с оценкой выше 0,5, мы получим отличные результаты.

Изучение фильтров

Если бы мне пришлось очень кратко объяснить, как работает машинное обучение, я бы сказал, что главная цель - выяснить, насколько неверен прогноз, и попытаться минимизировать ошибку.

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

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

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

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

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

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

Вы можете визуализировать это, поскольку каждый слой передает своему предыдущему слою все его ошибки нейронов:

Или вы можете визуализировать это так, как будто каждый нейрон передает свои связанные нейроны своей собственной ошибкой:

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

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

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

Выход

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

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

Свертка

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

Здесь мы собираемся вычислить две вещи. Ошибка, которую мы собираемся распространить, и ошибка, в которой мы собираемся изменить фильтр.

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

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

Объединение

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

Обратное распространение слоя объединения будет отбрасывать входные элементы. Если элемент был тем, который был объединен, мы собираемся вставить туда дельту, в противном случае будет помещен 0.

Тест-драйв

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

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

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

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

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

Классификация

Для классификации построил следующую сеть

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

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

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

Вы можете найти исходный код и пример Pokemon здесь.

Когда наш герой написал свою собственную сеть и получил значок CNN, он переходит к своему следующему приключению и реализует рабочий Pokedex.