ロジスティック回帰と「最尤」という考え方 ― 分類は確率を出すこと
分類とは確率を出すこと、という視点からロジスティック回帰を作り直します。シグモイド、尤度と最尤推定の直感、交差エントロピー=負の対数尤度、そして勾配降下法の最小限。Titanicで確認し、SOLNAの初回CV確率へ移します。最適化の土台を作る回です。
by Shin
分類モデルが返す「確率」は、どこから来てどう学習されるのか。答えは、シグモイドで確率を出し、その確率の尤もらしさ(尤度)を最大化する重みを、勾配降下で反復的に探す、というものです。この回では、最尤推定と勾配降下を Titanic でスクラッチ実装して掴み、同じ発想を実務データの初回コンバージョン確率へ移します。
前回(第3回)は線形回帰を最小二乗から作り直し、正規方程式という「一発で解ける解析解」を見ました。今回はその対になる話です。ロジスティック回帰には解析解がありません。だからこそ、機械学習の大半を支える「反復で解く」という作法(最適化)の入口になります。LogisticRegression().fit() を写経で回すことと、その一行が最尤推定を勾配降下で解いていると言えることの差――それがこの回の主題です。
分類=確率を出す(シグモイドの役割)
回帰は実数を返しますが、分類で欲しいのは「陽性である確率」、つまり 0 から 1 の値です。第3回の線形結合 をそのまま使うと値は実数全体に散らばるので、これを確率の範囲に押し込む関数が要ります。それがシグモイドです。
シグモイドは S 字型で、 で 、 が大きいほど 1 に、小さいほど 0 に漸近します。逆に見ると、 は確率のオッズの対数(log-odds)です。すなわち 。線形モデルが直接予測しているのは確率そのものではなく「対数オッズ」で、それをシグモイドが確率へ翻訳している、と読むのが解読です。
ここで一つ、連載を通じて繰り返す誤解を置いておきます。「精度が高い=役に立つ」ではありません。モデルが返すのは 0.7 のような確率であって、0/1 のラベルではありません。0.7 を 1 に丸めた瞬間に意思決定の情報が落ちます。確率をそのまま使う(閾値を業務に合わせて決める)話は、第7回の較正——予測確率を実際の発生率に合わせる作業——で詰めます。
尤度と最尤推定(コインの例→ロジスティック)
「重みをどう決めるか」を、最尤推定(MLE)という一本の原理で説明します。発想はシンプルで、観測されたデータが最も起こりやすくなるパラメータを選ぶ、というものです。
コイン投げが分かりやすい例です。 回投げて表が 回出たとき、表が出る確率を とすると、その並びが起きる尤もらしさ(尤度)は次の形になります。
これを最大にする を求めると 、つまり「観測した表の割合」になります。直感どおりの答えですが、大事なのは答えではなく、割合を「データを最もよく説明するパラメータ」として導いた手続きの方です。確率・尤度の素地が怪しいと感じたら、赤本『統計学入門』の確率・推定の章で土台を固められます。
ロジスティック回帰は、このコインを一人ずつ別の確率で投げる話に拡張したものです。サンプル の陽性確率を とし、ラベル が観測されたとすると、全データの尤度は各サンプルの寄与の積になります。
なら が、 なら が掛かる、という指数の使い分けに注目してください。これを最大にする を探すのが、ロジスティック回帰の学習です。
交差エントロピー損失=負の対数尤度
実装では尤度の積を直接扱いません。積は桁あふれしやすく微分も面倒なので、対数を取って和に変えます。対数尤度は次の形です。
符号を反転して件数で割ったこの式は、深層学習の教科書で「交差エントロピー損失」あるいは「logloss」と呼ばれているものと同一です。つまり、よく見る損失関数の正体は、対数尤度の符号を裏返しただけです。尤度を最大化することと交差エントロピーを最小化することは、完全に同じ作業を別の言葉で言っているにすぎません。sklearn の LogisticRegression が内部で最小化しているのもこの損失で、第7回で評価指標として再登場します。
勾配降下法の最小限(解析解が無いとき反復で解く)
第3回の線形回帰は、正規方程式という連立一次方程式を解くだけで重みが一発で求まりました(解析解)。ロジスティック回帰にはこれがありません。シグモイドが入って損失が について非線形になり、微分してゼロと置いても閉じた式で解けないからです。
そこで、解を一歩ずつ近づける反復法を使います。損失を で微分した勾配は、シグモイドの性質のおかげで驚くほど素直な形になります。
は「予測確率と正解ラベルのズレ」です。勾配はそのズレを特徴量で重み付けして集めたものになります。あとは、勾配が指す向きは損失が増える方向なので、その逆向きに少しずつ動かせば損失は下がります。
これを Titanic でスクラッチ実装します。他者の公開ノートは貼らず、要点だけを最小再実装したものです。
import numpy as np
import pandas as pd
def sigmoid(z):
# z が大きいと exp がオーバーフローするので素朴版。実務は clip する
return 1.0 / (1.0 + np.exp(-z))
def fit_logistic(X, y, lr=0.1, n_iter=5000):
n, d = X.shape
w = np.zeros(d) # 係数(最初はゼロから)
b = 0.0 # 切片
for _ in range(n_iter):
p = sigmoid(X @ w + b) # 予測確率
grad_w = X.T @ (p - y) / n # ∇L = Xᵀ(p − y)/n
grad_b = (p - y).mean()
w -= lr * grad_w # 勾配の逆向きに一歩
b -= lr * grad_b
return w, b勾配降下は特徴量の尺度に敏感です。第2回で触れたとおり、線形・距離系のモデルでは標準化してから渡します(木系のモデルでは不要)。スケールを揃えないと、桁の大きい列に学習率が引っ張られて収束が極端に遅くなります。
df = pd.read_csv("train.csv") # 公開データ。同じノートを再現できる粒度
df["Sex"] = (df["Sex"] == "male").astype(int)
df["Age"] = df["Age"].fillna(df["Age"].median()) # 注意: 本来は CV 内で算出(第2回)
feat = ["Pclass", "Sex", "Age", "Fare"]
X = df[feat].to_numpy(dtype=float)
y = df["Survived"].to_numpy(dtype=float)
X = (X - X.mean(0)) / X.std(0) # 勾配降下のため標準化
w, b = fit_logistic(X, y, lr=0.1, n_iter=5000)
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression(penalty=None, max_iter=5000).fit(X, y)
# スクラッチの w と clf.coef_ がほぼ一致することを確認する(=同じ最尤を解いている)ここが写経と解読の分岐点です。clf.fit() の一行は、写経すれば誰でも動きます。けれど、それが「交差エントロピー=負の対数尤度を、勾配降下で最小化して重みを探している」と言え、スクラッチの結果と突き合わせて一致を確認できるなら、その一行は解読できています。
なお、係数の符号を見て「この変数が効いている」と読みたくなりますが、ここで止めてください。係数は相関の方向であって因果ではありません。「重要度が高い=因果がある」ではない、という連載通底の注意で、深掘りは因果編に譲ります。
学習率という最初のハイパーパラメータ
更新式の (学習率)が、本連載で最初に出会う調整つまみです。大きすぎると一歩が行き過ぎて損失が振動・発散し、小さすぎると収束に膨大な反復が要ります。最適化の良し悪しが、モデルの良し悪しの前に立ちはだかる、という事実をここで体感しておくと、第6回・第8回のチューニングの話が地続きになります。
実務では、学習率を手で探すより、損失が反復とともに単調に下がっているかを監視するのが定石です。下がっていなければ、学習率か標準化のどちらかを疑います。
SOLNAへ ― 初回CV確率の推定とクラス不均衡
公開データで原理を確認したので、同じ手法を過去の受託案件をもとに設計した架空のD2CブランドSOLNAに移します。題材は「あるセッションが初回注文に繋がるか」の確率推定です。sessions を主テーブルに、customers と orders を結合してセッション単位の学習データを作ります(最小構成・BigQuery 想定)。
-- SOLNA: セッション単位で「初回CVしたか」を作る(最小構成)
-- リーク注記: 予測時点で手に入る特徴量だけを使う(未来情報を入れない)
WITH first_orders AS (
SELECT customer_id, MIN(order_at) AS first_order_at
FROM orders
WHERE order_type = 'first'
GROUP BY customer_id
)
SELECT
s.session_id,
s.channel,
s.landing_page,
s.pageviews,
s.product_views,
s.engaged,
-- ラベル: セッション開始から24時間以内に初回注文があれば 1
IF(fo.first_order_at BETWEEN s.session_start
AND TIMESTAMP_ADD(s.session_start, INTERVAL 24 HOUR), 1, 0) AS converted
FROM sessions s
LEFT JOIN customers c ON c.customer_id = s.customer_id
LEFT JOIN first_orders fo ON fo.customer_id = s.customer_id
-- 初回CVの母集団に合わせ、既に初回購入済みのセッションは除外
WHERE c.first_order_at IS NULL OR s.session_start < c.first_order_atここから先で、公開データには無い「実務だけの崩れ」が三つ出ます。
第一にデータリークです。converted を作るために未来の注文情報を参照していますが、これはラベル作成のための参照であって、特徴量に未来情報を混ぜてはいけません。たとえば「そのセッション後の総ページ閲覧数」のような、予測時点では確定していない値を特徴量に入れると、検証では高精度に見えて本番で崩れます。前処理(補完・エンコード)も第2回のとおり CV の内側で完結させます。
第二にクラス不均衡です。初回CV率はおおむね数パーセント(レンジ: 1〜5% 程度・匿名化)で、陽性が極端に少ない。ここで accuracy を見ると、「全部CVしない」と予測するだけで 95% 超になり、しかし一件も当てていない、という事故が起きます。「精度が高い≠役に立つ」が最も鮮明に出る場面です。
from sklearn.linear_model import LogisticRegression
# 不均衡対策: 少数クラスの重みを上げる。評価は accuracy を見ない
clf = LogisticRegression(class_weight="balanced", max_iter=1000)
# 評価は logloss / AUC(順位付けの指標・第7回で詳述)で行い、閾値は意思決定(通知頻度・営業負荷)に合わせて決める(第7回)第三に分布シフトです。Kaggle のデータは固定ですが、SOLNA の初回CV率は季節・キャンペーン・流入構成で動きます。コンペで効いたモデルが実務で劣化する典型で、「コンペで効く≠実務で効く」という非対称そのものです。学習時の分布と推論時の分布がずれていないかは、運用で監視する論点として第8回で扱います。
PRMLへの一歩
この回は PRML 4章(線形識別モデル)の入口です。シグモイドによる確率出力とロジスティック回帰は 4.3 に対応します。そして「観測データを最もよく説明するパラメータを選ぶ」という最尤の考え方は、PRML 1.2(確率論)と 1.2.5(曲線フィッティングの最尤的再解釈)で串刺しにできます。第3回の最小二乗も、ガウスノイズを仮定すれば最尤推定として読み直せる――この視点は次回以降で効いてきます。
章末チェックリスト
- 分類を確率推定として説明できる(シグモイドが対数オッズを確率へ翻訳している)
- 尤度と最尤推定の関係を、コインの例から言える
- 交差エントロピー=負の対数尤度を理解した
- 解析解が無いとき反復で解く理由と、学習率の役割を言える
- スクラッチと
sklearnを突き合わせ、同じ最尤を解いていると確認した - 不均衡で
accuracyが誤誘導すること、リーク・分布シフトの注意を言える
ここまでが「分類は確率を出すこと」という解読です。出した確率をどの指標で測り、どの閾値で意思決定に変えるかは、何を測るべきかという上流の判断と地続きです。その全体像は記述・予測編を合わせてどうぞ。なお「係数が大きい=効く要因」という読みは相関であって因果ではありません。その線引きは因果編に譲ります。次回は、公開ノートに必ずある交差検証と正則化の意味を詰めます。