20
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

今さら聞けないPython - scikit-learnを用いた機械学習

Last updated at Posted at 2023-03-31

こちらのウェビナーで説明した内容の抜粋です。最初のセクションの「pandasを用いたデータ分析」をカバーしています。今さら聞けないPython - pandasを用いたデータ分析の続きです。

全体構成は以下の通りです。

ウェビナーで使用したノートブックはこちらにあります。

本記事でカバーしているノートブックはこちらです。

  • 機械学習とは何か?
    • 機械学習のタイプ
  • トレーニング - テストデータセットの分割
  • 線形回帰モデルを構築するために 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

Screenshot 2023-03-31 at 11.05.55.png

Python
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'
Python
df.shape
Out[22]: (20584, 5)

感染者数と死者数の関係

Python
# ノートブックでプロットを出力できるようにします
%matplotlib inline
Python
# 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);

Screenshot 2023-03-31 at 11.07.43.png

ニューヨークとニュージャージーは外れ値と言えます

Python
# New YorkとNew Jersey以外の州にフィルタリングします
not_ny = df[(df["state"] != "New York") & (df["state"] != "New Jersey")]
not_ny.head()

Screenshot 2023-03-31 at 11.08.22.png

Python
# 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);

Screenshot 2023-03-31 at 11.08.51.png

New YorkとCaliforniaにおけるCOVID-19の死者数の比較

Python
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

Screenshot 2023-03-31 at 11.09.40.png

Python
df_ny_cali_pivot.plot.line(title="Deaths 2020-01-25 to 2020-05-01 - CA and NY", figsize=(12,8))

Screenshot 2023-03-31 at 11.10.11.png

トレーニング - テストデータセットの分割

これは時系列データなのでランダムに分割するのではなく、モデルのトレーニングには3/1から4/7のデータを使い、4/8から4/14の値を予測することでモデルをテストします。

Python
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モデルをフィッティングします。

Python
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を指定します。

Python
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によってモデルが自動でトラッキングされます。
Screenshot 2023-03-31 at 11.14.02.png
これによって、「どれがどのモデルかわからない」という状況を避けることができます。
Screenshot 2023-03-31 at 11.14.41.png

ワンホットエンコーディング

州のように数値では無い特徴量をどのように扱ったらいいのでしょうか?

あるアイデア:

  • 非数値を表現する単一の数値特徴量を作成する
  • カテゴリー変数の特徴量を作成する:
    • 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"として知られるものです。

Python
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.]])

形状を確認してみましょう。

Python
enc.fit(X_train).transform(X_train).shape
Out[13]: (1754, 915)

うわっ、感染者数の変数もワンホットエンコーディングしてしまいました。

Python
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が必要となります。

Python
from sklearn.compose import ColumnTransformer

ct = ColumnTransformer([("enc", enc, ["state"])], remainder="passthrough")
ct.fit_transform(X_train).shape
Out[15]: (1754, 56)

パイプライン

パイプラインを用いることで、一連のデータ変換処理をチェーンすることができます。また、このようにすることで、トレーニングセットに適用したすべてのオペレーションが、同じ順序でテストセットに適用されることを保証できます。

Python
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)

州が異なるとどうなるのか?

特徴量を追加することで、感染者数特徴量の係数も変化していることに気づきます。

Python
print(f"num_deaths = {pipeline_model.steps[1][1].coef_[-1]:.4f}*cases + state_coef")
num_deaths = 0.0381*cases + state_coef
Python
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"])

Screenshot 2023-03-31 at 11.19.05.png

評価メトリクス

sklearn.metricsを用いて、データセットのMSEとRMSEを計算しましょう。

Python
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

予測結果の可視化

Python
pred = pd.concat([test_df.reset_index(drop=True), pd.DataFrame(y_pred, columns=["predicted_deaths"])], axis=1)
pred

Screenshot 2023-03-31 at 11.20.17.png

やりました!scikit-learnを用いて機械学習パイプラインを構築することができました!

scikit-learnを探索し続ける場合には、UCI ML RepositoryKaggleのデータセットをチェックしてみてください!

Databricksクイックスタートガイド

Databricksクイックスタートガイド

Databricks無料トライアル

Databricks無料トライアル

20
26
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
20
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?