Pull to refresh

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

Reading time 9 min
Views 55K

Мотивация


В данной статье вы познакомитесь 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 не вызывает больших трудностей. Достаточно создать пару конфигурационных файлов и одной командой запустить процесс обучения. Конечно, также нужны базовые познания в теории искусственных нейронных сетей. Эту (в виде ссылок на материалы) и другую информацию для быстрого старта я постарался дать в этой статье.
Tags:
Hubs:
+21
Comments 11
Comments Comments 11

Articles