理論も理解しないでデータ解析するんですか??(機械学習チートシート)
機械学習チートシート
対象読者:機械学習に関する講義を履修済みの大学生や社会人の方
数式の導出過程に重きを置いていますが、細かい説明や背景は省いているので、これを最初の学習用としないでください
参考までにPythonでのデータ解析用コードを記しています
概要
教師あり学習
- 入力データと出力データから関数のパラメータを予測
教師なし学習
- 入力データだけから関数のパラメータを予測
強化学習
- 与えられた状態において報酬を最大化する行動を推定
データ前処理
標準化
- 平均が0、標準偏差が1となるように変換
- 単位や尺度の影響を取り除く
from sklearn.preprocessing import StandardScaler
import numpy as np
X = np.array([[10, 20], [30, 40], [50, 60]])
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
print("標準化前:\n", X)
print("標準化後:\n", X_scaled)
多重共線性
- 説明変数同士の相関関係が高く、パラメータの推定が不安定になる
過学習
- 学習データに過剰に適合し、汎化性能が低下する
回帰(Regression)
最小二乗法
- 以下の損失関数を最小化
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_regression
X, y = make_regression(n_samples=100, n_features=1, noise=10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
model = LinearRegression()
model.fit(X_train, y_train)
print("係数:", model.coef_)
print("切片:", model.intercept_)
print("決定係数 R2:", model.score(X_test, y_test))
正則化回帰
- 訓練データへの過剰な適合を防ぐため、パラメータの大きさにペナルティを与える
Ridge回帰(L2正則化)
- 単位行列は正則化するパラメータに対応する対角成分のみ1とする
- 対角成分を足してランク落ちを防ぐことで、逆行列を安定に計算
Lasso回帰(L1正則化)
-
の一部を0にする(特徴量選択)\mathbf{w} - 解析的に解けないため、数値計算を行う
# Ridge回帰
from sklearn.linear_model import Ridge
ridge = Ridge(alpha=1.0)
ridge.fit(X_train, y_train)
print("Ridge回帰 R2:", ridge.score(X_test, y_test))
# Lasso回帰
from sklearn.linear_model import Lasso
lasso = Lasso(alpha=0.1)
lasso.fit(X_train, y_train)
print("Lasso回帰 R2:", lasso.score(X_test, y_test))
print("Lassoで選択された特徴量:", lasso.coef_)
評価指標
MSE(Mean Squared Error)
MAE(Mean Absolute Error)
R^2 スコア(決定係数)
-
:R^2 = 1 y_i=\hat{y}_i
完全に説明できている -
:R^2 = 0 \hat{y}_i=\bar{y}
平均を使うのと同等
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
y_pred = model.predict(X_test)
print("MSE:", mean_squared_error(y_test, y_pred))
print("MAE:", mean_absolute_error(y_test, y_pred))
print("R2:", r2_score(y_test, y_pred))
ホールドアウト法
- データを訓練用と検証用にランダムに分割し、汎化性能を検証
from sklearn.model_selection import train_test_split
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)
交差検証法(Cross Validation)
- 元のデータから一つずつ検証用データとして選び、すべてのデータが訓練・検証に一度ずつ使われるように評価する
K-fold交差検証法
- データをK個の等しいサイズのサブセットに分割し、各サブセットを1回ずつ検証用データとし、残りを訓練用データに使ってK回モデルを評価
from sklearn.model_selection import KFold, cross_val_score
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
iris = load_iris()
X, y = iris.data, iris.target == 0
kf = KFold(n_splits=5, shuffle=True, random_state=1)
model = LogisticRegression()
scores = cross_val_score(model, X, y, cv=kf)
print("各Foldのスコア:", scores)
print("平均スコア:", scores.mean())
混同行列(Confusion Matrix)
| 真値\識別 | P | N |
|---|---|---|
| P | TP(True Positive) | FN(False Negative) |
| N | FP(False Positive) | TN(True Negative) |
- TP: 正しく陽性と判定された数(真陽性)
- TN: 正しく陰性と判定された数(真陰性)
- FP: 本当は陰性なのに陽性と判定された数(偽陽性)
- FN: 本当は陽性なのに陰性と判定された数(偽陰性)
Recall(再現率)
- 実際に陽性だったもののうち、陽性と正しく予測された割合
Precision(適合率)
- 陽性と予測したもののうち、実際に陽性だった割合
Accuracy(正解率)
- 全体のうち、正しく分類できた割合。
F1 スコア
- Precision と Recall の調和平均
from sklearn.metrics import confusion_matrix, classification_report
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
iris = load_iris()
X, y = iris.data, iris.target
# 2クラスに簡略化 (class 0 vs rest)
y_binary = (y == 0).astype(int)
X_train, X_test, y_train, y_test = train_test_split(X, y_binary, random_state=42)
model = LogisticRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print("Confusion Matrix:\n", confusion_matrix(y_test, y_pred))
print("Classification Report:\n", classification_report(y_test, y_pred))
ROC曲線・AUC
- ROC(Receiver Operating Characteristic)
横軸:False Positive Rate(FPR)
縦軸:True Positive Rate(TPR) - AUC:(Area Under the Curve)
ROC曲線の下の面積
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve, auc
y_prob = model.predict_proba(X_test)[:,1]
fpr, tpr, thresholds = roc_curve(y_test, y_prob)
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, label=f'ROC curve (area = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], linestyle='--')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend()
plt.show()
ロジスティック回帰
-
に対して、事後確率をシグモイド関数で表現:y \in \{0,1\}
from sklearn.linear_model import LogisticRegression
model = LogisticRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:, 1]
識別
最近傍法
- データのノイズに強く影響する
k-NN(k近傍法)
- 入力データに最も近い
個のサンプルを選び、多数決でクラスを決定k - 過学習を防止
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)
print("k-NN 正解率:", knn.score(X_test, y_test))
サポートベクトルマシン(SVM)
ハードマージン
決定境界:
ご分類なしの場合、ラベル
サポートベクトル
Mの最大化は
ソフトマージン
現実のデータは完全に線形分離できないので、スラック変数
カーネルトリック(非線形分離)
入力
双対問題の内積をカーネルに置き換えれば非線形SVMが得られる:
代表的なカーネル:
- 線形カーネル:
K(\mathbf{x_i}, \mathbf{x_j}) = \mathbf{x_i}^T \mathbf{x_j} - 多項式カーネル:
K(\mathbf{x_i}, \mathbf{x_j}) = (\mathbf{x_i}^T \mathbf{x_j} + c)^d - RBFカーネル:
K(\mathbf{x_i}, \mathbf{x_j}) = \exp\left(-\gamma{\|\mathbf{x_i} - \mathbf{x_j}\|^2}\right)
from sklearn.svm import SVC
svm = SVC(kernel='rbf', C=1.0, gamma='scale')
svm.fit(X_train, y_train)
print("SVM 正解率:", svm.score(X_test, y_test))
決定木
- 木構造を用いて、ルールに従った分類または回帰を行う
-
回の分岐でどのような入力データも分類可能\log_2N
ジニ不純度(Gini Imprity)
- ある領域に相当するノード
において誤分類する確率t - 誤分類がないとき0で最小、異なるクラスで同じ数だけ誤分類されるとき最大
- ジニ不純度の差分が最大となる境界値で分割
- 左右それぞれのノードにおけるジニ不純度を
,G(t_L) とすると、G(t_R)
剪定(Pruning)
-
決定木の部分木をいくつか削除することで過学習を防止
-
以下のコスト複雑度を最小化
from sklearn.tree import DecisionTreeClassifier
from sklearn import tree
import matplotlib.pyplot as plt
dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(X_train, y_train)
plt.figure(figsize=(12,8))
tree.plot_tree(dt, filled=True, feature_names=iris.feature_names, class_names=['Not class0','Class0'])
plt.show()
バイアス・バリアンス分解
- 第1項はバリアンス
- 第2項はバイアスの二乗
- バリアンスとバイアスはトレードオフの関係
アンサンブル学習
- 複数のモデル(弱学習器)を組み合わせて、精度の向上や過学習の抑制を図る
バギング
- バリアンスが大きく、バイアスが小さい弱学習器を集めてモデルを作成
- 無相関な決定木の多数決や平均により予測
- 元の訓練データから重複ありの復元抽出を行うブートストラップ法を行う
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier
bagging = BaggingClassifier(base_estimator=DecisionTreeClassifier(), n_estimators=10, random_state=42)
bagging.fit(X_train, y_train)
print("バギング 正解率:", bagging.score(X_test, y_test))
ブースティング
- バイアスが大きく、バリアンスが小さい弱学習器を集めてモデルを作成
from sklearn.ensemble import AdaBoostClassifier
ada = AdaBoostClassifier(n_estimators=50, random_state=42)
ada.fit(X_train, y_train)
print("AdaBoost 正解率:", ada.score(X_test, y_test))
ランダムフォレスト(Random Forest)
- バギングに説明変数のランダムサンプリングを加えることで、バリアンスを抑え、過学習を防ぐ
- 各決定木の分岐時に説明変数
個からランダムにp 個だけ選ぶ\sqrt{p}
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)
print("ランダムフォレスト 正解率:", rf.score(X_test, y_test))
クラスタリング
k-meansクラスタリング
- 各データ点
を\mathbf{x}_i 個のクラスタに分類する手法K
以下の損失関数を最小化:
$$
L(\mu_k) = \sum_{k=1}^{K} \sum_{\mathbf{x}_n \in C_k} | \mathbf{x}_i - \boldsymbol{\mu}_k |^2
$$
-
: クラスタC_k k -
: クラスタ\boldsymbol{\mu}_k の中心(Centroid)k - ランダムな初期値に依存するため、局所解に陥ることがある
from sklearn.cluster import KMeans
import numpy as np
X = np.array([[1, 2], [1, 4], [1, 0],
[4, 2], [4, 4], [4, 0]])
kmeans = KMeans(n_clusters=2, random_state=42)
kmeans.fit(X)
print("クラスタ割当:", kmeans.labels_)
print("クラスタ中心:", kmeans.cluster_centers_)
エルボー法
- 最適なクラスタ数
を選ぶ方法K - クラスタ数の増加に伴う損失関数の減少度合いが緩やかになるKを選択する
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
# サンプルデータ作成
X, _ = make_blobs(n_samples=300, centers=4, cluster_std=0.60, random_state=0)
# SSE (Within-Cluster Sum of Squares) の計算
sse = []
k_range = range(1, 11)
for k in k_range:
kmeans = KMeans(n_clusters=k, random_state=0)
kmeans.fit(X)
sse.append(kmeans.inertia_) # inertia_ はSSE
# プロット
plt.plot(k_range, sse, 'bx-')
plt.xlabel('クラスタ数 k')
plt.ylabel('SSE (Within-cluster sum of squares)')
plt.title('エルボー法によるクラスタ数決定')
plt.show()
生成モデル
- 観測データ
の母集団の確率分布をモデル化するx - モデルの表現力向上のため、潜在変数を導入
EMアルゴリズム(Expectation-Maximization Algorithm)
- 潜在変数を含む確率モデルのパラメータ推定に用いられる反復最適化手法
- Eステップ:隠れ変数の分布の期待値を計算
- Mステップ:期待値で得られた分布を用いてパラメータを最適化
- 局所最適に収束するが、大域最適とは限らない
\begin{aligned}
\ln p(\mathbf{x};\theta)
&= \sum_{\mathbf{z}} q(\mathbf{z}) \ln p(\mathbf{x};\theta) \\
&= \sum_{\mathbf{z}} q(\mathbf{z}) \ln \frac{p(\mathbf{x}, \mathbf{z};\theta)}{p(\mathbf{z}|\mathbf{x};\theta)} \\
&= \sum_{\mathbf{z}} q(\mathbf{z}) \ln \frac{p(\mathbf{x}, \mathbf{z};\theta)}{q(\mathbf{z})} \cdot \frac{q(\mathbf{z})}{p(\mathbf{z}|\mathbf{x};\theta)} \\
&= \sum_{\mathbf{z}} q(\mathbf{z}) \ln \frac{p(\mathbf{x}, \mathbf{z};\theta)}{q(\mathbf{z})} + \sum_{\mathbf{z}} q(\mathbf{z}) \ln \frac{q(\mathbf{z})}{p(\mathbf{z}|\mathbf{x};\theta)} \\
&= \underbrace{\mathcal{L}(q,\theta)}_{\text{ELBO}} + \underbrace{D(q\|p)}_{\text{KL - divergence}}
\end{aligned}
第二項は定数と見なせるため、第一項(完全データ対数尤度の期待値)を最大化する:
混合ガウス分布(GMM)
-
: 混合比\pi_k \sum_{k=1}^{K} \pi_k =1 -
: 多変量正規分布\mathcal{N}
import numpy as np
import matplotlib.pyplot as plt
from sklearn.mixture import GaussianMixture
from sklearn.datasets import make_blobs
# データ作成
X, y_true = make_blobs(n_samples=400, centers=3, cluster_std=0.60, random_state=42)
# GMM モデルの作成と学習
gmm = GaussianMixture(n_components=3, covariance_type='full', random_state=42)
gmm.fit(X)
# 各データ点のクラスタ予測
labels = gmm.predict(X)
# 結果の可視化
plt.scatter(X[:, 0], X[:, 1], c=labels, s=40, cmap='viridis')
plt.title("Gaussian Mixture Model (EMアルゴリズム)によるクラスタリング")
plt.show()
階層的クラスタリング(Hierarchical Clustering)
- クラスタ間距離が最も近いクラスタを段階的に統合
全てのペア
クラスタ間距離
単連結(Single Linkag)
完全連結(Complete Linkage)
平均連結(Average Linkage)
ウォード連結(Ward Linkage)
系統樹(dendrogram)
- 階層的クラスタリングにおける統合過程を視覚化
from scipy.cluster.hierarchy import dendrogram, linkage
import matplotlib.pyplot as plt
import numpy as np
X = np.array([[1, 2], [3, 4], [5, 6], [8, 8]])
Z = linkage(X, 'ward')
plt.figure(figsize=(6, 4))
dendrogram(Z)
plt.show()
主成分分析(Principal Component Analysis: PCA)
- 多次元データをより少ない次元に射影して、元のデータの分散をできるだけ保持する次元削減
中心化(平均0)したデータの
Lagrange未定乗数を導入:
このラグランジュ関数を
この固有値問題を解いて、最大の固有値に対応する固有ベクトルが第一主成分の射影方向
寄与率(Explained Variance Ratio)
- 各主成分がデータ全体の分散のうちどれだけを説明しているかを示す
累積寄与率(Cumulative Explained Variance Ratio)
from sklearn.decomposition import PCA
import numpy as np
X = np.random.rand(100, 5)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
print("寄与率:", pca.explained_variance_ratio_)
print("変換後データ形状:", X_pca.shape)
ニューラルネットワーク(Neural Networks)
- 多層のノードと活性化関数で構成されるモデル
活性化関数
- ReLU
$$
f(x) = \max(0, x)
$$ - Sigmoid
$$
f(x) = \frac{1}{1 + e^{-x}}
$$
出力は0から1の範囲
勾配消失問題あり - tanh
$$
f(x) = \tanh(x) = \frac{e^{x} - e^{-x}}{e^{x} + e^{-x}}
$$
出力範囲は -1 から 1
import torch
import torch.nn as nn
import torch.optim as optim
class SimpleNN(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(10, 5)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(5, 1)
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.fc2(x)
return x
model = SimpleNN()
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
# ダミーデータ
x = torch.randn(3, 10)
y = torch.randn(3, 1)
# 学習ステップ例
optimizer.zero_grad()
output = model(x)
loss = criterion(output, y)
loss.backward()
optimizer.step()
勾配降下法
- パラメータ
を損失関数w の勾配に沿って更新:L(w)
誤差伝播法
- 多層ネットワークで出力層から入力層へ誤差を逆伝播し、各パラメータの勾配を効率的に計算
確率勾配降下法
- データの一部をランダムにバッチとして選び、バッチを用いて勾配降下法を行う
- 局所解に陥ることを防ぐ
スキップ接続
- 深いネットワークで勾配消失を防ぐために、層をまたいで入力を後の層に直接渡す
- 出力
y = F(x) + x
Dropout
- 過学習防止のため、学習時にランダムに一部のニューロンの結合を削除
CNN(畳み込みニューラルネットワー)
- 画像や時系列データなどの局所特徴抽出に適したネットワーク
畳み込み演算(Convolution)
入力画像
パディング
- 畳み込み時に画像の端を拡張し、ゼロパディングなどを施すことで出力サイズを調整
ストライド
- フィルタをスライドさせる移動幅
- 出力サイズの調整や計算効率の効率化を行う
プーリング
- 特徴マップの空間サイズを縮小し、計算負荷を削減
- 最大値プーリング、平均値プーリングなどが用いられる
VAE(変分オートエンコーダ)
- 入力
を潜在変数x にエンコードし、デコードする生成モデルz
損失関数:
第一項:再構成誤差
第二項:正則化項
ベイズ推論
- パラメータ
に対する事後確率を計算して推論を行う:\theta
import torch.nn as nn
class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, padding=1)
self.relu = nn.ReLU()
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
self.fc = nn.Linear(16 * 16 * 16, 10) # 例: 32x32画像の場合
def forward(self, x):
x = self.pool(self.relu(self.conv1(x)))
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
Discussion