Статья выбрана из блога Hive, автор: Bowei, составлена сердцем машины
В этой статье будет представлен способ быстрого развертывания обученной модели машинного обучения в рабочей среде. Если вы уже обучили модель машинного обучения с помощью среды глубокого обучения, такой как TensorFlow или Caffe, эту модель можно использовать в качестве демонстрации. Если вы предпочитаете облегченное решение, прочитайте эту статью.
Адрес GitHub: https://github.com/hiveml/simple-ml-serving
Включены записи:
Проверьте установку TensorFlow: https://github.com/hiveml/simple-ml-serving/blob/master/test/test_tensorflow.sh
Запуск онлайн-классификации со стандартным вводом: https://github.com/hiveml/simple-ml-serving/blob/master/test/test_label_image.sh
Запустите онлайн-классификацию на локальном хосте: https://github.com/hiveml/simple-ml-serving/blob/master/test/test_tf_classify_server.sh
Поместите классификатор за жестко заданным прокси: https://github.com/hiveml/simple-ml-serving/blob/master/test/test_basic_proxy.sh
Поместите классификатор за прокси-сервером, который позволяет обнаруживать службы: https://github.com/hiveml/simple-ml-serving/blob/master/test/test_seaport_proxy.sh
Включить классификатор с псевдо-DN: https://github.com/hiveml/simple-ml-serving/blob/master/test/test_p2p_proxy.sh
Машинное обучение в производстве
В первый раз, когда мы вошли в пространство машинного обучения Hive, у нас уже были миллионы помеченных изображений, которые позволили нам обучить (т. е. случайным весам) современную глубокую сверточную классификацию изображений для конкретных случаев использования с нуля в течение в неделю Модель. Более типичные варианты использования машинного обучения обычно основаны на сотнях изображений, и в этом случае я рекомендую доработать существующую модель. Например, на странице https://www.tensorflow.org/tutorials/image_retraining есть руководство по тонкой настройке модели ImageNet для классификации набора данных образцов цветов (3647 изображений, 5 классов).
После установки Bazel и TensorFlow вам нужно запустить следующий код, сборка которого занимает около 30 минут, а обучение — 5 минут:
(
cd "$HOME" && \
curl -O http://download.tensorflow.org/example_images/flower_photos.tgz && \
tar xzf flower_photos.tgz ;
) && \
bazel build tensorflow/examples/image_retraining:retrain \
tensorflow/examples/image_retraining:label_image \
&& \
bazel-bin/tensorflow/examples/image_retraining/retrain \
--image_dir "$HOME"/flower_photos \
--how_many_training_steps=200
&& \
bazel-bin/tensorflow/examples/image_retraining/label_image \
--graph=/tmp/output_graph.pb \
--labels=/tmp/output_labels.txt \
--output_layer=final_result:0 \
--image=$HOME/flower_photos/daisy/21652746_cc379e0eea_m.jpg
Или, если у вас есть Docker, вы можете использовать готовый образ Docker,
sudo docker run -it --net=host liubowei/simple-ml-serving:latest /bin/bash
>>> cat test.sh && bash test.sh
Войдите в интерактивную оболочку в контейнере и выполните указанную выше команду. Вы также можете прочитать ниже и следовать инструкциям ниже в контейнере.
TensorFlow теперь сохраняет информацию о модели в /tmp/output_graph.pb и /tmp/output_labels.txt, которые вводятся в качестве аргументов командной строки для label_image.py (GitHub.com/tensorflow/…) сценарий. Учебник по распознаванию изображений от Google также работает с другим скриптом (GitHub.com/tensorflow/…), но в этом примере мы продолжим использовать label_image.py.
Преобразование одноточечного вывода в онлайн-вывод (TensorFlow)
Если мы просто хотим принимать имена файлов из стандартного ввода, по одному в строке, мы можем легко реализовать «онлайн» вывод:
while read line ; do
bazel-bin/tensorflow/examples/image_retraining/label_image \
--graph=/tmp/output_graph.pb --labels=/tmp/output_labels.txt \
--output_layer=final_result:0 \
--image="$line" ;
done
С точки зрения производительности это ужасно: нам нужно перезагружать нейронную сеть, веса, всю архитектуру TensorFlow и Python для каждого входного образца!
Конечно, мы можем сделать лучше. Начнем с редактирования скрипта label_image.py. Его адрес: bazel-bin/tensorflow/examples/image_retraining/label_image.runfiles/org_tensorflow/tensorflow/examples/image_retraining/label_image.py.
Мы поместим следующую строку
141: run_graph(image_data, labels, FLAGS.input_layer, FLAGS.output_layer,
142: FLAGS.num_top_predictions)
Измените его на:
141: for line in sys.stdin:
142: run_graph(load_image(line), labels, FLAGS.input_layer, FLAGS.output_layer,
142: FLAGS.num_top_predictions)
Это намного быстрее, но все же не самое лучшее!
Причина в том, что tf.Session() используется как конструкция sess в строке 100. По сути, TensorFlow загружает все вычисления в память каждый раз, когда включен run_graph. Это очевидно, если вы попытаетесь выполнить логический вывод на графическом процессоре, вы увидите, что память графического процессора увеличивается и уменьшается по мере того, как TensorFlow загружает и выгружает параметры модели на графическом процессоре. Насколько мне известно, этой конструкции нет в других фреймворках машинного обучения, таких как Caffe или PyTorch.
Решение состоит в том, чтобы удалить оператор with и добавить переменную sess в run_graph:
def run_graph(image_data, labels, input_layer_name, output_layer_name,
num_top_predictions, sess):
# Feed the image_data as input to the graph.
# predictions will contain a two-dimensional array, where one
# dimension represents the input image count, and the other has
# predictions per class
softmax_tensor = sess.graph.get_tensor_by_name(output_layer_name)
predictions, = sess.run(softmax_tensor, {input_layer_name: image_data})
# Sort to show labels in order of confidence
top_k = predictions.argsort()[-num_top_predictions:][::-1]
for node_id in top_k:
human_string = labels[node_id]
score = predictions[node_id]
print('%s (score = %.5f)' % (human_string, score))
return [ (labels[node_id], predictions[node_id].item()) for node_id in top_k ] # numpy floats are not json serializable, have to run item... with tf.Session() as sess: for line in sys.stdin:
run_graph(load_image(line), labels, FLAGS.input_layer, FLAGS.output_layer,
FLAGS.num_top_predictions, sess)
Кодовый адрес:GitHub.com/hiveml/simp…
После запуска вы обнаружите, что каждое изображение занимает около 0,1 секунды, что достаточно быстро для использования в Интернете.
Преобразование одноточечного вывода в онлайн-вывод (другие платформы машинного обучения)
Caffe использует свой код net.forward, см.:NB Viewer Fear Intel.org/GitHub/B VLC….
Mxnet также уникален: он фактически открыл доступный код сервера логического вывода:GitHub.com/AWS labs/Мэн Сюэнонг….
развертывать
План состоит в том, чтобы обернуть код в приложение Flask. Flask — это легкая веб-инфраструктура Python, которая позволяет запускать HTTP-серверы API с минимальными усилиями.
В качестве быстрого вывода следующее приложение Flask принимает POST-запросы multipart/form-data:
#!/usr/bin/env python
# usage: python echo.py to launch the server ; and then in another session, do
# curl -v -XPOST 127.0.0.1:12480 -F "data=@./image.jpg"
from flask import Flask, request
app = Flask(__name__)
@app.route('/', methods=['POST'])
def classify():
try:
data = request.files.get('data').read()
print repr(data)[:1000]
return data, 200
except Exception as e:
return repr(e), 500
app.run(host='127.0.0.1',port=12480)
Ниже приведено соответствующее приложение Flask, которое можно подключить к упомянутому выше run_graph:
And here is the corresponding flask app hooked up to run_graph above:
#!/usr/bin/env python
# usage: bash tf_classify_server.shfrom flask import Flask, request
import tensorflow as tf
import label_image as tf_classify
import json
app = Flask(__name__)
FLAGS, unparsed = tf_classify.parser.parse_known_args()
labels = tf_classify.load_labels(FLAGS.labels)
tf_classify.load_graph(FLAGS.graph)
sess = tf.Session()
@app.route('/', methods=['POST'])
def classify():
try:
data = request.files.get('data').read()
result = tf_classify.run_graph(data, labels, FLAGS.input_layer, FLAGS.output_layer, FLAGS.num_top_predictions, sess)
return json.dumps(result), 200
except Exception as e:
return repr(e), 500
app.run(host='127.0.0.1',port=12480)
Выглядит нормально, за исключением того, что Flask и TensorFlow полностью синхронизированы: при выполнении классификации изображений Flask обрабатывает запросы по одному в том порядке, в котором они получены, а TensorFlow — полностью потокоемкий.
Как упоминалось выше, узкое место в скорости может по-прежнему заключаться в фактическом объеме вычислений, поэтому нет особого смысла обновлять код оболочки Flask. Возможно, этого кода достаточно для обработки загрузки. Существует два очевидных способа масштабирования пропускной способности запросов: горизонтально за счет увеличения количества рабочих потоков (описанных в следующем разделе) или вертикально за счет использования графических процессоров и логики пакетной обработки. Последняя реализация требует, чтобы веб-сервер одновременно обрабатывал несколько ожидающих запросов и решал, ждать ли больших пакетов или отправлять их в поток графа TensorFlow для сортировки, что совершенно не подходит для приложений Flask. Два способа написания кода на Python с использованием Twisted + Klein: Node.js + ZeroMQ, если вы предпочитаете первоклассную поддержку циклов обработки событий и хотите иметь возможность подключаться к платформам машинного обучения, отличным от Python, таким как Torch.
Масштабирование: балансировка нагрузки и обнаружение сервисов
Теперь у нас есть сервер с доступной моделью, но он может быть слишком медленным или наша нагрузка слишком высока. Мы хотим запустить больше таких серверов, так как же нам распределить их по нескольким серверам? Обычный подход заключается в добавлении прокси-уровня, который может быть haproxy или nginx, который балансирует нагрузку между внутренними серверами, предоставляя пользователю унифицированный интерфейс. Вот пример кода для запуска элементарного http-прокси балансировщика нагрузки Node.js:
// Usage : node basic_proxy.js WORKER_PORT_0,WORKER_PORT_1,...
const worker_ports = process.argv[2].split(',')
if (worker_ports.length === 0) { console.err('missing worker ports') ; process.exit(1) }
const proxy = require('http-proxy').createProxyServer({})proxy.on('error', () => console.log('proxy error'))let i = 0require('http').createServer((req, res) => {
proxy.web(req,res, {target: 'http://localhost:' + worker_ports[ (i++) % worker_ports.length ]})
}).listen(12480)
console.log(`Proxying localhost:${12480} to [${worker_ports.toString()}]`)
// spin up the ML workersconst { exec } = require('child_process')
worker_ports.map(port => exec(`/bin/bash ./tf_classify_server.sh ${port}`))
Для автоматического определения количества и адресов внутренних серверов обычно используется инструмент «обнаружения службы», который может быть связан или не связан с балансировщиком нагрузки. Некоторые известные инструменты, такие как Consul и Zookeeper. Настройка и изучение того, как использовать такой инструмент, выходит за рамки этой статьи, поэтому я сделал вывод об очень примитивном прокси-сервере, использующем морской порт пакета обнаружения службы node.js. Прокси-код:
// Usage : node seaport_proxy.js
const seaportServer = require('seaport').createServer()
seaportServer.listen(12481)
const proxy = require('http-proxy').createProxyServer({})
proxy.on('error', () => console.log('proxy error'))
let i = 0require('http').createServer((req, res) => {
seaportServer.get('tf_classify_server', worker_ports => {
const this_port = worker_ports[ (i++) % worker_ports.length ].port
proxy.web(req,res, {target: 'http://localhost:' + this_port })
})
}).listen(12480)
console.log(`Seaport proxy listening on ${12480} to '${'tf_classify_server'}' servers registered to ${12481}`)
Код рабочего потока:
// Usage : node tf_classify_server.js
const port = require('seaport').connect(12481).register('tf_classify_server')
console.log(`Launching tf classify worker on ${port}`)
require('child_process').exec(`/bin/bash ./tf_classify_server.sh ${port}`)
Однако применительно к машинному обучению эта конфигурация страдает от проблем с пропускной способностью.
Если система обрабатывает десятки или сотни изображений в секунду, она может застрять в пропускной способности системы. При текущей настройке все данные должны проходить через наш единственный хост морского порта, который также является единой конечной точкой, обращенной к клиенту.
Чтобы решить эту проблему, нам нужно, чтобы клиент не попадал в единственную конечную точку: http://127.0.0.1:12480, а автоматически переключался между внутренними серверами для достижения цели. Если вы понимаете сетевую архитектуру, это больше похоже на работу с DNS.
Однако настройка пользовательских DNS-серверов выходит за рамки этой статьи. Адаптируя клиентский код для следования протоколу 2-го порядка «ручной DNS», мы можем повторно использовать базовый прокси-сервер морского порта для реализации «сквозного» протокола, где клиент может напрямую подключаться к серверу:
Прокси-код:
// Usage : node p2p_proxy.js
const seaportServer = require('seaport').createServer()
seaportServer.listen(12481)
let i = 0
require('http').createServer((req, res) => {
seaportServer.get('tf_classify_server', worker_ports => {
const this_port = worker_ports[ (i++) % worker_ports.length ].port
res.end(`${this_port}
`)
})
}).listen(12480)
console.log(`P2P seaport proxy listening on ${12480} to 'tf_classify_server' servers registered to ${12481}`)
(рабочий код такой же, как указано выше)
Код клиента:
curl -v -XPOST localhost:`curl localhost:12480` -F"data=@$HOME/flower_photos/daisy/21652746_cc379e0eea_m.jpg"
Заключение и дальнейшее чтение
В этот момент вы должны начать что-то делать, но это определенно не та технология, которая не выйдет из моды. Есть много важных тем, не затронутых в этой статье:
- Автоматизированная разработка и сборка на новом оборудовании
На вашем собственном оборудовании инструменты для наблюдения включают Openstack/VMware, Chef/Puppet для установки Docker, управления сетевыми путями, Docker для установки TensorFlow, Python и другие.
В облаке отлично подходят Kubernetes или Marathon/Mesos.
Управление версиями модели
Поначалу управлять моделью вручную несложно.
TensorFlow Serving — отличный инструмент для решения этой проблемы, наряду с пакетной обработкой и монолитным развертыванием, очень тщательно. Недостатком является то, что его немного сложно настроить, сложно написать код на стороне клиента, и он пока не поддерживает Caffe/PyTorch.
Как перенести код машинного обучения из Matlab?
Не используйте Matlab при разработке продуктов.
Драйвер графического процессора, Cuda, CUDNN
Используйте контейнеры nvidia и попробуйте найти файлы Dorckerfile в Интернете.
слой постобработки. Как только вы обнаружите несколько разных моделей машинного обучения в процессе разработки продукта, вы можете захотеть смешать эти модели и сопоставить разные модели для разных вариантов использования — например, модель B не может запустить модель A, запустить модель в Caffe C и отправить результаты в модель D, работающую на TensorFlow, и так далее.