end0tknr's kipple - 新web写経開発

http://d.hatena.ne.jp/end0tknr/ から移転しました

perlでコサイン類似度を算出

まず、2個のベクトル:a,b の関係:θは、次のように表せます。

cos\theta = \frac{a \cdot b}{\mid a \mid \mid b \mid}

ここで、a \cdot b はaとbの内積なので、2次元のベクトルであれば、a \cdot b = a_{1}b_{1} + a_{2}b_{2} で表せ、また、\mid a \mid はベクトルの大きさなので、\mid a \mid = \sqrt{ {a_{1}}^2 + {a_{2}}^2} となります。
※ベクトルの内積や大きさは、更に多次元(n次元)になると、a \cdot b = a_{1}b_{1} + a_{2}b_{2} + ... +a_{n}b_{n}\mid a \mid = \sqrt{ {a_{1}}^2 + {a_{2}}^2 + ... + {a_{n}}^2}

これをperlで実装すると、次のように通り。

#!/usr/local/bin/perl
use strict;
use Data::Dumper;

my @VEC_KEYS = qw/hoge fuga foo/;	#ベクトルが持つkey

main();

sub main {
    my $vec_1 = {hoge => 1,
		 fuga => 2,
		 foo =>  3};
    my $vec_2 = {hoge => 3,
		 fuga => 1};
    my $cos_similar = cosine_similarity($vec_1,$vec_2);
    print "COSINE SIMILARITY : $cos_similar\n";
}


sub cosine_similarity {
    my ($vec_1, $vec_2) = @_;
    return inner_product($vec_1, $vec_2)
        / (vector_size($vec_1) * vector_size($vec_2));
}

sub inner_product {	#内積
    my ($vec_1,$vec_2) = @_;
    my $ret = 0;
    for my $key ( @VEC_KEYS ) {
	next if (not $vec_1->{$key} or not $vec_2->{$key} );
	$ret += $vec_1->{$key} * $vec_2->{$key};
    }
    return $ret;
}

sub vector_size {	#ベクトルのサイズ
    my ($vec) = @_;

    my $sum = 0;
    for my $key ( @VEC_KEYS ) {
	$sum += $vec->{$key} * $vec->{$key};
    }
    return sqrt($sum);
}

実行結果(1に近い程、類似)

[endo@colinux tmp]$ ./foo.pl 
COSINE SIMILARITY : 0.422577127364258

実際に類似アイテムを探す場合は、昨日のエントリにあるMath::Combinatoricsで組み合わせを算出してから用いるとよさそう。
http://d.hatena.ne.jp/end0tknr/20111020/1319119274