こちらのウェビナーで説明した内容の抜粋です。最初のセクションの「pandasを用いたデータ分析」をカバーしています。今さら聞けないPython - pandasを用いたデータ分析の続きです。
全体構成は以下の通りです。
- 今さら聞けないPython - Pythonの基礎
- 今さら聞けないPython - pandasを用いたデータ分析
- 今さら聞けないPython - scikit-learnを用いた機械学習
- 今さら聞けないPython - Sparkのご紹介
ウェビナーで使用したノートブックはこちらにあります。
本記事でカバーしているノートブックはこちらです。
- 機械学習とは何か?
- 機械学習のタイプ
- トレーニング - テストデータセットの分割
- 線形回帰モデルを構築するために
sklearn
を活用 - ワンホットエンコーディング
- パイプライン
- 評価メトリクス
必要に応じてscikit-learnのドキュメントやpandasのドキュメントを参照します。そして、 https://github.com/nytimes/covid-19-data にあるNew York Times COVID-19 US Statesデータセットのデータを分析します。
免責
このデータセットにおいては線形回帰が最適のアルゴリズムではありませんが、ここではscikit-learnの使い方を説明するために線形回帰を使用します。
機械学習とは何か?
- 明示的なプログラミングを行うことなしにデータからパターンを学習します。
- 特徴量をアウトプットにマッピングする関数です。
参考資料
機械学習のタイプ
- 強化学習
ここではシンプルにスタートし、教師あり学習(回帰)問題にフォーカスします。ここでは、COVID-19による死者数を予測する線形回帰モデルを使用します。
%fs ls databricks-datasets/COVID/covid-19-data/us-states.csv
import pandas as pd
df = pd.read_csv("/dbfs/databricks-datasets/COVID/covid-19-data/us-states.csv")
df.head()
df.date.max()
Out[21]: '2021-03-11'
df.shape
Out[22]: (20584, 5)
感染者数と死者数の関係
# ノートブックでプロットを出力できるようにします
%matplotlib inline
# 2020-05-01のデータにフィルタリングします
df_05_01 = df[df["date"] == "2020-05-01"]
ax = df_05_01.plot(x="cases", y="deaths", kind="scatter",
figsize=(12,8), s=100, title="Deaths vs Cases on 2020-05-01 - All States")
df_05_01[["cases", "deaths", "state"]].apply(lambda row: ax.text(*row), axis=1);
ニューヨークとニュージャージーは外れ値と言えます
# New YorkとNew Jersey以外の州にフィルタリングします
not_ny = df[(df["state"] != "New York") & (df["state"] != "New Jersey")]
not_ny.head()
# 2020-05-01のデータにフィルタリングします
not_ny_05_01 = not_ny[not_ny["date"] == "2020-05-01"]
ax = not_ny_05_01.plot(x="cases", y="deaths", kind="scatter",
figsize=(12,8), s=50, title="Deaths vs Cases on 2020-05-01 - All States but NY and NJ")
not_ny_05_01[["cases", "deaths", "state"]].apply(lambda row: ax.text(*row), axis=1);
New YorkとCaliforniaにおけるCOVID-19の死者数の比較
df_ny_cali = df[(df["state"] == "New York") | (df["state"] == "California")]
# 両方の州における死者数の時間変換をプロットできるように、df_ny_caliデータフレームをピボットしましょう
df_ny_cali_pivot = df_ny_cali.pivot(index='date', columns='state', values='deaths').fillna(0)
df_ny_cali_pivot
df_ny_cali_pivot.plot.line(title="Deaths 2020-01-25 to 2020-05-01 - CA and NY", figsize=(12,8))
トレーニング - テストデータセットの分割
これは時系列データなのでランダムに分割するのではなく、モデルのトレーニングには3/1から4/7のデータを使い、4/8から4/14の値を予測することでモデルをテストします。
train_df = df[(df["date"] >= "2020-03-01") & (df["date"] <= "2020-04-07")]
test_df = df[df["date"] > "2020-04-07"]
X_train = train_df[["cases"]]
y_train = train_df["deaths"]
X_test = test_df[["cases"]]
y_test = test_df["deaths"]
線形回帰
- ゴール: 最もフィットする直線を見つけ出す
$$\hat{y} = w_0 + w_1x$$
$${y} ≈ \hat{y} + ϵ$$
- x: 特徴量
- y: ラベル
ここでは、scikit-learnのLinearRegressionモデルをフィッティングします。
from sklearn.linear_model import LinearRegression
lr = LinearRegression().fit(X_train, y_train)
print(f"num_deaths = {lr.intercept_:.4f} + {lr.coef_[0]:.4f}*cases")
num_deaths = -16.6442 + 0.0359*cases
むむむ...感染者数が0
の場合、COVID-19の死者はいないはずですので切片は0
であるべきです。fit_intercept=False
を指定します。
lr = LinearRegression(fit_intercept=False).fit(X_train, y_train)
print(f"num_deaths = {lr.coef_[0]:.4f}*cases")
num_deaths = 0.0355*cases
これによって、使用しているデータセットにおいては死亡率が3.5%であることを示唆しています。しかし、我々はいくつかの州においてはさらに高い死亡率になっていることを知っています。それでは州を特徴量として追加しましょう!
その前に一点補足を。Databricksでは機械学習モデルをトレーニングすると、MLflowによってモデルが自動でトラッキングされます。
これによって、「どれがどのモデルかわからない」という状況を避けることができます。
ワンホットエンコーディング
州のように数値では無い特徴量をどのように扱ったらいいのでしょうか?
あるアイデア:
- 非数値を表現する単一の数値特徴量を作成する
- カテゴリー変数の特徴量を作成する:
- state = {'New York', 'California', 'Louisiana'}
- 'New York' = 1, 'California' = 2, 'Louisiana' = 3
しかし、これではCaliforniaがNew Yorkの2倍ということになってしまいます!
より良いアイデア:
- カテゴリーごとの
ダミー
特徴量を作成する - 'New York' => [1, 0, 0], 'California' => [0, 1, 0], 'Louisiana' => [0, 0, 1]
このテクニックは"One Hot Encoding"として知られるものです。
from sklearn.preprocessing import OneHotEncoder
X_train = train_df[["cases", "state"]]
X_test = test_df[["cases", "state"]]
enc = OneHotEncoder(handle_unknown='ignore', sparse=False)
enc.fit(X_train).transform(X_train)
Out[12]: array([[1., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 1., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 1., 0., 0.],
[0., 0., 0., ..., 0., 1., 0.],
[0., 0., 0., ..., 0., 0., 1.]])
形状を確認してみましょう。
enc.fit(X_train).transform(X_train).shape
Out[13]: (1754, 915)
うわっ、感染者数の変数もワンホットエンコーディングしてしまいました。
enc.categories_
Out[14]: [array([ 1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 63, 64,
65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79, 80,
81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 91, 92, 93, 94, 95, 96,
97, 98, 99, 100, 101, 102, 103, 104,
特定のカラムにのみワンホットエンコーディングを適用するように、column transformerが必要となります。
from sklearn.compose import ColumnTransformer
ct = ColumnTransformer([("enc", enc, ["state"])], remainder="passthrough")
ct.fit_transform(X_train).shape
Out[15]: (1754, 56)
パイプライン
パイプラインを用いることで、一連のデータ変換処理をチェーンすることができます。また、このようにすることで、トレーニングセットに適用したすべてのオペレーションが、同じ順序でテストセットに適用されることを保証できます。
from sklearn.pipeline import Pipeline
pipeline = Pipeline(steps=[("ct", ct), ("lr", lr)])
pipeline_model = pipeline.fit(X_train, y_train)
y_pred = pipeline_model.predict(X_test)
州が異なるとどうなるのか?
特徴量を追加することで、感染者数特徴量の係数も変化していることに気づきます。
print(f"num_deaths = {pipeline_model.steps[1][1].coef_[-1]:.4f}*cases + state_coef")
num_deaths = 0.0381*cases + state_coef
import pandas as pd
pd.set_option('display.float_format', '{:.2f}'.format)
categories = pipeline_model.steps[0][1].transformers[0][1].categories_[1]
pd.DataFrame(zip(categories, pipeline_model.steps[1][1].coef_[:-1]), columns=["State", "Coefficient"])
評価メトリクス
sklearn.metricsを用いて、データセットのMSEとRMSEを計算しましょう。
from sklearn.metrics import mean_squared_error
import numpy as np
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
print(f"MSE is {mse:.1f}, RMSE is {rmse:.1f}")
MSE is 78694639.4, RMSE is 8871.0
予測結果の可視化
pred = pd.concat([test_df.reset_index(drop=True), pd.DataFrame(y_pred, columns=["predicted_deaths"])], axis=1)
pred
やりました!scikit-learnを用いて機械学習パイプラインを構築することができました!
scikit-learnを探索し続ける場合には、UCI ML RepositoryやKaggleのデータセットをチェックしてみてください!