¿Demasiadas características? Veamos el Análisis de Componentes Principales
Too many features? Let's try Principal Component Analysis.
Serie de modelos de aprendizaje automático caseros
¡El repositorio complementario está disponible aquí!
La maldición de la dimensionalidad es uno de los principales problemas en el aprendizaje automático. A medida que aumenta el número de características, aumenta la complejidad del modelo. Además, si no hay suficientes datos de entrenamiento, resulta en sobreajuste.
En esta entrada, se presentará el Análisis de Componentes Principales (PCA). Primero, explicaré por qué demasiadas características son un problema. Luego, la matemática detrás de PCA y por qué funciona. Además, PCA se desglosará en pasos, acompañados de ejemplos visuales y fragmentos de código. Además, se explicarán las ventajas y desventajas de PCA. Por último, estos pasos se encapsularán en una clase de Python para su uso posterior.
Nota para el lector: Si no está interesado en la explicación matemática y solo desea ver los ejemplos prácticos y cómo funciona PCA, salte a la sección “PCA en la práctica”. Si solo está interesado en la clase de Python, diríjase a “Implementación de PCA casera”.
¿Cuál es el problema con demasiadas características?
Eche un vistazo al espacio de características en la Figura 1. Hay pocos ejemplos para llenar todo el espacio, por lo que un modelo de estos datos podría no generalizarse bien a nuevos ejemplos no vistos.
- Más allá de los números El papel crucial de las habilidades blandas...
- Explorando tendencias y patrones de conflicto Análisis de datos ACL...
- Acelere el tiempo para obtener conocimientos empresariales con la c...
![Figura 1. Un ejemplo de espacio de características bidimensional. De mi propia autoría, inspirado en [1].](https://miro.medium.com/v2/resize:fit:640/format:webp/1*cgSYspiNhnpbu7bHIumTBA.png)
¿Qué sucede si agregamos otra característica? Echemos un vistazo al nuevo espacio de características en la Fig. 2. Puede ver que hay aún más espacio vacío que en el ejemplo anterior. A medida que aumenta el número de características, el modelo sobreajustará los datos actuales. Es por eso que existen técnicas para reducir la dimensionalidad de los datos y aliviar este problema. [1]
![El mismo ejemplo con una característica adicional. De mi propia autoría, inspirado en [1].](https://miro.medium.com/v2/resize:fit:640/format:webp/1*6uhA2HhNaGEK8cFhJ6Nbqw.png)
¿Cuál es el objetivo de PCA?
En pocas palabras, el propósito de PCA es extraer nuevas características no correlacionadas de dimensiones más bajas que maximicen la cantidad de información conservada de los datos originales. La medida de la información en este contexto es la variación. Veamos por qué:
Esta técnica se basa en la suposición de que nuestro punto de datos d-dimensional x puede ser representado por una combinación lineal de vectores de una base ortonormal [1]:
No se preocupe, explicaré de dónde obtenemos los vectores de dicha base más adelante. Además, podemos extraer una representación x̂ usando m de los d vectores en la combinación (m < d):
Por supuesto, no obtenemos una representación exacta ya que hay menos características, pero al menos intentamos minimizar la pérdida de información. Definamos el Error Cuadrático Medio (MSE) entre el ejemplo original x y la aproximación x̂:
Como las sumatorias usan las mismas variables con diferentes cortes, entonces la diferencia es solo el desplazamiento:
Sabemos por nuestra hipótesis inicial que x es la suma de vectores ortogonales. Por lo tanto, el producto punto de estos vectores es cero y cada una de sus normas euclidianas es uno. Así:
Resolviendo el valor de importancia yi:
Insertando ese resultado en su esperanza:
Podemos ver que si xi está centrado (media igual a cero), entonces la esperanza resulta ser la matriz de covarianza de todos los datos, y este resultado no es más que la varianza en el espacio original. Al elegir los vectores adecuados vi que maximizan la varianza, efectivamente minimizaremos el error de representación.
¿De dónde viene esta base ortonormal?
Como se dijo anteriormente, queremos obtener m vectores que maximicen la varianza:
Si tomamos toda la matriz de datos, se puede ver que vi es una dirección de proyección. Los datos se proyectarán en un espacio de menor dimensionalidad.
Si diagonalizamos la matriz de covarianza Σ usando la descomposición espectral obtenemos:
Donde U es una matriz con los vectores propios normalizados de Σ y Λ es una matriz diagonal que contiene los valores propios de Σ en orden descendente. Esto es posible ya que Σ es una matriz real y simétrica.
Además, como Λ solo contiene valores no nulos en la diagonal, podemos reescribir la ecuación anterior como:
con:
Observe que los vectores en U y el vector v están normalizados . Por lo tanto, al realizar el producto punto cuadrado de cada v con a, obtenemos un valor entre [0,1] y, por lo tanto, w también debe ser un vector normalizado:
De aquí surgen propiedades interesantes.
El primer componente principal
Recuerde el problema de optimización. Dado que los valores propios están ordenados y w debe ser un vector normalizado, nuestra mejor opción es obtener el primer vector propio con w = (1,0,0,…). Como consecuencia, el límite superior se alcanza cuando:
¡La dirección de proyección que maximiza la varianza resulta ser el autovector asociado con el mayor valor propio!
El resto de los componentes
Una vez que se establece el primer componente principal, se agrega una nueva restricción al problema de optimización:
Esto significa que el nuevo componente v2 debe ser ortogonal al componente anterior, el autovector u1, para que la información no sea redundante. Se puede demostrar que todos los d componentes corresponden a los d autovectores normalizados de Σ asociados con los valores propios, en orden descendente. Eche un vistazo a estas notas para la prueba formal de esta afirmación [2].
PCA en la práctica
A partir de la descripción teórica anterior, se pueden describir los pasos necesarios para obtener los componentes principales de un conjunto de datos. Sea el conjunto de datos inicial una muestra aleatoria de la siguiente distribución normal 2D:
from scipy import statsmean = [3,3]var = [[6, 3], [3, 3.5]]n = 100data_raw = np.random.multivariate_normal(mean, var, 100)

1. Centrar los datos
El primer paso es mover la nube al origen del sistema de coordenadas para que los datos tengan media cero. Este paso se realiza restando la media de la muestra de cada punto en el conjunto de datos.
import numpy as npdata_centered = data_raw - np.mean(data_raw, axis=0)

2. Calcular la matriz de covarianza
La varianza definida anteriormente es la matriz de covarianza poblacional Σ. En la práctica, esa información no está disponible para nosotros, ya que solo tenemos una muestra. Por lo tanto, podemos aproximar ese parámetro utilizando la covarianza de muestra S.
Recuerde que los datos ya están centrados. Por lo tanto:
Podemos escribir esto de manera compacta usando multiplicaciones de matrices. Esto también puede ayudarnos a vectorizar los cálculos:
cov_mat = np.matmul(data_centered.T, data_centered)/(len(data_centered) - 1)# > array([[5.62390186, 2.47275007],# > [2.47275007, 3.19395349]])
La razón por la que se pasa la matriz transpuesta como primer argumento en el código es que, en la formulación matemática de la matriz de datos, las características están en las filas y los sujetos en las columnas. En la implementación, ocurre lo contrario, ya que en casi todos los sistemas, los eventos, sujetos, registros, etc., se almacenan en filas.
3. Realizar la descomposición en valores y vectores propios de la matriz de covarianza
Los autovalores y autovectores a se calculan utilizando eig()
de scipy:
from scipy.linalg import eigheigvals, eigvecs = eigh(cov_mat)# Ordenando los autovalores y autovectoresindices = eigvals.argsort()[::-1]eigvals, eigvecs = eigvals[indices], eigvecs[:,indices]eigvecs# > array([[-0.82348021, 0.56734499],# > [-0.56734499, -0.82348021]])
Como se explicó anteriormente, los autovalores representan la varianza de los componentes principales, y los autovectores son las direcciones de proyección:

Se puede ver que se crea un nuevo sistema de coordenadas utilizando las direcciones de los componentes principales. Además, los autovalores y autovectores deben almacenarse para transformar nuevos datos posteriormente.
4. Imponer determinismo
Los coeficientes de los autovectores siempre serán los mismos, excepto por su signo. PCA puede tener múltiples orientaciones válidas. Por lo tanto, debemos imponer un resultado determinista tomando la matriz de autovectores y, para cada una de sus columnas, aplicando el signo del valor absoluto más grande dentro de esa columna.
max_abs_cols = np.argmax(np.abs(eigvecs), axis=0)signs = np.sign(eigvecs[max_abs_cols, range(eigvecs.shape[1])])eigvecs = eigvecs*signseigvecs# > array([[ 0.82348021, -0.56734499],# > [ 0.56734499, 0.82348021]])
5. Extraer las nuevas características
Cada nueva característica (componente principal) se extrae realizando el producto punto entre cada punto en el espacio de características original y el autovector:
new_features = np.dot(data_centered, eigvecs)
Para este ejemplo en particular, después de calcular los componentes, los nuevos puntos en el espacio se representan de la siguiente manera:

Se puede observar que este resultado es básicamente una rotación del conjunto de puntos original de tal manera que los atributos estén no correlacionados.
6. Reducir la dimensionalidad
Hasta ahora, se calcularon los componentes principales en su totalidad para comprenderlos de manera visual. Lo que queda es elegir cuántos componentes se necesitan. Recurrimos a los autovalores para esta tarea, ya que representan la varianza de cada componente principal.
La relación entre la varianza retenida por el componente i se da por:
Y la relación de la varianza preservada al elegir m componentes se da por:
Si visualizamos la varianza de cada componente en nuestro ejemplo, llegamos al siguiente resultado:
# Varianza de cada componente individual como barrasplt.bar( [f"PC_{i}" for i in range(1,len(eigvals)+1)], eigvals/sum(eigvals))# Porcentaje retenido por m componentes como la líneaplt.plot( [f"PC_{i}" for i in range(1,len(eigvals)+1)], np.cumsum(eigvals)/sum(eigvals), color='red')plt.scatter( [f"PC_{i}" for i in range(1,len(eigvals)+1)], np.cumsum(eigvals)/sum(eigvals), color='red')

En este caso, PC1 representa el 80% de la varianza de los datos originales, mientras que el 20% restante pertenece a PC2. Además, podemos elegir usar solo el primer componente principal, en cuyo caso los datos se verían así:

Esta es la proyección de los datos en la dirección del primer eigenvector. En este momento no parece muy útil. ¿Qué sucedería si en su lugar elegimos datos que pertenecen a tres clases? ¿Cómo se vería el PCA?
PCA en datos de múltiples clases
Creemos un conjunto de datos con tres clases que puedan ser linealmente separables:
from sklearn.datasets import make_blobsX, y = make_blobs()plt.scatter(X[:,0], X[:,1],c=y)plt.legend()plt.show()

Si aplicamos PCA a los datos anteriores, esta sería la trama de los componentes principales:

Y esta sería la trama del primer componente (la proyección de los datos en la dirección del eigenvector correspondiente al eigenvalor más grande):

¡Funciona! Los datos aún parecen fácilmente separables por un modelo lineal.
Ventajas y desventajas
Como todo en la ciencia, no hay una solución universal. A continuación, se muestra una lista de ventajas y desventajas que debe tener en cuenta antes de utilizar PCA con datos del mundo real.
Ventajas de PCA
- Reducción de dimensionalidad: PCA permite la reducción de datos de alta dimensionalidad en un espacio de menor dimensión mientras se conserva la mayor parte de la información importante. Esto puede ser útil para la visualización de datos, eficiencia computacional y para tratar con la maldición de la dimensionalidad.
- Decorrelación: PCA transforma las variables originales en un nuevo conjunto de variables no correlacionadas llamadas componentes principales. Esta decorrelación simplifica el análisis y puede mejorar el rendimiento de los algoritmos de aprendizaje automático que asumen la independencia de las características.
- Reducción de ruido: La representación de menor dimensión obtenida a través de PCA tiende a filtrar el ruido y enfocarse en las variaciones más significativas en los datos. Esto puede mejorar la relación señal-ruido y mejorar la robustez de los análisis posteriores.
Desventajas de PCA
- Suposición de linealidad: PCA asume que las relaciones de datos subyacentes son lineales. Si los datos tienen relaciones no lineales complejas, PCA puede no capturar las variaciones más significativas y podría proporcionar resultados subóptimos.
- Interpretación: Los componentes principales obtenidos a partir de PCA son combinaciones lineales de las características originales. Puede ser difícil relacionar los componentes principales de vuelta a las variables originales y comprender su significado exacto.
- Sensibilidad a la escalación: PCA es sensible a la escalación de las variables de entrada. Si las variables tienen diferentes escalas, aquellas con mayores varianzas pueden dominar el análisis, lo que potencialmente lleva a resultados sesgados. La escalación adecuada de características es crucial para obtener resultados confiables con PCA.
- Valores atípicos: PCA es sensible a los valores atípicos ya que se enfoca en capturar la varianza en los datos. Los valores atípicos pueden influir significativamente en los componentes principales y distorsionar los resultados.
Implementación de PCA casera
Ahora que hemos cubierto los detalles del Análisis de Componentes Principales, todo lo que queda es crear una clase que encapsule el comportamiento mencionado anteriormente y que pueda ser reutilizada en problemas futuros.
Para esta implementación, se utilizará la interfaz de scikit-learn, que tiene los siguientes métodos:
fit()
transform()
fit_transform()
Constructor
No se necesita lógica compleja. El constructor solo definirá el número de componentes (características) que tendrá los datos transformados.
import numpy as npfrom scipy.linalg import eighclass PCA: """Análisis de Componentes Principales. """ def __init__(self, n_componentes): """Constructor de la clase PCA. Parameters: =========== n_componentes: int El número de dimensiones para los datos transformados. Debe ser menor o igual a n_características. """ self.n_componentes = n_componentes self._instancia_fit = False
El método fit
El método fit aplicará los pasos 1-4 de la sección anterior.
- Centrar los datos
- Calcular la matriz de covarianza
- Calcular los valores y vectores propios y ordenarlos
- Imponer el determinismo invirtiendo los signos de los vectores propios
También almacenará los valores y vectores propios, así como la media de la muestra, como atributos del objeto para transformar nuevos datos más tarde.
def fit(self, X): """Calcular los vectores propios para transformar los datos más tarde. Parameters: =========== X: np.array de forma [n_ejemplos, n_características] La matriz de datos. Returns: =========== None. """ # Ajusta la media de los datos y centralízalos self.media = np.mean(X, axis=0) X_centralizado = X - self.media # Calcular la matriz de covarianza cov_mat = np.matmul(X_centralizado.T, X_centralizado)/(len(X_centralizado) - 1) # Calcular los valores propios, vectores propios y ordenarlos valores_propios, vectores_propios = eigh(cov_mat) self.valores_propios, self.vectores_propios = self._ordenar_vectores_propios(valores_propios, vectores_propios) # Obtén las proporciones de varianza explicadas self.proporciones_varianza_explicada = self.valores_propios/np.sum(self.valores_propios) # Imponer el determinismo invirtiendo los vectores propios self.vectores_propios = self._invertir_vectores_propios(self.vectores_propios)[:, :self.n_componentes] self._instancia_fit = True
El método transform
Aplicará los pasos 1, 5 y 6:
- Centrar los nuevos datos utilizando la media de la muestra almacenada.
- Extraer las nuevas características de PC.
- Reducir la dimensionalidad seleccionando
n_components
dimensiones.
def transform(self, X): """Proyectar los datos en las direcciones de los eigenvectores. Parámetros: =========== X: np.array de forma [n_ejemplos, n_características] La matriz de datos Retorna: =========== pcs: np.array[n_ejemplos, n_componentes] Las nuevas características no correlacionadas de PCA. """ if not self._fit_instance: raise Exception("¡PCA primero debe ajustarse a los datos! Llame a fit()") X_centro = X - self.media return np.dot(X_centro, self.eigenvectores)
El método fit_transform
Por simplicidad de implementación, este método aplicará primero la función fit()
y luego transform()
. Estoy seguro de que puedes idear una definición más inteligente.
def fit_transform(self, X): """Ajusta PCA y transforma los datos. """ self.fit(X) return self.transform(X)
Funciones auxiliares
Estos métodos se definieron como componentes separados, en lugar de aplicar todos los pasos en la función fit()
para hacer que el código sea más legible y mantenible.
def _flip_eigenvectors(self, eigenvectors): """Asegurar la determinación cambiando los signos de los eigenvectores. """ max_abs_cols = np.argmax(np.abs(eigenvectors), axis=0) signs = np.sign(eigenvectors[max_abs_cols, range(eigenvectors.shape[1])]) return eigenvectors*signs def _sort_eigen(self, eigenvalues, eigenvectors): """Ordenar los eigenvalues en orden descendente y sus correspondientes eigenvectores. """ indices = eigenvalues.argsort()[::-1] return eigenvalues[indices], eigenvectors[:, indices]
Probando la clase
Utilicemos el ejemplo anterior con nuestra clase PCA
:
from pca import PCA# Utilizando nuestra implementación de PCApca = PCA(n_components=1)X_transformado = pca.fit_transform(X)# Graficando el primer PCplt.scatter(X_transformado[:,0], [0]*len(X_transformado),c=y)plt.legend()plt.show()

Conclusión
Tener muchas características con pocos datos puede ser perjudicial y, muy probablemente, resultará en sobreajuste. El Análisis de Componentes Principales es una herramienta que puede ayudar a aliviar este problema. Es una técnica de reducción de dimensionalidad que funciona encontrando direcciones de proyección para los datos de manera que se preserve la variabilidad original tanto como sea posible, y las características resultantes no estén correlacionadas. Además, se puede medir la varianza explicada por cada nueva característica, o componente principal. Luego, el usuario puede elegir cuántos componentes principales y cuánta varianza son suficientes para la tarea. Finalmente, asegúrate de conocer tus datos primero, ya que PCA funciona con muestras que pueden ser separables linealmente y puede ser sensible a los valores atípicos.
Referencias
[1] Fernández, A. Reducción de dimensionalidad. Universidad Autónoma de Madrid. Madrid, España. 2022.
[2] Berrendero, J. R. Regresión lineal con datos de alta dimensión. Universidad Autónoma de Madrid. Madrid, España. 2022.
¡Conéctate conmigo en LinkedIn!