MDS&文字列カーネルの可視化を試す

はじめに

せっかく文字列カーネルで遊んでみたので、可視化も試してみる。

Multidimensional Scalingとは

  • n個のm次元ベクトルについて、それらの距離のみが与えられる
  • それらの距離をできる限り保存して低次元空間(1,2,3次元など)にマッピングする方法
  • 計量的MDS(古典的MDS)
  • 非計量的MDS
    • 計量的MDSで仮定されている比率尺度以外の尺度の場合でも対応

MDSを試す

アメリカの飛行場をプロットするのが流行らしいので、自分もやってみる。

使ったもの
コード
#include <iostream>
#include <vector>
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

struct ST {
  double lambda;
  VectorXd v;
};
bool operator<(const ST& a, const ST& b){
  return a.lambda < b.lambda;
}


int main(){
  int N;
  cin >> N;

  MatrixXd m(N,N); //2点間の二乗の行列
  MatrixXd one(N,N), e(N,N); //要素が全て1の行列、単位行列

  for(int i=0; i<N; i++){
    e(i,i) = 1;
    for(int j=0; j<N; j++){
      double tmp;
      cin >> tmp;
      m(i,j) = tmp * tmp;
      one(i,j) = 1;
    }
  }

  //centering(Young-Householder transformation)
  MatrixXd P = -0.5 * (e - 1/(double)N * one) * m * (e - 1/(double)N * one);

  //固有値固有ベクトルの計算
  SelfAdjointEigenSolver<MatrixXd> eigensolver(P);
  if(eigensolver.info() != Success) return 1;
  
  vector<ST> res;
  VectorXd vv = eigensolver.eigenvalues();
  MatrixXd mm = eigensolver.eigenvectors();

  for(int i=0; i<vv.size(); i++){
    res.push_back((ST){vv(i),mm.col(i)});
  }

  //固有値でソート
  sort(res.begin(), res.end());

  //座標のプロット
  for(int i=0; i<N; i++){
    cout << res[N-1].v(i) << " " << res[N-2].v(i) << endl;
  }
  
  return 0;
}
実行&結果
$ less dist.txt
10
0 587 1212 701 1936 604 748 2139 2182 543
587 0 920 940 1745 1188 713 1858 1737 597
1212 920 0 879 831 1726 1631 949 1021 1494
701 940 879 0 1374 968 1420 1645 1891 1220
1936 1745 831 1374 0 2339 2451 347 959 2300
604 1188 1726 968 2339 0 1092 2594 2734 923
748 713 1631 1420 2451 1092 0 2571 2408 205
2139 1858 949 1645 347 2594 2571 0 678 2442
2182 1737 1021 1891 959 2734 2408 678 0 2329
543 597 1494 1220 2300 923 205 2442 2329 0

$ g++ mds_dist.cc -I/usr/local/include/eigen3 
$ ./a.out < dist.txt > dist.out
$ gnuplot
> set terminal dumb
> unset key
> plot "./dist.out" using 1:2
 0.5 ++------+------+-------+-------+------+-------+-------+------+------++
     +    A  +      +       +       +      +       +       +      +       +
 0.4 ++                                                               A  ++
     |                                                                    |
 0.3 ++                                                                  ++
     |                                               A              A     |
 0.2 ++                                                                  ++
     |                                                                    |
 0.1 ++                                                                  ++
   0 ++                        A                                         ++
     |                                                                    |
-0.1 ++ A                                                                ++
     |                                                       A            |
-0.2 ++                                                                  ++
     |                                                                    |
-0.3 ++       A                                                          ++
     |                                                                    |
-0.4 ++                                                                  ++
     +       +      +       +       +      +   A   +       +      +    A  +
-0.5 ++------+------+-------+-------+------+-------+-------+------+------++
   -0.5    -0.4   -0.3    -0.2    -0.1     0      0.1     0.2    0.3     0.4


大まかな位置関係はあってる。よさそう。

カーネルの可視化を試す

カーネル法によるパターン解析」にカーネル行列Kの可視化が載っていたので、それを試す。
カーネル行列Kとは、(i,j)成分がK(i,j)=<φ(i),φ(j)>となっているようなN*N行列をいう。

方法
コード

MDSのコードを少し修正して利用。

#include <iostream>
#include <vector>
#include <cmath>
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

struct ST {
  double lambda;
  VectorXd v;
};
bool operator<(const ST& a, const ST& b){
  return fabs(a.lambda) < fabs(b.lambda);
}


int main(){
  int N;
  cin >> N;

  MatrixXd K(N,N); //カーネル行列K
  MatrixXd D(N,N); //対角行列D

  for(int i=0; i<N; i++){
    D(i,i) = 0;
    for(int j=0; j<N; j++){
      cin >> K(i,j);
      D(i,i) += K(i,j);
    }
  }

  MatrixXd L = D - K; //Kのラプラシアン

  //固有値固有ベクトルの計算
  SelfAdjointEigenSolver<MatrixXd> eigensolver(L);
  if(eigensolver.info() != Success) return 1;
  
  vector<ST> res;
  VectorXd vv = eigensolver.eigenvalues();
  MatrixXd mm = eigensolver.eigenvectors();

  for(int i=0; i<vv.size(); i++){
    res.push_back((ST){vv(i),mm.col(i)});
  }

  //固有値でソート
  sort(res.begin(), res.end());

  //for(int idx=0; idx<N; idx++){
  //  cout << res[idx].lambda << endl;
  //}

  //座標のプロット
  for(int i=0; i<N; i++){
    cout << res[N-1].v(i) << " " << res[N-2].v(i) << endl;
  }
  

  return 0;
}
実行&結果

「今日は晴れ」「明日は晴れ」「今日は雨」「こんにちは」の全部分列カーネル(の正規化カーネル)について試してみる。

$ less kernel.txt
4
1 0.5 0.353553 0.0625
0.5 1 0.176777 0.0625
0.353553 0.176777 1 0.0883883
0.0625 0.0625 0.0883883 1

$ g++ kernel_vis.cc -I/usr/local/include/eigen3 
$ ./a.out < kernel.txt > kernel.out
$ gnuplot
> set terminal dumb
> unset key
> gnuplot> set xrange [-1:1]
> gnuplot> set yrange [-1:1]
> plot "./kernel.out" using 1:2
    1 ++---------------+-----------------+----------------+---------------++
      +                +                 +                +                +
      |                         A                                          |
      |                                                                    |
      |                                                                    |
  0.5 ++                                                                  ++
      |                                                                    |
      |                                                                    |
      |                                                                    |
    0 ++                                                                  ++
      |                                  A                                 |
      |                                                             A      |
      |                                                                    |
      |                                                                    |
 -0.5 ++                                                                  ++
      |              A                                                     |
      |                                                                    |
      |                                                                    |
      +                +                 +                +                +
   -1 ++---------------+-----------------+----------------+---------------++
     -1              -0.5                0               0.5               1

左3つが「今日は晴れ」「明日は晴れ」「今日は雨」で、右1つが「こんにちは」。
大まかに取れてるみたいだけど、微妙。。。?

比率的なのは大体あってそう。眺めて知見を得ることが目的なので、大まかにこれでもよさそうかな。