著者: 株式会社 日立ソリューションズ 柳村 明宏
監修: 株式会社 日立製作所
はじめに
近年、機械学習をはじめとするAI技術を活用したデータ分析が注目を集めています。
本投稿では、機械学習に馴染みのない方やデータ分析に馴染みのない方が、データ分析に触れていただくことを目的に、手軽に利用可能なOSSを利用したデータ分析の一連の手順とサンプルコードを紹介いたします。
データ分析の流れ
データ分析環境と、データ分析の流れを以下に示します。
吹き出しはそれぞれのフェーズで利用するOSSです。
-
「データの理解」では、データの全体像を把握するためにJupyter Notebook上でデータセットを可視化します。
-
「モデルの作成と評価」では、データセットを利用して機械学習モデルの学習・評価を行います。今回は、再利用を考慮してPythonコードで記載していますが、Jupyter Notebookでも作成可能です。
-
「モデルの利用」では、作成した機械学習モデルをWebサーバにデプロイして、HTTP API経由で利用します。
利用するデータセット
データセットには、scikit-learnに同梱されている「Boston housing prices dataset」という、米国ボストン市の住宅価格に関するデータセットを利用します。これは、住宅周辺の環境情報(説明変数)から、住宅価格(目的変数)を予測する、回帰分析向けのサンプルデータです。次の表の通り、ボストンの住宅価格のデータセットは、13項目の説明変数と1項目の目的変数をもつ、506個のデータで構成されています。
なお、本データセットはscikit-learnのバージョン1.0で非推奨となりました。バージョン1.2で削除される予定です。バージョン1.2以降で、今回のサンプルを動作させる場合は、データセットの読込み部分を変更させる必要があると想定されます。詳細は下記のサイトを参考にしてください。
Boston housing prices datasetに関して
# | カラム | 説明 | 変数分類 |
---|---|---|---|
1 | CRIM | 町別の「犯罪率」 | 説明変数 |
2 | ZN | 25,000平方フィートを超える区画に分類される住宅地の割合=「広い家の割合」 | 説明変数 |
3 | INDUS | 町別の「非小売業の割合」 | 説明変数 |
4 | CHAS | チャールズ川のダミー変数(区画が川に接している場合は1、そうでない場合は0)=「川の隣か」 | 説明変数 |
5 | NOX | 「NOx濃度(0.1ppm単位)」=一酸化窒素濃度(parts per 10 million単位)。この項目を目的変数とする場合もある | 説明変数 |
6 | RM | 1戸当たりの「平均部屋数」 | 説明変数 |
7 | AGE | 1940年より前に建てられた持ち家の割合=「古い家の割合」 | 説明変数 |
8 | DIS | 5つあるボストン雇用センターまでの加重距離=「主要施設への距離」 | 説明変数 |
9 | RAD | 「主要高速道路へのアクセス性」の指数 | 説明変数 |
10 | TAX | 10,000ドル当たりの「固定資産税率」 | 説明変数 |
11 | PTRATIO | 町別の「生徒と先生の比率」 | 説明変数 |
12 | B | 「1000(Bk - 0.63)」の二乗値。Bk=「町ごとの黒人の割合」を指す | 説明変数 |
13 | LSTAT | 「低所得者人口の割合」 | 説明変数 |
14 | MEDV | 「住宅価格」(1,000ドル単位)の中央値。通常はこの数値が目的変数として使われる | 目的変数 |
データ分析のチュートリアル
利用するデータセットで紹介したデータセットを利用して、データ分析を行っていきます。実現する内容を次の表に示します。
# | 項目 | 内容 | 利用するライブラリ |
---|---|---|---|
1 | データの理解 | データの全体像を把握するために、Jupyter Notebook上でデータセットを可視化します。 |
|
2 | モデルの作成と評価 | データセットを利用して、機械学習モデルの学習・評価を行います。 |
|
3 | モデルの利用 | #2 で作成した機械学習モデルをWebサーバにデプロイして、HTTP API経由で利用します。 |
|
データの理解
今回は、データの全体像を把握するために、Jupyter Notebook上でデータセットを可視化します。データセットを読込み、データセットの内容の参照と、説明変数と目的変数のデータの相関を可視化します。データの理解に使用したノートブック の内容を以下に示します。
ライブラリをimportし、データセットを読み込みます。
import pandas as pd
import numpy as np
from pandas import DataFrame
from sklearn.datasets import load_boston
# ボストン住宅価格データセットの読み込み
boston = load_boston()
# 説明変数
X_array = boston.data
# 目的変数
y_array = boston.target
データセットを表形式で参照します。今回は、先頭と末尾の行を表示します。
df = DataFrame(X_array, columns = boston.feature_names).assign(MEDV=np.array(y_array))
# ヘッダ出力
df.head
出力結果は次の通りです。
利用するデータセットで紹介したカラムで、506個のデータが存在することがわかりました。
次に、説明変数と目的変数の相関関係を可視化してみます。
可視化用のライブラリとしてseabornをimportします。
import seaborn as sns
CRIM(犯罪率)と、MEDV(住宅価格)の相関を見ます。
sns.regplot(x=df["CRIM"], y=df["MEDV"], data = df)
出力結果は次の通りです。
犯罪率が低い地域の住宅は、住宅価格が高いことがわかります。
最後に、RM(1戸当たりの「平均部屋数」)と、MEDV(住宅価格)の相関を見ます。
sns.regplot(x=df["RM"], y=df["MEDV"], data = df)
部屋数が多い住宅は、住宅価格が高いことがわかります。
モデルの作成と評価
本項では、ボストンの住宅価格データセットを用いて、住宅周辺の環境情報から住宅価格を予測するモデルを作成します。また、作成したモデルの予測精度を評価します。
初めに学習・検証・評価データに分割します。次に学習・検証データを利用して、Optunaによるハイパーパラメータチューニングを行いつつ、モデルを学習します。最後に評価データを利用して、モデルの予測精度(評価指標)を確認します。
検証用と評価用のデータを分ける理由は、もし評価データを学習時のハイパーパラメータチューニングに利用してしまうと、評価データに最適化されたモデルが選択されてしまうためです。評価ではモデルの本番導入後(=サービング時)に全く知らない新しいデータが来た際の性能を知りたいため、一度も学習に使ったことのないデータで評価する必要があります。
本項の実施内容を以下の表に示します。
# | 内容 | ポイント |
---|---|---|
1 | データセットを読込み学習、評価、検証用にデータを分割します。 | 分割数は任意ですが、今回は以下のように分割しました。
|
2 | Optunaでモデルのハイパーパラメータチューニングを行い、モデルを学習します。 |
|
Ridge回帰に関しては次のサイトを参考にしてください。
平均絶対誤差 (MAE, Mean Absolute Error) は、実際の値と予測値の絶対値を平均したものです。MAE が小さいほど誤差が少なく、予測モデルが正確に予測できていることを示しています。回帰分析に対する評価指標に関しては、次のサイトを参考にしてください。
モデルの作成と評価に使用したPythonコードの内容を以下に示します。
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.linear_model import Ridge
import optuna
import pickle
import pandas as pd
import sklearn.metrics
boston = load_boston()
# xが説明変数
# yが目的変数(MEDV)
x = boston.data
y = boston.target
# 訓練データ:6、検証データ:2、評価データ:2に分割
train_x, valid_test_x, train_y, valid_test_y = train_test_split(x, y, train_size = 0.6, random_state = 1)
test_x, valid_x, test_y, valid_y = train_test_split(valid_test_x, valid_test_y, test_size = 0.5, random_state = 1)
# Optunaでtrial回数分のモデルをregister_modelで保持する
# 試行が完了したら、get_modelで最良の試行回数をキーにモデルを取得する
class Callback:
def __init__(self):
self.models = {}
def __call__(self, study, trial):
pass
def register_model(self, trial_number, model):
self.models[str(trial_number)] = model
def get_model(self, trial_number):
return self.models[str(trial_number)]
def objective(trial):
# ハイパーパラメータとして指定範囲の乱数を生成
param_alpha = trial.suggest_float("alpha", 0.01, 100.00)
# リッジ回帰モデルを作成
model = Ridge(alpha = param_alpha)
# モデルの訓練を実行
model.fit(train_x, train_y)
# 訓練済みモデルを登録
callback.register_model(trial.number, model)
# 訓練済みモデルを使用して、検証データによる予測を実行
y_pred = model.predict(valid_x)
# 訓練データの正解値と予測結果から、MAE(Mean Absolute Error)を算出
return sklearn.metrics.mean_absolute_error(valid_y, y_pred)
callback = Callback()
# セッションの作成
study = optuna.create_study()
# 試行
study.optimize(objective,n_trials=100, callbacks=[callback])
# 最良のモデルの取得
best_model = callback.get_model(study.best_trial.number)
# 評価データによる予測を実行
pred = best_model.predict(test_x)
# 評価データの正解値と予測結果から、MAEを算出
print("MAE : " + str(sklearn.metrics.mean_absolute_error(test_y, pred)))
# 学習済みモデルをファイルに出力
filename = "model.pickle"
pickle.dump(best_model, open(filename, 'wb'))
作成したPythonコードを実行してモデルの作成と評価を行います。
C:\work>python train.py
# --- 実行結果例 ここから -------------------------------------------------
# [I 2021-09-13 16:47:35,285] A new study created in memory with name: no-name-063072d1-55ec-433c-b92e-de199a5af387
# [I 2021-09-13 16:47:35,298] Trial 0 finished with value: 3.4490167509024525 and parameters: {'alpha': 30.18482525879907}. Best is trial 0 with value: 3.4490167509024525.
# [I 2021-09-13 16:47:35,300] Trial 1 finished with value: 3.5562661263791084 and parameters: {'alpha': 70.35330894918852}. Best is trial 0 with value: 3.4490167509024525.
# :
# [I 2021-09-13 16:47:35,690] Trial 68 finished with value: 3.301292598752645 and parameters: {'alpha': 0.1044170092688274}. Best is trial 68 with value: 3.301292598752645.
# [I 2021-09-13 16:47:35,694] Trial 69 finished with value: 3.356478806393828 and parameters: {'alpha': 6.2616723436725525}. Best is trial 68 with value: 3.301292598752645.
# :
# [I 2021-09-13 16:47:35,867] Trial 99 finished with value: 3.3576048354298553 and parameters: {'alpha': 6.4807971462797465}. Best is trial 68 with value: 3.301292598752645.
# MAE : 3.6333021709099773
# --- 実行結果例 ここまで--------------------------------------------------
trial 68が最良のモデルとなったため、trial 68の学習済モデルを出力します。学習済みモデルは、上述の実装にある通り、~/model.pickle
で出力されます。
モデルの利用
Webサーバの起動
モデルの作成と評価で作成した学習済みモデルをWebサーバにロードして、HTTP API経由で利用してみます。
Flaskで実装したHTTP APIをもつWebサーバ のPythonコードの内容を以下に示します。
import pickle
import pandas as pd
from flask import Flask, request, jsonify
# 学習済みパイプラインをロードする
filename = "model.pickle"
model = pickle.load(open(filename, 'rb'))
# Flaskサーバを作成
app = Flask(__name__)
# エンドポイントを定義 http:/host:port/predict, POSTメソッドのみ受け付ける
@app.route('/predict', methods=['POST'])
def get_predict():
# Jsonリクエストから値取得
X_dict = request.json
# DataFrame化
X_df = pd.DataFrame(X_dict)
# 予測実行
prediction = model.predict(X_df)
# 予測結果をJson化して返信
result = {"MEDV": float(prediction[0])}
return jsonify(result)
# Flaskサーバをポート番号5060で起動
if __name__ == "__main__":
app.run(port=5060)
Webサーバを起動します。
C:\work>python web_server.py
# --- 実行結果例 ここから -------------------------------------------------
# * Serving Flask app "web_server" (lazy loading)
# * Environment: production
# WARNING: This is a development server. Do not use it in a production deployment.
# Use a production WSGI server instead.
# * Debug mode: off
# * Running on http://127.0.0.1:5060/ (Press CTRL+C to quit)
# --- 実行結果例 ここまで--------------------------------------------------
推論実行
住宅価格を予測したい住宅の周辺情報のデータをJSONファイルで作成し、HTTPリクエストで送信します。入力データのJSONファイルの例を示します。今回各項目の値は、データセットの先頭のデータを利用します。
[
{
"CRIM":"0.00632",
"ZN":"18.0",
"INDUS":"2.31",
"CHAS":"0.0",
"NOX":"0.538",
"RM":"6.575",
"AGE":"65.2",
"DIS":"4.0900",
"RAD":"1.0",
"TAX":"296.0",
"PTRATIO":"15.3",
"B":"396.90",
"LSTAT":"4.98"
}
]
curl コマンドを利用して、HTTPリクエストを送信します。前述で作成したJSONファイルを入力データとします。
# HTTPリクエストの送信
C:\work>curl -v http://127.0.0.1:5060/predict -H "Content-Type: application/json" -d @request.json
# --- 実行結果例 ここから -------------------------------------------------
# * Trying 127.0.0.1:5060...
# * Connected to 127.0.0.1 (127.0.0.1) port 5060 (#0)
# > POST /predict HTTP/1.1
# > Host: 127.0.0.1:5060
# > User-Agent: curl/7.71.1
# > Accept: */*
# > Content-Type: application/json
# > Content-Length: 242
# >
# * upload completely sent off: 242 out of 242 bytes
# * Mark bundle as not supporting multiuse
# * HTTP 1.0, assume close after body
# < HTTP/1.0 200 OK
# < Content-Type: application/json
# < Content-Length: 28
# < Server: Werkzeug/1.0.1 Python/3.8.8
# < Date: Mon, 13 Sep 2021 09:39:02 GMT
# <
# {"MEDV":29.493047836307362}
# * Closing connection 0
# --- 実行結果例 ここまで -------------------------------------------------
HTTPレスポンスとして、MEDVの値が{"MEDV": 29.493047836307362}
のような形式で返却されます。 MEDVは千ドル単位の住宅価格なので、この場合は住宅価格が29,493ドルと予測されたことを示しています。
Webサーバのログには次のようなログが出力されます。
# ---出力例 ここから ------------------------------------------------------
# 127.0.0.1 - - [13/Sep/2021 18:39:02] "[37mPOST /predict HTTP/1.1[0m" 200 -
# --- 出力例 ここまで -----------------------------------------------------
おわりに
本投稿では、Python製の機械学習フレームワークであるscikit-learnとハイパーパラメータの最適化を自動化するOptunaとWebサーバであるFlaskを活用して、データの理解・モデルの開発・モデルの利用(サービング)の一連の手順を紹介しました。機械学習に馴染みのない方や、簡単にデータ分析を行ってみたいという方は、是非試してみてください。