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

Praca ze środowiskami

W tym momencie kursu masz za sobą sporo eksperymentów w obszarze roboczym Azure Machine Learning, a w niektórych trzeba było wskazać konkretne pakiety Pythona wymagane tam, gdzie wykonuje się kod eksperymentu. W tym ćwiczeniu przyjrzysz się środowiskom dokładniej, korzystając z Azure Machine Learning SDK v2.

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 importlib.metadata import version
from azure.ai.ml import MLClient
from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()
ml_client = MLClient.from_config(credential=credential)

print(f"Azure ML {version('azure-ai-ml')} gotowe do pracy z obszarem roboczym {ml_client.workspace_name}")

Przygotowanie danych do eksperymentu

W tym ćwiczeniu pracujesz na zbiorze z wynikami badań pacjentów pod kątem cukrzycy. Ćwiczenie dotyczy jednak środowisk, a nie zasobów danych, więc zamiast uzależniać się od typu i wersji współdzielonego zasobu diabetes_mltable z wcześniejszych ćwiczeń (zarejestrowanego jako mltable), każde zadanie dostanie lokalny plik data/diabetes.csv bezpośrednio, jako wejście typu uri_file. SDK wysyła ten plik automatycznie przy zleceniu zadania.

Utworzenie skryptu trenującego

Uruchom dwie poniższe komórki, aby utworzyć:

  1. Folder na pliki nowego eksperymentu
  2. Plik skryptu trenującego, który uczy model przy użyciu scikit-learn i rysuje krzywą ROC przy użyciu matplotlib.
In [ ]:
import os

# Utwórz folder na pliki eksperymentu
experiment_folder = 'diabetes_training_logistic'
os.makedirs(experiment_folder, exist_ok=True)
print(experiment_folder, '- folder utworzony')
In [ ]:
%%writefile $experiment_folder/diabetes_training.py
# Import bibliotek
import argparse
import os
import pandas as pd
import numpy as np
import joblib
import mlflow
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.metrics import roc_curve
import matplotlib.pyplot as plt

# Ustaw hiperparametr regularyzacji (przekazany do skryptu jako argument)
parser = argparse.ArgumentParser()
parser.add_argument('--training-data', type=str, dest='training_data', help='ścieżka do danych uczących')
parser.add_argument('--regularization', type=float, dest='reg_rate', default=0.01, help='wskaźnik regularyzacji')
args = parser.parse_args()
reg = args.reg_rate

# wczytaj dane o cukrzycy (przekazane jako wejście)
print("Wczytywanie danych...")
diabetes = pd.read_csv(args.training_data)

# Rozdziel cechy (ang. features) i etykietę (ang. label)
X, y = diabetes[['Pregnancies','PlasmaGlucose','DiastolicBloodPressure','TricepsThickness','SerumInsulin','BMI','DiabetesPedigree','Age']].values, diabetes['Diabetic'].values

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

# Wytrenuj model regresji logistycznej
print('Trenowanie modelu regresji logistycznej ze wskaźnikiem regularyzacji', reg)
mlflow.log_metric('Regularization Rate', float(reg))
model = LogisticRegression(C=1/reg, solver="liblinear").fit(X_train, y_train)

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

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

# narysuj krzywą ROC
fpr, tpr, thresholds = roc_curve(y_test, y_scores[:,1])
fig = plt.figure(figsize=(6, 4))
# Narysuj przekątną odpowiadającą losowemu zgadywaniu
plt.plot([0, 1], [0, 1], 'k--')
# Narysuj FPR i TPR osiągnięte przez model
plt.plot(fpr, tpr)
plt.xlabel('Odsetek fałszywie pozytywnych (FPR)')
plt.ylabel('Odsetek prawdziwie pozytywnych (TPR)')
plt.title('Krzywa ROC')
mlflow.log_figure(fig, "ROC.png")
plt.show()

os.makedirs('outputs', exist_ok=True)
# plik zapisany w folderze outputs jest automatycznie dołączany do wyników zadania
joblib.dump(value=model, filename='outputs/diabetes_model.pkl')

Zdefiniowanie środowiska

Gdy skrypt Pythona wykonuje się w Azure Machine Learning jako zadanie, środowisko określa kontekst jego wykonania - bazowy obraz kontenera oraz pakiety conda i pip dostępne w trakcie działania skryptu. Azure Machine Learning udostępnia gotowe (ang. curated) środowiska z wieloma popularnymi pakietami, ale można też zdefiniować własne.

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

# Zdefiniuj zależności conda dla eksperymentu
conda_spec = {
    "name": "diabetes-experiment-env",
    "channels": ["conda-forge"],
    "dependencies": [
        "python=3.10",
        "scikit-learn",
        "ipykernel",
        "matplotlib",
        "pandas",
        "pip",
        {
            "pip": [
                # Bez pakietu mlflow - wtyczka dociąga zgodną wersję sama.
                # Dodanie go tutaj zrywa logowanie artefaktów.
                "azureml-mlflow",
                "pyarrow",
            ]
        },
    ],
}

# Utwórz środowisko Pythona dla eksperymentu
diabetes_env = Environment(
    name="diabetes-experiment-env",
    description="A custom environment for training the diabetes model",
    conda_file=conda_spec,
    image="mcr.microsoft.com/azureml/openmpi5.0-ubuntu24.04",
)

print(diabetes_env.name, '- środowisko zdefiniowane.')

Teraz możesz użyć tego środowiska w eksperymencie, przypisując je do zadania typu command.

Poniższy kod przypisuje utworzone środowisko do zadania command i zleca je na klastrze aml-cluster. W trakcie działania zadania obserwuj strumieniowane logi - przy pierwszym uruchomieniu zobaczysz, jak budowane jest środowisko conda. Kolejne zadania korzystają z gotowego obrazu.

Dlaczego nie local: zadanie można też uruchomić na samej instancji obliczeniowej, podając compute="local". Ta ścieżka wymaga jednak osobnego uwierzytelnienia sesji notatnika i na instancji obliczeniowej często kończy się błędem SSO failure. Klaster od tego nie zależy.

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

job = command(
    code=experiment_folder,
    command="python diabetes_training.py --training-data ${{inputs.diabetes}} --regularization 0.1",
    inputs={"diabetes": Input(type=AssetTypes.URI_FILE, path="data/diabetes.csv")},
    environment=diabetes_env,  # anonimowa wersja środowiska - SDK ostrzeże, że podana nazwa nie zostanie użyta
    compute="aml-cluster",
    display_name="diabetes-train-logistic",
    experiment_name="diabetes-training",
)

returned_job = ml_client.jobs.create_or_update(job)
ml_client.jobs.stream(returned_job.name)

Zadanie skorzystało ze środowiska, w którym znalazły się wszystkie potrzebne pakiety. Zapisane metryki i wyniki zadania obejrzysz w Azure Machine Learning studio - w tym model wytrenowany przy użyciu scikit-learn i wykres krzywej ROC wygenerowany przez matplotlib - albo pobierzesz je przez MLflow, tak jak poniżej.

In [ ]:
import mlflow

mlflow.set_tracking_uri(ml_client.workspaces.get(ml_client.workspace_name).mlflow_tracking_uri)

mlflow_run = mlflow.get_run(returned_job.name)
print("Metryki:")
for key, value in mlflow_run.data.metrics.items():
    print(f"  {key}: {value}")

print(f"\nZadanie w Azure Machine Learning studio: {returned_job.studio_url}")

Rejestrowanie środowiska

Skoro definicja środowiska z potrzebnymi pakietami już powstała, warto zarejestrować je w obszarze roboczym i używać ponownie w kolejnych zadaniach.

In [ ]:
# Zarejestruj środowisko
registered_env = ml_client.environments.create_or_update(diabetes_env)
print(f"Zarejestrowane środowisko: {registered_env.name}, wersja {registered_env.version}")

Zwróć uwagę, że środowisko rejestruje się pod nazwą nadaną przy jego tworzeniu - tutaj diabetes-experiment-env.

Zarejestrowane środowisko możesz wykorzystać w dowolnym skrypcie o tych samych wymaganiach. Dla przykładu utwórz folder i skrypt, który trenuje model cukrzycy innym algorytmem:

In [ ]:
import os

# Utwórz folder na pliki eksperymentu
experiment_folder = 'diabetes_training_tree'
os.makedirs(experiment_folder, exist_ok=True)
print(experiment_folder, '- folder utworzony')
In [ ]:
%%writefile $experiment_folder/diabetes_training.py
# Import bibliotek
import argparse
import os
import pandas as pd
import numpy as np
import joblib
import mlflow
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import roc_auc_score
from sklearn.metrics import roc_curve
import matplotlib.pyplot as plt

parser = argparse.ArgumentParser()
parser.add_argument('--training-data', type=str, dest='training_data', help='ścieżka do danych uczących')
args = parser.parse_args()

# wczytaj dane o cukrzycy (przekazane jako wejście)
print("Wczytywanie danych...")
diabetes = pd.read_csv(args.training_data)

# Rozdziel cechy (ang. features) i etykietę (ang. label)
X, y = diabetes[['Pregnancies','PlasmaGlucose','DiastolicBloodPressure','TricepsThickness','SerumInsulin','BMI','DiabetesPedigree','Age']].values, diabetes['Diabetic'].values

# Podziel dane na zbiór uczący 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)
mlflow.log_metric('Accuracy', float(acc))

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

# narysuj krzywą ROC
fpr, tpr, thresholds = roc_curve(y_test, y_scores[:,1])
fig = plt.figure(figsize=(6, 4))
# Narysuj przekątną odpowiadającą losowemu zgadywaniu
plt.plot([0, 1], [0, 1], 'k--')
# Narysuj FPR i TPR osiągnięte przez model
plt.plot(fpr, tpr)
plt.xlabel('Odsetek fałszywie pozytywnych (FPR)')
plt.ylabel('Odsetek prawdziwie pozytywnych (TPR)')
plt.title('Krzywa ROC')
mlflow.log_figure(fig, "ROC.png")
plt.show()

os.makedirs('outputs', exist_ok=True)
joblib.dump(value=model, filename='outputs/diabetes_model.pkl')

Teraz pobierz zarejestrowane środowisko i skonfiguruj z nim nowe zadanie, które uruchomi alternatywny skrypt trenujący. Tym razem skrypt nie ma parametrów, bo klasyfikator drzewa decyzyjnego nie wymaga tutaj żadnych wartości hiperparametrów.

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

registered_env = ml_client.environments.get(name="diabetes-experiment-env", label="latest")

job = command(
    code=experiment_folder,
    command="python diabetes_training.py --training-data ${{inputs.diabetes}}",
    inputs={"diabetes": Input(type=AssetTypes.URI_FILE, path="data/diabetes.csv")},
    environment=registered_env,
    compute="aml-cluster",
    display_name="diabetes-train-tree",
    experiment_name="diabetes-training",
)

returned_job = ml_client.jobs.create_or_update(job)
ml_client.jobs.stream(returned_job.name)

Tym razem zadanie rusza szybciej, bo pasujący obraz środowiska został już zbudowany przy poprzednim uruchomieniu i trafił do pamięci podręcznej. Co ważniejsze, na innym środowisku obliczeniowym zbudowałoby się i zostało użyte dokładnie to samo środowisko - to właśnie zapewnia powtarzalny kontekst wykonania skryptu trenującego.

Zajrzyj do metryk i wyników zadania.

In [ ]:
mlflow_run = mlflow.get_run(returned_job.name)
print("Metryki:")
for key, value in mlflow_run.data.metrics.items():
    print(f"  {key}: {value}")

print(f"\nZadanie w Azure Machine Learning studio: {returned_job.studio_url}")

Ten model wygląda na nieco lepszy od regresji logistycznej, więc warto go zarejestrować.

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

model = Model(
    path=f"azureml://jobs/{returned_job.name}/outputs/artifacts/paths/outputs/diabetes_model.pkl",
    type=AssetTypes.CUSTOM_MODEL,
    name="diabetes_model",
    description="A diabetes classification model",
    tags={"Training context": "Command job + custom environment (Decision Tree)"},
    properties={
        "AUC": str(mlflow_run.data.metrics.get("AUC")),
        "Accuracy": str(mlflow_run.data.metrics.get("Accuracy")),
    },
)
ml_client.models.create_or_update(model)

for m in ml_client.models.list(name="diabetes_model"):
    print(m.name, 'wersja:', m.version)
    for tag_name in m.tags:
        print('\t', tag_name, ':', m.tags[tag_name])
    for prop_name in m.properties:
        print('\t', prop_name, ':', m.properties[prop_name])
    print('\n')

Przegląd zarejestrowanych środowisk

Poza środowiskami definiowanymi samodzielnie Azure Machine Learning udostępnia gotowe (ang. curated) środowiska dla typowych scenariuszy trenowania i wnioskowania. Poniższy kod wypisuje własne środowiska zarejestrowane w obszarze roboczym:

In [ ]:
for env in ml_client.environments.list():
    print("Nazwa:", env.name)

Gotowe środowiska utrzymuje centralnie Microsoft w systemowym rejestrze azureml - nie w obszarze roboczym - i odwołuje się do nich ścieżką azureml://registries/azureml/environments/<nazwa>/labels/latest. Połącz się z tym rejestrem i obejrzyj jedno z dostępnych tam gotowych środowisk razem z pakietami, które zawiera.

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

# Połącz się z zarządzanym przez Microsoft rejestrem "azureml", w którym leżą gotowe środowiska
registry_client = MLClient(credential=credential, registry_name="azureml")

curated_env_name = "sklearn-1.5"
curated_env = registry_client.environments.get(name=curated_env_name, label="latest")

print("Nazwa:", curated_env.name)
print("Wersja:", curated_env.version)
print("Obraz:", curated_env.image)
print(f"Pełna referencja: azureml://registries/azureml/environments/{curated_env.name}/labels/latest")

Więcej informacji: O środowiskach w Azure Machine Learning przeczytasz w dokumentacji Azure Machine Learning