Разберём основы компьютерного зрения на примерах с котиками, узнаем, почему CV на самом деле совсем не про зрение и научимся делать свёртку.
Привет, Хабр! Пётр Емельянов — CEO в Bloomtech, R&D Director в UBIC Tech, специалист по информационной безопасности и анализу данных. Спикер курса «Machine Learning с нуля до Junior» в Skillbox. Опыт в IT — 20 лет. Про машинное обучение и компьютерное зрение слышали многие, но мало кто понимает как это работает. Опытный в ML и CV эксперт Пётр Емельянов провёл эфир для комьюнити Skillbox Code Experts и объяснил, может ли ИИ видеть и обучаться, или эти термины несут совсем иной смысл. Поехали!
Прогресс алгоритмов машинного зрения, компьютерного зрения на примере узнавания котиков на картинках выглядит так: |
На графике по годам, изображенном выше, видим, что заметное развитие технологии началось в 2010 году. А с 2015 года технология наконец‑то перешла отметку в 94.9. Это, кстати, не совсем научное, но интересное число. Довольно известный в Data Science эксперт Эндрю Карпатый в 2014-м определил, что может узнавать котов на картинках с точностью 94.9 (условно почти 95 котов из 100). В 2014 Карпатый ещё опережал компьютер, а в 2015-м — уже нет. Роботы узнают котов лучше, чем мы. Караул!
Компьютерное зрение — это не зрение
Компьютерное зрение или CV нельзя объяснить не затронув зрение человека. Человеческий глаз — совершенная система, которая состоит из оптики и мышц, с помощью которых эта оптика автоматически фокусируется, следит за объектом и подстраивается под освещение.
А ещё есть сенсорные части — роговицы или ретины. Слово «ретина» известно благодаря инженерам Apple. На ретине у нас есть палочки и колбочки. Упрощённо, колбочки отвечают за цветовосприятие, а палочки — за черно‑белое изображение. И те и другие — фоторецепторы, которые преобразуют световое возбуждение в нервные импульсы, то есть в электричество.
Получается, что свет попадает в оптику глаза. Оптика глаза автоматически фокусируется, приспосабливается к интенсивности освещения и проецирует этот свет на поверхность роговицы. Там этот свет фиксируется колбочками‑палочками, преобразуется в нервные импульсы, и ба‑бах! Электрический вихрь проносится у вас в голове, и вы каким‑то магическим образом понимаете, что вот котик, а вот — собачка.
И при всей этой магии человек проигрывает в узнавании котиков обычному компьютеру. Почему так происходит? Дело в том, что компьютерное зрение — это не зрение, а умножение матриц. Компьютеры умножают матрицы гораздо лучше и быстрее, чем люди.
А вот что человек делает лучше, несмотря на последние успехи больших языковых моделей, так это придумывает названия. Я думаю, что если назвать компьютерное зрение каким-нибудь алгоритмическим распознаванием образов, чем оно, несомненно, и является, то звучать будет гораздо более скучно и не так интересно.
Когда я был студентом, уже измученным матаном и линейными алгебрами, у нас начался курс, который назывался «Машинная графика». Звучит-то как! Хочется сразу всё бросить и заниматься только этим. Игры программировать, трёхмерные сцены делать. В общем, машинная графика звучала очень многообещающе, а на деле оказалось всё той же линейной алгеброй. Синусы, косинусы, аффинные преобразования, умножение матриц.
Как компьютерное зрение — никакое не зрение, так и машинное обучение — это никакое не обучение. Машины не учатся, машины просто хорошо умножают матрицы. Всё машинное обучение сводится к оптимизации функций.
В средней школе на алгебре учителя заставляли нас учить таблицы производных. И неспроста. Таблицы производных мы учили для того, чтобы находить на функциях так называемые критические точки. Это такие точки, в которых функция достигает некоторого экстремального (минимального или максимального) значения.
В учебниках было написано, что производная — это скорость изменения функций в данной точке. Это достаточно логично, потому что геометрически производная — это тангенс угла между касательной к функции в этой самой точке и осью абсцисс. Очевидно, что от угла наклона касательной зависит, как быстро наша функция убывает или возрастает.
Если производная равна нулю, то тангенс угла наклона касательной равен нулю, и сам угол тоже равен нулю. Получается, касательная параллельна оси абсцисс. То есть функция в некоторой точке не меняется ни в какую сторону, ни вверх, ни вниз. Представьте, функция быстро растёт, потом замедляется, замедляется, и в какой-то точке останавливается. Еще буквально шажочек, и функция начнёт убывать, но в данной конкретной точке она не меняется, потому что достигла определённого максимального значения. Чтобы такую точку найти, нужно взять функцию, продифференцировать её, приравнять производную нулю и решить уравнение. В машинном обучении всё происходит почти также, но с некоторыми особенностями. Например, функции в машинном обучении очень сложные, как правило, зависят от огромного количества аргументов, и найти производную аналитически (используя таблицу производных из средней школы) не получится. Но ее можно подобрать численными методами. Впрочем, для дальнейшего рассказа это не очень важно.
Машины — не учатся
Любая модель машинного обучения, как вы уже поняли, это функция. Она может быть очень сложной, но сути дела это не меняет. На вход такая функция принимает объекты, с которыми мы хотим чего-нибудь сделать. Например, картинки, на которых хотим определить котиков. У такой функции очень много аргументов или параметров. На картинке ниже эти параметры обозначены греческой буквой θ.
Вначале эти параметры мы, как правило, инициализируем случайными числами. Выход модели –тоже случайные числа. В результате такая модель у нас не котиков определяет, а выдаёт какой-то случайный мусор.
Но фокус в том, что для картинок, на которых мы эту функцию «учим», мы знаем правду. Мы знаем, что это котик, а это — нет. Поэтому можем составить еще одну функцию, которая, условно, будет сравнивать показания нашей модели с этой правдой. Чем выше значение этой функции, тем сильнее наша модель ошибается.
Именно поэтому вторую функцию называют функцией ошибки, функцией потери или, коротко по-английски — loss. Важно: чем выше значение этой функции, тем больше наша модель не права. И, соответственно, чем меньше значение, тем наша модель работает лучше. Поэтому задача сводится просто к оптимизации этой функции по параметру θ.
А дальше всё, как в школе: дифференцируем функцию потерь по θ, приравниваем её к нулю, решаем уравнение, находим такие θ, при котором производная нашей функции loss равна нулю, и находим её экстремальное значение — минимум. Конечно, в большинстве случаев найти аналитическую производную не получится, придётся использовать численные методы, но сути это не меняет: мы подбираем такие значения θ, при которых значение функции loss минимально. При таких значениях θ наша модель работает лучше всего.
Вот и всё машинное обучение. Оно заключается в том, чтобы подобрать гору чисел, при которых какая-то функция выдаёт нужные нам значения. Насколько мне известно, научная гипотеза о том, как именно учится человек, ещё не составлена окончательно. То есть ученые до конца этого не знают. Но одно можно сказать твёрдо – точно не так, как ML-алгоритмы, потому что это, на самом деле, не обучение. Как я уже сказал, машины не учатся, машины просто подбирают числа, умножая матрицы.
Две большие проблемы или как компьютерное зрение распознаёт котиков
У компьютерного зрения есть две большие проблемы:
Размер входного изображения. Любая оцифрованная картинка — это матрица, даже котик. А цветная картинка — это несколько матриц. Например, у аддитивной цветовой модели RGB матриц три — по соответствующему цветовому каналу. Значение в матрице выражает интенсивность соответствующего цвета — красного, зеленого или синего. Получается, что крохотная картинка, например, 16×16 — это 16*16*3=768 чисел.
В общем виде неизвестно, какие пиксели этой картинки важны в задаче, распознавания котиков, а какие нет. Раз мы этого не знаем, обрабатывать нужно все. Получается, что у модели, которую мы будем делать, только входов почти тысяча. Плюс добавляется множество параметров даже для очень маленькой картинки. Получается много вычислений. Несмотря на то, что компьютеры в таких вычислениях гораздо более хороши, чем мы, люди, но размер входного изображения — это всё равно проблема для алгоритмов компьютерного зрения. В частности, ещё и потому, что ненужные пиксели могут мешать модели, «отвлекая» её от нужных.
Искажения. Задача усложняется ещё тем, что картинку с котиком может покорёжить, сжать, растянуть, повернуть. Наш глаз и мозг удивительным образом справляются с этим без затруднений. Вы смотрите на покореженные картинки котика и моментально понимаете, что это тот же самый котик, просто немного деформированный.
Но для компьютера это может быть не так очевидно. Меняются формы матриц, их размеры и сами числа в них. Сильно покорёженная картинка, на которой вы без труда узнаете котика, причём того же самого, для компьютера может быть совершенно другим набором чисел.
Как решали проблемы в ML
Чтобы решить эти проблемы, нужно было каким-то образом снизить размерность, обрабатывать не все пиксели картинки, а только те, которые важны для узнавания котика. А ещё было здорово сделать так, чтобы эти пиксели имели форму, инвариантную к деформации. Условно говоря, мы корёжим картинку с котиком, а эти самые важные котиковые пиксели не меняются или меняются не очень сильно, так, чтобы компьютер, обрабатывая эти измененные пиксели, понимает, что это котик, причём тот же самый.
В машинном обучении есть такое понятие как feature engineering. По-русски это значит отбор признаков. Вы берёте какой‑то объект, например, картинку с котиком, и каким‑то образом выделяете из него то, что важно. Например, вы считаете, что самое важное в коте — это лапы, хвост и усы, поэтому отбрасываете все остальные пиксели.
Когда компьютеры были большими, а вычислительные мощности, наоборот, маленькими, отбором признаков (feature engineering), занимался исключительно человек. Это справедливо для всех задач машинного обучения, а не только для компьютерного зрения. Человек экспертно выделял в наборе данных, что важно, а что нет, неважное отбрасывал, важное делал ещё важнее (например, вычислял площадь Ильича, умножая длину Ильича на его ширину), а потом учил на этом модель. Если модель получалась так себе, процесс повторялся. Это и было классическое машинное обучение.
Методов преобразования изображения в набор «важных» графических признаков достаточно много. На картинке выше как раз один из таких, называется гистограмма направленных градиентов, HOG. Метод в историческом масштабе времени довольно свежий — появился в начале двухтысячных. Этот метод неплохо работал как раз в задачах распознавания образа и сжимал признаковое пространство исходной картинки примерно на порядок.
Условно, если в картинке десятки тысяч пикселей, то после такого преобразования получался плоский вектор в тысячи элементов, и этот уменьшенный вектор мы помещали в классификатор, и классификатор уже определял, есть кот или нет кота.
Но в этой схеме есть два важных момента: алгоритм HOG придумал человек и решил, что на что и в каком порядке умножить, чтобы получить скукоженный маленький вектор. Несмотря на то, что HOG работал достаточно неплохо в задачах распознавания образов, никакой революции в машинном и компьютерном зрении не произошло. Как я уже сказал, HOG начали использовать в нулевых, если верить Википедии, то в 2005-м, и 2005-го года у нас на слайде про развитие технологии вообще нет. Зато есть 2008-й и 2009-й, но с достаточно скромными показателями роста.
Глубокое обучение
И вот в 2010 году происходит настоящий прорыв — появляется Deep Learning или глубокое обучение.
Я уже говорил, что отбором признаков из данных занимался человек. И чем опытнее этот человек был, тем лучше у него получались модели. Это классическое машинное обучение, схема которого выглядит так: данные-человек-модель. Человек смотрит на данные и сам определяет, что хорошо для его модели, а что нет. Если убрать из этой схемы человека, то модели, которые он строил, почти наверняка будут работать хуже. Но если поменять и модели тоже, сделать их больше, намного больше, то всё становится хорошо — гораздо лучше, чем было.
Вот, собственно, и всё глубокое обучение: просто убрали человека из процесса отбора признаков, упростили его роль, а модель увеличили. Мы кормим эту новую большую модель сырыми данными, а она уже сама определяет, что для неё важно, а что нет.
Почему теперь можно так делать, а раньше было нельзя:
К 2010-му году человечество накопило и сохранило достаточно серьёзные объемы данных.
Вычислительные мощности к 2010 году стали гораздо доступнее, чем были до этого. Поэтому, мало того, что Deep Learning работает лучше, чем человек, так ещё и стоит дешевле. Зачастую дешевле на несколько часов поставить машину что-то читать, чем платить высококвалифицированному человеку, чтобы он глазами и своим крутым мозгом пытался определить, что важнее для модели распознавания котиков — усы или хвост.
Deep Learning совершил революцию для многих направлений машинного обучения. Прежде всего, это обработка текстов, обработка речи, и, конечно, компьютерное зрение. В каждой из этих областей появились свои методы и алгоритмы. В компьютерном зрении — это свёрточные нейронные сети.
Свёрточные нейронные сети
Благодаря понятию свёрточные нейронные сети само понятие «свёртка» стало общеизвестным. Хотя свёртка — довольно старый термин из функционального анализа, или, короче говоря, из матана.
Вот так недружелюбно на первый взгляд выглядит формула свёртки, но мы разберём её на простом примере:
Давайте представим, что у нас есть некий госпиталь, и у этого госпиталя есть план госпитализации, то есть план прибытия новых пациентов. В понедельник кладут одного человека, во вторник ещё двоих, в среду ещё троих, и так далее, до пятницы.
Есть некий план лечения, который заключается в количестве пилюль, которые должен принять каждый пациент. Он расписан по дням. В день госпитализации каждый пациент должен съесть три таблетки, на следующий день ещё две, на следующий — ещё одну, и на следующий день он здоров, его выписывают.
Задача – посчитать, сколько всего нужно пилюль, чтобы вылечить всех пациентов, согласно этому плану госпитализации. Это легко, потому что видно, что у нас всего 15 пациентов, каждый слопает по шесть пилюль. Простое умножение даёт результат, что нужно 90 таблеток. А теперь нам нужно раскидать это количество по дням, чтобы узнать, сколько из этих таблеток потребуется каждый день.
Давайте попробуем посчитать. В понедельник нам нужно три таблетки для первого и единственного пациента. Во вторник — шесть таблеток для двух новых пациентов и две таблетки для того, который лёг в понедельник. Итого восемь. В среду уже сложнее: девять таблеток для новеньких и таблетки для ранее поступивших — четыре для тех, кто приехал во вторник, и одна для того, кто остался с понедельника. Всего 14.
Посчитать можно, хотя немного неудобно. Ведь всё время нужно помнить, что у нас где было, кто выписывался, кто когда поступил, и кому сколько таблеток осталось выпить. Причём это не rocket science, а простая задача, которую можно считать в уме. Хочется придумать универсальный алгоритм, который будет работать для любых таких госпитализаций и планов лечения. При этом хотелось бы, чтобы этот алгоритм легко реализовался, чтобы можно было просто сесть и написать простую универсальную программу.
Для этого инвертируем план прибытия пациентов и выпишем его в другую сторону. То есть пять, четыре, три, два, один. А с другой стороны запишем план принятия таблеток так, чтобы самая правая цифра инвертированного плана госпитализации была над самой левой цифрой план лечения. Тогда на шаге № 1 будет троечка под единичкой, то есть три умножить на один — три. Это как раз ровно три таблетки, которые нужны первому прибывшему пациенту в первый день.
Сдвигаем этот план госпитализации на один шаг. Теперь троечка под двоечкой, а двоечка под единичкой. Просто последовательно умножаем три на два, два на один. Шесть плюс два равно восемь. Восемь таблеток надо во второй день. Идем на шаг номер три. Сдвигаем, делаем то же самое, умножаем, получается 14. Идем на шаг номер четыре, сдвигаем, умножаем, получается 20.
Таким образом, двигая это окошко, мы простой операцией, которая легко алгоритмизируется, рассчитываем, сколько таблеточек потребуется, чтобы вылечить всех пациентов. Сдвигаем до тех пор, пока не дойдём до шага номер семь, когда последняя цифра плана приема таблеток оказалась под первой цифрой инвертированного плана госпитализации. То есть в последний день нужно будет всего пять таблеток.
Легко проверить, что цифры, которые получились — 3, 8, 14, 20, 26, 14, 5 — в сумме дают 90 таблеток. Это и есть свёртка, так она работает. Математически это как будто бы такое хитрое умножение двух функций, результатом которого становится третья функция, оценивающая корреляцию первых двух.
Свёртка в компьютерном зрении
В компьютерном зрении это работает точно также, но немного сложнее. У нас есть матрица исходного изображения. Свёртка в примере с больницей была одномерной, мы двигали относительно друг друга одномерные массивы. В компьютерном зрении у нас в общем случае измерений на одно больше, так как картинка двухмерная. А в цветной картинке добавляются ещё и каналы.
Точно также, как двигали относительно друг друга одномерные массивы, мы по матрице двигаем окошко, которое меньше самой матрицы. Это окошко на каждом шаге движения высвечивает какой-то кусочек картинки. Мы поэлементно умножаем значение пикселей в этом высвеченном окошком кусочке на числа в самом окошке и поэлементно складываем то, что получилось. В результате получаем одно единственное число. Это число записываем в другую матрицу.
Получается, что целый кусочек картинки свернулся до одного единственного числа. Информация, которая в этом кусочке картинки содержалась, как будто бы концентрировалась. За значение этого нового числа, которое мы записали в новую матрицу, отвечают числа в скользящем окошке. Это скользящее окошко называют свёрточным фильтром, просто фильтром, свёрточным ядром или просто ядром, зависит от языка. В англоязычной литературе чаще используют «фильтр», а в русском языке — «ядро». Эти числа внутри фильтра или ядра – это параметры модели, те самые θ, про которые мы говорили в разделе про машинное обучение.
Задача – подобрать числа в этих фильтрах или ядрах так, чтобы модель работала наилучшим образом. Решается задача точно так же. Дифференцируем по θ, то есть по числам в этих фильтрах, находим производную, меняем эти числа в направлении производной, чтобы значение функций потерь убывало, и шаг за шагом делаем это, пока наша модель не начнёт работать так, как нам нужно и не достигнет определённого приемлемого уровня качества.
Почти все современные модели компьютерного зрения – это свёрточные нейронные сети. Просто таких свёрток очень-очень много. Топологически, как принято в нейронных сетях, свёртки организуются в слои.
Каждый слой свёрточной нейронной сети характеризуется следующим. Это размерность входных данных, то есть размер картинки и количество каналов в ней, если картинка цветная. Ещё важен размер самого скользящего окошка, который тоже может меняться, а также количество таких скользящих окошек в слое. Важен и сам принцип «скольжения». Например, фильтр может двигаться с шагом в один пиксель, а может — с шагом в несколько. Может выходить за пределы изображения, а может не выходить.
Получается, что на каждом конкретном слое входящая матрица изображения обрабатывается каждым фильтром или ядром в этом слое и сворачивается до некоторой другой матрицы. На выходе конкретного слоя будет столько таких других матриц, сколько свёрточных фильтров в этом слое. По аналогии с цветовыми моделями, эти новые матрицы генерируются свёрточными слоями и называются каналами.
Основной фокус в том, что каждый свёрточный слой на самом деле уменьшает картину. Он делает её меньше, сжимает её размерность, но, как правило, генерирует больше каналов.
Если, как в примере на картинке выше, вы поместили на вход свёрточной нейронной сети монохромную (состоящую из одного канала) картинку 28 на 28, то читая информацию слайда, первый слой с N1 ядрами 5 на 5 свернёт нашу входную картинку до N1 матриц 24 на 24. Каждая такая матрица будет меньше исходного изображения, но самих матриц станет больше. Поэтому часто эти свёрточные сети, рисуют как будто бы трёхмерными. Но на самом деле таким образом показывают, что в результате каждого свёрточного слоя получится много матриц.
Потом матрицы с первого свёрточного слоя поступают на второй. На иллюстрации выше видно, что на втором слое все эти матрицы сжимаются еще сильнее, становятся 12 на 12. На слайде их также N1, как на предыдущем слое.
Так изображение путешествует от слоя к слою, как правило уменьшаясь в размере, но увеличиваясь (хоть это необязательно) в числе каналов. В конце то, что получается, вытягивают в один плоский вектор. Этот плоский вектор идёт на вход классификатора, который чаще всего представлен полносвязанными слоями нейронной сети, потому что такие сети хорошо зарекомендовали себя в задаче классификации. И этот классификатор обучается определять вероятность, котик на картинке или нет — решает ту задачу, которую нужно.
Алгоритм вариативнее человека
Когда мы говорили про классическое машинное обучение, у нас был некий человек, который смотрел на картинку и пытался придумать алгоритм, который из этой картинки сделает компактное представление, содержащее максимально важную информацию, чтобы картинку обработать.
Свёрточная нейронная сеть, как, собственно, и весь Deep Learning, убирает из уравнения человека и делает это самостоятельно. Задача свёрточных слоев – извлечь из картинки компактное представление информации, которая в этой картинке содержится, и передать его на вход модели (например, классификатора), которая примет решение.
Если модель учится узнавать котиков, она каким-то образом сама решит, что ей важно, а что нет. И это хорошо работает, потому, что человек менее вариативен, чем алгоритм.
Например, HOG — диаграмма ориентированных градиентов — хороший метод, неплохо работает, но он чересчур универсален и более-менее одинаково работает и для картинок, на которых котики, и для картинок, на которых люди. Он одинаковым образом эти картинки обработает и по одинаковым правилам превратит их в вектора.
Но cвёрточные нейронные сети мы обучаем на конкретной задаче, например, узнавать котиков, и они сами подбирают эти важные признаки. И признаки эти могут сильно отличаться от выбранных человеком. А когда вы возьмётесь обучать нейронную сеть с совершенно той же архитектурой узнавать людей, то она, может быть, выберет снова совсем другие признаки. За счёт большей вариативности такие модели и достигают большей точности.
Важно, что матрицы, которые получаются в результате свёрточных слоев, ещё называют feature maps или по-русски карты признаков. Карты признаков — это, по сути, те самые фичи, которые выделяет нейронная сеть, чтобы лучше узнавать и работать.
Каждому любопытному исследователю наверняка будет очень интересно, как выглядит feature maps для свёрточного слоя, если просто снять его сигналы и преобразовать обратно, сделать из этого картинку.
Чтобы это сделать, есть несколько подходов, но все они не очень развиваются, потому что для них нет прикладной задачи. Когда я искал примеры такой картинки, то среди большого количества скучных монохромных маленьких изображений, на которых вообще ничего непонятно, нашёл такую красоту.
На рисунке выше слева расположена курица. А справа feature map, который получился в результате обработки этой курицы свёрточной сетью. Feature map сняли примерно из середины этой свёрточной сети. Если приглядеться, тут видно много куриных голов, гребней, глаз, клювов. Всё это разного размера и размещено в разном положении.
Есть мнение, что это как раз тот случай, когда исследователь приукрасил, пожертвовал научной строгостью во имя эстетики. Но сама идея в том, что нейронная сеть переварила картинку курицы и где-то внутри родила новую, на которой запечатлела то, что по ее мнению, делает курицу курицей. Здесь важно, что этот feature map будет более-менее одинаковым практически для любой курицы, которую вы этой нейронной сети покажете. Конкретно из этой получился такой, но и из какой-то другой курицы получится похожий. За счёт этого нейронная сеть сможет легко отличить, курица на изображении или нет.
Нейронная сеть сама меняет и искажает изображение и выделяет какие-то важные части конкретно для этой курицы – клювы и глаза. Конечно, именно картинку не стоит воспринимать как научную истину, но в качестве более-менее наглядной иллюстрации процесса компьютерного зрения она подходит.
Итоги
Компьютерное зрение – важная технология, которая находит много разных применений. Например, сегодня мы можем заплатить «улыбкой», глядя в миниатюрную камеру на кассе, или разблокировать телефон лицом. Камеры на дорогах сделали движение намного более безопасным, – и это случилось тоже с помощью CV. Роботы, анализирующие КТ-снимки, чтобы помогать врачам – компьютерное зрение. Контроль качества на производстве – тоже оно. Продолжать можно долго, находить существующие применения и придумывать новые — бесконечно. Там, где есть изображения, всегда найдется место компьютерному зрению.
Сама технология довольно сложная, но бурно развивается, и изучать её стоит. Во-первых, потому, что это безумно интересно. Во-вторых, может быть полезно и выгодно. Задачек много, а хороших специалистов (впрочем, как почти везде в IT) не хватает. К тому же, если есть слона изучать технологию по частям, то каждый отдельный кусочек вполне доступен для понимания. Желаю удачи!