Reducción de la dimensionalidad en Python con Scikit-Learn

R

Introducción

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

Existen múltiples técnicas que se pueden utilizar para combatir el sobreajuste, pero la reducción de dimensionalidad es una de las técnicas más efectivas. La reducción de dimensionalidad selecciona los componentes más importantes del espacio de características, preservándolos y descartando los otros componentes.

¿Por qué se necesita la reducción de la dimensionalidad?

Hay algunas razones por las que la reducción de dimensionalidad se utiliza en el Machine Learning: 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 Machine Learning, 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 llevar a largos tiempos de entrenamiento, sino que más características 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 dimensionalidad reduce el número total de características, puede reducir las demandas computacionales asociadas con el entrenamiento de un modelo, pero también ayuda a combatir el sobreajuste al mantener las características que se enviarán al modelo de manera bastante simple.

La reducción de dimensionalidad se puede utilizar tanto en contextos de aprendizaje supervisados ​​como no supervisados . En el caso del aprendizaje no supervisado, la reducción de dimensionalidad se usa a menudo para preprocesar los datos mediante la selección o extracción de características.

Los algoritmos principales utilizados para llevar a cabo la reducción de dimensionalidad para el aprendizaje no supervisado son el análisis de componentes principales (PCA) y la descomposición de valores singulares (SVD).

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

Tenga en cuenta que los casos de uso descritos anteriormente son casos de uso general 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 Machine Learning.

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

El análisis de componentes principales (PCA) es un método estadístico que crea nuevas características 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 “aplastando” los datos en 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 CO2, 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 características basadas en ellas. Al elegir solo las entidades con mayor influencia en el conjunto de datos, se reduce la dimensionalidad.

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

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_splity herramientas de etiquetado y escalado:

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, buscaremos 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 etiquetas en consecuencia:

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:

# 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 características explican la mayoría, casi el 95% de nuestros datos:

# 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()

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

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()

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

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()

Valor singular de descomposición

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 Machine Learning, pero tener una intuición de cómo funciona le dará una mejor idea de cuándo usarlo.

La SVD se puede realizar en matrices complejas o de valor real, pero para facilitar la comprensión de esta explicación, 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 el número 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.

La descomposición de valores para una matriz implica convertir los valores singulares de la matriz original en los valores diagonales de la nueva matriz. Las matrices ortogonales no tienen sus propiedades cambiadas 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 juntas combinadas cuando la transpuesta de la matriz V, obtenemos una matriz que es equivalente a la matriz original A.

Cuando dividimos / descomponemos la matriz A en U, D y V, entonces 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 la Matriz A. Esta nueva matriz es mucho más simple y más 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 comprimir imágenes. Después de todo, los valores de píxeles que componen los canales rojo, verde y azul en la imagen pueden simplemente reducirse y el resultado será una imagen menos compleja pero que aún contiene 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 Numpy y la Imagefunción de la biblioteca PIL para lograr esto, ya que Numpy tiene un método para realizar el cálculo de SVD:

import numpy
from PIL import Image

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

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 queramos. Luego crearemos una matriz de ceros que completaremos después de que se complete la multiplicación de la matriz. Luego especificamos el límite de valor singular que queremos usar al hacer los cálculos:

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 de la izquierda y luego la multiplicamos por la matriz V. Esto debería darnos los valores comprimidos que transformamos al tipo ‘uint8’:

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:

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

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:

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 un cierto punto, aparecerán artefactos en la imagen y la calidad de la imagen se degradará:

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

El 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 hay 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 utilizar 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 base a 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 la gráfica original al nuevo eje. Primero, se debe calcular la separabilidad entre las clases, y esto se basa en la distancia entre las medias de las clases o la varianza entre clases. En el siguiente paso, se debe calcular la varianza dentro de la clase, 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 los medios de las clases están lejos unos de otros. Si se comparten las medias de la distribución, LDA no podrá separar las clases con un nuevo eje lineal.

Ejemplo de implementación de LDA

Por último, veamos cómo se puede utilizar LDA para realizar la reducción de dimensionalidad. Tenga en cuenta que LDA se puede utilizar como un algoritmo de clasificación además de realizar la reducción de dimensionalidad.

Usaremos el conjunto de datos Titanic para el siguiente ejemplo.

Comencemos haciendo todas nuestras importaciones necesarias:

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 pequeño preprocesamiento de datos. Dejemos las Name, Cabiny Ticketlas columnas, ya que no tienen una gran cantidad de información útil. También necesitamos completar cualquier dato que falte, que reemplazaremos con valores medianos en el caso de la Agecaracterística y Sen el caso de la Embarkedcaracterística:

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 Sexy Embarked. Dejemos también la Namecolumna, ya que parece poco probable que sea útil en la clasificación:

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 Scalerherramienta toma matrices, por lo que los valores que queremos remodelar deben convertirse primero en matrices. Después de eso, podemos escalar los datos:

# 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 usarlas train_test_splitpara hacer nuestros datos de entrenamiento y validación. Es fácil hacer la clasificación 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:

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:

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 dimensionalidad:

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:

Accuracy: 0.8100558659217877
F1 Score: 0.734375

Ahora transformaremos las características de 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:

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:

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

Ahora solo tenemos que hacer la división de entrenamiento / prueba nuevamente con las nuevas características y ejecutar el clasificador nuevamente para ver cómo cambió el rendimiento:

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))
Accuracy: 0.8212290502793296
F1 Score: 0.7500000000000001

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 utilizar para ayudar a que sus modelos de Machine Learning funcionen mejor, combatir el sobreajuste y ayudar en el análisis de datos.

Si bien estas tres técnicas son las técnicas de reducción de dimensionalidad más comúnmente utilizadas, existen otras. Otras técnicas de dimensionalidad incluyen la aproximación del núcleo y la incrustación espectral de isomapas .

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad