Publicar e rastrear gasodutos de aprendizagem de máquinas

Este artigo irá mostrar-lhe como partilhar um oleoduto de aprendizagem automática com os seus colegas ou clientes.

Os oleodutos de aprendizagem automática são fluxos de trabalho reutilizáveis para tarefas de aprendizagem automática. Um dos benefícios dos gasodutos é o aumento da colaboração. Também pode ver pipelines, permitindo que os clientes utilizem o modelo atual enquanto estiver a trabalhar numa nova versão.

Pré-requisitos

Publicar um oleoduto

Uma vez que tenha um oleoduto em funcionamento, pode publicar um oleoduto para que este corra com diferentes entradas. Para o ponto final do REST de um oleoduto já publicado para aceitar parâmetros, deve configurar o seu oleoduto para utilizar PipelineParameter objetos para os argumentos que variarão.

  1. Para criar um parâmetro de pipeline, utilize um objeto PipelineParameter com um valor predefinido.

    from azureml.pipeline.core.graph import PipelineParameter
    
    pipeline_param = PipelineParameter(
      name="pipeline_arg",
      default_value=10)
    
  2. Adicione este PipelineParameter objeto como parâmetro a qualquer um dos passos na tubagem da seguinte forma:

    compareStep = PythonScriptStep(
      script_name="compare.py",
      arguments=["--comp_data1", comp_data1, "--comp_data2", comp_data2, "--output_data", out_data3, "--param1", pipeline_param],
      inputs=[ comp_data1, comp_data2],
      outputs=[out_data3],
      compute_target=compute_target,
      source_directory=project_folder)
    
  3. Publique este oleoduto que aceitará um parâmetro quando invocado.

    published_pipeline1 = pipeline_run1.publish_pipeline(
         name="My_Published_Pipeline",
         description="My Published Pipeline Description",
         version="1.0")
    

Executar um oleoduto publicado

Todos os oleodutos publicados têm um ponto final REST. Com o ponto final do gasoduto, pode desencadear uma corrida do gasoduto a partir de quaisquer sistemas externos, incluindo clientes não-Python. Este ponto final permite a "repetibilidade gerida" em cenários de pontuação e reconversão de lotes.

Importante

Se estiver a utilizar o controlo de acesso baseado em funções (Azure RBAC) para gerir o acesso ao seu oleoduto, desaje em si as permissões para o seu cenário de pipeline (treino ou pontuação).

Para invocar o percurso do oleoduto anterior, precisa de um Azure Ative Directory token de cabeçalho de autenticação. Obter tal token é descrito na referência da classe AzureCliAuthentication e na Autenticação em Azure Machine Learning caderno.

from azureml.pipeline.core import PublishedPipeline
import requests

response = requests.post(published_pipeline1.endpoint,
                         headers=aad_token,
                         json={"ExperimentName": "My_Pipeline",
                               "ParameterAssignments": {"pipeline_arg": 20}})

O json argumento do pedido DOM deve conter, para a ParameterAssignments chave, um dicionário que contenha os parâmetros do gasoduto e os seus valores. Além disso, o json argumento pode conter as seguintes teclas:

Chave Descrição
ExperimentName O nome da experiência associada a este ponto final
Description Texto freeform que descreve o ponto final
Tags Pares de valor-chave freeform que podem ser usados para rotular e anotar pedidos
DataSetDefinitionValueAssignments Dicionário utilizado para alterar conjuntos de dados sem requalificação (ver discussão abaixo)
DataPathAssignments Dicionário utilizado para alterar datapaths sem requalificação (ver discussão abaixo)

Executar um oleoduto publicado usando C

O código que se segue mostra como chamar um gasoduto assíncronamente de C#. O corte parcial do código apenas mostra a estrutura de chamada e não faz parte de uma amostra da Microsoft. Não mostra aulas completas ou manipulação de erros.

[DataContract]
public class SubmitPipelineRunRequest
{
    [DataMember]
    public string ExperimentName { get; set; }

    [DataMember]
    public string Description { get; set; }

    [DataMember(IsRequired = false)]
    public IDictionary<string, string> ParameterAssignments { get; set; }
}

// ... in its own class and method ... 
const string RestEndpoint = "your-pipeline-endpoint";

using (HttpClient client = new HttpClient())
{
    var submitPipelineRunRequest = new SubmitPipelineRunRequest()
    {
        ExperimentName = "YourExperimentName", 
        Description = "Asynchronous C# REST api call", 
        ParameterAssignments = new Dictionary<string, string>
        {
            {
                // Replace with your pipeline parameter keys and values
                "your-pipeline-parameter", "default-value"
            }
        }
    };

    string auth_key = "your-auth-key"; 
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", auth_key);

    // submit the job
    var requestPayload = JsonConvert.SerializeObject(submitPipelineRunRequest);
    var httpContent = new StringContent(requestPayload, Encoding.UTF8, "application/json");
    var submitResponse = await client.PostAsync(RestEndpoint, httpContent).ConfigureAwait(false);
    if (!submitResponse.IsSuccessStatusCode)
    {
        await WriteFailedResponse(submitResponse); // ... method not shown ...
        return;
    }

    var result = await submitResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
    var obj = JObject.Parse(result);
    // ... use `obj` dictionary to access results
}

Executar um oleoduto publicado usando Java

O código que se segue mostra uma chamada para um gasoduto que requer autenticação (ver Configurar autenticação para Azure Machine Learning recursos e fluxos de trabalho). Se o seu oleoduto for implantado publicamente, não precisa das chamadas que authKey produzem. O corte parcial do código não mostra a classe Java e a placa de tratamento de exceções. O código utiliza Optional.flatMap para acorrentar funções que podem devolver um vazio Optional . O uso de flatMap encurtamentos e esclarece o código, mas note que getRequestBody() engole exceções.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Optional;
// JSON library
import com.google.gson.Gson;

String scoringUri = "scoring-endpoint";
String tenantId = "your-tenant-id";
String clientId = "your-client-id";
String clientSecret = "your-client-secret";
String resourceManagerUrl = "https://management.azure.com";
String dataToBeScored = "{ \"ExperimentName\" : \"My_Pipeline\", \"ParameterAssignments\" : { \"pipeline_arg\" : \"20\" }}";

HttpClient client = HttpClient.newBuilder().build();
Gson gson = new Gson();

HttpRequest tokenAuthenticationRequest = tokenAuthenticationRequest(tenantId, clientId, clientSecret, resourceManagerUrl);
Optional<String> authBody = getRequestBody(client, tokenAuthenticationRequest);
Optional<String> authKey = authBody.flatMap(body -> Optional.of(gson.fromJson(body, AuthenticationBody.class).access_token);;
Optional<HttpRequest> scoringRequest = authKey.flatMap(key -> Optional.of(scoringRequest(key, scoringUri, dataToBeScored)));
Optional<String> scoringResult = scoringRequest.flatMap(req -> getRequestBody(client, req));
// ... etc (`scoringResult.orElse()`) ... 

static HttpRequest tokenAuthenticationRequest(String tenantId, String clientId, String clientSecret, String resourceManagerUrl)
{
    String authUrl = String.format("https://login.microsoftonline.com/%s/oauth2/token", tenantId);
    String clientIdParam = String.format("client_id=%s", clientId);
    String resourceParam = String.format("resource=%s", resourceManagerUrl);
    String clientSecretParam = String.format("client_secret=%s", clientSecret);

    String bodyString = String.format("grant_type=client_credentials&%s&%s&%s", clientIdParam, resourceParam, clientSecretParam);

    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(authUrl))
        .POST(HttpRequest.BodyPublishers.ofString(bodyString))
        .build();
    return request;
}

static HttpRequest scoringRequest(String authKey, String scoringUri, String dataToBeScored)
{
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(scoringUri))
        .header("Authorization", String.format("Token %s", authKey))
        .POST(HttpRequest.BodyPublishers.ofString(dataToBeScored))
        .build();
    return request;

}

static Optional<String> getRequestBody(HttpClient client, HttpRequest request) {
    try {
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() != 200) {
            System.out.println(String.format("Unexpected server response %d", response.statusCode()));
            return Optional.empty();
        }
        return Optional.of(response.body());
    }catch(Exception x)
    {
        System.out.println(x.toString());
        return Optional.empty();
    }
}

class AuthenticationBody {
    String access_token;
    String token_type;
    int expires_in;
    String scope;
    String refresh_token;
    String id_token;
    
    AuthenticationBody() {}
}

Alteração de conjuntos de dados e datapats sem reconversão

Pode querer treinar e inferir diferentes conjuntos de dados e datapats. Por exemplo, pode desejar treinar num conjunto de dados mais pequeno, mas inferência no conjunto de dados completo. Troque os conjuntos de dados com a DataSetDefinitionValueAssignments chave no argumento do json pedido. Muda-se os caminhos de dados com DataPathAssignments . A técnica para ambos é semelhante:

  1. No seu script de definição de pipeline, crie um PipelineParameter para o conjunto de dados. Criar um DatasetConsumptionConfig ou DataPath a partir PipelineParameter do:

    tabular_dataset = Dataset.Tabular.from_delimited_files('https://dprepdata.blob.core.windows.net/demo/Titanic.csv')
    tabular_pipeline_param = PipelineParameter(name="tabular_ds_param", default_value=tabular_dataset)
    tabular_ds_consumption = DatasetConsumptionConfig("tabular_dataset", tabular_pipeline_param)
    
  2. No seu script ML, aceda ao conjunto de dados especificado dinamicamente utilizando Run.get_context().input_datasets :

    from azureml.core import Run
    
    input_tabular_ds = Run.get_context().input_datasets['tabular_dataset']
    dataframe = input_tabular_ds.to_pandas_dataframe()
    # ... etc ...
    

    Note que o ML script acede ao valor especificado para o DatasetConsumptionConfig ( ) e não o valor do ( tabular_dataset PipelineParameter tabular_ds_param ).

  3. No seu roteiro de definição de pipeline, desafine o DatasetConsumptionConfig parâmetro PipelineScriptStep para:

    train_step = PythonScriptStep(
        name="train_step",
        script_name="train_with_dataset.py",
        arguments=["--param1", tabular_ds_consumption],
        inputs=[tabular_ds_consumption],
        compute_target=compute_target,
        source_directory=source_directory)
    
    pipeline = Pipeline(workspace=ws, steps=[train_step])
    
  4. Para mudar os conjuntos de dados dinamicamente na sua chamada DERESE inferencionante, DataSetDefinitionValueAssignments utilize:

    tabular_ds1 = Dataset.Tabular.from_delimited_files('path_to_training_dataset')
    tabular_ds2 = Dataset.Tabular.from_delimited_files('path_to_inference_dataset')
    ds1_id = tabular_ds1.id
    d22_id = tabular_ds2.id
    
    response = requests.post(rest_endpoint, 
                             headers=aad_token, 
                             json={
                                "ExperimentName": "MyRestPipeline",
                               "DataSetDefinitionValueAssignments": {
                                    "tabular_ds_param": {
                                        "SavedDataSetReference": {"Id": ds1_id #or ds2_id
                                    }}}})
    

Os cadernos que mostram dataset e PipelineParameter e Apresentando DataPath e PipelineParameter têm exemplos completos desta técnica.

Criar um ponto final de pipeline em versão

Pode criar um Pipeline Endpoint com vários oleodutos publicados por trás. Esta técnica dá-lhe um ponto final de REST fixo à medida que se iteriza e atualiza os seus ML oleodutos.

from azureml.pipeline.core import PipelineEndpoint

published_pipeline = PublishedPipeline.get(workspace=ws, name="My_Published_Pipeline")
pipeline_endpoint = PipelineEndpoint.publish(workspace=ws, name="PipelineEndpointTest",
                                            pipeline=published_pipeline, description="Test description Notebook")

Submeter um trabalho a um ponto final de oleoduto

Pode submeter um trabalho à versão padrão de um ponto final do gasoduto:

pipeline_endpoint_by_name = PipelineEndpoint.get(workspace=ws, name="PipelineEndpointTest")
run_id = pipeline_endpoint_by_name.submit("PipelineEndpointExperiment")
print(run_id)

Também pode submeter um trabalho a uma versão específica:

run_id = pipeline_endpoint_by_name.submit("PipelineEndpointExperiment", pipeline_version="0")
print(run_id)

O mesmo pode ser feito utilizando a API REST:

rest_endpoint = pipeline_endpoint_by_name.endpoint
response = requests.post(rest_endpoint, 
                         headers=aad_token, 
                         json={"ExperimentName": "PipelineEndpointExperiment",
                               "RunSource": "API",
                               "ParameterAssignments": {"1": "united", "2":"city"}})

Use oleodutos publicados no estúdio

Também pode executar um oleoduto publicado a partir do estúdio:

  1. Inscreva-se no Azure Machine Learning estúdio.

  2. Veja o seu espaço de trabalho.

  3. À esquerda, selecione Endpoints.

  4. Por cima, selecione os pontos finais do Pipeline. lista de oleodutos publicados machine learning

  5. Selecione um gasoduto específico para executar, consumir ou rever os resultados de execuções anteriores do ponto final do gasoduto.

<a name="disable-a-published-pipeline">Desativar um oleoduto publicado

Para esconder um oleoduto da sua lista de oleodutos publicados, desativa-o, quer no estúdio, quer no SDK:

# Get the pipeline by using its ID from Azure Machine Learning studio
p = PublishedPipeline.get(ws, id=&quot;068f4885-7088-424b-8ce2-eeb9ba5381a6")
p.disable()

Pode ative-lo novamente com p.enable() . Para mais informações, consulte a referência da classe PublishedPipeline.

Passos seguintes