処理の流れと各処理におけるデータ形式
前回 の繰り返しになりますが、Hadoopで同じ関数を使い類似性の計算をするのであれば、コンテンツベースと協調フィルタリングの違いは、keyおよびvalueの設定の違いにあります。
今回利用するデータも協調フィルタリングと同様にAmazon review data setです。このデータは以前紹介した場所から、現在は次の場所に移っています。
アイテム間の相関を協調フィルタリングではユーザの数で計算しましたが、コンテンツベースでは単語の数で計算します。したがって、MapReduceの第一段階における<key value>は協調フィルタリングでは<ユーザid アイテムid>でしたが、コンテンツベースでは<単語id アイテムid>となります。
図1 にMapReduceの各段階で使われる<key value>を示します。
図1 協調フィルタリングおよびコンテンツベースの処理における<key value>
ユーザidもアイテムidもプログラムにとっては英数字の文字列なので、プログラムに変更を加えず第二段階以降の処理を実行できます。したがって、変更するのは第一段階のMapperのみです。
リスト1 コンテンツベースの第一段階のMapper
#!/usr/bin/perl
use strict;
use utf8;
my $term;
while (<STDIN>) {
chomp $_;
my @string = split ( /\t/, $_);
my @string2 = ();
my $text = $string[7];
unless ( defined $text ) { next; }
if ( $text eq '' ) { next; }
my $item_id = $string[1];
if ( $item_id eq '' ) { next; }
else {
#英大文字を英小文字に変換
$text =~ tr/A-Z/a-z/;
#レビュー本文を連続する英字を単語として分割し、リスト化する
@string2 = split ( /[^a-z]+/, $text);
}
#単語の重複をリストから除く
my %count;
@string2 = grep(!$count{$_}++, @string2);
foreach $term ( @string2 ) {
#空白文字、文字長が3未満の単語を除く
if ( $term eq "" or length($term) < 3 ) { next; }
print ($term . "\t" . $item_id . "\n" );
}
}
今回使うデータがアイテムごとのレビュー本文なので、抽出した単語にはアイテムの特徴を現す単語だけでなく、レビュワーの感情や主観などを表す単語が含まれています。このためアイテムによっては、同じような特徴を持つアイテムよりも評価が類似するアイテム間の相関が高くなることに注意してください。
また、アイテム間でなくレビュー間の相関を計算することもできます。その場合は第一段階のMapperでkeyはそのままで、valueをレビューidにすることで、レビュー間の類似性を計算できます。
今回のデータは英文ですが、日本語の場合、第一段階のMapperで形態素解析を行う必要があります。
今回は英文ということで、単純に連続する英字を単語として分割しました。そのため、たとえば、Sixpence None the Richerは通過しても、2Pacなどの英数字が混在した単語は以降の処理から除かれます(2Pacファンの方、ごめんなさい) 。英文でもTreeTagger等のツールが存在しますので、これらをMapper内で呼び出して処理することで分割の精度を上げることができるでしょう。
stop wordsを取り除く
レビュー本文には” the” や” a” など、どのレビューにも共通して出現する単語、あるいはアイテムの説明にはあまり関係ない単語も含まれています。それらの単語は自然言語処理では、処理から除くことがよく行われます。
この単語は一般にstop wordsと呼ばれ、冠詞、前置詞、代名詞などがあります。stop wordsのリストが公開されていて、その中から今回は次のリストを使います。
Text Fixer - web resources
URL:http://www.textfixer.com/resources/common-english-words.txt
stop wordsをMapperで処理する方法は三通りほどあります。
Mapperの中にリストとして埋め込んで参照に使う。
stop wordsのリストをhadoop streamingで他のMapperおよびReducerのスクリプトと同様に各ノードに配布して、第一段階のMapperから読み込んで処理する。
stop wordsのリストをHDFSに置き、第一段階のMapperから読み込んで処理する。
リスト2 に上記の3.の方針で実装したMapperを示します。
リスト2 stop wordsの除去を取り入れたMapper
#!/usr/bin/perl
use strict;
use utf8;
my $term;
my %stw;
my @stwl;
#HDFS内からstop_word_list.txtの読み込み
open (IN, "hadoop dfs -cat /user/gihyo/amazon_gihyo_stw/stop_word_list.txt |");
while (<IN>) {
chomp $_;
@stwl = split (/,/);
}
close IN;
#stop wordへのフラグを立てる
foreach ( @stwl ) {
$stw{$_} = 1;
}
while (<STDIN>) {
chomp $_;
my @string = split ( /\t/, $_);
my @string2 = ();
my $text = $string[7];
unless ( defined $text ) { next; }
if ( $text eq '' ) { next; }
my $item_id = $string[1];
if ( $item_id eq '' ) { next; }
else {
$text =~ tr/A-Z/a-z/;
@string2 = split ( /[^a-z]+/, $text);
}
my %count;
@string2 = grep(!$count{$_}++, @string2);
foreach $term ( @string2 ) {
#空白文字、stop word及び文字長が3未満の単語を除く
if ( $term eq "" or defined $stw{$term} or length($term) < 3 ) { next; }
print ($term . "\t" . $item_id . "\n" );
}
}
協調フィルタリングでも、stop wordsの考え方は利用できます。たとえば、皆が観たあるいは評価した映画はユーザ間の類似性を計算するのに重要ではないと考えて、そのような映画をstop wordsならぬstop itemsとして処理から除外することもあります。
まだまだ足りない?
stop wordsを除外することで、アイテムの内容に関係がある単語でアイテム間の類似性を計算できるようになりました。しかし、これで十分でしょうか? それを確認するためにMapReduce処理の第一段階の出力結果を確認してみましょう。
ミススペリングや記号等、stop wordsのリストには無いものの、意味の不明な単語が結構あることがわかります。これらの単語を新たにstop wordsのリストに追加することで、これ以降の処理から除くことが可能です。
データが増えるに従って、このような単語が増えるのは明らかです。また、それに伴うstop wordsのリストへの追加作業の負担も増えてきます。何とか自動化とまでは行かないまでも、効率的なアプローチはないでしょうか?
次回はこの処理を情報検索の観点から見てみます。
この場を借りて宣伝させていただきます。「 スマートデバイスジャパン2012 (2012年6月13日~6月15日 幕張メッセ) 」にてレコメンド・エンジンを含む弊社ソリューションを出展いたします。ご都合がよろしければ、NTTコムウェア 展示ブースにぜひお立ち寄りください。
皆様のお越しを心よりお待ち申し上げております。