Materiały kursów
Materiały / Azure ML (polski)
Instrukcja do tego ćwiczeniaPobierz notatnik (.ipynb)

Tworzenie usługi wnioskowania wsadowego

W poprzednim ćwiczeniu model trafił do zarządzanego punktu końcowego online, obsługującego wnioskowanie (ang. inference) w czasie rzeczywistym. Teraz utworzysz punkt końcowy wsadowy (ang. batch endpoint), który przetwarza dane partiami. Po co? Wyobraź sobie przychodnię, która przez cały dzień zbiera wyniki badań i zapisuje dane każdego pacjenta w osobnym pliku. W nocy model może przetworzyć komplet danych z całego dnia naraz, a rano na personelu czekają gotowe predykcje - i wiadomo, do których pacjentów trzeba się odezwać. Dokładnie to zbudujesz w tym ćwiczeniu.

Połączenie z obszarem roboczym

Na początek połącz się z obszarem roboczym przy użyciu Azure ML SDK v2.

Uwaga: Jeśli od poprzedniego ćwiczenia wygasła uwierzytelniona sesja z subskrypcją Azure, pojawi się prośba o ponowne zalogowanie.

In [ ]:
from azure.ai.ml import MLClient
from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()
ml_client = MLClient.from_config(credential=credential)
print(f"Azure ML gotowe do pracy z obszarem roboczym {ml_client.workspace_name}")

Trenowanie i rejestracja modelu

Wsadowy skrypt scoringowy (ang. scoring script), którego użyjesz w dalszej części ćwiczenia, wczytuje model przez joblib z pliku diabetes_model.pkl. Zarejestrowany model diabetes_model musi więc być zwykłym modelem scikit-learn zapisanym jako zasób typu CUSTOM_MODEL - a nie modelem w formacie MLflow, jaki rejestrują niektóre wcześniejsze ćwiczenia (Lab 3B, Lab 6A).

Dlatego poniższa komórka jest obowiązkowa: trenuje model i rejestruje go we właściwym formacie, niezależnie od tego, które wcześniejsze ćwiczenia zostały wykonane. Bez niej dalsze kroki wdrożenia nie zadziałają.

In [ ]:
import pandas as pd
import numpy as np
import joblib
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import roc_auc_score
from azure.ai.ml.entities import Model
from azure.ai.ml.constants import AssetTypes

# wczytaj zbiór danych o cukrzycy
print("Wczytywanie danych...")
diabetes = pd.read_csv('data/diabetes.csv')

# Rozdziel cechy i etykiety
X, y = diabetes[['Pregnancies','PlasmaGlucose','DiastolicBloodPressure','TricepsThickness','SerumInsulin','BMI','DiabetesPedigree','Age']].values, diabetes['Diabetic'].values

# Podziel dane na zbiór treningowy i testowy
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30, random_state=0)

# Wytrenuj model drzewa decyzyjnego
print('Trenowanie modelu drzewa decyzyjnego')
model = DecisionTreeClassifier().fit(X_train, y_train)

# policz skuteczność
y_hat = model.predict(X_test)
acc = np.average(y_hat == y_test)
print('Skuteczność:', acc)

# policz AUC
y_scores = model.predict_proba(X_test)
auc = roc_auc_score(y_test, y_scores[:,1])
print('AUC: ' + str(auc))

# Zapisz wytrenowany model do pliku
model_file = 'diabetes_model.pkl'
joblib.dump(value=model, filename=model_file)

# Zarejestruj model
registered_model = Model(
    path=model_file,
    type=AssetTypes.CUSTOM_MODEL,
    name="diabetes_model",
    description="A decision tree model that classifies patients by their likelihood of being diabetic.",
    tags={"Training context": "Inline Training"},
    properties={"AUC": str(auc), "Accuracy": str(acc)},
)
ml_client.models.create_or_update(registered_model)

print('Model wytrenowany i zarejestrowany.')

Przygotowanie danych wsadowych

Na potrzeby kursu nie ma prawdziwej przychodni, która dostarczyłaby nowe wyniki badań, więc wylosujesz próbkę z drugiego pliku CSV z danymi o cukrzycy i na niej przetestujesz punkt końcowy wsadowy. Tak przygotowane pliki zarejestrujesz w obszarze roboczym jako zasób danych typu uri_folder.

In [ ]:
import pandas as pd
import os
from azure.ai.ml.entities import Data
from azure.ai.ml.constants import AssetTypes

# Wczytaj dane o cukrzycy (nowi pacjenci, dla których chcemy predykcji)
diabetes = pd.read_csv('data/diabetes2.csv')
# Wylosuj 100 obserwacji, biorąc same kolumny z cechami (bez etykiety Diabetic)
sample = diabetes[['Pregnancies','PlasmaGlucose','DiastolicBloodPressure','TricepsThickness','SerumInsulin','BMI','DiabetesPedigree','Age']].sample(n=100).values

# Utwórz folder lokalny
batch_folder = './batch-data'
os.makedirs(batch_folder, exist_ok=True)
print("Folder utworzony!")

# Zapisz każdą obserwację w osobnym pliku
print("Zapisywanie plików...")
for i in range(100):
    fname = str(i+1) + '.csv'
    sample[i].tofile(os.path.join(batch_folder, fname), sep=",")
print("Pliki zapisane!")

# Zarejestruj folder z plikami jako zasób danych typu uri_folder
print("Wysyłanie plików i rejestrowanie zasobu danych...")
batch_data_set = Data(
    path=batch_folder,
    type=AssetTypes.URI_FOLDER,
    description="A folder of new patient observations to be scored in batch",
    name="diabetes_batch_data",
)
ml_client.data.create_or_update(batch_data_set)

print("Gotowe!")

Przygotowanie środowiska obliczeniowego

Wdrożenie wsadowe potrzebuje środowiska obliczeniowego, na którym wykona swoją pracę. Użyjesz klastra obliczeniowego aml-cluster z wcześniejszych ćwiczeń (jeśli jeszcze nie istnieje, zostanie utworzony).

In [ ]:
from azure.ai.ml.entities import AmlCompute

cluster_name = "aml-cluster"

# Sprawdź, czy klaster już istnieje
try:
    inference_cluster = ml_client.compute.get(cluster_name)
    print('Klaster już istnieje - używamy go.')
except Exception:
    # Utwórz klaster obliczeniowy Azure ML
    compute_config = AmlCompute(
        name=cluster_name,
        type="amlcompute",
        size="Standard_D2as_v4",
        min_instances=0,
        max_instances=3,
        idle_time_before_scale_down=300,
    )
    inference_cluster = ml_client.compute.begin_create_or_update(compute_config).result()

print(inference_cluster.name, "jest dostępny.")

Wsadowy skrypt scoringowy

Teraz możesz zdefiniować wdrożenie (ang. deployment) wsadowe. Potrzebuje ono kodu w Pythonie, który wykona właściwe przetwarzanie, więc najpierw utwórz folder na pliki wdrożenia:

In [ ]:
import os
# Utwórz folder na pliki wdrożenia
experiment_folder = 'batch_deploy'
os.makedirs(experiment_folder, exist_ok=True)

print(experiment_folder)

Teraz utwórz skrypt Pythona, który wykona właściwą pracę, i zapisz go w folderze wdrożenia:

In [ ]:
%%writefile $experiment_folder/batch_diabetes.py
import os
import numpy as np
import joblib


def init():
    # Wykonuje się raz, przy inicjalizacji wdrożenia
    global model

    # Zmienną AZUREML_MODEL_DIR ustawia wdrożenie wsadowe; wskazuje ona
    # folder z plikami zarejestrowanego modelu
    model_path = os.path.join(os.getenv("AZUREML_MODEL_DIR"), "diabetes_model.pkl")
    model = joblib.load(model_path)


def run(mini_batch):
    # Wykonuje się dla każdej porcji plików (mini-batcha)
    resultList = []

    # przetwórz każdy plik z porcji
    for f in mini_batch:
        # Wczytaj dane rozdzielone przecinkami do tablicy
        data = np.genfromtxt(f, delimiter=',')
        # Zmień kształt na tablicę dwuwymiarową - model oczekuje zbioru obserwacji, nie jednej
        prediction = model.predict(data.reshape(1, -1))
        # Dopisz predykcję do wyników
        resultList.append("{}: {}".format(os.path.basename(f), prediction[0]))
    return resultList

Kolejny krok to zdefiniowanie środowiska z pakietami, których potrzebuje skrypt.

In [ ]:
%%writefile $experiment_folder/batch_env.yml
name: batch-environment
dependencies:
  - python=3.8
  - numpy
  - pip
  - pip:
      - scikit-learn
      - joblib
In [ ]:
from azure.ai.ml.entities import Environment

batch_env = Environment(
    name="batch-environment",
    conda_file=f"{experiment_folder}/batch_env.yml",
    image="mcr.microsoft.com/azureml/openmpi5.0-ubuntu24.04",
)
print('Konfiguracja środowiska gotowa.')

Skrypt predykcji wsadowej wdrożysz jako wdrożenie wsadowe ukryte za punktem końcowym wsadowym. Wdrożenie wsadowe samo rozdziela pliki wejściowe na porcje (mini-batche) między węzły klastra, równolegle uruchamia na nich skrypt scoringowy i scala wyniki w jeden plik wyjściowy.

Potrzebne będą klasy służące do definiowania punktów końcowych i wdrożeń wsadowych.

Wszystko jest gotowe do utworzenia punktu końcowego i wdrożenia. Punkt końcowy będzie się nazywał diabetes-batch-endpoint, a wdrożenie diabetes-batch-dpl.

In [ ]:
from azure.ai.ml.entities import BatchEndpoint, ModelBatchDeployment, ModelBatchDeploymentSettings, CodeConfiguration
from azure.ai.ml.constants import BatchDeploymentOutputAction

endpoint_name = "diabetes-batch-endpoint"

# Utwórz punkt końcowy wsadowy
endpoint = BatchEndpoint(
    name=endpoint_name,
    description="A batch endpoint for scoring diabetes patient data",
)
ml_client.batch_endpoints.begin_create_or_update(endpoint).result()

# Pobierz zarejestrowany model, który chcesz wdrożyć
model = ml_client.models.get(name="diabetes_model", label="latest")

# Utwórz wdrożenie wsadowe
batch_deployment = ModelBatchDeployment(
    name="diabetes-batch-dpl",
    endpoint_name=endpoint_name,
    model=model,
    environment=batch_env,
    code_configuration=CodeConfiguration(code=experiment_folder, scoring_script="batch_diabetes.py"),
    compute=cluster_name,
    settings=ModelBatchDeploymentSettings(
        instance_count=2,
        max_concurrency_per_instance=2,
        mini_batch_size=5,
        output_action=BatchDeploymentOutputAction.APPEND_ROW,
        output_file_name="predictions.csv",
    ),
)
ml_client.batch_deployments.begin_create_or_update(batch_deployment).result()

# Ustaw to wdrożenie jako domyślne dla punktu końcowego
endpoint = ml_client.batch_endpoints.get(endpoint_name)
endpoint.defaults.deployment_name = batch_deployment.name
ml_client.batch_endpoints.begin_create_or_update(endpoint).result()

print('Punkt końcowy wsadowy i wdrożenie utworzone.')

Czas wywołać punkt końcowy wsadowy, przekazując mu zasób danych jako wejście, i poczekać na zakończenie zadania.

Uwaga: To może chwilę potrwać!

In [ ]:
from azure.ai.ml import Input
from azure.ai.ml.constants import AssetTypes

# Pobierz najnowszą wersję zarejestrowanego zasobu danych wsadowych
batch_data_asset = ml_client.data.get(name="diabetes_batch_data", label="latest")

job = ml_client.batch_endpoints.invoke(
    endpoint_name=endpoint_name,
    inputs={
        "input_data": Input(
            type=AssetTypes.URI_FOLDER,
            path=f"azureml:diabetes_batch_data:{batch_data_asset.version}",
        )
    },
)

ml_client.jobs.stream(job.name)

Po zakończeniu zadania predykcje są już zapisane przez podrzędne zadanie scoringowe. Możesz je pobrać w ten sposób:

In [ ]:
import glob
import shutil
import pandas as pd

shutil.rmtree('diabetes-results', ignore_errors=True)

# Wdrożenie uruchamia skrypt scoringowy w zadaniu podrzędnym - pobierz referencję do niego
scoring_job = list(ml_client.jobs.list(parent_job_name=job.name))[0]

# Pobierz jego wyjście (dla wdrożenia wsadowego modelu domyślnie nazywa się "score")
ml_client.jobs.download(name=scoring_job.name, download_path='diabetes-results', output_name='score')

# Odszukaj plik z predykcjami
result_file = glob.glob('diabetes-results/**/predictions.csv', recursive=True)[0]

# uporządkuj format wyników
df = pd.read_csv(result_file, delimiter=":", header=None)
df.columns = ["File", "Prediction"]

# Wyświetl pierwsze 20 wyników
df.head(20)

Korzystanie z punktu końcowego wsadowego w aplikacji

Punkt końcowy wsadowy jest trwałym zasobem, gotowym do wywołania od chwili utworzenia. Aplikacja kliencka może uruchomić zadanie scoringowe metodą invoke z Azure ML SDK (tak jak przed chwilą), poleceniem Azure CLI (az ml batch-endpoint invoke) albo bezpośrednim wywołaniem interfejsu REST punktu końcowego.

Wywołania REST punktów końcowych wsadowych uwierzytelnia się tokenami Microsoft Entra ID, a nie prostym kluczem. Prawdziwa aplikacja logowałaby się więc zwykle jako jednostka usługi (ang. service principal) i przedstawiała uzyskany token typu bearer. Szczegóły i przykłady opisuje dokumentacja Authorization on batch endpoints.

Masz już punkt końcowy wsadowy, który potrafi codziennie przetwarzać komplet danych pacjentów.

Więcej informacji: O wnioskowaniu wsadowym przeczytasz w dokumentacji Batch endpoints.

Sprzątanie

Wdrożenie wsadowe zużywa zasoby obliczeniowe tylko wtedy, gdy trwa zadanie scoringowe, więc samo pozostawienie punktu końcowego niczego nie kosztuje. Jeśli mimo to chcesz go usunąć, uruchom poniższą komórkę:

In [ ]:
# ml_client.batch_endpoints.begin_delete(name=endpoint_name)