Compartilhar via


Exibir código de treinamento para um modelo de ML automatizado

Neste artigo, você aprenderá a ver o código de treinamento gerado de qualquer modelo treinado de machine learning automatizado.

A geração de código para modelos treinados de ML automatizado permite que você veja os detalhes a seguir que o ML automatizado usa para treinar e criar o modelo para uma execução específica.

  • Pré-processamento de dados
  • Escolha do algoritmo
  • Personalização
  • Hiperparâmetros

Você pode selecionar qualquer modelo treinado de ML automatizado, uma execução recomendada ou filho e ver o código de treinamento do Python gerado que criou esse modelo específico.

Com o código de treinamento do modelo gerado, você pode:

  • Descobrir qual processo de definição de recursos e quais hiperparâmetros são usados pelo algoritmo de modelo.
  • Acompanhar/controlar a versão/auditar os modelos treinados. Armazenar o código com controle de versão para acompanhar o código de treinamento específico usado com o modelo a ser implantado em produção.
  • Personalizar o código de treinamento alterando hiperparâmetros ou aplicando suas habilidades/sua experiência de ML e algoritmos e treinar novamente um novo modelo com o código personalizado.

O diagrama a seguir ilustra que você pode gerar o código para experimentos de ML automatizados com todos os tipos de tarefa. Primeiro, selecione um modelo. O modelo que você selecionou será destacado e, em seguida, o Azure Machine Learning copia os arquivos de código usados para criar o modelo e os exibe na pasta compartilhada de notebooks. Daqui em diante, você pode ver e personalizar o código conforme necessário.

Captura de tela que mostra a guia de modelos, além de ter um modelo selecionado, conforme explicado no texto acima.

Pré-requisitos

  • Um Workspace do Azure Machine Learning. Para criar o workspace, confira Criar recursos do workspace.

  • Este artigo pressupõe alguma familiaridade com a configuração de um experimento de machine learning automatizado. Siga o tutorial ou as instruções para ver os principais padrões de design de experimentos de machine learning automatizado.

  • A geração de código de ML automatizado só fica disponível para os experimentos executados em destinos de computação remotos do Azure Machine Learning. Não há suporte para a geração de código em execuções locais.

  • Todas as execuções de ML automatizado disparadas por meio do Estúdio do Azure Machine Learning, do SDK v2 ou da CLI v2 terão a geração de código habilitada.

Obter o código gerado e os artefatos de modelo

Por padrão, cada modelo treinado de ML automatizado gera o respectivo código de treinamento após a conclusão do treinamento. O ML automatizado salva esse código em outputs/generated_code do experimento para esse modelo específico. Você pode visualizá-las na interface do usuário do Estúdio do Azure Machine Learning na guia Saídas + logs do modelo selecionado.

  • script.py Esse é o código de treinamento do modelo que você provavelmente deseja analisar com as etapas do definição de recursos, o algoritmo específico usado e os hiperparâmetros.

  • script_run_notebook.ipynb Notebook com um código clichê para executar o código de treinamento do modelo (script.py) na computação do Azure Machine Learning por meio de classes do SDK v2 do Azure Machine Learning.

Depois que a execução de treinamento de ML automatizada for concluída, você poderá acessar os arquivos script.py e script_run_notebook.ipynb por meio da interface do usuário do Estúdio do Azure Machine Learning.

Para fazer isso, navegue até a guia Modelos da página de execução pai do experimento de ML automatizado. Depois de selecionar um dos modelos treinados, escolha o botão Exibir código gerado. Esse botão redirecionará você para a extensão do portal Notebooks, na qual poderá ver, editar e executar o código gerado para esse modelo selecionado específico.

Exibição da guia de modelos de execução pai do botão do código gerado

Você também pode acessar o código gerado do modelo na parte superior da página da execução filho depois de navegar até a página da execução filho de um modelo específico.

Exibição da página de execução filho do botão do código gerado

Se você estiver usando o Python SDKv2, também poderá baixar o "script.py" e o "script_run_notebook.ipynb" recuperando a melhor execução por meio do MLFlow e baixando os artefatos resultantes.

Limitações

Há um problema conhecido ao selecionar Exibir o código gerado. Essa ação falha ao redirecionar para o portal do Notebooks quando o armazenamento está protegido por uma VNet. Como solução alternativa, o usuário pode baixar manualmente os arquivos script.py e script_run_notebook.ipynb navegando até a guia Saídas + Logs na pasta outputs>generated_code. Esses arquivos podem ser carregados manualmente na pasta notebooks para execução ou edição. Siga este link para saber mais sobre VNets no Azure Machine Learning.

Captura de tela mostrando a guia Saídas e Logs, além da pasta de saídas e código gerado selecionada, conforme explicado no texto acima.

script.py

O arquivo script.py contém a lógica principal necessária para treinar um modelo com os hiperparâmetros usados anteriormente. Embora deva ser executado no contexto de uma execução do script do Azure Machine Learning com algumas modificações, o código de treinamento do modelo também pode ser executado de modo autônomo em um ambiente local próprio.

O script pode, aproximadamente, ser dividido em várias das seguintes partes: carregamento de dados, preparação de dados, definição de recursos de dados, especificação de pré-processador/algoritmo e treinamento.

Carregamento de dados

A função get_training_dataset() carrega o conjunto de dados usado anteriormente. Ela pressupõe que o script seja executado em um script do Azure Machine Learning executado no mesmo workspace do experimento original.

def get_training_dataset(dataset_id):
    from azureml.core.dataset import Dataset
    from azureml.core.run import Run
    
    logger.info("Running get_training_dataset")
    ws = Run.get_context().experiment.workspace
    dataset = Dataset.get_by_id(workspace=ws, id=dataset_id)
    return dataset.to_pandas_dataframe()

Ao executá-lo como parte de uma execução de script, o Run.get_context().experiment.workspace recupera o workspace correto. No entanto, se esse script for executado dentro de um workspace diferente ou executado localmente, você precisará modificar o script para especificar explicitamente o workspace apropriado.

Depois que o workspace for recuperado, o conjunto de dados original será recuperado pela ID. Outro conjunto de dados com exatamente a mesma estrutura também pode ser especificado pela ID ou pelo nome com get_by_id() ou get_by_name(), respectivamente. Encontre a ID posteriormente no script, em uma seção semelhante à do código a seguir.

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--training_dataset_id', type=str, default='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx', help='Default training dataset id is populated from the parent run')
    args = parser.parse_args()
    
    main(args.training_dataset_id)

Você também pode optar por substituir toda essa função por um mecanismo próprio de carregamento de dados. As únicas restrições são que o valor retornado precisa ser um dataframe do Pandas e que os dados precisam ter a mesma forma que no experimento original.

Código de preparação de dados

A função prepare_data() limpa os dados, divide o recurso e as colunas de peso de exemplo e prepara os dados para uso no treinamento. Essa função pode variar dependendo do tipo de conjunto de dados e do tipo de tarefa de experimento: classificação, regressão, previsão de série temporal, imagens ou tarefas de NLP.

O exemplo a seguir mostra que, em geral, o dataframe da etapa de carregamento de dados é transmitido. A coluna de rótulo e os pesos de exemplo, se especificados originalmente, são extraídos, e as linhas que contêm NaN são removidas dos dados de entrada.

def prepare_data(dataframe):
    from azureml.training.tabular.preprocessing import data_cleaning
    
    logger.info("Running prepare_data")
    label_column_name = 'y'
    
    # extract the features, target and sample weight arrays
    y = dataframe[label_column_name].values
    X = dataframe.drop([label_column_name], axis=1)
    sample_weights = None
    X, y, sample_weights = data_cleaning._remove_nan_rows_in_X_y(X, y, sample_weights,
     is_timeseries=False, target_column=label_column_name)
    
    return X, y, sample_weights

Caso deseje fazer alguma preparação de dados adicional, isso pode ser feito nesta etapa adicionando o código de preparação de dados personalizado.

Código de definição de recursos de dados

A função generate_data_transformation_config() especifica a etapa de definição de recursos no pipeline final do Scikit-learn. As definições de recursos do experimento original são reproduzidas aqui, juntamente com os parâmetros.

Por exemplo, uma possível transformação de dados que pode ocorrer nessa função pode ser baseada em insertores como SimpleImputer() e CatImputer() ou transformadores como StringCastTransformer() e LabelEncoderTransformer().

Veja a seguir um transformador do tipo StringCastTransformer() que pode ser usado para transformar um conjunto de colunas. Nesse caso, o conjunto indicado por column_names.

def get_mapper_0(column_names):
    # ... Multiple imports to package dependencies, removed for simplicity ...
    
    definition = gen_features(
        columns=column_names,
        classes=[
            {
                'class': StringCastTransformer,
            },
            {
                'class': CountVectorizer,
                'analyzer': 'word',
                'binary': True,
                'decode_error': 'strict',
                'dtype': numpy.uint8,
                'encoding': 'utf-8',
                'input': 'content',
                'lowercase': True,
                'max_df': 1.0,
                'max_features': None,
                'min_df': 1,
                'ngram_range': (1, 1),
                'preprocessor': None,
                'stop_words': None,
                'strip_accents': None,
                'token_pattern': '(?u)\\b\\w\\w+\\b',
                'tokenizer': wrap_in_lst,
                'vocabulary': None,
            },
        ]
    )
    mapper = DataFrameMapper(features=definition, input_df=True, sparse=True)
    
    return mapper

Se você tiver muitas colunas que precisam ter a mesma definição de recursos/transformação aplicada (por exemplo, 50 colunas em vários grupos de colunas), essas colunas serão tratadas pelo agrupamento com base no tipo.

No exemplo a seguir, observe que cada grupo tem um mapeador exclusivo aplicado. Em seguida, esse mapeador é aplicado a cada uma das colunas desse grupo.

def generate_data_transformation_config():
    from sklearn.pipeline import FeatureUnion
    
    column_group_1 = [['id'], ['ps_reg_01'], ['ps_reg_02'], ['ps_reg_03'], ['ps_car_11_cat'], ['ps_car_12'], ['ps_car_13'], ['ps_car_14'], ['ps_car_15'], ['ps_calc_01'], ['ps_calc_02'], ['ps_calc_03']]
    
    column_group_2 = ['ps_ind_06_bin', 'ps_ind_07_bin', 'ps_ind_08_bin', 'ps_ind_09_bin', 'ps_ind_10_bin', 'ps_ind_11_bin', 'ps_ind_12_bin', 'ps_ind_13_bin', 'ps_ind_16_bin', 'ps_ind_17_bin', 'ps_ind_18_bin', 'ps_car_08_cat', 'ps_calc_15_bin', 'ps_calc_16_bin', 'ps_calc_17_bin', 'ps_calc_18_bin', 'ps_calc_19_bin', 'ps_calc_20_bin']
    
    column_group_3 = ['ps_ind_01', 'ps_ind_02_cat', 'ps_ind_03', 'ps_ind_04_cat', 'ps_ind_05_cat', 'ps_ind_14', 'ps_ind_15', 'ps_car_01_cat', 'ps_car_02_cat', 'ps_car_03_cat', 'ps_car_04_cat', 'ps_car_05_cat', 'ps_car_06_cat', 'ps_car_07_cat', 'ps_car_09_cat', 'ps_car_10_cat', 'ps_car_11', 'ps_calc_04', 'ps_calc_05', 'ps_calc_06', 'ps_calc_07', 'ps_calc_08', 'ps_calc_09', 'ps_calc_10', 'ps_calc_11', 'ps_calc_12', 'ps_calc_13', 'ps_calc_14']
    
    feature_union = FeatureUnion([
        ('mapper_0', get_mapper_0(column_group_1)),
        ('mapper_1', get_mapper_1(column_group_3)),
        ('mapper_2', get_mapper_2(column_group_2)),
    ])
    return feature_union

Essa abordagem permite que você tenha um código mais simplificado, não tendo o bloco de código de um transformador para cada coluna, o que pode ser especialmente complexo mesmo quando você tem dezenas ou centenas de colunas no conjunto de dados.

Com as tarefas de classificação e regressão, [FeatureUnion] é usado para definições de recursos. Para modelos de previsão de série temporal, várias definições de recursos com reconhecimento de série temporal são coletadas em um pipeline do Scikit-learn e encapsuladas no TimeSeriesTransformer. Qualquer definição de recursos fornecida pelo usuário para modelos de previsão de série temporal ocorre antes daquelas fornecidas pelo ML automatizado.

Código de especificação do pré-processador

A função generate_preprocessor_config(), se presente, especifica uma etapa de pré-processamento a ser feita após a definição de recursos no pipeline final do Scikit-learn.

Normalmente, essa etapa de pré-processamento consiste apenas na padronização/na normalização de dados que é feita com sklearn.preprocessing.

O ML automatizado especifica apenas uma etapa de pré-processamento para modelos de classificação e regressão não ensemble.

Este é um exemplo de um código de pré-processador gerado:

def generate_preprocessor_config():
    from sklearn.preprocessing import MaxAbsScaler
    
    preproc = MaxAbsScaler(
        copy=True
    )
    
    return preproc

Código de especificação de algoritmo e hiperparâmetros

Provavelmente, o código de especificação de algoritmo e hiperparâmetros é o que muitos profissionais de ML estão mais interessados.

A função generate_algorithm_config() especifica o algoritmo e os hiperparâmetros reais para treinar o modelo como a última fase do pipeline final do Scikit-learn.

O exemplo a seguir usa um algoritmo XGBoostClassifier com hiperparâmetros específicos.

def generate_algorithm_config():
    from xgboost.sklearn import XGBClassifier
    
    algorithm = XGBClassifier(
        base_score=0.5,
        booster='gbtree',
        colsample_bylevel=1,
        colsample_bynode=1,
        colsample_bytree=1,
        gamma=0,
        learning_rate=0.1,
        max_delta_step=0,
        max_depth=3,
        min_child_weight=1,
        missing=numpy.nan,
        n_estimators=100,
        n_jobs=-1,
        nthread=None,
        objective='binary:logistic',
        random_state=0,
        reg_alpha=0,
        reg_lambda=1,
        scale_pos_weight=1,
        seed=None,
        silent=None,
        subsample=1,
        verbosity=0,
        tree_method='auto',
        verbose=-10
    )
    
    return algorithm

O código gerado na maioria dos casos usa classes e pacotes de software de código aberto. Há instâncias em que classes wrapper intermediárias são usadas para simplificar um código mais complexo. Por exemplo, o classificador XGBoost e outras bibliotecas comumente usadas como o LightGBM ou os algoritmos do Scikit-learn podem ser aplicados.

Como um profissional de ML, você pode personalizar o código de configuração desse algoritmo ajustando os hiperparâmetros conforme necessário de acordo com suas habilidades e a experiência para esse algoritmo e seu problema específico de ML.

Para modelos de ensemble, generate_preprocessor_config_N() (se necessário) generate_algorithm_config_N() e são definidos para cada aprendiz no modelo de ensemble, em que N representa o posicionamento de cada aprendiz na lista do modelo de ensemble. Para modelos de ensemble de pilha, o meta aprendiz generate_algorithm_config_meta() é definido.

Código de treinamento completo

A geração de código emite build_model_pipeline() e train_model() para definir o pipeline do Scikit-learn e para chamar fit() nele, respectivamente.

def build_model_pipeline():
    from sklearn.pipeline import Pipeline
    
    logger.info("Running build_model_pipeline")
    pipeline = Pipeline(
        steps=[
            ('featurization', generate_data_transformation_config()),
            ('preproc', generate_preprocessor_config()),
            ('model', generate_algorithm_config()),
        ]
    )
    
    return pipeline

O pipeline do Scikit-learn inclui a etapa de definição de recursos, um pré-processador (se usado) e o algoritmo ou o modelo.

Para modelos de previsão de série temporal, o pipeline do Scikit-learn é empacotado em um ForecastingPipelineWrapper, que tem uma lógica adicional necessária para lidar corretamente com os dados de série temporal, dependendo do algoritmo aplicado. Para todos os tipos de tarefa, usamos PipelineWithYTransformer nos casos em que a coluna de rótulo precisa ser codificada.

Depois que você obtém o pipeline do Scikit-learn, tudo o que resta chamar é o método fit() para treinar o modelo:

def train_model(X, y, sample_weights):
    
    logger.info("Running train_model")
    model_pipeline = build_model_pipeline()
    
    model = model_pipeline.fit(X, y)
    return model

O valor retornado de train_model() é o modelo ajustado/treinado nos dados de entrada.

O código principal que executa todas as funções anteriores é o seguinte:

def main(training_dataset_id=None):
    from azureml.core.run import Run
    
    # The following code is for when running this code as part of an Azure Machine Learning script run.
    run = Run.get_context()
    setup_instrumentation(run)
    
    df = get_training_dataset(training_dataset_id)
    X, y, sample_weights = prepare_data(df)
    split_ratio = 0.1
    try:
        (X_train, y_train, sample_weights_train), (X_valid, y_valid, sample_weights_valid) = split_dataset(X, y, sample_weights, split_ratio, should_stratify=True)
    except Exception:
        (X_train, y_train, sample_weights_train), (X_valid, y_valid, sample_weights_valid) = split_dataset(X, y, sample_weights, split_ratio, should_stratify=False)

    model = train_model(X_train, y_train, sample_weights_train)
    
    metrics = calculate_metrics(model, X, y, sample_weights, X_test=X_valid, y_test=y_valid)
    
    print(metrics)
    for metric in metrics:
        run.log(metric, metrics[metric])

Depois que você tiver o modelo treinado, use-o para fazer previsões com o método predict(). Se o experimento for para um modelo de série temporal, use o método forecast() para previsões.

y_pred = model.predict(X)

Por fim, o modelo é serializado e salvo como um arquivo .pkl chamado "model.pkl":

    with open('model.pkl', 'wb') as f:
        pickle.dump(model, f)
    run.upload_file('outputs/model.pkl', 'model.pkl')

script_run_notebook.ipynb

O notebook script_run_notebook.ipynb serve como uma forma fácil de executar script.py em uma computação do Azure Machine Learning. Esse notebook é semelhante aos notebooks de exemplo de ML automatizado existentes. No entanto, há algumas diferenças importantes, conforme explicado nas seções a seguir.

Ambiente

Normalmente, o ambiente de treinamento para uma execução de ML automatizado é definido automaticamente pelo SDK. No entanto, durante a execução de um script personalizado como o código gerado, o ML automatizado deixa de gerar o processo, ou seja, o ambiente precisa ser especificado para que o trabalho de comando seja bem-sucedido.

A geração de código reutiliza o ambiente que foi usado no experimento de ML automatizado original, se possível. Isso garante que a execução do script de treinamento não falhe devido a dependências ausentes e tenha um benefício adicional de não precisar de uma recompilação de imagem do Docker, o que economiza tempo e recursos de computação.

Se você fizer alterações em script.py que exigem dependências adicionais ou se quiser usar um ambiente próprio, atualize o ambiente em script_run_notebook.ipynb adequadamente.

Enviar o teste

Como o código gerado não é mais orientado por ML automatizado, em vez de criar um trabalho de AutoML, você precisa criar um Command Job e fornecer o código gerado (script.py) a ele.

O exemplo a seguir contém os parâmetros e as dependências regulares necessários para executar um Trabalho de comando, como computação, ambiente etc.

from azure.ai.ml import command, Input

# To test with new training / validation datasets, replace the default dataset id(s) taken from parent run below
training_dataset_id = '<DATASET_ID>'

dataset_arguments = {'training_dataset_id': training_dataset_id}
command_str = 'python script.py --training_dataset_id ${{inputs.training_dataset_id}}'

command_job = command(
    code=project_folder,
    command=command_str,
    environment='AutoML-Non-Prod-DNN:25',
    inputs=dataset_arguments,
    compute='automl-e2e-cl2',
    experiment_name='build_70775722_9249eda8'
)
 
returned_job = ml_client.create_or_update(command_job)
print(returned_job.studio_url) # link to naviagate to submitted run in Azure Machine Learning studio

Próximas etapas