Praca ze środowiskami obliczeniowymi¶
Dotychczasowe zadania (ang. job) wykonywały się na instancji obliczeniowej Azure Machine Learning, czyli na maszynie, przy której pracujesz. Czas sięgnąć po moc obliczeniową chmury i uruchomić trenowanie tam, gdzie da się je swobodnie skalować.
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.
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. Tematem jest jednak środowisko obliczeniowe, a nie zasoby danych, więc zamiast opierać się na współdzielonym zasobie diabetes_mltable (zarejestrowanym jako mltable, w konkretnej wersji) przekażesz do zadania lokalny plik data/diabetes.csv wprost jako wejście typu uri_file. SDK wyśle go do chmury automatycznie przy zlecaniu zadania.
Utworzenie środowiska obliczeniowego¶
Maszyna, przy której pracujesz, często nie wystarcza do policzenia czegoś złożonego albo długo trwającego na dużym zbiorze danych. W takich sytuacjach warto sięgnąć po zasoby obliczeniowe tworzone w chmurze na żądanie.
Azure Machine Learning obsługuje kilka rodzajów środowisk obliczeniowych, które definiuje się w obszarze roboczym i wykorzystuje do uruchamiania zadań - płacąc tylko za czas, w którym faktycznie pracują. Klaster obliczeniowy (ang. compute cluster) o nazwie aml-cluster powstał już w pierwszym ćwiczeniu, więc teraz wystarczy sprawdzić, czy nadal istnieje (a jeśli nie - utworzyć go) i użyć go do trenowania modelu.
from azure.ai.ml.entities import AmlCompute
from azure.core.exceptions import ResourceNotFoundError
cluster_name = "aml-cluster"
# Sprawdź, czy klaster już istnieje
try:
training_cluster = ml_client.compute.get(cluster_name)
print('Znaleziono istniejący klaster - zostanie użyty.')
except ResourceNotFoundError:
# Jeśli nie istnieje, utwórz go
training_cluster = AmlCompute(
name=cluster_name,
type="amlcompute",
size="Standard_D2as_v4",
min_instances=0,
max_instances=4,
idle_time_before_scale_down=300,
)
training_cluster = ml_client.begin_create_or_update(training_cluster).result()
print(training_cluster.name, "- stan:", training_cluster.provisioning_state)
Uruchomienie eksperymentu na zdalnym środowisku obliczeniowym¶
Klaster jest gotowy, więc można na nim uruchamiać zadania. Poniższy kod tworzy folder na pliki zadania. Folder mógł już powstać w poprzednim ćwiczeniu, ale uruchom tę komórkę mimo to.
import os
# Utwórz folder na pliki eksperymentu
experiment_folder = 'diabetes_training_tree'
os.makedirs(experiment_folder, exist_ok=True)
print('Utworzono folder', experiment_folder)
Teraz utwórz skrypt Pythona z kodem zadania. Nadpisze on skrypt użyty w poprzednim ćwiczeniu.
%%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 treningowych')
args = parser.parse_args()
# wczytaj dane o cukrzycy (przekazane jako wejście zadania)
print("Wczytywanie danych...")
diabetes = pd.read_csv(args.training_data)
# Oddziel cechy od etykiet
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)
mlflow.log_metric('Accuracy', float(acc))
# policz pole pod krzywą ROC
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))
# Linia przekątnej odpowiadająca losowemu zgadywaniu
plt.plot([0, 1], [0, 1], 'k--')
# Wartości FPR i TPR osiągnięte przez model
plt.plot(fpr, tpr)
plt.xlabel('Odsetek fałszywie pozytywnych')
plt.ylabel('Odsetek prawdziwie pozytywnych')
plt.title('Krzywa ROC')
mlflow.log_figure(fig, "ROC.png")
plt.show()
os.makedirs('outputs', exist_ok=True)
# pliki zapisane w folderze outputs trafiają automatycznie do wyników zadania
joblib.dump(value=model, filename='outputs/diabetes_model.pkl')
Wszystko jest gotowe, żeby uruchomić zadanie na utworzonym klastrze.
Uwaga: Zadanie potrwa wyraźnie dłużej niż poprzednie - najpierw trzeba zbudować obraz kontenera ze środowiskiem conda, potem uruchomić węzły klastra i wdrożyć na nich ten obraz, a dopiero na końcu wykona się skrypt. Przy tak prostym trenowaniu wygląda to na marnowanie czasu, ale przy zadaniu liczącym się kilka godzin możliwość rozłożenia pracy na skalowalny klaster potrafi skrócić całość o rząd wielkości.
from azure.ai.ml import command, Input
from azure.ai.ml.entities import Environment
from azure.ai.ml.constants import AssetTypes
# Zdefiniuj zależności conda dla zadania
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 (lub odtwórz) środowisko - na wypadek gdyby poprzednie ćwiczenie nie zostało wykonane
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",
)
registered_env = ml_client.environments.create_or_update(diabetes_env)
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=cluster_name, # Użyj klastra utworzonego wcześniej
display_name="diabetes-train-tree-remote",
experiment_name="diabetes-training",
)
returned_job = ml_client.jobs.create_or_update(job)
ml_client.jobs.stream(returned_job.name)
Czekając na wykonanie zadania, możesz obserwować stan środowiska obliczeniowego w wypisywanych powyżej logach albo w Azure Machine Learning studio. Stan klastra sprawdzisz też kodem poniżej. Zmiana z steady na resizing zajmuje chwilę - to dobry moment na przerwę.
Uwaga: Obiekt klastra zwracany przez SDK nie pokazuje liczby aktualnie działających węzłów. Żeby zobaczyć, jak klaster skaluje się na żywo, otwórz stronę Compute w studio.
cluster_status = ml_client.compute.get(cluster_name)
print(f"Stan: {cluster_status.provisioning_state}")
print(f"Minimalna liczba węzłów: {cluster_status.min_instances}, maksymalna: {cluster_status.max_instances}")
Po zakończeniu zadania możesz pobrać jego metryki i pliki wyjściowe. Tym razem wśród wyników znajdą się także logi z budowania obrazu środowiska i z zarządzania klastrem.
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}")
Teraz możesz zarejestrować model wytrenowany przez to zadanie.
from azure.ai.ml.entities import Model
from azure.ai.ml.constants import AssetTypes
# Zarejestruj model
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 on aml-cluster (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)
# Wypisz zarejestrowane modele
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')
Więcej informacji: O środowiskach obliczeniowych w Azure Machine Learning przeczytasz w artykule What are compute targets in Azure Machine Learning? w dokumentacji.