OCI Always Free 環境でJupyter Notebookを使った機械学習 (2019/12/17)

この記事は「[Oracle Cloud Infrastructure Advent Calendar 2019](https://adventar.org/calendars/4516)」の[12月17日の記事](https://adventar.org/calendars/4516#list-2019-12-17)として書かれています。

### この記事でやっていること
- Oracle Cloud Infrastructure のAlways Freeの環境にVCN/Compute インスタンス/Autonomous Database(ADW)の作成
- Compute インスタンスに Jupyter Notebook環境をセットアップ
- Autonomous Databaseのデータを使って機械学習(決定木・ランダムフォレスト)

## 環境構築手順
1. 仮想クラウド・ネットワークの作成
2. Compute インスタンスの作成
3. Autonmous Database(今回はADW)の作成![image01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/5a0120bc-05c4-6f3a-e99a-3e873a044f1e.jpeg)

4. Compute インスタンスにJupyter Notebook の環境セットアップ
5. Jupyter Notebook から Autonomous Databaseに接続し、機械学習の実施

### 1. 仮想クラウド・ネットワークの作成
OCI Webコンソールで仮想クラウド・ネットワーク(VCN)を作成
2019年12月5日からVirtual networking quickstartが追加され、
「VCN名」、「VCN CIDR」、「パブリックサブネットCIDR」、「プライベートサブネットCIDR」の4つの入力項目で簡単にVCN作成が可能に。
![image01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/8c9c8e32-48cf-fd30-3f26-e4f6c0fd343e.jpeg)
(QuickstartをつかってVCNを作成する場合 日本語表示で作成すると、サブネット名が「パブリック・サブネット-＜VCN名＞」のようになる表示言語を英語に変更することを推奨)
※ Always Free 環境はホームリージョンで作成可能です
![image01.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/ab637bf3-0bd8-5f2c-8c0b-c96df292ecf2.jpeg)



### 2. Compute インスタンスの作成
Oracle Linux 7 をイメージとした Compute インスタンスを作成
- 「シェイプ、ネットワークおよびストレージ・オプションの表示」をクリックし、ネットワーキングの構成で、１．で作成した仮想クラウド・ネットワーク、パブリックサブネットを選択
![image02.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/0a931d72-0944-6bea-4e10-271bea983893.jpeg)
- あわせて、「パブリックIPアドレスの割当て」にチェックして作成
![image03.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/3a6a8beb-1190-9b0f-c013-397df5013029.jpeg)


### 3. Autonmous Database(今回はADW)の作成
Autonomous Databaseの作成からワークロード・タイプを「データ・ウェアハウス」を選択して作成
![image04.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/0523f8e9-09b0-f3ee-39af-16889adf9538.jpeg)

SQL Develoer Webなどを使って作業用ユーザを作成

```
CREATE USER scott IDENTIFIED BY password; 

GRANT DWROLE TO scott;
GRANT UNLIMITED TABLESPACE TO scott;

GRANT
    SELECT ANY TABLE,
    UPDATE ANY TABLE,
    DELETE ANY TABLE,
    INSERT ANY TABLE
TO scott;
```

### 4. Compute インスタンスにJupyter Notebook の環境セットアップ
2.で作成したCompute インスタンス(OracleLinux)に ssh で接続 (opcユーザ)しJupyter Notebookをインストール

4-1. Python 3をインストール
4-1-1. EPELリポジトリ・Python3.6のインストール

```
sudo yum install -y oracle-epel-release-el7 oracle-release-el7
sudo yum install -y python36
sudo yum install -y libSM.x86_64
sudo yum install -y libXext.x86_64
sudo yum install -y libXrender.x86_64
```

4-1-2. mlevn(Python仮想環境セットアップ・アクティブ化)   

 ```
python3.6 -m venv mlenv 
source mlenv/bin/activate
 ```
​
4-1-3. 機械学習で使用するライブラリをインストール(今回の手順実行のためのライブラリ)

```
pip3 install --upgrade pip
pip3 install pandas
pip3 install seaborn
pip3 install sklearn
```

4-1-4. Jupyterをインストール

```
python3 -m pip install jupyter
```

4-1-5. Oracle Instant Clientインストールと構成

```
sudo yum -y install oraclelinux-developer-release-el7
sudo yum -y install python-cx_Oracle
pip3 install cx_Oracle
sudo sh -c "echo /usr/lib/oracle/18.3/client64/lib > /etc/ld.so.conf.d/oracle-instantclient.conf"
sudo ldconfig
```

4-1-6. Autonomous Databaseのクライアント資格証明(ウォレット)のダウンロード
3.で作成したAutonomous Databaseの詳細画面から、「DB接続」をクリックし、「データベース接続」ダイアログから「ウォレットのダウンロード」をクリックし、Zipファイルをダウンロード
![image05.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/6fbf6a29-23b4-da0a-9387-86cb41d6eb7b.jpeg)
ダウンロードしたZipファイルを Compute インスタンスに転送(scpなど)し、解凍(解凍ディレクトリをメモ 例：/home/opc/wallet)
解凍したディレクトリにある sqlnet.ora ファイルを編集
「WALLET_LOCATION」行のDIRECTORYで指定している「~/network/admin」を解凍したディレクトリに変更

```sqlnet.ora
WALLET_LOCATION = (SOURCE = (METHOD = file) (METHOD_DATA = (DIRECTORY="/home/opc/wallet")))
SSL_SERVER_DN_MATCH=yes
```

4-1-7. ノートブックを実行
環境変数を設定(LD_LIBRARY_PATH にはInstance Clientのパス、TNS_ADMINにはウォレットを解凍したディレクトリを指定)

```
export LD_LIBRARY_PATH=/usr/lib/oracle/18.3/client64/lib:$LD_LIBRARY_PATH
export TNS_ADMIN=/home/opc/wallet
source mlenv/bin/activate
jupyter notebook --ip=0.0.0.0 &
```

jupyterノートブックを実行時に表示されるトークンキーをメモ

```
    To access the notebook, open this file in a browser:
        file:///home/opc/.local/share/jupyter/runtime/nbserver-2210-open.html
    Or copy and paste one of these URLs:
        http://compute01:8888/?token=0e701120a4e7319ae8b970ac069fbfee53e7b07f072cc2dc
     or http://127.0.0.1:8888/?token=0e701120a4e7319ae8b970ac069fbfee53e7b07f072cc2dc     ```
```
### 5. Jupyter Notebook から Autonomous Databaseに接続し、機械学習の実施
5-1. クライアントPCから別のsshセッションでトンネリングを確立
```$ ssh –i <private_key> opc@<public_IP_address> -L 8000:localhost:8888​```
5-2. ローカルマシンでWebブラウザを開き以下URLにアクセス
http://localhost:8000 
5-3. トークンの入力を求められたら、jupyter Notebookを実行時に表示されるトークンキーを入力
![image06.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/bbf7a0a4-64f7-a9a5-0a5f-1ed216b319c3.jpeg)

![image07.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/b1949454-7e7c-4cd5-4d61-fc0e5c6bfb37.jpeg)

5-4. Jupyter NotebookからAutonomous Databaseに接続
Autonomous Databaseのユーザ名、パスワード および 解凍したウォレット内のtnsnames.oraから接続用サービス名を確認し、指定

Autonomous Databaseに接続し、DBバージョンを確認例

```
import cx_Oracle
con = cx_Oracle.connect('scott/<password>@orcl_medium')
print(con.version)
con.close()
```

Autonomous Databaseに接続し、shスキーマのSALES表の件数を確認例

```
import cx_Oracle
con = cx_Oracle.connect('scott/<password>@orcl_medium')
cur = con.cursor()        # opens cursor for statements to use
cur.execute('select count(*) from sh.sales')
for result in cur:        # prints all data
    print(result)
con.close()
```

CUSTOMERS表から男女別のCUST_VALIDを比較

```
import pandas.io.sql as psql
import cx_Oracle
import seaborn as sns
%matplotlib inline

con = cx_Oracle.connect('scott/<password>@orcl_medium')

sql = 'select CUST_GENDER,CUST_VALID from sh.customers'
df = psql.read_sql(sql,con)
sns.countplot('CUST_GENDER',hue='CUST_VALID',data=df)
# 列名は大文字で表記
con.close() 
```

![image08.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/b0fd0ce8-514a-d38e-c19e-ca4228e4cd5a.jpeg)



#### 機械学習
scikit-learnの中のライブラリtreeとscikit-learnのensembleの中のrandom forest classfierを使って
CUSTOMERS表のCUST_VALIDを予測する機械学習モデルを作成

- SELECT文で特徴量と目的変数を取得しDataFrameに格納

```
sql = 'select cust_id,cust_year_of_birth,CUST_GENDER,cust_postal_code,cust_valid from sh.customers'
df = psql.read_sql(sql,con)
```

- 欠損値処理とカテゴリ変数(文字)の変換処理

```
#欠損値処理
df['CUST_YEAR_OF_BIRTH'] = df['CUST_YEAR_OF_BIRTH'].fillna(df['CUST_YEAR_OF_BIRTH'].median())
df['CUST_POSTAL_CODE'] = df['CUST_POSTAL_CODE'].fillna(df['CUST_POSTAL_CODE'].median())

#カテゴリ変数の変換
df['CUST_VALID']=df['CUST_VALID'].apply(lambda x:1 if x == 'I' else 0)
df['CUST_GENDER']=df['CUST_GENDER'].apply(lambda x:1 if x == 'M' else 0)
```

- 学習データとテストデータに分割(今回は９割を学習データにしている)

```
X = df.loc[:, ["CUST_YEAR_OF_BIRTH","CUST_GENDER","CUST_POSTAL_CODE"]]
Y = df.loc[:, "CUST_VALID"]
(train_X, test_X ,train_y, test_y) = train_test_split(X, Y, test_size = 0.1, random_state = 666)
```

- 決定木モデル作成とテストデータでの検証、精度の確認

```
clf = DecisionTreeClassifier(random_state=0)
clf = clf.fit(train_X, train_y) # モデル作成!
pred = clf.predict(test_X)
print("Prediction Score: {}".format(clf.score(test_X, test_y)))
```

- ランダムフォレストモデル作成とテストデータでの検証

```
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(random_state=0)
clf = clf.fit(train_X, train_y)
pred = clf.predict(test_X)
```

- 機械学習コード全体

```ml4py.py
import pandas.io.sql as psql
import cx_Oracle
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import (roc_curve, auc, accuracy_score)
from sklearn.metrics import classification_report
con = cx_Oracle.connect('scott/<password>@orcl_medium')

sql = 'select cust_id,cust_year_of_birth,CUST_GENDER,cust_postal_code,cust_valid from sh.customers'
df = psql.read_sql(sql,con)
#欠損値処理
df['CUST_YEAR_OF_BIRTH'] = df['CUST_YEAR_OF_BIRTH'].fillna(df['CUST_YEAR_OF_BIRTH'].median())
df['CUST_POSTAL_CODE'] = df['CUST_POSTAL_CODE'].fillna(df['CUST_POSTAL_CODE'].median())

#カテゴリ変数の変換
df['CUST_VALID']=df['CUST_VALID'].apply(lambda x:1 if x == 'I' else 0)
df['CUST_GENDER']=df['CUST_GENDER'].apply(lambda x:1 if x == 'M' else 0)

X = df.loc[:, ["CUST_YEAR_OF_BIRTH","CUST_GENDER","CUST_POSTAL_CODE"]]
Y = df.loc[:, "CUST_VALID"]
(train_X, test_X ,train_y, test_y) = train_test_split(X, Y, test_size = 0.1, random_state = 666)

clf = DecisionTreeClassifier(random_state=0)
clf = clf.fit(train_X, train_y) # モデル作成!
pred = clf.predict(test_X)
fpr, tpr, thresholds = roc_curve(test_y, pred, pos_label=1)
auc(fpr, tpr)
accuracy_score(pred, test_y)
classification_report(pred, test_y, labels=None)
print("Prediction Score: {}".format(clf.score(test_X, test_y)))
print("決定木モデルの精度: {:.6f}".format(accuracy_score(pred, test_y)))

from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(random_state=0)
clf = clf.fit(train_X, train_y)
pred = clf.predict(test_X)
fpr, tpr, thresholds = roc_curve(test_y, pred, pos_label=1)
auc(fpr, tpr)
print("ランダムフォレストモデルの精度: {:.6f}".format(accuracy_score(pred, test_y)))
con.close() 
```
## まとめと今後に向けて
Always Freeの環境(Compute インスタンスとAutonomous DB)を使ってJupyter Notebook で機械学習(決定木・ランダムフォレスト)の実施ができました。

[2019年12月5日付け](https://blogs.oracle.com/database/machine-learning%2c-spatial-and-graph-no-license-required-v2)でOracle Databaseで機械学習、地理データ、グラフデータの分析機能が、有償オプションではなくなり、Standard Editionでも利用可能になりました。
Oracle Machine Learning API for Pythonは 「[coming soon](https://blogs.oracle.com/machinelearning/machine-learning-in-oracle-database)」ということなので、
Oralce Machine Learning for Pythonがリリースされた時点で比較できるようにしたいと思います。

### 参考情報
- [Oracle Cloud の永久無料枠で ATP : Autonomous Transaction Processing を作ってみた](https://qiita.com/mkrydik/items/8c9322440f4dc954d5ed)
- [Set Up a Machine-Learning Workbench on Oracle Cloud Infrastructure](https://blogs.oracle.com/cloud-infrastructure/set-up-a-machine-learning-workbench-on-oracle-cloud-infrastructure)
- [Oracle CloudにあるAutonomous Data WarehouseにJupyter Notebookから接続してみる](https://qiita.com/mikika/items/62757402810af5ad122d)
- [Python (cx_Oracle) から２つ目のAutonomous Database (ADWやATP)に接続してみる](https://qiita.com/mikika/items/5d157c2fcdd80d560bd4)