kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Scratchpad は便利!Jupyter Notebook で検証用のセルをシュッと追加する

Jupyter Notebook でコードを書いているときに「検証のために一時的にセルを追加してコードを書いて終わったらセルを消す」という操作を頻繁にしている(表現しにくいけど...).些細な操作ではあるけど地味に面倒だった.

そこで「Scratchpad」を使ったら便利だった!「Scratchpad」 nbextensions を有効化すればすぐに使える.Jupyter Notebook の右下に表示されている アイコンをクリック(もしくは Ctrl-B)すると一時的に使えるセルが出てきて書き捨てのコードを気軽に実行できる.

便利だー👏

f:id:kakku22:20211218183323p:plain

Feature Engineering : Kaggle Courses で「特徴量エンジニアリング」を学ぶ

Kaggle が公開している「Kaggle Courses」「Feature Engineering」コースを受講した.機械学習モデルを構築するときに重要になる「特徴量エンジニアリング」を多岐にわたる観点から学べる.「特徴量エンジニアリング」の目標は「データセットを目の前の問題に対してより適切な形にすること」であるとコースでは紹介されていて,より具体的には以下となる.

  • モデルの予測パフォーマンスを向上させるため
  • 計算またはデータのニーズを削減するため
  • 結果の解釈可能性を向上させるため

www.kaggle.com

アジェンダ ✏️

「Feature Engineering」コースは「計6種類」のレッスン(ドキュメント)と「計5種類」のレッスン(演習)から構成されている.「相互情報量」「K-means」「主成分分析 (PCA)」「Target Encoding」など,多岐にわたる.

  1. What Is Feature Engineering
  2. Mutual Information
  3. Creating Features
  4. Clustering With K-Means
  5. Principal Component Analysis
  6. Target Encoding

また「Feature Engineering」コースの前提になる「Intermediate Machine Learning」コースは既に受講していて,以下にまとめてある.

kakakakakku.hatenablog.com

相互情報量 ✏️

まず,特徴量とラベルの関係を知るために「相互情報量 (Mutual Information)」を使う.よく Pandas の corr() 関数を使って「相関係数」を算出するけど,あくまで「相関係数」は2種類のデータが「線形的な」関係性のときに使う.「相互情報量」は線形的な関係性でなくても使える.簡単に表現すると「相互情報量」とは「A が B の不確実性を低減する尺度」と言える.

kakakakakku.hatenablog.com

そして scikit-learnfeature_selection モジュールには「相互情報量」を計算する関数として mutual_info_regression() : 回帰mutual_info_classif() : 分類 がある.

今回は Kaggle の住宅価格を予測する「住宅データセット」を使う.

www.kaggle.com

今回は住宅価格を予測する回帰問題であるため mutual_info_regression() を使って SalePrice(住宅価格) との「相互情報量」を計算する.以下にソートした結果を表示する.結果を読み解くと OverallQual(全体的なクオリティ)Neighborhood(地域内の場所)GrLivArea(リビングの広さ)YearBuilt(建設日) など,まぁ一般的に考えて重要そうな特徴量が上位になっている.なお「相互情報量」が低くても重要な特徴量はあるため,最終的には「ビジネス理解」が重要とも書いてあった.

OverallQual     0.581262
Neighborhood    0.569813
GrLivArea       0.496909
YearBuilt       0.437939
GarageArea      0.415014
TotalBsmtSF     0.390280
GarageCars      0.381467
FirstFlrSF      0.368825
BsmtQual        0.364779
KitchenQual     0.326194
ExterQual       0.322390
YearRemodAdd    0.315402
MSSubClass      0.287131
GarageFinish    0.265440
FullBath        0.251693
Foundation      0.236115
LotFrontage     0.233334
GarageType      0.226117
FireplaceQu     0.221955
SecondFlrSF     0.200658
Name: MI Scores, dtype: float64

特徴量抽出 ✏️

次に新しく特徴量を抽出する「計5種類」の手法を学ぶ,それぞれを以下に簡単にまとめる.

1. Create Mathematical Transforms(数学的変換)

例えば,GrLivArea(リビングの広さ)LotArea(ロットサイズ)から,新しく LivLotRatio(ロット比率) を計算する.

X_1["LivLotRatio"] = df.GrLivArea / df.LotArea

2. Interaction with a Categorical(カテゴリ変数)

例えば,BldgType(住居タイプ) を One-Hot エンコーディングをする.

X_2 = pd.get_dummies(df.BldgType, prefix="Bldg")

kakakakakku.hatenablog.com

3. Count Feature(カウント)

例えば,OpenPorchSF(オープンポーチエリア) など「Porch(建物の外壁から突き出している部分)」の総和を計算する.

X_3["PorchTypes"] = df[[
    "WoodDeckSF",
    "OpenPorchSF",
    "EnclosedPorch",
    "Threeseasonporch",
    "ScreenPorch",
]].gt(0.0).sum(axis=1)

4. Break Down a Categorical Feature(カテゴリ変数を区切る)

例えば,MSSubClass(販売に関係する住居タイプ) には One_and_Half_Story_Finished_All_AgesOne_Story_1946_and_Newer_All_Styles という「カテゴリ変数」が含まれているため _ で文字列分割をした1個目(今回で言えば One)を抽出する.バリエーションを減らすために使う.

X_4["MSClass"] = df.MSSubClass.str.split("_", n=1, expand=True)[0]

5. Use a Grouped Transform(グループ化)

例えば,Neighborhood(地域内の場所) でグループ化をした GrLivArea(リビングの広さ) の中央値を特徴量にする.カテゴリ変数の値によって差が出る場合に効果のある特徴量になりそう.

X_5["MedNhbdArea"] = df.groupby("Neighborhood")["GrLivArea"].transform("median")

K-means ✏️

次は「教師なし学習」の代表的なアルゴリズムである「K-means」を使ってクラスタリングを行う.今回は「5種類の特徴量」から「10種類のクラスタ」を探索する.クラスタリングの結果 0 ~ 9 の値を Cluster という新しい特徴量として追加している.以下はサンプルコードを抜粋して載せている.

X = df.copy()
y = X.pop("SalePrice")

features = [
    "LotArea",
    "TotalBsmtSF",
    "FirstFlrSF",
    "SecondFlrSF",
    "GrLivArea",
]

X_scaled = X.loc[:, features]
X_scaled = (X_scaled - X_scaled.mean(axis=0)) / X_scaled.std(axis=0)

kmeans = KMeans(n_clusters=10, n_init=10, random_state=0)
X["Cluster"] = kmeans.fit_predict(X_scaled)

また Seaborn で可視化をして,特徴量ごとにどのようにクラスタリングされているのかを確認することもできる.

f:id:kakku22:20211202141634p:plain

主成分分析 (PCA) ✏️

「主成分分析 (PCA)」「教師なし学習」の代表的なアルゴリズムで以下の目的で使うことができる.

  • Dimensionality reduction(次元削減)
  • Anomaly detection(異常検出)
  • Noise reduction(ノイズリダクション)
  • Decorrelation(非相関)

今回は以下の「4種類の特徴量」「主成分分析 (PCA)」を実行する.必ずしも次元削減をする必要はなく,データの特性(分散など)を計算することにも使える.今回は scikit-learnPCApca.fit_transform(X) を使う.

  • GarageArea(ガレージサイズ)
  • YearRemodAdd(リフォーム日)
  • TotalBsmtSF(地下室サイズ)
  • GrLivArea(リビングの広さ)

scikit-learn.org

そして,PC1 ~ PC4 という「4種類の特徴量」を抽出することができる.そして,以下のように「loading(主成分負荷量)」を表示すると,例えば,PC1「広さ」に着目した値になっているように考えることができる.また PC3GarageArea(ガレージサイズ)YearRemodAdd(リフォーム日)0 に近いため「リビングと地下室の関係性」 に着目した値になっているように考えることができる.

                   PC1       PC2       PC3       PC4
GarageArea    0.541229  0.102375 -0.038470  0.833733
YearRemodAdd  0.427077 -0.886612 -0.049062 -0.170639
TotalBsmtSF   0.510076  0.360778 -0.666836 -0.406192
GrLivArea     0.514294  0.270700  0.742592 -0.332837

f:id:kakku22:20211202152718p:plain

まとめ ✏️

「Kaggle Courses」「Feature Engineering」コースを受講した.「特徴量エンジニアリング」として使うテクニックなどを多岐にわたって学べてとても良かった.そして,改めて「ビジネス理解」が重要だなと感じることができた.また「主成分分析 (PCA)」の活用に関しては個人的にまだ理解が浅いため引き続き学んでいく!

f:id:kakku22:20211202142251p:plain

関連記事

現場の視点で機械学習に必要な知識を学べる「仕事ではじめる機械学習 第2版」を読んだ

「仕事ではじめる機械学習 第2版」を読んだ.実は「第1版」を買ってずっと積読をしていたところに「第2版」が出たので書い直した📖書名に「仕事ではじめる」と書いてある通り,現場の視点で理解しておくべき「機械学習」の知識がまとまっていて良かった.そして,本当に読みやすく挫折させないように工夫して書かれているのも素晴らしかった!読書メモを見返しながら書評記事をまとめる.

目次

「第I部」では幅広く知識を学び「第II部」ではケーススタディから学ぶ.流れるように最後まで読み進めることができる.「第1版」と比較すると「MLOps」「解釈性」など,重要なトピックが追加されているため,間違いなく「第2版」を読むべきだと思う.

また本書を読んでいて個人的に感じたのは「関連するけど詳しくは扱わないトピック」に関しては注釈などを使って関連情報(ドキュメントやブログなど)にうまく誘導していた.よって,興味があったら追加で調べることにより,本書を超えて知識を獲得できる仕組みになっている点も良かった.

  • 第I部
    • 1章 : 機械学習プロジェクトのはじめ方
    • 2章 : 機械学習で何ができる?
    • 3章 : 学習結果を評価するには
    • 4章 : システムに機械学習を組み込む
    • 5章 : 学習のためのリソースを収集する
    • 6章 : 継続的トレーニングをするための機械学習基盤
    • 7章 : 効果検証:機械学習にもとづいた施策の成果を判断する
    • 8章 : 機械学習のモデルを解釈する
  • 第II部
    • 9章 : Kickstarter の分析、機械学習を使わないという選択肢
    • 10章 : Uplift Modeling によるマーケティング資源の効率化
    • 11章 : バンディットアルゴリズムによる強化学習入門
    • 12章 : オンライン広告における機械学習

なお「第2版」の誤植は以下にまとまっている.載っていないものだと以下を発見した.

  • P.81
    • 真陽性率と縦軸、偽陽性率を横軸にプロット真陽性率を縦軸、偽陽性率を横軸にプロット

github.com

前提

本書の冒頭に「二冊目として活用する」と読者層が紹介されている.具体的には「Coursera : Machine Learning」を受講したり「ゼロから作る Deep Learning」を読んだ次に読むべしと書いてある.僕自身は「機械学習図鑑」「Pandas ライブラリ活用入門」を読んだり,Kaggle Courses を試したりしているため,ピッタリ読者層だった.実際に「第1版」を買って積読をしてしまった理由は「前提知識が足りなくて理解しにくかった」となるため,今はより自信を持って読めるようになったと感じる.

機械学習プロジェクト

1章「機械学習プロジェクトのはじめ方」は,技術職に限らず「機械学習に興味のある人なら」誰もが読むべき内容になっていた.機械学習を導入することを「目的」にせず,あくまで「手段」としてビジネスに活用するべきという視点が重要で「機械学習をしなくて良い方法を考える」というのはまさに!という感じだった.例えば,9章「Kickstarterの分析、機械学習を使わないという選択肢」も Excel でここまで深く分析できる!ということに気付かされる内容になっている.そして「システム設計を考える」では「具体的な目標性能と撤退ラインを決めておく」とも書いてある.サンクコストを考慮しつつ「撤退する判断」も現場では重要になりそうだと感じた.

  • ビジネス課題を機械学習の課題に定式化する
  • 類似の課題を、論文を中心にサーベイする
  • 機械学習をしなくて良い方法を考える
  • システム設計を考える
  • 特徴量、教師データとログの設計をする
  • 実データの収集と前処理をする
  • 探索的データ分析とアルゴリズムを選定する
  • 学習、パラメータチューニング
  • システムに組み込む

試して学べるサンプルコード

本書は読者を挫折させないように工夫して文章が書かれているだけではなく,図解や簡単なサンプルコードを使った解説も充実している.サンプルコードは GitHub に Jupyter Notebook として公開されているため,「1周目」は本書をザッと読んで「2周目」は気になったところを試しながら学ぶこともできる.

github.com

例えば,3章「学習結果を評価するには」では,分類と回帰それぞれの評価指標が解説されている.分類の評価指標として「F 値」以外に使うことができる「ROC 曲線と AUC」もあり,scikit-learn のサンプルコードを書き換えながら試行錯誤ができる.

import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve

#
# AUC = 0.8333333333333333
#

y_pass = [0, 0, 0, 1, 0, 0, 1, 0, 1, 1]
y_score = [0.1, 0.2, 0.4, 0.45, 0.5, 0.65, 0.7, 0.8, 0.85, 0.95]

fpr, tpr, thresholds = roc_curve(y_pass, y_score)

plt.plot(fpr, tpr, marker='o')
plt.xlabel('FPR: False Positive Rate')
plt.ylabel('TPR: True Positive Rate')
plt.grid()

#
# AUC = 1.0
#

y_perfect = [0, 0, 0, 0, 1, 1, 1, 1, 1, 1]
y_score_perfect = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]

fpr, tpr, _ = roc_curve(y_perfect, y_score)

plt.plot(fpr, tpr, marker='o')
plt.xlabel('FPR: False Positive Rate')
plt.ylabel('TPR: True Positive Rate')
plt.grid()

f:id:kakku22:20211128192027p:plain f:id:kakku22:20211128192036p:plain

MLOps

「第2版」で追加された6章「継続的トレーニングをするための機械学習基盤」では,最近特によく聞くようになった MLOps という概念を学べる.DevOps などの文脈で出てくる CI (Continuous Integration) / CD (Continuous Delivery or Continuous Deployment) に CT (Continuous Training) を追加することで,機械学習プロジェクトを継続的に進めるための心得を学べる.また本書では,以下のドキュメントで紹介されている MLOps のレベルをよりわかりやすく整理されている.

  • 共通の実験環境
  • 予測結果のサービング
  • 学習,予測共通の処理のパイプライン化
  • モデルの継続的トレーニング・デプロイ

cloud.google.com

そして,MLOps を実現するために使えるツールの選択肢は多岐にわたるため,本書では概要レベルでたくさん紹介されていた.一部を抜粋すると,Amazon SageMaker / MLflow / Hydra / Apache Airflow / Kubeflow など(正確には並列に列挙するのは正しくないけど).このあたりは興味のあるところから入門したいと思う.ツールの選択肢をポインタとして知ることができただけでも価値があった.

解釈性

同じく「第2版」で追加された8章「機械学習のモデルを解釈する」では,「Explainable AI(説明可能な AI)」とも言われるトピックで「解釈性(どの特徴量が寄与していたのか?など)」をサンプルコードを使って体験できる.今回は Kaggle に公開されている「退職予測データセット」を使って「線形回帰(回帰係数と切片)」「決定木」「ランダムフォレスト」それぞれで解釈性を可視化する方法を学べる.

IBM HR Analytics Employee Attrition & Performancewww.kaggle.com

例えば「ランダムフォレスト」では SHAP を使って(解釈可視化ツール)を使って,「解釈性」を可視化する.

github.com

以下を見ると,例えば「OverTime_Yes(残業の有無)」「MonthlyIncome(月収の低さ)」が影響していることを確認できたりする.

f:id:kakku22:20211128192016p:plain

まとめ

「仕事ではじめる機械学習 第2版」を読んだ.ザッと「1周目」を読んでから「2周目」ではサンプルコードを試したりしていた.並行して Kaggle Courses なども活用しつつ,理解度が上がってきたため,読書メモを見返しながら書評記事にまとめた.この記事では紹介しきれないほどに素晴らしい一冊なので是非読んでみると良いのではないでしょうか!

train_test_split() の stratify パラメータを使って層化サンプリングをする

データセットを分割するときに scikit-learntrain_test_split() をよく使う.今回は train_test_split() に設定できる stratify パラメータを試す.stratify「層化」という意味で「データセットの特性を考慮した分割」とも言える.特に「不均衡データセット」を使うときに重要になる.

scikit-learn.org

train_test_split() をデフォルト設定で使う

train_test_split() のデフォルト設定を抜粋すると以下のようになる.stratify はデフォルトで None になる.

  • train_size = 0.75(トレーニングデータ 75 %)
  • test_size = 0.25(テストデータ 25 %)
  • shuffle = True(ランダムに分割する)
  • stratify = None(層化なし)

例として,scikit-learn に組み込まれた「ワインデータセット🍷」を使う.正解ラベルは 012「計3種類」あり,それぞれの分布は以下のようになっている.ワインデータセットは不均衡ではないけど,検証には使える.

  • 178 件
    • 正解ラベル 0 : 59 件(33.1 %)
    • 正解ラベル 1 : 71 件(39.8 %)
    • 正解ラベル 2 : 48 件(26.9 %)

scikit-learn.org

train_test_split(X, y) のように「デフォルト設定のまま」実行するように以下のコードを書いた.3回実行したところ「正解ラベル 0」だと 0.34 %0.32 %0.36 % と推移した.もし不均衡データセットを使うと,より顕著に差が出る可能性がある.

import numpy as np
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split

wine = load_wine()
X = wine.data
y = wine.target

def calc(i, y):
        print(str(i+1) + '回目 (' + str(len(y_train)) + '件)')
        print('・Label 0 : ' + str(np.round((y == 0).sum()/len(y), 2)) + ' %')
        print('・Label 1 : ' + str(np.round((y == 1).sum()/len(y), 2)) + ' %')
        print('・Label 2 : ' + str(np.round((y == 2).sum()/len(y), 2)) + ' %')
        print('---')

for i in range(3):
    X_train, X_test, y_train, y_test = train_test_split(X, y)
    calc(i, y_train)

# 1回目 (133件)
# ・Label 0 : 0.34 %
# ・Label 1 : 0.38 %
# ・Label 2 : 0.28 %
# ---
# 2回目 (133件)
# ・Label 0 : 0.32 %
# ・Label 1 : 0.41 %
# ・Label 2 : 0.27 %
# ---
# 3回目 (133件)
# ・Label 0 : 0.36 %
# ・Label 1 : 0.36 %
# ・Label 2 : 0.28 %
# ---

train_test_split()stratify パラメータを使う

次にtrain_test_split(X, y, stratify=y) のように stratify パラメータを使って実行する.正解ラベル y を前提に「層化」するため,分布を維持しながらデータセットを分割することができる.なるほど💡

for i in range(3):
    X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y)
    calc(i, y_train)

# 1回目 (133件)
# ・Label 0 : 0.33 %
# ・Label 1 : 0.4 %
# ・Label 2 : 0.27 %
# ---
# 2回目 (133件)
# ・Label 0 : 0.33 %
# ・Label 1 : 0.4 %
# ・Label 2 : 0.27 %
# ---
# 3回目 (133件)
# ・Label 0 : 0.33 %
# ・Label 1 : 0.4 %
# ・Label 2 : 0.27 %
# ---

まとめ

今回は scikit-learntrain_test_split()「層化サンプリング」ができる stratify パラメータを試した.覚えておこう!

scikit-learn の Pipeline を使って前処理やアルゴリズムをまとめて宣言する

scikit-learnPipeline を使うと,データセットの前処理や機械学習アルゴリズムなどを「1つのオブジェクトに」まとめることができる.

scikit-learn.org

前回の記事で紹介した「Kaggle Courses」「Intermediate Machine Learning」コースでも使われていたこともあり,もう少しドキュメントを読みながら試していく.

kakakakakku.hatenablog.com

Pipeline に入門する

「Kaggle Courses」のコースを参考にサンプルコードを書くと,以下のようになる(重要なコードのみを抜粋している).Pipeline を使わずにコードを書くと SimpleImputerOneHotEncoderRandomForestRegressor など,それぞれで fit()fit_transform() を実行する必要があるため,コード量を減らしつつ,可読性が高く実装できる.イイネ👏

from sklearn.ensemble import RandomForestRegressor
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder

X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.8, test_size=0.2, random_state=0)

pipeline = Pipeline(
    steps=[
        ('imputer', SimpleImputer(strategy='most_frequent')),
        ('encoder', OneHotEncoder(handle_unknown='ignore')),
        ('model', RandomForestRegressor(n_estimators=50, random_state=0))
    ]
)

pipeline.fit(X_train, y_train)

pipeline.predict(X_test)

また Pipelineverbose=True パラメータを追加すると fit() を実行しながら以下のように進捗を表示することもできる.

pipeline = Pipeline(
    steps=[
        ('imputer', SimpleImputer(strategy='most_frequent')),
        ('encoder', OneHotEncoder(handle_unknown='ignore')),
        ('model', RandomForestRegressor(n_estimators=50, random_state=0))
    ],
    verbose=True
)

# [Pipeline] ........... (step 1 of 3) Processing imputer, total=   0.1s
# [Pipeline] ........... (step 2 of 3) Processing encoder, total=   0.1s
# [Pipeline] ............. (step 3 of 3) Processing model, total=  10.1s

そして,以下のドキュメントを参考に set_config() を使って「ダイアグラム表示」を有効化すると,以下のように Pipeline 構成を図として表示できる.実際には枠をクリックすることができて,パラメータを表示したり,非表示にしたりできる.

scikit-learn.org

from sklearn import set_config
set_config(display='diagram')
pipeline

f:id:kakku22:20211123175045p:plain

Pipeline で前処理をカテゴライズする

さらに Pipeline の中で前処理をカテゴライズすることもできる.例えば,以下のように numeric_preprocessor(量的変数)categorical_preprocessor(カテゴリ変数) としてまとめている(重要なコードのみを抜粋している).最終的には make_pipeline() を使って RandomForestRegressor とまとめている.今回のサンプルコード以上に前処理などが多いときに効果が出そう.

scikit-learn.org

from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestRegressor
from sklearn.impute import SimpleImputer
from sklearn.pipeline import make_pipeline
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

numeric_preprocessor = Pipeline(
    steps=[
        ('imputer', SimpleImputer(strategy='most_frequent')),
        ('scaler', StandardScaler()),
    ]
)

categorical_preprocessor = Pipeline(
    steps=[
        ('onehot', OneHotEncoder(handle_unknown='ignore')),
    ]
)

pipeline = make_pipeline(
    ColumnTransformer(
        [
            ('numerical', numeric_preprocessor, numerical_cols),
            ('categorical', categorical_preprocessor, categorical_cols)
        ]
    ),
    RandomForestRegressor(n_estimators=50, random_state=0)
)

同じく set_config() を使って「ダイアグラム表示」を有効化すると,より整理された Pipeline 構成を表示することができた.

from sklearn import set_config
set_config(display='diagram')
pipeline

f:id:kakku22:20211124095951p:plain

まとめ

scikit-learnPipeline を使うと,データセットの前処理や機械学習アルゴリズムなどを「1つのオブジェクトに」まとめることができる.今回はシンプルな Pipeline 構成とカテゴライズをした構成を紹介した.ドキュメントを読むと,他にも GridSearchCV と組み合わせてハイパーパラメータを探索することもできて,便利だった.

scikit-learn.org