結論
deeplearning4jのKmeansを使うときにdistanceFunctionにcosinesimilarityを使ってはいけない。
理由
※deeplearning4jは2017/7/2時点で最新版の0.8.0です。
deeplearning4jにはKmeansの関数があります。以下のような形で使います。
KMeansClustering kmc = KMeansClustering.setup(num, iter, distanceFunction);
ClusterSet cs = kmc.applyTo(pointsLst);
ここで、numはクラスタ数、iterは繰り返し回数(10がよく使われる)、destanceFunctionは距離関数(euclideanかmanhattanかcosinesimilarityを指定できる)です。
さて、ここに地雷があります。destanceFunctionにcosinesimilarityを指定すると、思ったような結果は出てきません。
※「普通euclideanを指定するだろ」って方は問題なしです。
鋭い方はお気づきでしょうが、cosinesimilarityだけ類似度です。残りの2つは距離です。すなわち、cosinesimilarityは値が大きいほど(MAXは1)類似度が高く、残りの2つは値が小さいほど類似度が高いということです。
deeplearning4jのプログラム的にはこんな感じになっています。ClusterSet.classにて、
public Pair<Cluster, Double> nearestCluster(Point point) {
Cluster nearestCluster = null;
double minDistance = Float.MAX_VALUE;
double currentDistance;
for (Cluster cluster : getClusters()) {
currentDistance = cluster.getDistanceToCenter(point);
if (currentDistance < minDistance) {
minDistance = currentDistance;
nearestCluster = cluster;
}
}
return new Pair<>(nearestCluster, minDistance);
}
この関数で今あるクラスタと自分の距離を計算し、一番距離が短いクラスタに分類するわけです。すごく真当な処理ですが、cosinesimilarityだけ値の解釈が逆なので、ここでとんでもないことになります。
おわりに
どうしてもcosinesimilarityを使いたい場合は、現状だとextendsクラス作るしかないです。そこだけ例外処理加えてプルリク送ろうかと思いましたが、なんかコード汚いので辞めました。ほっとくのもどうかと思うのですが、どうしましょ。
追記
issuesに登録されていました。
https://github.com/deeplearning4j/deeplearning4j/issues/2361
どうやら「正常な動作」ということで片付いてるようです。たしかに・・・正常な動作ですけど「正しい処理」ではないですよね。使えなくするか、無理やり使うならcosinesimilarityをcosinedistanceとして、s[cosinedistance]=1-s[cosinesimilarity]のみ受け付けるようにした方が良いと思いますね。