2016年4月10日

データ分析用ライブラリ MADlib を使って PostgreSQL で機械学習する

MADlibは、現代的なデータ分析には欠かせない回帰分析やデータマイニングのアルゴリズムが実装されているオープンソースのライブラリです。

MADlibを導入することによって、これらのアルゴリズムをPostgreSQLのユーザ定義関数の形で使うことのでき、データベースサーバの内部でデータ分析の処理できるようになります。

今回は、このMADlibの導入方法から動作確認、ロジスティック回帰分析における簡単な使い方までをご紹介します。

■MADlibとは何か


MADlibは、もともとはGreenplumというPostgreSQLをベースにしたMPP製品(DWH用RDBMS)を開発していた企業が開発していたライブラリで、Greenplumで利用できるように開発されていたものでした。

2015年9月に、Greenplum(を買収したEMC)がMADlib(や他のソフトウェア類)をApache Foundationに寄贈し、MADlib は Apache Incubator のプロジェクトとなりました。

そして、4月6日に Apache Incubator のプロジェクトになって最初の GA(Generally Available) リリースである1.9がリリースされました。
冒頭にも述べたように、MADlibはデータ分析用の統計解析や機械学習のアルゴリズムを集めたライブラリで、以下のようなアルゴリズムが実装されています(抜粋)。
  • Linear Regression
  • Logistic Regression
  • Multinomial Logistic Regression
  • Cox Proportional Hazards Regression
  • Principal Component Analysis (PCA)
  • Association Rules (Apriori)
  • Decision Trees
  • Random Forest
  • Clustering (K-means)
  • Naïve Bayes
  • Support Vector Machines (SVM)
これらのアルゴリズムをPostgreSQL(やGreenplum、HAWQ)のSQLから扱えるようにするライブラリで、以下のようなアーキテクチャとなっています。


このようなアーキテクチャを取ることによって、分析するデータをデータベースから取り出すことなくデータベース内で処理できるようになります。

なお、1月に開催された FOSDEM'16 でも MADlib が紹介されていたようですので、そちらのプレゼン資料も参考にしてください。
今回は、この MADlib を PostgreSQL 9.5 と連携させて動作確認、簡単な使い方の確認までを行ってみようと思います。

■動作確認環境


今回の動作確認環境は以下の通りです。
  • CentOS 6.6 (x86_64)
  • PostgreSQL 9.5.2 (コミュニティ版RPM)
  • cmake 2.8.12
cmakeはyumコマンドでインストールできます。

# yum install -y cmake

MADlibは1.9ブランチの最新のコードを使っていますが、オリジナルのコードには
  • ビルドスクリプト内で呼び出しているリダイレクト用URLが利用不可能でインストール失敗。
  • PostgreSQL 9.5で動作させる場合に一部関数定義に不具合でインストール後のテストに失敗。
という問題があっため、修正して使っています。

ソースコードは以下から確認・取得可能です。

■ソースコードからのビルド


それではソースコードからビルドします。PostgreSQL 9.5が既にインストールされていることを前提としています。

手順をスクリプトにしたものは以下にありますので、詳細はそちらを参照してください。
基本的には Installation Guide に沿っていますので、そちらも参考にしてください。
まず、git cloneしてソースコードを取得し、バージョン1.9系のブランチに切り替えます。

[root@localhost madlib]# git clone https://github.com/snaga/incubator-madlib.git
Initialized empty Git repository in /tmp/madlib/incubator-madlib/.git/
remote: Counting objects: 23090, done.
remote: Compressing objects: 100% (55/55), done.
remote: Total 23090 (delta 21), reused 0 (delta 0), pack-reused 23032
Receiving objects: 100% (23090/23090), 15.32 MiB | 1.23 MiB/s, done.
Resolving deltas: 100% (15912/15912), done.
[root@localhost madlib]# cd incubator-madlib
[root@localhost incubator-madlib]# git checkout v1.9
Branch v1.9 set up to track remote branch v1.9 from origin.
Switched to a new branch 'v1.9'
[root@localhost incubator-madlib]# git log -1
commit a3c1eab2a92dd19c8a3f098e59cf56b8b974a3bb
Merge: a152c25 054136b
Author: Satoshi Nagayasu 
Date:   Sun Apr 10 12:26:28 2016 +0900

    Merge pull request #3 from snaga/fix/v1.9-pg9.5

    Fix to add support for PostgreSQL 9.5.
[root@localhost incubator-madlib]# ls
CMakeLists.txt    LICENSE    RELEASE_NOTES     cmake      doc       methods
DISCLAIMER        NOTICE     ReadMe.txt        configure  examples  pom.xml
HAWQ_Install.txt  README.md  ReadMe_Build.txt  deploy     licenses  src
[root@localhost incubator-madlib]#

次にconfigureスクリプトを実行します。この時、OSバンドル版のPythonとPostgreSQL 9.5を見つけられるように明示的にPATHを通して実行します。


[root@localhost incubator-madlib]# env PATH=/usr/bin:/usr/pgsql-9.5/bin:$PATH ./configure
-- The C compiler identification is GNU 4.4.7
-- The CXX compiler identification is GNU 4.4.7
(...)
-- Could NOT find Boost
-- No sufficiently recent version (>= 1.47) of Boost was found. Will download.
-- Found PythonInterp: /usr/bin/python (found version "2.6.6")
-- Found PostgreSQL: /usr/pgsql-9.5/bin/postgres
-- Found PostgreSQL_9_5: /usr/pgsql-9.5/bin/postgres
>> Adding PostgreSQL 9.5 (x86_64) to target list...
-- Could NOT find Greenplum (missing:  GREENPLUM_EXECUTABLE)
-- Could NOT find HAWQ (missing:  HAWQ_EXECUTABLE)
(...)
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/madlib/incubator-madlib/build
[root@localhost incubator-madlib]#

configureが終わったら、buildディレクトリに移動してmakeを実行します。すると、依存するライブラリ類をダウンロードしてビルドが始まります。

[root@localhost incubator-madlib]# cd build/
[root@localhost build]# make
[  0%] Performing download step (download, verify and extract) for 'EP_boost'
-- downloading...
     src='http://sourceforge.net/projects/boost/files/boost_1_47_0.tar.gz'
     dst='/disk/disk1/snaga/madlib/incubator-madlib/build/third_party/downloads/boost_1_47_0.tar.gz'
     timeout='none'
-- [download 0% complete]
-- [download 1% complete]
(...)
[ 28%] Validating and copying svec_util/src/pg_gp/sql/svec_test.sql_in
[ 28%] Validating and copying svec_util/src/pg_gp/svec_util.sql_in
[ 29%] Validating and copying stemmer/src/pg_gp/porter_stemmer.sql_in
[ 29%] Built target sqlFiles_postgresql
Scanning dependencies of target madlib_postgresql_9_5
[ 29%] Building CXX object src/ports/postgres/9.5/CMakeFiles/madlib_postgresql_9_5.dir/__/__/__/modules/linear_systems/sparse_linear_systems.cpp.o
[ 29%] Building CXX object src/ports/postgres/9.5/CMakeFiles/madlib_postgresql_9_5.dir/__/__/__/modules/linear_systems/dense_linear_systems.cpp.o
[ 29%] Building CXX object src/ports/postgres/9.5/CMakeFiles/madlib_postgresql_9_5.dir/__/__/__/modules/assoc_rules/assoc_rules.cpp.o
(...)
[ 98%] Validating and copying svec_util/src/pg_gp/sql/svec_test.sql_in
[100%] Validating and copying svec_util/src/pg_gp/svec_util.sql_in
[100%] Validating and copying stemmer/src/pg_gp/porter_stemmer.sql_in
[100%] Built target sqlFiles_hawq
[root@localhost build]#

makeが終わったら、make packageコマンドでRPMファイルを作成します。

[root@localhost build]# make package
[  1%] Built target EP_boost
[  2%] Built target EP_eigen
(...)
CPack: Create package
CPackRPM: Will use USER specified spec file: /tmp/madlib/incubator-madlib/deploy/madlib.spec.in
CPack: - package: /tmp/madlib/incubator-madlib/build/madlib-1.9-Linux.rpm generated.
[root@localhost build]# ls
CMakeCache.txt           Makefile             doc                   third_party
CMakeFiles               _CPack_Packages      install_manifest.txt
CPackConfig.cmake        cmake_install.cmake  madlib-1.9-Linux.rpm
CPackSourceConfig.cmake  deploy               src
[root@localhost build]#

これでRPMパッケージの完成です。

■インストール


作成したRPMパッケージをrpmコマンドでインストールします。

[root@localhost build]# rpm -ivh madlib-1.9-Linux.rpm
Preparing...                ########################################### [100%]
   1:madlib                 ########################################### [100%]
[root@localhost build]#

■データベースへのデプロイと回帰テスト


それでは、MADlibをPostgreSQLのデータベースで使えるようにセットアップします。

まずデータベースを作成します。ここではtestdbとしています。
[snaga@localhost ~]$ createdb -U postgres -h 127.0.0.1 testdb

データベースを作成したら、MADlibをデータベースにデプロイします。デプロイするにはmadpackコマンドにinstallオプションを渡して実行します。

以下はtestdbで使えるようにMADlibをインストールしているところです。接続するデータベースにはホスト127.0.0.1、ポート5432、データベース名testdb、ユーザ名postgresを指定しています。

[snaga@localhost ~]$ /usr/local/madlib/bin/madpack -s madlib -p postgres -c postgres@127.0.0.1:5432/testdb install
madpack.py : INFO : Detected PostgreSQL version 9.5.
madpack.py : INFO : *** Installing MADlib ***
madpack.py : INFO : MADlib tools version    = 1.9 (/usr/local/madlib/Versions/1.9/bin/../madpack/madpack.py)
madpack.py : INFO : MADlib database version = None (host=127.0.0.1:5432, db=testdb, schema=madlib)
madpack.py : INFO : Testing PL/Python environment...
madpack.py : INFO : > Creating language PL/Python...
madpack.py : INFO : > PL/Python environment OK (version: 2.6.6)
madpack.py : INFO : Installing MADlib into MADLIB schema...
madpack.py : INFO : > Creating MADLIB schema
madpack.py : INFO : > Creating MADLIB.MigrationHistory table
madpack.py : INFO : > Writing version info in MigrationHistory table
madpack.py : INFO : > Creating objects for modules:
madpack.py : INFO : > - array_ops
madpack.py : INFO : > - bayes
madpack.py : INFO : > - crf
(...)
madpack.py : INFO : > - sample
madpack.py : INFO : > - summary
madpack.py : INFO : > - kmeans
madpack.py : INFO : > - pca
madpack.py : INFO : > - validation
madpack.py : INFO : MADlib 1.9 installed successfully in MADLIB schema.
[snaga@localhost ~]$

「MADlib 1.9 installed successfully in MADLIB schema」と表示されたらインストール成功ですが、インストールされたものが正しく動作するかどうか、madpackコマンドで回帰テストを実行します(install-checkオプション)。

[snaga@localhost ~]$ /usr/local/madlib/bin/madpack -s madlib -p postgres -c postgres@127.0.0.1:5432/testdb install-check
madpack.py : INFO : Detected PostgreSQL version 9.5.
TEST CASE RESULT|Module: array_ops|array_ops.sql_in|PASS|Time: 98 milliseconds
TEST CASE RESULT|Module: bayes|gaussian_naive_bayes.sql_in|PASS|Time: 137 milliseconds
TEST CASE RESULT|Module: bayes|bayes.sql_in|PASS|Time: 299 milliseconds
(...)
TEST CASE RESULT|Module: pca|pca_project.sql_in|PASS|Time: 533 milliseconds
TEST CASE RESULT|Module: pca|pca.sql_in|PASS|Time: 2098 milliseconds
TEST CASE RESULT|Module: validation|cross_validation.sql_in|PASS|Time: 1672 milliseconds
[snaga@localhost ~]$

すべてのテストに「PASS」と出れば問題ありません。

なお、PostgreSQL 9.5でinstall-checkを実行している際、以下のようなエラーが出る可能性がありますが、ここでは特に気にしなくて構いません(手元の9.5の環境ではこのエラーが出て、9.4では再現しませんでした)。

TEST CASE RESULT|Module: validation|cross_validation.sql_in|PASS|Time: 1741 milliseconds
madpack.py : ERROR : SQL command failed:
SQL: DROP OWNED BY madlib_19_installcheck CASCADE;
ERROR:  could not open relation with OID 551341

最後に管理者でGRANTを発行して、他のユーザもmadlibスキーマのオブジェクトを使えるように設定します。(MADlib関連のオブジェクトはすべてmadlibスキーマにインストールされます)

testdb=# GRANT ALL ON SCHEMA madlib TO public;
GRANT
testdb=#

■ロジスティック回帰のサンプルによる動作確認


それでは、MADlibの Quick Start Guide for Users からロジスティック回帰のサンプルを動かしてみます。
まず、学習用データのテーブルを作成します。

testdb=> CREATE TABLE patients( id INTEGER NOT NULL,
testdb(>                         second_attack INTEGER,
testdb(>                         treatment INTEGER,
testdb(>                         trait_anxiety INTEGER);
CREATE TABLE
testdb=> INSERT INTO patients VALUES
testdb-> (1,     1,      1,      70),
testdb-> (3,     1,      1,      50),
testdb-> (5,     1,      0,      40),
testdb-> (7,     1,      0,      75),
testdb-> (9,     1,      0,      70),
testdb-> (11,    0,      1,      65),
testdb-> (13,    0,      1,      45),
testdb-> (15,    0,      1,      40),
testdb-> (17,    0,      0,      55),
testdb-> (19,    0,      0,      50),
testdb-> (2,     1,      1,      80),
testdb-> (4,     1,      0,      60),
testdb-> (6,     1,      0,      65),
testdb-> (8,     1,      0,      80),
testdb-> (10,    1,      0,      60),
testdb-> (12,    0,      1,      50),
testdb-> (14,    0,      1,      35),
testdb-> (16,    0,      1,      50),
testdb-> (18,    0,      0,      45),
testdb-> (20,    0,      0,      60);
INSERT 0 20
testdb=> \d patients
       Table "public.patients"
    Column     |  Type   | Modifiers
---------------+---------+-----------
 id            | integer | not null
 second_attack | integer |
 treatment     | integer |
 trait_anxiety | integer |

testdb=> select * from patients limit 5;
 id | second_attack | treatment | trait_anxiety
----+---------------+-----------+---------------
  1 |             1 |         1 |            70
  3 |             1 |         1 |            50
  5 |             1 |         0 |            40
  7 |             1 |         0 |            75
  9 |             1 |         0 |            70
(5 rows)

testdb=>

この例は患者の例で、treatmentとtrait_anxietyが説明変数で、treatmentは anger control の治療を受けたどうか(カテゴリカル変数)、trait_anxietyは不安症の度合い(数値が高いほど度合が高い連続型変数)です。second_attackは被説明変数で「1年以内に二度目の発作が起こったかどうか」です。

詳細は以下を参照してください。
それでは、このデータを使って学習させてみます。

testdb=> SELECT madlib.logregr_train(
testdb(>     'patients',                                 -- source table
testdb(>     'patients_logregr',                         -- output table
testdb(>     'second_attack',                            -- labels
testdb(>     'ARRAY[1, treatment, trait_anxiety]',       -- features
testdb(>     NULL,                                       -- grouping columns
testdb(>     20,                                         -- max number of iteration
testdb(>     'irls'                                      -- optimizer
testdb(>     );
 logregr_train
---------------

(1 row)

testdb=>

上記の例では、学習用データのテーブルを patients 、学習した結果の出力を patients_logregr テーブル、被説明変数を second_attack 、説明変数を切片(1)と treatment, trait_anxiety に指定して学習させています。

その他の引数の詳細は以下を参照してください。
学習させた結果の各係数などは以下のように出力用に指定したテーブルから取得することができます。

testdb=> \x on
Expanded display is on.
testdb=> SELECT * from patients_logregr;
-[ RECORD 1 ]------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
coef                     | {-6.36346994178187,-1.02410605239327,0.119044916668606}
log_likelihood           | -9.41018298388876
std_err                  | {3.21389766375091,1.17107844860318,0.0549790458269303}
z_stats                  | {-1.9799852414576,-0.874498248699553,2.1652779686892}
p_values                 | {0.0477051870698109,0.381846973530448,0.0303664045046153}
odds_ratios              | {0.00172337630923231,0.359117354054955,1.12642051220895}
condition_no             | 326.081922791559
num_rows_processed       | 20
num_missing_rows_skipped | 0
num_iterations           | 5
variance_covariance      | {{10.3291381930635,-0.474304665195729,-0.171995901260048},{-0.474304665195729,1.37142473278283,-0.00119520703381591},{-0.171995901260048,-0.00119520703381591,0.00302269548003971}}

testdb=>

また、以下のように各説明変数の係数やp値だけを表形式で表示することもできます。

testdb=> \x off
Expanded display is off.
testdb=>  SELECT unnest(array['intercept', 'treatment', 'trait_anxiety']) as attribute,
testdb->         unnest(coef) as coefficient,
testdb->         unnest(std_err) as standard_error,
testdb->         unnest(z_stats) as z_stat,
testdb->         unnest(p_values) as pvalue,
testdb->         unnest(odds_ratios) as odds_ratio
testdb->  FROM patients_logregr;
   attribute   |    coefficient    |   standard_error   |       z_stat       |       pvalue       |     odds_ratio
---------------+-------------------+--------------------+--------------------+--------------------+---------------------
 intercept     | -6.36346994178187 |   3.21389766375091 |   -1.9799852414576 | 0.0477051870698109 | 0.00172337630923231
 treatment     | -1.02410605239327 |   1.17107844860318 | -0.874498248699553 |  0.381846973530448 |   0.359117354054955
 trait_anxiety | 0.119044916668606 | 0.0549790458269303 |    2.1652779686892 | 0.0303664045046153 |    1.12642051220895
(3 rows)

testdb=>

それではこの学習したモデルを使って予測をしてみましょう。

ここでは再度学習用データを使ってテストすることでトレーニングエラーを確認してみます。

実行するクエリは以下です。second_attack_predictは二回目の心臓発作が起こるかどうかの予測、second_attack_boolは実際に起こったかどうかのフラグ、correctは予測が当たったかどうかのフラグです。

WITH TEMP AS (
SELECT p.id,
       madlib.logregr_predict(coef, ARRAY[1, treatment, trait_anxiety]) as second_attack_predict,
       CASE p.second_attack
         WHEN 1 THEN true
         ELSE false
       END AS second_attack_bool
FROM patients p, patients_logregr f
)
SELECT id,
       second_attack_predict,
       second_attack_bool,
       second_attack_predict = second_attack_bool AS correct
FROM temp
ORDER BY id;

実行すると以下のような結果が得られ、20件中15件は予測が当たっていることが分かります。

testdb=> WITH TEMP AS (
testdb(> SELECT p.id,
testdb(>        madlib.logregr_predict(coef, ARRAY[1, treatment, trait_anxiety]) as second_attack_predict,
testdb(>        CASE p.second_attack
testdb(>          WHEN 1 THEN true
testdb(>          ELSE false
testdb(>        END AS second_attack_bool
testdb(> FROM patients p, patients_logregr f
testdb(> )
testdb-> SELECT id,
testdb->        second_attack_predict,
testdb->        second_attack_bool,
testdb->        second_attack_predict = second_attack_bool AS correct
testdb-> FROM temp
testdb-> ORDER BY id;
 id | second_attack_predict | second_attack_bool | correct
----+-----------------------+--------------------+---------
  1 | t                     | t                  | t
  2 | t                     | t                  | t
  3 | f                     | t                  | f
  4 | t                     | t                  | t
  5 | f                     | t                  | f
  6 | t                     | t                  | t
  7 | t                     | t                  | t
  8 | t                     | t                  | t
  9 | t                     | t                  | t
 10 | t                     | t                  | t
 11 | t                     | f                  | f
 12 | f                     | f                  | t
 13 | f                     | f                  | t
 14 | f                     | f                  | t
 15 | f                     | f                  | t
 16 | f                     | f                  | t
 17 | t                     | f                  | f
 18 | f                     | f                  | t
 19 | f                     | f                  | t
 20 | t                     | f                  | f
(20 rows)

testdb=>

■まとめ


以上、駆け足ではありましたが、MADlibの紹介から導入方法、簡単な使い方までを紹介してきました。

大量データを処理する場合、データベースからデータを取り出さずに(=クライアントに転送せずに)処理できる「In-Database処理」には非常に大きなアドバンテージがあります。今後はこのようなテクノロジーがさらに普及するのではないかと考えています。

この領域に興味がある方は、ぜひ一度試してみてください。PostgreSQLの可能性がより広がると思います。

では。

0 件のコメント:

コメントを投稿