Compartir a través de


Entrenamiento de máquinas de incrementación explicable: clasificación (versión preliminar)

En este artículo, aprenderás a entrenar modelos de clasificación mediante máquinas de potenciación explicativa (EBM). Una máquina de incrementación explicable es una técnica de aprendizaje automático que combina la potencia de la potenciación del gradiente con un énfasis en la interpretación del modelo. Crea un conjunto de árboles de decisión, similar al impulso por gradiente, pero con un enfoque único en la generación de modelos fáciles de interpretar por humanos. Los EBM no solo proporcionan predicciones precisas, sino que también ofrecen explicaciones claras e intuitivas para esas predicciones. Son adecuados para las aplicaciones en las que comprender los factores subyacentes que impulsan las decisiones del modelo es esencial, como la atención sanitaria, las finanzas y el cumplimiento normativo.

En SynapseML, puede usar una implementación escalable de un EBM, con tecnología de Apache Spark, para entrenar nuevos modelos. Este artículo le guía por el proceso de aplicación de la escalabilidad e interpretación de los EBM en Microsoft Fabric mediante Apache Spark.

Importante

Esta característica se encuentra en versión preliminar.

En este artículo, recorrerá el proceso de adquisición y preprocesamiento de datos de Azure Open Datasets que registra los viajes de taxis amarillos de NUEVA YORK. A continuación, entrenará un modelo predictivo con el objetivo final de determinar si se producirá o no un viaje determinado.

Ventajas de las máquinas de incrementación explicable

Los EBM ofrecen una combinación única de interpretabilidad y potencia predictiva, lo que les convierte en una opción ideal cuando la transparencia y la comprensión de los modelos de aprendizaje automático son cruciales. Con los EBM, los usuarios pueden obtener información valiosa sobre los factores subyacentes que impulsan las predicciones, lo que les permite comprender por qué un modelo toma decisiones o predicciones específicas, lo que es esencial para crear confianza en los sistemas de inteligencia artificial.

Su capacidad de descubrir relaciones complejas dentro de los datos, al tiempo que proporciona resultados claros e interpretables hace que sean incalculables en campos como finanzas, atención sanitaria y detección de fraudes. En estos campos, la explicación del modelo no solo es deseable, sino que suele ser un requisito normativo. En última instancia, los usuarios que optan por EBM pueden lograr un equilibrio entre el rendimiento y la transparencia del modelo, lo que garantiza que las soluciones de inteligencia artificial sean precisas, fáciles de entender y responsables.

Prerrequisitos

  • Cree un nuevo cuaderno en el área de trabajo seleccionando + y luego Cuaderno.

Instalación de bibliotecas

Para empezar, instale la biblioteca Open Datasets de Azure Machine Learning, que concede acceso al conjunto de datos. Este paso de instalación es esencial para acceder y usar el conjunto de datos de forma eficaz.

%pip install azureml-opendatasets

Importación de MLflow

MLflow permite realizar un seguimiento de los parámetros y resultados del modelo. El siguiente fragmento de código muestra cómo usar MLflow con fines de experimentación y seguimiento. El ebm_classification_nyc_taxi valor es el nombre del experimento donde se registra la información.

import mlflow

# Set up the experiment name
mlflow.set_experiment("ebm_classification_nyc_taxi")

Importar bibliotecas

A continuación, agregue el código siguiente para importar los paquetes esenciales que se usan para el análisis:

# Import necessary packages
import interpret
from pyspark.ml.feature import StringIndexer, VectorAssembler
from pyspark.ml import Pipeline
from pyspark.mllib.evaluation import BinaryClassificationMetrics
from pyspark.sql import SparkSession
from pyspark.sql.functions import *
from pyspark.sql.types import DoubleType
from synapse.ml.ebm import EbmClassification

Estos paquetes importados proporcionan las herramientas y funcionalidades fundamentales necesarias para las tareas de análisis y modelado.

Carga de datos

A continuación, agregue el código siguiente para recuperar los datos de taxis amarillos de Nueva York de Azure Machine Learning Open Datasets y, opcionalmente, reducir el ejemplo del conjunto de datos para acelerar el desarrollo:

# Import NYC Yellow Taxi data from Azure Open Datasets
from azureml.opendatasets import NycTlcYellow

from datetime import datetime
from dateutil import parser

# Define the start and end dates for the dataset
end_date = parser.parse('2018-05-08 00:00:00')
start_date = parser.parse('2018-05-01 00:00:00')

# Load the NYC Yellow Taxi dataset within the specified date range
nyc_tlc = NycTlcYellow(start_date=start_date, end_date=end_date)
nyc_pd = nyc_tlc.to_pandas_dataframe()
nyc_tlc_df = spark.createDataFrame(nyc_pd)

Para facilitar el desarrollo y reducir la sobrecarga computacional, podría submuestrear el conjunto de datos.

# For development convenience, consider down-sampling the dataset
sampled_taxi_df = nyc_tlc_df.sample(True, 0.001, seed=1234)

El muestreo descendente permite una experiencia de desarrollo más rápida y rentable, especialmente cuando se trabaja con grandes conjuntos de datos.

Preparación de los datos

En esta sección, preparará el conjunto de datos filtrando las filas con valores atípicos y variables irrelevantes.

Generación de características

Cree un código para nuevas características derivadas que se espera que mejore el rendimiento del modelo:

taxi_df = sampled_taxi_df.select('totalAmount', 'fareAmount', 'tipAmount', 'paymentType', 'rateCodeId', 'passengerCount',
                                'tripDistance', 'tpepPickupDateTime', 'tpepDropoffDateTime',
                                date_format('tpepPickupDateTime', 'hh').alias('pickupHour'),
                                date_format('tpepPickupDateTime', 'EEEE').alias('weekdayString'),
                                (unix_timestamp(col('tpepDropoffDateTime')) - unix_timestamp(col('tpepPickupDateTime'))).alias('tripTimeSecs'),
                                (when(col('tipAmount') > 0, 1).otherwise(0)).alias('tipped')
                                )\
                        .filter((sampled_taxi_df.passengerCount > 0) & (sampled_taxi_df.passengerCount < 8)
                                & (sampled_taxi_df.tipAmount >= 0) & (sampled_taxi_df.tipAmount <= 25)
                                & (sampled_taxi_df.fareAmount >= 1) & (sampled_taxi_df.fareAmount <= 250)
                                & (sampled_taxi_df.tipAmount < sampled_taxi_df.fareAmount)
                                & (sampled_taxi_df.tripDistance > 0) & (sampled_taxi_df.tripDistance <= 100)
                                & (sampled_taxi_df.rateCodeId <= 5)
                                & (sampled_taxi_df.paymentType.isin({"1", "2"}))
                                )

Ahora que ha creado nuevas variables, agregue código para quitar las columnas de las que se derivaron. El marco de datos ahora está más optimizado para la entrada del modelo. Además, puede generar más características basadas en las columnas recién creadas:

taxi_featurised_df = taxi_df.select('totalAmount', 'fareAmount', 'tipAmount', 'paymentType', 'passengerCount',
                                    'tripDistance', 'weekdayString', 'pickupHour', 'tripTimeSecs', 'tipped',
                                    when((taxi_df.pickupHour <= 6) | (taxi_df.pickupHour >= 20), "Night")
                                    .when((taxi_df.pickupHour >= 7) & (taxi_df.pickupHour <= 10), "AMRush")
                                    .when((taxi_df.pickupHour >= 11) & (taxi_df.pickupHour <= 15), "Afternoon")
                                    .when((taxi_df.pickupHour >= 16) & (taxi_df.pickupHour <= 19), "PMRush")
                                    .otherwise(0).alias('trafficTimeBins'))\
                                    .filter((taxi_df.tripTimeSecs >= 30) & (taxi_df.tripTimeSecs <= 7200))

Estos pasos de preparación de datos garantizan que el conjunto de datos esté refinado y optimizado para los procesos de modelado posteriores.

Codificación de los datos

En el contexto de los EBM de SynapseML, el Estimator espera que los datos de entrada estén en forma de un org.apache.spark.mllib.linalg.Vector, básicamente un vector de Doubles. Por lo tanto, es necesario convertir cualquier variable de categorías (cadena) en representaciones numéricas, y las variables que poseen valores numéricos, pero los tipos de datos no numéricos deben convertirse en tipos de datos numéricos.

Actualmente, los EBM de SynapseML emplean el enfoque de StringIndexer para administrar variables de categorías. Actualmente, los EBM de SynapseML no ofrecen manejo especializado para las características categóricas.

Esta es una serie de pasos para agregar al código para preparar los datos para su uso con EBMs de SynapseML:

# Convert categorical features into numerical representations using StringIndexer
sI1 = StringIndexer(inputCol="trafficTimeBins", outputCol="trafficTimeBinsIndex")
sI2 = StringIndexer(inputCol="weekdayString", outputCol="weekdayIndex")

# Apply the encodings to create a new dataframe
encoded_df = Pipeline(stages=[sI1, sI2]).fit(taxi_featurised_df).transform(taxi_featurised_df)
final_df = encoded_df.select('fareAmount', 'paymentType', 'passengerCount', 'tripDistance',
                             'pickupHour', 'tripTimeSecs', 'trafficTimeBinsIndex', 'weekdayIndex',
                             'tipped')

# Ensure that any string representations of numbers in the data are cast to numeric data types
final_df = final_df.withColumn('paymentType', final_df['paymentType'].cast(DoubleType()))
final_df = final_df.withColumn('pickupHour', final_df['pickupHour'].cast(DoubleType()))

# Define the label column name
labelColumnName = 'tipped'

# Assemble the features into a vector
assembler = VectorAssembler(outputCol='features')
assembler.setInputCols([c for c in final_df.columns if c != labelColumnName])
vectorized_final_df = assembler.transform(final_df)

Estos pasos de preparación de datos son cruciales para alinear el formato de datos con los requisitos de los EBM de SynapseML, lo que garantiza el entrenamiento preciso y eficaz del modelo.

Generación de conjuntos de datos de prueba y entrenamiento

Ahora, agregue código para dividir el conjunto de datos en conjuntos de entrenamiento y pruebas mediante una división sencilla. El 70 % de los datos se asignan para el entrenamiento y el 30 % para probar el modelo:

# Decide on the split between training and test data from the dataframe 
trainingFraction = 0.7
testingFraction = (1-trainingFraction)
seed = 1234

# Split the dataframe into test and training dataframes
train_data_df, test_data_df = vectorized_final_df.randomSplit([trainingFraction, testingFraction], seed=seed)

Esta división de datos nos permite entrenar el modelo en una parte sustancial mientras reserva otra parte para evaluar su rendimiento de forma eficaz.

Entrenamiento del modelo

Ahora, agregue código para entrenar el modelo EBM y, a continuación, evalúe su rendimiento mediante el área bajo la curva Característica operativa del receptor (ROC) (AUROC) como métrica:

# Create an instance of the EBMClassifier
estimator = EbmClassification()
estimator.setLabelCol(labelColumnName)

# Fit the EBM model to the training data
model = estimator.fit(train_data_df)

# Make predictions for tip (1/0 - yes/no) on the test dataset and evaluate using AUROC
predictions = model.transform(test_data_df)
predictionsAndLabels = predictions.select("prediction", labelColumnName)
predictionsAndLabels = predictionsAndLabels.withColumn(labelColumnName, predictionsAndLabels[labelColumnName].cast(DoubleType()))

# Calculate AUROC using BinaryClassificationMetrics
metrics = BinaryClassificationMetrics(predictionsAndLabels.rdd)
print("Area under ROC = %s" % metrics.areaUnderROC)

Este proceso implica entrenar el modelo EBM en el conjunto de datos de entrenamiento y usarlo para realizar predicciones en el conjunto de datos de prueba, seguido de evaluar el rendimiento del modelo mediante AUROC como métrica clave.

Visualización de explicaciones globales

Para visualizar la explicación general del modelo, puede agregar código para obtener el contenedor de visualización y utilizar el método show de la biblioteca interpret. El envoltorio de visualización actúa como un puente para facilitar la experiencia de visualización del modelo. Así es como puede hacerlo:

wrapper = model.getVizWrapper()
explanation = wrapper.explain_global()

A continuación, agregue código para importar la interpret biblioteca y use el show método para mostrar la explicación:

import interpret
interpret.show(explanation)

Captura de pantalla de explicaciones globales.

El término "importancias" representa la contribución absoluta media (puntuación) de cada término (característica o interacción) hacia las predicciones. Estas contribuciones se calculan en promedio sobre el conjunto de datos de entrenamiento, teniendo en cuenta el número de muestras en cada rango y los pesos de muestra (si procede). Los 15 términos más importantes se muestran en la explicación.

Visualización de explicaciones locales

Las explicaciones proporcionadas se encuentran en un nivel global, pero hay escenarios en los que las salidas por característica también son valiosas. Tanto el entrenador como el modelo ofrecen la capacidad de establecer el featurescores, que, cuando se rellena, introduce otra columna con valores vectoriales. Cada vector de esta columna coincide con la longitud de la columna de característica, con cada valor correspondiente a la característica en el mismo índice. Estos valores representan la contribución del valor de cada característica a la salida final del modelo.

A diferencia de las explicaciones globales, actualmente no hay ninguna integración directa con la visualización interpret para salidas por característica. Esto se debe a que las visualizaciones globales se escalan principalmente con el número de características (que suele ser pequeña), mientras que las explicaciones locales se escalan con el número de filas (lo que, si es un dataframe de Spark, puede ser considerable).

Aquí está el código para configurar y usar el featurescores:

prediction2 = model.setFeatureScoresCol("featurescores").transform(train_data_df)

Para fines ilustrativos, vamos a agregar código para imprimir los detalles del primer ejemplo:

# Convert to Pandas for easier inspection
predictions_pandas = prediction2.toPandas()
predictions_list = prediction2.collect()

# Extract the first example from the collected predictions.
first = predictions_list[0]

# Print the lengths of the features and feature scores.
print('Length of the features is', len(first['features']), 'while the feature scores have length', len(first['featurescores']))

# Print the values of the features and feature scores.
print('Features are', first['features'])
print('Feature scores are', first['featurescores'])