Начало работы с деплоями

В этом документе описаны условия и алгоритм работы с деплоями на платформе ML Space и приведен пример развертывания модели для распознавания кошек и собак на изображениях.

Предварительные условия

Для развертывания модели в модуле Deployments необходимо:

Работа с модулем Deployments

Работа с модулем предполагает следующую последовательность действий:

  1. Сборка Docker-образа с моделью на основе базового или кастомного образа.

  2. Развертывание образа с моделью на указанной конфигурации.

  3. Отправка HTTP-запросов к модели вручную или от автоматизированных систем.

Пример развертывания модели

Рассмотрим пример развертывания модели по описанному выше алгоритму.

Предположим, у пользователя есть модель, обученная распознавать кошек и собак на изображениях.

Код обучения модели

from matplotlib import pyplot
from matplotlib.image import imread
from os import listdir
from numpy import asarray
from numpy import save
import tensorflow.keras as keras
from tensorflow.keras.preprocessing.image import load_img
from tensorflow.keras.preprocessing.image import img_to_array
from os import makedirs
from shutil import copyfile
from random import seed
from random import random

# Plot dog photos from the dogs vs cats dataset
# Define location of dataset
folder = 'train/'
# plot first few images
for i in range(9):
    # Define subplot
    pyplot.subplot(330 + 1 + i)
    # Define filename
    filename = folder + 'dog.' + str(i) + '.jpg'
    # Load image pixels
    image = imread(filename)
    # Plot raw pixel data
    pyplot.imshow(image)
# Show the figure
pyplot.show()

# Plot cat photos from the dogs vs cats dataset
# Define location of dataset
folder = 'train/'
# Plot first few images
for i in range(9):
    # Define subplot
    pyplot.subplot(330 + 1 + i)
    # Define filename
    filename = folder + 'cat.' + str(i) + '.jpg'
    # Load image pixels
    image = imread(filename)
    # Plot raw pixel data
    pyplot.imshow(image)
# Show the figure
pyplot.show()

# Load dogs vs cats dataset, reshape and save to a new file
# Define location of dataset
folder = 'train/'
photos, labels = list(), list()
# Enumerate files in the directory
for file in listdir(folder):
# Determine class
    output = 0.0
    if file.startswith('cat'):
        output = 1.0
    # Load image
    photo = load_img(folder + file, target_size=(200, 200))
    # Convert to numpy array
    photo = img_to_array(photo)
    # Store
    photos.append(photo)
    labels.append(output)
# Convert to a numpy arrays
photos = asarray(photos)
labels = asarray(labels)
print(photos.shape, labels.shape)
# Save the reshaped photos
save('dogs_vs_cats_photos.npy', photos)
save('dogs_vs_cats_labels.npy', labels)

# Organize dataset into a useful structure
# Create directories
dataset_home = 'dataset_dogs_vs_cats/'
subdirs = ['train/', 'test/']
for subdir in subdirs:
    # Create label subdirectories
    labeldirs = ['dogs/', 'cats/']
    for labldir in labeldirs:
        newdir = dataset_home + subdir + labldir
        makedirs(newdir, exist_ok=True)
# Seed random number generator
seed(1)
# Define ratio of pictures to use for validation
val_ratio = 0.25
# Copy training dataset images into subdirectories
src_directory = 'train/'
for file in listdir(src_directory):
    src = src_directory + '/' + file
    dst_dir = 'train/'
    if random() < val_ratio:
        dst_dir = 'test/'
    if file.startswith('cat'):
        dst = dataset_home + dst_dir + 'cats/' + file
        copyfile(src, dst)
    elif file.startswith('dog'):
        dst = dataset_home + dst_dir + 'dogs/' + file
        copyfile(src, dst)

# Organize dataset into a useful structure
from os import makedirs
from os import listdir
from shutil import copyfile
# Create directories
dataset_home = 'finalize_dogs_vs_cats/'
# Create label subdirectories
labeldirs = ['dogs/', 'cats/']
for labldir in labeldirs:
    newdir = dataset_home + labldir
    makedirs(newdir, exist_ok=True)

# Copy training dataset images into subdirectories
src_directory = 'dataset_dogs_vs_cats/train'
for file2 in listdir(src_directory):
    ext_path = src_directory + '/' + file2
    for file in listdir(ext_path):
        src = ext_path + '/' + file
        if file.startswith('cat'):
            dst = dataset_home + 'cats/' + file
            # print(dst)
            copyfile(src, dst)
        elif file.startswith('dog'):
            dst = dataset_home + 'dogs/' + file
            # print(dst)
            copyfile(src, dst)

# Save the final model to file
import tensorflow.keras as keras
from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Flatten
from tensorflow.keras.optimizers import SGD
from tensorflow.keras.preprocessing.image import ImageDataGenerator

# Define cnn model
def define_model():
    # Load model
    model = VGG16(include_top=False, input_shape=(224, 224, 3))
    # Mark loaded layers as not trainable
    for layer in model.layers:
        layer.trainable = False
        # Add new classifier layers
        flat1 = Flatten()(model.layers[-1].output)
        class1 = Dense(128, activation='relu', kernel_initializer='he_uniform')(flat1)
        output = Dense(1, activation='sigmoid')(class1)
        # Define new model
        model = Model(inputs=model.inputs, outputs=output)
        # Compile model
        opt = SGD(lr=0.001, momentum=0.9)
        model.compile(optimizer=opt, loss='binary_crossentropy', metrics=['accuracy'])
    return model

# Run the test harness for evaluating a model
def run_test_harness():
# Define model
    model = define_model()
    # Create data generator
    datagen = ImageDataGenerator(featurewise_center=True)
    # Specify imagenet mean values for centering
    datagen.mean = [123.68, 116.779, 103.939]
    # Prepare iterator
    train_it = datagen.flow_from_directory('finalize_dogs_vs_cats/',
        class_mode='binary', batch_size=64, target_size=(224, 224))
    # Fit model
    # model = keras.utils.multi_gpu_model(model, gpus=8)
    model.fit_generator(train_it, steps_per_epoch=len(train_it), epochs=10, verbose=1)

Также у пользователя есть serving-скрипт, который описывает взаимодействие с моделью.

Код serving-скрипта

import kfserving
import boto3
from typing import List, Dict
import re
import os
import tensorflow as tf
from tensorflow.keras.preprocessing.image import load_img
from tensorflow.keras.preprocessing.image import img_to_array
from tensorflow.keras.models import load_model
import numpy as np
import json

class KFServingExplainModel(kfserving.KFModel):
    def __init__(self, name: str):
        super().__init__(name)
        self.name = name
        self.ready = False
        self.model = None
        self.gpu = True

    def load(self):
        self.model = load_model('final_model.h5')
        self.ready = True

    def predict(self, request: Dict) -> Dict:
        image_link = request['instances'][0]['image_link']
        img = "123.jpg"

        session = boto3.session.Session()
        s3_client = session.client(
        service_name='s3',
        aws_access_key_id='<INPUT S3 ACCESS KEY HERE>',
        aws_secret_access_key='<INPUT S3 SECRET KEY HERE>',
        endpoint_url='<INPUT S3 CREDENTIALS HERE>',
        region_name='ru-1a'
        )
        s3_client.download_file('<INPUT S3 BUCKETNAME CREDENTIALS HERE>', image_link, img)

        img = load_img(img, target_size=(224, 224))
        img = img_to_array(img)
        img = img.reshape(1, 224, 224, 3)
        img = img.astype('float32')
        img = img - [123.68, 116.779, 103.939]

        result = self.model.predict(img)
        result = result[0][0]

        if(result >= 0.5):
        result = 'dog'
        elif(result < 0.5):
            result = 'cat'

        # result = json.dumps(str(result))
        # print(type(result))
        return {"predictions": result}


if __name__ == "__main__":
    x = re.compile('(kfserving-\d+)').search(os.environ.get('HOSTNAME'))
    name = "kfserving-default"
    if x:
        name = x[0]

    model = KFServingExplainModel(name)
    model.load()
    kfserving.KFServer(workers=1).start([model])

Задача состоит в том, чтобы развернуть эту модель и впоследствии передавать ей новые данные в виде HTTP-запросов.

Для решения поставленной задачи необходимо:

  1. Сериализовать модель, например, в формат h5:

    model.save('final_model.h5')
    
  2. Загрузить модель и serving-скрипт в бакет пользователя на S3.

  3. Собрать Docker-образ c моделью на основе кастомного образа.

  4. Развернуть образ в модуле. Обратите внимание на возможность динамически изменять конфигурацию деплоя.

После развертывания образа можно передавать модели новые данные в виде HTTP-запросов. Подробнее см. Отправить синхронный HTTP-запрос к развернутой модели.

Запустили Evolution free tier
для Dev & Test
Получить