Программа магистратуры в области электротехники и вычислительной техники
Факультет вычислительной техники и автоматизации
EEC1509 — Машинное обучение
Этот репозиторий представляет собой набор тестов для навыков, полученных на уроке Основы машинного обучения и деревья решений, для развертывания модели классификации в общедоступном наборе данных MNIST.
Основная цель данной работы — развернуть модель с помощью модуля FastAPI, создать API и тесты. Тесты API будут включены в структуру CI/CD с использованием GitHub Actions. Живой API будет развернут с помощью Heroku. Веса и смещения будут использоваться для управления и отслеживания всех артефактов.
Настройка среды
Все необходимые модули можно найти в файле requirements.txt
и установить, выполнив:
pip install -r requirements.txt
Набор данных
База данных рукописных цифр MNIST имеет обучающий набор из 60 000 примеров и тестовый набор из 10 000 примеров. Цифры были нормализованы по размеру и центрированы на изображении фиксированного размера. Это хорошая база данных для людей, которые хотят попробовать методы обучения и методы распознавания образов на реальных данных, затрачивая минимум усилий на предварительную обработку и форматирование.
Разработка модели
Самые сложные части были сделаны за нас благодаря библиотеке Keras и Национальному институту стандартов и технологий (NIST of MNIST). Информация собрана и готова к обработке.
from tensorflow.keras.datasets import mnist (x_train, y_train), (x_test, y_test) = mnist.load_data()
Разработанная здесь модель не принимает матрицы 28x28, предоставляемые модулем Keras, поэтому требуется выравнивание данных. Этот процесс не идеален, поскольку он может скрыть информацию о соседних пикселях, но его достаточно для построения базовой модели.
Затем полученные данные будут сглажены для получения нескольких векторов длиной 784 (28 * 28):
def reshape(array: np.array) -> np.array: """The samples in the input array are faltered.""" samples, w, h = array.shape return array.reshape((samples, w * h)) x_train = reshape(x_train) x_test = reshape(x_test)
Примеры распределений в исследованных наборах изображены ниже:
Модель классификации, в данном случае Дерево решений, затем может быть применена к данным следующим образом:
clf = DecisionTreeClassifier(max_depth=10, random_state=42) clf.fit(x_train, y_train)
Чтобы следить за эффективностью экспериментов по машинному обучению, проект пометил определенные выходные данные этапа конвейера данных как метрики. Принятые здесь метрики: точность, f1, точность, полнота. Результаты для обученной модели показаны ниже.
| | Precision | Recall | F1-Score | Samples | | ------------ | --------- | ------ | -------- | ------- | | 0 | 0.91 | 0.94 | 0.92 | 980 | | 1 | 0.95 | 0.96 | 0.95 | 1135 | | 2 | 0.85 | 0.84 | 0.84 | 1032 | | 3 | 0.82 | 0.84 | 0.83 | 1010 | | 4 | 0.86 | 0.85 | 0.86 | 982 | | 5 | 0.84 | 0.80 | 0.82 | 892 | | 6 | 0.91 | 0.87 | 0.89 | 958 | | 7 | 0.90 | 0.88 | 0.89 | 1028 | | 8 | 0.80 | 0.81 | 0.80 | 974 | | 9 | 0.81 | 0.86 | 0.83 | 1009 | | accuracy | | | 0.87 | 10000 | | macro avg | 0.87 | 0.86 | 0.86 | 10000 | | weighted avg | 0.87 | 0.87 | 0.87 | 10000 |
Матрица путаницы классификаций модели показана ниже.
Эта базовая модель может быть улучшена в будущем, а модель логического вывода сохранена на платформе Weights & Biases для замены в реальном времени в окончательном приложении.
Сохранение модели в Weights & Biases
Используя следующий код, этот тренировочный прогон можно сохранить на платформе Weights & Biases для отслеживания эксперимента:
run = wandb.init(project='proj_mnist', job_type='train') print('Evaluating Model...') fbeta = fbeta_score(y_test, y_pred, beta=1, zero_division=1, average='weighted') precision = precision_score(y_test, y_pred, zero_division=1, average='weighted') recall = recall_score(y_test, y_pred, zero_division=1, average='weighted') acc = accuracy_score(y_test, y_pred) print(f'Accuracy: {acc}') print(f'Precision: {precision}') print(f'Recall: {recall}') print(f'F1: {fbeta}') run.summary['Acc'] = acc run.summary['Precision'] = precision run.summary['Recall'] = recall run.summary['F1'] = fbeta print('Uploading confusion matrix...') run.log({ 'confusion_matrix': wandb.Image(fig) }) wandb.sklearn.plot_classifier( clf, x_train, x_test, y_train, y_test, y_pred, clf.predict_proba(x_test), np.unique(y_train), model_name='Baseline_model' ) wandb.sklearn.plot_summary_metrics( clf, x_train, y_train, x_test, y_test ) # ROC curve predict_proba = clf.predict_proba(x_test) wandb.sklearn.plot_roc(y_test, predict_proba, np.unique(y_train)) run.finish()
Затем модель логического вывода можно сохранить на платформе и позже получить через API.
run = wandb.init(project='proj_mnist', job_type='model') artifact_model = 'model_export' print('Dumping model to disk...') joblib.dump(clf, artifact_model) artifact = wandb.Artifact( artifact_model, type='inference_artifact', description='Model for inference' ) print('Logging model artifact...') artifact.add_file(artifact_model) run.log_artifact(artifact) run.finish()
Введение в FastAPI
FastAPI – это современная платформа API, возможности которой в значительной степени зависят от подсказок типов.
Как следует из названия, FastAPI предназначен для быстрого выполнения и разработки. Он создан для максимальной гибкости, поскольку является исключительно API. Вы не привязаны к конкретным бэкендам, внешним интерфейсам и т. д. Таким образом обеспечивается возможность компоновки с вашими любимыми пакетами и/или существующей инфраструктурой.
Начать работу так же просто, как написать файл main.py, содержащий:
from fastapi import FastAPI # Instantiate the app. app = FastAPI() # Define a GET on the specified endpoint. @app.get('/') async def say_hello(): return {'greeting': 'Hello World!'}
Для запуска приложения можно использовать uvicorn в оболочке: uvicorn source.main:app --reload
.
Uvicorn — это реализация веб-сервера ASGI (интерфейс асинхронного шлюза сервера) для Python.
По умолчанию наше приложение будет доступно локально по адресу http://127.0.0.1:8000
. --reload
позволяет вам вносить изменения в свой код и мгновенно развертывать их без перезапуска uvicorn. Для дальнейшего чтения отлично написана Документация по FastAPI, ознакомьтесь с ней!
Когда пользователь делает запрос на получение в корневом маршруте для этого проекта, будет отображаться статическая страница. FastAPI уже поддерживает этот вариант использования, и пример кода можно изменить, чтобы он соответствовал следующим требованиям:
from fastapi.staticfiles import StaticFiles from fastapi import FastAPI # Instantiate the app. app = FastAPI() # Servers a static page for requests to the root endpoint. app.mount('/', StaticFiles(directory='./source/static', html = True), name='static')
Домашняя страница приложения показана ниже.
С помощью декоратора app.post
в API можно включить новый маршрут POST, который будет получать захват изображения числа, нарисованного пользователем на холсте, и возвращать вероятности классификации для каждого класса.
Платформа Weights & Biases будет использоваться для получения модели классификации. Это может быть конкретная версия самой последней версии, обеспечивающая постоянную интеграцию любых изменений в модель в приложение.
artifact_model_name = 'proj_mnist/model_export:latest' run = wandb.init(project='proj_mnist', job_type='api') # Instantiate the app. app = FastAPI() # Define a GET on the specified endpoint. @app.post('/predict/') async def predict(drawing_data: str) -> dict: """ Receive the base64 encoding of an image containing a handwritten digit and make predictions using an ml model. Args: drawing_data (str): Base64 encoding of an image. Returns: dict: JSON response containing the predictions """ # Convert data in url to numpy array img_str = re.search(r'base64,(.*)', drawing_data.replace(' ', '+')).group(1) img_bytes = io.BytesIO(base64.b64decode(img_str)) img = Image.open(img_bytes) # Normalize pixel values input = np.array(img)[:, :, 0:1].reshape((1, 28*28)) / 255.0 model_export_path = run.use_artifact(artifact_model_name).file() clf = joblib.load(model_export_path) predictions = clf.predict_proba(input)[0] return { 'result': 1, 'error': '', 'data': list(predictions) } app.mount('/', StaticFiles(directory='./source/static', html = True), name='static')
С ответом прогнозируемого маршрута можно отобразить график, отображающий вероятности для каждого прогноза.
Развертывание в Heroku
Чтобы развернуть разработанный код на Heroku, выполните следующие действия.
- Зарегистрируйтесь бесплатно и испытайте Heroku.
- Теперь пришло время создать новое приложение. Очень важно подключить приложение к нашему репозиторию Github и включить автоматическое развертывание.
- Установите Heroku CLI, следуя инструкциям.
- Войдите в героку с помощью терминала
heroku login
5. В корневой папке проекта проверьте уже созданные проекты heroku.
heroku apps
6. Проверьте правильность сборки:
heroku buildpacks --app proj-mnist
7. При необходимости обновите билдпак:
heroku buildpacks:set heroku/python --app proj-mnist
8. Когда вы запускаете сценарий в автоматизированной среде, вы можете управлять Wandb с помощью переменных среды, установленных до запуска сценария или внутри него. Настройте доступ к Wandb на Heroku, если используете интерфейс командной строки:
heroku config:set WANDB_API_KEY=xxx --app proj-mnist
9. Инструкции по запуску приложения содержатся в файле Procfile, который находится на самом высоком уровне каталога вашего проекта. Создайте файл Procfile с помощью:
web: uvicorn source.api.main:app --host=0.0.0.0 --port=${PORT:-5000}
10. Настройте удаленный репозиторий для Heroku:
heroku git:remote --app proj-mnist
11. Переместите все файлы в удаленный репозиторий в Heroku. Приведенная ниже команда установит все пакеты, указанные в файле requirements.txt, на виртуальную машину Heroku.
git push heroku main
12. Проверьте запуск удаленных файлов:
heroku run bash --app proj-mnist
13. Если все предыдущие шаги были выполнены успешно, после открытия вы увидите сообщение ниже: https://proj-mnist.herokuapp.com/
.
14. В целях отладки всякий раз, когда вы можете получить самые последние журналы вашего приложения, используйте команду heroku logs:
heroku logs
Первоначально опубликовано на https://github.com/xarmison/proj_mnist