Reducción de dimensionalidad en Python con Scikit-Learn

La reducción de dimensionalidad selecciona los componentes más importantes del espacio de características, preservándolos, para combatir el sobreajuste. En este artículo, reduciremos las dimensiones de varios conjuntos de datos usando una amplia variedad de técnicas en Python usando Scikit-Learn.

Introducción

En el aprendizaje automático, el rendimiento de un modelo solo se beneficia de más funciones hasta cierto punto. Cuantas más características se introducen en un modelo, más aumenta la dimensionalidad de los datos. A medida que aumenta la dimensionalidad, el sobreajuste se vuelve más probable.

Existen múltiples técnicas que pueden usarse para combatir el sobreajuste, pero la reducción de la dimensionalidad es una de las técnicas más efectivas. La reducción de la dimensionalidad selecciona los componentes más importantes del espacio de funciones, los conserva y descarta los demás componentes.

¿Por qué es necesaria la reducción de la dimensionalidad? {#por qué se necesita una reducción de la dimensionalidad}

Hay algunas razones por las que la reducción de la dimensionalidad se usa en el aprendizaje automático: para combatir el costo computacional, para controlar el sobreajuste y para visualizar y ayudar a interpretar conjuntos de datos de alta dimensión.

A menudo, en el aprendizaje automático, cuantas más funciones estén presentes en el conjunto de datos, mejor podrá aprender un clasificador. Sin embargo, más funciones también significan un mayor costo computacional. La alta dimensionalidad no solo puede conducir a largos tiempos de entrenamiento, sino que más funciones a menudo conducen a un sobreajuste del algoritmo mientras intenta crear un modelo que explique todas las características en los datos.

Debido a que la reducción de la dimensionalidad reduce el número total de funciones, puede reducir las demandas computacionales asociadas con el entrenamiento de un modelo, pero también ayuda a combatir el sobreajuste al mantener las funciones que se alimentarán al modelo bastante simples.

La reducción de dimensionalidad se puede utilizar tanto en contextos supervisados ​​como [contextos de aprendizaje no supervisados] (https://scikit-learn.org/stable/modules/unsupervised_reduction.html). En el caso del aprendizaje no supervisado, la reducción de la dimensionalidad se usa a menudo para preprocesar los datos mediante la selección o extracción de características.

Los principales algoritmos utilizados para llevar a cabo la reducción de dimensionalidad para el aprendizaje no supervisado son Análisis de componentes principales (PCA) y Valor singular de generación (SVD).

En el caso del aprendizaje supervisado, la reducción de la dimensionalidad se puede utilizar para simplificar las funciones que se introducen en el clasificador de aprendizaje automático. Los métodos más comunes utilizados para llevar a cabo la reducción de la dimensionalidad de los problemas de aprendizaje supervisado son Análisis Discriminante Lineal (LDA) y PCA, y puede utilizarse para predecir nuevos casos.

Tenga en cuenta que los casos de uso descritos anteriormente son casos de uso generales y no las únicas condiciones en las que se utilizan estas técnicas. Después de todo, las técnicas de reducción de dimensionalidad son métodos estadísticos y su uso no está restringido por modelos de aprendizaje automático.

Tomemos un tiempo para explicar las ideas detrás de cada una de las técnicas de reducción de dimensionalidad más comunes.

Análisis de componentes principales

Análisis de componentes principales (PCA) es un método estadístico que crea nuevas funciones o características de los datos mediante el análisis de las características del conjunto de datos. Esencialmente, las características de los datos se resumen o combinan. También puede concebir el análisis de componentes principales como "aplastar" los datos en solo unas pocas dimensiones desde un espacio de dimensiones mucho más altas.

Para ser más concretos, una bebida puede describirse por muchas características, pero muchas de estas características serán redundantes y relativamente inútiles para identificar la bebida en cuestión. En lugar de describir el vino con características como aireación, niveles de C02, etc., podrían describirse más fácilmente por color, sabor y edad.

El análisis de componentes principales selecciona las características "principales" o más influyentes del conjunto de datos y crea funciones basadas en ellas. Al elegir solo las características con mayor influencia en el conjunto de datos, se reduce la dimensionalidad.

PCA preserva las correlaciones entre variables cuando crea nuevas funciones. Los principales componentes creados por la técnica son combinaciones lineales de las variables originales, calculadas con conceptos llamados vectores propios.

Se supone que los nuevos componentes son ortogonales o no están relacionados entre sí.

Ejemplo de implementación de PCA

Echemos un vistazo a cómo se puede implementar PCA en Scikit-Learn. Usaremos el Conjunto de datos de clasificación de hongos para esto.

Primero, necesitamos importar todos los módulos que necesitamos, que incluyen PCA, train_test_split y herramientas de etiquetado y escalado:

1
2
3
4
5
6
7
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings("ignore")

Después de cargar los datos, verificaremos si hay valores nulos. También codificaremos los datos con LabelEncoder. La característica de clase es la primera columna del conjunto de datos, por lo que dividimos las características y las etiquetas en consecuencia:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
m_data = pd.read_csv('mushrooms.csv')

# Machine learning systems work with integers, we need to encode these
# string characters into ints

encoder = LabelEncoder()

# Now apply the transformation to all the columns:
for col in m_data.columns:
    m_data[col] = encoder.fit_transform(m_data[col])

X_features = m_data.iloc[:,1:23]
y_label = m_data.iloc[:, 0]

Ahora escalaremos las características con el escalador estándar. Esto es opcional ya que en realidad no estamos ejecutando el clasificador, pero puede afectar la forma en que PCA analiza nuestros datos:

1
2
3
# Scale the features
scaler = StandardScaler()
X_features = scaler.fit_transform(X_features)

Ahora usaremos PCA para obtener la lista de características y trazar qué características tienen el mayor poder explicativo o tienen la mayor variación. Estos son los componentes principales. Parece que alrededor de 17 o 18 de las funciones explican la mayoría, casi el 95% de nuestros datos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Visualize
pca = PCA()
pca.fit_transform(X_features)
pca_variance = pca.explained_variance_

plt.figure(figsize=(8, 6))
plt.bar(range(22), pca_variance, alpha=0.5, align='center', label='individual variance')
plt.legend()
plt.ylabel('Variance ratio')
plt.xlabel('Principal components')
plt.show()

pca features

Convirtamos las características en las 17 características principales. Luego trazaremos un diagrama de dispersión de la clasificación de puntos de datos en función de estas 17 características:

1
2
3
4
5
6
7
pca2 = PCA(n_components=17)
pca2.fit(X_features)
x_3d = pca2.transform(X_features)

plt.figure(figsize=(8,6))
plt.scatter(x_3d[:,0], x_3d[:,5], c=m_data['class'])
plt.show()

top 17 features

Hagamos esto también para las 2 características principales y veamos cómo cambia la clasificación:

1
2
3
4
5
6
7
pca3 = PCA(n_components=2)
pca3.fit(X_features)
x_3d = pca3.transform(X_features)

plt.figure(figsize=(8,6))
plt.scatter(x_3d[:,0], x_3d[:,1], c=m_data['class'])
plt.show()

top 2 features

Descomposición de valores singulares {#descomposición de valores singulares}

El propósito de la descomposición de valores singulares es simplificar una matriz y facilitar los cálculos con la matriz. La matriz se reduce a sus partes constituyentes, similar al objetivo de PCA. Comprender los entresijos de SVD no es completamente necesario para implementarlo en sus modelos de aprendizaje automático, pero tener una intuición de cómo funciona le dará una mejor idea de cuándo usarlo.

SVD se puede llevar a cabo en matrices complejas o de valor real, pero para que esta explicación sea más fácil de entender, repasaremos el método de descomposición de una matriz de valor real.

Al hacer SVD, tenemos una matriz llena de datos y queremos reducir la cantidad de columnas que tiene la matriz. Esto reduce la dimensionalidad de la matriz al tiempo que conserva la mayor cantidad posible de variabilidad en los datos.

Podemos decir que la matriz A es igual a la transpuesta de la matriz V:

$$
A = U * D * V^t
$$

Suponiendo que tenemos alguna matriz A, podemos representar esa matriz como otras tres matrices llamadas U, V y D. La matriz A tiene los elementos x*y originales, mientras que la matriz U es una matriz ortogonal que contiene elementos x*x y la matriz V es una matriz ortogonal diferente que contiene elementos y*y . Finalmente, D es una matriz diagonal que contiene elementos x*y.

Descomponer los valores de una matriz implica convertir los valores singulares de la matriz original en los valores diagonales de la nueva matriz. Las matrices ortogonales no cambian sus propiedades si se multiplican por otros números, y podemos aprovechar esta propiedad para obtener una aproximación de la matriz A. Al multiplicar la matriz ortogonal combinada con la transposición de la matriz V, obtenemos una matriz que es equivalente a la matriz original A.

Cuando descomponemos/descomponemos la matriz A en U, D y V, tenemos tres matrices diferentes que contienen la información de la matriz A.

Resulta que las columnas más a la izquierda de las matrices contienen la mayoría de nuestros datos, y podemos seleccionar solo estas pocas columnas para tener una buena aproximación de Matrix A. Esta nueva matriz es mucho más simple y fácil de trabajar, ya que tiene muchas menos dimensiones.

Ejemplo de implementación de SVD

Una de las formas más comunes en que se usa SVD es para comprimir imágenes. Después de todo, los valores de píxel que componen los canales rojo, verde y azul de la imagen se pueden reducir y el resultado será una imagen menos compleja pero que aún contenga el mismo contenido de imagen. Intentemos usar SVD para comprimir una imagen y renderizarla.

Usaremos varias funciones para manejar la compresión de la imagen. Realmente solo necesitaremos entumecido y la función Image de la biblioteca PIL para para lograr esto, ya que Numpy tiene un método para realizar el cálculo de SVD:

1
2
import numpy
from PIL import Image

Primero, solo escribiremos una función para cargar la imagen y convertirla en una matriz Numpy. Luego queremos seleccionar los canales de color rojo, verde y azul de la imagen:

1
2
3
4
5
6
7
8
9
def load_image(image):
    image = Image.open(image)
    im_array = numpy.array(image)

    red = im_array[:, :, 0]
    green = im_array[:, :, 1]
    blue = im_array[:, :, 2]

    return red, green, blue

Ahora que tenemos los colores, necesitamos comprimir los canales de color. Podemos comenzar llamando a la función SVD de Numpy en el canal de color que queremos. Luego crearemos una matriz de ceros que completaremos después de que se complete la multiplicación de matrices. Luego especificamos el límite de valor singular que queremos usar al hacer los cálculos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def channel_compress(color_channel, singular_value_limit):
    u, s, v = numpy.linalg.svd(color_channel)
    compressed = numpy.zeros((color_channel.shape[0], color_channel.shape[1]))
    n = singular_value_limit

    left_matrix = numpy.matmul(u[:, 0:n], numpy.diag(s)[0:n, 0:n])
    inner_compressed = numpy.matmul(left_matrix, v[0:n, :])
    compressed = inner_compressed.astype('uint8')
    return compressed

red, green, blue = load_image("dog3.jpg")
singular_val_lim = 350

Después de esto, hacemos la multiplicación de matrices en la diagonal y los límites de valor en la matriz U, como se describió anteriormente. Esto nos da la matriz izquierda y luego la multiplicamos con la matriz V. Esto debería darnos los valores comprimidos que transformamos al tipo ‘uint8':

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def compress_image(red, green, blue, singular_val_lim):
    compressed_red = channel_compress(red, singular_val_lim)
    compressed_green = channel_compress(green, singular_val_lim)
    compressed_blue = channel_compress(blue, singular_val_lim)

    im_red = Image.fromarray(compressed_red)
    im_blue = Image.fromarray(compressed_blue)
    im_green = Image.fromarray(compressed_green)

    new_image = Image.merge("RGB", (im_red, im_green, im_blue))
    new_image.show()
    new_image.save("dog3-edited.jpg")

compress_image(red, green, blue, singular_val_lim)

Usaremos esta imagen de un perro para probar nuestra compresión SVD en:

imagen no reducida de un perro

También necesitamos establecer el límite de valor singular que usaremos, comencemos con 600 por ahora:

1
2
red, green, blue = load_image("dog.jpg")
singular_val_lim = 350

Finalmente, podemos obtener los valores comprimidos para los tres canales de color y transformarlos de matrices Numpy en componentes de imagen usando PIL. Entonces solo tenemos que unir los tres canales y mostrar la imagen. Esta imagen debería ser un poco más pequeña y simple que la imagen original:

dimension reduced image of a dog

De hecho, si inspecciona el tamaño de las imágenes, notará que la comprimida es más pequeña, aunque también hemos tenido un poco de compresión con pérdida. También puede ver algo de ruido en la imagen.

Puede jugar ajustando el límite de valor singular. Cuanto más bajo sea el límite elegido, mayor será la compresión, pero en cierto punto aparecerán artefactos en la imagen y la calidad de la imagen se degradará:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def compress_image(red, green, blue, singular_val_lim):
    compressed_red = channel_compress(red, singular_val_lim)
    compressed_green = channel_compress(green, singular_val_lim)
    compressed_blue = channel_compress(blue, singular_val_lim)

    im_red = Image.fromarray(compressed_red)
    im_blue = Image.fromarray(compressed_blue)
    im_green = Image.fromarray(compressed_green)

    new_image = Image.merge("RGB", (im_red, im_green, im_blue))
    new_image.show()

compress_image(red, green, blue, singular_val_lim)

Análisis Discriminante Lineal

Análisis Discriminante Lineal funciona proyectando datos de un gráfico multidimensional en un gráfico lineal. La forma más fácil de concebir esto es con un gráfico lleno de puntos de datos de dos clases diferentes. Suponiendo que no haya una línea que separe claramente los datos en dos clases, el gráfico bidimensional se puede reducir a un gráfico 1D. Este gráfico 1D se puede usar para lograr la mejor separación posible de los puntos de datos.

Cuando se lleva a cabo LDA, hay dos objetivos principales: minimizar la varianza de las dos clases y maximizar la distancia entre las medias de las dos clases de datos.

Para lograr esto, se trazará un nuevo eje en el gráfico 2D. Este nuevo eje debe separar los dos puntos de datos en función de los criterios mencionados anteriormente. Una vez que se ha creado el nuevo eje, los puntos de datos dentro del gráfico 2D se vuelven a dibujar a lo largo del nuevo eje.

LDA realiza tres pasos diferentes para mover el gráfico original al nuevo eje. Primero, se debe calcular la separabilidad entre las clases, y esto se basa en la distancia entre las medias de clase o la varianza entre clases. En el siguiente paso, se debe calcular la varianza intraclase, que es la distancia entre la media y la muestra para las diferentes clases. Finalmente, se debe construir el espacio dimensional inferior que maximiza la varianza entre clases.

LDA funciona mejor cuando las medias de las clases están lejos unas de otras. Si se comparten los medios de distribución, LDA no podrá separar las clases con un nuevo eje lineal.

Ejemplo de implementación de LDA

Finalmente, veamos cómo se puede usar LDA para llevar a cabo la reducción de dimensionalidad. Tenga en cuenta que LDA se puede utilizar como un algoritmo de clasificación además de llevar a cabo la reducción de dimensionalidad.

Usaremos el conjunto de datos Titánico para el siguiente ejemplo.

Empecemos por hacer todas nuestras importaciones necesarias:

1
2
3
4
5
6
7
import pandas as pd
import numpy as np
from sklearn.metrics import accuracy_score, f1_score
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

Ahora cargaremos nuestros datos de entrenamiento, que dividiremos en conjuntos de entrenamiento y validación.

Sin embargo, primero debemos hacer un poco de preprocesamiento de datos. Descartemos las columnas Nombre, Cabina y Boleto ya que no contienen mucha información útil. También necesitamos completar los datos faltantes, que reemplazaremos con valores medianos en el caso de la función Edad y una S en el caso de la función Embarcado:

1
2
3
4
5
6
7
training_data = pd.read_csv("train.csv")

# Let's drop the cabin and ticket columns
training_data.drop(labels=['Cabin', 'Ticket'], axis=1, inplace=True)

training_data["Age"].fillna(training_data["Age"].median(), inplace=True)
training_data["Embarked"].fillna("S", inplace=True)

También necesitamos codificar las características no numéricas. Codificaremos las columnas Sexo y Embarcado. También eliminemos la columna Nombre, ya que parece poco probable que sea útil en la clasificación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
encoder_1 = LabelEncoder()

# Fit the encoder on the data
encoder_1.fit(training_data["Sex"])

# Transform and replace the training data
training_sex_encoded = encoder_1.transform(training_data["Sex"])
training_data["Sex"] = training_sex_encoded

encoder_2 = LabelEncoder()
encoder_2.fit(training_data["Embarked"])

training_embarked_encoded = encoder_2.transform(training_data["Embarked"])
training_data["Embarked"] = training_embarked_encoded

# Assume the name is going to be useless and drop it
training_data.drop("Name", axis=1, inplace=True)

Necesitamos escalar los valores, pero la herramienta Scaler toma matrices, por lo que los valores que queremos remodelar deben convertirse primero en matrices. Después de eso, podemos escalar los datos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Remember that the scaler takes arrays
ages_train = np.array(training_data["Age"]).reshape(-1, 1)
fares_train = np.array(training_data["Fare"]).reshape(-1, 1)

scaler = StandardScaler()

training_data["Age"] = scaler.fit_transform(ages_train)
training_data["Fare"] = scaler.fit_transform(fares_train)

# Now to select our training and testing data
features = training_data.drop(labels=['PassengerId', 'Survived'], axis=1)
labels = training_data['Survived']

Ahora podemos seleccionar las funciones y etiquetas de entrenamiento y usar train_test_split para crear nuestros datos de entrenamiento y validación. Es fácil clasificar con LDA, lo maneja como lo haría con cualquier otro clasificador en Scikit-Learn.

Simplemente ajuste la función en los datos de entrenamiento y haga que prediga en los datos de validación/prueba. Luego podemos imprimir métricas para las predicciones contra los valores reales:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
X_train, X_val, y_train, y_val = train_test_split(features, labels, test_size=0.2, random_state=27)

model = LDA()
model.fit(X_train, y_train)
preds = model.predict(X_val)
acc = accuracy_score(y_val, preds)
f1 = f1_score(y_val, preds)

print("Accuracy: {}".format(acc))
print("F1 Score: {}".format(f1))

Aquí está la impresión:

1
2
Accuracy: 0.8100558659217877
F1 Score: 0.734375

Cuando se trata de transformar los datos y reducir la dimensionalidad, primero ejecutemos un clasificador de regresión logística en los datos para que podamos ver cuál es nuestro rendimiento antes de la reducción de la dimensionalidad:

1
2
3
4
5
6
7
8
logreg_clf = LogisticRegression()
logreg_clf.fit(X_train, y_train)
preds = logreg_clf.predict(X_val)
acc = accuracy_score(y_val, preds)
f1 = f1_score(y_val, preds)

print("Accuracy: {}".format(acc))
print("F1 Score: {}".format(f1))

Aquí están los resultados:

1
2
Accuracy: 0.8100558659217877
F1 Score: 0.734375

Ahora transformaremos las características de los datos especificando una cantidad de componentes deseados para LDA y ajustando el modelo en las características y etiquetas. Luego simplemente transformamos las características y las guardamos en una nueva variable. Imprimamos el número original y reducido de características:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
LDA_transform = LDA(n_components=1)
LDA_transform.fit(features, labels)
features_new = LDA_transform.transform(features)

# Print the number of features
print('Original feature #:', features.shape[1])
print('Reduced feature #:', features_new.shape[1])

# Print the ratio of explained variance
print(LDA_transform.explained_variance_ratio_)

Aquí está la impresión del código anterior:

1
2
3
Original feature #: 7
Reduced feature #: 1
[1.]

Ahora solo tenemos que volver a dividir el entrenamiento/prueba con las nuevas funciones y ejecutar el clasificador nuevamente para ver cómo cambió el rendimiento:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
X_train, X_val, y_train, y_val = train_test_split(features_new, labels, test_size=0.2, random_state=27)

logreg_clf = LogisticRegression()
logreg_clf.fit(X_train, y_train)
preds = logreg_clf.predict(X_val)
acc = accuracy_score(y_val, preds)
f1 = f1_score(y_val, preds)

print("Accuracy: {}".format(acc))
print("F1 Score: {}".format(f1))
1
2
Accuracy: 0.8212290502793296
F1 Score: 0.7500000000000001

Yendo más lejos: proyecto de extremo a extremo portátil

¿Tu naturaleza inquisitiva te hace querer ir más allá? Recomendamos consultar nuestro Proyecto guiado: ["Predicción práctica del precio de la vivienda: aprendizaje automático en Python"](https://wikihtp.com/courses/hands-on-house- precio-predicción-aprendizaje-máquina-en-python/#cta){target="_blank"}.

[](https://wikihtp.com/ cursos/predicción-de-precio-de-la-casa-práctica-aprendizaje-de-máquina-en-python/#cta)

En este proyecto guiado, aprenderá a crear potentes modelos tradicionales de aprendizaje automático, así como modelos de aprendizaje profundo, utilizar Ensemble Learning y capacitar a los meta-aprendices para predecir los precios de la vivienda a partir de una bolsa de modelos Scikit-Learn y Keras.

Con Keras, la API de aprendizaje profundo creada sobre Tensorflow, experimentaremos con arquitecturas, crearemos un conjunto de modelos apilados y entrenaremos una red neuronal meta-aprendizaje (modelo de nivel 1) para averiguar el precio de un casa.

El aprendizaje profundo es sorprendente, pero antes de recurrir a él, se recomienda intentar resolver el problema con técnicas más simples, como los algoritmos de aprendizaje superficial. Nuestro rendimiento de referencia se basará en un algoritmo de Regresión de bosque aleatorio. Además, exploraremos la creación de conjuntos de modelos a través de Scikit-Learn a través de técnicas como embalaje y votación.

Este es un proyecto integral y, como todos los proyectos de aprendizaje automático, comenzaremos con Análisis exploratorio de datos, seguido de Preprocesamiento de datos y, finalmente, Creación de modelos de aprendizaje superficial y Profundo para ajustarse a los datos que hemos explorado y limpiado previamente.

Conclusión

Hemos repasado los principales métodos de técnicas de reducción de dimensionalidad: análisis de componentes principales, descomposición de valores singulares y análisis discriminante lineal. Estas son técnicas estadísticas que puede usar para ayudar a que sus modelos de aprendizaje automático funcionen mejor, combatan el sobreajuste y ayuden en el análisis de datos.

Si bien estas tres técnicas son las técnicas de reducción de dimensionalidad más utilizadas, existen otras. Otras técnicas de dimensionalidad incluyen la [aproximación del núcleo] (https://en.wikipedia.org/wiki/Kernel_method) y la incrustación espectral [isomap] (https://en.wikipedia.org/wiki/Isomap).