Каждый должен делать свою работу качественно и в срок. Допустим, вам нужно сделать веб-сервис классификации картинок на базе обученной нейронной сети с помощью библиотеки caffe. В наши дни качество — это асинхронные неблокирующие вызовы, возможность параллельного исполнения нескольких заданий при наличии свободных процессорных ядер, мониторинг очередей заданий… Библиотека RQ позволяет реализовать все это в сжатые сроки без изучения тонны документации.
Сделаем веб-сервис на одном сервере, ориентированный на несильно нагруженные проекты и сравнительно длительные задания. Естественно, его применение не ограничивается этими вашими нейронными сетями.
Постановка задачи
Входными данными является файл (например, картинка в формате JPEG). Для простоты считаем, что ее уже разместили в выделенную директорию. Выходными данными является строка в формате JSON. Для солидности будем пользоваться стандартными кодами результатов HTTP.
Веб-сервис будет реализовывать два HTTP-вызова (назовём это API):
- /process/[имя файла] — обработать файл (предварительно загружаем входной файл в выделенную директорию, возвращаем идентификатор задания)
- /result/[идентификатор задания] — получить результат (если результат не готов, возвращаем код 202 «Not ready», если результат готов — возвращаем json, если идентификатор задания не существует, возвращаем код 404 «Not found»)
Инсталлируем компоненты в Ubuntu
В качестве HTTP-сервера будем использовать Flask. Установка:
Если не установлен pip:
sudo apt-get install python-pip
sudo apt-get install --upgrade pipСобственно, установка Flask:
pip install flaskТеперь нужно установить Redis — хранилище данных и брокер сообщений:
wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make
sudo make installУстановка библиотеки RQ (Redis Queue):
pip install rqДля автоматического запуска и конфигурации всех компонентов будем использовать Supervisor:
sudo apt-get install supervisorПишем наш сервис
Это легко в Flask. Создадим файл deep_service.py:
#Путь к директории, выделенной под хранение входных файлов
BASEDIR = '/home/sergey/verysecure'
#Импортируем служебные библиотеки
import argparse
import os
import json
#Импортируем только что установленные компоненты нашего сервиса
from flask import Flask
app = Flask(__name__)
from redis import Redis
from rq import Queue
#Импортируем функцию, реализующую наш вычислительный процесс, который будет выполняться асинхронно
#classify.py - модуль, поставляемый с библиотекой машинного обучения caffe
#Естественно, вместо него можно импортировать собственные разработки
from classify import main
#Подключаемся к базе данных Redis
q = Queue(connection=Redis(), default_timeout=3600)
#Реализуем первый вызов нашего API
@app.route('/process/<path:file_path>')
def process(file_path):
full_path = os.path.join(BASEDIR, file_path) #входной файл лежит в директории BASEDIR
argv = {'input_file': full_path,
'gpu': True}
args = argparse.Namespace(**argv)
r = q.enqueue_call(main, args=(args,), result_ttl=86400)
return r.id
#В порядке обмена опытом: ограничим 4-мя цифрами после запятой вещественные числа,
#при сериализации в JSON из массива numpy
def decimal_default(obj):
if isinstance(obj, float32):
return round(float(obj), 4)
else:
raise TypeError()
#Реализуем второй вызов нашего API
@app.route('/result/<id>')
def result(id):
try:
job = q.fetch_job(id)
if job.is_finished:
return json.dumps(job.result, ensure_ascii=False, default=decimal_default)
else:
return 'Not ready', 202
except:
return "Not found", 404
if __name__ == '__main__':
app.run()
#app.run(debug=False, host='0.0.0.0')Запуск вручную — проверяем как работает
На этом этапе можно проверить, работает ли наш веб-сервис. Запускаем Redis:
redis-serverЗапускаем один рабочий процесс (их можно запускать несколько, например по количеству процессорных ядер или по наличию нескольких видеокарт, если они требуются для обработки данных). Процесс лучше запускать из той директории, в которой будут запускаться вычислительные функции, в нашем случае это там, где лежит classif.py:
rq workerЗапускаем http-сервер:
python deep_service.pyЗаписываем в директорию для входных данных картинку cat.jpg и выполняем запрос к сервису:
wget 127.0.0.1/process/cat.jpgВ ответ получаем идентификатор задания. Копируем идентификатор и выполняем второй запрос к сервису:
wget 127.0.0.1/result/[идентификатор]В ответ получаем строку JSON с весовыми коэффициентами принадлежности картинки категориям IMAGENET.
Теперь осталось сконфигурировать автоматический запуск компонентов нашего сервера.
Автозапуск
Настройка supervisor — возможно, самая сложная часть этого пути. Хороший обучающий материал по настройке supervisor тут.
Прежде всего нужно понять, что supervisor запускает каждый процесс в собственном о��ружении. В большинстве случаев сложных вычислений программа, их реализующая, зависит от ряда настроек, например путей. Эти настройки обычно хранятся в файле /home/usersname/.bashrc
Например, библиотека нейросетевых вычислений caffe и питоновские модули к ней потребовали дописать в этот файл следующие строки:
export PATH=/usr/local/cuda-7.5/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-7.5/lib64:$LD_LIBRARY_PATH
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
export PKG_CONFIG_PATH
LD_LIBRARY_PATH=/home/username/caffe/build/lib:$LD_LIBRARY_PATH
export PYTHONPATH="${PYTHONPATH}:/home/username/caffe/python"Скопируйте эти строки в буфер обмена!
В директории /usr/local/bin создайте файл deep_worker.sh
#!/bin/bash
cd /home/username/caffe/python
export PATH=/usr/local/cuda-7.5/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-7.5/lib64:$LD_LIBRARY_PATH
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
export PKG_CONFIG_PATH
LD_LIBRARY_PATH=/home/username/caffe/build/lib:$LD_LIBRARY_PATH
export PYTHONPATH="${PYTHONPATH}:/home/username/caffe/python"
rq workerНу вы поняли — в первой строчке мы переходим в рабочую директорию, затем вставляем переменные окружения, скопированные из .bashrc, затем запускаем процесс.
В директории /usr/local/bin создайте файл deep_flask.sh
#!/bin/bash
cd /home/username/caffe/python
export PATH=/usr/local/cuda-7.5/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-7.5/lib64:$LD_LIBRARY_PATH
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
export PKG_CONFIG_PATH
LD_LIBRARY_PATH=/home/username/caffe/build/lib:$LD_LIBRARY_PATH
export PYTHONPATH="${PYTHONPATH}:/home/username/caffe/python"
python deep_service.pyОпять — в первой строчке мы переходим в рабочую директорию, затем вставляем переменные окружения, скопированные из .bashrc, затем запускаем наш Flask-сервер.
Немного системного администрирования:
sudo chmod +x /usr/local/bin/deep_flask.sh
sudo chmod +x /usr/local/bin/deep_worker.sh
mkdir /var/log/deepserviceВ директории /etc/supervisor/conf.d создайте файл deepservice.conf:
[program:redis]
command=/usr/local/bin/redis-server
autostart=true
autorestart=true
stderr_logfile=/var/log/deepservice/redis.err.log
stdout_logfile=/var/log/deepservice/redis.out.log
[program:worker1]
command=/usr/local/bin/deep_worker.sh
autostart=true
autorestart=true
stderr_logfile=/var/log/deepservice/worker1.err.log
stdout_logfile=/var/log/deepservice/worker1.out.log
user=username
directory=/home/username/caffe/python
[program:flask]
command=/usr/local/bin/deep_flask.sh
autostart=true
autorestart=true
stderr_logfile=/var/log/deepservice/flask.err.log
stdout_logfile=/var/log/deepservice/flask.out.log
user=username
directory=/home/username/caffe/pythonНаконец, запустим всю эту конструкцию:
sudo supervisorctl reread
sudo supervisorctl updateВсё!
