[Из песочницы] Deep learning и Caffe на новогодних праздниках

Мотивация В данной статье вы познакомитесь c применением deep learning на практике. Будет использован фреймворк Caffe на датасете SVHN.Deep Learning. Этот buzz word уже давно звенит в ушах, но попробовать его на практике никак не удавалось. Подвернулся удобный случай это исправить! На новогодние праздники был назначен контест на kaggle по распознаванию номеров домов в рамках курса по анализу изображений.Была дана часть известной выборки SVHN, состоящей из 73257 изображений в обучающей и 26032 в тестовой (неразмеченной) выборках. Всего 10 классов для каждой цифры. Изображение имеет размер 32×32 в цветовом пространстве RGB. Как показывает бенчмарк, методы на основе deep learning показывают точность выше чем у человека — 1.92% против 2% ошибки!

У меня был опыт работы с алгоритмами машинного обучения на основе SVM и Naive Bayes. Применять уже известные методы скучно, поэтому решил использовать что-нибудь из deep learning, а именно сверточную нейронную сеть.

Выбор Caffe Для работы с глубокими нейросетями существует много разных библиотек и фреймворков. Мои критерии были такими: туториалы, легкость освоения, легкость разворачивания, активное сообщество. По ним отлично подошел Caffe: Хорошие туториалы есть на их сайте. Отдельно рекомендую лекции из Caffe Summer Bootcamp. Для быстрого старта можно почитать про основания нейронных сетей и потом про Caffe. Для начала работы с Caffe даже не требуется язык программирования. Конфигурируется Caffe с помощью конфигурационных файлов, а запускается из командной строки. Для разворачивания есть chef-кукбук и docker-образы. На гитхабе ведется активная разработка, а в гугл-группе можно задать вопрос по использованию фреймворка. К тому же Caffe очень быстрый, т.к. использует GPU (хотя можно обойтись и CPU).Установка Изначально я поставил Caffe на свой ноутбук с помощью docker и запускал его в режиме CPU. Обучение нейросети проходило очень медленно, но сравнивать было не с чем и казалось, что это нормально.Затем наткнулся на 25$ купон амазона и решил попробовать на AWS g2.2xlarge с NVIDIA GPU и поддержкой CUDA. Там развернул Caffe с помощью Chef. В итоге получилось в 41 раз быстрее — на CPU 100 итераций проходило за 290 сек, на GPU c CUDA за 7 cек!

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

input — входной слой, обычно это пиксели изображения, conv — слой свертки [1], pool — слой подвыборки [2], fully-conn — полносвязный слой [3], output — выходной слой, выдает предполагаемый класс изображения. Для задачи классификации изображений основной является следующая архитектура НС: input → conv → pool → conv → pool → fully-conn → fully-conn → output Количество (conv → pool) слоев может быть разным, но обычно не меньше 2х. Количество fully-conn не меньше 1 го.В рамках данного контеста было перепробовано несколько архитектур. Наибольшую точность я получил со следующей:

input → conv → pool → conv → pool → conv → pool → fully-conn → fully-conn → output Имплементация архитектуры на Caffe Caffe конфигурируется с помощью Protobuf файлов. Имплементация архитектуры для контеста находится здесь. Рассмотрим ключевые моменты конфигурации каждого слоя.Входной слой (input) Конфигурация входного слоя name: «WinnyNet-F» layers { name: «svhn-rgb» type: IMAGE_DATA top: «data» top: «label» image_data_param { source:»/home/deploy/opt/SVHN/train-rgb-b.txt» batch_size: 128 shuffle: true } transform_param { mean_file:»/home/deploy/opt/SVHN/svhn/winny_net5/mean.binaryproto» } include: { phase: TRAIN } } layers { name: «svhn-rgb» type: IMAGE_DATA top: «data» top: «label» image_data_param { source:»/home/deploy/opt/SVHN/test-rgb-b.txt» batch_size: 120 } transform_param { mean_file:»/home/deploy/opt/SVHN/svhn/winny_net5/mean.binaryproto» } include: { phase: TEST } } … Первые 2 слоя (для обучающей и тестовой фазы) имеют type: IMAGE_DATA, т.е. сеть на вход принимает изображения. Изображения перечисляются в текстовом файле, где 1 колонка — путь к изображению, 2 колонка — класс. Путь к текстовому файлу указывается в атрибуте image_data_param.Кроме изображений, можно подавать на вход данные из HDF5, LevelDB и lmbd. Последние 2 варианта особенно актуальны, если критична скорость работы. Таким образом Caffe может работать с любыми данными, а не только изображениями. Проще всего работать с IMAGE_DATA, поэтому он и был выбран для контеста.

Также входные слои могут включать атрибут transform_param. В нем указываются трансформации, которым надо подвергнуть входные данные. Обычно, перед подачей изображений на нейросеть, их нормализуют или проводят более хитрые операции, например Local Contrast Normalization. В данном случае был указан mean_file — вычитание «среднего» изображения из входного.

В Caffe используется batch gradient descent. Входной слой содержит параметр batch_size. За одну итерацию на вход нейросети поступает batch_size элементов выборки.

Слои свертки и подвыборки (conv, pool) Конфигурация слоев свертки и подвыборки … layers { bottom: «data» top: «conv1/5×5_s1» name: «conv1/5×5_s1» type: CONVOLUTION blobs_lr: 1 blobs_lr: 2 convolution_param { num_output: 64 kernel_size: 5 stride: 1 pad: 2 weight_filler { type: «xavier» std: 0.0001 } } } layers { bottom: «conv1/5×5_s1» top: «conv1/5×5_s1» name: «conv1/relu_5×5» type: RELU } layers { bottom: «conv1/5×5_s1» top: «pool1/3×3_s2» name: «pool1/3×3_s2» type: POOLING pooling_param { pool: MAX kernel_size: 3 stride: 2 } }

layers { bottom: «pool1/3×3_s2» top: «conv2/5×5_s1» name: «conv2/5×5_s1» type: CONVOLUTION blobs_lr: 1 blobs_lr: 2 convolution_param { num_output: 64 kernel_size: 5 stride: 1 pad: 2 weight_filler { type: «xavier» std: 0.01 } } } layers { bottom: «conv2/5×5_s1» top: «conv2/5×5_s1» name: «conv2/relu_5×5» type: RELU } layers { bottom: «conv2/5×5_s1» top: «pool2/3×3_s2» name: «pool2/3×3_s2» type: POOLING pooling_param { pool: MAX kernel_size: 3 stride: 2 } } layers { bottom: «pool2/3×3_s2» top: «conv3/5×5_s1» name: «conv3/5×5_s1» type: CONVOLUTION blobs_lr: 1 blobs_lr: 2 convolution_param { num_output: 128 kernel_size: 5 stride: 1 pad: 2 weight_filler { type: «xavier» std: 0.01 } } } layers { bottom: «conv3/5×5_s1» top: «conv3/5×5_s1» name: «conv3/relu_5×5» type: RELU } layers { bottom: «conv3/5×5_s1» top: «pool3/3×3_s2» name: «pool3/3×3_s2» type: POOLING pooling_param { pool: MAX kernel_size: 3 stride: 2 } } … 3 м является слой свертки с type: CONVOLUTION. Далее идет указание функции активации c type: RELU. 4 м слоем является слой подвыборки с type: POOL. Далее 2 раза идет повторение conv, pool слоев, но с другими параметрами.Подбор параметров для этих слоев является эмпирическим.

Полносвязные и выходные слои (fully-conn, output) Конфигурация полносвязных и выходных слоев … layers { bottom: «pool3/3×3_s2» top: «ip1/3072» name: «ip1/3072» type: INNER_PRODUCT blobs_lr: 1 blobs_lr: 2 inner_product_param { num_output: 3072 weight_filler { type: «gaussian» std: 0.001 } bias_filler { type: «constant» } } } layers { bottom: «ip1/3072» top: «ip1/3072» name: «ip1/relu_5×5» type: RELU } layers { bottom: «ip1/3072» top: «ip2/2048» name: «ip2/2048» type: INNER_PRODUCT blobs_lr: 1 blobs_lr: 2 inner_product_param { num_output: 2048 weight_filler { type: «xavier» std: 0.001 } bias_filler { type: «constant» } } } layers { bottom: «ip2/2048» top: «ip2/2048» name: «ip2/relu_5×5» type: RELU } layers { bottom: «ip2/2048» top: «ip3/10» name: «ip3/10» type: INNER_PRODUCT blobs_lr: 1 blobs_lr: 2 inner_product_param { num_output: 10 weight_filler { type: «xavier» std: 0.1 } } } layers { name: «accuracy» type: ACCURACY bottom: «ip3/10» bottom: «label» top: «accuracy» include: { phase: TEST } } layers { name: «loss» type: SOFTMAX_LOSS bottom: «ip3/10» bottom: «label» top: «loss» } Полносвязный слой имеет type: INNER_PRODUCT. Выходной слой соединяется со слоем функцией потерь (type: SOFTMAX_LOSS) и слоем точности (type: ACCURACY). Слой точности срабатывает только в тестовой фазе и показывает процент верно классифицированных изображений в валидационной выборке.Важным является указание атрибута weight_filler. Если он будет большим, то функция потерь (loss) может на начальных итерациях возвращать NaN. В таком случае надо уменьшить параметр std у атрибута weight_filler.

Параметры обучения Конфигурация параметров обучения net:»/home/deploy/opt/SVHN/svhn/winny-f/winny_f_svhn.prototxt» test_iter: 1 test_interval: 700 base_lr: 0.01 momentum: 0.9 weight_decay: 0.004 lr_policy: «inv» gamma: 0.0001 power: 0.75 solver_type: NESTEROV display: 100 max_iter: 77000 snapshot: 700 snapshot_prefix:»/mnt/home/deploy/opt/SVHN/svhn/snapshots/winny_net/winny-F» solver_mode: GPU Для получения хорошо обученной нейронной сети нужно задать параметры обучения. В Caffe параметры обучения устанавливаются через конфигурационный protobuf файл. Конфигурационный файл для данного контеста находится здесь. Параметров много, рассмотрим некоторые из них подробнее: net — путь к конфигурации архитектуры НС, test_interval — количество итераций между которыми проводится тестирование НС (phase: test), snapshot — количество итераций между которыми сохраняется состояние обучения НС.В Caffe можно приостанавливать и возобновлять обучение. Обучение и тестирование Чтобы запустить обучение НС, нужно выполнить команду caffe train с указанием конфигурационного файла, где заданы параметры обучения: > caffe train --solver=/home/deploy/winny-f/winny_f_svhn_solver.prototxt Краткий лог обучения … I0109 18:12:17.035543 12864 solver.cpp:160] Solving WinnyNet-F I0109 18:12:17.035578 12864 solver.cpp:247] Iteration 0, Testing net (#0) I0109 18:12:17.077910 12864 solver.cpp:298] Test net output #0: accuracy = 0.0666667 I0109 18:12:17.077997 12864 solver.cpp:298] Test net output #1: loss = 2.3027 (* 1 = 2.3027 loss) I0109 18:12:17.107712 12864 solver.cpp:191] Iteration 0, loss = 2.30359 I0109 18:12:17.107795 12864 solver.cpp:206] Train net output #0: loss = 2.30359 (* 1 = 2.30359 loss) I0109 18:12:17.107817 12864 solver.cpp:516] Iteration 0, lr = 0.01 … I0109 18:13:17.960325 12864 solver.cpp:247] Iteration 700, Testing net (#0) I0109 18:13:18.045385 12864 solver.cpp:298] Test net output #0: accuracy = 0.841667 I0109 18:13:18.045462 12864 solver.cpp:298] Test net output #1: loss = 0.675567 (* 1 = 0.675567 loss) I0109 18:13:18.072872 12864 solver.cpp:191] Iteration 700, loss = 0.383181 I0109 18:13:18.072949 12864 solver.cpp:206] Train net output #0: loss = 0.383181 (* 1 = 0.383181 loss) … I0109 20:08:50.567730 26450 solver.cpp:247] Iteration 77000, Testing net (#0) I0109 20:08:50.610496 26450 solver.cpp:298] Test net output #0: accuracy = 0.916667 I0109 20:08:50.610571 26450 solver.cpp:298] Test net output #1: loss = 0.734139 (* 1 = 0.734139 loss) I0109 20:08:50.640389 26450 solver.cpp:191] Iteration 77000, loss = 0.0050708 I0109 20:08:50.640470 26450 solver.cpp:206] Train net output #0: loss = 0.0050708 (* 1 = 0.0050708 loss) I0109 20:08:50.640494 26450 solver.cpp:516] Iteration 77000, lr = 0.00197406 … I0109 20:52:32.236827 30453 solver.cpp:247] Iteration 103600, Testing net (#0) I0109 20:52:32.263108 30453 solver.cpp:298] Test net output #0: accuracy = 0.883333 I0109 20:52:32.263183 30453 solver.cpp:298] Test net output #1: loss = 0.901031 (* 1 = 0.901031 loss) I0109 20:52:32.290550 30453 solver.cpp:191] Iteration 103600, loss = 0.00463345 I0109 20:52:32.290627 30453 solver.cpp:206] Train net output #0: loss = 0.00463345 (* 1 = 0.00463345 loss) I0109 20:52:32.290644 30453 solver.cpp:516] Iteration 103600, lr = 0.00161609 Одна эпоха — это (73257–120)/128 ~= 571 итерация. Чуть больше чем за 1 эпоху, на 700 итерации, точность сети на валидационной выборке 84%. На 134 эпохе точность уже 91%. На 181 эпохе — 88%. Возможно, если обучать сеть больше эпох, например 1000, точность стабилизируется и будет выше. В данном контесте обучение было остановлено на 181 эпохе.В Caffe можно возобновлять обучение сети из snapshot добавляя параметр --snapshot:

> caffe train --solver=/home/deploy/winny-f/winny_f_svhn_solver.prototxt --snapshot=winny_net/winny-F_snapshot_77000.solverstate Тестирование на неразмеченных изображениях Для тестирования НС, необходимо создать deploy конфигурацию архитектуры сети. В ней, в отличие от предыдущей конфигурации, отсутствует слой точности и упрощен входной слой.Тестовая выборка, состоящая из 26032 изображений, идет без разметки. Поэтому, чтобы оценить точность на тестовой выборке контеста, нужно написать немного кода. Caffe имеет интерфейсы для Питона и Матлаба.

Для тестирования сетей из разных эпох в Caffe есть снапшоты. Сеть 134 эпохи показала точность (Private Score в kaggle) 88.7%, а сеть 181 эпохи — 87.6%.

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

Обучать сеть больше эпох. Например, в туториале по deep learning в facial keypoints detection сеть обучали 1000 эпох. Стандартизовать данные, чтобы математическое ожидание было равно 0 и дисперсия 1. Для этого потребуется использовать HDF5 или LevelDb/lmdb для хранения данных. Поработать с параметрами обучения. Например, уменьшать learning_rate каждые 100 эпох. Также можно попробовать использовать dropout слои, но для этого потребуется обучать сеть еще больше эпох, чем 1000. Датасет SVHN содержит дополнительные 600 000 размеченных изображений. В исследованиях их используют, но в рамках контеста их использование было бы нечестным. В этом случае можно сгенерировать новые данные на основе имеющихся. Заключение Реализованная сверточная нейронная сеть показала точность 88.9%. Это не лучший результат, но для первого блина неплохо. Есть потенциал для увеличения точности до 96%.Благодаря фреймворку Caffe погружение в deep learning не вызывает больших трудностей. Достаточно создать пару конфигурационных файлов и одной командой запустить процесс обучения. Конечно, также нужны базовые познания в теории искусственных нейронных сетей. Эту (в виде ссылок на материалы) и другую информацию для быстрого старта я постарался дать в этой статье.

© Habrahabr.ru