[Из песочницы] Глубокое обучение и Raspberry PI
Содержание
«Что у нас есть?» — спросил горбоносый поворачиваясь.
«Алдан-3», — сказал бородатый.
«Богатая машина, — сказал я.”[1]
Недавно я решил заняться изучением глубокого обучения. На работе мне выдали новую карточку с поддержкой CUDA и шеф выразил пожелание что эта вершина инженерной мысли позволит нашей лаборатории сделать рывок вперёд, ну или по крайней мере, не отстать от массы конкурентов. У меня уже был некоторый опыт общения с Tensor Flow, но в этот раз я решил попробовать Torch. Привлекало что он написан на языке Lua и C, является достаточно легковесным и легко расширяемым через FFI. И ещё мне не нравится Python.
Недавно на Хабрахабр я наткнулся на статью, в процессе обсуждения которой я вспомнил что где-то в тумбочке у меня пилится Raspberry Pi, модель B+ и мне захотелось посмотреть — а смогу ли я поднять на ней torch и запустить что-нибудь несложное.
Естественно первым делом я захотел посмотреть как на моем десктопе с новой карточкой GPU будет тренироваться alexnet и другие известные сети. На гитхабе есть небольшой проект в котором несколько популярных сетей реализованы на Torch[1]. Поигравшись с ними, я перешёл на решение своих задач, но про них я тут говорить не буду.
А теперь переходим к малинке (Raspberry PI модель B+).
Установка
Копируем инсталлятор torch на малинку[2]:
apt-get install git-core
git clone https://github.com/torch/distro.git ~/torch --recursive
Первым делом, я решил что мне не хочется ждать пока стандартный установщик Torch скомпилирует OpenBLAS и установит QT со всеми зависимостями, поэтому я решил проделать это вручную:
apt-get install -y build-essential gcc g++ curl cmake libreadline-dev libjpeg-dev libpng-dev ncurses-dev imagemagick libzmq3-dev gfortran libopenblas-base libopenblas-dev
Запускаем компиляцию torch:
cd ~/torch;
./install.sh
У меня компиляция всего занимает примерно час.
— А что это там у вас за лампа? — подозрительно спросил Фарфуркис. [1]
И тут нас ждёт первый облом: создатели torch: не предполагали что его будут компилировать на архитектуре arm, но без поддержки NEON:
[ 6%] Building C object lib/TH/CMakeFiles/TH.dir/THVector.c.o
In file included from /home/pi/torch/pkg/torch/lib/TH/THVector.c:2:0:
/home/pi/torch/pkg/torch/lib/TH/generic/THVectorDispatch.c: In function ‘THByteVector_vectorDispatchInit’:
/home/pi/torch/pkg/torch/lib/TH/generic/simd/simd.h:64:3: error: impossible constraint in ‘asm’
asm volatile ( "cpuid\n\t"
Пришлось пофиксить это дело[3]. И после этого, всё заработало! Если вам лень делать это всё самому и хочется быстро попробовать пример, я сделал архив с пред-компилированным torch для Raspberry PI -B (без поддержки NEON): https://github.com/vfonov/deep-pi/releases/download/v1/torch_intstall_raspbian_arm6l_20161218.tar.gz[4], распаковывается в /home/pi
Тестирование
Для проверки я решил посмотреть скорость тренировки распознавания рукописных цифр MNIST[5], соответствующий пример есть в наборе демок[6] для Torch:
th train-on-mnist.lua
<torch> set nb of threads to 4
<mnist> using model:
nn.Sequential {
[input -> (1) -> (2) -> (3) -> (4) -> (5) -> (6) -> (7) -> (8) -> (9) -> (10) -> output]
(1): nn.SpatialConvolutionMM(1 -> 32, 5x5)
(2): nn.Tanh
(3): nn.SpatialMaxPooling(3x3, 3,3, 1,1)
(4): nn.SpatialConvolutionMM(32 -> 64, 5x5)
(5): nn.Tanh
(6): nn.SpatialMaxPooling(2x2, 2,2)
(7): nn.Reshape(576)
(8): nn.Linear(576 -> 200)
(9): nn.Tanh
(10): nn.Linear(200 -> 10)
}
<warning> only using 2000 samples to train quickly (use flag -full to use 60000 samples)
<mnist> loading only 2000 examples
<mnist> done
<mnist> loading only 1000 examples
<mnist> done
<trainer> on training set:
<trainer> online epoch # 1 [batchSize = 10]
[===================>.................... 471/2000 ....................................] ETA: 2m20s | Step: 92ms
В общем неплохо, для сравнения — на десктопе с i5-4590 CPU @ 3.30GHz, без использования GPU:
[=======================>................ 571/2000 ....................................] ETA: 27s613ms | Step: 19ms
Т.е в этом примере малинка работает примерно в 5 раз медленнее чем современный десктоп.
Распознавания изображений
одушевлённый теперь «Алдан» иногда печатал на выходе: «Думаю. Прошу не мешать» [1]
Теперь настал черёд заставить малинку распознавать изображения с помощью натренированной googlenet. Тут меня ждал второй подвох: в Alexnet такое огромное количество параметров, что памяти малинки уже не хватает. Но тут на выручку приходит squeezenet и Network-in-Network, автор последней даже сделал натренерованную модель в формате для torch[7].
Для начала надо преобразовать модель так чтобы её можно было использовать на архитектуре ARM (тренировать на Raspberry PI не стоит — результаты будут готовы лет через сто).
На десктопе надо загрузить модель в бинарном формате torch, и записать в формате ‘ascii’, потом на малинке — преобразовать обратно:
Desktop:
model=torch.load(‘blah.t7’)
torch.save(‘blah_ascii.t7’,model,’ascii’)
Raspberry PI:
model=torch.load(‘blah_ascii.t7’,’ascii’)
torch.save(‘blah_arm.t7’,model)
Версию для arm можно загрузить тут[8].
Я сделал маленький скриптик для проверки работоспособности на малинке:
Полный текст тут[9].
...
local m=torch.load(prefix..'nin_bn_final_arm.t7')
...
local input=image.load(prefix.."n07579787_ILSVRC2012_val_00049211.JPEG")
...
local output=model:forward(cropped)
...
И вуаля, запускаем с изображением из тестового набора ImageNET[10]:
>th test_single.lua n07579787_ILSVRC2012_val_00049211.JPEG
loading model:0.57sec
Running neural net:13.46sec
25.3%: n07579787: plate
13.8%: n07873807: pizza, pizza pie
8.8%: n04263257: soup bowl
8.0%: n07590611: hot pot, hotpot
7.2%: n07831146: carbonara
T.e за 14 сек малинка удачно справилась с процедурой распознавания образов!
Настало время сделать пример поинтересней: приделываем интерфейс к камере из пакета camera и веб интерфейс из пакета display, и у нас получается интерактивная машина, раз в 14 секунд объявляющая миру что она видит. Надо только установить пакет для работы с камерой (luarocks install camera) и для визуализации через веб-интерфейс (luarocks install display).
…
-- подцепляем камеру
local cam = image.Camera {idx=0,width=iW,height=iH}
...
local frame = cam:forward()
local cropped = image.crop(frame, w1, h1, w1+oW, h1+oH) -- center patch
…
-- отправляем кадр зрителю
display_sample_in.win=display.image(cropped,display_sample_in)
…
-- прогоняем его через нейросеть
local output=model:forward(cropped)
…
-- отправляем описание - чего увидели
display_output.win=display.text(out_text,display_output)
Перед запуском надо запустить демона из пакета display: nohup th -ldisplay.start 8000 0.0.0.0 &
Испытания
Тестовая установка:
Результат:
Заключение
Итак у нас получилась недорогая машинка для распознавания образов, которой вы можете порадовать ваших друзей во время новогодних праздников. Естественно, задача классификации изображения может быть заменена на что-то более продуктивное, например можно легко сделать систему для идентификации человека по физиономии, несколько примеров есть тут[12] или можно идентифицировать фигуры людей[13] на камере наблюдения за вашим огородом.
Для оптимизации производительности можно попробовать использовать nnpack[14], или даже сделать интерфейс к векторному ускорителю встроенному в процессор малинки как тут[15].
Примечания:
Цитаты из «Понедельник начинается в субботу» и «Сказка о тройке» А. и Б. Стругацких.
Описание процедуры на английском и репозиторий со всеми исходниками находится на гитхабе[16].
Использованны источники
- ^ Torch (github.com)
- ^ малинку (torch.ch)
- ^ пофиксить это дело (github.com)
- ^ https://github.com/vfonov/deep-pi/releases/download/v1/torch_intstall_raspbian_arm6l_20161218.tar.gz (github.com)
- ^ MNIST (yann.lecun.com)
- ^ наборе демок (github.com)
- ^ модель в формате для torch (gist.github.com)
- ^ тут (github.com)
- ^ тут (github.com)
- ^ ImageNET (image-net.org)
- ^ тут (github.com)
- ^ тут (torch.ch)
- ^ идентифицировать фигуры людей (github.com)
- ^ nnpack (github.com)
- ^ тут (github.com)
- ^ гитхабе (github.com)