selección de características en machine learning

Selección de características en machine learning

Cuando los modelos de machine learning son demasiado complejos, se corre el riesgo de que sufran sobreajuste (overfitting), es decir, que se ajusten excesivamente a los datos de entrenamiento y no generalicen bien. Para mitigar este problema, una estrategia eficaz es aplicar reducción de la dimensionalidad mediante selección de características.

La selección de características en machine learning consiste en eliminar aquellas variables que no aportan valor al modelo, reduciendo así la complejidad sin perder capacidad predictiva. El objetivo es obtener un subconjunto óptimo de características relevantes a partir del conjunto original de datos, lo que mejora tanto el rendimiento como la interpretabilidad del modelo.

Este proceso implica generar un subconjunto de características d a partir de un conjunto mayor D, y utilizar una función criterio para evaluar qué combinación de variables ofrece el mejor rendimiento, junto con el algoritmo de búsqueda más adecuado para encontrarlo.

Existen distintas técnicas para realizar esta selección, como los algoritmos voraces, búsqueda exhaustiva, Branch and Bound (BB) o métodos secuenciales como SBS (Sequential Backward Selection). En esta introducción nos centraremos en el algoritmo SBS, una técnica eficiente y fácil de aplicar para reducir modelos complejos de machine learning.

Búsqueda Exhaustiva

El algoritmo de búsqueda exhaustiva evalúa siempre todos los subconjuntos posibles.

Por ejemplo tenemos un conjunto de características (X1, X2, X3, X4) y las vamos a tomar de dos en dos, cuyas soluciones serán:

  1. (X1, X2)
  2. (X1, X3)
  3. (X1, X4)
  4. (X2, X3)
  5. (X2, X4)
  6. (X3, X4)

El algoritmo es capaz de encontrar el mejor subconjunto de d de características pero a costa de tener un coste exponencial, solución no viable.

Aplicación del Algoritmo SBS (Sequential Backward Selection) para la Selección de Características

El algoritmo SBS (Sequential Backward Selection) es una técnica de reducción de características en machine learning que permite disminuir la complejidad de los modelos eliminando variables no relevantes. Este método secuencial parte del conjunto completo de características y elimina iterativamente aquellas que menos aportan al rendimiento del modelo.

A continuación, se describen los pasos del algoritmo SBS:

  1. Definir el número de características deseadas que se quiere conservar.

  2. Crear el nodo padre con todas las características disponibles (n).

  3. Eliminar una característica, generando combinaciones con n-1 atributos.

  4. Crear nodos hijos para cada posible subconjunto resultante.

  5. Evaluar el coste de predicción (por ejemplo, usando precisión o error) para cada hijo.

  6. Seleccionar el nodo hijo con mejor rendimiento predictivo.

  7. Convertir ese nodo hijo en el nuevo nodo padre.

  8. Repetir el proceso desde el paso 3 hasta alcanzar el número de características deseadas.

Este algoritmo es especialmente útil para mejorar la eficiencia de modelos complejos, evitando el sobreajuste y reduciendo el tiempo de entrenamiento sin sacrificar precisión.

Selección de Características con SBS (Sequential Backward Selection) usando KNN y el Dataset de Vinos

El siguiente código implementa una versión manual del algoritmo Sequential Backward Selection (SBS) para la selección de características en modelos de machine learning, usando el dataset wine.csv.

¿Qué hace el código?

  1. Carga datos de vinos (getDatos) con cinco características numéricas.

  2. Divide los datos en entrenamiento y prueba.

  3. Normaliza las características con StandardScaler.

  4. Usa un clasificador K-Nearest Neighbors (KNN).

  5. Aplica SBS para seleccionar de 2 a 5 características que maximizan la precisión (accuracy) del modelo.

  6. En cada iteración, elimina una característica que menos aporta al modelo.

  7. Imprime el mejor subconjunto de características y su desempeño.

 Resultado

El código muestra qué combinación de atributos ofrece la mejor predicción para diferentes cantidades de características seleccionadas.

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from itertools import combinations
from sklearn.metrics import accuracy_score
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier
from sympy import Not
def getDatos():
    #Lectura de datos
    wine = pd.read_csv("wine.csv")
    X = wine.iloc[:, 1:6].values
    Y = wine.iloc[:, 0].values   
    return X,Y 
def costeNodo(clasificador, X_train, y_train, X_test, y_test, hijo):
        clasificador.fit(X_train[:, hijo], y_train)
        y_pred = clasificador.predict(X_test[:, hijo])
        score = accuracy_score(y_test, y_pred)
        return score
class SBS():
      def __init__(self, clasificador,n_caracteristicas, n_caracteristicasDesadas):
        self.clasificador = clasificador
        self.n_caracteristicasDesadas=n_caracteristicasDesadas
        self.n_caracteristicas=n_caracteristicas
        self.mejorCoste=0  
        self.mejorSolucion_hijos=""
      def fit(self,x_train, y_train,x_test, y_test):   
        #Creamos el primer nodo
        nodo = tuple(range(self.n_caracteristicas))
        #Coste del nodo padre
        costePadre = costeNodo(self.clasificador,x_train, y_train, x_test, y_test,nodo)
        self.mejorCoste=costePadre   
        self.nodo = nodo
        #Mientras que las caracteristicas de entrada sean mayores que las deseadas
        while (self.n_caracteristicas > self.n_caracteristicasDesadas):             
          #Mediante las combinaciones creamos los n nodos hijos
          n_hijos=combinations(nodo, r=self.n_caracteristicas - 1)  
          hijos_nivel=[]  
          #Array de costes
          coste_nhijos=[]  
          #Para cada hijo   
          for hijo in n_hijos:                            
                    hijos_nivel.append(hijo)   
                    #calculamos el coste
                    coste = costeNodo(self.clasificador,x_train, y_train, 
                                            x_test, y_test, hijo)
                    coste_nhijos.append(coste)        
          self.mejorCoste = np.amax(coste_nhijos)
          self.mejorSolucion_hijos=np.argmax(coste_nhijos)          
          #Cogemos el nodo mas prometedor
          self.nodo = hijos_nivel[self.mejorSolucion_hijos] 
          #Bajamos el nivel del arbol
          self.n_caracteristicas -= 1 
        return self  
X,Y = getDatos()
#Division del conjunto de los datos
X_entrenamiento,X_prueba,Y_entrenamiento,Y_prueba = train_test_split(X,Y, test_size=0.3,random_state=1,stratify=Y)
#Normalizacion de los datos
sc = StandardScaler()
sc.fit(X_entrenamiento) #Estima los parametros u y o para dimension de los datos
X_entrenamiento_std = sc.transform(X_entrenamiento)
X_prueba_std = sc.transform(X_prueba)
#Division de los datos 
x_train, x_test, y_train, y_test = train_test_split(X,Y,test_size=0.25,random_state=0)
clasificador=KNeighborsClassifier(n_neighbors=5, metric="minkowski",p=2)
for i in range(2,6):
    n_CaracteristicasDeseadas=i
    sbs = SBS(clasificador,X.shape[1],n_CaracteristicasDeseadas)
    solucionOptima = sbs.fit(x_train, y_train,x_test, y_test)
    print("Para", n_CaracteristicasDeseadas, "caracteristicas el mejor nodo encontrado es: ",solucionOptima.nodo)
    print("Con un coste de", solucionOptima.mejorCoste)

Para 2 caracteristicas el mejor nodo encontrado es:  (0, 1)

Con un coste de 0.8444444444444444

Para 3 caracteristicas el mejor nodo encontrado es:  (0, 1, 2)

Con un coste de 0.8888888888888888

Para 4 caracteristicas el mejor nodo encontrado es:  (0, 1, 2, 3)

Con un coste de 0.8222222222222222

Para 5 caracteristicas el mejor nodo encontrado es:  (0, 1, 2, 3, 4)

Con un coste de 0.7333333333333333