ML | Manejo de datos desequilibrados con SMOTE y Near Miss Algorithm en Python

En Machine Learning y Data Science, a menudo nos encontramos con un término llamado Distribución de datos desequilibrada , que generalmente ocurre cuando las observaciones en una de las clases son mucho más altas o más bajas que las otras clases. Como los algoritmos de Machine Learning tienden a aumentar la precisión al reducir el error, no consideran la distribución de clases. Este problema prevalece en ejemplos como Detección de fraude, Detección de anomalías , Reconocimiento facial , etc.

Las técnicas estándar de ML, como el árbol de decisión y la regresión logística, tienen un sesgo hacia la clase mayoritaria y tienden a ignorar la clase minoritaria. Tienden solo a predecir la clase mayoritaria, por lo tanto, tienen una clasificación errónea importante de la clase minoritaria en comparación con la clase mayoritaria. En palabras más técnicas, si tenemos una distribución de datos desequilibrada en nuestro conjunto de datos, nuestro modelo se vuelve más propenso al caso en que la clase minoritaria tiene un recuerdo insignificante o muy bajo .

Técnicas de manejo de datos desequilibrados: existen principalmente 2 algoritmos que se utilizan ampliamente para manejar la distribución de clases desequilibrada.

  1. SMOTE
  2. Algoritmo de error cercano

SMOTE (técnica de sobremuestreo de minorías sintéticas) – Sobremuestreo

SMOTE (técnica de sobremuestreo de minorías sintéticas) es uno de los métodos de sobremuestreo más utilizados para resolver el problema del desequilibrio.
Su objetivo es equilibrar la distribución de clases aumentando aleatoriamente los ejemplos de clases minoritarias al replicarlos.
SMOTE sintetiza nuevas instancias minoritarias entre instancias minoritarias existentes. Genera los registros de entrenamiento virtual por interpolación lineal para la clase minoritaria. Estos registros de entrenamiento sintéticos se generan seleccionando aleatoriamente uno o más de los k-vecinos más cercanos para cada ejemplo en la clase minoritaria. Después del proceso de sobremuestreo, los datos se reconstruyen y se pueden aplicar varios modelos de clasificación para los datos procesados.
¡Más información profunda sobre cómo funciona el algoritmo SMOTE!

  • Paso 1: Establecer el conjunto de clases minoritarias A , para cada $x \in A$, los k vecinos más cercanos de x se obtienen calculando la distancia euclidiana entre x y cualquier otra muestra en el conjunto A .
  • Paso 2: La frecuencia de muestreo N se establece de acuerdo con la proporción desequilibrada. Para cada $x \in A$, se seleccionan aleatoriamente N ejemplos (es decir, x1, x2, …xn) de sus k vecinos más cercanos y construyen el conjunto $A_1$.
  • Paso 3: Para cada ejemplo $x_k \in A_1$(k=1, 2, 3…N), se utiliza la siguiente fórmula para generar un nuevo ejemplo:
    $x' = x + rand(0, 1) * \mid x - x_k \mid$
    en el que rand(0, 1) representa el número aleatorio entre 0 y 1.
  • Algoritmo NearMiss – Submuestreo

    NearMiss es una técnica de submuestreo. Su objetivo es equilibrar la distribución de clases eliminando aleatoriamente los ejemplos de clases mayoritarias. Cuando las instancias de dos clases diferentes están muy cerca una de la otra, eliminamos las instancias de la clase mayoritaria para aumentar los espacios entre las dos clases. Esto ayuda en el proceso de clasificación.
    Para evitar el problema de la pérdida de información en la mayoría de las técnicas de submuestreo, se utilizan ampliamente los métodos de vecinos cercanos .
    La intuición básica sobre el funcionamiento de los métodos del vecino cercano es la siguiente:

  • Paso 1: el método primero encuentra las distancias entre todas las instancias de la clase mayoritaria y las instancias de la clase minoritaria. Aquí, la clase mayoritaria debe submuestrearse.
  • Paso 2: Luego, se seleccionan n instancias de la clase mayoritaria que tienen las distancias más pequeñas a las de la clase minoritaria.
  • Paso 3: si hay k instancias en la clase minoritaria, el método más cercano dará como resultado k*n instancias de la clase mayoritaria.
  • Para encontrar n instancias más cercanas en la clase mayoritaria, existen varias variaciones de la aplicación del algoritmo NearMiss:

    1. NearMiss – Versión 1: Selecciona muestras de la clase mayoritaria para las cuales las distancias promedio a las k instancias más cercanas de la clase minoritaria es menor.
    2. NearMiss – Versión 2: Selecciona muestras de la clase mayoritaria para las cuales la distancia promedio a las k instancias más lejanas de la clase minoritaria es menor.
    3. NearMiss – Versión 3: Funciona en 2 pasos. En primer lugar, para cada instancia de clase minoritaria, se almacenarán sus M vecinos más cercanos . Luego, finalmente, se seleccionan las instancias de clase mayoritaria para las cuales la distancia promedio a los N vecinos más cercanos es la mayor.

    Este artículo ayuda a una mejor comprensión y práctica sobre cómo elegir mejor entre diferentes técnicas de manejo de datos desequilibrados.

    Cargar bibliotecas y archivo de datos

    El conjunto de datos consiste en transacciones realizadas con tarjetas de crédito. Este conjunto de datos tiene 492 transacciones fraudulentas de 284 807 transacciones . Eso lo hace muy desequilibrado, la clase positiva (fraudes) representa el 0,172% de todas las transacciones.
    El conjunto de datos se puede descargar desde aquí .

    # import necessary modules 
    import pandas  as pd
    import matplotlib.pyplot as plt
    import numpy as np
    from sklearn.linear_model import LogisticRegression
    from sklearn.preprocessing import StandardScaler
    from sklearn.metrics import confusion_matrix, classification_report
      
    # load the data set
    data = pd.read_csv('creditcard.csv')
      
    # print info about columns in the dataframe
    print(data.info())
    

    Producción:

    RangeIndex: 284807 entries, 0 to 284806
    Data columns (total 31 columns):
    Time      284807 non-null float64
    V1        284807 non-null float64
    V2        284807 non-null float64
    V3        284807 non-null float64
    V4        284807 non-null float64
    V5        284807 non-null float64
    V6        284807 non-null float64
    V7        284807 non-null float64
    V8        284807 non-null float64
    V9        284807 non-null float64
    V10       284807 non-null float64
    V11       284807 non-null float64
    V12       284807 non-null float64
    V13       284807 non-null float64
    V14       284807 non-null float64
    V15       284807 non-null float64
    V16       284807 non-null float64
    V17       284807 non-null float64
    V18       284807 non-null float64
    V19       284807 non-null float64
    V20       284807 non-null float64
    V21       284807 non-null float64
    V22       284807 non-null float64
    V23       284807 non-null float64
    V24       284807 non-null float64
    V25       284807 non-null float64
    V26       284807 non-null float64
    V27       284807 non-null float64
    V28       284807 non-null float64
    Amount    284807 non-null float64
    Class     284807 non-null int64
    
    # normalise the amount column
    data['normAmount'] = StandardScaler().fit_transform(np.array(data['Amount']).reshape(-1, 1))
      
    # drop Time and Amount columns as they are not relevant for prediction purpose 
    data = data.drop(['Time', 'Amount'], axis = 1)
      
    # as you can see there are 492 fraud transactions.
    data['Class'].value_counts()
    

    Producción:

           0    284315
           1       492
    

    Dividir los datos en conjuntos de prueba y entrenamiento

    from sklearn.model_selection import train_test_split
      
    # split into 70:30 ration
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 0)
      
    # describes info about train and test set
    print("Number transactions X_train dataset: ", X_train.shape)
    print("Number transactions y_train dataset: ", y_train.shape)
    print("Number transactions X_test dataset: ", X_test.shape)
    print("Number transactions y_test dataset: ", y_test.shape)
    

    Producción:

          Number transactions X_train dataset:  (199364, 29)
          Number transactions y_train dataset:  (199364, 1)
          Number transactions X_test dataset:  (85443, 29)
          Number transactions y_test dataset:  (85443, 1)
    

    Ahora entrene el modelo sin manejar la distribución de clases desequilibrada

    # logistic regression object
    lr = LogisticRegression()
      
    # train the model on train set
    lr.fit(X_train, y_train.ravel())
      
    predictions = lr.predict(X_test)
      
    # print classification report
    print(classification_report(y_test, predictions))
    

    Producción:

    
                    precision   recall   f1-score  support
    
               0       1.00      1.00      1.00     85296
               1       0.88      0.62      0.73       147
    
        accuracy                           1.00     85443
       macro avg       0.94      0.81      0.86     85443
    weighted avg       1.00      1.00      1.00     85443
    

    La precisión resulta ser del 100%, pero ¿notaste algo extraño?
    El recuerdo de la clase minoritaria en muy poco. Demuestra que el modelo está más sesgado hacia la clase mayoritaria. Por lo tanto, demuestra que este no es el mejor modelo.
    Ahora, aplicaremos diferentes técnicas de manejo de datos desequilibrados y veremos su precisión y recuperaremos los resultados.

    Uso del algoritmo SMOTE

    Puedes consultar todos los parámetros desde aquí .

    print("Before OverSampling, counts of label '1': {}".format(sum(y_train == 1)))
    print("Before OverSampling, counts of label '0': {} \n".format(sum(y_train == 0)))
      
    # import SMOTE module from imblearn library
    # pip install imblearn (if you don't have imblearn in your system)
    from imblearn.over_sampling import SMOTE
    sm = SMOTE(random_state = 2)
    X_train_res, y_train_res = sm.fit_sample(X_train, y_train.ravel())
      
    print('After OverSampling, the shape of train_X: {}'.format(X_train_res.shape))
    print('After OverSampling, the shape of train_y: {} \n'.format(y_train_res.shape))
      
    print("After OverSampling, counts of label '1': {}".format(sum(y_train_res == 1)))
    print("After OverSampling, counts of label '0': {}".format(sum(y_train_res == 0)))
    

    Producción:

    Before OverSampling, counts of label '1': [345]
    Before OverSampling, counts of label '0': [199019] 
    
    After OverSampling, the shape of train_X: (398038, 29)
    After OverSampling, the shape of train_y: (398038, ) 
    
    After OverSampling, counts of label '1': 199019
    After OverSampling, counts of label '0': 199019
    

    ¡Mirar! que el algoritmo SMOTE ha sobremuestreado las instancias minoritarias y las ha igualado a la clase mayoritaria. Ambas categorías tienen la misma cantidad de registros. Más específicamente, la clase minoritaria se ha incrementado al número total de la clase mayoritaria.
    Ahora vea la precisión y recupere los resultados después de aplicar el algoritmo SMOTE (Sobremuestreo).

    Predicción y recuerdo

    lr1 = LogisticRegression()
    lr1.fit(X_train_res, y_train_res.ravel())
    predictions = lr1.predict(X_test)
      
    # print classification report
    print(classification_report(y_test, predictions))
    

    Producción:

                    precision   recall   f1-score  support
    
               0       1.00      0.98      0.99     85296
               1       0.06      0.92      0.11       147
    
        accuracy                           0.98     85443
       macro avg       0.53      0.95      0.55     85443
    weighted avg       1.00      0.98      0.99     85443
    

    Vaya , hemos reducido la precisión al 98 % en comparación con el modelo anterior, pero el valor de recuperación de la clase minoritaria también ha mejorado al 92 %. Este es un buen modelo en comparación con el anterior. Recordar es genial.
    Ahora, aplicaremos la técnica NearMiss para submuestrear la clase mayoritaria y ver su precisión y resultados de recuperación.

    Algoritmo de NearMiss:

    Puedes consultar todos los parámetros desde aquí .

    print("Before Undersampling, counts of label '1': {}".format(sum(y_train == 1)))
    print("Before Undersampling, counts of label '0': {} \n".format(sum(y_train == 0)))
      
    # apply near miss
    from imblearn.under_sampling import NearMiss
    nr = NearMiss()
      
    X_train_miss, y_train_miss = nr.fit_sample(X_train, y_train.ravel())
      
    print('After Undersampling, the shape of train_X: {}'.format(X_train_miss.shape))
    print('After Undersampling, the shape of train_y: {} \n'.format(y_train_miss.shape))
      
    print("After Undersampling, counts of label '1': {}".format(sum(y_train_miss == 1)))
    print("After Undersampling, counts of label '0': {}".format(sum(y_train_miss == 0)))
    

    Producción:

    Before Undersampling, counts of label '1': [345]
    Before Undersampling, counts of label '0': [199019] 
    
    After Undersampling, the shape of train_X: (690, 29)
    After Undersampling, the shape of train_y: (690, ) 
    
    After Undersampling, counts of label '1': 345
    After Undersampling, counts of label '0': 345
    

    El algoritmo NearMiss submuestreó las instancias mayoritarias y las igualó a la clase mayoritaria. Aquí, la clase mayoritaria se ha reducido al número total de clases minoritarias, por lo que ambas clases tendrán el mismo número de registros.

    Predicción y recuerdo

    # train the model on train set
    lr2 = LogisticRegression()
    lr2.fit(X_train_miss, y_train_miss.ravel())
    predictions = lr2.predict(X_test)
      
    # print classification report
    print(classification_report(y_test, predictions))
    

    Producción:

                   precision    recall   f1-score   support
    
               0       1.00      0.56      0.72     85296
               1       0.00      0.95      0.01       147
    
        accuracy                           0.56     85443
       macro avg       0.50      0.75      0.36     85443
    weighted avg       1.00      0.56      0.72     85443
    

    Este modelo es mejor que el primer modelo porque clasifica mejor y además el valor de recuerdo de la clase minoritaria es del 95 %. Pero debido al submuestreo de la clase mayoritaria, su recuerdo ha disminuido al 56 %. Entonces, en este caso, SMOTE me está brindando una gran precisión y recuperación, ¡seguiré adelante y usaré ese modelo! 🙂

    Publicación traducida automáticamente

    Artículo escrito por tyagikartik4282 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *