今回は2ちゃんねるデータを処理するという実践を行ってみます。
品詞解析ライブラリの導入
品詞解析にはいくつかのツールがありますが、今回はMeCabというツールを使用します。
- MeCabのサイト
- URL:http://mecab.sourceforge.net/
MeCab本体と、CMeCabというMeCabをJNIで使用するライブラリを導入します。
MeCab本体のセットアップ
mecab-0.98.tar.gzをダウンロード&展開します。
/usr/local/libにlibmecab.so.1が入ります。
辞書のセットアップ
MeCabの動作には辞書が必要ですので、辞書のセットアップを行います。
mecab-ipadic-2.7.0-20070801.tar.gzをダウンロード&展開します。
以上でMeCabの導入が終わりました。
CMeCabの導入にはprotocol bufferとsconsが必要となります。
protocol buffer
URL:http://code.google.com/p/protobuf/downloads/list
このページから最新版をダウンロードします。
scons
CMeCab
CMeCabのサイトで公開されている。cmecab-1.7.tar.gzをダウンロード&展開
bin/cmecab-1.7.jarが生成されるので、任意のクラスパスにコピーします。ここでは、/usr/local/apache_proj/myproj/lib/を作成しそこにコピーしました。
ライブラリの生成
続いてネイティブライブラリの生成です。sconsを使用してコンパイルしますが、javahomeのパスが間違っている場合は、jni/SConstructファイルを次のように編集します。
sconsを実行します。
環境変数の設定
今回jarファイルを/usr/local/apache_proj/myproj/libにおきましたが、hadoopはこのパスのことを知りません。hadoopの環境変数に設定してあげる必要があります。$HADOOP_HOME/conf/hadoop-env.shを編集します。13行目がコメントになっていると思いますので、コメントを外し、cmecab-1.7.jarへのパスを記述します。
cmecab-1.7.jarからJNIによりCMeCabライブラリが呼び出されますが、下記の設定が必要です。MapReduceの処理はJVMのプロセスが新規で起こされますが、そのJVMにパラメータを渡すことができます。cmecab-1.7.jarで使用されるCMeCabのライブラリへのパスを記述する必要があります。
$HADOOP_HOME/conf/mapred-site.xmlに追記(赤字部分)。
また、上記CMeCabライブラリがMeCabのライブラリを使用しているため、全てのサーバのhユーザの.bashrcファイルに環境変数を設定します。~h/.bashrcに下記を追記します。
ここでhadoopを再起動します。
これで準備が整いました!
MapReduceプログラム
ようやくプログラム本体に入れます。今回はJAVAのネイティブなプログラムで機能の実装を行います。
基本的な流れをおさらいすると以下の通りです。
プログラムの全体像
MapReduce処理はデータの入出力がキーとバリューと決まっているため、Map, Reduce処理に何をキーにして、何をバリューにするか、にさえ注意すればわりと簡単に書くことができます。ここら辺が複雑な分散コンピューティングとの差であると言えます。
プログラムの全体像はたったこれだけです。
メイン処理ではジョブの定義とmapperとreducerは誰?というのを定義しています。色をつけた部分がメインとmapper, reducerの関係する部分です。
ここにMap処理、Reduce処理の部分に処理を埋めていけばそれでOKです。簡単ですよね!?
Map処理
まずMap処理から見ていきましょう。
Map処理で入力とされる2ちゃんねるデータは以下のようなフォーマットとなります(ある3行だけ抜粋&加工しています)。
それではMap処理の出力はどうでしょうか?
キーがunixtime(シリアルな秒)を600秒(10分)で丸めた値で、バリューとして品詞解析したパーツを並べています。1行が品詞の数だけ複数行に展開されたようなイメージです。キーが600秒で丸められていますので、2行目と3行目は同一のキーとして扱われています。ただ“、”や“。”などが出力されてしまうのはイマイチかもしれません。ここは名詞だけを出力するようにします。
それでは実際のコードを見てみましょう。innerクラスJ2chMapperです。
この部分で、mapperクラスの
- 入力キー、バリュー(LongWritable, Text)
- 出力キー、バリュー(IntWritable, Text)
を定義しています。map()には入力キー、バリューが渡され実行されます(第三引数のContextは意識しなくてもいいです)。今回のmapにおけるキーはファイルのバイト位置になります(デフォルトの入力フォーマットTextInputFormatが適用されているため)。また、バリューでは行データが渡されます。
つまり、今回のデータの場合、
というふうに1行目が処理されます。2行目は、
という具合です。
今回のmap処理ではバイト位置は不要ですので、key変数は使っていません。“600”というマジックナンバーを使ってたりしますが、この辺はstaticな値として変数にした方がいいですね。
品詞解析している部分を別のクラスにして隠蔽化、抽象化した方が別の品詞解析エンジンを使ったときに柔軟に対応できますね、など突っ込みどころがあるかと思いますが、まずはこれだけのコードでmap処理がかけることに驚いてください。
内容は単純なテキスト処理ですので、コメントを追っていただけるとご理解いただけると思います。
Recude処理
続いてReduce処理です。
Reduce処理の入力はMap処理の出力になります。名詞のみに絞った場合下記のようになりました。
次に出力です。
丸めたunixtimeごとに出現回数の多い単語順にベスト5が出力されます。これが最終的なMapReduceの出力となります(ファイル出力となる)。
それでは実際のコードを見てみましょう。J2chReduerクラスです。
こちらもJ2chMapperと同様に、
の部分が、reducerクラスの
- 入力キー、バリュー(IntWritable, Text)
- 出力キー、バリュー(IntWritable, Text)
を定義しています。Mapperの出力とReducerの入力が同じであることに注意してください。
さて、reduce()関数ですが、第二引数がiterable<Text>となっています。mapの出力であればTextじゃないか、と思われるかもしれませんが、実際にはText型のリストが渡されます。つまり、mapの出力であると想定された下記は、
実際には
というふうに、キーごとにTextのリストが渡されます。また、Reduce処理の前にキーでソートされますので、同一キーについては必ず1つにまとめられます。
リストで渡された名詞の単語を一度Hashtableに登録し、ArrayListに入れ直してバリューで降順にソートしています(明らかに無駄な動作をしているような気がしますが…)。ソート後の単語と出現回数を上位5つまで出力しています。
これで当初の目的である、2ちゃんねるの実況スレッドから時間単位に出現数の多い単語を出力する、という機能が実装されました。
いかがでしたか? 思ってたよりずっと簡単に感じたんではないでしょうか? Map,Reduceのそれぞれの処理での入出力のフォーマットがある程度決まっていることで、肝心のやりたいことに集中できたと思います。
Map、Reduceというふうに役割がきっちり分かれていることで、どこに何を実装するべきかというのが自然に分担されたと思います。さらに、MapとReduceの間にキーでのソート、マージがあることもReduce処理の書きやすさを増していたと思います。
コンパイル
ソースのコンパイルですが、以下のようにクラスパスを指定してコンパイルします。
jarファイルの作成は以下のように行います。
実行
hadoopコマンドにJ2ch.jarを渡して実行させます。
hadoop jar プログラムのjar メイン関数を含んだクラス 第一引数 第二引数
という構文になります。
ここでは、第一引数に入力ファイル名、第二引数に出力ディレクトリを指定しています。入力ファイル、出力ディレクトリはいずれもHDFS上となります。出力先ディレクトリがすでにあるとエラーになるので、
というふうにディレクトリを削除しましょう。
出力データ例
「カイジ 人生逆転ゲーム」の実況スレのデータを処理しました。
ざっと眺めてみると、カイジという主人公の名前、利根川という相手役の名前、船井という人物、佐原、ざわざわ、土下座、焼、と原作をご存じの方や映画を見た方はピンとくるキーワードが並んでいるんではないでしょうか?? カット、原作という単語が多いのも原作からカットされているシーンが多いぞ、ということでしょうか。出現回数だけの解析結果としては傾向がつかめていると思います。おもしろいですね !
全プログラムリスト
最後に全リストを載せておきます。アスキーアートの除外や意味のない単語の削除など上の例では省いていた関数を載せいてます。
次回はJavaを使わないMapReduceの書き方をご紹介します!