Udostępnij za pośrednictwem


Konwertowanie eksperymentów uczenia maszynowego na produkcyjny kod w języku Python

DOTYCZY: Zestaw SDK języka Python w wersji 1

Z tego samouczka dowiesz się, jak przekonwertować notesy Jupyter na skrypty języka Python, aby ułatwić testowanie i automatyzację przy użyciu szablonu kodu MLOpsPython i usługi Azure Machine Learning. Zazwyczaj ten proces służy do wykonywania eksperymentów/ kodu szkoleniowego z notesu Jupyter i konwertowania go na skrypty języka Python. Te skrypty można następnie używać do testowania i automatyzacji ciągłej integracji/ciągłego wdrażania w środowisku produkcyjnym.

Projekt uczenia maszynowego wymaga eksperymentowania, w którym hipotezy są testowane za pomocą elastycznych narzędzi, takich jak Jupyter Notebook przy użyciu rzeczywistych zestawów danych. Gdy model będzie gotowy do produkcji, kod modelu powinien zostać umieszczony w produkcyjnym repozytorium kodu. W niektórych przypadkach kod modelu musi zostać przekonwertowany na skrypty języka Python, aby można je było umieścić w repozytorium kodu produkcyjnego. W tym samouczku omówiono zalecane podejście do eksportowania kodu eksperymentowania do skryptów języka Python.

Z tego samouczka dowiesz się, jak wykonywać następujące czynności:

  • Czysty kod beznessential
  • Refaktoryzacja kodu notesu Jupyter Notebook do funkcji
  • Tworzenie skryptów języka Python dla powiązanych zadań
  • Tworzenie testów jednostkowych

Wymagania wstępne

  • Wygeneruj szablon MLOpsPython i użyj experimentation/Diabetes Ridge Regression Training.ipynb notesów i experimentation/Diabetes Ridge Regression Scoring.ipynb . Te notesy są używane jako przykład konwertowania z eksperymentów na środowisko produkcyjne. Te notesy można znaleźć na stronie https://github.com/microsoft/MLOpsPython/tree/master/experimentation.
  • Zainstaluj system nbconvert. Postępuj zgodnie z instrukcjami instalacji w sekcji Instalowanie aplikacji nbconvert na stronie Instalacja .

Usuń cały beznieściowy kod

Część kodu napisana podczas eksperymentowania jest przeznaczona tylko do celów eksploracyjnych. W związku z tym pierwszym krokiem do przekonwertowania kodu eksperymentalnego na kod produkcyjny jest usunięcie tego kodu beznieściowego. Usunięcie kodu beznieściowego spowoduje również, że kod będzie bardziej konserwowalny. W tej sekcji usuniesz kod z notesu experimentation/Diabetes Ridge Regression Training.ipynb . Instrukcje drukowania kształtu X i y i wywołania features.describe komórek są tylko do eksploracji danych i można je usunąć. Po usunięciu kodu experimentation/Diabetes Ridge Regression Training.ipynb bez nessential powinien wyglądać podobnie do następującego kodu bez znaczników markdown:

from sklearn.datasets import load_diabetes
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
import joblib
import pandas as pd

sample_data = load_diabetes()

df = pd.DataFrame(
    data=sample_data.data,
    columns=sample_data.feature_names)
df['Y'] = sample_data.target

X = df.drop('Y', axis=1).values
y = df['Y'].values

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=0)
data = {"train": {"X": X_train, "y": y_train},
        "test": {"X": X_test, "y": y_test}}

args = {
    "alpha": 0.5
}

reg_model = Ridge(**args)
reg_model.fit(data["train"]["X"], data["train"]["y"])

preds = reg_model.predict(data["test"]["X"])
mse = mean_squared_error(preds, y_test)
metrics = {"mse": mse}
print(metrics)

model_name = "sklearn_regression_model.pkl"
joblib.dump(value=reg, filename=model_name)

Refaktoryzacja kodu do funkcji

Po drugie kod Jupyter musi zostać refaktoryzowany do funkcji. Refaktoryzacja kodu do funkcji ułatwia testowanie jednostkowe i ułatwia obsługę kodu. W tej sekcji wykonasz refaktoryzację:

  • Notes trenowania regresji diabetes Ridge(experimentation/Diabetes Ridge Regression Training.ipynb)
  • Notes analizy punktacji diabetes Ridge Regression (experimentation/Diabetes Ridge Regression Scoring.ipynb)

Refaktoryzacja notesu trenowania regresji diabetes Ridge do funkcji

W experimentation/Diabetes Ridge Regression Training.ipynbpliku wykonaj następujące kroki:

  1. Utwórz funkcję o nazwie split_data , aby podzielić ramkę danych na dane testowe i wytrenować. Funkcja powinna przyjąć ramkę df danych jako parametr i zwrócić słownik zawierający klucze train i test.

    Przenieś kod pod nagłówkiem Split Data into Training and Validation Sets (Podział danych do nagłówka Training and Validation Sets) i split_data zmodyfikuj go, aby zwrócić data obiekt.

  2. Utwórz funkcję o nazwie train_model, która przyjmuje parametry data i args zwraca wytrenowany model.

    Przenieś kod pod nagłówkiem Training Model on Training Set (Model trenowania) do train_model funkcji i zmodyfikuj go, aby zwrócić reg_model obiekt. args Usuń słownik, a wartości będą pochodzić z parametru args .

  3. Utwórz funkcję o nazwie get_model_metrics, która przyjmuje parametry reg_model i data, i ocenia model, a następnie zwraca słownik metryk dla wytrenowanego modelu.

    Przenieś kod pod nagłówkiem Validate Model on Validation Set do get_model_metrics funkcji i zmodyfikuj go, aby zwrócić metrics obiekt.

Trzy funkcje powinny być następujące:

# Split the dataframe into test and train data
def split_data(df):
    X = df.drop('Y', axis=1).values
    y = df['Y'].values

    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=0)
    data = {"train": {"X": X_train, "y": y_train},
            "test": {"X": X_test, "y": y_test}}
    return data


# Train the model, return the model
def train_model(data, args):
    reg_model = Ridge(**args)
    reg_model.fit(data["train"]["X"], data["train"]["y"])
    return reg_model


# Evaluate the metrics for the model
def get_model_metrics(reg_model, data):
    preds = reg_model.predict(data["test"]["X"])
    mse = mean_squared_error(preds, data["test"]["y"])
    metrics = {"mse": mse}
    return metrics

Nadal w pliku experimentation/Diabetes Ridge Regression Training.ipynbwykonaj następujące kroki:

  1. Utwórz nową funkcję o nazwie main, która nie przyjmuje żadnych parametrów i nie zwraca żadnych parametrów.

  2. Przenieś kod pod nagłówkiem "Załaduj dane" do main funkcji.

  3. Dodaj wywołania dla nowo napisanych funkcji do main funkcji:

    # Split Data into Training and Validation Sets
    data = split_data(df)
    
    # Train Model on Training Set
    args = {
        "alpha": 0.5
    }
    reg = train_model(data, args)
    
    # Validate Model on Validation Set
    metrics = get_model_metrics(reg, data)
    
  4. Przenieś kod pod nagłówkiem "Zapisz model" do main funkcji.

Funkcja main powinna wyglądać podobnie do następującego kodu:

def main():
    # Load Data
    sample_data = load_diabetes()

    df = pd.DataFrame(
        data=sample_data.data,
        columns=sample_data.feature_names)
    df['Y'] = sample_data.target

    # Split Data into Training and Validation Sets
    data = split_data(df)

    # Train Model on Training Set
    args = {
        "alpha": 0.5
    }
    reg = train_model(data, args)

    # Validate Model on Validation Set
    metrics = get_model_metrics(reg, data)

    # Save Model
    model_name = "sklearn_regression_model.pkl"

    joblib.dump(value=reg, filename=model_name)

Na tym etapie w notesie nie powinien znajdować się żaden kod, który nie znajduje się w funkcji, poza instrukcjami import w pierwszej komórce.

Dodaj instrukcję, która wywołuje main funkcję.

main()

Po refaktoryzacji experimentation/Diabetes Ridge Regression Training.ipynb powinien wyglądać podobnie do następującego kodu bez znaku markdown:

from sklearn.datasets import load_diabetes
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
import pandas as pd
import joblib


# Split the dataframe into test and train data
def split_data(df):
    X = df.drop('Y', axis=1).values
    y = df['Y'].values

    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=0)
    data = {"train": {"X": X_train, "y": y_train},
            "test": {"X": X_test, "y": y_test}}
    return data


# Train the model, return the model
def train_model(data, args):
    reg_model = Ridge(**args)
    reg_model.fit(data["train"]["X"], data["train"]["y"])
    return reg_model


# Evaluate the metrics for the model
def get_model_metrics(reg_model, data):
    preds = reg_model.predict(data["test"]["X"])
    mse = mean_squared_error(preds, data["test"]["y"])
    metrics = {"mse": mse}
    return metrics


def main():
    # Load Data
    sample_data = load_diabetes()

    df = pd.DataFrame(
        data=sample_data.data,
        columns=sample_data.feature_names)
    df['Y'] = sample_data.target

    # Split Data into Training and Validation Sets
    data = split_data(df)

    # Train Model on Training Set
    args = {
        "alpha": 0.5
    }
    reg = train_model(data, args)

    # Validate Model on Validation Set
    metrics = get_model_metrics(reg, data)

    # Save Model
    model_name = "sklearn_regression_model.pkl"

    joblib.dump(value=reg, filename=model_name)

main()

Refactor Diabetes Ridge Regression Scoring notebook into functions (Refaktoryzacja cukrzycy Ridge Regression Scoring notebook do funkcji)

W experimentation/Diabetes Ridge Regression Scoring.ipynbpliku wykonaj następujące kroki:

  1. Utwórz nową funkcję o nazwie init, która nie przyjmuje żadnych parametrów i nie zwraca żadnych parametrów.
  2. Skopiuj kod pod nagłówkiem "Load Model" (Załaduj init model) do funkcji .

Funkcja init powinna wyglądać podobnie do następującego kodu:

def init():
    model_path = Model.get_model_path(
        model_name="sklearn_regression_model.pkl")
    model = joblib.load(model_path)

Po utworzeniu init funkcji zastąp cały kod pod nagłówkiem "Load Model" pojedynczym wywołaniem w init następujący sposób:

init()

W experimentation/Diabetes Ridge Regression Scoring.ipynbpliku wykonaj następujące kroki:

  1. Utwórz nową funkcję o nazwie run, która przyjmuje raw_data parametry i request_headers jako parametry i zwraca słownik wyników w następujący sposób:

    {"result": result.tolist()}
    
  2. Skopiuj kod w nagłówkach "Prepare Data" (Przygotowywanie danych) i "Score Data" (Generowanie wyników danych) do run funkcji .

    Funkcja run powinna wyglądać podobnie do następującego kodu (Pamiętaj, aby usunąć instrukcje, które ustawiają zmienne raw_data i request_headers, które będą używane później po wywołaniu run funkcji):

    def run(raw_data, request_headers):
        data = json.loads(raw_data)["data"]
        data = numpy.array(data)
        result = model.predict(data)
    
        return {"result": result.tolist()}
    

Po utworzeniu run funkcji zastąp cały kod w nagłówkach "Prepare Data" (Przygotowywanie danych) i "Score Data" (Generowanie wyników danych) następującym kodem:

raw_data = '{"data":[[1,2,3,4,5,6,7,8,9,10],[10,9,8,7,6,5,4,3,2,1]]}'
request_header = {}
prediction = run(raw_data, request_header)
print("Test result: ", prediction)

Poprzedni kod ustawia zmienne raw_data i , wywołuje run funkcję za pomocą raw_data poleceń i request_headeri request_headeri wyświetla przewidywania.

Po refaktoryzacji experimentation/Diabetes Ridge Regression Scoring.ipynb powinien wyglądać podobnie do następującego kodu bez znaku markdown:

import json
import numpy
from azureml.core.model import Model
import joblib

def init():
    model_path = Model.get_model_path(
        model_name="sklearn_regression_model.pkl")
    model = joblib.load(model_path)

def run(raw_data, request_headers):
    data = json.loads(raw_data)["data"]
    data = numpy.array(data)
    result = model.predict(data)

    return {"result": result.tolist()}

init()
test_row = '{"data":[[1,2,3,4,5,6,7,8,9,10],[10,9,8,7,6,5,4,3,2,1]]}'
request_header = {}
prediction = run(test_row, {})
print("Test result: ", prediction)

Po trzecie, powiązane funkcje należy scalić z plikami języka Python, aby lepiej ułatwić ponowne użycie kodu. W tej sekcji utworzysz pliki języka Python dla następujących notesów:

  • Notes trenowania regresji diabetes Ridge(experimentation/Diabetes Ridge Regression Training.ipynb)
  • Notes analizy punktacji diabetes Ridge Regression (experimentation/Diabetes Ridge Regression Scoring.ipynb)

Tworzenie pliku języka Python dla notesu trenowania regresji diabetes Ridge

Przekonwertuj notes na skrypt wykonywalny, uruchamiając następującą instrukcję w wierszu polecenia, która używa nbconvert pakietu i ścieżki experimentation/Diabetes Ridge Regression Training.ipynb:

jupyter nbconvert "Diabetes Ridge Regression Training.ipynb" --to script --output train

Po przekonwertowaniu notesu na train.pyprogram usuń wszelkie niepożądane komentarze. Zastąp wywołanie na main() końcu pliku wywołaniem warunkowym, tak jak w poniższym kodzie:

if __name__ == '__main__':
    main()

Plik train.py powinien wyglądać podobnie do następującego kodu:

from sklearn.datasets import load_diabetes
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
import pandas as pd
import joblib


# Split the dataframe into test and train data
def split_data(df):
    X = df.drop('Y', axis=1).values
    y = df['Y'].values

    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=0)
    data = {"train": {"X": X_train, "y": y_train},
            "test": {"X": X_test, "y": y_test}}
    return data


# Train the model, return the model
def train_model(data, args):
    reg_model = Ridge(**args)
    reg_model.fit(data["train"]["X"], data["train"]["y"])
    return reg_model


# Evaluate the metrics for the model
def get_model_metrics(reg_model, data):
    preds = reg_model.predict(data["test"]["X"])
    mse = mean_squared_error(preds, data["test"]["y"])
    metrics = {"mse": mse}
    return metrics


def main():
    # Load Data
    sample_data = load_diabetes()

    df = pd.DataFrame(
        data=sample_data.data,
        columns=sample_data.feature_names)
    df['Y'] = sample_data.target

    # Split Data into Training and Validation Sets
    data = split_data(df)

    # Train Model on Training Set
    args = {
        "alpha": 0.5
    }
    reg = train_model(data, args)

    # Validate Model on Validation Set
    metrics = get_model_metrics(reg, data)

    # Save Model
    model_name = "sklearn_regression_model.pkl"

    joblib.dump(value=reg, filename=model_name)

if __name__ == '__main__':
    main()

train.py można teraz wywołać z poziomu terminalu, uruchamiając polecenie python train.py. Funkcje z train.py programu mogą być również wywoływane z innych plików.

train_aml.py Plik znaleziony w katalogu w diabetes_regression/training repozytorium MLOpsPython wywołuje funkcje zdefiniowane w train.py kontekście zadania eksperymentu usługi Azure Machine Learning. Funkcje można również wywoływać w testach jednostkowych, które zostały omówione w dalszej części tego przewodnika.

Tworzenie pliku języka Python dla notesu analizy regresji diabetes Ridge

Przekonwertuj notes na skrypt wykonywalny, uruchamiając następującą instrukcję w wierszu polecenia, który używa nbconvert pakietu i ścieżki experimentation/Diabetes Ridge Regression Scoring.ipynb:

jupyter nbconvert "Diabetes Ridge Regression Scoring.ipynb" --to script --output score

Po przekonwertowaniu notesu na score.pyprogram usuń wszelkie niepożądane komentarze. Plik score.py powinien wyglądać podobnie do następującego kodu:

import json
import numpy
from azureml.core.model import Model
import joblib

def init():
    model_path = Model.get_model_path(
        model_name="sklearn_regression_model.pkl")
    model = joblib.load(model_path)

def run(raw_data, request_headers):
    data = json.loads(raw_data)["data"]
    data = numpy.array(data)
    result = model.predict(data)

    return {"result": result.tolist()}

init()
test_row = '{"data":[[1,2,3,4,5,6,7,8,9,10],[10,9,8,7,6,5,4,3,2,1]]}'
request_header = {}
prediction = run(test_row, request_header)
print("Test result: ", prediction)

Zmienna model musi być globalna, aby była widoczna w całym skrycie. Dodaj następującą instrukcję na początku init funkcji:

global model

Po dodaniu poprzedniej instrukcji init funkcja powinna wyglądać podobnie do następującego kodu:

def init():
    global model

    # load the model from file into a global object
    model_path = Model.get_model_path(
        model_name="sklearn_regression_model.pkl")
    model = joblib.load(model_path)

Tworzenie testów jednostkowych dla każdego pliku języka Python

Po czwarte, utwórz testy jednostkowe dla funkcji języka Python. Testy jednostkowe chronią kod przed regresjami funkcjonalnymi i ułatwiają konserwację. W tej sekcji utworzysz testy jednostkowe dla funkcji w programie train.py.

train.py Zawiera wiele funkcji, ale w tym samouczku train_model utworzymy tylko jeden test jednostkowy dla funkcji przy użyciu platformy Pytest. Narzędzie Pytest nie jest jedyną strukturą testowania jednostkowego języka Python, ale jest jedną z najczęściej używanych platform. Aby uzyskać więcej informacji, odwiedź stronę Pytest.

Test jednostkowy zwykle zawiera trzy główne akcje:

  • Rozmieszczanie obiektu — tworzenie i konfigurowanie niezbędnych obiektów
  • Działanie na obiekcie
  • Potwierdzanie oczekiwanego

Test jednostkowy wywoła train_model metodę z niektórymi zakodowanymi danymi i argumentami oraz sprawdzi, czy train_model działa zgodnie z oczekiwaniami, używając wynikowego wytrenowanego modelu w celu przewidywania i porównywania tego przewidywania z oczekiwaną wartością.

import numpy as np
from code.training.train import train_model


def test_train_model():
    # Arrange
    X_train = np.array([1, 2, 3, 4, 5, 6]).reshape(-1, 1)
    y_train = np.array([10, 9, 8, 8, 6, 5])
    data = {"train": {"X": X_train, "y": y_train}}

    # Act
    reg_model = train_model(data, {"alpha": 1.2})

    # Assert
    preds = reg_model.predict([[1], [2]])
    np.testing.assert_almost_equal(preds, [9.93939393939394, 9.03030303030303])

Następne kroki

Teraz, gdy rozumiesz sposób konwertowania z eksperymentu na kod produkcyjny, zobacz następujące linki, aby uzyskać więcej informacji i następne kroki: