newsmode
search
Меню
arrow_back Назад

What a Deep Neural Network thinks about your #selfie

auto_awesomeКраткое саммари

Андрей Карпаты описывает шуточный эксперимент: он взял современную свёрточную нейросеть (ConvNet) на 140 млн параметров, скормил ей около 2 млн селфи из интернета и обучил отличать удачные селфи от неудачных, оценивая качество по числу лайков с поправкой на количество подписчиков. Исходные ~5 млн картинок с тегом #selfie были сужены до 2 млн с лицами, из них собран датасет из 1 млн «хороших» и 1 млн «плохих» снимков; модель VGGNet, предобученную на ImageNet, дообучили в Caffe на NVIDIA K40, достигнув точности 60% (случайное угадывание — 50%). Анализ лучших селфи выявил закономерности: лучше всего работают женские снимки, лицо занимает около 1/3 кадра, лоб обрезан, видны длинные волосы, применён фильтр с пересвеченным лицом и добавлена рамка; для мужчин часть правил не действует. Среди плохих приёмов — тёмное освещение, слишком крупный план головы и групповые кадры. Карпаты также показывает t-SNE-визуализацию пространства селфи, автоматический подбор оптимального кропа, Twitter-бота @deepselfie и даёт обзор инструментов и материалов по глубокому обучению (Caffe, Torch, Theano, курсы Ng, CS231n и книга Bengio).

Свёрточные нейросети — это здорово: они распознают вещи, места и людей на ваших личных фото, знаки, людей и светофоры в беспилотных автомобилях, посевы, леса и трафик на аэроснимках, различные аномалии на медицинских изображениях и множество других полезных вещей. Но время от времени эти мощные модели визуального распознавания можно использовать и для отвлечения, веселья и развлечения. В этом забавном эксперименте мы именно этим и займёмся: возьмём мощную свёрточную нейросеть последнего поколения на 140 миллионов параметров, скормим ей 2 миллиона селфи из интернета и обучим отличать хорошие селфи от плохих. Просто потому что это легко и потому что мы можем. И заодно, возможно, научимся делать селфи получше :)

Ага, я займусь настоящей работой. Но сначала дай-ка я отмечу одно #selfie.

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

Прежде чем мы начнём, я подумал, что стоит кратко описать, что такое свёрточные нейросети (или сокращённо ConvNets) — на случай, если сюда заглянет читатель из более широкой аудитории. По сути, ConvNets — это очень мощный молоток, а задачи компьютерного зрения — это гвозди. Если вы в 2015 году что-то видите или читаете о том, как компьютер распознаёт вещи на изображениях или видео, почти наверняка речь идёт о ConvNet. Несколько примеров:

Немного истории. У ConvNets довольно интересная предыстория. Впервые их разработали Yann LeCun и его коллеги в 1980-х (опираясь на более ранние работы, например Fukushima). В качестве забавного раннего примера посмотрите эту демонстрацию LeNet 1 (так называлась та ConvNet), распознающей цифры, ещё в 1993 году. Однако эти модели в основном игнорировались сообществом компьютерного зрения, потому что считалось, что они не масштабируются до изображений «реального мира». Это оказалось верным лишь примерно до 2012 года, когда у нас наконец появилось достаточно вычислительных мощностей (в виде GPU, спасибо NVIDIA) и достаточно данных (спасибо ImageNet), чтобы реально масштабировать эти модели — что и было впервые продемонстрировано, когда Alex Krizhevsky, Ilya Sutskever и Geoff Hinton выиграли соревнование ImageNet 2012 (считайте, чемпионат мира по компьютерному зрению), разгромив конкурентов (16,4% ошибки против 26,2% у второго места).

Мне довелось воочию наблюдать этот переломный момент, потому что соревнование ImageNet в последние несколько лет организовывала лаборатория Fei-Fei Li (моя лаборатория), так что я помню, как моя коллега ахнула от недоверия, заметив в логах заявок (очень сильную) работу на ConvNet. И помню, как мы ходили по комнате, пытаясь осмыслить случившееся. За следующие несколько месяцев ConvNets превратились из малоизвестных моделей, окружённых скепсисом, в рок-звёзд компьютерного зрения, ставших ключевым строительным блоком почти в каждой новой статье по теме. Соревнование ImageNet отражает этот тренд: в 2012 году была всего одна заявка на ConvNet, а с тех пор, в 2013 и 2014 годах, почти все заявки использовали ConvNets. И ещё забавный факт: команда-победитель каждый год сразу же оформлялась в компанию.

За следующие несколько лет мы довели до совершенства, упростили и масштабировали исходную архитектуру 2012 года «AlexNet» (да, мы даём им имена). В 2013 году была «ZFNet», а затем в 2014 году «GoogLeNet» (поняли? потому что это как LeNet, но от Google? ха) и «VGGNet». В общем, что мы теперь знаем — это что ConvNets:

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

Под капотом

Так как же они работают? Заглянув под капот, вы обнаружите очень простой вычислительный мотив, повторяющийся снова и снова. Гифка ниже иллюстрирует весь вычислительный процесс небольшой ConvNet:

Слева мы подаём на вход сырые пиксели изображения, которые представляем в виде трёхмерной сетки чисел. Например, изображение 256x256 будет представлено как массив 256x256x3 (последняя 3 — для красного, зелёного, синего). Затем мы выполняем свёртки, что является мудрёным способом сказать, что мы берём небольшие фильтры и пространственно скользим ими по изображению. Разные фильтры «возбуждаются» от разных особенностей на изображении: одни могут сильно откликаться, увидев небольшой горизонтальный край, другие — на области красного цвета и так далее. Если предположить, что у нас было 10 фильтров, то таким образом мы преобразовали бы исходное изображение (256,256,3) в «изображение» (256,256,10), где мы выбросили исходную информацию об изображении и сохранили только 10 откликов наших фильтров в каждой позиции. Это как если бы три цветовых канала (красный, зелёный, синий) теперь заменились 10 каналами откликов фильтров (я показываю их в первом столбце сразу справа от изображения на гифке выше).

Итак, я объяснил первый столбец активаций сразу после изображения — а что же со всеми остальными столбцами, которые появляются со временем? Это та же самая операция, повторённая снова и снова, по разу для получения каждого нового столбца. Следующие столбцы будут соответствовать ещё одному набору фильтров, применяемых к откликам предыдущего столбца, постепенно обнаруживая всё более и более сложные визуальные паттерны, пока последний набор фильтров не вычислит вероятность целых визуальных классов (например, собака/жаба) на изображении. Очевидно, я пробегаюсь по некоторым моментам поверхностно, но суть такова: это просто свёртки от начала до конца.

Обучение. Мы увидели, что ConvNet — это большой набор фильтров, применяемых друг поверх друга. Но откуда мы знаем, что эти фильтры должны искать? Мы не знаем — мы инициализируем их все случайным образом, а затем обучаем со временем. Например, мы подаём изображение в ConvNet со случайными фильтрами, и она может заявить, что на 54% уверена, что это собака. Тогда мы можем сказать ей, что на самом деле это жаба, и существует математический процесс, позволяющий чуть-чуть изменить все фильтры в ConvNet так, чтобы в следующий раз, увидев то же изображение, она с чуть большей вероятностью сказала «жаба». Затем мы просто повторяем этот процесс десятки/сотни миллионов раз, для миллионов изображений. Автомагическим образом разные фильтры вдоль вычислительного пути в ConvNet постепенно настроятся на отклик на важные вещи на изображениях, такие как глаза, затем головы, затем целые тела и так далее.

Ещё один хороший набор визуализаций для полностью обученной ConvNet можно найти в проекте deepvis Jason Yosinski и его коллег. Он включает забавную живую демонстрацию ConvNet, работающей в реальном времени с камеры вашего компьютера, как хорошо объясняет Jason в этом видео:

В общем, весь процесс обучения напоминает то, как ребёнку показывают множество изображений вещей, и ему/ей приходится постепенно понимать, на что смотреть на изображениях, чтобы отличать эти вещи друг от друга. А если вам ближе технические объяснения, то ConvNet просто выражает функцию от пикселей изображения к вероятностям классов, где фильтры — это параметры, и мы запускаем стохастический градиентный спуск, чтобы оптимизировать функцию потерь классификации. Или, если вы любите хайп про ИИ/мозг/сингулярность, то эта функция — «глубокая нейронная сеть», фильтры — нейроны, а вся ConvNet — кусок адаптивной, симулированной ткани зрительной коры.

Обучение ConvNet

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

Я написал быстрый скрипт для сбора изображений, отмеченных тегом #selfie. В итоге я получил около 5 миллионов изображений (с ConvNets всегда чем больше, тем лучше). Я сузил это с помощью другой ConvNet примерно до 2 миллионов изображений, содержащих хотя бы одно лицо. Теперь пора решить, какие из этих селфи хорошие, а какие плохие. Интуитивно мы хотим вычислить некоторое приближение к тому, сколько людей увидели селфи, а затем посмотреть на число лайков как функцию от размера аудитории. Я взял всех пользователей и отсортировал их по числу подписчиков. Я дал небольшой бонус за каждый дополнительный тег на изображении, предполагая, что лишние теги привлекают больше глаз. Затем я прошёлся по этому отсортированному списку группами по 100 и отсортировал эти 100 селфи по числу лайков. Я использовал только селфи, которые были онлайн более месяца, чтобы обеспечить почти стабильное число лайков. Я взял топ-50 селфи и отнёс их к положительным, а нижние 50 отнёс к отрицательным. Таким образом мы получаем бинарное разбиение данных на две половины, где мы попытались нормировать по числу людей, которые, вероятно, видели каждое селфи. В этом процессе я также отфильтровал людей со слишком малым или слишком большим числом подписчиков, а также людей, использовавших слишком много тегов на изображении. Берём полученный датасет из 1 миллиона хороших и 1 миллиона плохих селфи и обучаем ConvNet.

На этом этапе вы можете возразить, что мой способ определять, хорошее селфи или плохое, неверен — например, что если кто-то выложил очень хорошее селфи, но было поздно ночью, поэтому, возможно, его увидело не так много людей, и оно набрало меньше лайков? Вы правы — почти наверняка это неверно, но это нужно лишь чтобы было верно чаще, чем нет, и ConvNet справится. Она не запутывается и не унывает, она просто делает всё возможное с тем, что ей дали. Чтобы понять, насколько трудно различить два класса в наших данных, взгляните на несколько примеров обучающих изображений ниже. Если бы я дал вам любое из этих изображений, смогли бы вы сказать, к какой категории оно относится?

Детали обучения. Просто чтобы привести немного технических деталей: для обучения ConvNet я использовал Caffe. Я взял VGGNet, предобученную на ImageNet, и дообучил её на датасете селфи. Модель обучалась за ночь на GPU NVIDIA K40. Я отключил dropout, потому что без него результаты были лучше. Я также пробовал VGGNet, предобученную на датасете с лицами, но не получил результатов лучше, чем при старте с чекпойнта ImageNet. Итоговая модель показала 60% точности на моей валидационной выборке (50% — это случайное угадывание).

Что делает #selfie хорошим?

Итак, мы собрали 2 миллиона селфи, решили, какие из них вероятно хорошие или плохие, на основе числа полученных ими лайков (с поправкой на число подписчиков), скормили всё это Caffe и обучили ConvNet. ConvNet «посмотрела» на каждое из 2 миллионов селфи по несколько десятков раз и настроила свои фильтры так, чтобы лучше всего отделять хорошие селфи от плохих. Мы не можем так уж легко рассмотреть, что именно она нашла (всё это перемешано в 140 миллионах чисел, которые вместе задают фильтры). Однако мы можем напустить её на селфи, которые она никогда раньше не видела, и попытаться понять, что она делает, глядя на то, какие изображения ей нравятся, а какие нет.

Я взял 50 000 селфи из своих тестовых данных (то есть ConvNet их раньше не видела). В качестве первой визуализации на изображении ниже я показываю визуализацию континуума: лучшие селфи в верхнем ряду, худшие — в нижнем, а каждый ряд между ними — это континуум:

Это было интересно. Теперь давайте вытянем топ-100 селфи (из 50 000) по версии ConvNet:

Если хотите увидеть больше, вот ссылка на топ-1000 селфи (3,5 МБ). Замечаете ли вы закономерность в том, что ConvNet, вероятно, научилась искать? Несколько паттернов бросаются мне в глаза, и если вы заметите что-то ещё, буду рад услышать в комментариях. Чтобы сделать хорошее селфи, Делайте:

Будьте женщиной. Женщин стабильно ранжируют выше мужчин. В частности, обратите внимание, что в топ-100 нет ни одного парня. Лицо должно занимать около 1/3 изображения. Заметьте, что положение и поза лица довольно последовательны среди лучших изображений. Лицо всегда занимает около 1/3 кадра, слегка наклонено и расположено по центру и вверху. Что также подводит меня к: Обрежьте лоб. Что это вообще такое? Похоже, это популярная стратегия, по крайней мере у женщин. Покажите длинные волосы. Заметьте частую заметность длинных прядей, спадающих на плечи. Пересветите лицо. Обратите внимание на частое появление переэкспонированного освещения, которое часто делает лицо гораздо более однородным и выцветшим. С этим же связано: Наложите фильтр. Чёрно-белые фото, похоже, неплохо заходят, и большинство лучших изображений, кажется, содержат какой-то фильтр, который засвечивает изображение и снижает контраст. Добавьте рамку. Вы заметите частое появление горизонтальных/вертикальных белых рамок.

Интересно, что не все эти правила применимы к мужчинам. Я вручную просмотрел топ-2000 селфи и выбрал лучшие мужские, вот что у нас получается:

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

Давайте также взглянем на некоторые из худших селфи, про которые ConvNet вполне уверена, что они не набрали бы много лайков. Я показываю изображения в гораздо меньшем и менее узнаваемом формате, потому что моё намерение — чтобы мы узнали о широких паттернах, снижающих качество селфи, а не проливать свет на людей, которым довелось сделать плохое селфи. Вот они:

Даже при таком малом разрешении некоторые паттерны явно проявляются. Не делайте:

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

В качестве последнего замечания отметим, что значительную долю разброса в том, что делает селфи хорошим или плохим, можно объяснить стилем изображения, а не сырой привлекательностью человека. Также, с некоторым облегчением, кажется, что лучшие селфи — не те, что показывают больше всего кожи. Я на мгновение всерьёз забеспокоился, что моя навороченная ConvNet на 140 миллионов параметров окажется простым счётчиком количества текстуры кожи.

Знаменитости. В качестве последнего забавного эксперимента я попробовал запустить ConvNet на нескольких знаменитых селфи звёзд и отсортировал результаты с помощью визуализации континуума, где лучшие селфи находятся наверху, а оценка ConvNet уменьшается вправо, а затем к низу:

Забавно, что общее эмпирическое правило, которое мы наблюдали ранее (никаких групповых фото), нарушается знаменитым групповым селфи Ellen DeGeneres и других с церемонии «Оскар», однако ConvNet считает это селфи на самом деле очень хорошим, помещая его во 2-й ряд! Здорово! :)

Другое из наших эмпирических правил (никаких мужчин) уверенно опровергает торс Chris Pratt (тоже 2-й ряд), а почётные упоминания достаются приподнятым бровям Justin Beiber и дуэту Stephen Collbert / Jimmy Fallon (3-й ряд). На селфи James Franco кожи видно заметно больше, чем у Chris, но ConvNet не очень впечатлена (4-й ряд). Я тоже.

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

Исследуем пространство #selfie

Ещё одна забавная визуализация, которую мы можем попробовать, — разложить селфи с помощью t-SNE. t-SNE — это чудесный алгоритм, который я люблю запускать почти на всём, до чего могу дотянуться, потому что он одновременно очень общий и очень эффективный: он берёт некоторое количество объектов (например, изображений в нашем случае) и раскладывает их так, что близкие объекты похожи. На самом деле с помощью t-SNE можно разложить много чего, например фильмы Netflix, слова, профили Twitter, изображения ImageNet или вообще что угодно, где есть некоторое количество объектов и способ сравнить, насколько похожи два объекта. В нашем случае мы разложим селфи на основе того, насколько похожими их воспринимает ConvNet. Технически выражаясь, мы делаем это на основе L2-норм активаций fc7 в последнем полносвязном слое. Вот эта визуализация:

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

Поиск оптимального кропа для селфи

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

Заметьте, что ConvNet любит делать так, чтобы голова занимала около 1/3 изображения, и отрезает лоб. Забавно, что на изображении внизу справа ConvNet решила избавиться от части «self» в слове selfie, полностью упустив суть :) Можно найти ещё много забавных примеров таких «грубых» кропов:

Прежде чем кто-то из более продвинутых пользователей спросит: да, я пробовал вставить слой Spatial Transformer сразу после изображения и перед ConvNet. Затем я делал обратное распространение в 6 параметров, задающих произвольный аффинный кроп. К сожалению, мне не удалось заставить это работать хорошо — оптимизация то застревала, то дрейфовала несколько хаотично. Я также пробовал ограничить преобразование масштабированием/сдвигом, но это не помогло. К счастью, когда у преобразования 3 ограниченных параметра, мы можем позволить себе выполнить глобальный перебор (как показано выше).

Насколько хорошо ваше?

Любопытно, что сеть думает о ваших селфи? Я упаковал сеть в Twitter-бота, чтобы вы могли легко это узнать. (Бот оказался всего ~150 строк на Python, включая весь код Caffe/Tweepy.) Прикрепите своё изображение к твиту (или включите ссылку) и упомяните бота @deepselfie где-нибудь в твите. Бот взглянет на ваше селфи и затем выскажет своё мнение! Для лучших результатов давайте ссылку на квадратное изображение, иначе боту придётся сжать его до квадрата, что ухудшает результаты. Бот должен ответить в течение минуты, иначе что-то пошло не так (попробуйте позже).

Прежде чем кто-нибудь спросит: я также пробовал портировать уменьшенную версию этой ConvNet для работы на iOS, чтобы вы могли наслаждаться обратной связью в реальном времени во время съёмки селфи, но это оказалось довольно нетривиальным для быстрого побочного проекта — например, я сначала попытался писать собственные фрагментные шейдеры, поскольку поддержки вроде CUDA там нет, затем посмотрел на некоторые многопоточные версии только для CPU, но мне не удалось заставить это работать аккуратно и в реальном времени. А у меня ведь есть и настоящая работа.

Заключение

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

Если хотите узнать больше, лучшее место для начала для новичка прямо сейчас — это, наверное, учебные материалы Michael Nielsen. Оттуда я бы посоветовал сначала посмотреть курс Andrew Ng на Coursera, а затем пройти конспекты лекций/задания для CS231n. Это курс именно по ConvNets, который я преподавал вместе с Fei-Fei в Стэнфорде прошлой зимней четверти. Мы также будем снова предлагать этот курс начиная с января 2016 года, и вы можете свободно следить за ним. Для более продвинутого материала я бы посмотрел курс Hugo Larochelle по нейросетям или книгу Deep Learning, которую сейчас пишут Yoshua Bengio, Ian Goodfellow и Aaron Courville.

Конечно, на практике вы научитесь гораздо большему, чем за чтением, поэтому я бы порекомендовал вам поиграть со 101 соревнованием Kaggle или развивать собственные побочные проекты — и в этом случае я искренне рекомендую не только делать, но и писать об этом и публиковать там, где все мы сможем это прочитать, например на /r/machinelearning, где сложилось хорошее сообщество. Что касается рекомендуемых инструментов, сейчас есть три распространённых варианта:

Caffe (C++, обёртки на Python/Matlab), который я использовал в этом посте. Если вам нужна базовая классификация изображений, то Caffe — самый простой путь, во многих случаях вообще не требующий писать код, достаточно вызывать включённые скрипты. Библиотеки глубокого обучения на основе Theano (Python), такие как Keras или Lasagne, которые дают больше гибкости. Torch (C++, Lua), который я сейчас использую в своих исследованиях. Я бы рекомендовал Torch самым продвинутым пользователям, так как он предлагает много свободы, гибкости, скорости — и всё это с довольно простыми абстракциями.

Среди других чуть более новых/менее проверенных, но перспективных библиотек — Neon от Nervana, CGT или Mocha на Julia.

Наконец, есть несколько компаний, которые стремятся принести глубокое обучение в массы. Один пример — MetaMind, которая предлагает веб-интерфейс, позволяющий перетаскивать изображения и обучать ConvNet (все детали они берут на себя в облаке). MetaMind и Clarifai также предлагают REST API для ConvNet.

Вот и всё, до встречи в следующий раз!