Узнайте, как распознавать цифры с помощью CNN и Джулии.

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

Вместо этого воспользуемся Julia и пакетом Flux.jl. Почему? Поскольку Julia работает быстро, и если у вас есть миллионы изображений для анализа, ускорение может быть значительным по сравнению с Python. Блокнот Jupyter для этого сообщения в блоге можно найти здесь.

Данные

Набор данных MNIST содержит изображения рукописных цифр (от 0 до 9) в оттенках серого, которые хорошо центрированы. Каждый пиксель представлен числом от 0 (черный) до 255 (белый). Каждое изображение имеет размер 28 на 28 пикселей. Один из способов представить изображение - это увидеть его как вектор из 1d-столбца размером 28 * 28 = 784 пикселей. Однако это представление игнорирует структуру изображения: близкие друг к другу пиксели информативны для цифры, которую мы пытаемся идентифицировать. CNN - хороший инструмент для сохранения пространственной структуры изображения, избегая при этом проблем, связанных с проклятием размерности: изображения - это зашумленные и многомерные входные данные.

Ускоренный курс на CNN

Двумя ключевыми составляющими CNN являются сверточный слой (отсюда и название) и слой maxpool.

Сверточный слой

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

Слой Maxpool

Слой maxpool - это шаблон, который выбирает максимальное значение внутри квадрата. Ниже приведена иллюстрация слоя maxpool, примененного к изображению 4 на 4:

Шаг и набивка

При создании CNN необходимо указать два гиперпараметра: шаг и отступ.

  • Когда шаг равен 1, мы перемещаем фильтры по одному пикселю за раз. Когда шаг равен 2, мы перемещаем фильтры на два пикселя за раз и т. Д.
  • Заполнение означает «добавление нулей» к границе изображения. Заполнение можно использовать для управления размером выходного объема и помогает сохранять информацию на границе изображений.

Ниже приведен пример фильтра 3 на 3, примененного к входу 5 на 5, дополненному границей нулей 1 на 1 с использованием шага 2 на 2:

Типичная инфраструктура CNN состоит в том, чтобы сначала применить сверточный слой к входному изображению, а затем использовать слой maxpool, а затем использовать полностью связанный слой. Несколько модулей сверточный слой - слой maxpool можно сложить вместе, прежде чем использовать полносвязный (FC) слой. Обратите внимание, что слой активации (часто ReLU) обычно вставляется между сверточным слоем и слоем maxpool.

Использование Flux.jl

Flux.jl - ведущий пакет машинного обучения в экосистеме Julia. Далее мы загружаем как поезд, так и тестовые образцы набора данных MNIST. Образец поезда - это набор изображений, используемых для точной настройки параметров CNN, в то время как тестовый образец содержит изображения, используемые для проверки того, что мы не переоборудовали образец поезда. Дымящийся пистолет для переобучения - это когда точность в образце поезда намного лучше, чем точность с использованием изображений из тестового образца.

Архитектура CNN

Наша CNN имеет обычные компоненты Conv- ›ReLU-› MaxPool перед использованием уровня FC. Мы используем отступ 1 на 1 и шаг 1 (значение по умолчанию). Размер ввода постепенно уменьшается за счет использования 2х2 слоев maxpool. Активация по умолчанию в Flux.jl - это функция x- ›x. Здесь мы вместо этого используем функцию выпрямленных линейных единиц (ReLU):

Функция активации ReLU является кусочно-линейной функцией. В статье Крижевского и соавторов Классификация ImageNet с глубокими сверточными нейронными сетями авторы пишут:

мы называем нейроны с такой нелинейностью выпрямленными линейными единицами (ReLU). Глубокие сверточные нейронные сети с ReLU обучаются в несколько раз быстрее, чем их эквиваленты с модулями tanh.

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

xgrid = collect(range(-1, 1, length=100)) plot(xgrid, NNlib.relu.(xgrid), label = "relu(x)", title="ReLU activation function", xlabel="x")

Обучение

Дозирование

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

Функция потерь и минимизация

Чтобы CNN хоть чему-то научился, он должен иметь понятие неправильно или правильно. Функция потерь делает именно это, определяя, насколько хорошо модель распознает цифры. Когда выход представляет собой вероятность, подходит функция потерь кросс-энтропия. Последний шаг - выбрать алгоритм, минимизирующий функцию потерь. Здесь давайте выберем алгоритм ADAM, который я понимаю как своего рода стохастический градиентный спуск с импульсом и адаптивной скоростью обучения:

Этот блок «тренирует» (настраивает значения параметров CNN) модель до тех пор, пока не будет достигнут заранее определенный уровень точности:

Прогнозы

После обучения модели прогнозируемые значения легко получить следующим образом:

# Get predictions and convert data to Array: 
pred = Tracker.data(model(test_set[1])); 

# Function to get the row index of the max value: 
f1(x) = getindex.(argmax(x, dims=1), 1) 
# Final predicted value is the one with the maximum probability: 
pred = f1(pred) .- 1; #minus 1, because the first digit is 0 (not 1)

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

println("Predicted value = $(pred[1])") a = reshape(test_imgs[1], NROWS, NCOLS)
Predicted value = 7

println("Predicted value = $(pred[2])") a = reshape(test_imgs[2], NROWS, NCOLS)
Predicted value = 2

println("Predicted value = $(pred[3])") a = reshape(test_imgs[3], NROWS, NCOLS)
Predicted value = 1

Проверки точности

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

Матрица путаницы

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

В последний раз, когда я проверял, Flux.jl не имел встроенной функции для вычисления матриц путаницы. К счастью, реализация доступна в пакете MLBase. Следующий блок кода вычисляет матрицу неточностей и отображает ее. Большинство экземпляров расположены по диагонали, что неудивительно, учитывая, что точность нашей модели составляет более 97,0%.

using MLBase 
# Adding 1 to outcome because the index 0 in arrays does not exist in Julia:
Cm = confusmat(10, test_labels .+ 1, vec(pred) .+ 1)
# Normalize output: 
Cm = Cm ./ sum(Cm, dims=2) 
# Labels 
xs = [string(i) for i = 0:9] 
heatmap(xs, xs, Cm, aspect_ratio=1, color=:plasma)

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

# Limits to colormap, so we can see where errors are located: 
xs = [string(i) for i = 0:9] 
heatmap(xs, xs, Cm, aspect_ratio=1, color=:plasma, clim=(0., 0.01))

Анализ ошибок

Следующий блок кода отображает цифры, для которых наш CNN потерпел неудачу:

using ImageView, Gtk.ShortNames
# indices for errors: 
mistakes = test_labels .!= vec(pred) 
max_images = 5 grid, frames, canvases = canvasgrid((1,max_images));
k=0#counter 
for mistakes for (j, i) in enumerate(mistakes) 
    if i == true k+=1 # a false value has been found 
       println("Predicted value = $(pred[j])") 
       println("True value = $(test_labels[j])") 
       imshow(canvases[1,k], test_imgs[j]) 
    end 
    if k >= max_images 
       break 
    end 
end 
win = Window(grid); 
Gtk.showall(win);
Predicted value = 5 True value = 9 
Predicted value = 5 True value = 6 
Predicted value = 4 True value = 8 
Predicted value = 3 True value = 2 
Predicted value = 7 True value = 2

Хотя кажется очевидным, что две цифры, начинающиеся слева (см. График выше), - это 9 и 6, остальные 3 элемента не являются тривиальными. Цифру 8 в середине можно легко спутать с чем-то еще, а две оставшиеся цифры имеют странную форму.

Заключение

При работе с изображениями сверточная нейронная сеть обычно отлично справляется с распознаванием шаблонов. Это сообщение в блоге не было техническим введением в тему. Хотя Python - инструмент пристрастия к машинному обучению (Keras, TensorFlow и т. Д.), Я предполагаю, что Джулия будет становиться все более популярной, потому что Джулия одновременно проста в использовании и работает быстро.

использованная литература

* Код для этого сообщения в блоге в значительной степени основан на этом руководстве по Flux.jl: https://github.com/FluxML/model-zoo/blob/master/vision/mnist/conv.jl
* Вкл. ссылки между CNN и PDE: https://mitmath.github.io/18337/lecture14/pdes_and_convolutions
* Полный курс по CNN. Большая часть контента доступна в Интернете: http://cs231n.github.io/convolutional-networks/

Первоначально опубликовано на https://julienpascal.github.io 5 декабря 2019 г.