Введение в развёртывание ML: Flask, Docker и Locust

Введение в развёртывание ML: Flask, Docker и Locust

Введение

Вы потратили много времени на EDA, тщательно проработали все функции, несколько дней настраивали модель и, наконец, получили то, что хорошо работает в тестовом варианте. Теперь, мой друг, вам нужно развернуть вашу модель. В конце концов, любая модель, которая остаётся на компьютере, ничего из себя не представляет, независимо от того, насколько она хороша.

Изучение этой части рабочего процесса Data Science может показаться непосильным, особенно если у вас нет большого опыта разработки программного обеспечения. Не бойтесь, основная цель этой статьи — познакомить вас с одним из самых популярных фреймворков для развёртывания на Python - Flask. Кроме того, вы узнаете, как контейнеризировать развёртывание и измерить его производительность - два аспекта, которые часто упускаются из виду.

Что такое развёртывание?

Перво-наперво давайте проясним, что я подразумеваю под развёртыванием в этой статье. Развёртывание ML - это процесс взятия обучённой модели и интеграции ёе в производственную систему (сервер на схеме ниже), что делает её доступной для использования конечными пользователями или другими системами.

Введение в развёртывание ML: Flask, Docker и Locust

Имейте в виду, что на самом деле процесс развёртывания намного сложнее, чем простое предоставление модели конечным пользователям. Он также включает интеграцию сервиса с другими системами, выбор соответствующей инфраструктуры, балансировку нагрузки и оптимизацию, а также тщательное тестирование всех этих компонентов. Большинство из этих шагов выходят за рамки данной статьи и в идеале должны выполняться опытными инженерами по программному обеспечению / ML. Тем не менее, важно иметь некоторое представление об этих областях, поэтому в этой статье будут рассмотрены контейнеризация, тестирование скорости вывода и обработка.

Установка

Весь код можно найти в этом репозитории GitHub. Я покажу фрагменты из него, но обязательно извлеките его и поэкспериментируйте с ним, ведь практика - это лучший способ научиться чему-либо. Для запуска кода вам понадобятся установленные docker, flask, fastapi и locust. Возможно, потребуется установить некоторые дополнительные зависимости (это зависит от среды, в которой вы запускаете этот код).

Обзор проекта

Чтобы сделать обучение более практичным, в этой статье будет показано простое демонстрационное развёртывание модели прогнозирования дефолта по кредиту. Процесс обучения модели выходит за рамки статьи, поэтому уже обученная модель Cat Boots доступна в репозитории GitHub. Модель была обучена на предварительно обработанном наборе данных U.S. Small Business Administration dataset (лицензия CC BY-SA 4.0). Не стесняйтесь обращаться к словарю данных, чтобы понять, что означает каждый из столбцов.

Этот проект фокусируется в основном на обслуживающей части, то есть на том, чтобы сделать модель доступной для других систем. Следовательно, модель фактически будет развёрнута на вашем локальном компьютере, что хорошо для тестирования, но неоптимально для реального мира. Вот основные шаги, которым будут следовать развёртывания для Flask и Fast API:

  • Создайте конечную точку API (используя Flask или Fast API)
  • Контейнеризируйте приложение (конечную точку) с помощью Docker
  • Запустите образ Docker локально, создав сервер
  • Проверьте производительность сервера
Введение в развёртывание ML: Flask, Docker и Locust

Что такое Flask?

Flask - популярный и широко распространённый веб-фреймворк для Python благодаря своему лёгкому характеру и минимальным требованиям к установке. Он предлагает простой подход к разработке REST API, которые идеально подходят для обслуживания моделей машинного обучения.

Типичный рабочий процесс для Flask включает в себя определение конечной точки HTTP прогнозирования и её привязку к определённым функциям Python, которые получают данные в качестве входных данных и генерируют прогнозы в качестве выходных данных. Затем пользователи и другие приложения могут получить доступ к этой конечной точке.

Создание Flask-приложения

Если вы заинтересованы в простом создании конечной точки прогнозирования, это будет довольно просто. Всё, что вам нужно сделать, это десериализовать модель, создать объект приложения Flask и указать конечную точку прогнозирования с помощью метода POST. Более подробную информацию о POST и других методах вы можете найти здесь.

import catboost as cb import pandas as pd from flask import Flask, jsonify, request # Load the model model = cb.CatBoostClassifier() model.load_model("loan_catboost_model.cbm") # Init the app app = Flask("default") # Setup prediction endpoint @app.route("/predict", methods=["POST"]) def predict(): # Get the provided JSON X = request.get_json() # Perform a prediction preds = model.predict_proba(pd.DataFrame(X, index=[0]))[0, 1] # Output json with prediction result = {"default_proba": preds} return jsonify(result) if __name__ == "__main__": # Run the app on local host and port 8989 app.run(debug=True, host="0.0.0.0", port=8989)

Наиболее важной частью приведённого выше кода является функция predict . Она считывает входные данные json, которые в данном случае представляют собой набор атрибутов, описывающих заявку на получение кредита. Затем берёт эти данные, преобразует их в DataFrame и передаёт их через модель. Результирующая вероятность значения по умолчанию затем форматируется обратно в json и возвращается. Когда это приложение развёрнуто локально, мы можем получить прогноз, отправив запрос с данными в формате json на http://0.0.0.0:8989/predict url-адрес. Давайте попробуем это! Чтобы запустить сервер, мы можем просто открыть файл Python с помощью приведённой ниже команды:

python app.py
Введение в развёртывание ML: Flask, Docker и Locust

При выполнении этой команды вы должны получить сообщение о том, что ваше приложение запущено по адресу http://0.0.0.0:8989/. А пока давайте проигнорируем большое красное предупреждение и протестируем приложение. Чтобы проверить, работает ли приложение должным образом, мы можем отправить тестовый запрос (данные заявки на получение кредита) в приложение и посмотреть, получим ли мы ответ (прогноз вероятности по умолчанию).

# Example loan application application = { "Term": 84, "NoEmp": 5, "CreateJob": 0, "RetainedJob": 5, "longitude": -77.9221, "latitude": 35.3664, "GrAppv": 1500000.0, "SBA_Appv": 1275000.0, "is_new": True, "FranchiseCode": "0", "UrbanRural": 1, "City": "Other", "State": "NC", "Bank": "BBCN BANK", "BankState": "CA", "RevLineCr": "N", "naics_first_two": "45", "same_state": False, } # Location of my server url = "http://0.0.0.0:8989/predict" # Send request resp = requests.post(url, json=application) # Print result print(resp.json())

Если вам удалось получить ответ — поздравляю! Вы развернули модель, используя свой собственный компьютер в качестве сервера. Теперь давайте сделаем шаг вперёд и упакуем ваше приложение для развёртывания с помощью Docker.

Контейнеризация приложения

Контейнеризация - это процесс инкапсуляции вашего приложения и всех его зависимостей (включая Python) в автономный изолированный пакет, который может последовательно выполняться в различных средах (например, локально, в облаке, на ноутбуке вашего друга и т.д.). Вы можете достичь этого с помощью Docker, и всё, что вам нужно сделать, это правильно указать Dockerfile, создать образ и затем запустить его. Dockerfile предоставляет инструкции вашему контейнеру, например, какую версию Python использовать, какие пакеты устанавливать и какие команды запускать.

Вот как это может выглядеть для приведённого выше приложения Flask:

# Base image is Python 3.9 FROM python:3.9-slim # Set the working directory WORKDIR /app # Copy the requirements file into the container COPY requirements.txt requirements.txt # Install the required packages RUN pip install --upgrade pip RUN pip install -r requirements.txt # Copy the model and application code into the container COPY ["loan_catboost_model.cbm", "app.py", "./"] . # Run the app using gunicorn ENTRYPOINT [ "gunicorn", "--bind=0.0.0.0:8989", "app:app" ]

Теперь мы можем создать образ с помощью команды docker build:

docker build -t default-service:v01 .

-t даёт вам возможность присвоить имя вашему образу docker и указать для него тег, поэтому имя этого образа - deafult-service с тегом v01 . Точка в конце ссылается на аргумент PATH, который необходимо указать. Это местоположение вашей модели, кода приложения и т.д. Создание этого образа может занять некоторое время, но как только это будет сделано, вы сможете увидеть его при запуске docker images.

Давайте запустим докеризованное приложение, используя следующую команду:

docker run -it --rm -p 8989:8989 default-service:v01

-it флаг заставляет изображение Docker запускаться в интерактивном режиме, что означает, что вы сможете просматривать журналы кода в командной оболочке и при необходимости останавливать изображение с помощью Ctrl +C. --rm гарантирует, что контейнер автоматически удаляется при остановке изображения. Наконец, -p делает порты из внутреннего образа Docker доступными за его пределами. Приведённая выше команда сопоставляет порт 8989 из Docker с localhost, делая нашу конечную точку доступной по тому же адресу.

Тестовое приложение Flask

Теперь, когда наша модель успешно развёрнута с использованием Flask и контейнер развёртывания запущен (по крайней мере, локально), пришло время оценить его производительность. На данный момент мы сосредоточены на таких показателях обслуживания, как время отклика и способность сервера обрабатывать запросы в секунду, а не на показателях ML, таких как RMSE или оценка F1.

Тестирование с использованием скрипта

Чтобы получить приблизительную оценку задержки ответа, мы можем создать скрипт, который отправляет несколько запросов на сервер и измеряет время, необходимое (обычно в миллисекундах) серверу для возврата прогноза. Однако важно отметить, что время отклика не является постоянным, поэтому нам нужно измерить среднюю задержку, чтобы оценить время, в течение которого пользователи обычно ожидают получения ответа, и 95-й процентиль задержки для измерения наихудших сценариев.

# Location of my server url = "http://0.0.0.0:8989/predict" # Measure the response time all_times = [] # For 1000 times for i in tqdm(range(1000)): t0 = time.time_ns() // 1_000_000 # Send a request resp = requests.post(url, json=application) t1 = time.time_ns() // 1_000_000 # Measure how much time it took to get a response in ms time_taken = t1 - t0 all_times.append(time_taken) # Print out the results print("Response time in ms:") print("Median:", np.quantile(all_times, 0.5)) print("95th precentile:", np.quantile(all_times, 0.95)) print("Max:", np.max(all_times))

Этот код находится в measure_response.py, поэтому мы можем просто запустить этот файл python для измерения этих показателей задержки:

python measure_response.py
Введение в развёртывание ML: Flask, Docker и Locust

Среднее время отклика оказалось равным 9 мс, но в наихудшем случае на этот раз оно превышает в 10 раз. Является ли эта производительность удовлетворительной или нет, зависит от вас и менеджера по продукту, но, по крайней мере, теперь вы знаете об этих показателях и можете продолжать работать над их улучшением.

Тестирование с использованием Locust

Locust - это пакет Python, предназначенный для тестирования производительности и масштабируемости веб-приложений. Мы собираемся использовать Locust для создания более продвинутого сценария тестирования, поскольку он позволяет настраивать такие параметры, как количество пользователей (т.е. заявителей на получение кредита) в секунду.

Перво-наперво, пакет можно установить, запустив pip install locust в вашем терминале. Затем нам нужно определить тестовый сценарий, который будет указывать, что наш воображаемый пользователь будет выполнять с нашим сервером. В нашем случае это довольно просто — пользователь отправит нам запрос с информацией (в формате json) о своей кредитной заявке и получит ответ от нашей развёрнутой модели.

Define test json request test_application = { "Term": 84, "NoEmp": 5, "CreateJob": 0, "RetainedJob": 5, "longitude": -77.9221, "latitude": 35.3664, "GrAppv": 1500000.0, "SBA_Appv": 1275000.0, "is_new": True, "FranchiseCode": "0", "UrbanRural": 1, "City": "Other", "State": "NC", "Bank": "BBCN BANK", "BankState": "CA", "RevLineCr": "N", "naics_first_two": "45", "same_state": False, } class BankLoan(HttpUser): # Means that a user will send 1 request per second wait_time = constant_throughput(1) # Task to be performed (send data & get response) @task def predict(self): self.client.post( "/predict", json=test_application, timeout=1, )

Как вы можете видеть, задача Locust очень похожа на предыдущий тест, который мы выполняли выше. Единственное отличие заключается в том, что он должен быть обёрнут в класс, который наследуется от locust.HttpUser и выполняемая задача (отправка данных и получение ответа) должны быть оформлены с помощью @task .

Чтобы начать нагрузочное тестирование, нам просто нужно выполнить приведённую ниже команду:

locust -f app_test.py

Когда он запустится, вы сможете получить доступ к пользовательскому интерфейсу тестирования по адресу http://0.0.0.0:8089, где вам нужно будет указать URL приложения, количество пользователей и частоту появления.

Введение в развёртывание ML: Flask, Docker и Locust

Частота появления 5 и 100 пользователей означает, что каждую секунду 5 новых пользователей будут отправлять запросы в ваше приложение, пока их число не достигнет 100. Это означает, что на пике производительности нашему приложению потребуется обрабатывать 100 запросов в секунду. Теперь давайте нажмём кнопку Start swarming и перейдем в раздел "Диаграммы" пользовательского интерфейса. Ниже я собираюсь представить результаты для моей машины, но они, безусловно, будут отличаться от ваших, поэтому обязательно запустите это и на своём компьютере.

Введение в развёртывание ML: Flask, Docker и Locust

Вы увидите, что по мере увеличения трафика время вашего отклика будет замедляться. Время от времени также будут наблюдаться пики, поэтому важно понимать, когда они происходят и почему. Самое главное, Locust помогает нам понять, что наш локальный сервер может обрабатывать 100 запросов в секунду со средним временем отклика ~ 250 мс.

Мы можем продолжать стресс-тестирование нашего приложения и определять нагрузку, с которой оно не справляется. Для этого давайте увеличим число пользователей до 1000, чтобы посмотреть, что произойдет.

Введение в развёртывание ML: Flask, Docker и Locust

Похоже, критическая точка моего локального сервера составляет ~ 180 одновременных пользователей. Это важная информация, которую мы смогли извлечь с помощью Locust.

Заключение

Я надеюсь, что эта статья предоставила вам практическое и содержательное введение в развёртывание модели. Следуя этому проекту или адаптируя его к вашей конкретной модели, теперь вы должны иметь полное представление об основных шагах, связанных с развёртыванием. В частности, вы получили знания о создании конечных точек REST API для вашей модели с помощью Flask, их контейнеризации с помощью Docker и систематическом тестировании этих конечных точек с помощью Locust.

Статья была взята из этого источника:

1
Начать дискуссию