逆共分散行列の計算の数値的不安定性


8

21次元データの65サンプル(ここに貼り付け)があり、それから共分散行列を構築しています。C ++で計算すると、ここに共分散行列が貼り付けられます。そして、データからMATLABで計算すると(以下に示すように)、ここに共分散行列が貼り付けられます

データからcovを計算するためのMatlabコード:

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

共分散行列の違いがわかるように(〜e-07)、これはおそらく浮動小数点演算を使用するコンパイラーの数値の問題が原因です。

ただし、matlabによって生成された共分散行列とC ++コードによって生成された共分散行列から疑似逆共分散行列を計算すると、大きく異なる結果が得られます。私はそれらを同じ方法で計算しています:

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

違いが非常に大きいため、サンプル(ここに貼り付け)から65サンプルの分布までのマハラノビス距離を次のように計算しています。

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

異なる逆共分散行列()を使用すると、大きく異なる結果が得られます。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

共分散行列の小さな(e-7)差が疑似逆行列の計算にそのような影響を与えるのは正常ですか?もしそうなら、この影響を緩和するために私は何ができますか?

これに失敗すると、逆共分散を含まない、使用できる他の距離メトリックスはありますか?私はマハラノビス距離を使用します。これは、n個のサンプルについてはベータ分布に従うため、仮説検定に使用します。

事前に感謝します

EDIT:以下、共分散行列を計算するためのC ++コードを追加:vector<vector<double> >貼り付けたファイルからの行の集合を表します。

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; 
        }
    }

逆行列.....それは危険なことです!通常、これに代わる方法(たとえば、
疑似

1
@Aly:反転しようとしている行列は、正定ではないため、「有効な」共分散行列ではありません。数値的には、負の(ただし、ゼロに近い)固有値もいくつかあります。おそらく、対角線に沿って非常に小さな定数を追加するだけです。それは実際にティホノフ補正の形式です()。また、floatを使用せず、doubleを使用して共分散行列を格納します。(そして、すでにOpenCVを使用しているだけでなく、Χ+λI
Eigen

1
@Aly:本当にウィキペディア。(それは補題です:Tikhonov正則化)。SVDを使用してwhuberが述べた方法では、小さな固有値をゼロに設定すると、非負定行列が得られます。すべての固有値に小さな定数を追加して、それらを正定にする必要があります。実際には、どちらの方法も同じです。私はSVDを使用しないことに頼りましたが、すべてにを追加することにより、サンプルの固有値に直接影響します。私は参考文献に出くわしていません。どちらの方法も非常に直感的だと思います。λ
usεr11852

1
@ user11852コメントを回答にしてもらえますか?私はまだ実験中ですが、有望な場合は受け入れます。また、他の人が提案に回答した場合は、問題の理解に非常に役立ちました/有用なので、投票します
Aly

1
他のスレッド、合計が1なる変数があると、データセットと同じように不安定になり、冗長な変数が含まれるとコメントしました。1列削除してみてください。pinvも必要ありません。共分散行列はもはや特異ではありません。
Cam.Davidson.Pilon 2013

回答:


7

反転しようとしている行列は正定行列ではないため、「有効な」共分散行列ではありません。数値的には、負の(ただし、ゼロに近い)固有値もいくつかあります。これはおそらくマシンのゼロによるものです。たとえば、「matlab_covariance」行列の最後の固有値は-0.000000016313723です。正定値に修正するには、2つのことを行うことができます。

  1. 対角線に沿って非常に小さな定数を追加するだけです。ティホノフ補正の形式(と)。λ 0Χ+λIλ0
  2. (whuberの提案に基づく)SVDを使用して、「問題のある」固有値を固定の小さな値(ゼロではない)に設定し、共分散行列を再構築してから反転します。明らかに、これらの固有値の一部をゼロに設定すると、非負(または準正)の行列になり、それはまだ反転できません。

非負行列には逆行列はありませんが、疑似逆行列があります(実数または複素数のエントリを持つすべての行列には疑似逆行列があります)。それにもかかわらず、ムーアペンローズ疑似逆行列は、真の逆行列よりも計算コストが高く逆が存在し、それは疑似逆に等しい。だから逆に行くだけです:)

どちらの方法も、実際にはゼロ(またはゼロ未満)に評価される固有値を処理しようとします。最初の方法は少し手を振っていますが、おそらく実装がはるかに高速です。少し安定しているものについては、SVDを計算し、次に最小の固有値(負でない値になる)と非常に小さい値(正になる値)の絶対値に等しく設定することをお勧めします。明らかに否定的(またはすでに肯定的)なマトリックスに陽性を強制しないように注意してください。どちらの方法でも、マトリックスの調整数が変わります。λ

統計的に言えば、共分散行列の対角線全体にそのを追加することにより、測定にノイズが追加されます。(共分散行列の対角線は各ポイントの分散であるため、これらの値に何かを追加することで、「読み取り値のあるポイントでの分散は、実際には私が当初考えていたものよりも少し大きい」と言うだけです。)λ

行列の正定性の高速テストは、そのコレスキー分解の存在(または非存在)です。

また、計算上の注意として:

  1. floatは使用せず、doubleを使用して共分散行列を格納します。
  2. C ++の数値線形代数ライブラリー(EigenやArmadilloなど)を使用して、行列の逆行列や行列積などを取得します。より高速で、安全で、より簡潔です。

EDIT:考えるあなたの行列のコレスキー分解持っようあなたはすぐにシステム解決することができます(あなたがPos.Def行列をしているチェックするためにそれを行う必要があります。)。前方置換によってyのLy = bを解き、次に後方置換によってxのL ^ Tx = yを解きます。(eigenでは、コレスキーオブジェクトの.solve(x)メソッドを使用するだけです)をPos.Defにすることに重点を置いたことを指摘してくれたbnaulとZenに感謝します。そもそもなぜそれを気にしたのか忘れてしまった:)L L T K x = bKLLTKx=bK


3
+1。Mathematicaを使用してそれをデータに適用する(あまりに精度が低いために投稿された共分散行列ではなく)負の固有値は見つかりません。つまり、共分散行列が正確に計算されると、正の半定値が保証されるため負の固有値は計算の不正確さに起因する必要があります。まともな一般化された逆の手順では、これらの小さな負の値をゼロとして「認識」し、それに応じてそれらを処理する必要があります。
whuber

述べたように、私は賛成票を投じたので、努力してくれてありがとう。コメントするか、それに応じて受け入れる。
アリー

申し訳ありませんが、少し混乱しています。コレスキーを解くと、マハラノビス距離がどのように使用されますか?
2013

元のbnaulの投稿のリンクを確認してください。しかし、なくコレスキーを使用しないでください(それがLDL *が意味することです)。LU
usεr11852

2

投稿された回答とコメントはすべて、ほぼ特異な行列を反転させることの危険性について良い点を示しています。ただし、私が知る限り、マハラノビス距離の計算にサンプルの共分散を反転する必要がないことについては誰も言及していません。分解を使用して行う方法の説明については、このStackOverflowの質問を参照してください。LU

原理は線形システムを解くのと同じです:ようにを解こうとするとき、をとるよりもはるかに効率的で数値的に安定した方法があります。A x = b x = A 1 bxAx=bx=A1b

編集:おそらく言うまでもありませんが、この方法は正確な距離値を生成しますが、を追加して反転すると近似のみが得られます。SλIS


1
あなたは正しい、@ bnaul。ただし、なんらかの正則化LUを行わないと、分解も機能しません。これに関するコメントを回答に追加します。

@bnaul:正定性をチェックするためにコレスキーに行ったときに、なぜLUを実行するのですか?有効な共分散行列すると、前方置換によってyのを解き、逆置換によってxのが速くなります。良い点はありますが、私はもともと気にしていた理由を忘れていた明確な明確さを得ることに焦点を当てています!:D L y = b L T x = yK=LLTLy=bLTx=y
usεr11852

0

(数年後)小さな例:ランクが不足している場合、 固有値は0からマシン精度の範囲内であり、これらの「ゼロ」の約半分は可能性があります。r < n n r A T A < 0Ar<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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.