CentOS 7.2(1511) + PostgreSQL 9.5(SCL) + MADlib 1.9.1(GA) で機械学習してみよう

# はじめに
本日時点(2016/09/26)での最新の環境で、MADlibの環境を構築してみたいと思います。
MADlibは「Big Data Machine Learning in SQL」との事で、PostgreSQL( or Greenplum or HAWQなどのPostgreSQLの仲間達)上で、統計や機械学習などのアルゴリズムをユーザ定義関数として実装したもので、SQLを使って機械学習なんかができる優れものです。

ちなみに、自分は普段PostgreSQLを普段使わない、機械学習とかほぼ知らない、程度の知識なので、間違った情報が含まれているかもしれませんので、ご注意ください。

今回は、基本的に下記の公式ドキュメントをやってみた形です。詳細はこちらをご参照ください。

* [Installation Guide](https://cwiki.apache.org/confluence/display/MADLIB/Installation+Guide)
* [Quick Start Guide for Users](https://cwiki.apache.org/confluence/display/MADLIB/Quick+Start+Guide+for+Users)

# 今回の環境
* [IDCF Cloud](https://www.idcf.jp/cloud/)
* [CentOS 7.2](https://www.idcf.jp/cloud/spec/template.html) + `yum update`
* [SCL版 PostgreSQL 9.5](https://access.redhat.com/documentation/en-US/Red_Hat_Software_Collections/2/html/Packaging_Guide/index.html)
* [MADlib Binaries v1.9.1](http://madlib.incubator.apache.org/download.html)

基本的にはすべてrpmでインストール可能なので、全然難しくありませんが、SCLは馴染みが薄いかもしれません。
ちょっと古い(基本的には変わっていません)ですが、下記の[IDCF Tech-Blog](http://blog.idcf.jp/)あたりを参照頂ければと思います。

* [Software Collections for CentOS 6を使おう！](http://blog.idcf.jp/entry/cloud/software-collections-for-centos-6/)

# CentOS + PostgreSQL(SCL)のインストール

以降は概ね、

* [Installation Guide](https://cwiki.apache.org/confluence/display/MADLIB/Installation+Guide)

の内容になります。CentOS 7.2 + SCLなので、少し違いますがMADlibの部分は殆ど同じです。

## 仮想マシンの起動
IDCF Cloudで、標準テンプレートのCentOS7.2から仮想マシンを起動します。下記参照していただければ、問題なくできるかと思います。

* [ご利用ガイド](https://www.idcf.jp/help/cloud/guide/)

インストール後に、`yum -y update`して再起動しておいてください。

## SCL版 PostgreSQLのインストール
まずは、SCLを有効にします。(sclのレポジトリのrpmを入れるだけ)

```
# yum -y install centos-release-scl centos-release-scl-rh
```

SCL版のPostgreSQL 9.5をインストールします。(これもrpm入れるだけ。)
MADLibでは、PL/Pythonも必要になるので、こちらも一緒にインストールします。

```
# yum -y install rh-postgresql95-postgresql-server rh-postgresql95-postgresql-plpython
```

インストールできたら、DBの初期化をして起動します。`scl enable rh-postgresql95 bash`を忘れないようにお願いします。

```
# scl enable rh-postgresql95 bash

# postgresql-setup --initdb
 * Initializing database in '/var/opt/rh/rh-postgresql95/lib/pgsql/data'
 * Initialized, logs are in /var/lib/pgsql/initdb_rh-postgresql95-postgresql.log

# systemctl start rh-postgresql95-postgresql
```

PostgreSQLが起動したら、DB上のpostgresユーザのパスワードを設定します。

```
# su - postgres

$ scl enable rh-postgresql95 bash

$ psql
psql (9.5.2)
"help" でヘルプを表示します.

postgres=# ALTER USER postgres with password '6zCYTSn4';
ALTER ROLE
postgres=# 

$ exit
$ exit
# 
```

パスワードを設定したら、localhostからの認証をmd5へ変更します。(MADlibをセットアップするときにパスワード認証しか受け付けてくれなかった。)

```
# vi /var/opt/rh/rh-postgresql95/lib/pgsql/data/pg_hba.conf 

# IPv4 local connections:
host    all             all             127.0.0.1/32            md5
```

127.0.0.1/32からの認証はmd5と設定しています。

PostgreSQLでは、[DateStyle](https://www.postgresql.jp/document/9.5/html/runtime-config-client.html#guc-datestyle)が`iso, ymd`がデフォルトとなっていますが、MADLibのinstall-checkでは`iso, myd`じゃないと失敗してしまうので`iso, myd`へ変更しておきます。

```
# vi /var/opt/rh/rh-postgresql95/lib/pgsql/data/postgresql.conf 

#datestyle = 'iso, ymd'
datestyle = 'iso, mdy'

# systemctl restart rh-postgresql95-postgresql
```

# MADLibのインストール

MADlibも公式から[rpmでbinary](http://madlib.incubator.apache.org/download.html)が提供されているので、こちらを利用してインストールします。

```
# yum -y install https://dist.apache.org/repos/dist/release/incubator/madlib/1.9.1-incubating/apache-madlib-1.9.1-incubating-bin-Linux.rpm
```

それでは、MADLibをPostgreSQLへセットアップしていきます。
(以降、ログを誤って消してしまったので、記憶で書いてます。間違ってるかも。。。)

```
# su - postgres

$ scl enable rh-postgresql95 bash

$ /usr/local/madlib/bin/madpack -p postgres -c postgres@127.0.0.1 install
Password for user postgres:
```

インストールしたPostgreSQLへセットアップしたMADLibが正しくインストールできているかチェックします。

```
$ /usr/local/madlib/bin/madpack -p postgres -c postgres@127.0.0.1 install-check
Password for user postgres:
```

全ての項目でOKであれば、問題ないと思います。

# Quick Startしてみる

以降は概ね、

* [Quick Start Guide for Users](https://cwiki.apache.org/confluence/display/MADLIB/Quick+Start+Guide+for+Users)

の内容になります。ロジスティック回帰を使って、学習と予測をしていきます。
このテスト問題では、心不全の患者のデータで、以下のようなデータを持っています。

* second_attack
    * 1年以内に、心臓発作を再発したかどうか。
* treatment
    * 患者がanger control(怒りのコントロール)の治療を受けたかどうか。
* trait_anxiety
    * trait anxiety(特性不安)の係数。(高いほど多くの不安に成りやすい。)

少々良くわかりませんが、まぁ、だいたいこんな感じだと思います。
これを使って、treatment(怒りのコントロールできる/できない)とtrait_anxiety(どのぐらい不安症)からsecond_attack(心臓発作再発)が発生するかどうかを予測します。

まずは、学習用のデータを作ります。

```
# su - postgres

$ scl enable rh-postgresql95 bash

$ psql
psql (9.5.2)
"help" でヘルプを表示します.

postgres=# 
```

```sql

DROP TABLE IF EXISTS patients, patients_logregr, patients_logregr_summary;
 
CREATE TABLE patients( id INTEGER NOT NULL,
                        second_attack INTEGER,
                        treatment INTEGER,
                        trait_anxiety INTEGER);
                          
INSERT INTO patients VALUES                                                     
(1,     1,      1,      70),
(3,     1,      1,      50),
(5,     1,      0,      40),
(7,     1,      0,      75),
(9,     1,      0,      70),
(11,    0,      1,      65),
(13,    0,      1,      45),
(15,    0,      1,      40),
(17,    0,      0,      55),
(19,    0,      0,      50),
(2,     1,      1,      80),
(4,     1,      0,      60),
(6,     1,      0,      65),
(8,     1,      0,      80),
(10,    1,      0,      60),
(12,    0,      1,      50),
(14,    0,      1,      35),
(16,    0,      1,      50),
(18,    0,      0,      45),
(20,    0,      0,      60);
```

このデータを使って、ロジスティック回帰を使って学習していきます。

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

この辺まで来ると、私もまだよくわかりません。ドキュメント見てください！(この辺からだんだん適当になります。ごめんなさい。)
[Logistic Regression](http://madlib.incubator.apache.org/docs/latest/group__grp__logreg.html)
ロジスティック回帰を多少なりとも理解していないと、ちょっと難しいのですかね。

良く分かってないですが、これで、patients_logregr tableに学習結果(model)が出来上がっているはずです。見てみましょう。

```sql
postgres=# \x on
拡張表示は on です。

postgres=# 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}}
```

なんか入ってるんで、出来てるに違いない。

それでは、この学習モデルを使って、second_attack(心臓発作再発)を予測してみましょう。とりあえずは、元データを使って、予測してみましょう。

```sql
postgres=# \x off
拡張表示は off です。

postgres=# SELECT id, second_attack, madlib.logregr_predict(coef, ARRAY[1, treatment, trait_anxiety]), madlib.logregr_predict_prob(coef, ARRAY[1, treatment, trait_anxiety])
FROM patients p, patients_logregr m ORDER BY id;
 id | second_attack | logregr_predict | logregr_predict_prob 
----+---------------+-----------------+----------------------
  1 |             1 | t               |    0.720223028941525
  2 |             1 | t               |    0.894354902502046
  3 |             1 | f               |    0.192269541755172
  4 |             1 | t               |    0.685513072239347
  5 |             1 | f               |     0.16774788150886
  6 |             1 | t               |     0.79809810891514
  7 |             1 | t               |    0.928568075752502
  8 |             1 | t               |     0.95930576369357
  9 |             1 | t               |    0.877576117431451
 10 |             1 | t               |    0.685513072239347
 11 |             0 | t               |    0.586700895943316
 12 |             0 | f               |    0.192269541755172
 13 |             0 | f               |    0.116032010632995
 14 |             0 | f               |   0.0383829143134989
 15 |             0 | f               |   0.0674976224147607
 16 |             0 | f               |    0.192269541755172
 17 |             0 | t               |    0.545870774302622
 18 |             0 | f               |    0.267675422387135
 19 |             0 | f               |    0.398618639285114
 20 |             0 | t               |    0.685513072239347
(20 行)
```

* `second_attack`は元データ上のsecond_attackです。つまり、心臓発作が実際に起きたのか、起きなかったのか。
* `logregr_predict`は予測結果で、true/falseが返ってきています。
* `logregr_predict_prob`は予測結果で、確率が返ってきています。50%以上が上記のtrueのようですね。

ということで、元データを使って予測してみると、20件中15件は予測が当たっていることが分かります。

# 最後に
ということで、ロジスティック回帰などがある程度わかっている人であれば、SQLを使って簡単に機械学習が出来ることがわかりました。
まぁ、僕の場合は「ある程度わかっている」の部分が結構問題ですけど。。。