PLS Regression

Basic

การถดถอยแบบ Partial Least Squares (PLS) | สร้างปัจจัยแฝงที่มุ่งช่วยการพยากรณ์

まとめ
  • PLS ดึงปัจจัยแฝงที่ทำให้ความแปรปรวนร่วมระหว่างตัวแปรอธิบายและตัวแปรเป้าหมายสูงสุด แล้วใช้ปัจจัยเหล่านั้นในการถดถอย จึงเป็นการลดมิติที่มีผู้สอน
  • ต่างจาก PCA ที่สนใจเฉพาะตัวแปรอธิบาย PLS เรียนรู้แกนที่สะท้อนเป้าหมาย จึงลดมิติได้โดยไม่เสียสมรรถนะการพยากรณ์
  • การเลือกจำนวนปัจจัยแฝงให้เหมาะช่วยแก้ปัญหาความสัมพันธ์เชิงเส้นพหุคูณที่รุนแรงได้
  • เมื่อดู loading plots จะเห็นได้ว่าฟีเจอร์ใดรวมตัวกันเพื่อมีผลกับเป้าหมาย ทำให้อธิบายแก่ผู้ใช้งานได้ง่าย

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

การถดถอยด้วยองค์ประกอบหลักเลือกแกนจากความแปรปรวนของตัวอธิบายเพียงอย่างเดียว จึงเสี่ยงทิ้งทิศทางที่ช่วยทำนาย PLS จึงสร้างปัจจัยแฝงโดยดูทั้งตัวอธิบายและเป้าหมายพร้อมกัน แล้วทำการถดถอยบนปัจจัยเหล่านี้ ทำให้ใช้ปัจจัยเพียงไม่กี่ตัวแต่ยังคงคุณภาพการทำนาย

สูตรสำคัญ #

ให้เมทริกซ์ตัวอธิบาย \(\mathbf{X}\) และเวกเตอร์เป้าหมาย \(\mathbf{y}\) เราอัปเดตคะแนนแฝง \(\mathbf{t} = \mathbf{X}\mathbf{w}\) และ \(\mathbf{u} = \mathbf{y} c\) สลับกันเพื่อเพิ่มความแปรปรวนร่วม \(\mathbf{t}^\top \mathbf{u}\) ให้สูงสุด ทำซ้ำหลายรอบเพื่อได้ปัจจัยแฝงหลายตัว แล้วทำการถดถอยเชิงเส้นบนปัจจัยเหล่านี้ หากใช้ \(k\) ปัจจัย สมการพยากรณ์จะมีรูป

$$ \hat{y} = \mathbf{t}\boldsymbol{b} + b_0 $$

จำนวนปัจจัยเหมาะสมมักเลือกโดย cross-validation

ทดลองด้วย Python #

โค้ดด้านล่างใช้ชุดข้อมูล Linnerud (ข้อมูลการออกกำลังกาย) เพื่อดูประสิทธิภาพของจำนวนปัจจัยแฝงต่างๆ

from __future__ import annotations

import japanize_matplotlib
import matplotlib.pyplot as plt
import numpy as np
from sklearn.cross_decomposition import PLSRegression
from sklearn.datasets import load_linnerud
from sklearn.model_selection import KFold, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler


def evaluate_pls_latent_factors(
    cv_splits: int = 5,
    xlabel: str = "Number of latent factors",
    ylabel: str = "CV MSE (lower is better)",
    label_best: str = "best={k}",
    title: str | None = None,
) -> dict[str, object]:
    """Cross-validate PLS regression for different latent factor counts.

    Args:
        cv_splits: Number of folds for cross-validation.
        xlabel: Label for the number-of-factors axis.
        ylabel: Label for the cross-validation error axis.
        label_best: Format string for the best-factor annotation.
        title: Optional plot title.

    Returns:
        Dictionary with the selected factor count, CV score, and loadings.
    """
    japanize_matplotlib.japanize()
    data = load_linnerud()
    X = data["data"]
    y = data["target"][:, 0]

    max_components = min(X.shape[1], 6)
    components = np.arange(1, max_components + 1)
    cv = KFold(n_splits=cv_splits, shuffle=True, random_state=0)

    scores = []
    pipelines = []
    for k in components:
        model = Pipeline(
            [
                ("scale", StandardScaler()),
                ("pls", PLSRegression(n_components=int(k))),
            ]
        )
        cv_score = cross_val_score(
            model,
            X,
            y,
            cv=cv,
            scoring="neg_mean_squared_error",
        ).mean()
        scores.append(cv_score)
        pipelines.append(model)

    scores_arr = np.array(scores)
    best_idx = int(np.argmax(scores_arr))
    best_k = int(components[best_idx])
    best_mse = float(-scores_arr[best_idx])

    best_model = pipelines[best_idx].fit(X, y)
    x_loadings = best_model["pls"].x_loadings_
    y_loadings = best_model["pls"].y_loadings_

    fig, ax = plt.subplots(figsize=(8, 4))
    ax.plot(components, -scores_arr, marker="o")
    ax.axvline(best_k, color="red", linestyle="--", label=label_best.format(k=best_k))
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    if title:
        ax.set_title(title)
    ax.legend()
    fig.tight_layout()
    plt.show()

    return {
        "best_k": best_k,
        "best_mse": best_mse,
        "x_loadings": x_loadings,
        "y_loadings": y_loadings,
    }


metrics = evaluate_pls_latent_factors(
    xlabel="จำนวนปัจจัยแฝง",
    ylabel="CV MSE (ยิ่งต่ำยิ่งดี)",
    label_best="k ที่ดีที่สุด = {k}",
    title="เลือกจำนวนปัจจัยแฝงสำหรับ PLS",
)
print(f"จำนวนปัจจัยแฝงที่เหมาะสม: {metrics['best_k']}")
print(f"ค่า CV MSE ที่ดีที่สุด: {metrics['best_mse']:.3f}")
print("โหลดดิงของ X:\n", metrics["x_loadings"])
print("โหลดดิงของ y:\n", metrics["y_loadings"])

เปรียบเทียบประสิทธิภาพของจำนวนปัจจัยแฝงใน PLS

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

  • CV MSE มักจะลดลงเมื่อเพิ่มจำนวนปัจจัย แล้วเริ่มเพิ่มขึ้นเมื่อเกินจุดที่เหมาะสม สามารถใช้กราฟเพื่อเลือกจุดสมดุล
  • x_loadings_ และ y_loadings_ แสดงว่าฟีเจอร์ใดมีผลต่อปัจจัยแฝง จึงอธิบายให้ผู้ใช้เข้าใจได้ง่าย
  • การทำมาตรฐานช่วยให้ฟีเจอร์ต่างสเกลอยู่ร่วมกันบนปัจจัยเดียวได้อย่างสมดุล

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

  • Wold, H. (1975). Soft Modelling by Latent Variables: The Non-Linear Iterative Partial Least Squares (NIPALS) Approach. In Perspectives in Probability and Statistics. Academic Press.
  • Geladi, P., & Kowalski, B. R. (1986). Partial Least-Squares Regression: A Tutorial. Analytica Chimica Acta, 185, 1 E7.