データ分析基盤 

Hivemall Getting Started

この記事はHadoop/Hive/Hivemall環境をローカルマシン(mac)上に構築して、テストしてみたい方向けに執筆しています。

はじめに

昨今、数多くの機械学習向けライブラリがリリースされています。今回紹介するHivemallは、HiveUDF上で動作するため、大量データに対して分散処理を行う際に高い性能を示します。分散環境の構築を1から始めるのは容易ではないですが、擬似分散モードであれば、マシンが1台あれば比較的容易に構築から動作確認まですることができます。今回は、 Hivemallを使って、Kaggleのチュートリアル的タイトルの「Titanic」に挑戦したいと思います。 今回やることは以下の通りです。
  • HomeBrewのインストール
  • Hadoopのインストール
  • Hiveのインストール
  • Hivemallのインストール
  • Hivemallによる機械学習

Javaのインストール

今回、インストールするソフトウェアを動かすには、Java7以上が必要になります。お使いのマシンに、インストールされていないようであれば、Oracleから最新バージョン(Java8)をダウンロードしてください。

Homebrewのインストール

今回は、できるだけ簡単に進めるために、Homebrewを使用します。Homebrewがインストールされていない場合はインストールしてください。下記コマンドでインストールできます。
$ /usr/bin/ruby -e 
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
以降、特に断りがなければ、「$...」はターミナルからのコマンド実行を示しています。 Homebrewについて詳しくは、下記URLを参照してください。

Hadoopのインストール

ここでは、HadoopをSingle Node Clusterで利用できるようにします。Homebrewがインストールされていれば、なんら恐れる必要はありません。次のコマンドひとつでインストールできます。
$ brew install Hadoop
インストールが完了したら、正常にHadoopがインストールされているかを確認してみましょう。ここではスタンドアロンモードで動作を確認します。これには、Hadoopに同封されているテストプログラムを利用した単語の集計(wordcount)を利用します。 では、テスト用の入力データを作成しましょう。
$ echo "dog dog dog cat cat cat" > input/test1.txt
$ echo "dog dog dog" > input/test2.txt
Hadoopを起動します。
$ HADOOP_INSTALL=/usr/local/Cellar/hadoop/2.8.0/libexec
$ hadoop jar ${HADOOP_INSTALL}/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.8.0.jar wordcount input output
ここでは、inputディレクトリ以下のファイルが読み込まれて、ファイル内に記載される単語の件数が集計され、outputディレクトリに出力されます。${HADOOP_INSTALL}は、インストール環境によって異なる値となるため、設定前に確認してください。Homebrewのデフォルト設定であれば、「/usr/local/Cellar」以下にインストールされていると思います。集計している間は、色々とログが出ます。 結果を確認します。
$ ls output/
_SUCCESS     part-r-00000
集計された結果が「part-r-xxxx」の形式で出力されます。_SUCCESSは、正常終了された場合に出力される空のファイルです。
$ cat output/*
  dog	6
  cat	3
ちゃんと集計されていることがわかります。 次にHiveを利用可能な状態にするためにHadoopを擬似分散モードで起動できるようにします。擬似分散モード実行には、マシンへのssh接続許可が必要です。「システム環境設定」から「共有」を選択し、「リモートログイン:入」に設定してください。 また、擬似分散モードにするためには設定ファイルを変更する必要があります。Hadoopにはたくさんの設定ファイルがありますが、テスト的に動作させる分には、次のような変更で十分です。
$ vi ${HADOOP_INSTALL}/etc/hadoop/core-site.xml
<configuration>
</configuration>
  ⬇
<configuration>
  <property>
    <name>fs.defaultFS</name>
    <value>hdfs://localhost:9000</value>
 </property>
</configuration>
$ vi ${HADOOP_INSTALL}/etc/hadoop/hdfs-site.xml
<configuration>
</configuration>
  ⬇
<configuration>
  <property>
    <name>dfs.replication</name>
    <value>1</value>
  </property>
</configuration>
Namenodeを初期化します。
$ hdfs namenode –format
*localhostのパスワードを要求される場合、ログインパスワードを入力してください。 Hadoopを分散モード(HDFS)で起動します。
$ ${HADOOP_INSTALL}/sbin/start-dfs.sh
JavaのjpsツールでHDFSの起動を確認します。
$ jps
14561 NameNode
14754 SecondaryNameNode
14646 DataNode
数値は、その時によって変わります。この3つのプロセスが動いていることを確認してください。 また、以下のURLに接続できることを確認してください。
  • http://localhost:50070/
この画面から、HDFSのステータスを確認することができます。 正常に起動していれば、画面が表示されます。起動できていない場合、画面が表示されません。 では、実際に動かしてみましょう。 先ほどのテストデータを使って、wordcountで確認してみたいと思います。 まず、HDFS管理下にディレクトリを作成します。
$ hdfs dfs -mkdir /test
$ hdfs dfs -ls /
Found 1 items
drwxr-xr-x  -  user  group  0 2017-00-00 00:00 /test
HomeBrewでインストールした場合、以下のようなWARNログが出る場合があります。
WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform..
今回の確認では、動作に支障ありませんので、そのまま勧めていただいても構いません。解決方法はAPPENDIXを参照してください。 先に作成したテストデータをHDFS管理下にコピーします。
$ hdfs dfs -mkdir /test/hdfs_input
$ hdfs dfs -put input/* /test/hdfs_input
擬似分散環境下でのwordcountの動作を確認しましょう。
$ hadoop jar ${HADOOP_INSTALL}/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.8.0.jar wordcount /test/hdfs_input /test/hdfs_output
$ hdfs dfs -cat /test/hdfs_output/*
   dog	6
   cat	3
問題なく動作していることが確認できたので、Hiveのインストールに続きたいと思います。

Hiveのインストール

ここでは、Hiveをインストールし接続できるようになるところまでを確認します。HiveもHomebrewを利用すれば、簡単にインストールできます。
$ brew install hive
これでインストールは完了したので、初期設定と接続確認を行っていきましょう。 Hiveメタストアを初期化します。
$ schematool -dbType "derby" –initSchema
HiveServerを起動します。
$ hiveserver2 &> hiveserver2.log &
クライアントを起動し、Hiveに接続します。
$ beeline -u jdbc:hive2:// --color=true
schematool実行時にmetastore_dbが作成されます。クライアント起動時にmetastore_dbを参照しています。beelineコマンドはmetastore_dbディレクトリの配置されるディレクトリと同じディレクトリ内で実行してください。 テストとして、データベースの一覧を表示します。
//> show databases;
  +----------------+--+
  | database_name  |
  +----------------+--+
  | default        |
  +----------------+--+
*以降、特に断りがなければ、「//>」はbeelineプロンプトからのクエリ実行になります。 初期化以外何もしていないので、default以外の表示はないと思います。

Hivemallのインストール

HivemallはHiveUDFなためHomebrewではインストールできません。HivemallのWEBページから、「1.2. Getting Started」「1.2.1. Instllation」「Prerequisites」下の「hivemall-core-xxx-with-dependencies.jar」リンク先からjarファイルと「define-all.hive」をダウンロードしてください。 beelineから、以下のコマンドを実行してください。
//>
add jar ${Download}/hivemall-core-0.4.2-rc.1-with-dependencies.jar;
//>
source ${Download}/define-all.hive;
sourceは正常終了しない場合があります。その場合はdefine-all.hive内の「CREATE FUNCTION」を手動実行することで、代替する必要があります。

Hivemallによる機械学習

ようやく、本題になります。今回は、KaggleのTitanicデータに対してRandomForestを行い、生存者予測を行いたいと思います。

データの取得

まずは、Kaggleに登録して、データをダウンロードしてください。検索フォームから「titanic」と検索し、「Titanic: Machine Learning from Disaster」(https://www.kaggle.com/c/titanic)を開けば、「Data」から訓練データtrain.csvとテストデータtest.csvをダウンロードできます。 ここからは、Hivemallの「Kaggle Titanic tutorial」ページの内容に沿って進めていきたいと思います。

データ準備

まずは、データベースを作成します。
//> create database titanic;
作業用にCURRENT_DBを切り替えます。
//> use titanic;
訓練データ用のTABLEを作成します。
//>
  create external table train (
    passengerid int, -- unique id
    survived int, -- target label
    pclass int,
    name string,
    sex string,
    age int,
    sibsp int, -- Number of Siblings/Spouses Aboard
    parch int, -- Number of Parents/Children Aboard
    ticket string,
    fare double,
    cabin string,
    embarked string
  )
  ROW FORMAT DELIMITED
     FIELDS TERMINATED BY '|'
     LINES TERMINATED BY '\n'
  STORED AS TEXTFILE LOCATION '/dataset/titanic/train';
データは次の工程で追加します。そのまま先に進んでください。 テストデータ用のTABLEを作成します。
//>
  create external table test_raw (
    passengerid int,
    pclass int,
    name string,
    sex string,
    age int,
    sibsp int, -- Number of Siblings/Spouses Aboard
    parch int, -- Number of Parents/Children Aboard
    ticket string,
    fare double,
    cabin string,
    embarked string
  )
  ROW FORMAT DELIMITED
     FIELDS TERMINATED BY '|'
     LINES TERMINATED BY '\n'
  STORED AS TEXTFILE LOCATION '/dataset/titanic/test_raw';

データロード

先ほど作成したテーブルにデータをロードします。
$ sed -e "1d" -e "s/, /. /g" -e"s/\"//g" -e "s/,/|/g" train.csv \
  | hdfs dfs -put - /dataset/titanic/train/train.csv
FPATを利用したawkは、意図した動作をしない場合があるので、sedで代替しています。 同様にテストデータもLOCATIONにputします。 これで、Hiveからデータが確認できるようになっているはずです。確認してみます。
//>
select passengerid,pclass,name,sex,age,sibsp,parch,ticket,fare,cabin,embarked from train limit 1;
  +--------------+---------+--------------------------+-------+------+--------+--------+------------+-------+--------+-----------+--+
  | passengerid  | pclass  |           name           |  sex  | age  | sibsp  | parch  |   ticket   | fare  | cabin  | embarked  |
  +--------------+---------+--------------------------+-------+------+--------+--------+------------+-------+--------+-----------+--+
  | 1            | 3       | Braund. Mr. Owen Harris  | male  | 22   | 1      | 0      | A/5 21171  | 7.25  |        | S         |
  +--------------+---------+--------------------------+-------+------+--------+--------+------------+-------+--------+-----------+--+

前処理

データ前処理の前にhiveの設定を一時的に変更します。
//> set hive.cbo.enable=false
次に使うUDTF:quantifyは現在、CBO管理外となっているため、一時的に使用不可とします。trueの場合、ERRORログが出ます。ログ出力されても、動作上は問題ないですが気持ちが悪いので停止します。 前処理UDTF用の変数を設定します。
//> set hivevar:output_row=true;
設定ができたので、データの前処理を行なっていきます。
//>
  create table train_rf
  as
  WITH train_quantified as (
    select
      quantify(
        ${output_row}, passengerid, survived, pclass, name, sex, age, sibsp, parch, ticket, fare, cabin, embarked
      ) as (passengerid, survived, pclass, name, sex, age, sibsp, parch, ticket, fare, cabin, embarked)
    from (
      select * from train
      order by passengerid asc
    ) t
  )
  select
    rand(31) as rnd,
    passengerid,
    array(pclass, name, sex, age, sibsp, parch, ticket, fare, cabin, embarked) as features,
    survived
  from
    train_quantified
  ;
train_rfの内容はこのようになっています。
//> select rnd,passengerid,features,survived from train_rf limit 1;
+---------------------+--------------+----------------------------------------------+-----------+--+
  |         rnd         | passengerid  |                   features                   | survived  |
  +---------------------+--------------+----------------------------------------------+-----------+--+
  | 0.7314156962376819  | 1            | [3.0,0.0,0.0,22.0,1.0,0.0,0.0,7.25,0.0,0.0]  | 0         |
  +---------------------+--------------+----------------------------------------------+-----------+--+
quantifyによりデータを定量化します。文字列の入力についても変換可能なものは数値に変換されます(例えば、sexなど)。randは乱数を出力します。クロスバリデーション時など、サンプリングする際に利用します。arrayで指定した説明変数を一つの配列とします。これは、ランダムフォレストの学習用関数(train_randomforest_classifier)が、説明変数をdoubleの配列として受け取ることを前提としているからです。 同様にテストデータも処理していきます。
//>
  create table test_rf
  as
  WITH test_quantified as (
    select
      quantify(
        output_row, passengerid, pclass, name, sex, age, sibsp, parch, ticket, fare, cabin, embarked
      ) as (passengerid, pclass, name, sex, age, sibsp, parch, ticket, fare, cabin, embarked)
    from (
      -- need training data to assign consistent ids to categorical variables
      select * from (
        select
          1 as train_first, false as output_row, passengerid, pclass, name, sex, age, sibsp, parch, ticket, fare, cabin, embarked
        from
          train
        union all
        select
          2 as train_first, true as output_row, passengerid, pclass, name, sex, age, sibsp, parch, ticket, fare, cabin, embarked
        from
          test_raw
      ) t0
      order by train_first asc, passengerid asc
    ) t1
  )
  select
    passengerid,
    array(pclass, name, sex, age, sibsp, parch, ticket, fare, cabin, embarked) as features
  from
    test_quantified  ;
次に、説明変数のメタ情報を設定します。
//>
select guess_attribute_types(pclass, name, sex, age, sibsp, parch, ticket, fare, cabin, embarked) from train limit 1;
  +----------------------+--+
  |          c0          |
  +----------------------+--+
  | Q,C,C,Q,Q,Q,C,Q,C,C  |
  +----------------------+--+
guess_attribute_typesにより、Hivemallが与えられた変数値を「Q:量的」であるか「C:質的」のどちらと認識するかを確認できます。 これを確認すると、「pclass:乗客のグレード」が量的と判断されており、明示する必要があることがわかります。そこで、説明変数の属性を明示しておきたいと思います。
//> set hivevar:attrs=C,C,C,Q,Q,Q,C,Q,C,C;
これは、次のクエリで使用する変数の設定です。

学習

では、モデルを構築していきたいと思います。
//>
  create table model_rf
  AS
  select
    train_randomforest_classifier(features, survived, "-trees 500 -attrs ${attrs}")
      -- as (model_id, model_type, pred_model, var_importance, oob_errors, oob_tests)
  from
    train_rf;
train_randomforest_classifierの詳しいオプションは、以下のクエリで確認してください。
//> select train_randomforest_classifier(features, survived,'-help') from train_rf;
私が確認した限りこのクエリでは例外ログが多量に出力されますが。何が違うのかよくわかってないです。スミマセンm(_ _)m。hivemallのWEBページかソースなどから確認してください。 気をとりなおして、モデルのimportanceは下記のクエリで確認してみます。
//>
  select
    array_sum(var_importance) as var_importance,
    sum(oob_errors) / sum(oob_tests) as oob_err_rate
  from
    model_rf;
+-----------------------------------------------------------------------------------+----------------------+--+
  |                                        var_importance                             |     oob_err_rate     |
  +-----------------------------------------------------------------------------------+----------------------+--+
  | [117.50, 707.23, 164.99, 729.44, 123.28, 152.52, 745.85, 759.21, 224.85, 137.27]  | 0.18518518518518517  |
  +-----------------------------------------------------------------------------------+----------------------+--+
予測 ここまでで、データの準備ができたので、予測を行ってみましょう。
//> set hivevar:classification=true;
//>
  create table predicted_rf
  as
  SELECT
    passengerid,
    predicted.label,
    predicted.probability,
    predicted.probabilities
  FROM (
    SELECT
      passengerid,
      rf_ensemble(predicted) as predicted
    FROM (
        SELECT
          t.passengerid,
          -- hivemall v0.4.1-alpha.2 or before
          -- tree_predict(p.model, t.features, ${classification}) as predicted
          -- hivemall v0.4.1-alpha.3 or later
          tree_predict(p.model_id, p.model_type, p.pred_model, t.features, ${classification}) as predicted
        FROM (
          SELECT model_id, model_type, pred_model FROM model_rf
          DISTRIBUTE BY rand(1)
        ) p
        LEFT OUTER JOIN test_rf t
      ) t1
      group by
        passengerid
  ) t2 
  ;
このクエリでは先に指定したtree=500から、予測結果とそのもっともらしさを集計しています。最終的には得られた結果も確認しておきましょう。
//> select passengerid,label,probability,probabilities from predicted_rf limit 5;
  +--------------+--------+--------------+----------------+--+
  | passengerid  | label  | probability  | probabilities  |
  +--------------+--------+--------------+----------------+--+
  | 892          | 0      | 0.994        | [0.994,0.006]  |
  | 893          | 0      | 0.674        | [0.674,0.326]  |
  | 894          | 0      | 0.94         | [0.94,0.06]    |
  | 895          | 0      | 0.926        | [0.926,0.074]  |
  | 896          | 1      | 0.506        | [0.494,0.506]  |
  +--------------+--------+--------------+----------------+--+
最後にKaggleに結果を提出するために、データをファイル出力します。 まず次のクエリで提出用の形式に変換します。
//>
  create table predicted_rf_submit
    ROW FORMAT DELIMITED
      FIELDS TERMINATED BY ","
      LINES TERMINATED BY "\n"
    STORED AS TEXTFILE
  as
  SELECT passengerid, label as survived
  FROM predicted_rf
  ORDER BY passengerid ASC;
次にターミナルからHive上のデータを通常ファイルに変換します。
$ hdfs dfs -getmerge /user/hive/warehouse/titanic.db/predicted_rf_submit predicted_rf_submit_raw.csv
predicted_rf_submitディレクトリ以下のファイルを結合して、predicted_rf_submit_raw.csvとして取得します。 また、結果にはヘッダーが必要なので、追加します。
$ awk 'NR==1{print "PassengerId,Survived"}{print $0}' predicted_rf_submit_raw.csv > predicted_rf_submit.csv
あとは、KaggleのTitanicページ上の「Submit Predictions」から、結果をアップロードしてみてください。 以上で、終了になります。おつかれさまでした。

APPENDIX

HadoopをHomebrewでインストールした場合、hdfsが実行されるたびにWARNログが出力されると思います。これは、native-hadoop libraryがインストールされていないことが原因です。ここでは、そのHadoopをソースからコンパイルし、ライブラリをインストールしていきたいと思います。 コンパイルには、以下が必要になります。
  • JDK 1.7+
  • Maven 3.0 or later
  • ProtocolBuffer 2.5.0
  • CMake 2.6 or newer (if compiling native code), must be 3.0 or newer on Mac
  • Zlib devel (if compiling native code)
JDKは、すでにインストール済みですね。maven、cmake、zlibはインストールされていなければ、Homebrewでインストールしてしまいましょう。 環境によっては、ProtocolBufferのインストールは多少の手間が必要かもしれません。私は、以降の手順でインストールしました。 まずは、Hadoopのバージョンを確認します。
$ hadoop version
  Hadoop 2.8.0
  Subversion https://git-wip-us.apache.org/repos/asf/hadoop.git -r 91f2b7a13d1e97be65db92ddabc627cc29ac0009
  Compiled by jdu on 2017-03-17T04:12Z
  Compiled with protoc 2.5.0
  From source with checksum 60125541c2b3e266cbf3becc5bda666
  This command was run using /usr/local/Cellar/hadoop/2.8.0/libexec/share/hadoop/common/hadoop-common-2.8.0.jar
ここから、HadoopのコンパイルにつかったシリアライザProtocol Buffersのバージョンを確認してください。おそらくprotoc 2.5.0となっているかと思います。 HomebrewからProtocolBufferについて検索し、2.5系をインストールします。
$ brew search /proto/
$ brew install protobuf@2.5

$ protoc --version
libprotoc 3.3.0
このままでは、インストール済みのProtocolBufferが優先され、2.5系を使用できません。そこで一度リンクを削除し、2.5系からリンクを再作成します。
$ brew unlink protobuf
$ brew link --force protobuf@2.5

$ protoc --version
libprotoc 2.5.0
Hadoopのソースをダウンロード、コンパイルし、ライブラリをインストールします。
$ wget http://ftp.jaist.ac.jp/pub/apache/hadoop/common/hadoop-2.8.0/hadoop-2.8.0-src.tar.gz
$ tar xvzf hadoop-2.8.0-src.tar.gz
$ pwd
/usr/local/src/hadoop-2.8.0-src/hadoop-common-project/hadoop-common
$ mvn -P native compile
...
$ sudo mv target/native/target/usr/local/lib/* /Library/Java/Extensions/
これで、作業は完了です。最後にWARNログが出なくなっていることを確認します。
$ hdfs dfs -ls /


DATUM STUDIOは、クライアントの事業成長と経営課題解決を最適な形でサポートする、データ・ビジネスパートナーです。
データ分析の分野でお客様に最適なソリューションをご提供します。まずはご相談ください。

このページをシェアする: