Hadoopでレコメンドシステムを作ろう

第5回 レコメンドシステム-協調フィルタリングのHadoopへの実装[後編]

この記事を読むのに必要な時間:およそ 4 分

今回は,Hadoopでコサイン関数を計算する方法の最終回です。

おさらい

前回は,履歴から作成した,ユーザごとにアクセスしたアイテムのリストであるユーザシーケンスから,同一シーケンスの中に出現したアイテムのペアとそれらをアクセスしたユーザ数(シーケンスの数)を抽出しました。この段階でコサイン関数の計算に必要な要素が揃いました。

図1 前回までに説明した処理の範囲

図1 前回までに説明した処理の範囲

アクセスユーザ数からコサイン関数へ

第三段階の処理フローは次の通りです。ここでもアイテムIDを具体的なアイテム名で示しています。

図2 第三段階の処理フロー

図2 第三段階の処理フロー

  • ①では,第二段階の出力結果を入力し,そのまま出力しています
  • ②では,keyであるアイテムペアでソートしてReducerに渡しています。
  • ③では,keyであるアイテムペアをアクセスしたユーザ数,および各アイテムをアクセスしたユーザ数を使いコサイン関数を計算します。

これをMapperおよびReducerで実装します。

リスト1 Mapperの処理

#!/usr/bin/perl

use strict;

while (<STDIN>) {
        chomp $_;
        print ( $_ . "\n" );
}

Mapperは図2の①に相当します。このMapperは第二段階の出力結果をReducerにそのまま渡しているだけです。アイテムペアをkeyにしているので,Reducerにはこのアイテムペアでソートされた結果が渡されます。このペアはアイテムの組み合わせでなく順列になっていることに注意してください。同一keyであれば,同じ順列であり,同じReducerに渡されています。

リスト2 Reducerの処理

#!/usr/bin/perl

use strict;

my ($key0,$value) = ();
my ($key,$id) = ();
my $co = 0;
my $corr = 0;
my %co_count = ();
my %key_count = ();
my $key_current = "";
my @list = ();

while (<STDIN>) {
        chomp $_;
      #入力したデータをkey0とvalueのペアに分割する。分割するデリミタはMap関数の出力形式に合わせる。
      #ここでは前出のMapperのkey及びvalueがタブ区切りで出力したので,デリミタをタブにする。  
        ($key0,$value) = split(/\t/,$_);
      #key0はアイテムペアなので,$key及び$idのアイテムに分割する。分割するデリミタは第二段階のReducerの出力形式に合わせる。	
        ($key,$id) = split(/:/,$key0);

      #key値が変わった=以前のkeyに関するデータの読み込みが終了のサインなので,以前のkeyである$key_currentに対する処理を開始する。
        if ( $key ne $key_current )  {
                if ($key_current ne "" && ( keys (%co_count) > 0 ) ) {
                        foreach $id ( sort keys %co_count ) {
                 #アイテムペアをアクセスしたユーザ数をダブルカウントしているので,二で割る。    
                                $co = $co_count{$id}/2;
                                $corr = $co/($key_count{$key_current}*$key_count{$id});
                #基準とするアイテムのID,ペアとなったアイテムID2,基準とするアイテムをアクセスしたユーザ数,両アイテムをアクセスしたユーザ数,ペアとなったアイテムをアクセスしたユーザ数,両アイテムの相関係数の順に出力 	  
                                print ($key_current . "\t". $id . "\t" . $key_count{$key_current} . "\t" . $co . "\t" . $key_count{$id} . "\t" . $corr . "\n");
                        }
                }
            #比較対象となるkeyを変更する
               $key_current = $key;
               $corr = %key_count = %co_count = @list = ();
        }
        if ( $value =~ /(\d+)>(\d+)>(\d+)/ ) {
                if ( $1 > 0 ) { $key_count{$key} = $1; }
                $co_count{$id} += $2;
                if ( $3 > 0 ) { $key_count{$id} = $3; }
        }
}
#標準入力を読み終えた段階で,まだ出力していないkeyに対し,集約を行い,その結果を出力する。
if ($key_current ne "" && ( keys (%co_count) > 0 ) ) {
        foreach $id ( sort keys %co_count ) {
                $co = $co_count{$id}/2;
                $corr = $co/($key_count{$key_current}*$key_count{$id});
                print ($key_current . "\t". $id . "\t" . $key_count{$key_current} . "\t" .
 $co . "\t" . $key_count{$id} . "\t" . $corr . "\n");
        }
}

Reducerは図2の③に相当します。主な処理はMapperの出力をマージして,アイテムペアごとの相関係数の計算です。

第二段階のReducerは任意の一組のアイテムのペアを順序を入れ替えものも含めて二通り(組み合わせでなく順列であることに注意)出力していたことを思い出してください。第三段階のReducerの処理直前では,同一keyであっても,valueにはペアを構成する両アイテムをアクセスしたユーザ数が含まれていますが,アイテムごとのアクセスユーザ数はどちらか一方しか含まれていませんでした。

図2の例では,⁠ザスーラ:ジュマンジ 3>2>0」「ザスーラ:ジュマンジ 0>2>2」は同じkeyですが,valueを構成する値が違っていることに注意してください。ソートの結果,第三段階のReducerで,アイテムの順列が一致する=同一keyのデータを同じReducerで受けることから,初めて,keyを構成するアイテムのアクセス数を両方取得できます。

著者プロフィール

川前徳章(かわまえのりあき)

工学博士,NTTコムウェア 研究開発部 勤務。専門は情報検索,統計的機械学習,マーケティングサイエンス。現在はレコメンドシステムと感性検索の研究と開発に従事。

東京電機大学安田研究室 研究員

コメント

コメントの記入