Guía de escalado multidimensional en Python con Scikit-Learn

En esta guía, veremos el escalado multidimensional en Python con Scikit-Learn, con aplicaciones prácticas para el conjunto de datos de Olivetta Faces.

Introducción

En esta guía, nos sumergiremos en una reducción de dimensionalidad, incrustación de datos y técnica de visualización de datos conocida como Escalamiento multidimensional (MDS).

Usaremos Scikit-Learn para realizar el escalado multidimensional, ya que tiene una API maravillosamente simple y poderosa. A lo largo de la guía, usaremos Olivetti se enfrenta al conjunto de datos de AT&T para ilustrar la incorporación de datos en un formato inferior. -espacio dimensional.

Al final de la guía, comprenderá firmemente el escalamiento multidimensional, así como sus hiperparámetros y cómo impactan en la técnica.

¿Qué es el escalado multidimensional?

MDS es una técnica no lineal para incrustar datos en un espacio de menor dimensión.

Mapea puntos que residen en un espacio de mayor dimensión a un espacio de menor dimensión mientras preserva las distancias entre esos puntos tanto como sea posible. Debido a esto, las distancias por pares entre los puntos en el espacio de menor dimensión coinciden estrechamente con sus distancias reales.

La siguiente figura es un ejemplo de un posible mapeo de puntos del espacio 3D al espacio 2D y 1D. Las distancias por pares de los tres puntos en el espacio 3D se conservan exactamente en el espacio 2D pero no en el espacio 1D. Si ejecutamos MDS, garantizaría una diferencia mínima entre las distancias reales por pares y las distancias por pares de los puntos mapeados:

ilustración de escalado multidimensional

MDS se puede utilizar como un paso de preprocesamiento para la reducción de la dimensionalidad en problemas de clasificación y regresión.

Además del escalado multidimensional, también puede utilizar otras técnicas de reducción de la dimensionalidad, como Análisis de componentes principales (PCA) o Descomposición de valores singulares (SVD). Si desea leer sobre ambos, así como también cómo usarlos a su favor, lea nuestra [Guía para la reducción de dimensionalidad en Python con Scikit-Learn](/reduccion-de-dimensionalidad-en- python-con-scikit-learn/)!

MDS no solo es una técnica efectiva para la reducción de dimensionalidad sino también para la visualización de datos. Mantiene los mismos grupos y patrones de datos de alta dimensión en el espacio de menor dimensión para que pueda reducir, por ejemplo, un conjunto de datos de 5 dimensiones a un conjunto de datos de 3 dimensiones que puede interpretar de manera mucho más fácil y natural.

Normalmente, la medida de distancia utilizada en MDS es la distancia euclidiana, sin embargo, se puede utilizar cualquier otra métrica de disimilitud adecuada al aplicar MDS.

Hay dos formas principales de implementar MDS:

  • Metric MDS / Classical MDS: esta versión de MDS tiene como objetivo preservar la medida de distancia/disimilitud por pares tanto como sea posible.
  • MDS no métrico: este método es aplicable cuando solo se conocen los rangos de una métrica de disimilitud. Luego, MDS mapea los objetos para que los rangos se conserven tanto como sea posible.

Realización de escalamiento multidimensional en Python con Scikit-Learn

El módulo sklearn.manifold de la biblioteca Scikit-Learn implementa múltiples técnicas de aprendizaje e integración de datos. Usaremos la clase MDS de este módulo. Las incrustaciones se determinan usando el algoritmo de minimización de estrés usando mayorización (SMACOF). Algunos de los parámetros importantes para configurar el objeto MDS son (esta no es una lista exhaustiva):

  • n_components: Número de dimensiones a mapear los puntos. El valor predeterminado es 2.
  • metric: una variable booleana con un valor predeterminado de True para MDS métrico y False para su versión no métrica.
  • disimilarity: el valor predeterminado es euclidean, que especifica las distancias euclidianas por pares. El otro valor posible es precalculado. El uso de precalculado requiere el cálculo de la matriz de distancia por pares y el uso de esta matriz como entrada para la función fit() o fit_transform().

Los cuatro atributos asociados con un objeto MDS son:

  • embedding_: Ubicación de los puntos en el nuevo espacio.
  • stress_: Estadístico de bondad de ajuste utilizado en MDS.
  • disimilitud_matrix_: La matriz de distancias/disimilitud por pares.
  • n_iter_: Número de iteraciones correspondientes a la mejor medida de bondad de ajuste.

Como todas las demás clases para la reducción de dimensionalidad en scikit-learn, la clase MDS también implementa los métodos fit() y fit_transform().

Una ilustración simple

En esta sección, mostramos cómo aplicar MDS usando un ejemplo muy simple. Agregaremos la sección de importación primero:

1
2
3
4
5
6
7
from sklearn.manifold import MDS
from matplotlib import pyplot as plt
import sklearn.datasets as dt
import seaborn as sns         
import numpy as np
from sklearn.metrics.pairwise import manhattan_distances, euclidean_distances
from matplotlib.offsetbox import OffsetImage, AnnotationBbox

El siguiente código configura un objeto MDS y llama a su método fit_transform(). Este método devuelve los puntos incrustados en el espacio 2D. Imprimamos el mapeo resultante:

1
2
3
4
X = np.array([[0, 0, 0], [0, 0, 1], [1, 1, 1], [0, 1, 0], [0, 1, 1]])
mds = MDS(random_state=0)
X_transform = mds.fit_transform(X)
print(X_transform)
1
2
3
4
5
[[ 0.72521687  0.52943352]
 [ 0.61640884 -0.48411805]
 [-0.9113603  -0.47905115]
 [-0.2190564   0.71505714]
 [-0.21120901 -0.28132146]]

Dado que las incrustaciones se crean en función del algoritmo de minimización de estrés, también podemos echar un vistazo a la variable estrés:

1
2
stress = mds.stress_
print(stress)

Esto resulta en:

1
0.18216844548575467

Otro método para aplicar MDS es construir una matriz de distancia y aplicar MDS directamente a esta matriz, como se muestra en el código a continuación. Este método es útil cuando se requiere una medida de distancia que no sea la distancia euclidiana. El siguiente código calcula las distancias de Manhattan por pares (también denominada distancia de bloque de ciudad o distancia L1) y transforma los datos a través de MDS.

Tenga en cuenta que el argumento disimilitud se ha establecido en precalculada:

1
2
3
4
dist_manhattan = manhattan_distances(X)
mds = MDS(dissimilarity='precomputed', random_state=0)
# Get the embeddings
X_transform_L1 = mds.fit_transform(dist_manhattan)

Esto resulta en:

1
2
3
4
5
[[ 0.9847767   0.84738596]
 [ 0.81047787 -0.37601578]
 [-1.104849   -1.06040621]
 [-0.29311254  0.87364759]
 [-0.39729303 -0.28461157]]

Sin embargo, esto no nos ayuda a obtener una buena intuición de lo que acaba de suceder. Los humanos no son tan buenos procesando números. Para obtener una mejor comprensión de todo el proceso, tracemos los puntos originales y sus incrustaciones creadas al preservar las distancias euclidianas. Un punto original y su punto incrustado correspondiente se muestran en el mismo color:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
colors = ['r', 'g', 'b', 'c', 'm']
size = [64, 64, 64, 64, 64]
fig = plt.figure(2, (10,4))
ax = fig.add_subplot(121, projection='3d')
plt.scatter(X[:,0], X[:,1], zs=X[:,2], s=size, c=colors)
plt.title('Original Points')

ax = fig.add_subplot(122)
plt.scatter(X_transform[:,0], X_transform[:,1], s=size, c=colors)
plt.title('Embedding in 2D')
fig.subplots_adjust(wspace=.4, hspace=0.5)
plt.show()

asignación de 3d a 2d con escalado multidimensional

El gráfico de la derecha mantiene las distancias relativas generalmente intactas: el púrpura, el verde y el azul están muy juntos, y su posición relativa entre sí es aproximadamente la misma en comparación con el cian y el rojo.

Escalado multidimensional práctico en Olivetti Faces Dataset de AT&T

Como ilustración práctica de MDS, usaremos Olivetti se enfrenta al conjunto de datos de AT&T para mostrar las incrustaciones en un espacio con dimensiones tan bajas como 2D. El conjunto de datos tiene 10 imágenes de mapa de bits de 64x64 por persona, cada imagen adquirida con diferentes expresiones faciales o condiciones de iluminación.

MDS conserva los patrones en los datos para que diferentes imágenes de caras de la misma persona estén cerca unas de otras en el espacio 2D y lejos de la cara mapeada de otra persona.

Para evitar el desorden, tomaremos solo las caras de 4 personas distintas y les aplicaremos MDS.

Antes de obtener el conjunto de datos y aplicar MDS, escribamos una pequeña función, mapData(), que tome los argumentos de entrada, es decir, la matriz de distancia por pares dist_matrix, la matriz de datos sin procesar X, la variable de clase y, la variable booleana metric y title para el gráfico.

La función aplica MDS a la matriz de distancia y muestra los puntos transformados en un espacio 2D, con los mismos puntos de color que indican la imagen asignada de la misma persona. En una segunda figura, también muestra la imagen de cada cara en el gráfico donde se mapea en el espacio de menor dimensión.

Demostraremos MDS con diferentes medidas de distancia junto con MDS no métrico:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def mapData(dist_matrix, X, y, metric, title):
    mds = MDS(metric=metric, dissimilarity='precomputed', random_state=0)
    # Get the embeddings
    pts = mds.fit_transform(dist_matrix)
    # Plot the embedding, colored according to the class of the points
    fig = plt.figure(2, (15,6))
    ax = fig.add_subplot(1,2,1)    
    ax = sns.scatterplot(x=pts[:, 0], y=pts[:, 1],
                         hue=y, palette=['r', 'g', 'b', 'c'])

    # Add the second plot
    ax = fig.add_subplot(1,2,2)
    # Plot the points again
    plt.scatter(pts[:, 0], pts[:, 1])
    
    # Annotate each point by its corresponding face image
    for x, ind in zip(X, range(pts.shape[0])):
        im = x.reshape(64,64)
        imagebox = OffsetImage(im, zoom=0.3, cmap=plt.cm.gray)
        i = pts[ind, 0]
        j = pts[ind, 1]
        ab = AnnotationBbox(imagebox, (i, j), frameon=False)
        ax.add_artist(ab)
    plt.title(title)    
    plt.show()

El siguiente código obtiene el conjunto de datos de rostros de Olivetti y extrae ejemplos con etiquetas < 4:

1
2
3
4
5
6
faces = dt.fetch_olivetti_faces()
X_faces = faces.data
y_faces = faces.target
ind = y_faces < 4
X_faces = X_faces[ind,:]
y_faces = y_faces[ind]

Y sin más preámbulos, ¡carguemos los datos y ejecutemos nuestra función mapData() en ellos!

Usando las distancias Euclidianas por pares

El mapeo del conjunto de datos de caras de Olivetti utilizando distancias euclidianas se muestra a continuación. La distancia euclidiana es la distancia predeterminada para MDS debido a lo versátil y comúnmente utilizada que es:

1
2
3
dist_euclid = euclidean_distances(X_faces)
mapData(dist_euclid, X_faces, y_faces, True, 
        'Metric MDS with Euclidean')

euclidean multidimensional scaling

Podemos ver un buen mapeo de imágenes de 64x64 a un espacio bidimensional, donde la clase de cada imagen está bien separada del resto en la mayoría de los casos. Vale la pena tomarse un momento para apreciar el hecho de que las imágenes que residen en un espacio de dimensión de 64x64 pueden reducirse a un espacio de dos dimensiones y aun así conservar su valor informativo.

Uso de las distancias por pares de Manhattan

A modo de comparación, podemos realizar MDS en los mismos datos utilizando las distancias por pares de Manhattan. El siguiente código usa la matriz de distancia de Manhattan como entrada para mapData():

1
2
3
dist_L1 = manhattan_distances(X_faces)
mapData(dist_L1, X_faces, y_faces, True, 
        'Metric MDS with Manhattan')

escalamiento multidimensional de mahnattan

Podemos ver que el mapeo es bastante similar al obtenido a través de distancias euclidianas. Cada clase está muy bien separada en el espacio de dimensiones inferiores, aunque están compensadas un poco de manera diferente en la trama.

Realización de escalamiento multidimensional no métrico

Como ejemplo final, mostraremos MDS no métricos en el mismo conjunto de datos utilizando distancias euclidianas y veremos cómo se compara con la versión métrica correspondiente:

1
2
mapData(dist_euclid, X_faces, y_faces, False, 
        'Non-metric MDS with Euclidean')

escalado multidimensional no métrico

Hay bastantes contratiempos aquí. Podemos ver que esta versión de MDS no funciona tan bien en el conjunto de datos de caras de Olivetti.

Esto se debe principalmente a la naturaleza cuantitativa de los datos.

El MDS no métrico mantiene las distancias clasificadas entre objetos en lugar de las distancias reales.

El parámetro n_components en MDS

Uno de los hiperparámetros importantes involucrados en MDS es el tamaño del espacio de menor dimensión en el que están incrustados los puntos.

Esto sería muy relevante cuando se utiliza MDS como un paso de preprocesamiento para la reducción de la dimensionalidad.

Surge la pregunta:

¿Cuántas dimensiones elige para reducir la dimensionalidad lo más que pueda, sin perder información importante?

Un método simple para elegir un valor de este parámetro es ejecutar MDS en diferentes valores de n_components y trazar el valor stress_ para cada incrustación. Dado que el valor de stress_ disminuye con dimensiones más altas, elige un punto que tenga una compensación justa entre stress_ y n_components.

El siguiente código ejecuta MDS variando las dimensiones de 1 a 20 y traza el atributo stress_ correspondiente para cada incrustación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
stress = []
# Max value for n_components
max_range = 21
for dim in range(1, max_range):
    # Set up the MDS object
    mds = MDS(n_components=dim, dissimilarity='precomputed', random_state=0)
    # Apply MDS
    pts = mds.fit_transform(dist_euclid)
    # Retrieve the stress value
    stress.append(mds.stress_)
# Plot stress vs. n_components    
plt.plot(range(1, max_range), stress)
plt.xticks(range(1, max_range, 2))
plt.xlabel('n_components')
plt.ylabel('stress')
plt.show()

encontrar el número correcto de compo

Podemos ver que aumentar el valor de n_components disminuye el valor de la tensión al principio y luego la curva se estabiliza. Casi no hay diferencia entre 18 y 19 dimensiones, pero hay una diferencia enorme entre 1 y 2 dimensiones.

El codo de la curva es una buena elección para el valor óptimo de n_components. En este caso, el valor se puede tomar en 4, que es una sorprendente reducción del 0,09 % de características/atributos.

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 calcular 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 superficiales y Aprendizaje profundo para ajustarse a los datos que hemos explorado y limpiado previamente.

Conclusiones

Esta guía fue una introducción a Escalado multidimensional en Python, utilizando Scikit-Learn. Hemos echado un vistazo a cómo funciona el escalamiento multidimensional, sus hiperparámetros, qué variaciones existen y luego lo aplicamos en un conjunto de datos práctico.

Usamos el conjunto de datos Olivetti Faces, de AT&T, e ilustramos que las imágenes que residen en un espacio dimensional de 64x64 se pueden asignar a un espacio bidimensional, y todavía conservan los patrones individuales o grupos en las imágenes.