image

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

В рассказе нам поможет Дмитрий Сошников — технический евангелист Microsoft, член Российской ассоциации искусственного интеллекта, преподаватель функционального и логического программирования ИИ в МАИ, МФТИ и ВШЭ, а также наших курсов.

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

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

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

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

Банки фильтров и биологическое зрение


image


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

В области машинного зрения наработаны банки фильтров — наборы фильтров для выделения основных особенностей объектов.

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

image


Разделение весов (Weight Sharing)


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

Это требует специализированной архитектуры сети:

  • сверточные сети для работы с изображениями
  • рекуррентные сети для работы с текстом / последовательностями

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

Основная идея заключается в следующем:

  • Используем weight sharing для создания «фильтрующего окна», пробегающего по изображению
  • Примененный к изображению фильтр помогает выделить фрагменты, важные для распознавания
  • В то время как в традиционном машинном зрении фильтры конструировали вручную, нейросети позволяют нам сконструировать оптимальные фильтры с помощью обучения
  • Фильтрацию изображения можно естественным образом совместить с вычислением нейронной сети

image

Для обработки изображений используется свертка, как и в обработке сигналов.

Опишем функцию свертки со следующими параметрами:

  • kernel — ядро свёртки, матрица весов
  • pad — сколько пискелей надо добавить к изображению по краям
  • stride — частота применения фильтра. Например, для stride=2 будем брать каждый второй пиксель изображения по вертикали и горизонтали, уменьшив разрешение вдвое

In [1]:
def convolve(image, kernel, pad = 0, stride = 1):
    rows, columns = image.shape
    output_rows = rows // stride
    output_columns = columns // stride
    result = np.zeros((output_rows, output_columns))
    if pad > 0:
        image = np.pad(image, pad, 'constant')    
    kernel_size = kernel.size
    kernel_length = kernel.shape[0]
    half_kernel = kernel_length // 2  
    kernel_flat = kernel.reshape(kernel_size, 1)
    offset = builtins.abs(half_kernel-pad)
    for r in range(offset, rows - offset, stride):
        for c in range(offset, columns - offset, stride):
            rr = r - half_kernel + pad
            cc = c - half_kernel + pad  
            patch = image[rr:rr + kernel_length, cc:cc + kernel_length]
            result[r//stride,c//stride] = np.dot(patch.reshape(1, kernel_size), kernel_flat)            
    return result


In [2]:
def show_convolution(kernel, stride = 1):
    """Displays the effect of convolving with the given kernel."""
    fig = pylab.figure(figsize = (9,9))
    gs = gridspec.GridSpec(3, 3, height_ratios=[3,1,3])
    start=1
    for i in range(3):        
        image = images_train[start+i,0]
        conv = convolve(image, kernel, kernel.shape[0]//2, stride)
        ax = fig.add_subplot(gs[i])
        pylab.imshow(image, interpolation='nearest')
        ax.set_xticks([])
        ax.set_yticks([])
        ax = fig.add_subplot(gs[i + 3])
        pylab.imshow(kernel, cmap='gray', interpolation='nearest')
        ax.set_xticks([])
        ax.set_yticks([])
        ax = fig.add_subplot(gs[i + 6])
        pylab.imshow(conv, interpolation='nearest')
        ax.set_xticks([])
        ax.set_yticks([])        
    pylab.show()


In [3]:
blur_kernel = np.array([[1, 4, 7, 4, 1],
                        [4, 16, 26, 16, 4],
                        [7, 26, 41, 26, 7],
                        [4, 16, 26, 16, 4],
                        [1, 4, 7, 4, 1]], dtype='float32')
blur_kernel /= 273

Фильтры


Blur


Фильтр размытия позволяет сгладить неровности и подчеркнуть общую форму объектов.

image

In [4]:
show_convolution(blur_kernel)

Вертикальные края


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

image

In [5]:
vertical_edge_kernel = np.array([[1, 4, 0, -4, 1],
                                   [4, 16, 0, -16, -4],
                                   [7, 26, 0, -26, -7],
                                   [4, 16, 0, -16, -4],
                                   [1, 4, 0, -4, -1]], dtype='float32')
vertical_edge_kernel /= 166


In [6]:
show_convolution(vertical_edge_kernel)

Горизонтальные края


Аналогичный фильтр можно построить для выделения горизонтальных штрихов на изображении.



In [7]:
horizontal_bar_kernel = np.array([[0, 0, 0, 0, 0],
                                 [-2, -8, -13, -8, -2],
                                 [4, 16, 26, 16, 4],
                                 [-2, -8, -13, -8, -2],
                                 [0, 0, 0, 0, 0]], dtype='float32')
horizontal_bar_kernel /= 132


In [8]:
show_convolution(horizontal_bar_kernel)

Контурный фильтр


Также можно построить фильтр 9x9, который будет выделять контуры изображения.



In [9]:
blob_kernel = np.array([[0, 1, 1, 2, 2, 2, 1, 1, 0],
                       [1, 2, 4, 5, 5, 5, 4, 2, 1],
                       [1, 4, 5, 3, 0, 3, 5, 4, 1],
                       [2, 5, 3, -12, -24, -12, 3, 5, 2],
                       [2, 5, 0, -24, -40, -24, 0, 5, 2],
                       [2, 5, 3, -12, -24, -12, 3, 5, 2],
                       [1, 4, 5, 3, 0, 3, 5, 4, 1],
                       [1, 2, 4, 5, 5, 5, 4, 2, 1],
                       [0, 1, 1, 2, 2, 2, 1, 1, 0]], dtype='float32')
blob_kernel /= np.sum(np.abs(blob_kernel))



In [10]:
show_convolution(blob_kernel)

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

image

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

Основная идея свёрточной сети


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

К примеру, у нас есть изображение i, 2 сверточных фильтра w c выходами o. Элементы выходного изображения будут вычисляться следующим образом:


Тренировка весов


Алгоритм таков:

  • Фильтр с одними и теми же весами применяется ко всем пикселям изображения.
  • При этом фильтр «пробегает» по всему изображению.
  • Мы хотим обучать эти веса (общие для всех пикселей) по алгоритму обратного распространения.
  • Для этого надо свести применение фильтра к однократному умножению матриц.
  • В отличие от полносвязного слоя, весов для обучения будет меньше, а примеров — больше.
  • Хитрость — im2col

im2col


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


Затем мы извлечем все фрагменты изображения 3x3 и поместим их в столбцы большой матрицы X:


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


Тогда свёртка по всему изображению превращается в обычное матричное умножение:


Проблемы анализа изображений


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

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

В конце концов, кривой код тоже никто не отменял.

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

Neuro Workshop пройдет 2 раза:


Выбирайте удобный день, приходите и задавайте Дмитрию свои вопросы.

Комментарии (3)


  1. lumaxy
    28.04.2018 10:21

    Предполагается только очное участие в Neuro Workshop, дистанционно нельзя?


    1. De11 Автор
      28.04.2018 11:17

      На этом курсе участие только очное


  1. atepeq
    29.04.2018 12:52

    В рассказе нам поможет Дмитрий Сошников — технический евангелист Microsoft

    Почему же тогда не на CNTK+.Net? Помнится, еще пол-года назад на интенсиве, Дмитрий говорил, что в Deep Learning все уже можно писать на C#, а не на Питоне, который он не любит, но приходилось ранее, поскольку ничего другого не было.