Inestabilidad numérica de cálculo de matriz de covarianza inversa

8

Tengo 65 muestras de datos de 21 dimensiones (pegados aquí ) y estoy construyendo la matriz de covarianza a partir de ellos. Cuando se calcula en C ++, obtengo la matriz de covarianza pegada aquí . Y cuando se calcula en matlab a partir de los datos (como se muestra a continuación) obtengo la matriz de covarianza pegada aquí

Código de Matlab para calcular cov a partir de datos:

data = csvread('path/to/data');
matlab_cov = cov(data);

Como puede ver, la diferencia en las matrices de covarianza es minuto (~ e-07), lo que probablemente se deba a problemas numéricos en el compilador que usa aritmética de coma flotante.

Sin embargo, cuando calculo la matriz de covarianza pseudo-inversa de la matriz de covarianza producida por matlab y la producida por mi código C ++, obtengo resultados muy diferentes. Los estoy computando de la misma manera, es decir:

data = csvread('path/to/data');
matlab_cov = cov(data);
my_cov = csvread('path/to/cov_file');
matlab_inv = pinv(matlab_cov);
my_inv = pinv(my_cov);

La diferencia es tan grande que cuando calculo la distancia mahalanobis de una muestra (pegada aquí ) a la distribución de las 65 muestras por:

(65/642)×((samplemean)×1×(samplemean))

usando las diferentes matrices de covarianza inversa ( ) obtengo resultados muy diferentes, es decir:1

 (65/(64^2))*((sample-sample_mean)*my_inv*(sample-sample_mean)')
ans =

   1.0167e+05

(65/(64^2))*((sample-sample_mean)*matlab_inv*(sample-sample_mean)')
ans =

  109.9612

¿Es normal que las pequeñas diferencias (e-7) en la matriz de covarianza tengan tal efecto en el cálculo de la matriz pseudo-inversa? Y si es así, ¿qué puedo hacer para mitigar este efecto?

De lo contrario, ¿hay alguna otra métrica de distancia que pueda usar que no implique la covarianza inversa? Utilizo la distancia de Mahalanobis como sabemos para n muestras, sigue una distribución beta, que utilizo para la prueba de hipótesis

Muchas gracias de antemano

EDIT: Adición de código C ++ para el cálculo de matriz de covarianza a continuación: El vector<vector<double> >representa la colección de filas desde el archivo de pegado.

Mat covariance_matrix = Mat(21, 21, CV_32FC1, cv::Scalar(0));
    for(int j = 0; j < 21; j++){
        for(int k = 0; k < 21; k++){
            for(std::vector<vector<double> >::iterator it = data.begin(); it!= data.end(); it++){
                covariance_matrix.at<float>(j,k) += (it->at(j) - mean.at(j)) * (it->at(k) - mean[k]);
            }
            covariance_matrix.at<float>(j,k) /= 64; 
        }
    }
Aly
fuente
Invertir matrices ... ¡Eso es algo peligroso! Por lo general, es preferible encontrar alternativas a eso (por ejemplo, pseudoinverso)
Ander Biguri
1
@Aly: las matrices que está buscando invertir no son matrices de covarianzas "válidas" porque no son definitivas positivas; numéricamente incluso tienen algunos valores propios que son negativos (pero cercanos a cero). Probablemente agregaría una constante muy pequeña a lo largo de la diagonal; Es una forma de corrección de Tikhonov realmente ( ). Tampoco use flotantes, use dobles para almacenar su matriz de covarianza. (Y además de que ya usa OpenCV, también podría usar Eigen o Armadillo ...)Χ+λI
usεr11852
1
@Aly: Wikipedia, de verdad. (es el lema: regularización de Tikhonov). El método que mencionó Whuber usando el SVD le dará una matriz definida no negativa si establece pequeños valores propios en cero; aún necesitará agregar una pequeña constante a todos sus valores propios para que sean definitivos positivos. Prácticamente ambos métodos hacen lo mismo. Acabo de recurrir a no usar el SVD, pero afecta directamente los valores propios de las muestras agregando a todos ellos. No he encontrado ninguna referencia, creo que ambos métodos son bastante intuitivos. λ
usεr11852
1
@ user11852 Por favor, ¿puedes responder a tus comentarios? Todavía estoy experimentando, pero si lo prometes, lo aceptaré. Además, si los demás hacen sus sugerencias respuestas que va a voto, ya que han sido muy útil / útil a mi comprensión del problema
Aly
1
Comenté en su otro hilo que tener variables que suman 1 , como lo hace su conjunto de datos, fomenta la inestabilidad y contiene una variable redundante. Intenta soltar una columna. Ni siquiera necesita el pinv: la matriz de covarianza ya no es singular.
Cam.Davidson.Pilon

Respuestas:

7

Las matrices que está buscando invertir no son matrices de covarianzas "válidas" porque no son definitivas positivas; numéricamente incluso tienen algunos valores propios que son negativos (pero cercanos a cero). Esto probablemente se deba a ceros de máquina, por ejemplo, el último valor propio de su matriz "matlab_covarnce" es -0.000000016313723. Para corregir a definitivo positivo, puede hacer dos cosas:

  1. Simplemente agregue una constante muy pequeña a lo largo de la diagonal; una forma de corrección de Tikhonov realmente ( con ).Χ+λIλ0
  2. (Basado en lo que propuso Whuber) Use SVD, establezca los valores propios "problemáticos" en un valor pequeño fijo (no cero), reconstruya su matriz de covarianza y luego invierta eso. Claramente, si establece algunos de esos valores propios en cero, terminará con una matriz no negativa (o semi-positiva), que aún no será invertible.

Una matriz no negativa no tiene un inverso, pero tiene un pseudo inverso (todas las matrices con entradas reales o complejas tienen un pseudo inverso), sin embargo, el pseudo inverso de Moore-Penrose es más costoso desde el punto de vista computacional que un inverso verdadero y si lo inverso existe es igual a lo pseudoinverso. Así que solo ve por el inverso :)

Ambos métodos prácticamente intentan manejar los valores propios que se evalúan a cero (o por debajo de cero). El primer método es un poco ondulado pero probablemente mucho más rápido de implementar. Para algo un poco más estable, es posible que desee calcular el SVD y luego configurarλ igual al absoluto del valor propio más pequeño (para que no sea negativo) más algo muy pequeño (para que sea positivo). Solo tenga cuidado de no imponer positividad a una matriz que es obviamente negativa (o ya positiva). Ambos métodos alterarán el número de acondicionamiento de su matriz.

En términos estadísticos, qué haces al agregar queλ través de la diagonal de su matriz de covarianza agrega ruido a sus mediciones. (Debido a que la diagonal de la matriz de covarianza es la varianza de cada punto y al agregar algo a esos valores, simplemente dice "la varianza en los puntos para los que tengo lecturas es en realidad un poco más grande de lo que pensé originalmente").

Una prueba rápida para la definición positiva de una matriz es la existencia (o no) de la descomposición de Cholesky.

También como una nota computacional:

  1. No use flotantes, use dobles para almacenar su matriz de covarianza.
  2. Use bibliotecas de álgebra lineal numérica en C ++ (como Eigen o Armadillo) para obtener inversas de matrices, productos de matriz, etc. Es más rápido, más seguro y más conciso.

EDITAR: dado que tiene una descomposición Cholesky de su matriz manera que (debe hacer eso para verificar que tiene una matriz Pos.Def.), Debería poder resolver inmediatamente el sistema . Simplemente resuelve Ly = b para y mediante sustitución hacia adelante, y luego L ^ Tx = y para x mediante sustitución hacia atrás. (En eigen simplemente use el método .solve (x) de su objeto Cholesky) Gracias a bnaul y Zen por señalar que me enfoqué tanto en hacer que sea ​​Pos.Def. que olvidé por qué nos preocupamos por eso en primer lugar :)KLLTKx=bK

usεr11852
fuente
3
+1. Usando Mathematica y su aplicación a la de datos (en lugar de la matriz de covarianza publicado, que puede haber sido presentado con muy poca precisión) Encuentro no hay valores propios negativos. Así es como debería ser: cuando una matriz de covarianza se calcula exactamente, se garantiza una semifinalidad positiva, por lo que cualquier valor propio negativo debe atribuirse a la imprecisión en los cálculos. Cualquier procedimiento inverso generalizado decente debe "reconocer" esos pequeños valores negativos como ceros y tratarlos en consecuencia.
whuber
Gracias a todos por el esfuerzo, como dije, he votado y probaré esto y comentaré o aceptaré en consecuencia.
Aly
Lo siento, estoy un poco confundido, ¿cómo la resolución de Cholesky da uso a la distancia de Mahalanobis?
Aly
Verifique el enlace en la publicación original de bnaul. Pero no use sino Cholesky (eso es lo que quieren decir con LDL *). LU
usεr11852
2

Las respuestas publicadas y los comentarios tienen buenos puntos sobre los peligros de invertir matrices casi singulares. Sin embargo, hasta donde puedo decir, nadie ha mencionado que calcular la distancia de Mahalanobis en realidad no requiere invertir la covarianza de la muestra. Consulte esta pregunta de StackOverflow para obtener una descripción de cómo hacerlo utilizando la descomposición de .LU

El principio es el mismo que resolver un sistema lineal: cuando se trata de resolver para tal que , existen métodos mucho más eficientes y numéricamente estables que tomar .xAx=bx=A1b

Editar: probablemente no hace falta decirlo, pero este método produce el valor exacto de la distancia, mientras que al agregar a e invertir solo se obtiene una aproximación.λIS

bnaul
fuente
1
Tienes razón, @bnaul. Sin embargo, sin algún tipo de regularización, la LUdescomposición tampoco funcionará. Agregaré un comentario sobre esto en mi respuesta.
Zen
@bnaul: ¿por qué la LU cuando le haces a Cholesky para verificar la definición positiva? Suponiendo que tiene una matriz de covarianza válida resolviendo para y por sustitución hacia adelante, y luego para x por sustitución hacia atrás será más rápido. Buen punto, sin embargo, definitivamente me enfoco en obtener una definición positiva que olvidé por qué me preocupaba originalmente. : DK=LLTLy=bLTx=y
usεr11852
0

(Años después) un pequeño ejemplo: con rango deficiente, valores propios de serán 0 dentro de la precisión de la máquina - y aproximadamente la mitad de estos "ceros" pueden ser :Ar<n, nrATA<0

#!/usr/bin/env python2
""" many eigenvalues of A'A are tiny but < 0 """
# e.g. A 1 x 10: [-1.4e-15 -6.3e-17 -4e-17 -2.7e-19 -8.8e-21  1e-18 1.5e-17 5.3e-17 1.4e-15  7.7]

from __future__ import division
import numpy as np
from numpy.linalg import eigvalsh  # -> lapack_lite
# from scipy.linalg import eigvalsh  # ~ same
from scipy import __version__

np.set_printoptions( threshold=20, edgeitems=10, linewidth=140,
        formatter = dict( float = lambda x: "%.2g" % x ))  # float arrays %.2g
print "versions: numpy %s  scipy %s \n" % (
        np.__version__, __version__  )

np.random.seed( 3 )

rank = 1
n = 10
A = np.random.normal( size=(rank, n) )
print "A: \n", A
AA = A.T.dot(A)
evals = eigvalsh( AA )
print "eigenvalues of A'A:", evals
denis
fuente