Включение CI/CD в конвейер машинного обучения дает много преимуществ, например автоматизация развертывания моделей машинного обучения в производстве в любом масштабе.

Цель этой статьи — проиллюстрировать, как интегрировать обучение модели AWS Sagemaker и ее развертывание в конвейеры CircleCI CI/CD. Структура этого проекта представляет собой монорепозиторий, содержащий несколько моделей. Подход с одним репозиторием может иметь преимущества перед подходом с полирепозиторием, включая упрощенное управление версиями зависимостей и управление уязвимостями безопасности.

Вы можете найти код этого руководства в этом репозитории Github.

Что такое CI/CD?

CI/CD означает непрерывную интеграцию/непрерывную доставку. Его цель — максимально повысить эффективность разработки за счет автоматизации процесса доставки кода от фиксации до производства. Применительно к конвейеру машинного обучения он помогает специалистам по данным сосредоточить свое время на работе с данными и построении моделей, а не на внедрении моделей в производственную инфраструктуру и развертывание. Кроме того, ценность CI/CD становится более очевидной по мере увеличения сложности системы. Команда с ограниченными ресурсами, управляющая несколькими моделями, которые обслуживаются в различных частях организации, может сэкономить много времени благодаря автоматизации, присущей CI/CD.

Переменные среды

Первый шаг — настроить учетные данные AWS в своем проекте на CircleCI. Вы можете сделать это, перейдя в настройки вашего проекта, щелкнув «Переменные среды», затем нажав кнопку «Добавить переменную», чтобы ввести имя и значение новой переменной среды. После установки вы можете выбрать эти переменные среды в своем скрипте Python, используя os.environ.

В этом примере проекта мы, в частности, сохранили ключи доступа AWS и исполнительную роль Sagemaker ARN в переменных среды. Следует отметить, что boto3 автоматически извлекает переменные среды с именами AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY при создании сеанса boto3, поэтому нам не следует переименовывать их во что-либо другое.

Кроме того, мы храним переменные среды, относящиеся к заданию CI/CD, объявляя их с помощью ключа environment в файле конфигурации. В нашем случае это не является строго необходимым, но мы делаем это для демонстрации.

environment:
  MODEL_NAME: abalone-model
  MODEL_DESC: abalone model description text

В наших сценариях Python мы извлекаем эти переменные среды следующим образом:

model_name = os.environ["MODEL_NAME"]
model_description = os.environ["MODEL_DESC"]
role_arn = os.environ["SAGEMAKER_EXECUTION_ROLE_ARN"]

Модели

Для демонстрации мы взяли две модели, часто встречающиеся в документации AWS, Abalone и Churn. Обе они представляют собой простые модели XGBoost, где Abalone — линейный регрессор, а Churn — двоичный классификатор. Каждая модель содержится в отдельной папке, и каждая папка содержит следующие файлы:

collect_data.py

Этот файл загружает и предварительно обрабатывает данные для своей модели, а затем загружает данные в S3. Мы загружаем наборы данных для обучения и проверки в отдельные папки, как того требует Sagemaker.

# Upload training and validation data to S3
csv_buffer = io.BytesIO()
train_data.to_csv(csv_buffer, index=False)
s3_client.put_object(Bucket=bucket, Body=csv_buffer.getvalue(), Key=f"{model_name}/train/train.csv")

csv_buffer = io.BytesIO()
validation_data.to_csv(csv_buffer, index=False)
s3_client.put_object(Bucket=bucket, Body=csv_buffer.getvalue(), Key=f"{model_name}/validation/validation.csv")

train_register.py

Этот файл обучает нашу модель, затем регистрирует ее в реестре моделей и является первым местом в нашем CI/CD, где мы фактически используем Sagemaker.

При настройке Sagemaker XGBoost Estimator мы можем указать путь S3 для вывода артефактов модели, используя output_path. Мы должны предоставить ему сеанс Sagemaker и ARN роли выполнения Sagemaker, так как мы выполняем этот код вне блокнотов Sagemaker, и поэтому он не будет автоматически правильно извлекать эту информацию.

# Configure training estimator
xgb_estimator = Estimator(
    base_job_name = model_name,
    image_uri = image_uri,
    instance_type = "ml.m5.large",
    instance_count = 1,
    output_path = model_location,
    sagemaker_session = sagemaker_session,
    role = role_arn,
    hyperparameters = {
        "objective": "reg:linear",
        "max_depth": 5,
        "eta": 0.2,
        "gamma": 4,
        "min_child_weight": 6,
        "subsample": 0.7,
        "verbosity": 2,
        "num_round": 50,
    }
)

После обучения модели мы отправляем пакет модели в реестр моделей Sagemaker. Sagemaker проводит различие между моделью и модельным пакетом. Модель — это просто объект, который мы развернули бы на конечной точке и выполнили вывод, тогда как пакет модели содержит все артефакты, связанные с этой моделью, такие как веса модели, результаты оценки и файлы конфигурации. Мы отправляем пакеты моделей в реестр моделей, а не модели.

Мы хотим использовать реестр моделей, чтобы мы могли легко ссылаться на обученные модели на последующих этапах, таких как развертывание, или когда мы хотим откатить модели до предыдущих версий. Обратите внимание, что мы предварительно утверждаем пакет модели, поскольку мы будем использовать задания утверждения CircleCI для управления утверждением модели.

# Retrieve model artifacts from training job
model_artifacts = xgb_estimator.model_data

# Create pre-approved cross-account model package
create_model_package_input_dict = {
    "ModelPackageGroupName": model_name,
    "ModelPackageDescription": "",
    "ModelApprovalStatus": "Approved",
    "InferenceSpecification": {
        "Containers": [
            {
                "Image": image_uri,
                "ModelDataUrl": model_artifacts
            }
        ],
        "SupportedContentTypes": [ "text/csv" ],
        "SupportedResponseMIMETypes": [ "text/csv" ]
    }
}

create_model_package_response = sagemaker_client.create_model_package(**create_model_package_input_dict)

развернуть.py

Этот файл развертывает последний утвержденный пакет модели в конечной точке модели либо путем создания конечной точки, если она еще не существует, либо путем обновления существующей конечной точки.

Чтобы получить последний утвержденный пакет модели, мы используем функцию Sagemaker для перечисления существующих пакетов моделей по убыванию времени создания и получения пакета модели ARN:

# Get the latest approved model package of the model group in question
model_package_arn = sagemaker_client.list_model_packages(
    ModelPackageGroupName = model_name,
    ModelApprovalStatus = "Approved",
    SortBy = "CreationTime",
    SortOrder = "Descending"
)['ModelPackageSummaryList'][0]['ModelPackageArn']

Затем мы создаем модель из пакета модели:

# Create the model
timed_model_name = f"{model_name}-{current_time}"
container_list = [{"ModelPackageName": model_package_arn}]

create_model_response = sagemaker_client.create_model(
    ModelName = timed_model_name,
    ExecutionRoleArn = role_arn,
    Containers = container_list
)

И создайте конфигурацию конечной точки, используя эту модель:

# Create endpoint config
create_endpoint_config_response = sagemaker_client.create_endpoint_config(
    EndpointConfigName = timed_model_name,
    ProductionVariants = [
        {
            "InstanceType": endpoint_instance_type,
            "InitialVariantWeight": 1,
            "InitialInstanceCount": endpoint_instance_count,
            "ModelName": timed_model_name,
            "VariantName": "AllTraffic",
        }
    ]
)

Наконец, мы обновляем конечную точку с новой конфигурацией:

create_update_endpoint_response = sagemaker_client.update_endpoint(
    EndpointName = model_name,
    EndpointConfigName = timed_model_name
)

Динамическая конфигурация

Поскольку мы выбрали монорепозиторий, нам нужен способ запустить CI/CD только для той модели, которая была изменена. В противном случае, когда мы объединим изменения в модели Abalone, модель Churn также будет переобучена и повторно развернута! Именно здесь пригодятся динамические конфигурации CircleCI. Эта функция позволяет нам определить, были ли внесены изменения в конкретную папку, и, если да, установить значение параметра конвейера. В свою очередь, параметр конвейера будет определять, какие рабочие процессы будут выполняться в нашем конвейере CI/CD.

Настройка конфигурации

Первым шагом в использовании динамических конфигураций является настройка конфигурации. В нашем примере репозитория он называется config.yml. Мы используем сферу path-filtering, чтобы определить, какие папки содержат изменения кода.

Обратите внимание, что мы сравниваем файлы с файлами в основной ветке. Кроме того, мы сопоставляем изменения в определенных папках со значениями параметров. Например, если в папке abalone_model обнаружены изменения, то для параметра пайплайна deploy-abalone будет установлено значение true. Кроме того, мы указываем путь к файлу конфигурации, который будет запускаться после завершения фильтрации путей и обновления значений параметров конвейера.

base-revision: main
mapping: |
  abalone_model/.* deploy-abalone true
  churn_model/.* deploy-churn true
config-path: ".circleci/dynamic_config.yml"

Продолжить настройку

После обновления значений параметров конвейера из конфигурации установки мы теперь запускаем конфигурацию продолжения, которая в нашем примере репозитория называется dynamic_config.yml. Чтобы было проще понять, что делает файл конфигурации, давайте сосредоточимся на рабочем процессе abalone-model.

workflows:
  abalone-model:
    when: << pipeline.parameters.deploy-abalone >>
    jobs:
      - abalone-model-train:
          filters:
            branches:
              ignore:
                - main
      - request-deployment:
          type: approval
          filters:
            branches:
              ignore:
                - main
          requires:
            - abalone-model-train
      - abalone-model-deploy:
          filters:
            branches:
              only:
                - main

Во-первых, рабочий процесс будет выполняться только в том случае, если параметр конвейера deploy-abalone равен true. Далее мы запускаем задание abalone-model-train, которое выполняет файл train_register.py. Затем мы запускаем задание request-deployment, которое представляет собой задание утверждения, требующее, чтобы пользователь вручную утвердил его в CircleCI, чтобы рабочий процесс продолжился. Это будет точка, в которой рецензент проверит метрики оценки модели в Sagemaker, прежде чем разрешить развертывание модели на конечной точке. Наконец, если получено одобрение, задание abalone-model-deploy выполняет deploy.py.

Обратите внимание, что задания обучения и утверждения игнорируют основную ветвь, тогда как задание развертывания происходит только в основной ветви. Это позволяет обучать новые версии модели, когда разработчик работает над обновлениями модели в ветке разработчика, не запуская какое-либо развертывание. Затем, как только изменения кода будут приняты и объединены с основным, запускается задание развертывания, не вызывая дальнейшего переобучения модели.

Конвейеры на CircleCI

Вот что мы видим на CircleCI, когда изменения кода передаются в модель Abalone в ветке разработчика. Динамическая конфигурация выборочно запускала только обучающий конвейер морского ушка. Шлюз задания утверждения request-deployment предотвращает слияние изменений кода с основным. Как только он будет одобрен, PR на Github можно будет объединить.

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

Заключение

Мы продемонстрировали использование CircleCI вместе с AWS Sagemaker для создания сквозного конвейера машинного обучения. Он автоматизирует процесс обучения модели и развертывания ее в конечной точке для получения выводов в реальном времени.

Он использует настройку монорепозитория, где каждая модель содержится в отдельной папке. Кроме того, он использует динамические конфигурации CircleCI для адаптации каждого конвейера к модели, в которой произошли изменения кода.