このエントリでは、
breeze(ScalaNLP)でのコサイン類似度・類似度行列の求め方についてまとめておきます。

コサイン類似度の定義式

コサイン類似度の定義式は、次の通りです。

cosinesimilarity

参考: Cosine similarity | Wikipedia
https://en.wikipedia.org/wiki/Cosine_similarity

コサイン類似度行列の計算手順

いくつかのベクトルのコサイン類似度をまとめ、
コサイン類似度行列として計算する場合の計算の流れを書いておきます。

ここでは、 2つの2次元のベクトルのコサイン類似度を計算する流れで説明します。

(1) 比較したいベクトルを用意する

ベクトル A: (a1, a2)
ベクトル B: (b1, b2)

(2) 各ベクトルの要素を列として、 比較したいベクトルの数だけの列がある行列を作る

行列:
 a1 a2
 b1 b2

(3) 2で作った行列と転置行列の内積をとる

行列:
 a1 a2
 b1 b2

転置行列:
 a1 b1
 a2 b2

転置行列との内積:
 a1*a1+a2*a2, a1*b1+a2*b2
 b1*a1+b2*a2, b1*b1+b2*b2

(4) 3で求めた行列の各要素を対角成分の平方根で割る

3で求めた行列をよく見てみます。

この行列の対角要素の平方根は、コサイン類似度の定義式の分母に対応付いています。
 1行1列目の要素: a1*a1+a2*a2

この行列の各要素は、コサイン類似度の定義式の分子に対応付いています。
 1行2列目の要素: a1*b1+a2*b2

つまり、3で求めた行列(内積)の各要素を対角成分の平方根で割れば、
各ベクトルのコサイン類似度が含まれる行列を求める事が出来ます。

breezeの実装例

breezeで実装すると、以下のようになります。

コサイン類似度の部分は、breezeのcosinedistanceから類似度を計算しています。
コサイン類似度行列の部分は、全項で説明した流れをそのまま実装しています。
コサイン類似度一覧の部分は、大きな行列を一度に処理してメモリ不足に成らないようにループで処理している例です。分散環境用で並列計算させたい場合に参考にもなるかと。

CosineSimilarity.scala

import breeze.linalg._
import breeze.linalg.functions._
import breeze.numerics._

object CosineSimilarity {
  def main(args: Array[String]): Unit = {
    // コサイン類似度
    println("**** コサイン類似度")
    val v1 = DenseVector(1.0, 2.0)
    val v2 = DenseVector(3.0, 4.0)
    println("ベクトルv1: " + v1)
    println("ベクトルv2: " + v2)
    println("コサイン類似度: " + (1 - cosineDistance(v1, v2)))

    // コサイン類似度行列
    println("\n**** コサイン類似度行列")
    def cossimMatrix(x: DenseMatrix[Double]): DenseMatrix[Double] = {
      val m = x * x.t
      val d = sqrt(diag(m))
      m / (d * d.t)
    }
    val m1 = DenseMatrix((1.0, 2.0), (3.0, 4.0))
    println("元の行列: \n" + m1)
    println("コサイン類似度行列: \n" + cossimMatrix(m1))

    // コサイン類似度一覧
    println("\n**** コサイン類似度一覧")
    def cossimSeq(x: DenseMatrix[Double]): IndexedSeq[Vector[Double]] = {
      val normList = (0 until x.rows).map(i => norm(x(i, ::).t))
      val d = Vector(normList.toArray)
      (0 until x.rows).map(i => {
        val v = x(i, ::)
        (v * x.t).t /:/ d /:/ d(i)
      })
    }
    val m2 = DenseMatrix((1.0, 2.0), (3.0, 4.0))
    println("元の行列: \n" + m2)
    println("コサイン類似度一覧:")
    cossimSeq(m2).foreach(println)
  }
}