Distatis: 距離行列が複数あるときに元のデータの関係性を推定する

MikuHatsune2016-10-04

読んだ。
Proceedings of the IEEE Computer Society: International Conference on Computer Vision and Pattern Recognition. 2005. pp. 42–47.
Food Quality and Preference, 2007, 18, 627–640.
 
複数の距離行列が与えられたとき、元のデータの関係をMDS やPCA っぽく2次元もしくは3次元にプロットし直す方法。
2つめの論文では、被験者10人に8種の銘柄のビールを飲んでもらって、適当にビールの種類についてグループ分けしてもらい、10人のグループ分けのパターンから元の8種のビールの「ビール空間」的なものを構築する話。
R ではDistatisR にある。
 
元の論文では、男女3人ずつのI=6 人の顔から、いろいろパラメータを取ってそのパラメータに着目した時の、6人の顔の距離行列のデータがある。study とはパラメータのとり方の数で、これはT=4である。4つのstudy を1つのcompromise にまとめることが目標である。
 
表記は論文に従っている。study t に関する距離行列D_{[t]}=I\times I があって、R 上ではI\times I \times Tのアレイになっている。
DistAlgo データセットは、Pixels, Measures, Ratings, Pairwise という方法でデータをとって距離行列を作っている。
 
cross-product matrix を作る。いま、I で補正するベクトルm\{m_i=\frac{1}{I}\}単位行列I を用いて
\Xi=I-1m^T
\tilde{S}=-\frac{1}{2}\Xi D\Xi^{T}
となる\tilde{S}を作る。これはtごとにある。
\tilde{S}I\times I の正方行列なので、特異値分解して固有値の1つめで割って補正したS_t を用意する。

library(DistatisR)
data(DistAlgo)

St <- function(arr){
  Imat <- diag(1, nrow(arr))
  M <- Imat - matrix(1, nr=nrow(arr)) %*% t(m)
  res <- mapply(function(z){
  S <- -1/2 * M %*% DistAlgo[,,z] %*% t(M)
  eiS <- eigen(S)
  Snorm <- S/eiS$values[1]
  }, 1:dim(arr)[3], SIMPLIFY=FALSE)
  return(res)
}

Snorm <- St(DistAlgo)
names(Snorm) <- dimnames(DistAlgo)[[3]]

 
いま、S_tI\times I \times T のアレイ(R ではリストにしている)だが、いわゆるR で普通にやるようなベクトル化して、列ベクトルs_{[t]} がstudy 分あるベクトルX=[s_{[1]},\dots,s_{[T]}] を作る。I^2\times T 次元である。

X <- sapply(Snorm, c) # complete data matrix

 
compromise matrix を作るが、これは各study での距離行列同士の相関Rv とみなせる。Rv はcongruence, monotonicity とか言われる。
ベクトル化したX を利用して、A=X^TX となる行列A を作る。正規化して-1~1 に収まる感じにしようと思ったら、内積とcos を使って
C=N^{-\frac{1}{2}}AN^{-\frac{1}{2}}=\frac{{s_{[t]}}^T s_{[t^{'}]}}{||s_{[t]}||\hspace{1}||s_{[t^{'}]}||} ただしN=diag\{diag\{A\}\} という相関行列を作る。これは
Rv=\frac{trace\{{s_{[t]}}^T s_{[t^{'}]}\}}{\sqrt{trace\{{s_{[t]}}^T s_{[t]}\}trace\{{s_{[t^{'}]}}^T s_{[t^{'}]}\}}
と同じことである。

A <- t(X) %*% X # studies scalar product matrix
N <- diag(diag(A))

n <- apply(X, 2, norm, "2")
C <- A/outer(n, n, "*")
Rv <- A/sqrt(outer(diag(A), diag(A), "*"))

 
行列C は正方な相関行列であり、PCA でいうところの共分散行列みたいなものだから、特異値分解してPCA っぽく主成分をプロットできる。
C=P\Theta P^{-1}
G=P\times \Theta^{\frac{1}{2}}
適当に回転してしまったことに注意。
また、PCA と同じことをしているので、分散により各主成分がどれくらい説明能力があるかを定量できる。

eiC <- eigen(C)
G <- eiC$vectors %*% diag(eiC$values^(1/2))
rownames(G) <- dimnames(DistAlgo)[[3]]
cols <- c("red", "purple", "orange", "green")
plot(G[,1:2], pch=16, xlim=range(G[,1], 0), xlab="PC1", ylab="PC2", col=cols)
text(G[,1:2], rownames(G), pos=4, col=cols)
abline(v=0, h=0, lty=3)
eiC$values/sum(eiC$values)
[1] 0.65528859 0.20015228 0.12116261 0.02339652


 
各study がどんなものかの説明がついたので、study を統合してひとつの結果(compromize) を出しにかかろう。いま、C特異値分解して得られた固有ベクトルの行列P のうち、第一固有ベクトルに着目する。これにより、重み係数\alpha=(1^T P_1)^{-1}\times P_1 を求める。これはすべてのstudy t について求められるから
S_{[+]}=\sum_t^T \alpha_t S_{[t]}
を求める。S_{[+]}I\times I 行列である。
これもまた、特異値分解されてS_{[t]}=V\Lambda V^T となるV\Lambda があって、compromise factor なるFF=V\Lambda ^{\frac{1}{2}} になる。
S_{[+]}特異値分解した主成分をプロットすれば、それぞれのstudy の距離行列をまとめたときの、元のサンプルたちの関係がわかる、ということになる。
論文では、PC1 は男女をわけており、PC2 はちょっとよくわからないが、髪が明るい人と、髪が濃いもしくはハゲの人をわけている。

P <- eiC$vectors
alpha <- sweep(P, 2, colSums(P), "/")
Splus <- matrix(rowSums(sweep(X, 2, alpha[,1], "*")), nrow(DistAlgo))
eiSplus <- eigen(Splus)
Fscore <- eiSplus$vectors %*% diag(eiSplus$values^(1/2))
plot(eiSplus$vectors[,1:2], type="n", xlab="PC1", ylab="PC2")
text(eiSplus$vectors[,1:2], dimnames(DistAlgo)[[1]])
abline(v=0, h=0, lty=3)
legend("topright", legend=names(Snorm), lty=1, col=cols)


 
もとのstudy ごとの距離行列がどれくらい寄与していたのかを考える。S_{[+]}=V\Lambda V^T, F=V\Lambda ^{\frac{1}{2}}, V^T V=I であることを利用すると、F_{[t]}=S_{[t]}V\Lambda^{-\frac{1}{2}} となる。F_{[t]} はstudy t ごとの寄与率みたいなものである。プロットするとFigure 5 ができる。
ここで、FI-1 列までしかない。というのも、S_{[+]}特異値分解した時点で、6番目の固有値は0となっている。特異値分解を繰り返しまくっているから一次独立でなくなるのか、主成分を全部集めれば説明できる度合いが1 になるのでここで1つパラメータが落ちるのか、たぶんそんなやつ。
また、特異値分解すると微妙に論文と値が違うのだが、R では不偏分散で計算しているので、もしかしたら論文では分散で計算しているのかもしれない(未確認で進行形
F1 とM3 の男女を見ると、Pixels, Measures, Ratings, Pairwise の4つのstudy が見事にすべて逆を向いているのがわかるし、F2 とF3 のよく似た女性は、Measures とPairwise が近いということがわかる。

Fs <- lapply(Snorm, "%*%", eiSplus$vectors %*% diag(eiSplus$values^(-1/2)))

plot(eiSplus$vectors[,1:2], type="n", xlab="PC1", ylab="PC2")
abline(v=0, h=0, lty=3)
legend("topright", legend=names(Snorm), lty=1, col=cols, lwd=3)
for(i in 1:nrow(eiSplus$vectors)){
  for(j in seq(Fs)){
    x0 <- eiSplus$vectors[i, 1]
    y0 <- eiSplus$vectors[i, 2]
    x1 <- Fs[[j]][i, 1]
    y1 <- Fs[[j]][i, 2]
    arrows(x0, y0, x1, y1, xpd=TRUE, col=cols[j], length=0.05, lwd=2)
  }
}
text(eiSplus$vectors[,1:2], dimnames(DistAlgo)[[1]])