古い記事
ランダムジャンプ
新しい記事
人生において何度も同じようなのを書いていますが、あらためて Perl によるコサイン類似度の計算。他で使っているコサイン類似度計算のコードの動作確認用。

コマンドラインオプション:
- "-c" : 出現頻度をそのまま使う。指定なしだと頻度は全て1。
- "-t" : TF-IDF での頻度補正を行う。

出力:
- 入力された各行ごとに計算した類似度。
- 先頭の二つの数字が入力行番号(1始まり)。

■コード (cossim.pl)
#!/usr/bin/env perl
use strict;
use warnings;
use List::Util qw(sum);
use Getopt::Long;

my $opt_count = 0; # 実際の頻度を使用
my $opt_tfidf = 0; # TF-IDF で頻度補正
GetOptions(
    "count" => \$opt_count,
    "tfidf" => \$opt_tfidf,
);

### 入力
my @ents;
while (<>) {
    chomp;
    next if /^\s*$/;
    next if /^\#/;
    push @ents, [split(/\s+/, $_)];
}

### 出現頻度カウント
my $N = @ents;
my @tf;
my %df;
for (my $i = 0; $i < $N; $i++) {
    if ($opt_count) {
        $tf[$i]{$_}++ for @{$ents[$i]};
    } else {
        $tf[$i]{$_} = 1 for @{$ents[$i]};
    }
    $df{$_}++ for keys %{$tf[$i]};
}
for (my $i = 0; $i < $N; $i++) {
    my @vals = values %{$tf[$i]};
    if ($opt_tfidf) {
        my $n = sum(@vals);
        $tf[$i]{$_} = $tf[$i]{$_}/$n * log($N / $df{$_}) for keys %{$tf[$i]};
    }
    my $len = sqrt(sum(map {$_**2} @vals)); # ベクトルの長さ
    $_ /= $len for values %{$tf[$i]}; # 長さで正規化
}

### 出力
for (my $i = 0; $i < $N; $i++) {
    for (my $j = $i + 1; $j < $N; $j++) {
        my $sim = sum(map {$tf[$i]{$_} * ($tf[$j]{$_}||0)} keys %{$tf[$i]});
        printf "%d %d %.8f\n", $i+1, $j+1, $sim;
    }
}

■実行例
% cat test-1.txt
hoge huga huga foo foo foo
hoge hoge hoge huga
% ./cossim.pl -c test-1.txt
1 2 0.42257713
(ref. [3])

% cat test-2.txt
リンゴ リンゴ バナナ
リンゴ バナナ ミカン
% ./cossim.pl test-2.txt
1 2 0.81649658
(ref. [4])

% cat test-3.txt
日本 今日 今日 今日 高校 高校 国語
日本 日本 明日 大学 数学
% ./cossim.pl -c test-3.txt
1 2 0.19518001
(ref. [5])

% cat cossim-test.txt
六本木 渋谷 恵比寿 目黒 目黒
六本木 渋谷 渋谷 恵比寿
六本木 六本木 渋谷 渋谷 目黒
% ./cossim.pl cossim-test.txt 
1 2 0.86602540
1 3 0.86602540
2 3 0.66666667
% ./cossim.pl -c cossim-test.txt
1 2 0.61721340
1 3 0.75592895
2 3 0.81649658
% ./cossim.pl -t cossim-test.txt
1 2 0.00395490
1 3 0.00395490
2 3 0.00000000
% ./cossim.pl -c -t cossim-test.txt
1 2 0.00126839
1 3 0.00165702
2 3 0.00000000

参考

[1] コサイン類似度
http://www.cse.kyoto-su.ac.jp/~g0846020/keywords/cosinSimilarity.html
[2] IIR C6
http://nlp.stanford.edu/IR-book/pdf/06vect.pdf
[3] perlでコサイン類似度を算出 (end0tknrのkipple - web写経開発)
http://d.hatena.ne.jp/end0tknr/20111021/1319162866
[4] Perlでコサイン類似度を計算する (work.log)
http://worklog.be/archives/3206
[5] コサイン尺度(コサイン類似度)の計算 (Ceekz Logs)
http://private.ceek.jp/archives/003891.html
この記事に言及しているこのブログ内の記事