21
21

More than 1 year has passed since last update.

scikit-learnのサンプルデータセットと主要OSSを活用したデータ分析のチュートリアル

Posted at

著者: 株式会社 日立ソリューションズ 柳村 明宏
監修: 株式会社 日立製作所

はじめに

近年、機械学習をはじめとするAI技術を活用したデータ分析が注目を集めています。
本投稿では、機械学習に馴染みのない方やデータ分析に馴染みのない方が、データ分析に触れていただくことを目的に、手軽に利用可能なOSSを利用したデータ分析の一連の手順とサンプルコードを紹介いたします。

データ分析の流れ

データ分析環境と、データ分析の流れを以下に示します。
吹き出しはそれぞれのフェーズで利用するOSSです。

データ分析.png

  • 「データの理解」では、データの全体像を把握するために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 町別の「非小売業の割合」 説明変数
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上でデータセットを可視化します。
  • scikit-learn
  • numpy
  • pandas
  • seaborn
2 モデルの作成と評価 データセットを利用して、機械学習モデルの学習・評価を行います。
  • scikit-learn
  • optuna
  • pandas
3 モデルの利用 #2 で作成した機械学習モデルをWebサーバにデプロイして、HTTP API経由で利用します。
  • flask

データの理解

今回は、データの全体像を把握するために、Jupyter Notebook上でデータセットを可視化します。データセットを読込み、データセットの内容の参照と、説明変数と目的変数のデータの相関を可視化します。データの理解に使用したノートブック の内容を以下に示します。
ライブラリをimportし、データセットを読み込みます。

Visualization.ipynb
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

データセットを表形式で参照します。今回は、先頭と末尾の行を表示します。

Visualization.ipynb
df = DataFrame(X_array, columns = boston.feature_names).assign(MEDV=np.array(y_array))

# ヘッダ出力
df.head

出力結果は次の通りです。

df_head_result.png

利用するデータセットで紹介したカラムで、506個のデータが存在することがわかりました。
次に、説明変数と目的変数の相関関係を可視化してみます。
可視化用のライブラリとしてseabornをimportします。

Visualization.ipynb
import seaborn as sns

CRIM(犯罪率)と、MEDV(住宅価格)の相関を見ます。

Visualization.ipynb
sns.regplot(x=df["CRIM"], y=df["MEDV"], data = df)

出力結果は次の通りです。

seaborn_CRIM_MEDV.png

犯罪率が低い地域の住宅は、住宅価格が高いことがわかります。

最後に、RM(1戸当たりの「平均部屋数」)と、MEDV(住宅価格)の相関を見ます。

Visualization.ipynb
sns.regplot(x=df["RM"], y=df["MEDV"], data = df)

seaborn_RM_MEDV.png

部屋数が多い住宅は、住宅価格が高いことがわかります。

モデルの作成と評価

本項では、ボストンの住宅価格データセットを用いて、住宅周辺の環境情報から住宅価格を予測するモデルを作成します。また、作成したモデルの予測精度を評価します。
初めに学習・検証・評価データに分割します。次に学習・検証データを利用して、Optunaによるハイパーパラメータチューニングを行いつつ、モデルを学習します。最後に評価データを利用して、モデルの予測精度(評価指標)を確認します。
検証用と評価用のデータを分ける理由は、もし評価データを学習時のハイパーパラメータチューニングに利用してしまうと、評価データに最適化されたモデルが選択されてしまうためです。評価ではモデルの本番導入後(=サービング時)に全く知らない新しいデータが来た際の性能を知りたいため、一度も学習に使ったことのないデータで評価する必要があります。
本項の実施内容を以下の表に示します。

# 内容 ポイント
1 データセットを読込み学習、評価、検証用にデータを分割します。 分割数は任意ですが、今回は以下のように分割しました。
  • 学習用:303個(60%)
  • 検証用:102個(20%)
  • 評価用:101個(20%)
2 Optunaでモデルのハイパーパラメータチューニングを行い、モデルを学習します。
  • 利用したモデル: Ridge回帰
  • 探索するモデルのハイパーパラメータ:正則化係数(今回の探索範囲:0.01~100.00)
  • 最適化する評価指標: 平均絶対誤差(MAE)

Ridge回帰に関しては次のサイトを参考にしてください。

平均絶対誤差 (MAE, Mean Absolute Error) は、実際の値と予測値の絶対値を平均したものです。MAE が小さいほど誤差が少なく、予測モデルが正確に予測できていることを示しています。回帰分析に対する評価指標に関しては、次のサイトを参考にしてください。

モデルの作成と評価に使用したPythonコードの内容を以下に示します。

train.py
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コードの内容を以下に示します。

web_server.py
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ファイルの例を示します。今回各項目の値は、データセットの先頭のデータを利用します。

request.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を活用して、データの理解・モデルの開発・モデルの利用(サービング)の一連の手順を紹介しました。機械学習に馴染みのない方や、簡単にデータ分析を行ってみたいという方は、是非試してみてください。

21
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
21