Перенос модели машинного обучения в production
Перенос модели машинного обучения в production
Весь код и конфиги из этой статьи доступны в репозитория GitHub.
Pipeline для построения моделей машинного обучения часто заканчивается на этапе оценки качества модели: вы достигли приемлемой точности и на этом все.
Замечание: конечно от machine learning engineer не всегда требуется доведение модели до production. И даже если это нужно, эта часто задача делегируется системному администратору.
Однако в настоящее время многие исследователи/инженеры несут (возможно моральную) ответственность за построение полного pipeline: от построения моделей до доведения их до production. Университетский проект это, личным эксперимент или же коммерческий продукт, демонстрация его работы является отличным способом заинтересовать широкую аудиторию. Немногие люди будут прилагать дополнительные усилия для исследования модели или продукта, результаты которого достаточно сложно воспроизвести.
Мы рассмотрим инфраструктуру основанную на Python фреймворках и на серверах Linux. Она будет включать:
- Anaconda: для управления установкой пакетов и создания изолированной среды Python 3.
- Keras: высокоуровневый API для нейронных сетей, который способен работать поверх TensorFlow, CNTK или Theano.
- Flask: минималистическый python framework для создания RESTful API. Замечание: встроенный сервер Flask не подходит для развертывания в production, так как обслуживает только один запрос по умолчанию. Этот сервер в большей степени предназначен для более простой отладки.
- nginx: стабильный веб-сервер, который реализует такой функционал, как балансировка нагрузки, конфигурация SSL и т. д.
- uWSGI: высоко конфигурируемый WSGI сервер(Web Server Gateway Interface), который позволяет нескольким worker'ам одновременно обслуживать несколько запросов.
- systemd: система init, используемая в нескольких дистрибутивах Linux для управления системными процессами после загрузки.
Несколько заметок в начале этого руководства:
- Большинство компонентов, приведенных выше, могут быть легко заменены эквивалентными, практически без изменений в остальных элементах выстроенного pipeline. Например, Keras можно легко заменить PyTorch, Flask можно легко заменить Bottle и тд.
- Когда мы говорим о переносе в production мы говорим не о enterprise масштабах огромной компании. Цель состоит в том, чтобы сделать все возможное в рамках одного сервера с большим количеством процессорных ядер и большим количеством оперативной памяти.
Настройка окружения
Для начала нам нужно установить пакеты systemd и nginx:sudo apt-get install systemd nginx
Затем мы должны установить Anaconda, следуя инструкциям на официальном сайте, которые состоят в загрузке исполняемого файла, его запуске и добавлении Anaconda в PATH вашей системы. Ниже мы предположим, что Anaconda установлена в домашнем каталоге.
Затем создадим изолированную среду Anaconda из файла environment.yml. Вот как выглядит этот файл (он уже содержит несколько фреймворков, которые мы будем использовать):
name: production_ml_env channels: - conda-forge dependencies: - python=3.6 - keras - flask - uwsgi - numpy - pip - pip: - uwsgitop
Для создания среды мы запускаем следующее:
conda env create --file environment.yml
Для активировация полученной среду, мы делаем:
source activate production_ml_env
К настоящему времени у нас есть Keras, flask, uwsgi, uwsgitop и т. д. Итак, мы готовы начать.
Построение веб-приложения на Flask
В рамках этого урока мы не будем глубоко погружаться в то, как создавать свою ML модель. Вместо этого мы адаптируем пример классификации текстовых новостей, используя датасет Reuters newswire, идущий в поставке с Keras. Это код для построения классификатора:'''Trains and evaluate a simple MLP on the Reuters newswire topic classification task. ''' from __future__ import print_function import os import numpy as np import keras from keras.datasets import reuters from keras.models import Sequential from keras.layers import Dense, Dropout, Activation from keras.preprocessing.text import Tokenizer from keras.callbacks import ModelCheckpoint MODEL_DIR = './models' max_words = 1000 batch_size = 32 epochs = 5 print('Loading data...') (x_train, y_train), (x_test, y_test) = reuters.load_data(num_words=max_words, test_split=0.2) print(len(x_train), 'train sequences') print(len(x_test), 'test sequences') num_classes = np.max(y_train) + 1 print(num_classes, 'classes') print('Vectorizing sequence data...') tokenizer = Tokenizer(num_words=max_words) x_train = tokenizer.sequences_to_matrix(x_train, mode='binary') x_test = tokenizer.sequences_to_matrix(x_test, mode='binary') print('x_train shape:', x_train.shape) print('x_test shape:', x_test.shape) print('Convert class vector to binary class matrix ' '(for use with categorical_crossentropy)') y_train = keras.utils.to_categorical(y_train, num_classes) y_test = keras.utils.to_categorical(y_test, num_classes) print('y_train shape:', y_train.shape) print('y_test shape:', y_test.shape) print('Building model...') model = Sequential() model.add(Dense(512, input_shape=(max_words,))) model.add(Activation('relu')) model.add(Dropout(0.5)) model.add(Dense(num_classes)) model.add(Activation('softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) if not os.path.exists(''): os.makedirs(MODEL_DIR) mcp = ModelCheckpoint(os.path.join(MODEL_DIR, 'reuters_model.hdf5'), monitor="val_acc", save_best_only=True) history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_split=0.1, callbacks=[mcp]) score = model.evaluate(x_test, y_test, batch_size=batch_size, verbose=1) print('Test score:', score[0]) print('Test accuracy:', score[1])
Для воспроизведения настроек, которые мы здесь используем, просто запустите следующие команды при обучении модели. Это позволит обучаться без GPU - на CPU:
export CUDA_VISIBLE_DEVICES=-1
KERAS_BACKEND=theano python build_classifier.py
Это создаст сериализованный файл reuters_model.hdf5 из обученной модели в папке models. Теперь мы готовы использовать модель через Flask на порту 4444. В приведенном ниже коде мы предоставляем единственную точку входа для REST запроса: /predict, которая представлена в виде запроса GET, где текст для классификации передается в качестве параметра. Возвращенный JSON имеет форму {"prediction": "N"}, где N - целое число, обозначающее предсказанный класс.
from flask import Flask from flask import request from keras.models import load_model from keras.datasets import reuters from keras.preprocessing.text import Tokenizer, text_to_word_sequence from flask import jsonify import os MODEL_DIR = './models' max_words = 1000 app = Flask(__name__) print("Loading model") model = load_model(os.path.join(MODEL_DIR, 'reuters_model.hdf5')) # we need the word index to map words to indices word_index = reuters.get_word_index() tokenizer = Tokenizer(num_words=max_words) def preprocess_text(text): word_sequence = text_to_word_sequence(text) indices_sequence = [[word_index[word] if word in word_index else 0 for word in word_sequence]] x = tokenizer.sequences_to_matrix(indices_sequence, mode='binary') return x @app.route('/predict', methods=['GET']) def predict(): try: text = request.args.get('text') x = preprocess_text(text) y = model.predict(x) predicted_class = y[0].argmax(axis=-1) print(predicted_class) return jsonify({'prediction': str(predicted_class)}) except: response = jsonify({'error': 'problem predicting'}) response.status_code = 400 return response if __name__ == "__main__": app.run(host='0.0.0.0', port=4444)
Чтобы запустить Flask сервер приложений, мы выполняем:
python app.py
Вы можете протестировать его с помощью любого REST клиента(например, Postman) или просто перейдя по этому URL-адресу в своем веб-браузере (замените your_server_url URL-адресом вашего сервера):
http://your_server_url:4444/pred?text=this is a news sample text about sports and football in specific
И вы получите ответ
{ "class": "11" }
Настройка uWSGI сервера
Теперь мы готовы масштабировать наш сервер приложений. uWSGI будет ключевым игроком здесь. Он связывается с нашим Flask приложением, вызывая app в файле app.py. uWSGI включает в себя большое количество функций распараллеливания, которыми мы и воспользуемся. Его конфигурационный файл выглядит следующим образом:[uwsgi] # placeholders that you have to change my_app_folder = /home/harkous/Development/production_ml my_user = harkous socket = %(my_app_folder)/production_ml.sock chdir = %(my_app_folder) file = app.py callable = app # environment variables env = CUDA_VISIBLE_DEVICES=-1 env = KERAS_BACKEND=theano env = PYTHONPATH=%(my_app_folder):$PYTHONPATH master = true processes = 5 # allows nginx (and all users) to read and write on this socket chmod-socket = 666 # remove the socket when the process stops vacuum = true # loads your application one time per worker # will very probably consume more memory, # but will run in a more consistent and clean environment. lazy-apps = true uid = %(my_user) gid = %(my_user) # uWSGI will kill the process instead of reloading it die-on-term = true # socket file for getting stats about the workers stats = %(my_app_folder)/stats.production_ml.sock # Scaling the server with the Cheaper subsystem # set cheaper algorithm to use, if not set default will be used cheaper-algo = spare # minimum number of workers to keep at all times cheaper = 5 # number of workers to spawn at startup cheaper-initial = 5 # maximum number of workers that can be spawned workers = 50 # how many workers should be spawned at a time cheaper-step = 3
Нам нужно изменить параметр my_app_folder, чтобы быть папкой вашего собственного каталога приложений, а параметр my_user - вашим собственным именем пользователя. В зависимости от ваших потребностей и местоположений файлов вам может потребоваться изменить/добавить другие параметры.
Одним из важных разделов в uwsgi.ini является часть, в которой мы используем Cheaper subsystem в uWSGI, которая позволяет нам запускать несколько workers параллельно, чтобы обслуживать несколько параллельных запросов. Это одна из интересных особенностей uWSGI, где динамическое масштабирование вверх и вниз возможно с помощью нескольких параметров. При вышеуказанной конфигурации мы будем иметь как минимум 5 workers в любое время. При увеличении нагрузки проще будет выделять 3 дополнительных worker за раз, пока все запросы не найдут свой worker. Максимальное количество workers выше установлено в 50.
В вашем случае лучшие параметры конфигурации зависят от количества ядер на сервере, общей доступной памяти и потреблением памяти вашего приложения. Взгляните на официальные документы для расширенных вариантов развертывания.
Связывание uWSGI и nginx
Если мы начнем работать с uWSGI сейчас (мы это сделаем немного позже), оно позаботится о вызове приложения из файла app.py, и мы получим все возможности масштабирования, которые он предоставляет. Но мы хотим получить запросы REST из Интернета и передать их в приложение Flask через uWSGI. Для этого мы будем настраивать nginx.Вот простой конфигурационный файл для nginx. Конечно, nginx можно дополнительно использовать для настройки SSL или для статических файлов, но это выходит за рамки этой статьи.
server { listen 4444; # change this to your server name or IP server_name YOUR_SERVER_NAME_OR_IP; location / { include uwsgi_params; # change this to the location of the uWSGI socket file (set in uwsgi.ini) uwsgi_pass unix:/home/harkous/Development/production_ml/production_ml.sock; } }
Мы размещаем этот файл в /etc/nginx/sites-available/nginx_production_ml (для этого вам понадобится sudo доступ). Затем, чтобы включить эту конфигурацию nginx, мы связываем ее с sites-enabled директорией:
sudo ln -s /etc/nginx/sites-available/nginx_production_ml /etc/nginx/sites-enabled
Затем перезапускаем nginx:
sudo service nginx restart
Настройка службы systemd
Наконец, мы запустим ранее настроенный сервер uWSGI. Однако, чтобы гарантировать, что наш сервер не умрет навсегда после перезапуска системы или неожиданных сбоев, мы запустим ее как службу systemd. Вот наш конфигурационный файл службы, который мы размещаем в каталоге /etc/systemd/system, используя:sudo vi /etc/systemd/system/production_ml.service
[Unit] Description=uWSGI instance to serve production_ml service [Service] User=harkous Group=harkous WorkingDirectory=/home/harkous/Development/production_ml/ ExecStart=/home/harkous/anaconda3/envs/production_ml_env/bin/uwsgi --ini /home/harkous/Development/production_ml/uwsgi.ini Restart=on-failure [Install] WantedBy=multi-user.target
Затем стартуем сервис с:
sudo systemctl start production_ml.service
Чтобы разрешить запуск этого сервиса при перезагрузке устройства:
sudo systemctl enable production_ml.service
На этом этапе наш сервис должно успешно стартовать. В случае обновления каких-либо конфигов, мы должны перезапустить его:
sudo systemctl restart production_ml.service
Мониторинг сервиса
Чтобы следить за сервисом и видеть нагрузку на одного worker, мы можем использовать uwsgitop. В uwsgi.ini мы уже настроили сокет статистики в нашей папке приложения. Чтобы просмотреть статистику, выполните следующую команду в этой папке:uwsgitop stats.production_ml.sock
Вот пример worker'ов в действии, с дополнительными worker'ами, которые уже были созданы. Чтобы имитировать такую большую нагрузку на вашем сервере, вы можете добавить time.sleep(3) в код прогнозирования.
Один из способов отправки параллельных запросов на ваш сервер - использовать curl (не забудьте заменить YOUR_SERVER_NAME_OR_IP на URL вашего сервера или IP-адрес.
#!/usr/bin/env bash url="http://YOUR_SERVER_NAME_OR_IP:4444/predict?text=this%20is%20a%20news%20sample%20text%20about%20sports,%20and%20football%20in%20specific" # add more URLs here for i in {0..10} do # run the curl job in the background so we can start another job # and disable the progress bar (-s) echo "fetching $url" curl $url -s & done wait #wait for all background jobs to terminate
Чтобы отслеживать журнал самого приложения, мы можем использовать journalctl:
sudo journalctl -u production_ml.service -f
Ваш вывод должен выглядеть следующим образом:
Финальные замечания
Если вы достигли этого этапа и ваша приложение успешно запущено, эта статья достигла свою цель. Некоторые дополнения заслуживают упоминания на данном этапе:- Чтобы эта статья была достаточно общей, мы использовали режим lazy-apps в uwsgi, который загружает приложение один раз для каждого worker. Согласно документации, для загрузки этого потребуется время O(n) (где n - количество workers). Это также, возможно, потребует большего объема памяти, но приводет к чистому окружению для каждого worker'а. По умолчанию uWSGI загружает все приложение по-разному. Он начинается с одного процесса; то он несколько раз разворачивается для дополнительных работников. Это приводит к увеличению экономии памяти. Однако это не очень хорошо работает со всеми структурами ML. Например, бэкэнд TensorFlow в Keras не работает без режима ленивых приложений (например, проверьте это, это и это). Лучше всего было бы попробовать сначала без lazy-apps = true и перейти на него, если вы столкнетесь с подобными проблемами.
- Параметры приложения Flask: поскольку uWSGI вызывает app как исполняемое, параметры самого приложения не должны передаваться через командную строку. Вам лучше использовать файл конфигурации с подобными configparser для чтения таких параметров.
- Масштабирование на нескольких серверах. В приведенном выше руководстве не обсуждается случай с несколькими серверами. К счастью, это может быть достигнуто без значительных изменений в нашей настройке. Используя функцию балансировки нагрузки в nginx, вы можете настроить несколько машин, каждый с настройкой uWSGI, описанной выше. Затем вы можете настроить nginx для маршрутизации запросов на разные серверы. nginx поставляется с несколькими методами для распределения нагрузки, начиная от простого циклического масштабирования и заканчивая учетом количества подключений или средней задержки.
- Выбор порта: в приведенном выше руководстве используется порт 4444 для иллюстрации. Вы можете изменить этот порт для ваших условий. Убедитесь, что вы открыли эти порты в брандмауэре или попросите администраторов вашего учреждения сделать это.
- Привилегии сокета: мы предоставляем доступ для всех пользователей. Не стесняйтесь также настраивать привелегии для своих целей и запускать сервер с другими группами привилегий. Но убедитесь, что ваши nginx и uWSGI могут по-прежнему успешно контактировать друг с другом после ваших изменений.
Ссылки на использованные материалы
- https://hackernoon.com/a-guide-to-scaling-machine-learning-models-in-production-aa8831163846
Комментарии
Отправить комментарий