集合知プログラミング 3.3をperl化してみた(階層化クラスタリング)
今回は、ブログから抽出したキーワード群をクラスタリングする方法。
とりあえず、階層化はできてたけど、出力結果が若干違ったから、もしかしたら一部間違ってるかも…。
追記(23:41):pythonのソースで実行してみたら同じ結果だった。
■ 流れ
テキストファイルに入ってるデータを読み込む。
一行目は列のタイトル(単語名?)
一行目以降の一カラム目は行(ブログ)の名前。
読み込んだデータをクラスタリングする。
すべての組を何回もループさせて、その中から距離の近い組を新しいクラスタとして配置。
全部のクラスタリングが終わるまで続ける?
■ ソース
#!/usr/bin/perl -w use strict; use Dumpvalue; package Clusters; # this subroutean is get first line that means row names, # and, othe line's first column is each column name. # those data insert big list. # * rownames and colnames useful index keys. sub readfile { my($self, $filename) = @_; # get file data open my $file, '<', $filename; # first line is column titles my @colnames = split('\t', <$file>); my @lines = <$file>; close $file; # set data my @rownames = (); my @data = (); foreach my $line(@lines) { my @p = split('\t', $line); # each line's first column is colum name unshift(@rownames, shift(@p)); # other column is this line's data unshift(@data, \@p); } return \@rownames, \@colnames, \@data; } sub hcluster { # $rows: array reference # $distance: subroutean reference my($self, $rows, $distance) = @_; my $currentclustid = -1; my %distances = (); # Clusters are lines first. my @clust; map { push(@clust, Bicluster->new(vec => $rows->[$_], id => $_)); } (0..$#$rows); while ($#clust > 0) { my @lowestpair = (0,1); my $closest = $distance->($clust[0]->{vec}, $clust[1]->{vec}); # 全ての組をループし、もっとも距離の近い組を探す foreach my $i(0..$#clust) { foreach my $j($i+1..$#clust){ # 距離をキャッシュしてあればそれを使う if (!defined($distances{$clust[$i]->{id}}{$clust[$j]->{id}})) { $distances{$clust[$i]->{id}}{$clust[$j]->{id}} = $distance->($clust[$i]->{vec}, $clust[$j]->{vec}); } my $d = $distances{$clust[$i]->{id}}{$clust[$j]->{id}}; if ($d < $closest) { $closest = $d; @lowestpair = ($i, $j); } } } # 二つのクラスタの平均を計算する my @mergevec = (); foreach my $i(0..$#{$clust[0]->{vec}}) { push(@mergevec, ($clust[$lowestpair[0]]->{vec}->[$i] + $clust[$lowestpair[1]]->{vec}->[$i]) / 2.0); } # 新たなクラスタを作る my $newcluster = Bicluster->new( id => $currentclustid, vec => \@mergevec, left => $clust[$lowestpair[0]], right => $clust[$lowestpair[1]], distance => $closest, ); # 元のセットではないクラスタのIDは負にする $currentclustid = -1; splice(@clust, $lowestpair[1], 1); splice(@clust, $lowestpair[0], 1); push(@clust, $newcluster); } return $clust[0]; } 1; # 階層的なツリーを表現するのに使うこれらのすべてのプロパティを持つBiclusterというクラス package Bicluster; sub new { my($self, %attr) = @_; return bless { id => $attr{id}, vec => $attr{vec}, left => $attr{left}, right => $attr{right}, distance => $attr{distance}, }, $self; } 1; package main; my($blognames, $words, $data) = Clusters->readfile('blogdata.txt'); my $start = time; my $clust = Clusters->hcluster($data, \&pearson); my $end = time; printclust($clust, $blognames, 0); #Dumpvalue->new->dumpValue($cluster); sub pearson { my($v1, $v2) = @_; my($sum1, $sum2, $sum1Sq, $sum2Sq, $pSum) = (0, 0, 0, 0, 0); map { $sum1 += $_; $sum1Sq += $_ ** 2; } @$v1; map { $sum2 += $_; $sum2Sq += $_ ** 2; } @$v2; map { $pSum += $v1->[$_] * $v2->[$_]; } 0..$#$v1; my $num = $pSum - ($sum1 * $sum2 / scalar(@$v1)); my $den = sqrt(($sum1Sq - $sum1 ** 2 / scalar(@$v1)) * ($sum2Sq - $sum2 ** 2 / scalar(@$v1))); if ($den == 0) { return 0; } return 1.0 - $num / $den; } sub printclust { my($clust, $labels, $n) = @_; # 階層化のレイアウトにするためにインデントする foreach my $i(0..$n) { print " "; } if ($clust->{id} < 0) { # 負のidはこれが枝である事を示している print '-', "\n"; } else { # 正のidはこれが終端だという事を示している if ($labels == undef) { print $clust->{id}, "\n"; } else { print $labels->[$clust->{id}], "\n"; } } # 右と左の枝を表示 if ($clust->{left}) { printclust($clust->{left}, $labels, $n + 1); } if ($clust->{right}) { printclust($clust->{right}, $labels, $n + 1); } } 1;
■ 出力結果
yuki@localhost>$ perl cluster.pl - - - - - - Creating Passionate Users Techdirt - Joel on Software Blog Maverick - - Bloggers Blog: Blogging the Blogsphere BuzzMachine - Steve Pavlina's Personal Development Blog Topix.net Weblog - - - PaulStamatiou.com - A Consuming Experience (full feed) - Official Google Blog - The Unofficial Apple Weblog (TUAW) Joystiq - Slashdot Mashable! - - Joi Ito's Web we make money not art - Oilman Copyblogger - - - - Dave Shea's mezzoblue lifehack.org - MetaFilter SpikedHumor - - Signum sine tinnitu--by Guy Kawasaki Treehugger - Shoemoney - Skills to pay the bills - Go Fug Yourself The Superficial - Because You're Ugly - - - Instapundit.com Schneier on Security - Online Marketing Report - Daily Kos Talking Points Memo: by Joshua Micah Marshall - - ongoing Eschaton - Derek Powazek flagrantdisregard - - - - - The Blotter Michelle Malkin - Bloglines | News - Boing Boing MAKE Magazine - - Autoblog TechEBlog - Wired News: Top Stories 43 Folders - - - gapingvoid: "cartoons drawn on the back of business cards" - Gawker Gothamist - WWdN: In Exile - Crooks and Liars Hot Air - - - The Huffington Post | Raw Feed Wonkette - ProBlogger Blog Tips Sifry's Alerts - - Micro Persuasion Publishing 2.0 - Deadspin Neil Gaiman's Journal - - - - - Think Progress Power Line - TMZ.com Engadget - - Seth's Blog The Viral Garden - Quick Online Tips - Read/WriteWeb - CoolerHeads Prevail Download Squad - - - Scobleizer - Tech Geek Blogger Valleywag - 456 Berea Street O'Reilly Radar - - Lifehacker - Search Engine Watch Blog - Search Engine Roundtable - Google Blogoscoped Google Operating System - kottke.org Signal vs. Noise - - - - Gizmodo Kotaku - Little Green Footballs Andrew Sullivan | The Daily Dish - - Jeremy Zawodny's blog PerezHilton.com - Matt Cutts: Gadgets, Google, and SEO - John Battelle's Searchblog - Pharyngula ScienceBlogs : Combined Feed - - - Cool Hunting SimpleBits - NewsBusters.org - Exposing Liberal Media Bias Captain's Quarters - - TechCrunch GigaOM - plasticbag.org Joho the Blog