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

Tworzenie usługi wnioskowania w czasie rzeczywistym

Dotychczasowe ćwiczenia dotyczyły trenowania i rejestrowania modeli. Teraz czas wdrożyć model jako usługę czasu rzeczywistego, z której aplikacje klienckie będą pobierać predykcje dla nowych danych. To właśnie jest wnioskowanie (ang. inference): model niczego już się nie uczy, tylko odpowiada na pytania.

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

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.')

Wdrożenie modelu w zarządzanym punkcie końcowym online

Model klasyfikujący pacjentów według prawdopodobieństwa cukrzycy jest już wytrenowany i zarejestrowany. Taki model mógłby pracować w przychodni, w której na badanie w kierunku cukrzycy kieruje się tylko pacjentów uznanych za zagrożonych. Żeby to było możliwe, wdrożysz model w zarządzanym punkcie końcowym (ang. endpoint) online, obsługującym wnioskowanie w czasie rzeczywistym.

Zacznij od sprawdzenia, jakie modele są zarejestrowane w obszarze roboczym.

In [ ]:
print("Zarejestrowane modele:")
for model in ml_client.models.list():
    print(f"\t{model.name}")

# Tak samo jak przy zasobach danych: wersja i typ należą do konkretnej wersji
najnowszy = ml_client.models.get(name="diabetes_model", label="latest")
print(f"\n{najnowszy.name}: najnowsza wersja {najnowszy.version}, typ {najnowszy.type}")

Teraz pobierz model, który ma zostać wdrożony. Jeśli podasz samą nazwę modelu, zwrócona zostanie jego najnowsza wersja.

In [ ]:
model = ml_client.models.get(name="diabetes_model", label="latest")
print(model.name, 'wersja', model.version)

Wdrożenie (ang. deployment) potrzebuje kilku plików z kodem i konfiguracją, więc najpierw utwórz na nie folder.

In [ ]:
import os

folder_name = 'diabetes_service'

# Utwórz folder na pliki wdrożenia
experiment_folder = './' + folder_name
os.makedirs(folder_name, exist_ok=True)

print(folder_name, '- folder utworzony.')

Punkt końcowy potrzebuje kodu w Pythonie, który wczyta dane wejściowe, załaduje model, wyliczy predykcje i je zwróci. Ten kod zapiszesz w skrypcie scoringowym (nazywanym też skryptem wejściowym), wdrażanym razem z modelem:

In [ ]:
%%writefile $folder_name/score_diabetes.py
import json
import joblib
import numpy as np
import os

# Wywoływana raz, przy inicjalizacji wdrożenia
def init():
    global model
    # Ustal ścieżkę do pliku wdrożonego modelu i wczytaj go
    # (zmienną AZUREML_MODEL_DIR ustawia punkt końcowy online; 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)

# Wywoływana przy każdym przychodzącym żądaniu
def run(raw_data):
    # Pobierz dane wejściowe jako tablicę numpy
    data = np.array(json.loads(raw_data)['data'])
    # Poproś model o predykcję
    predictions = model.predict(data)
    # Zamień każdą predykcję (0 lub 1) na odpowiadającą jej nazwę klasy
    classnames = ['not-diabetic', 'diabetic']
    predicted_classes = []
    for prediction in predictions:
        predicted_classes.append(classnames[prediction])
    # Zwracamy listę predykcji - serwer inferencyjny sam zamieni ją na JSON
    return predicted_classes

Punkt końcowy działa w kontenerze, który przy uruchomieniu musi doinstalować wymagane pakiety Pythona. Skrypt scoringowy korzysta z scikit-learn, więc utworzysz plik środowiska conda z listą potrzebnych pakietów.

In [ ]:
%%writefile $folder_name/diabetes_env.yml
name: diabetes-env
dependencies:
  - python=3.8
  - numpy
  - scikit-learn
  - pip
  - pip:
      - azureml-inference-server-http
In [ ]:
# Wyświetl zawartość pliku .yml
with open(folder_name + "/diabetes_env.yml", "r") as f:
    print(f.read())

Wszystko jest gotowe do wdrożenia. Utworzysz punkt końcowy o nazwie diabetes-endpoint z jednym wdrożeniem o nazwie blue. Całość składa się z czterech kroków:

  1. Utworzenie zarządzanego punktu końcowego online.
  2. Utworzenie zarządzanego wdrożenia, które wskazuje model, skrypt scoringowy, środowisko oraz zasoby obliczeniowe. Zasoby obliczeniowe dla zarządzanych punktów końcowych zapewnia i utrzymuje Azure - nie trzeba ich samodzielnie tworzyć ani nimi administrować.
  3. Skierowanie całego ruchu punktu końcowego do nowego wdrożenia.
  4. Sprawdzenie stanu wdrożenia.

Więcej informacji: Szczegóły wdrażania modeli opisuje dokumentacja.

Wdrożenie chwilę potrwa: najpierw budowany jest obraz kontenera ze środowiskiem, potem przydzielane są zasoby obliczeniowe, a na końcu trafia na nie model. Po pomyślnym zakończeniu stan wdrożenia (provisioning state) to Succeeded.

In [ ]:
from azure.ai.ml.entities import (
    ManagedOnlineEndpoint,
    ManagedOnlineDeployment,
    Environment,
    CodeConfiguration,
)

endpoint_name = "diabetes-endpoint"

# Utwórz zarządzany punkt końcowy online
endpoint = ManagedOnlineEndpoint(
    name=endpoint_name,
    description="Real-time diabetes classification service",
    auth_mode="key",
)
ml_client.online_endpoints.begin_create_or_update(endpoint).result()

# Zdefiniuj środowisko, w którym uruchamiany jest skrypt scoringowy
env = Environment(
    conda_file=f"{folder_name}/diabetes_env.yml",
    image="mcr.microsoft.com/azureml/openmpi5.0-ubuntu24.04",
)

# Utwórz zarządzane wdrożenie online
blue_deployment = ManagedOnlineDeployment(
    name="blue",
    endpoint_name=endpoint_name,
    model=model,
    environment=env,
    code_configuration=CodeConfiguration(code=folder_name, scoring_script="score_diabetes.py"),
    instance_type="Standard_DS2_v2",
    instance_count=1,
)
ml_client.online_deployments.begin_create_or_update(blue_deployment).result()

# Skieruj cały ruch punktu końcowego do wdrożenia blue
endpoint.traffic = {"blue": 100}
ml_client.online_endpoints.begin_create_or_update(endpoint).result()

print("Wdrożenie zakończone.")

Jeśli wdrożenie się powiodło, jego stan to Succeeded. Jeśli nie - poniższy kod sprawdzi stan i pobierze logi wdrożenia, co pomaga znaleźć przyczynę problemu.

In [ ]:
endpoint = ml_client.online_endpoints.get(name=endpoint_name)
print(endpoint.provisioning_state)

logs = ml_client.online_deployments.get_logs(
    name="blue", endpoint_name=endpoint_name, lines=50
)
print(logs)

# Jeśli trzeba coś poprawić i wdrożyć ponownie, może się okazać, że najpierw należy usunąć wdrożenie:
# ml_client.online_deployments.begin_delete(name="blue", endpoint_name=endpoint_name)

Zajrzyj do obszaru roboczego w Azure Machine Learning studio i otwórz stronę Endpoints - zobaczysz na niej punkty końcowe wdrożone w obszarze roboczym.

Nazwy punktów końcowych online możesz też wypisać z poziomu kodu:

In [ ]:
for online_endpoint in ml_client.online_endpoints.list():
    print(online_endpoint.name)

Korzystanie z punktu końcowego

Punkt końcowy jest wdrożony, więc można już z niego korzystać w aplikacji klienckiej.

In [ ]:
import json

x_new = [[2,180,74,24,21,23.9091702,1.488172308,22]]
print ('Pacjent: {}'.format(x_new[0]))

# Zapisz dane przykładowego pacjenta jako plik żądania w formacie JSON
request_file_name = "sample-data.json"
with open(request_file_name, "w") as f:
    json.dump({"data": x_new}, f)

# Wywołaj punkt końcowy, przekazując plik żądania (punkt końcowy przyjmuje też dane w postaci binarnej)
predictions = ml_client.online_endpoints.invoke(
    endpoint_name=endpoint_name,
    deployment_name="blue",
    request_file=request_file_name,
)

# Odczytaj przewidzianą klasę - jest tylko jedna, więc bierzemy pierwszą.
predicted_classes = json.loads(predictions)
print(predicted_classes[0])

Do punktu końcowego można też wysłać wyniki badań wielu pacjentów naraz i otrzymać predykcję dla każdego z nich.

In [ ]:
import json

# Tym razem dane wejściowe to tablica dwóch zestawów cech
x_new = [[2,180,74,24,21,23.9091702,1.488172308,22],
         [0,148,58,11,179,39.19207553,0.160829008,45]]

# Zapisz dane przykładowych pacjentów jako plik żądania w formacie JSON
request_file_name = "sample-data.json"
with open(request_file_name, "w") as f:
    json.dump({"data": x_new}, f)

# Wywołaj punkt końcowy, przekazując plik żądania
predictions = ml_client.online_endpoints.invoke(
    endpoint_name=endpoint_name,
    deployment_name="blue",
    request_file=request_file_name,
)

# Odczytaj przewidziane klasy.
predicted_classes = json.loads(predictions)

for i in range(len(x_new)):
    print ("Pacjent {}".format(x_new[i]), predicted_classes[i] )

Powyższy kod łączy się z zarządzanym punktem końcowym online przy użyciu Azure ML SDK i pobiera z niego predykcje modelu. W praktyce z modelu korzystają zwykle aplikacje biznesowe, które nie używają Azure ML SDK, tylko wysyłają do punktu końcowego zwykłe żądania HTTP.

Sprawdź więc, pod jaki adres URL takie aplikacje mają wysyłać żądania:

In [ ]:
endpoint = ml_client.online_endpoints.get(name=endpoint_name)
scoring_uri = endpoint.scoring_uri
print(scoring_uri)

Znając adres URI punktu końcowego, aplikacja może wysłać żądanie HTTP z danymi pacjenta w formacie JSON i odebrać w odpowiedzi przewidziane klasy. Punkt końcowy powstał z ustawieniem auth_mode="key", więc żądanie musi zawierać nagłówek Authorization z poprawnym kluczem.

In [ ]:
import requests
import json

# Pobierz klucz uwierzytelniający do punktu końcowego
keys = ml_client.online_endpoints.get_keys(name=endpoint_name)
primary_key = keys.primary_key

x_new = [[2,180,74,24,21,23.9091702,1.488172308,22],
         [0,148,58,11,179,39.19207553,0.160829008,45]]

# Zamień tablicę na dokument JSON, który da się przesłać w żądaniu
input_json = json.dumps({"data": x_new})

# Ustaw typ zawartości i nagłówek uwierzytelniający
headers = {
    'Content-Type':'application/json',
    'Authorization': f'Bearer {primary_key}',
}

response = requests.post(scoring_uri, input_json, headers = headers)
predicted_classes = json.loads(response.json())

for i in range(len(x_new)):
    print ("Pacjent {}".format(x_new[i]), predicted_classes[i] )

Model jest wdrożony w zarządzanym punkcie końcowym online z uwierzytelnianiem kluczem. Takie punkty końcowe same zajmują się równoważeniem obciążenia, skalowaniem i monitorowaniem. W zastosowaniach produkcyjnych, gdzie poświadczenia powinny wygasać, punkt końcowy można utworzyć z auth_mode="aml_token" albo auth_mode="aad_token" zamiast auth_mode="key".

Więcej informacji: O wdrażaniu modeli w punktach końcowych online przeczytasz w dokumentacji.

Sprzątanie

Zarządzany punkt końcowy utrzymuje przypisane mu zasoby obliczeniowe (i nalicza za nie koszt) aż do usunięcia. Jeśli punkt końcowy nie jest już potrzebny, uruchom poniższą komórkę, aby go usunąć:

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