В этом руководстве вы узнаете, как определить модель машинного обучения в Python, а затем развернуть ее с помощью Amazon SageMaker. Следуйте за репозиторием GitHub для получения дополнительной информации и ссылок. AWS также поддерживает обширную коллекцию примеров, которую вы можете использовать в качестве дополнительной справки.

Обзор моделей SageMaker

SageMaker использует контейнеры Docker для разделения алгоритмов машинного обучения. Этот контейнерный подход позволяет SageMaker предлагать широкий спектр легкодоступных алгоритмов для распространенных случаев использования, оставаясь при этом достаточно гибким для поддержки моделей, разработанных с использованием общих библиотек или пользовательских написанных моделей. Контейнеры модели можно использовать на трех основных уровнях:

  1. Готовые алгоритмы — фиксированный класс алгоритмов, полностью поддерживаемый AWS.
  2. «Режим сценария» — позволяет использовать популярные фреймворки машинного обучения с помощью сценария.
  3. «Режим контейнера» — позволяет использовать полностью настраиваемый алгоритм машинного обучения.

Эти режимы предлагают различные степени сложности и простоты использования.

Ниже вы найдете краткое описание каждого режима. Основное внимание в этом руководстве будет уделено пошаговому процессу использования режима контейнера для развертывания модели машинного обучения. Если вы новичок в использовании SageMaker, AWS подготовила серию глубоких видеороликов, на которые вы можете ссылаться.

В дополнение к стандартным AWS SDK у Amazon также есть пакет Python более высокого уровня, SageMaker Python SDK, для обучения и развертывания моделей с помощью SageMaker, который мы будем использовать здесь.

Предустановленные алгоритмы

SageMaker предлагает предварительно созданные алгоритмы, которые могут решать широкий спектр типов задач и вариантов использования. AWS поддерживает все контейнеры, связанные с этими алгоритмами. Вы можете найти полный список доступных алгоритмов и узнать больше о каждом из них в Документации SageMaker.

Режим сценария

Режим сценария позволяет вам писать сценарии Python для часто используемых фреймворков машинного обучения. AWS по-прежнему поддерживает базовый контейнер, на котором размещена любая выбранная вами платформа, а ваш скрипт встраивается в контейнер и используется для управления логикой во время выполнения. Чтобы сценарий был совместим с поддерживаемым AWS контейнером, он должен соответствовать определенным требованиям к дизайну.

Контейнерный режим

Режим контейнера позволяет использовать пользовательскую логику для определения модели и развертывания ее в экосистеме SageMaker; в этом режиме вы поддерживаете как контейнер, так и базовую логику, которую он реализует. Этот режим является наиболее гибким и позволяет получить доступ ко многим доступным библиотекам Python и инструментам машинного обучения. Чтобы контейнер был совместим с SageMaker, он должен соответствовать определенным требованиям к дизайну. Это может быть выполнено одним из двух способов:

  1. Определите свой собственный контейнер, расширив один из существующих, поддерживаемых AWS.
  2. Используйте Библиотеку контейнеров SageMaker, чтобы определить свой контейнер

Здесь мы сосредоточимся на использовании метода 1, но AWS действительно приложила все усилия, чтобы максимально упростить использование собственной пользовательской логики в SageMaker.

После разработки контейнера необходимо загрузить его в реестр AWS Elastic Container Registry (ECR). Это образ модели, на который вы будете указывать SageMaker при обучении или развертывании модели.

Шаги

Здесь мы опишем основные шаги, связанные с созданием и развертыванием пользовательской модели в SageMaker:

  1. Определить логику модели машинного обучения
  2. Определите изображение модели
  3. Создайте образ контейнера и отправьте его в Amazon Elastic Container Registry (ECR).
  4. Обучение и развертывание образа модели

В качестве общего обзора вся структура нашей пользовательской модели будет выглядеть примерно так:

. 
├── container 
│ ├── build_and_push.sh 
│ ├── code 
│ │ ├── model_logic.py 
│ │ └── requirements.txt 
│ ├── Dockerfile 
│ ├── gam_model 
│ │ ├── gam_model 
│ │ │ ├── __init__.py 
│ │ │ └── _models.py 
│ │ ├── gen_pack.sh 
│ │ └── setup.py

Каталог gam_model содержит основную логику пользовательской модели. Каталог code содержит код, который указывает нашему контейнеру, как использовать модель в SageMaker (обучение модели, сохранение, загрузка и вывод). Из оставшихся файлов DockerFile определяет образ докера, а build_and_push.sh — это вспомогательный bash-скрипт (который я нашел здесь) для отправки нашего контейнера в ECR, чтобы мы могли использовать его в SageMaker. Мы рассмотрим каждую часть более подробно по мере прохождения каждого шага.

Определение логики модели

Для нашей пользовательской модели машинного обучения мы будем использовать обобщенную аддитивную модель (или GAM). GAM — это мощный, но интерпретируемый алгоритм, который может обнаруживать нелинейные отношения и, возможно, взаимодействия. Если вы не знакомы с GAM, Ким Ларсон и Майкл Кларк содержат полезные сведения о нем. Также обратите внимание, что существует пакет Python, реализующий GAM с надежными функциями, pyGAM. Для наших целей мы будем использовать пакет statsmodels.

При создании контейнера с пользовательской моделью я обычно предпочитаю помещать реальную реализацию алгоритма машинного обучения в отдельный пакет Python. Это позволяет мне отделить логику модели от логики, необходимой для ее запуска в SageMaker, а также независимо изменять и тестировать каждую часть. Затем модель можно повторно использовать и в других средах.

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

В этом случае наш пакет будет выглядеть так:

. 
├── gam_model 
│ ├── __init__.py 
│ └── _models.py 
├── gen_pack.sh 
└── setup.py

Это довольно простой модуль Python, который превращает реализацию GAM statsmodel в модель, подобную scikit-learn. Содержание _models.py гласит:

import numpy as np
from sklearn.base import BaseEstimator, RegressorMixin
from sklearn.utils.validation import check_is_fitted
from sklearn.utils import check_array
from statsmodels.gam.api import GLMGam, BSplines

class GAMRegressor(BaseEstimator, RegressorMixin):
    def __init__(self, df = 15, alpha = 1.0, degree = 3):
        self.df = df
        self.alpha = alpha
        self.degree = degree
    
    def fit(self, X, y):
        X, y = self._validate_data(X, y, y_numeric=True)
        
        self.spline = BSplines(
            X, df = [self.df] * self.n_features_in_, 
            degree = [self.degree] * self.n_features_in_, 
            include_intercept = False
        )
        
        gam = GLMGam(
            y, exog = np.ones(X.shape[0]), 
            smoother = self.spline, alpha = self.alpha
        )
        self.gam_predictor = gam.fit()
        
        return self

    def predict(self, X):
        check_is_fitted(self, attributes = "gam_predictor")
        X = check_array(X)
        
        return self.gam_predictor.predict(
            exog = np.ones(X.shape[0]), 
            exog_smooth = X
        )
    
    @property
    def summary(self):
        return self.gam_predictor.summary() if \
               hasattr(self, "gam_predictor") else None

gen_pack.sh — это вспомогательный скрипт, который перестраивает и устанавливает пакет каждый раз, когда мне нужно его изменить. Остальные компоненты пакета достаточно стандартны и находятся на соответствующей странице GitHub.

Определение образа модели

Теперь, когда наша модель реализована и помещена в пакет, следующим шагом будет определение образа контейнера Docker, в котором будет храниться наша модель в экосистеме AWS. Для этого сначала напишем наш DockerFile:

ARG REGION=us-east-1 
FROM 683313688378.dkr.ecr.us-east-1.amazonaws.com/sagemaker-scikit-learn:0.23-1-cpu-py3 
ENV PATH="/opt/ml/code:${PATH}" 
COPY /code /opt/ml/code 
COPY gam_model/dist/gam_model-0.0.1-py3-none-any.whl /opt/gam_model-0.0.1-py3-none-any.whl
RUN pip install -r /opt/ml/code/requirements.txt /opt/gam_model-0.0.1-py3-none-any.whl
ENV SAGEMAKER_PROGRAM model_logic.py

Здесь мы используем один из образов контейнеров, созданных и поддерживаемых AWS для среды scikit-learn. Текущие контейнеры фреймворка можно найти на страницах документации SageMaker (здесь и здесь). Расширяя их контейнер, мы можем воспользоваться всем, что они уже сделали для его настройки, и просто беспокоиться о включении нашего дополнительного кода и функций (мы рассмотрим это позже). Затем мы копируем файл колеса пакета gam_model и устанавливаем его и другие зависимости. Наконец, мы устанавливаем файл Python, model_logic.py, в качестве точки входа для контейнера.

Поскольку мы расширяем один из контейнеров платформы AWS, нам необходимо убедиться, что инструкции для логики, которую должен выполнять контейнер, соответствуют требованиям к дизайну, изложенным в документации sagemaker-python-sdk. Подробнее об общих требованиях к контейнерам SageMaker можно прочитать в их документации, а также на странице sagemaker-containers.

В нашем случае каталог code выглядит так:

├── model_logic.py 
└── requirements.txt

requirements.txt содержит некоторые дополнительные пакеты, которые нам нужно установить в контейнере, а model_logic.py содержит инструкции о том, как мы хотим, чтобы контейнер обучал, загружал и обслуживал модель.

Учебная часть выглядит так:

import argparse
import os
import json
import pandas as pd
import numpy as np
import joblib
from gam_model import GAMRegressor
if __name__ =='__main__':
    print('initializing')
    parser = argparse.ArgumentParser()
    gam = GAMRegressor()
    gam_dict = gam.get_params()
    # Data, model, and output directories
    parser.add_argument('--output-data-dir', 
                        type = str, 
                        default = os.environ.get('SM_OUTPUT_DATA_DIR'))
    parser.add_argument('--model-dir', 
                        type = str, 
                        default = os.environ.get('SM_MODEL_DIR'))
    parser.add_argument('--train', 
                        type = str, 
                        default = os.environ.get('SM_CHANNEL_TRAIN'))
    parser.add_argument('--train-file', type = str)
    parser.add_argument('--test', 
                        type = str, 
                        default = os.environ.get('SM_CHANNEL_TEST'))
    parser.add_argument('--test-file', type = str, default = None)
    
    for argument, default_value in gam_dict.items():
        parser.add_argument(f'--{argument}', 
                            type = type(default_value),
                            default = default_value)
    print('reading arguments')
    args, _ = parser.parse_known_args()
    print(args)
    
    print('setting parameters')
    gam_dict.update({key: value for key, value in vars(args).items()\
                    if key in gam_dict and value is not None})
    gam.set_params(**gam_dict)
    
    print(gam)
    print('reading training data') 
    # assume there's no headers and the target is the last column
    data = np.loadtxt(os.path.join(args.train, args.train_file), delimiter = ',')
    X = data[:, :-1]
    y = data[:, -1]
    
    print("X shape:", X.shape)
    print("y shape:", y.shape)
    if args.test_file is not None:
        print('reading training data') 
        # assume there's no headers and the target is the last column
        data = np.loadtxt(os.path.join(args.test, args.test_file), delimiter = ',')
        X_test = data[:, :-1]
        y_test = data[:, -1]
        print("X_test shape:", X_test.shape)
        print("y_test shape:", y_test.shape)
    else:
        X_test = None
        y_test = None
    
    print('fitting model') 
    gam.fit(X, y)
    
    print("R2 (train):", gam.score(X, y))
    
    if X_test is not None:
        print("R2 (test):", gam.score(X_test, y_test))
    
    print('saving model') 
    path = os.path.join(args.model_dir, "model.joblib")
    print(f"saving to {path}")
    joblib.dump(gam, path)

Загрузка модели:

def model_fn(model_dir):
    model = joblib.load(os.path.join(model_dir, "model.joblib"))
    return model

Использование модели для прогнозирования:

def predict_fn(input_object, model):
    return model.predict(input_object)

Обратите внимание: если бы мы хотели иметь возможность использовать различные методы сериализации/десериализации с нашей моделью в SageMaker, мы могли бы также определить input_fn и output_fn, но мы будем использовать реализации по умолчанию.

Создание образа контейнера и отправка его в Amazon Elastic Container Registry (ECR)

Теперь, когда у нас есть все ингредиенты для нашего контейнера, мы можем собрать его и отправить в ECR.

./build_and_push.sh gam-model

Примечание.Я жестко запрограммировал регион как в DockerFile, так и в build_and_push.sh, чтобы извлечь его из us-east-1 (account id 683313688378). Вы можете настроить это для другого региона, обратившись к документам.

Сделав это, перейдите в консоль AWS, перейдите к ECR и запишите URI образа вашей модели.

Обучение и развертывание пользовательской модели

Теперь, когда мы определили образ нашей модели и зарегистрировали его в ECR, мы можем использовать SageMaker для обучения и развертывания нашей модели! Вы можете следить за этим процессом, ссылаясь на блокнот example.ipynb.

В этом примере мы будем использовать небольшой, относительно простой набор данных, который продемонстрирует способность GAM моделировать нелинейные отношения: набор данных Gauss3.

Сначала мы импортируем необходимые библиотеки и запускаем некоторую инициализацию:

import requests
import sagemaker
import boto3
import s3fs
import json
import io
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score
from sagemaker.estimator import Estimator
from sagemaker.predictor import Predictor
from sagemaker.serializers import NumpySerializer
from sagemaker.deserializers import NumpyDeserializer
from sagemaker.local import LocalSession
from matplotlib import pyplot as plt
import matplotlib as mpl
import seaborn as sns
%matplotlib inline
sns.set()
seed = 42
rand = np.random.RandomState(seed)
local_mode = False # activate to use local mode
with open("config.json") as f:
    configs = json.load(f)
    
default_bucket = configs["default_bucket"] #put your bucket name here
role = configs["role_arn"] # put your sagemaker role arn here
boto_session = boto3.Session()
   
if local_mode:
    sagemaker_session = LocalSession(boto_session = boto_session)
    sagemaker_session._default_bucket = default_bucket
else:
    sagemaker_session = sagemaker.Session(
        boto_session = boto_session,
        default_bucket = default_bucket
    )
ecr_image = configs["image_arn"] #put the image uri from ECR here
prefix = "modeling/sagemaker"
data_name = f"gauss3"
test_name = "gam-demo"

Обратите внимание, что я использую файл конфигурации для хранения имени корзины S3, роли SageMaker и URI обучающего образа, но вы можете установить их напрямую. Далее мы определяем две вспомогательные функции. Я также включаю логику для обучения и развертывания модели локально или на экземплярах SageMaker.

def get_s3fs():
    return s3fs.S3FileSystem(key = boto_session.get_credentials().access_key,
                             secret = boto_session.get_credentials().secret_key,
                             token = boto_session.get_credentials().token)
def plot_and_clear():
    plt.show()
    plt.clf()
    plt.cla()
    plt.close()

Мы можем получить данные Гаусса с помощью модуля запросов и применить разделение поезда-теста.

url = "https://www.itl.nist.gov/div898/strd/nls/data/LINKS/DATA/Gauss3.dat"
r = requests.get(url)
y, x = np.loadtxt(
    io.StringIO(r.text[r.text.index("Data:   y          x"):]), 
    skiprows=1, unpack=True
)
x = x.reshape(-1, 1)
X_train, X_test, y_train, y_test = train_test_split(
    x, y, test_size = 0.25, 
    random_state = rand
)

После записи данных обучения в нашу корзину S3,

file_fn = f"{default_bucket}/{prefix}/{data_name}/train/data.csv"
file_path = f"s3://{file_fn}"
s3 = get_s3fs()
with s3.open(file_fn, 'wb') as f:
    np.savetxt(f, np.c_[X_train, y_train], delimiter = ',')

мы можем обучить нашу модель:

hyperparameters = {
    "train-file": "data.csv",
    "df": "20"
}
data_channels = {
    "train": file_path
}
estimator = Estimator(
    role = role,
    sagemaker_session = sagemaker_session,
    instance_count = 1,
    instance_type = "local" if local_mode else "ml.m5.large",
    image_uri = ecr_image,
    base_job_name = f'{data_name}-{test_name}',
    hyperparameters = hyperparameters,
    output_path = f"s3://{default_bucket}/{prefix}/{data_name}/model"
)
estimator.fit(data_channels, wait = True, logs = "None")
job_name = estimator.latest_training_job.name
print(job_name)

Как только модель обучена, мы можем развернуть ее, чтобы делать выводы в реальном времени.

np_serialize = NumpySerializer()
np_deserialize = NumpyDeserializer()
predictor = estimator.deploy(
    initial_instance_count = 1,
    instance_type = "local" if local_mode else "ml.t2.medium",
    serializer = np_serialize,
    deserializer = np_deserialize
)

Теперь давайте получим прогнозы модели на данных обучения и тестирования и сравним их с фактическими данными.

y_hat_train = predictor.predict(X_train)
y_hat_test = predictor.predict(X_test)

Мы видим, что GAM нашел гладкое представление, которое фиксирует нелинейность данных.

Не забудьте удалить конечную точку модели, когда закончите тестирование модели.

predictor.delete_endpoint() predictor.delete_model()

Вывод

В этом руководстве описан процесс создания уникального образа контейнера в SageMaker и показано, как его можно использовать для обучения и развертывания пользовательской модели машинного обучения. Надеюсь, это было полезно и послужит полезным справочником.

Примечание. Если у вас возникнут проблемы во время этого процесса, обязательно проверьте группы журналов CloudWatch для экземпляров сборки, обучения и развертывания SageMaker. Они ваш лучший друг для поиска и решения проблем!

PREDICTif является избранным партнером-консультантом Amazon Web Services. Для получения дополнительной информации посетите нашу страницу партнера Amazon. Эта статья была написана Дэвидом Хреном, ведущим специалистом по данным в PREDICTif Solutions.

Первоначально опубликовано на https://www.predictifsolutions.com 4 сентября 2020 г.