Orthogonal Matching Pursuit

Basic

Orthogonal Matching Pursuit (OMP) | เลือกสัมประสิทธิ์แบบเบาบางด้วยวิธีละโมบ

まとめ
  • OMP เป็นวิธีละโมบที่เลือกคุณลักษณะซึ่งมีสหสัมพันธ์สูงที่สุดกับส่วนที่เหลืออยู่ (residual) ทีละตัว ทำให้สร้างโมเดลเชิงเส้นที่สปาร์สได้
  • ทุกครั้งที่เลือกฟีเจอร์ใหม่ จะคำนวณคำตอบกำลังสองน้อยที่สุดเฉพาะฟีเจอร์ที่เลือกไว้ จึงตีความสัมประสิทธิ์ได้ตรงไปตรงมา
  • แทนที่จะกำหนดค่าการทำให้เป็นระเบียบ เรากำหนด “จำนวนฟีเจอร์ที่ต้องการคงไว้” ได้โดยตรง จึงควบคุมความสปาร์สได้อย่างชัดเจน
  • การทำมาตรฐานฟีเจอร์และลดการพึ่งพากันช่วยให้ได้คำตอบที่เสถียรยิ่งขึ้น

ภาพรวมเชิงสัญชาติญาณ #

เมื่อเรามีคุณลักษณะจำนวนมากแต่ต้องการใช้เพียงไม่กี่ตัว OMP จะเริ่มจาก residual ทั้งหมด แล้วเลือกฟีเจอร์ที่ลด residual ได้มากที่สุดครั้งละหนึ่งตัว ขั้นตอนนี้เป็นรากฐานของการเรียนรู้พจนานุกรมและการทำ sparse coding จึงมีประโยชน์เมื่ออยากอธิบายข้อมูลด้วยตัวแปรเพียงไม่กี่ตัว

สูตรสำคัญ #

ให้ residual เริ่มต้นเป็น \(\mathbf{r}^{(0)} = \mathbf{y}\) แล้วในรอบที่ \(t\) ทำดังนี้

  1. คำนวณสหสัมพันธ์ระหว่างแต่ละฟีเจอร์ \(\mathbf{x}_j\) กับ \(\mathbf{r}^{(t-1)}\) แล้วเลือกดัชนี \(j\) ที่มีค่าสัมบูรณ์มากที่สุด
  2. เพิ่ม \(j\) เข้าเซตรวมฟีเจอร์ที่เลือก \(\mathcal{A}_t\)
  3. คำนวณคำตอบวิธีกำลังสองน้อยที่สุดโดยใช้เฉพาะฟีเจอร์ใน \(\mathcal{A}t\) ได้ \(\hat{\boldsymbol\beta}{\mathcal{A}_t}\)
  4. อัปเดต residual เป็น \(\mathbf{r}^{(t)} = \mathbf{y} - \mathbf{X}_{\mathcal{A}t} \hat{\boldsymbol\beta}{\mathcal{A}_t}\)

ทำซ้ำจนกว่าจะเลือกครบจำนวนที่กำหนดหรือ residual มีค่าน้อยพอ

ทดลองด้วย Python #

ตัวอย่างต่อไปนี้สร้างข้อมูลที่มีสัมประสิทธิ์จริงแบบสปาร์ส แล้วเปรียบเทียบ OMP กับ Lasso

from __future__ import annotations

import japanize_matplotlib
import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import Lasso, OrthogonalMatchingPursuit
from sklearn.metrics import mean_squared_error


def run_omp_vs_lasso(
    n_samples: int = 200,
    n_features: int = 40,
    sparsity: int = 4,
    noise_scale: float = 0.5,
    xlabel: str = "feature index",
    ylabel: str = "coefficient",
    label_true: str = "true",
    label_omp: str = "OMP",
    label_lasso: str = "Lasso",
    title: str | None = None,
) -> dict[str, object]:
    """Compare OMP and lasso on synthetic sparse regression data.

    Args:
        n_samples: Number of training samples to generate.
        n_features: Total number of features in the dictionary.
        sparsity: Count of non-zero coefficients in the ground truth.
        noise_scale: Standard deviation of Gaussian noise added to targets.
        xlabel: Label for the coefficient plot x-axis.
        ylabel: Label for the coefficient plot y-axis.
        label_true: Legend label for the ground-truth bars.
        label_omp: Legend label for the OMP bars.
        label_lasso: Legend label for the lasso bars.
        title: Optional title for the bar chart.

    Returns:
        Dictionary containing recovered supports and MSE values.
    """
    japanize_matplotlib.japanize()
    rng = np.random.default_rng(0)

    X = rng.normal(size=(n_samples, n_features))
    true_coef = np.zeros(n_features)
    true_support = rng.choice(n_features, size=sparsity, replace=False)
    true_coef[true_support] = rng.normal(loc=0.0, scale=3.0, size=sparsity)
    y = X @ true_coef + rng.normal(scale=noise_scale, size=n_samples)

    omp = OrthogonalMatchingPursuit(n_nonzero_coefs=sparsity)
    omp.fit(X, y)
    lasso = Lasso(alpha=0.05)
    lasso.fit(X, y)

    omp_pred = omp.predict(X)
    lasso_pred = lasso.predict(X)

    fig, ax = plt.subplots(figsize=(10, 5))
    indices = np.arange(n_features)
    ax.bar(indices - 0.3, true_coef, width=0.2, label=label_true, color="#2ca02c")
    ax.bar(indices, omp.coef_, width=0.2, label=label_omp, color="#1f77b4")
    ax.bar(indices + 0.3, lasso.coef_, width=0.2, label=label_lasso, color="#d62728")
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    if title:
        ax.set_title(title)
    ax.legend()
    fig.tight_layout()
    plt.show()

    return {
        "true_support": np.flatnonzero(true_coef),
        "omp_support": np.flatnonzero(omp.coef_),
        "lasso_support": np.flatnonzero(np.abs(lasso.coef_) > 1e-6),
        "omp_mse": float(mean_squared_error(y, omp_pred)),
        "lasso_mse": float(mean_squared_error(y, lasso_pred)),
    }


metrics = run_omp_vs_lasso(
    xlabel="ดัชนีฟีเจอร์",
    ylabel="สัมประสิทธิ์",
    label_true="ค่าจริง",
    label_omp="OMP",
    label_lasso="Lasso",
    title="เปรียบเทียบสัมประสิทธิ์ของ OMP และ Lasso",
)
print("ดัชนีที่ไม่เป็นศูนย์ของค่าจริง:", metrics["true_support"])
print("ดัชนีที่ไม่เป็นศูนย์ของ OMP:", metrics["omp_support"])
print("ดัชนีที่ไม่เป็นศูนย์ของ Lasso:", metrics["lasso_support"])
print(f"MSE ของ OMP: {metrics['omp_mse']:.4f}")
print(f"MSE ของ Lasso: {metrics['lasso_mse']:.4f}")

วิเคราะห์ผลลัพธ์ #

  • ถ้ากำหนด n_nonzero_coefs ให้ตรงกับจำนวนสัมประสิทธิ์ที่แท้จริง OMP มักกู้คืนฟีเจอร์เป้าหมายได้ครบ
  • ต่างจาก Lasso ที่ยังมีสัมประสิทธิ์เล็กๆ หลงเหลือ OMP ให้ค่าศูนย์อย่างแท้จริงกับฟีเจอร์ที่ไม่ได้เลือก
  • หากฟีเจอร์มีความสัมพันธ์สูง การเลือกอาจไม่นิ่ง ดังนั้นการทำมาตรฐานและออกแบบฟีเจอร์ให้ดีเป็นเรื่องสำคัญ

เอกสารอ้างอิง #

  • Pati, Y. C., Rezaiifar, R., & Krishnaprasad, P. S. (1993). Orthogonal Matching Pursuit: Recursive Function Approximation with Applications to Wavelet Decomposition. In Conference Record of the Twenty-Seventh Asilomar Conference on Signals, Systems and Computers.
  • Tropp, J. A. (2004). Greed is Good: Algorithmic Results for Sparse Approximation. IEEE Transactions on Information Theory, 50(10), 2231 E242.