livedoorグルメのデータで機械学習してみる⑥

ようやく協調フィルタリングに再挑戦できる状況に。
Pythonで変換した入力ファイルをHDFSにputして「Item-Based Recommendations」を実行。

$ hadoop fs -put ldgourmet_ratings_for_ibr2.csv ldgourmet

$ mahout recommenditembased -s SIMILARITY_LOGLIKELIHOOD -i ldgourmet/ldgourmet_ratings_for_ibr2.csv -o ldgourmet/output --numRecommendations 3
MAHOUT_LOCAL is not set; adding HADOOP_CONF_DIR to classpath.
Running on hadoop, using /usr/lib/hadoop/bin/hadoop and HADOOP_CONF_DIR=/etc/hadoop/conf
MAHOUT-JOB: /usr/lib/mahout/mahout-examples-0.9-cdh5.3.1-job.jar
15/03/17 23:07:52 INFO common.AbstractJob: Command line arguments: {--booleanData=[false], --endPhase=[2147483647], --input=[ldgourmet/ldgourmet_ratings_for_ibr2.csv], --maxPrefsInItemSimilarity=[500], --maxPrefsPerUser=[10], --maxSimilaritiesPerItem=[100], --minPrefsPerUser=[1], --numRecommendations=[3], --output=[ldgourmet/output], --similarityClassname=[SIMILARITY_LOGLIKELIHOOD], --startPhase=[0], --tempDir=[temp]}
15/03/17 23:07:52 INFO common.AbstractJob: Command line arguments: {--booleanData=[false], --endPhase=[2147483647], --input=[ldgourmet/ldgourmet_ratings_for_ibr2.csv], --minPrefsPerUser=[1], --output=[temp/preparePreferenceMatrix], --ratingShift=[0.0], --startPhase=[0], --tempDir=[temp]}
15/03/17 23:07:53 INFO Configuration.deprecation: mapred.input.dir is deprecated. Instead, use mapreduce.input.fileinputformat.inputdir
15/03/17 23:07:53 INFO Configuration.deprecation: mapred.compress.map.output is deprecated. Instead, use mapreduce.map.output.compress
15/03/17 23:07:53 INFO Configuration.deprecation: mapred.output.dir is deprecated. Instead, use mapreduce.output.fileoutputformat.outputdir
15/03/17 23:07:54 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:8032
15/03/17 23:07:55 WARN security.UserGroupInformation: PriviledgedActionException as:take (auth:SIMPLE) cause:org.apache.hadoop.mapred.FileAlreadyExistsException: Output directory temp/preparePreferenceMatrix/itemIDIndex already exists
Exception in thread "main" org.apache.hadoop.mapred.FileAlreadyExistsException: Output directory temp/preparePreferenceMatrix/itemIDIndex already exists
        at org.apache.hadoop.mapreduce.lib.output.FileOutputFormat.checkOutputSpecs(FileOutputFormat.java:146)
        at org.apache.hadoop.mapreduce.JobSubmitter.checkSpecs(JobSubmitter.java:554)
        at org.apache.hadoop.mapreduce.JobSubmitter.submitJobInternal(JobSubmitter.java:430)
        at org.apache.hadoop.mapreduce.Job$10.run(Job.java:1295)
        at org.apache.hadoop.mapreduce.Job$10.run(Job.java:1292)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.security.auth.Subject.doAs(Subject.java:415)
        at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1642)
        at org.apache.hadoop.mapreduce.Job.submit(Job.java:1292)
        at org.apache.hadoop.mapreduce.Job.waitForCompletion(Job.java:1313)
        at org.apache.mahout.cf.taste.hadoop.preparation.PreparePreferenceMatrixJob.run(PreparePreferenceMatrixJob.java:77)
        at org.apache.hadoop.util.ToolRunner.run(ToolRunner.java:70)
        at org.apache.mahout.cf.taste.hadoop.item.RecommenderJob.run(RecommenderJob.java:164)
        at org.apache.hadoop.util.ToolRunner.run(ToolRunner.java:70)
        at org.apache.mahout.cf.taste.hadoop.item.RecommenderJob.main(RecommenderJob.java:322)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at org.apache.hadoop.util.ProgramDriver$ProgramDescription.invoke(ProgramDriver.java:72)
        at org.apache.hadoop.util.ProgramDriver.run(ProgramDriver.java:145)
        at org.apache.hadoop.util.ProgramDriver.driver(ProgramDriver.java:153)
        at org.apache.mahout.driver.MahoutDriver.main(MahoutDriver.java:195)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at org.apache.hadoop.util.RunJar.main(RunJar.java:212)

あれ、、、またエラーに。
今度は中間データと思われるファイルの出力先が既に存在している、と。
恐らく前回エラーになったときに残ったのだと思われるが、それにしても指定したパス以外に勝手にディレクトリを作っておいて、残したままにしておくとは。もうちょっと後処理ちゃんとせえよ…。
とぼやいていても仕方ないのでディレクトリを消して再度。

$ hadoop fs -rmr temp/preparePreferenceMatrix

$ mahout recommenditembased -s SIMILARITY_LOGLIKELIHOOD -i ldgourmet/ldgourmet_ratings_for_ibr2.csv -o ldgourmet/output --numRecommendations 3
(省略)
15/03/17 23:18:37 INFO driver.MahoutDriver: Program took 347306 ms (Minutes: 5.788433333333334)

MapReduceが9回ほど動いて5分ちょいで無事終了。
出力データの中身を見てみる。

$ hadoop fs -ls ldgourmet/output
Found 2 items
-rw-r--r--   1 take supergroup          0 2015-03-17 23:18 ldgourmet/output/_SUCCESS
-rw-r--r--   1 take supergroup     245633 2015-03-17 23:18 ldgourmet/output/part-r-00000
$ hadoop fs -tail ldgourmet/output/part-r-00000
9923823]
4278205127      [1050:4.0119233,138:4.007087,586:4.007003]
4278971698      [277:1.0,1637:1.0]
4279952571      [6645:2.50514,581:1.0,4244:1.0]
4281265025      [311518:4.0,370162:4.0,332770:4.0]
4281634113      [300933:4.4904943,335499:4.0,373547:4.0]
4281870486      [11824:4.500969,21348:4.488718,3729:4.487594]
4282442439      [2358:4.0,313:4.0]
4282703212      [384403:4.2505198,334805:4.20008,380188:4.20008]
4283170779      [7910:5.0,8796:5.0]
4283926049      [12555:4.4908667]
4284165778      [8728:5.0,2701:5.0,2302:5.0]
4285085889      [300933:5.0,301515:5.0,323026:4.673149]
4288106213      [406041:4.0870314,396687:4.087023,359265:4.083425]
4288708981      [2945:4.504145,4365:4.502466,8206:4.502196]
4289424931      [2297:5.0,11250:4.5070863,20130:4.498961]
4289532598      [308708:5.0,300489:5.0,308715:5.0]
4291952257      [5343:4.0,5433:4.0,2044:4.0]
4291973773      [2855:5.0,891:5.0,3574:5.0]
4292027381      [352331:4.0,374319:4.0,341967:4.0]
4292733388      [3240:4.514431,10312:4.4899635,20040:4.0136404]
4293060625      [4836:5.0,25064:5.0,3074:5.0]
4294950334      [7207:4.494212,6595:4.493965,12624:4.0]

それっぽく出ている感じ。
データはユーザーIDに対しておすすめレストランのIDと点数のセットが3つ(パラメータで3つレコメンドするようにしたので)出ているよう。元の評価データが5点満点だからか見た感じ5点満点で点数が出ている。

ちょっと気になるのは、3つとも満点で出ているユーザーが結構いること。そんなにお勧めなのか、あるいは自分が評価して満点を付けたレストランが出ているのか。

試しに元データを追ってみる。

$ grep 4293060625 ldgourmet_ratings_for_ibr2.csv
4293060625,433,5
4293060625,6839,5
4293060625,302354,5
$ grep 4291973773 ldgourmet_ratings_for_ibr2.csv
4291973773,4243,4
4291973773,6404,5
4291973773,6925,5
4291973773,11719,4
4291973773,13890,5
4291973773,17502,4
4291973773,19616,3

どうやら、自分が評価したのとは違うレストランがちゃんと選ばれている様子。
ほんとに適切なレコメンドがされているのか気になるが、今日はここまで。
次回、レコメンド結果を定性的に評価してみよ。

livedoorグルメのデータで機械学習してみる⑤

データクレンジングにあたり必要な機能のPythonでの実装方法の確認が一通り終わったので組み合わせてデータ変換プログラムを完成させる。

convert_ratings.py

# -*- coding: utf-8 -*-
 
import sys
 
if __name__ == "__main__":
    arg = sys.argv
 
    input_file = arg[1]
    output_file = arg[2]
 
    with open(input_file, 'r') as in_file:
        with open(output_file, 'w') as out_file:
            rows = 0
            for line in in_file:
                rows += 1

                # 先頭行はヘッダなのでスキップ
                if rows == 1:
                    continue

                list = line.split(",")

                restaurant_id = list[1]

                # ユーザーIDは16進->10進変換
                user_id = long(list[2], 16)

                total = list[3]

                out_file.write(str(user_id) + "," + restaurant_id + "," + total + "\n")

                if rows % 1000 == 0:
                    print(str(rows) + "行変換しました。")

            print(str(rows) + "行変換しました。")

まあ、こんな感じで。
では、ようやく変換を。

$ python convert_ratings.py ratings.csv ldgourmet_ratings_for_ibr2.csv
1000行変換しました。
2000行変換しました。
(省略)
205833行変換しました。

$ head ldgourmet_ratings_for_ibr2.csv
3993170538,310595,5
4240577537,10237,1
104934135,3334,2
104934135,15163,5
1290717597,567,3
1290717597,1026,5
1290717597,1058,5
1290717597,2569,3
1290717597,3309,4
1290717597,3648,4

バッチリ。
とりあえず今日はここまでにして、次回いよいよ協調フィルタリングに再挑戦。

livedoorグルメのデータで機械学習してみる④

Mahoutに読み込ませるデータのユーザーIDをlong型にしなければならない。
Hiveの関数を見てみたが文字列を数値に変換できそうなものがなかったので別の手段を使うことに。
簡単なプログラム書くだけなので何でもいいのだけれど、せっかくなので勉強してみようと思っていたPythonを使ってみる。

開発環境でコマンドを叩いてみると幸いデフォルトでインストール済みのよう。CentOS6のデフォルトで2.6.6とちょっと古めだが、大したことをするわけではないので特に問題はないだろうと思ってバージョンアップはしないまま使う。

さて、必要なプログラムの要件はとりあえず以下のような感じ。
・コマンドライン引数で入力ファイルと出力ファイルのパスを受け取る
・入力ファイルを1行ずつ読み込み、必要な項目を抜き出してレイアウトを整え、出力ファイルに出力する
・ユーザーID(データを見た感じ16進表記の数値っぽい)はJavaのlong型に変換可能な数値にして出力

Python初体験で構文も全く知らないド素人なので一歩ずつ進める。
参考にしたのは↓こちら。(微妙にバージョン違うけど…)

Python 2.7ja1 documentation
Python-ism

続きは別記事にて。

livedoorグルメのデータで機械学習してみる③

いよいよ口コミデータを協調フィルタリングで処理してみる。
まずは、Hiveで作った入力データをHDFSに置く。(HDFS上のHiveの出力データそのままでもよかったのだけど)

$ hadoop fs -mkdir ldgourmet
$ hadoop fs -put ldgourmet_ratings_for_ibr.csv ldgourmet

次に、下記のサイトのサンプルを参考に「Item-Based Recommendations」を実行。

Introduction to Item-Based Recommendations with Hadoop

$ mahout recommenditembased -s SIMILARITY_LOGLIKELIHOOD -i ldgourmet/ldgourmet_ratings_for_ibr.csv -o ldgourmet/output --numRecommendations 3
(省略)
Error: java.lang.NumberFormatException: For input string: "ee02f26a"
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
        at java.lang.Long.parseLong(Long.java:441)
        at java.lang.Long.parseLong(Long.java:483)
        at org.apache.mahout.cf.taste.hadoop.ToEntityPrefsMapper.map(ToEntityPrefsMapper.java:60)
        at org.apache.mahout.cf.taste.hadoop.ToEntityPrefsMapper.map(ToEntityPrefsMapper.java:30)
        at org.apache.hadoop.mapreduce.Mapper.run(Mapper.java:145)
        at org.apache.hadoop.mapred.MapTask.runNewMapper(MapTask.java:784)
        at org.apache.hadoop.mapred.MapTask.run(MapTask.java:341)
        at org.apache.hadoop.mapred.YarnChild$2.run(YarnChild.java:168)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.security.auth.Subject.doAs(Subject.java:415)
        at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1642)
        at org.apache.hadoop.mapred.YarnChild.main(YarnChild.java:163)
(省略)
Exception in thread "main" java.io.FileNotFoundException: File does not exist: /user/take/temp/preparePreferenceMatrix/numUsers.bin
        at org.apache.hadoop.hdfs.server.namenode.INodeFile.valueOf(INodeFile.java:65)
        at org.apache.hadoop.hdfs.server.namenode.INodeFile.valueOf(INodeFile.java:55)
        at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.getBlockLocationsUpdateTimes(FSNamesystem.java:1878)
        at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.getBlockLocationsInt(FSNamesystem.java:1819)
        at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.getBlockLocations(FSNamesystem.java:1799)
        at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.getBlockLocations(FSNamesystem.java:1771)
        at org.apache.hadoop.hdfs.server.namenode.NameNodeRpcServer.getBlockLocations(NameNodeRpcServer.java:527)
        at org.apache.hadoop.hdfs.server.namenode.AuthorizationProviderProxyClientProtocol.getBlockLocations(AuthorizationProviderProxyClientProtocol.java:85)
        at org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolServerSideTranslatorPB.getBlockLocations(ClientNamenodeProtocolServerSideTranslatorPB.java:356)
        at org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos$ClientNamenodeProtocol$2.callBlockingMethod(ClientNamenodeProtocolProtos.java)
        at org.apache.hadoop.ipc.ProtobufRpcEngine$Server$ProtoBufRpcInvoker.call(ProtobufRpcEngine.java:587)
        at org.apache.hadoop.ipc.RPC$Server.call(RPC.java:1026)
        at org.apache.hadoop.ipc.Server$Handler$1.run(Server.java:2013)
        at org.apache.hadoop.ipc.Server$Handler$1.run(Server.java:2009)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.security.auth.Subject.doAs(Subject.java:415)
        at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1642)
        at org.apache.hadoop.ipc.Server$Handler.run(Server.java:2007)
(省略)

エラーになった。
いくつかExceptionが出ているが、根本はユーザーIDのデータをlongに変換しようとしてエラーになっているのが原因と思われる。ていうか何故long?ユーザーIDが数値でなければならない理由が分からない。。。

よく分からないがGitHub上のRecommender.javaItemBasedRecommender.javaあたりを見てみると確かにuserIDがlongになっている。でも理由は不明。

理由は不明だがとりあえずlongに変換したデータを作ってやってみる。
でも、今日はここまで。

livedoorグルメのデータで機械学習してみる②

結局Hiveを使ってデータのクレンジングをすることに。

まずはテーブルを作ってダウンロードした元データを取り込む。

create table ldgourmet_ratings_org (
    id                  int,
    restaurant_id       int,
    user_id             string,
    total               int,
    food                int,
    service             int,
    atmosphere          int,
    cost_performance    int,
    title               string,
    body                string,
    purpose             string,
    created_on          string
) row format delimited fields terminated by ',';

load data local inpath 'ratings.csv' into table ldgourmet_ratings_org;    

ちゃんと取り込めたかどうかHiveQLで抽出してみる。

select count(distinct restaurant_id), count(distinct user_id), count(*) from ldgourmet_ratings_org;
(省略)
OK
72181   17771   205833
Time taken: 45.217 seconds, Fetched: 1 row(s)

次に、Mahoutに食べさせるデータ形式のテーブルを作成し、そこに抽出。
必要なデータは「ユーザーID,アイテムID,評価」なので、「user_id,restaurant_id,total」とする。
取り込んだデータの1行目がヘッダ行だったので、ついでにそれも除く。

create table ldgourmet_ratings_for_ibr (
    user_id             string,
    restaurant_id       int,
    total               int
) row format delimited fields terminated by ',';

insert overwrite table ldgourmet_ratings_for_ibr
select user_id, restaurant_id, total from ldgourmet_ratings_org
where user_id <> 'user_id';

select * from ldgourmet_ratings_for_ibr limit 10;
OK
ee02f26a        310595  5
fcc21401        10237   1
06412af7        3334    2
06412af7        15163   5
4ceec99d        567     3
4ceec99d        1026    5
4ceec99d        1058    5
4ceec99d        2569    3
4ceec99d        3309    4
4ceec99d        3648    4
Time taken: 0.056 seconds, Fetched: 10 row(s)

できあがったテーブルのデータをファイルとして取得。

$ hadoop fs -ls /user/hive/warehouse/ldgourmet_ratings_for_ibr/
Found 1 items
-rwxr-xr-x   1 take supergroup    3476773 2015-03-04 00:10 /user/hive/warehouse/ldgourmet_ratings_for_ibr/000000_0

$ hadoop fs -get /user/hive/warehouse/ldgourmet_ratings_for_ibr/000000_0 ldgourmet_ratings_for_ibr.csv

今日はここまで。

livedoorグルメのデータで機械学習してみる①

Mahoutを使って機械学習の勉強するにあたって適当な題材がないか探していたところ、livedoorグルメ(今はロケタッチグルメというサービスに変わっている様子)のデータが学術用に公開されていることを発見。

利用規約に「学術研究の目的に限ります」と書いてあるが、個人の勉強も「学術研究」の範囲内だろうということで使わせていただく。(少なくとも商用利用するわけではないし)

livedoor グルメの DataSet を公開

データを見てみると「口コミデータ」と「口コミへの投票データ」がトランザクションデータで、後はそれに関連するマスタのよう。「口コミへの投票」の意味がよく分からなかったが、実際のサイトを見てみるとその口コミに対して閲覧者が「参考になった」ボタンを押下したログだと思われる。livedoorグルメのAPIだと「口コミデータ」のレイアウトの中に「投票数」という項目があるので、恐らくこれの元ネタだろう。

さて、ではこのデータを使ってどんな機械学習をしてみようか…
いろいろ考えてみたが、そもそもデータマイニングに詳しくないのであまり思いつかない。ということで、ベタだがレコメンデーションでもやってみよう。

で、レコメンデーションといえば代表的なのが協調フィルタリング。簡単に言うと買物する商品が似ているAさんとBさんを探し、Aさんが買っていなくてBさんが買っているものをAさんにお勧めする、というようなもの。

今回は口コミデータを元に、評価が似ているAさんとBさんを探し、Aさんが評価していなくてBさんが高い評価をしている店をAさんにレコメンドする、ということを目標にしてみる。たった20万件しか口コミデータがなく、しかも全国に範囲が散らばっていて、そもそも似た傾向の人を探すこと自体が可能なのか疑問だが、とりあえずやってみよう。

Mahoutの協調フィルタリングについて調べてみると、「Item-Based Recommendations」というのが上記に該当するアルゴリズムのよう。Mahout公式サイトの以下のページの解説によると必要なデータは「ユーザーID,アイテムID,評価」という形式だとわかる。

Introduction to Item-Based Recommendations with Hadoop

livedoorグルメの口コミデータは以下のレイアウトなので、「user_id,restaurant_id,total」を抜き出せば良さそう。(本当は評価の細かい内訳を使った方が精度が高そうだが、Mahoutのアルゴリズムが複数の評価項目を受け取れなさそうなのと、データをざっと見た限り総合評価以外の点数がまともに入っていなさそうなのとで諦める)

  • id 口コミID
  • restaurant_id 対象お店ID
  • user_id ユーザID
  • total 総合評価(0-5)
  • food 料理評価(0-5)
  • service サービス評価(0-5)
  • atmosphere 雰囲気評価(0-5)
  • cost_performance コストパフォーマンス評価(0-5)
  • title 口コミコメントタイトル
  • body 口コミコメント
  • purpose 利用目的
  • created_on 投稿日時

ということで、Mahoutに読み込ませるためにデータの加工が必要になった。
20万件ならExcelでも何とかなりそうなレベルだが、せっかくなのでプログラムで処理する。Hadoopの勉強なのでMapReduceでやるか、あるいはHiveでお気軽にやるか、はたまたちょっと触ってみようかと思ってずっと放置しているPythonを試してみるか。

悩んだので今日はここまで。

Mahoutのインストールとサンプル実行

Hadoop上で動く機械学習ライブラリMahoutを入れてみる。

# yum -y install mahout
(省略)
====================================================================================================
 Package                 Arch     Version                                     Repository       Size
====================================================================================================
Installing:
 mahout                  noarch   0.9+cdh5.3.1+18-1.cdh5.3.1.p0.17.el6        cloudera-cdh5    64 M
Installing for dependencies:
 hadoop-0.20-mapreduce   x86_64   2.5.0+cdh5.3.1+791-1.cdh5.3.1.p0.17.el6     cloudera-cdh5    28 M
 hadoop-client           x86_64   2.5.0+cdh5.3.1+791-1.cdh5.3.1.p0.17.el6     cloudera-cdh5    31 k
 hbase                   x86_64   0.98.6+cdh5.3.1+74-1.cdh5.3.1.p0.17.el6     cloudera-cdh5    47 M

Transaction Summary
====================================================================================================
Install       4 Package(s)
(省略)

依存関係のあるパッケージも含めて完了。
hadoop-0.20-mapreduceがインストールされたのはちょっと気になる。
なぜMRv1に依存?YARNでは動かないということだろうか。

ただ、MRv1がインストールされてもJobTrackerやTaskTrackerはサービス登録されていない様子。もしこのまま動くならYARN上で動いているということに。

サンプルを動かしてみる。
とりあえず以下のサイトに載っているものを。
Mahoutで体感する機械学習の実践 第3回 Mahoutの環境構築とFP-Growthによるマーケットバスケット分析

$ wget http://image.gihyo.co.jp/assets/files/dev/serial/01/mahout/0003/gihyo-mahout-fpg-sample.csv

$ mahout fpg -i gihyo-mahout-fpg-sample.csv -o output/gihyo-mahout-fpg-sample
(省略)
15/02/25 23:20:00 WARN driver.MahoutDriver: Unable to add class: fpg
java.lang.ClassNotFoundException: fpg
(省略)

エラー。調べたところ、どうやらMahout 0.9からFP-Growthはなくなったらしい。
そんな簡単にアルゴリズムが消えるなんて、怖くて使えない。。。

仕方ないので、別のアルゴリズムのサンプルでも。
今度は以下のサイトのものを。

Mahoutで体感する機械学習の実践 第5回 K-MeansとCanopyクラスタリングでセグメンテーション分析を行う

$ wget http://image.gihyo.co.jp/assets/files/dev/serial/01/mahout/0005/gihyo-mahout-kmeans-sample.arff
$ mahout arff.vector --input gihyo-mahout-kmeans-sample.arff --output  gihyo-mahout-kmeans-sample.vector --dictOut gihyo-mahout-kmeans-sample.dict
$ mahout kmeans --input gihyo-mahout-kmeans-sample.vector --output gihyo-kmeans-output --maxIter 50 --numClusters 10 --clusters gihyo-kmeans-null-cluster --clustering -dm org.apache.mahout.common.distance.EuclideanDistanceMeasure
(省略)
15/02/26 00:14:39 INFO driver.MahoutDriver: Program took 684308 ms (Minutes: 11.405133333333334)

–maxIter(クラスタ重心点の最大計算回数)に50をセットしたせいか、MapReduceが33段も動いてえらく時間がかかった。でも、結局MRv1ではなくYARN上のMRv2で動いたよう。
出力結果を見てみる。

$ hadoop fs -ls -R gihyo-kmeans-output
(省略)
drwxr-xr-x   - take supergroup          0 2015-02-26 00:14 gihyo-kmeans-output/clusters-33-final
-rw-r--r--   1 take supergroup          0 2015-02-26 00:14 gihyo-kmeans-output/clusters-33-final/_SUCCESS
-rw-r--r--   1 take supergroup        194 2015-02-26 00:14 gihyo-kmeans-output/clusters-33-final/_policy
-rw-r--r--   1 take supergroup       2633 2015-02-26 00:14 gihyo-kmeans-output/clusters-33-final/part-r-00000
(省略)

finalが付いているのが最終出力らしい。
中身を見てみる。

$ mahout clusterdump --input gihyo-kmeans-output/clusters-33-final --output ./gihyo-kmeans-dump.txt
$ cat gihyo-kmeans-dump.txt
VL-699{n=92 c=[74.815, 69.804, 13.402] r=[13.741, 16.057, 9.035]}
VL-733{n=95 c=[23.189, 83.537, 25.747] r=[13.245, 11.736, 13.585]}
VL-124{n=103 c=[18.505, 75.049, 74.388] r=[11.747, 14.355, 14.764]}
VL-669{n=106 c=[21.613, 25.462, 24.311] r=[14.588, 14.396, 14.659]}
VL-992{n=93 c=[50.312, 45.806, 43.602] r=[11.221, 12.955, 11.853]}
VL-90{n=98 c=[23.133, 21.051, 79.714] r=[13.375, 13.353, 13.786]}
VL-172{n=104 c=[66.683, 72.288, 84.462] r=[16.433, 15.380, 10.757]}
VL-977{n=98 c=[80.327, 81.388, 49.092] r=[13.999, 12.359, 13.020]}
VL-329{n=105 c=[73.524, 21.086, 83.914] r=[14.420, 13.686, 11.711]}
VL-864{n=106 c=[79.698, 17.934, 31.972] r=[12.508, 12.466, 16.247]}

無事に動いたようなので今回はこれで終わり。