2012年12月10日月曜日

Mahoutでレコメンデーション(2)〜レコメンド→レポート作成編



Mahoutでレコメンデーションの後半です。前半ではMahoutの入力データを作成しました。それでは、Mahoutでレコメンデーションを実行してみます。以下のコマンドを実行します。
MasahiroPC:~ masahiro$ mahout recommenditembased -n 1 --input /user/hive/warehouse/mahout_input/000000_0 --output /user/hive/warehouse/mahout_output
ちなみに上記コマンドを実行すると、一時ファイルを格納するためのtempディレクトリをHDFSのホームディレクトリに作成します。再度Mahoutコマンドを実行する際には、このディレクトリを削除してからでないとエラーを返すのでご注意ください。

Map/Reduceの処理が終わったら、出力ファイルを確認します。
MasahiroPC:~ masahiro$ hadoop fs -cat /user/hive/warehouse/mahout_output/part-r-00000
・・・省略・・・
3 [2:3.6]


これは、受講者3番には2番のクラスがおすすめで、2番のクラスを受講すると3.6の評価をするであろう、ということを表しています。

ただし、ここで一つ問題があります。HiveQLのCREATE TABLE文では、どうやらフィールドの区切り文字は一種類しか指定できないようです。

そこで、とりあえず次のようなテーブルを作成します。
hive> create table temp_result(c1 int, c2 string)
    > row format delimited fields terminated by '\t';
OK
Time taken: 0.183 seconds
hive> load data inpath '/user/hive/warehouse/mahout_output/part-r-00000' 
    > into table temp_result;                                            
Loading data to table default.temp_result
OK
Time taken: 0.891 seconds
hive> select * from temp_result;
OK
3 [2:3.6]
Time taken: 0.067 seconds


次に、length関数とsubstr関数を使って、以下の文を実行します。
hive> create table temp_result2
    > row format delimited fields terminated by ','
    > as select c1 as tid,substr(c2,2,length(c2) - 6) as cid ,substr(c2,4,3)  as score from temp_result;
・・・省略・・・
OK
Time taken: 12.82 seconds
hive> select * from temp_result2;
OK
3 2 3.6
Time taken: 0.069 seconds



これで出力結果のフォーマットを表の形式に整えることが出来ました。最後に各マスターと結合します。
hive> create table result
    > as select tm.name as tname , cm.name as cname, tr.score as score
    > from temp_result2 tr join trainee_master tm
    > on tr.tid = tm.id
    > join class_master cm
    > on tr.cid = cm.id;
・・・省略・・・
OK
Time taken: 46.829 seconds
hive> select * from result;
OK
Jotaro Java 3.6
Time taken: 0.074 seconds



以上です。あとはこの出力結果をRDBなり、HBaseなりに登録しておけば、訪問者にお勧めとして提示することが可能です。他にも色々なやり方があるので、もっと効率のいい方法があるかもしれません。

補足
 あとで分かったんですが、instr関数がありました(汗)これを使えばもう少し、スマートなコードを書けそうです。


Mahoutでレコメンデーション(1)〜データ準備編



MahoutはHadoop Map/Reduce上※で動作する機会学習のフレームワークです。例えばユーザーと購入した商品の評価からレコメンド(推奨)をするプログラムをJavaで書くと、とても複雑なプログラムになりますが、Mahoutを使うとコマンド一つで実行できます。

※Map/Reduce以外でも動作します。

例えば次のようなデータを入力値とします。

Jonathan,Hadoop,3.0
Jonathan,Linux,2.5
Jonathan,Java,5.0
Joseph,Hadoop,4.2
Joseph,Linux,4.8
Joseph,Java,3.0
Jotaro,Hadoop,3.0
Jotaro,Linux,4.2

※セミナーの受講者と評価の情報です。項目は右から(受講者,クラス,評価)を表します。

しかしここで問題があります。Mahout 0.7のレコメンデーションは、一つ目と二つ目のフィールドが数値形式でなくてはなりません。そこで、まずはMahoutが扱える形式に変換する必要がありますが、ここでHiveを使うと簡単です。

Hiveは本来はJavaなどのプログラミング言語で記述するMap/ReduceをHiveQLというSQLに似た独自言語で実装するためのものです。それでは前述のデータをHiveに取り込んでみます。

まずhiveシェルを起動する前に、次のコマンドを実行しておきます。
export HIVE_AUX_JARS_PATH=$HIVE_HOME/lib
※実行しておかないと、row_sequence関数を使うとエラーになります。

hiveシェルを起動し、以下のコマンドを実行します。
hive> create table trainee_list(tname string, cname string, score double)
    > row format delimited 
    > fields terminated by ',';
OK
Time taken: 6.173 seconds
hive> load data local inpath './trainee_list'    
    > into table trainee_list;
Copying data from file:/Users/masahiro/trainee_list
Copying file: file:/Users/masahiro/trainee_list
Loading data to table default.trainee_list
OK
Time taken: 0.684 seconds
hive> select * from trainee_list;
OK
Jonathan Hadoop 3.0
Jonathan Linux 2.5
Jonathan Java 5.0
Joseph Hadoop 4.2
Joseph Linux 4.8
Joseph Java 3.0
Jotaro Hadoop 3.0
Jotaro Linux 4.2
Time taken: 0.341 seconds



これで、Hiveのテーブルにデータを取り込むことが出来ました。なお、テーブルのデータはHDFS(/user/hive/warehouse以下)に格納されています。次に、このデータから受講者マスタとクラスマスタのテーブルを作成します。

これ以降の処理でrow_sequence関数を使います。row_sequence関数を有効にするために、以下のコマンドを実行します。
hive> set mapred.reduce.tasks=1;
hive> create temporary function row_sequence as  
    > 'org.apache.hadoop.hive.contrib.udf.UDFRowSequence';
OK
Time taken: 0.0090 seconds
なお、上記2つのコマンドはhiveシェルにログインするたびに必要なようです。

次に、受講者マスタテーブルを作成するために、以下のコマンドを実行します。
hive> create table trainee_master                   
    > row format delimited
    > fields terminated by ','
    > as select row_sequence() as id, tname as name 
    > from trainee_list                             
    > group by tname; 
・・・省略・・・
OK
Time taken: 29.581 seconds
hive> select * from trainee_master;
OK
1 Jonathan
2 Joseph
3 Jotaro
Time taken: 0.089 seconds



同様にクラスマスタの方も作成します。
hive> create table class_master                     
    > row format delimited                          
    > fields terminated by ','                      
    > as select row_sequence() as id, cname as name  
    > from trainee_list                             
    > group by cname;                               
・・・省略・・・
OK
Time taken: 22.834 seconds
hive> select * from class_master;
OK
1 Hadoop
2 Java
3 Linux


※LinuxとJavaの順番が入れ替わってしまいました(汗)


最後にこれらのデータを使って、Mahoutのインプットデータを作成します。
hive> create table mahout_input                     
    > row format delimited                          
    > fields terminated by ','                      
    > as select tm.id as tid, cm.id as cid, tl.score as score
    > from trainee_list tl join trainee_master tm
    > on tl.tname=tm.name
    > join class_master cm
    > on tl.cname=cm.name
    > sort by tid,cid; 
・・・省略・・・
OK
Time taken: 76.213 seconds
hive> select * from mahout_input;
OK
1 1 3.0
1 2 5.0
1 3 2.5
2 1 4.2
2 2 3.0
2 3 4.8
3 1 3.0
3 3 4.2
Time taken: 0.074 seconds



これでMahoutのインプットデータが完成しました。ちなみに、今回は見やすくするためにソートを使いましたが、処理が遅くなるので実際には使わない方がいいと思います。

次回はこのデータをもとに、レコメンドをしていきます。







2012年12月9日日曜日

Hadoopをカスタマイズする

Hadoopのソースコードはよく目を通しますが、自分でカスタマイズしたことはなかったので、少しコードをいじってみました。


とりあえず簡単なところから・・・hadoop job -list allというコマンドを実行すると、今まで実行したJobの情報が見れます。


MasahiroPC:core masahiro$ hadoop job -list all
・・・省略・・・
Running : 1 Succeded : 2 Failed : 3 Prep : 4
JobId State StartTime UserName Priority SchedulingInfo
job_201212082249_0001 2 1354974604193 masahiro NORMAL NA


見ての通り、Stateから先のラベルがずれているので、合わせてみます。

コードを追っていくと、Jobの一覧表示はorg.apache.hadoop.mapred.JobClientが行っていることが分かります。displayJobListメソッドを以下のように変更しました。
※ファイル=$HADOOP_HOME/src/mapred/org/apache/hadoop/mapred/JobClient.java


void displayJobList(JobStatus[] jobs) {    
 System.out.printf("JobId\t\t\tState\tStartTime\tUserName\tPriority\tSchedulingInfo\n");
  for (JobStatus job : jobs) {
    System.out.printf("%s\t%d\t%d\t%s\t%s\t\t%s\n", job.getJobID(), job.getRunState(),job.getStartTime(), job.getUsername(), job.getJobPriority().name(), job.getSchedulingInfo());
  }
}
黄色のマーカーが追加した部分です。


次にHadoopをこの修正したコードで実行できるようにビルドしますが、javacコマンドなどを使って手動で行うのは面倒です。そこで、HadoopではApache Antを使用します。

Apache Antはビルドツールと言って、コンパイルなどに必要な情報をbuild.xmlファイルに記述しておくことで手軽にビルドできるというものです。詳しくはこちら(http://www.javadrive.jp/ant/)をご覧ください。また、似たようなツールにmavenというものもあります。Hadoopにはbuild.xmlが付属しているので、Antを使えば簡単にビルドできます。

詳細は省きますが、build.xmlによると、今回修正したコードをビルドするには以下のコマンドを実行すればいいようです。


ant compile-mapred-classes
※一回目は少し時間がかかります。

BUILD SUCCESSFULと出ましたら、Hadoopのプロセスを起動し、適当なJobを投げたあとでhadoop job -list allを実行してみます。




Running : 1 Succeded : 2 Failed : 3 Prep : 4
JobId                                     State StartTime             UserName Priority      SchedulingInfo
job_201212090101_0001 2        1354982617339 masahiro    NORMAL NA


ちゃんとラベルの位置がそろいました。フォントの設定上(?)ここではそろってないですが、ターミナルではちゃんとそろってます(汗)

ということでHadoopはこのように簡単にカスタマイズ出来ます。今回は練習なので簡単な例ですが、例えば出力されるログをカスタマイズしたり、自作の管理コマンドを作ることも出来ます。また、Hadoop JIRA(https://issues.apache.org/jira/browse/HADOOP)で公開されているバグのパッチを作成し、コミュニティに貢献することも出来ます。

なお、上記の例はあくまでもサンプルであり、修正することでWebUIなど、他の機能に影響が出るかもしれないことをご承知置きください。また気が向いたら、ちゃんと使えるものを実装してみます。