Otimização paralela de hiperparâmetros (HPO) em Container Runtime para ML

A Snowflake ML Hyperparameter Optimization (HPO) API é uma estrutura independente de modelos que permite o ajuste eficiente e paralelo de hiperparâmetros de modelos. Você pode usar qualquer estrutura ou algoritmo de código aberto. Você também pode usar Snowflake ML APIs.

Hoje, essa API está disponível para uso em um Snowflake Notebook configurado para usar o Container Runtime no Snowpark Container Services (SPCS). Depois que você criar esse notebook, poderá:

  • Treinar um modelo usando qualquer pacote de código aberto e usar esta API para distribuir o processo de ajuste de hiperparâmetros

  • Treinar um modelo usando as APIs de treinamento distribuído do Snowflake ML e dimensione HPO e, ao mesmo tempo, dimensione cada uma das execuções de treinamento

A carga de trabalho do HPO, iniciada no Notebook, é executada dentro do Snowpark Container Services nas instâncias CPU ou GPU e é dimensionada para os núcleos (CPUs ou GPUs) disponíveis em um único nó no pool de computação SPCS.

A HPO API paralelizada oferece os seguintes benefícios:

  • Uma única API que lida automaticamente com todas as complexidades da distribuição do treinamento em vários recursos

  • A capacidade de treinar com praticamente qualquer framework ou algoritmo usando estruturas de código aberto ML ou APIs de modelagem do Snowflake ML

  • Uma seleção de opções de ajuste e amostragem, incluindo algoritmos de pesquisa Bayesiana e aleatória, juntamente com várias funções de amostragem contínuas e não contínuas

  • Forte integração com o restante do Snowflake; por exemplo, ingestão eficiente de dados por meio do Snowflake Datasets ou Dataframes e captura automática da linhagem do ML

Nota

Você pode dimensionar a execução do HPO para usar vários nós no pool de computação SPCS. Para obter mais informações, consulte Executar uma carga de trabalho em um cluster de vários nós.

Otimizar os hiperparâmetros de um modelo

Use a Snowflake ML HPO API para ajustar um modelo. As etapas a seguir ilustram o processo:

  1. Ingerir os dados.

  2. Usar o algoritmo de pesquisa para definir a estratégia usada para otimizar os hiperparâmetros.

  3. Definir como os hiperparâmetros são amostrados.

  4. Configurar o ajustador.

  5. Obter os hiperparâmetros e as métricas de treinamento de cada trabalho de treinamento.

  6. Iniciar o trabalho de treinamento.

  7. Obter os resultados do trabalho de treinamento.

As seções a seguir explicam as etapas anteriores. Para obter um exemplo, consulte Exemplo do Container Runtime HPO.

Ingerir os dados

Use o objeto dataset_map para ingerir os dados na HPO API. O objeto dataset_map é um dicionário que combina o conjunto de dados de treinamento ou teste com o objeto Snowflake DataConnector correspondente. O objeto dataset_map é passado para a função de treinamento. A seguir, um exemplo de um objeto dataset_map:

dataset_map = {
  "x_train": DataConnector.from_dataframe(session.create_dataframe(X_train)),
  "y_train": DataConnector.from_dataframe(
      session.create_dataframe(y_train.to_frame())
  ),
  "x_test": DataConnector.from_dataframe(session.create_dataframe(X_test)),
  "y_test": DataConnector.from_dataframe(
      session.create_dataframe(y_test.to_frame())
  ),
}
Copy

Definir o algoritmo de pesquisa

Defina o algoritmo de pesquisa usado para explorar o espaço do hiperparâmetro. O algoritmo usa os resultados de tentativas anteriores para determinar como configurar os hiperparâmetros. Você pode usar os seguintes algoritmos de pesquisa:

  • Pesquisa de grade

    Explora uma grade para valores de hiperparâmetros que você define. A HPO API avalia todas as combinações possíveis de hiperparâmetros. A seguir, um exemplo de uma grade de hiperparâmetros:

    search_space = {
        "n_estimators": [50, 51],
        "max_depth": [4, 5]),
        "learning_rate": [0.01, 0.3],
    }
    
    Copy

    No exemplo anterior, cada parâmetro tem dois valores possíveis. Há 8 (2 * 2 * 2) combinações possíveis de hiperparâmetros.

  • Otimização bayesiana

    Usa um modelo probabilístico para determinar o próximo conjunto de hiperparâmetros a ser avaliado. O algoritmo usa os resultados de tentativas anteriores para determinar como configurar os hiperparâmetros. Para obter mais informações sobre a otimização bayesiana, consulte Otimização bayesiana.

  • Pesquisa aleatória

    São obtidas amostras aleatórias do espaço de hiperparâmetros. É uma abordagem simples e eficaz que funciona particularmente bem com espaços de pesquisa grandes ou mistos (contínuos ou discretos).

Você pode usar o código a seguir para definir o algoritmo de pesquisa:

from entities import search_algorithm
search_alg = search_algorithm.BayesOpt()
search_alg = search_algorithm.RandomSearch()
search_alg = search_algorithm.GridSearch()
Copy

Definir amostragem de hiperparâmetros

Use as funções do espaço de pesquisa para definir o método de amostragem de hiperparâmetros durante cada tentativa. Use-as para descrever o intervalo e o tipo de valores que os hiperparâmetros podem assumir.

Veja a seguir as funções de amostragem disponíveis:

  • uniform(lower, upper): são obtidas uniformemente amostras de um valor contínuo entre o inferior e o superior. Útil para parâmetros como taxas de desistência ou pontos fortes de regularização.

  • loguniform(lower, upper): são obtidas amostras de um valor no espaço logarítmico, ideal para parâmetros que abrangem várias ordens de magnitude (por exemplo, taxas de aprendizado).

  • randint(lower, upper): são obtidas uniformemente amostras de um número inteiro entre o inferior (inclusivo) e o superior (exclusivo). Adequado para parâmetros discretos, como o número de camadas.

  • choice(options): seleciona aleatoriamente um valor de uma lista fornecida. Geralmente usado para parâmetros categóricos.

A seguir, um exemplo de como você pode definir o espaço de pesquisa com a função uniform:

search_space = {
    "n_estimators": tune.uniform(50, 200),
    "max_depth": tune.uniform(3, 10),
    "learning_rate": tune.uniform(0.01, 0.3),
}
Copy

Configurar o ajustador

Use o objeto TunerConfig para configurar o ajustador. No objeto, você especifica a métrica que está sendo otimizada, o modo de otimização e os outros parâmetros de execução. Veja a seguir as opções de configuração disponíveis:

  • Métrica A métrica de desempenho, como precisão ou perda, que você está otimizando.

  • Modo Determina se o objetivo é maximizar ou minimizar a métrica ("max" ou "min").

  • Algoritmo de pesquisa Especifica a estratégia para explorar o espaço de hiperparâmetros.

  • Número de tentativas Define o número total de configurações de hiperparâmetro a serem avaliadas.

  • Simultaneidade Define quantas tentativas podem ser executadas ao mesmo tempo.

O código de exemplo a seguir usa a biblioteca de otimização bayesiana para maximizar a precisão de um modelo em cinco tentativas.

from snowflake.ml.modeling import tune
tuner_config = tune.TunerConfig(
  metric="accuracy",
  mode="max",
  search_alg=search_algorithm.BayesOpt(
      utility_kwargs={"kind": "ucb", "kappa": 2.5, "xi": 0.0}
  ),
  num_trials=5,
  max_concurrent_trials=1,
)
Copy

Obter os hiperparâmetros e as métricas de treinamento

A Snowflake ML HPO API requer as métricas de treinamento e os hiperparâmetros de cada execução de treinamento para otimizar os hiperparâmetros de forma eficaz. Use o objeto TunerContext para obter os hiperparâmetros e as métricas de treinamento. O exemplo a seguir cria uma função de treinamento para obter os hiperparâmetros e as métricas de treinamento:

def train_func():
  tuner_context = get_tuner_context()
  config = tuner_context.get_hyper_params()
  dm = tuner_context.get_dataset_map()
  ...
  tuner_context.report(metrics={"accuracy": accuracy}, model=model)
Copy

Iniciar o trabalho de treinamento

Use o objeto Tuner para iniciar o trabalho de treinamento. O objeto Tuner usa a função de treinamento, o espaço de pesquisa e a configuração do ajustador como argumentos. A seguir, um exemplo de como iniciar o trabalho de treinamento:

from snowflake.ml.modeling import tune
tuner = tune.Tuner(train_func, search_space, tuner_config)
tuner_results = tuner.run(dataset_map=dataset_map)
Copy

O código anterior distribui a função de treinamento entre os recursos disponíveis. Ele coleta e resume os resultados dos testes e identifica a configuração de melhor desempenho.

Obter os resultados do trabalho de treinamento

Após a conclusão de todas as tentativas, o objeto TunerResults consolida os resultados de cada tentativa. Ele fornece acesso estruturado às métricas de desempenho, à melhor configuração e ao melhor modelo.

Os atributos disponíveis são os seguintes:

  • results: um Pandas DataFrame contendo métricas e configurações para cada tentativa.

  • best_result: uma linha DataFrame que resume a tentativa com o melhor desempenho.

  • best_model: a instância do modelo associada à melhor tentativa, se aplicável.

O código a seguir obtém os resultados, o melhor modelo e o melhor resultado:

print(tuner_results.results)
print(tuner_results.best_model)
print(tuner_results.best_result)
Copy

Referência de API

Ajustador

A seguir, a instrução de importação do módulo Ajustador:

from snowflake.ml.modeling.tune import Tuner
Copy

A classe Ajustador é a principal interface para interagir com a HPO API do Container Runtime. Para executar um trabalho de HPO, use o código a seguir para inicializar um objeto Ajustador e chamar o método de execução com os conjuntos de dados do Snowflake.

class Tuner:
  def __init__(
      self,
      train_func: Callable,
      search_space: SearchSpace,
      tuner_config: TunerConfig,
  )

  def run(
      self, dataset_map: Optional[Dict[str, DataConnector]] = None
  ) -> TunerResults
Copy

SearchSpace

A seguir, a instrução de importação para o espaço de pesquisa:

from entities.search_space import uniform, choice, loguniform, randint
Copy

O código a seguir define as funções do espaço de pesquisa:

def uniform(lower: float, upper: float)
    """
    Sample a float value uniformly between lower and upper.

    Use for parameters where all values in range are equally likely to be optimal.
    Examples: dropout rates (0.1 to 0.5), batch normalization momentum (0.1 to 0.9).
    """


def loguniform(lower: float, upper: float) -> float:
    """
    Sample a float value uniformly in log space between lower and upper.

    Use for parameters spanning several orders of magnitude.
    Examples: learning rates (1e-5 to 1e-1), regularization strengths (1e-4 to 1e-1).
    """


def randint(lower: int, upper: int) -> int:
    """
    Sample an integer value uniformly between lower(inclusive) and upper(exclusive).

    Use for discrete parameters with a range of values.
    Examples: number of layers, number of epochs, number of estimators.
    """



def choice(options: List[Union[float, int, str]]) -> Union[float, int, str]:
    """
    Sample a value uniformly from the given options.

    Use for categorical parameters or discrete options.
    Examples: activation functions ['relu', 'tanh', 'sigmoid']
    """
Copy

TunerConfig

A seguir, a instrução de importação do módulo TunerConfig:

from snowflake.ml.modeling.tune import TunerConfig
Copy

Use o código a seguir para definir a classe de configuração do ajustador:

class TunerConfig:
  """
  Configuration class for the tuning process.

  Attributes:
    metric (str): The name of the metric to optimize. This should correspond
        to a key in the metrics dictionary reported by the training function.

    mode (str): The optimization mode for the metric. Must be either "min"
        for minimization or "max" for maximization.

    search_alg (SearchAlgorithm): The search algorithm to use for
        exploring the hyperparameter space. Defaults to random search.

    num_trials (int): The maximum number of parameter configurations to
        try. Defaults to 5

    max_concurrent_trials (Optional[int]): The maximum number of concurrently running trials per node. If   not specified, it defaults to the total number of nodes in the cluster. This value must be a positive
    integer if provided.


  Example:
      >>> from entities import search_algorithm        >>> from snowflake.ml.modeling.tune import  TunerConfig
      >>> config = TunerConfig(
      ...     metric="accuracy",
      ...     mode="max",
      ...     num_trials=5,
      ...     max_concurrent_trials=1
      ... )
  """
Copy

SearchAlgorithm

A seguir, a instrução de importação do algoritmo de pesquisa:

from entities.search_algorithm import BayesOpt, RandomSearch, GridSearch
Copy

O código a seguir cria um objeto de algoritmo de pesquisa de otimização bayesiana:

@dataclass
class BayesOpt():
    """
    Bayesian Optimization class that encapsulates parameters for the acquisition function.

    This class is designed to facilitate Bayesian optimization by configuring
    the acquisition function through a dictionary of keyword arguments.

    Attributes:
        utility_kwargs (Optional[Dict[str, Any]]):
            A dictionary specifying parameters for the utility (acquisition) function.
            If not provided, it defaults to:
                {
                    'kind': 'ucb',   # Upper Confidence Bound acquisition strategy
                    'kappa': 2.576,  # Exploration parameter for UCB
                    'xi': 0.0      # Exploitation parameter
                }
    """
    utility_kwargs: Optional[Dict[str, Any]] = None
Copy

O código a seguir cria um objeto de algoritmo de pesquisa aleatória:

@dataclass
class RandomSearch():
    The default and most basic way to do hyperparameter search is via random search.

    Attributes:
Seed or NumPy random generator for reproducible results. If set to None (default), the global generator (np.random) is used.
    random_state: Optional[int] = None
Copy

TunerResults

A seguir, a instrução de importação do módulo TunerResults:

from entities.tuner_results import TunerResults
Copy

O código a seguir cria um objeto TunerResults:

@dataclass
class TunerResults:
    results: pd.DataFrame
    best_result: pd.DataFrame
    best_model: Optional[Any]
Copy

get_tuner_context

A seguir, a instrução de importação do módulo get_tuner_context:

from snowflake.ml.modeling.tune import get_tuner_context
Copy

Esse método auxiliar foi projetado para ser chamado dentro da função de treinamento. Ele retorna um objeto TunerContext que encapsula vários campos úteis para a execução do teste, inclusive:

  • Hiperparâmetros selecionados pela estrutura de HPO para o teste atual.

  • O conjunto de dados necessário para o treinamento.

  • Uma função auxiliar para relatar métricas, orientando a estrutura de HPO na sugestão do próximo conjunto de hiperparâmetros

O código a seguir cria um objeto de contexto do ajustador:

class TunerContext:
    """
    A centralized context class for managing trial configuration, reporting, and dataset information.
    """

    def get_hyper_params(self) -> Dict[str, Any]:
        """
        Retrieve the configuration dictionary.

        Returns:
            Dict[str, Any]: The configuration dictionary for the trial.
        """
        return self._hyper_params

    def report(self, metrics: Dict[str, Any], model: Optional[Any] = None) -> None:
    """
    Report metrics and optionally the model if provided.

    This method is used to report the performance metrics of a model and, if provided, the model itself.
    The reported metrics will be used to guide the next set of hyperparameters selection in the
    optimization process.

    Args:
        metrics (Dict[str, Any]): A dictionary containing the performance metrics of the model.
            The keys are metric names, and the values are the corresponding metric values.
        model (Optional[Any], optional): The trained model to be reported. Defaults to None.

    Returns:
        None: This method doesn't return anything.
    """

    def get_dataset_map(self) -> Optional[Dict[str, Type[DataConnector]]]:
        """
        Retrieve the dataset mapping.

        Returns:
            Optional[Dict[str, Type[DataConnector]]]: A mapping of dataset names to DataConnector types, if available.
        """
        return self._dataset_map
Copy

Limitações

A otimização bayesiana requer espaços de pesquisa contínuos e funciona apenas com a função de amostragem uniforme. Ela é incompatível com parâmetros discretos amostrados usando os métodos tune.randint ou tune.choice. Para contornar essa limitação, use tune.uniform e converta o parâmetro dentro da função de treinamento ou mude para um algoritmo de amostragem que lide com espaços discretos e contínuos, como tune.RandomSearch.

Solução de problemas

Mensagem de erro

Causas possíveis

Possíveis soluções

Configuração inválida do espaço de pesquisa: BayesOpt exige que todas as funções de amostragem sejam do tipo ‘Uniforme”.

A otimização bayesiana funciona apenas com amostragem uniforme, não com amostras discretas. (Consulte Limitações acima.)

  • Use o tune.uniform e converta o resultado em sua função de treinamento.

  • Mudar para o algoritmo RandomSearch, que aceita amostras discretas e não discretas.

Recursos insuficientes de CPU. Necessário: 16, Disponível: 8. O número de recursos necessários e disponíveis pode ser diferente.

max_concurrent_trials é definido como um valor maior do que os núcleos disponíveis.

Siga as orientações fornecidas pela mensagem de erro.

Recursos insuficientes de GPU. Necessários: 4, Disponíveis: 2. Pode se referir a CPU ou GPU. O número de recursos necessários e disponíveis pode ser diferente.

max_concurrent_trials é definido como um valor maior do que os núcleos disponíveis.

Siga as orientações fornecidas pela mensagem de erro.