Deep learning и Caffe на новогодних праздниках

Мотивация


В данной статье вы познакомитесь c применением deep learning на практике. Будет использован фреймворк Caffe на датасете SVHN.

Deep Learning. Этот buzz word уже давно звенит в ушах, но попробовать его на практике никак не удавалось. Подвернулся удобный случай это исправить! На новогодние праздники был назначен контест на kaggle по распознаванию номеров домов в рамках курса по анализу изображений.

Была дана часть известной выборки SVHN, состоящей из 73257 изображений в обучающей и 26032 в тестовой (неразмеченной) выборках. Всего 10 классов для каждой цифры. Изображение имеет размер 32x32 в цветовом пространстве RGB. Как показывает бенчмарк, методы на основе deep learning показывают точность выше чем у человека — 1.92% против 2% ошибки!

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

Выбор Caffe


Для работы с глубокими нейросетями существует много разных библиотек и фреймворков. Мои критерии были такими:
  1. туториалы,
  2. легкость освоения,
  3. легкость разворачивания,
  4. активное сообщество.

По ним отлично подошел Caffe:
  1. Хорошие туториалы есть на их сайте. Отдельно рекомендую лекции из Caffe Summer Bootcamp. Для быстрого старта можно почитать про основания нейронных сетей и потом про Caffe.
  2. Для начала работы с Caffe даже не требуется язык программирования. Конфигурируется Caffe с помощью конфигурационных файлов, а запускается из командной строки.
  3. Для разворачивания есть chef-кукбук и docker-образы.
  4. На гитхабе ведется активная разработка, а в гугл-группе можно задать вопрос по использованию фреймворка.

К тому же 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/5x5_s1"
      name: "conv1/5x5_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/5x5_s1"
      top: "conv1/5x5_s1"
      name: "conv1/relu_5x5"
      type: RELU
    }
    layers {
      bottom: "conv1/5x5_s1"
      top: "pool1/3x3_s2"
      name: "pool1/3x3_s2"
      type: POOLING
      pooling_param {
        pool: MAX
        kernel_size: 3
        stride: 2
      }
    }

    layers {
      bottom: "pool1/3x3_s2"
      top: "conv2/5x5_s1"
      name: "conv2/5x5_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/5x5_s1"
      top: "conv2/5x5_s1"
      name: "conv2/relu_5x5"
      type: RELU
    }
    layers {
      bottom: "conv2/5x5_s1"
      top: "pool2/3x3_s2"
      name: "pool2/3x3_s2"
      type: POOLING
      pooling_param {
        pool: MAX
        kernel_size: 3
        stride: 2
      }
    }
    layers {
      bottom: "pool2/3x3_s2"
      top: "conv3/5x5_s1"
      name: "conv3/5x5_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/5x5_s1"
      top: "conv3/5x5_s1"
      name: "conv3/relu_5x5"
      type: RELU
    }
    layers {
      bottom: "conv3/5x5_s1"
      top: "pool3/3x3_s2"
      name: "pool3/3x3_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/3x3_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_5x5"
      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_5x5"
      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 не вызывает больших трудностей. Достаточно создать пару конфигурационных файлов и одной командой запустить процесс обучения. Конечно, также нужны базовые познания в теории искусственных нейронных сетей. Эту (в виде ссылок на материалы) и другую информацию для быстрого старта я постарался дать в этой статье.
Поделиться публикацией

Комментарии 11

    0
    Тот случай когда вроде и интересно, но непонятно абсолютно все.
      +1
      Caffe — хорошая штука. Плюсую за обзор. Но введение не помешало бы. Так сходу непонятно о чем речь, если не работал с этим раньше.

      Пара рецептов улучшить качество:
      — используйте солвер adagrad
      — используйте слои нормализации (local response normalization)
      и, самое действенное для картинок
      — можно использовать трансформацию и дополнение данных: вращать, менять масштаб, зашумлять и т.п.

      Также, если скачать и установить библиотечку CuDNN от Nvidia, можно скорость сверток увеличить 1.5-3 раза.
        +1
        Спасибо за статью. Очень толково, компактно и по делу.
          0
          Статья хорошая, но исключительно с инженерной точки зрения.
          Взят готовый фреймворк, показано как его использовать.

          Это хорошо

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

          б) для человека, который и не хочет разбираться и/или ему не нужно разбираться в том, как все устроено внутри,
          но который просто хочет, использовав фреймворк, быстро решить задачу.(обычный технарь)

          Я не принадлежу ни к первой ни ко второй категории. Я хочу разобраться в том, как все устроено «under-the-hood».
          Если автор статьи разбирается в этом вопросе, буду очень благодарен за разъяснения/ссылки/книги с детальным описанием
          сверточных сетей. Что называется «на пальцах».

          Конкретно у меня возникли трудности в том, как реализован алгоритм обратного распостранения ошибки для convolutional слоя в CNN.

          Что я перепробовал:

          1) Реверс-инжиниринг неоднозначных формул
          Я перерыл довольно много открытых источников, включая стендфортский туториал.
          В том же стендфортском туториале есть формулы касательно алгоритма обратного распостранения ошибки для convolutional слоев.
          Но при этом не указана архитектура сети. Не указаны связи между слоями. Без указания этих связей формулы противоречивы и
          неоднозначны. Из-за этого приходится реверс-инжинирить эти формулы, чтобы
          понять какие конкретно связи существуют в их конкретной сети. К тому же, это всего-лишь частный случай.

          2) реверс-инжиниринг готовых реализаций
          Еще я натыкался на несколько реализаций сверточных сетей, где реализован сам алгоритм обратного распостранения через сверточный слой.
          Но там приходится реверс инжинирить код и по нему понимать как работает бэкпропагэйшн через сверточный слой. Код не всегда простой
          и я не уверен, что я на 100% правильно этот код реверс-инжинирю( в математике важна точность, поэтому мне хочется быть уверенным на 100%)

          3)Еще не попробовал. Но есть идея
          Забить конкретную сверточную сеть в maple. И посчитать символьные производные по каждому конкретному весу.
          Исходя из этих значений этих символьных производных понять, почему все именно так.
          Но этот 3й пункт — reinventing the wheel(изобретение велосипеда).

          Все 3 метода являются в той или иной мере непрямыми и это получение знаний не напрямую а-ля
          «это работает так потому-то и потому-то» и поэтому требуют очень много времени, усилий. И нервов :)

          Поэтому хотелось бы найти что-нибудь с детальным описанием архитектуры сети «до конкретной связи между нейронами»
          и самыми детальными формулами относящимися к этой конкретной архитектуре.

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

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

            Можете ещё на черновик книги Y. Bengio, I. Goodfellow, A. Courville посмотреть.
              +2
              Могу посоветовать http://cs231n.stanford.edu/syllabus.html.
              Слайды и записи лекций (notes) хороши.

              На данный момент этот курс продолжается, поэтому время от времени появляются новые материалы.
              0
              Вот сравнение разных библиотек:
              fastml.com/torch-vs-theano/

              Еще запуски на AWS:
              nolearn
              www.pyimagesearch.com/2014/10/13/deep-learning-amazon-ec2-gpu-python-nolearn/
              theano
              markus.com/install-theano-on-aws/

                +1
                theano — не библиотека, theano — компилятор для GPU.
                для библиотеки на основе theano, см., скажем, pylearn2.
                0
                Посоветуйте пожалуйста книгу по нейронным сетям, для человека до этого с НС не знакомого. Есть наверное своя «классическая литература».
                Хочу разобраться, не понимаю с чего начать. Что ни начну читать — выглядит как китайская грамота.

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

              Самое читаемое