Machine Learning · Insper

Pipelines

Abordagem avançada

Abrir o pipeline, salvá-lo, estendê-lo com passos seus e ajustá-lo pelo nome

O caminho desta parte

  1. Abrir e salvarpassos pelo nome, valores aprendidos, um arquivo
  2. Passos seusFunctionTransformer e uma classe que aprende
  3. Ajustar através delepasso__parametro e set_params
Cada etapa responde a uma pergunta: o que cada passo aprendeu e como entregamos o pipeline? → como acrescentar um passo que o scikit-learn não tem? → como uma busca alcança cada configuração?
Etapa 1 · O que cada passo aprendeu e como entregamos o pipeline?

Um pipeline ajustado é um contêiner que você pode abrir


list(full.named_steps)
full[-1]                   # indexação, como numa lista
full.named_steps['columntransformer'] \
    .named_transformers_['num'][0].statistics_
full[:-1].transform(X_test.head(2)).shape
    

['columntransformer', 'kneighborsregressor']
KNeighborsRegressor(n_neighbors=10)
[-118.5 34.2 29. 2129. 437. 1169. 411. 3.5]
(2, 13)
    
  • O named_transformers_ abre um ramo do ColumnTransformer: as oito medianas de treino que o imputador vai usar (437 quartos).
  • Uma fatia é um pipeline menor: full[:-1] é só o pré-processamento, exatamente o que o modelo recebe.
  • O print(full) dá o diagrama em texto; no Jupyter o pipeline é desenhado como um diagrama clicável.

Um arquivo: pré-processamento + modelo


joblib.dump(full, 'housing-knn.joblib')
loaded = joblib.load('housing-knn.joblib')
np.array_equal(loaded.predict(X_test), full.predict(X_test))   # True
  

3,9 MB em disco

O k-NN (k vizinhos mais próximos, do inglês k-nearest neighbors) guarda os 16.512 distritos de treino para achar vizinhos; a maioria dos modelos guarda bem menos.

previsões idênticas

O pipeline carregado recebe linhas cruas e devolve as mesmas previsões, até o último dígito.

Quem o carrega não tem como esquecer um passo, reajustar um scaler ou reordenar as colunas. Versões e carregamento seguro são o assunto da aula de MLOps (operações de machine learning).
Etapa 2 · Como acrescentar um passo que o scikit-learn não tem?

Seus próprios passos: sem estado


log = FunctionTransformer(np.log1p, feature_names_out='one-to-one')   # nada a aprender
  
assimetria das quatro colunas de contagem
antes3,85; 3,32; 5,17; 3,26
depois de \(\log(1+x)\)−1,09; −1,0; −1,04; −1,06
O fit dele não faz nada, então ele não tem como vazar. O 'one-to-one' mantém o get_feature_names_out funcionando.

Seus próprios passos: um transformador que aprende

Um cortador (clipper): aprende os percentis 1 e 99 de cada coluna nas linhas de treino e puxa todo valor fora deles de volta para o limite.

  • O __init__ só guarda os hiperparâmetros.
  • O fit aprende e devolve self; o estado aprendido termina em _ (lower_, upper_).
  • O transform aplica o que foi aprendido, depois de conferir que houve ajuste.
  • O TransformerMixin dá o fit_transform; o OneToOneFeatureMixin, os nomes das colunas.

aprendido em 16.512 distritos

total_rooms: de 162,2 a 11.248,0

US$ 41.361
erro absoluto médio (MAE, de mean absolute error), um pouco melhor que US$ 41.552

Um transformador que aprende: o código


class Clipper(OneToOneFeatureMixin, TransformerMixin, BaseEstimator):
    def __init__(self, low=1, high=99):
        self.low = low                     # hiperparâmetros: guardados, nada mais
        self.high = high

    def fit(self, X, y=None):
        X = validate_data(self, X)         # guarda os nomes e o número de colunas
        self.lower_ = np.percentile(X, self.low, axis=0)   # o estado aprendido termina em "_"
        self.upper_ = np.percentile(X, self.high, axis=0)
        return self

    def transform(self, X):
        check_is_fitted(self)
        X = validate_data(self, X, reset=False)            # mesmas colunas do fit
        return np.clip(X, self.lower_, self.upper_)
  

Ele entra no ramo numérico, entre o imputador e o scaler: make_pipeline(SimpleImputer(strategy='median'), Clipper(), StandardScaler()).

Para que o cortador serve de verdade

Preveja. Um distrito novo chega com o total_rooms multiplicado por 1.000 (um erro de unidade). O que acontece com a previsão dele, com e sem o cortador?
valor verdadeiroUS$ 65.600
linha corretaUS$ 90.030
erro, sem cortadorUS$ 251.020
erro, com cortadorUS$ 145.690
Valor previsto de um distrito quando o total_rooms é multiplicado de 1 a 1000: sem o cortador sobe até US$ 251 mil; com ele, para em US$ 146 mil

Continua errado: nenhum transformador conserta uma entrada ruim. Mas o estrago tem limite, um limite aprendido só com dados de treino.

Etapa 3 · Como uma busca alcança cada configuração?

Olhando adiante: ajuste através do pipeline


full.get_params()        # 46 parâmetros, p. ex. columntransformer__num__simpleimputer__strategy
full.set_params(kneighborsregressor__n_neighbors=20)
  
  • Toda configuração de todo passo é acessível como passo__parametro.
  • Uma busca que reajusta o pipeline inteiro reaprende o pré-processamento nas linhas de treino de cada tentativa.
Nunca escolha uma configuração olhando a nota de teste: a aula de validação cruzada faz isso de forma honesta, com o GridSearchCV e esses nomes.

Resumo

  1. Um pipeline ajustado é um contêiner: named_steps, indexação, named_transformers_; todo valor aprendido pode ser lido.
  2. Um arquivo guarda a cadeia inteira; o pipeline carregado recebe linhas cruas.
  3. Nada a aprender: FunctionTransformer. Algo a aprender: uma classe cujo fit guarda atributos terminados em _.
  4. Toda configuração é acessível como passo__parametro; uma busca reajusta o pipeline inteiro.
Hábito de projeto: escreva primeiro o esqueleto do pipeline. O objeto que você avalia é o objeto que você entrega.
Lab avançado · cerca de 30 min, em duplas

Abrir, usar, salvar e estender o pipeline, passos 6 a 9

  • Mesmo notebook, mesma sessão: os passos usam preprocess, model, prep, X_train e X_test dos passos 1 a 5.
  • O que o modelo vê (set_output); passageiros novos crus; salvar, carregar, comparar; um transformador seu: o título.
  • Notebook do lab, a partir do passo 6.
  • Simulações: um encoder que recusa categorias novas (passo 7) e quão raro é "Rare"? (passo 9).

Ritmo: passos 6 e 7 e a primeira simulação em 12 min; passo 8 em 5 min; passo 9 e a segunda em 13 min.