Как свёрточные нейронные сети видят мир. cnn.. cnn. keras.. cnn. keras. opencv.. cnn. keras. opencv. компьютерное зрение.. cnn. keras. opencv. компьютерное зрение. сверточные нейронные сети.

Меня зовут Яна Вольнова, я ex-разработчик систем распознавания образов, а сейчас преподаю глубокое машинное обучение в МГТУ им. Н.Э. Баумана и пишу курсы для Яндекс Практикума, например, «Инженер по глубокому обучению нейросетей» и «Компьютерное зрение — CV». Я расскажу изнутри, как устроено нейросетевое компьютерное зрение под капотом и почему «много параметров» не всегда равно «лучшая нейросеть». Речь пойдет о классике — CNN или свёрточных нейронных сетях, а в следующей статье рассмотрим, как работают трансформеры зрения.

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

Что такое компьютерное зрение

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

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

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

Так как компьютерному зрению нужно что-то видеть, для примера в статье будем использовать это фото игрушечной еды. Мы с вами легко узнаём, что тут изображено, а компьютеру придётся напрячься :-)

Designed by Freepik

Всё, что видит компьютер — это признаки

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

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

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

Признаки — это тоновые переходы, изменения света и тени, контуры. Рассмотрим их подробнее.

Изменение света и тени

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

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

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

Как свёрточные нейронные сети видят мир - 2

Тоновые переходы

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

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

Как свёрточные нейронные сети видят мир - 3

Края, контуры

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

Как свёрточные нейронные сети видят мир - 4

Текстуры

Признаки бывают более сложные и комбинированные. К таким относятся текстуры и узоры — повторяющиеся последовательности простых признаков. 

Для примера держите текстуру игрушечной котлеты:

Как свёрточные нейронные сети видят мир - 5

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

Чтобы компьютер мог увидеть признаки, используют фильтры

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

С точки зрения алгоритмов фильтры — это матрицы чисел, на которые нужно умножить изображение.

Самый простой фильтр – фильтр прямых

Фильтр прямых — это вот такая матрица чисел. 

-1

0

1

-2

0

2

-1

0

1

Её называют оператором Собеля, и если поэлементно (постепенно сдвигая матрицу) умножать все числовые значения пикселей картинки на эту матрицу, суммировать и записывать результат в новое изображение, то на итоговом изображении будут выделены вертикальные линии. В итоге оно станет картой признака «вертикальная линия». Такая операция называется свёрткой, отсюда и название — свёрточные сети. Если нужны горизонтальные прямые — просто поворачиваем матрицу на 90 градусов и делаем всё то же самое.

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

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

Фильтр прямых на практике

Изображения в компьютере представляются тремя смешиваемыми цветами RGB — красным, зелёным и синим. 

Как свёрточные нейронные сети видят мир - 6

Получается, что каждое изображение — это тензор значений пикселей, имеющий форму ШИРИНА х ВЫСОТА х 3, где тройка — это как раз три цветовых канала: красный, зеленый, синий. Соответственно, можно выделить один цвет, например, красный — то есть взять только нулевой канал, а затем применить к нему фильтр.

В примерах я буду использовать keras и OpenCV на python. 

import cv2
from matplotlib import pyplot as plt

img = cv2.imread('test.png')[:, :, 0] # берем только нулевой канал
# буквально тот самый фильтр вертикальных линий
kernel = np.array([[-1, 0, 1],
                   [-2, 0, 2],
                   [-1, 0, 1]])
# функция, которая осуществляет процедуру свёртки, cv2.CV_64F отмечает формат, в котором отдать результат свёртки
new_img = cv2.filter2D(img, cv2.CV_64F, kernel)
plt.subplot(1, 2, 1).imshow(img, cmap='gray')
plt.axis('off')
plt.subplot(1, 2, 2).imshow(new_img, cmap='gray')
plt.axis('off')
plt.show()

Теперь прогоним нашу картинку через этот код и посмотрим на результат.

Фото слева — просто красный канал. Он тёмный там, где на фото есть красный, и светлый, где на фото красного нет. Так как жёлтый — это сочетание красного и зелёного, то он получился серым (проще говоря, красный тут тоже есть, но его меньше, чем в помидоре или на упаковке картошки).

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

Как свёрточные нейронные сети видят мир - 7

Если увеличить размер фильтра с 3х3 на 11х11 (условно, сделать оператор Собеля побольше), то признаки будут обобщаться по большей площади и станут более заметными мягкие переходы.

import cv2
from matplotlib import pyplot as plt
img = cv2.imread('test.png')[:, :, 0]
new_img = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=11) # применяем оператор Собеля нужного размера
plt.subplot(1, 2, 1).imshow(img, cmap='gray')
plt.axis('off')
plt.subplot(1, 2, 2).imshow(new_img, cmap='gray')
plt.axis('off')
plt.show()

Сравните результат с предыдущими картинками:

Как свёрточные нейронные сети видят мир - 8

Фильтр прямых, но по всем цветам

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

import cv2
from matplotlib import pyplot as plt
img = cv2.imread('test.png', cv2.IMREAD_GRAYSCALE)
new_img = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
plt.subplot(1, 2, 1).imshow(img, cmap='gray')
plt.axis('off')
plt.subplot(1, 2, 2).imshow(new_img, cmap='gray')
plt.axis('off')
plt.show()

Бургер внутри стал более детальным, плюс появились явные границы теней от предметов:

Как свёрточные нейронные сети видят мир - 9

Фильтр границ

Кажется, что фильтром прямых можно сразу определять и границы, но для этого лучше подходит другой инструмент. Это тоже фильтр, но он посложнее и нацелен на выделения контуров. Его называют оператором Лапласа, он помогает найти пересечения и углы. Он выглядит так:

0

1

0

1

-4

1

0

1

0

А вот его применение в коде:

import cv2
from matplotlib import pyplot as plt
img = cv2.imread('test.png', cv2.IMREAD_GRAYSCALE)
new_img = cv2.Laplacian(img, cv2.CV_64F, ksize=3)
plt.subplot(1, 2, 1).imshow(img, cmap='gray')
plt.axis('off')
plt.subplot(1, 2, 2).imshow(new_img, cmap='gray')
plt.axis('off')
plt.show()

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

Как свёрточные нейронные сети видят мир - 10

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

Поиск текстур

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

Свёрточные нейросети — смотрят на мелочи и из них собирают картинку

Свёрточные сети — это способ автоматизировать процессы подбора подходящих фильтров и комбинации найденных простых признаков в более сложные. 

В первом слое свёрточной сети выделяются простейшие признаки, например, вертикальные и горизонтальные линии. Этот слой состоит из фильтров, похожих на операторы Собеля или Лапласа. 

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

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

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

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

Первый слой — собираем простейшие признаки

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

import pandas as pd
from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.applications.vgg16 import preprocess_input
from tensorflow.keras.preprocessing.image import load_img
from tensorflow.keras.preprocessing.image import img_to_array
from tensorflow.keras.models import Model
from matplotlib import pyplot
from numpy import expand_dims
from matplotlib import pyplot as plt

model = VGG16()

# Выводим номера ее свёрточных слоев
for layer in model.layers:
    if '_conv' in layer.name:
        filters, bias = layer.get_weights()
        print(layer.name, filters.shape)

# В этой модели первый слой - входной, второй - как раз свёрточный
filters, bias = model.layers[1].get_weights()


# Отобразим половину фильтров первого свёрточного слоя  - 32 из 64
n_filters = 32

counter = 1
fig = plt.figure(figsize=(12,1))
for i in range(n_filters):
    f = filters[:,:,:,i]
    for j in range(3):
        plt.subplot(3, n_filters, counter)
        plt.imshow(f[:,:,j], cmap='gray')
        plt.axis('off')
        counter += 1

plt.show()

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

 

Как свёрточные нейронные сети видят мир - 11

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

import cv2
# Сокращаем модель, оставив только один свёрточный слой
model_truncated = Model(inputs=model.inputs , outputs=model.layers[1].output)

img = cv2.imread('test.png')
img = expand_dims(img, axis=0)

img = preprocess_input(img)

features = model_truncated.predict(img)[0]

fig = plt.figure(figsize=(15,15))
for i in range(features.shape[2]):

    plt.subplot(8, 8, i+1)
    plt.imshow(features[:,:,i], cmap='gray')
    plt.axis('off')
    
plt.show()

Так выглядят карты местонахождения признаков, выделенных первыми 16-ю фильтрами в первом свёрточном слое. Некоторые из них похожи на результат работы оператора Собеля или Лапласа, другие отличаются. Но все они выделили разные базовые признаки — местоположения основных цветов, переходы между ними и контуры:

Как свёрточные нейронные сети видят мир - 12

Второй слой— ищем группы более сложных признаков

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

blocks = [4, 9, 17]
outputs = [model.layers[i].output for i in blocks]

model_conv = Model(inputs=model.inputs, outputs=outputs)

feature_map = model_conv.predict(img)

for i, fmap in zip(blocks, feature_map):
    fig = plt.figure(figsize=(15,15))
    fig.suptitle("свёрточный блок {}".format(i))
    for i in range(features.shape[2]):
        plt.subplot(8,8,i+1)
        plt.imshow(fmap[0,:,:,i], cmap='gray')
        plt.axis('off')
    
plt.show()

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

Как свёрточные нейронные сети видят мир - 13

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

Как свёрточные нейронные сети видят мир - 14

Семнадцатый слой — переходим к поиску объектов

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

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

Как свёрточные нейронные сети видят мир - 15

Примеры из реальных архитектур

Используемая до этого VGG16 — это одна из первых свёрточных архитектур. Её точность довольно ограничена (бенчмарк ImageNet — 71,3%), при том, что параметров в ней сравнительно много — 138,4 млн. Однако, как вы могли увидеть по семнадцатому слою, в ней нет смысла добавлять ещё больше слоев  — на 17 слое уже очень обобщённые признаки. Поэтому новые архитектуры отличаются не только количеством слоёв, но и их механизмами, что позволяет более качественно обучать сети.

Архитектура посложнее и поновее

Такой архитектурой можно назвать ResNet50. Подобные ей модели много лет были стандартом, уступив это место совсем недавно, когда появились трансформеры зрения. Её точность на бенчмарке — 74,9%, количество параметров при этом всего 25,6 млн.

Аналогично посмотрим ее фильтры и карты признаков.

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

Как свёрточные нейронные сети видят мир - 16

Они более затейливые, чем у VGG16, а значит могут выделять более сложные признаки, такие как кривые, пересечения трех линий и прочие. Это достигнуто за счет увеличения размера фильтра для первого слоя. В VGG16 были 3х3, а здесь 7х7.

Изображения на выходе первого слоя получаются такие:

Как свёрточные нейронные сети видят мир - 17

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

Посмотрим также на промежуточный 19 слой, 103 (что-то посерединке) и 171 (последний свёрточный). На самом деле свёрточных слоев в этой модели меньше чем 171, но они имеют такие индексы, потому что перемежаются другими слоями.

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

Как свёрточные нейронные сети видят мир - 18

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

Как свёрточные нейронные сети видят мир - 19

171 слой — окончательное абстрагирование. Карты отмечают только наличие признаков объектов на изображении. Супрематисты в восторге.

Как свёрточные нейронные сети видят мир - 20

Вообще сложная архитектура жесть

Для примера рассмотрим ещё более современную архитектуру — ConvNext, она появилась как ответ трансформерам зрения. Её точность на бенчмарке — 81,3% при всего лишь 28,6 млн параметров. Получается, что у неё размер как у ResNet50, но точность на 6% выше.

Ее фильтры первого слоя выглядят так:

Как свёрточные нейронные сети видят мир - 21

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

Как свёрточные нейронные сети видят мир - 22

За счёт большого количества разнообразных фильтров признаки тоже очень разнообразны. Изображения уже выглядят так, будто прошли много стадий абстрагирования признаков. По аналогии с ResNet посмотрим на слой из начала — 7, из середины — 86, и последний — 129.

7 — даже в самом начале работы нейросети становится сложнее понять, что значит тот или иной обобщённый признак:

Как свёрточные нейронные сети видят мир - 23

86 — абстракция, где даже не угадывается местоположение исходных трех предметов.

Как свёрточные нейронные сети видят мир - 24

129 – только обобщенные абстрактные признаки.

Как свёрточные нейронные сети видят мир - 25

А зачем нам последний слой, если на нём сплошная абстракция?

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

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

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

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

Заключение — как выбрать, сколько слоев нужно

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

  • Если задача простая, если признак очевидный — нескольких слоёв будет достаточно. 

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

  • Чем больше фильтров в слое, тем больше признаков он способен извлечь. 

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

  • Чем больше слоёв — тем больше возможностей у компьютера обобщить сложные и абстрактные признаки. Всегда ли это нужно — отдельный вопрос.

А в следующий раз мы рассмотрим трансформеры зрения и чем они лучше (или хуже) классических свёрточных сетей.

Автор: yvoln

Источник