Armazenamento de modelos personalizados no registro do modelo Snowflake

O registro do modelo Snowflake permite que você registre (como log) modelos e os utilize para inferência dentro do Snowflake. O registro suporta vários tipos de modelos:

  • Snowpark ML Modeling

  • scikit-learn

  • XGBoost

  • LightGBM

  • CatBoost

  • PyTorch

  • TensorFlow

  • MLFlow PyFunc

  • Transformador de sentença

  • Pipeline do Hugging Face

A API do modelo do registro também permite que você registre outros tipos de modelos, incluindo aqueles treinados usando ferramentas externas ou obtidos de repositórios de código aberto, desde que sejam serializáveis e derivados da classe snowflake.ml.model.custom_model.CustomModel.

Nota

Este guia fornece um exemplo de registro de um modelo personalizado.

Veja também esta postagem do blog e os guias de início rápido que o acompanham para obter informações sobre como importar modelos do AWS SageMaker ou do Azure ML.

Este tópico explica como criar modelos, registrá-los no Registro do modelo Snowflake e implantá-los. Um caso de uso comum para esse recurso é definir pipelines de várias classes, como alguns transformadores ou imputadores seguidos por um preditor ou classificador. Nesses casos, a própria classe do modelo personalizado é relativamente simples, chamando essas classes em sequência e passando o resultado de uma como entrada para a próxima.

Definição do contexto do modelo

Os modelos geralmente exigem um ou mais arquivos estáticos, como arquivos de configuração ou pesos de modelo, além do código. Os modelos personalizados devem fornecer informações sobre todos esses artefatos para que o registro saiba como compactá-los junto com o modelo. Os artefatos podem ser declarados usando a classe ModelContext da seguinte forma.

from snowflake.ml.model import custom_model

mc = custom_model.ModelContext(
    artifacts={
        'config': 'local_model_dir/config.json'
    },
)
Copy

Os caminhos para arquivos de artefatos no contexto do modelo são relativos ao diretório atual no ambiente do qual o modelo é registrado. O Registro do modelo Snowflake usa essas informações para garantir que todo o código e dados necessários sejam implantados no warehouse onde seu modelo será executado. No tempo de execução, o modelo pode encontrar esses artefatos chamando self.context.path('config') (o valor 'config' é o mesmo que a chave no dicionário passada para ModelContext).

Além de arquivos estáticos, um modelo pode compor outros modelos ou pipelines de um tipo suportado (por exemplo, um snowflake.ml.modeling.pipeline ou um modelo scikit-learn). O registro já sabe como registrar em log esses tipos de objetos, então você pode passar os objetos Python diretamente no contexto do modelo usando model_refs, como mostrado aqui. Você não precisa colocar esses objetos em um pacote por conta própria. Isso pode ser útil para agregação bootstrap (bagging) ou para pré-processamento ou pós-processamento.

Nota

model1 e model2 são objetos de qualquer tipo de modelo suportado nativamente pelo registro. feature_preproc é um objeto snowflake.ml.modeling.pipeline.

mc = custom_model.ModelContext(
    artifacts={
        'config': 'local_model_dir/config.json'
    },
    models={
        'm1': model1,
        'm2': model2,
        'feature_preproc': preproc
    }
)
Copy

O registro do modelo serializa essas referências de modelo ao registrar o modelo e as reidrata em tempo de execução. Seu modelo pode recuperar referências a esses modelos subordinados usando, por exemplo, self.context.model_ref('m1'). Se o modelo expuser um método predict, seu código poderá chamá-lo diretamente da referência do modelo recuperado, por exemplo com self.context.model_ref('m1').predict().

Em resumo, o contexto de um modelo personalizado declara que os objetos Python serão serializados junto com os artefatos do modelo, que são arquivos locais usados pelo modelo e que devem ser armazenados no Snowflake junto com o código. Sua classe de modelo usa o contexto para localizar o código e os artefatos; isso funciona quer seu modelo esteja sendo executado localmente ou no Snowflake.

Como escrever a classe de modelo personalizado

Para informar ao registro do modelo como registrar e implantar seu modelo personalizado, herde de snowflake.ml.model.custom_model.CustomModel.

Os modelos podem expor vários métodos de inferência (por exemplo, os modelos scikit-learn expõem métodos predict e predict_proba). Para declarar funções de inferência em seu modelo personalizado, defina-as como métodos públicos de sua subclasse e decore-as com @custom_model.inference_api. Este decorador indica que um método faz parte da API pública do modelo, permitindo que seja chamado de Python ou SQL através do registro do modelo. Métodos decorados com inference_api deve aceitar e devolver um DataFrame pandas. Qualquer número de métodos pode ser decorado com inference_api.

Nota

A exigência para a API pública aceitar e devolver um DataFrame pandas é a mesma que para UDFs vetorizadas. Assim como com UDFs vetorizadas, essas APIs de inferência podem ser chamadas do Python passando por um DataFrame Snowpark também

Uma classe de modelo personalizado de esqueleto com uma API pública é mostrada abaixo.

Observe o uso de context.path para abrir o arquivo de polarização e self.context.model_ref para obter referências às classes de modelos subordinadas para que seus métodos predict possam ser chamados.

from snowflake.ml.model import custom_model
import pandas as pd

class ExamplePipelineModel(custom_model.CustomModel):

    @custom_model.inference_api
    def predict(self, input: pd.DataFrame) -> pd.DataFrame:
        ...
        return pd.DataFrame(...)
Copy

Juntando todas as peças, o que se segue é um modelo personalizado totalmente funcional.

class ExamplePipelineModel(custom_model.CustomModel):
    def __init__(self, context: custom_model.ModelContext) -> None:
        super().__init__(context)
        v = open(context.path('config')).read()
        self.bias = json.loads(v)['bias']

    @custom_model.inference_api
    def predict(self, input: pd.DataFrame) -> pd.DataFrame:
        features = self.context.model_ref('feature_preproc').transform(input)
        model_output = self.context.model_ref('m2').predict(
            self.context.model_ref('m1').predict(features)
        )
        return pd.DataFrame({
            'output': model_output + self.bias})
Copy

Uso do modelo personalizado

É possível testar seu novo modelo personalizado (pipeline) executando-o localmente da seguinte forma:

my_model_pipeline = ExamplePipelineModel(mc)
output_df = my_model_pipeline.predict(input_df)
Copy

Ou registre-o no registro e implante-o no Snowflake. Conforme mostrado no próximo exemplo de código, forneça conda_dependencies (ou pip_requirements) para especificar as bibliotecas que a classe de modelo precisa.

Forneça sample_input_data (um DataFrame pandas) para inferir a assinatura de entrada para o modelo. Como alternativa, forneça uma assinatura do modelo.

reg = Registry(session=sp_session, database_name="ML", schema_name="REGISTRY")
mv = reg.log_model(my_model_pipeline,
            model_name="my_custom_model_pipeline",
            version_name="v1",
            conda_dependencies=["scikit-learn"],
            comment="My Custom ML Model Pipeline",
            sample_input_data=train_features)
output_df = mv.run(input_df)
Copy

Inferência de função de tabela

A partir do Snowpark ML 1.5.4, é possível registrar modelos com métodos de inferência que retornam várias colunas. Para fazer isso, registre seu modelo com a opção {"function_type": "TABLE_FUNCTION"} e use o decorador @inference_api como acima. No exemplo a seguir, o método decorado retorna um DataFrame pandas que inclui duas colunas de saída.

class ExampleTableFunctionModel(custom_model.CustomModel):

  @custom_model.inference_api
  def predict(self, input: pd.DataFrame) -> pd.DataFrame:
      output_df = pandas.DataFrame([{"OUTPUT1": input["INPUT1"] + 1, input["INPUT2"] + 1}])
      return output_df

my_model = ExampleTableFunctionModel()
reg = Registry(session=sp_session, database_name="ML", schema_name="REGISTRY")
mv = reg.log_model(my_model,
            model_name="my_custom_table_function_model",
            version_name="v1",
            options={"function_type": "TABLE_FUNCTION"},
            sample_input_data=train_features
            )
output_df = mv.run(input_df)
Copy

Se o modelo incluir vários métodos de inferência, use a opção method_options para registrar o modelo, indicando quais são FUNCTION e quais são TABLE_FUNCTION:

reg = Registry(session=sp_session, database_name="ML", schema_name="REGISTRY")
mv = reg.log_model(my_model,
            model_name="my_custom_table_function_model",
            version_name="v1",
            options={
              "method_options": {                                 ###
                "METHOD1": {"function_type": "TABLE_FUNCTION"},   ###
                "METHOD2": {"function_type": "FUNCTION"}          ###
              }
            },
            sample_input_data=train_features
            )
Copy

O método de inferência da função de tabela do modelo registrado também pode ser invocado via SQL da seguinte forma:

SELECT OUTPUT1, OUTPUT2
  FROM input_table,
      table(
          MY_MODEL!PREDICT(input_table.INPUT1, input_table.INPUT2)
      )
Copy

Para saber mais sobre métodos de inferência de modelo particionados, em que o método de inferência usa uma partição de dados como entrada e gera várias linhas e colunas, consulte Modelos personalizados particionados.