Lea la Parte II de esta historia aquí.

En Nubank, dependemos en gran medida del aprendizaje automático para tomar decisiones escalables basadas en datos. Si bien existen muchas otras bibliotecas de ML (usamos ampliamente Xgboost, LGBM y ScikitLearn por ejemplo), sentimos la necesidad de una abstracción de nivel superior que nos ayudara a aplicar estas bibliotecas más fácilmente a los problemas que enfrentamos. Fklearn empaqueta eficientemente estas bibliotecas en un formato que hace que su uso en producción sea más efectivo.

Actualmente, Fklearn impulsa un amplio conjunto de modelos de aprendizaje automático en Nubank, resolviendo problemas que van desde la calificación crediticia hasta respuestas automatizadas por chat de atención al cliente. Lo construimos con los siguientes objetivos en mente:

  1. La validación debe reflejar situaciones de la vida real
  2. Los modelos de producción deben coincidir con los modelos validados
  3. Los modelos deberían estar listos para producción con unos pocos pasos adicionales
  4. La reproducibilidad y el análisis en profundidad de los resultados del modelo deberían ser fáciles de lograr

Desde el principio, decidimos que la programación funcional sería un poderoso aliado para intentar lograr estos objetivos.

F es de Funcional

Aquí en Nubank, somos grandes admiradores de la programación funcional, y eso no se limita al capítulo de Ingeniería. Pero, ¿cómo ayuda la programación funcional a los científicos de datos?

El aprendizaje automático se realiza frecuentemente mediante el uso de código Python orientado a objetos, y esa es la forma en que solíamos hacerlo también en Nubank. En aquel entonces, el proceso de crear modelos de aprendizaje automático y ponerlos en producción era tedioso y, a menudo, lleno de errores. Implementaríamos un modelo solo para descubrir que las predicciones hechas en producción no coincidían con las observadas durante la validación. Es más, la validación a menudo era imposible de reproducir, y con frecuencia se realizaba en Jupyter Notebooks con estado.

La programación funcional ayuda a solucionar estos problemas al:

  • facilitar la creación de canales donde las transformaciones de datos que ocurren durante el entrenamiento coincidan con los modelos en producción;
  • permitir una iteración más segura en entornos interactivos (por ejemplo, Jupyter Notebooks), prevenir errores causados por código con estado y hacer que la investigación sea más reproducible;
  • permitiéndonos escribir código de validación, ajuste y selección de características muy genérico que funciona en todos los tipos de modelos y aplicaciones, lo que nos hace más eficientes en general.

Veamos un ejemplo para ver cómo la programación funcional hace esto en la práctica. Digamos que estamos tratando de predecir cuánto gastará alguien en su tarjeta de crédito en función de dos variables: ingresos mensuales y monto de la factura anterior. Como el resultado de este modelo se utilizará para la toma de decisiones sensibles, nos gustaría asegurarnos de que sea robusto frente a valores atípicos en las variables de entrada, razón por la cual decidimos:

  1. Limitar los ingresos mensuales a 50,000, ya que los ingresos se autoreportan y a veces son exagerados.
  2. Limitar el rango de salida del modelo al intervalo [0, 20,000].

Y luego usar un modelo de regresión lineal simple. Así es como se ve el código:

from fklearn.training.pipeline import build_pipeline
from fklearn.training.regression import linear_regression_learner
from fklearn.training.transformation import capper, floorer, prediction_ranger

def fit(train_data):
    capper_fn = capper(columns_to_cap=["income"], precomputed_caps={"income": 50,000})
    regression_fn = linear_regression_learner(features=["income", "bill_amount"], target="spend")
    ranger_fn = prediction_ranger(prediction_min=0.0,   prediction_max=20000.0)
    
    learner = build_pipeline(capper_fn, regression_fn, ranger_fn)
    predict_fn, training_predictions, logs = learner(train_data)
    
    return predict_fn, logs

¡No se alarme! Revisaremos el código paso a paso y explicaremos algunos conceptos importantes de fklearn.

Funciones de aprendizaje

Mientras que en scikit-learn, la abstracción principal de un modelo es una clase con métodos de ajuste y transformación; en fklearn, usamos lo que llamamos función de aprendizaje. Una función de aprendizaje toma algunos datos de entrenamiento (más otros parámetros), aprende algo de ellos y devuelve tres cosas: una función de predicción, losdatos de entrenamiento transformados y un registro. Las primeras tres líneas de nuestro ejemplo inicializan tres funciones de aprendizaje: capper, linear_regression_learner y prediction_ranger.

Para ilustrarlo mejor, aquí hay una definición simplificada de linear_regression_learner:

from typing import Any, Dict, List
from sklearn.linear_model import LinearRegression
from toolz import curry
import pandas as pd
 
@curry
def linear_regression_learner(df: pd.DataFrame,
                              features: List[str],
                              target: str,
                              params: Dict[str, Any] = None) -> LearnerReturnType:
 
   # initialize and fit the linear regression
   reg = LinearRegression(**params) 
   reg.fit(df[features].values, df[target].values)
 
   # define the prediction function
   def p(new_df: pd.DataFrame) -> pd.DataFrame:
       # note that `reg` here refers to the linear regression fit above, via the functions closure.
       return new_df.assign(prediction=reg.predict(new_df[features].values))
 
   # the log can contain arbitrary information that helps inspect or debug the model
   log = {'linear_regression_learner': {
       'features': features,
       'target': target,
       'parameters': params,
       'training_samples': len(df),
       'feature_importance': dict(zip(features, reg.coef_.flatten()))
   }
 
   return p, p(df), log

¡Observe el uso de sugerencias! Ayudan a que la programación funcional en Python sea menos complicada, junto con la inmensamente útil biblioteca toolz.

Como mencionamos, una función de aprendizaje devuelve tres cosas (una función, un Marco de Datos, y un diccionario), como lo describe la definición LearnerReturnType:

from typing import Any, Callable, Dict, Tuple
import pandas as pd

LearnerReturnType = Tuple[PredictFnType, pd.DataFrame, LearnerLogType]
PredictFnType = Callable[[pd.DataFrame], pd.DataFrame]
LearnerLogType = Dict[str, Any]
  • La función de predicción siempre tiene la misma firma: toma un Marco de Datos y devuelve un Marco de Datos (usamos Pandas). Debería poder aceptar cualquier Marco de Datos nuevo (siempre que contenga las columnas requeridas) y transformarlo (es equivalente al método de transformación de un objeto scikit-learn). En este caso, la función de predicción simplemente crea una nueva columna con las predicciones del modelo de regresión lineal que se entrenó.
  • Los datos de entrenamiento transformados suelen ser solo la función de predicción aplicada a los datos de entrenamiento. Es útil cuando desea realizar predicciones sobre su conjunto de entrenamiento o para crear canales, como veremos más adelante.
  • El registro es un diccionario y puede incluir cualquier información que sea relevante para inspeccionar o depurar al aprendizaje (por ejemplo, qué funciones se utilizaron, cuántas muestras había en el conjunto de entrenamiento, importancia de las funciones o coeficientes).

Las funciones de aprendizaje muestran algunas propiedades de programación funcional comunes:

  • Son funciones puras, lo que significa que siempre devuelven el mismo resultado dada la misma entrada y no tienen efectos secundarios. En la práctica, esto significa que puedes llamar al aprendizaje tantas veces como quieras sin preocuparte por obtener resultados inconsistentes. Este no es siempre el caso cuando se llama a fit en un objeto scikit-learn, por ejemplo, ya que los objetos pueden mutar.
  • Son funciones de orden superior, ya que devuelven otra función (la función de predicción). Dado que la función de predicción se define dentro del propio aprendizaje, puede acceder a variables en el alcance de la función del aprendizaje a través de su closure.
  • Al tener firmas coherentes, las funciones de aprendizaje (y las funciones de predicción) se pueden componer. Significa que construir canales enteros a partir de ellos es sencillo, como veremos pronto.
  • Son currables, lo que significa que puede inicializarlos en pasos, pasando solo unos pocos argumentos a la vez (esto es lo que realmente sucede en las primeras tres líneas de nuestro ejemplo). Esto será útil al definir canalizaciones y aplicar un modelo único a diferentes conjuntos de datos mientras se obtienen resultados consistentes.

Puede que le lleve algún tiempo comprender todo esto, pero no se preocupe, no necesita ser un experto en programación funcional para utilizar fklearn de forma eficaz. La clave es comprender que los modelos (y otras transformaciones de datos) se pueden definir como funciones que siguen la abstracción del aprendizaje.

Descubre las oportunidades

Canales

Sin embargo, los modelos de aprendizaje automático rara vez existen por sí solos. Al centrarse únicamente en el modelo, los científicos de datos tienden a olvidar las transformaciones que atraviesan los datos antes y después de la parte de ML. Estas transformaciones a menudo deben ser exactamente las mismas cuando se entrenan e implementan modelos, y los científicos de datos pueden intentar recrear manualmente sus pasos de pre y posprocesamiento de entrenamiento en producción, lo que conduce a una duplicación de código que es difícil de mantener.

Las funciones del aprendizaje son componibles, lo que significa que dos o más alumnos combinados pueden verse como un aprendizaje nuevo y más complejo. Esto significa que no importa cuántos pasos tenga en su proceso, su modelo final se comportará igual que uno solo, y hacer predicciones es tan simple como llamar a la función de predicción final sobre nuevos datos. Tener todos los pasos de su proceso de modelado contenidos en una única función pura también ayuda con la validación y el ajuste, ya que podemos pasarlo a otras funciones sin temor a efectos secundarios.

En nuestro ejemplo, nuestra canalización consta de tres pasos: limitar la variable de ingresos, ejecutar la regresión y luego restringir el resultado de la regresión al rango [0, 20,000]. Después de inicializar a cada aprendizaje, creamos la canalización y la aplicamos al conjunto de capacitación utilizando estas dos líneas de código:

  ...
  learner = build_pipeline(capper_fn, regression_fn, ranger_fn)
  predict_fn, training_predictions, logs = learner(train_data)
  ...

La variable de aprendizaje ahora contiene la canalización resultante de la composición de las tres funciones de aprendizaje y se aplica a los datos de entrenamiento para producir la función de predicción final. Esta función aplicará todos los pasos equivalentes en la canalización a los datos de prueba, como lo ilustra la siguiente imagen:

Ejemplo de cómo los datos fluyen a través de una canalización durante el entrenamiento y a través de una función de predicción al realizar predicciones. La función de predicción en sí es devuelta por la canalización; es la composición de las tres funciones de predicción generadas por cada alumno cuando se llamó por primera vez a la canalización con los datos de entrenamiento. Los registros son una combinación de los registros provenientes de todas las funciones del alumno en proceso.

¿Qué sigue?

Hemos visto cómo los modelos y los pasos de transformación de datos se pueden escribir como funciones de aprendizaje y cómo los canales funcionales en fklearn nos ayudan a garantizar que las transformaciones realizadas durante el entrenamiento y la validación coincidan con las realizadas en producción.

En la Parte II de esta publicación de blog, hablamos sobre la validación y el análisis de modelos y las herramientas que proporciona fklearn para que esos pasos sean más efectivos.

Mientras tanto, ¡le invitamos a probar fklearn por si mismo! No esperamos que fklearn reemplace los estándares actuales en ML, pero esperamos que inicie conversaciones interesantes sobre los beneficios de la programación funcional para el aprendizaje automático.

Descubre las oportunidades