- SVR ใช้ ε-insensitive zone ทำให้ความคลาดเคลื่อนเล็กๆ ถือว่าเป็นศูนย์ ลดผลกระทบของ noise และ outlier
- ด้วยเคอร์เนลสามารถเรียนรู้ความสัมพันธ์ไม่เชิงเส้นได้อย่างยืดหยุ่น โดยใช้จำนวน support vector เพียงไม่กี่ตัว
- ปรับ
C,epsilon,gammaเพื่อควบคุมความสามารถในการทั่วไปและความเรียบของฟังก์ชัน - การทำสเกลฟีเจอร์เป็นสิ่งจำเป็น ควรใช้ pipeline เพื่อผูกขั้นตอนการเตรียมข้อมูลกับการเรียนรู้เข้าด้วยกัน
ภาพรวมเชิงสัญชาติญาณ #
SVR วางท่อล้อมรอบฟังก์ชันประมาณโดยมีความกว้าง ε จุดที่อยู่ในท่อไม่โดนลงโทษ ส่วนจุดที่ทะลุออกนอกท่อเท่านั้นที่จะมีค่าเพิ่มในฟังก์ชันวัตถุประสงค์ ดังนั้นเฉพาะจุดที่สัมผัสหรืออยู่นอกท่อ (support vector) เท่านั้นที่กำหนดรูปของฟังก์ชัน ทำให้ได้เส้นที่ราบเรียบและไม่ถูก noise รบกวนมาก
สูตรสำคัญ #
ปัญหาการเพิ่มประสิทธิภาพมีรูป
$$ \min_{\mathbf{w}, b, \boldsymbol{\xi}, \boldsymbol{\xi}^} \frac{1}{2} |\mathbf{w}|^2 + C \sum_{i=1}^{n} (\xi_i + \xi_i^) $$
พร้อมข้อจำกัด
$$ \begin{aligned} y_i - (\mathbf{w}^\top \phi(\mathbf{x}_i) + b) &\le \epsilon + \xi_i, \ (\mathbf{w}^\top \phi(\mathbf{x}_i) + b) - y_i &\le \epsilon + \xi_i^, \ \xi_i, \xi_i^ &\ge 0 \end{aligned} $$
\(\phi\) คือการแปลงฟีเจอร์ผ่านเคอร์เนล การแก้ปัญหาในรูปเชิงคู่จะได้ค่าสัมประสิทธิ์เฉพาะที่ support vector
ทดลองด้วย Python #
โค้ดด้านล่างสร้างข้อมูลไม่เชิงเส้น เทรน SVR ด้วย RBF kernel และวาดเส้นประมาณ
from __future__ import annotations
import japanize_matplotlib
import matplotlib.pyplot as plt
import numpy as np
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVR
def run_svr_demo(
*,
n_samples: int = 300,
train_size: float = 0.75,
xlabel: str = "input x",
ylabel: str = "output y",
label_train: str = "train samples",
label_test: str = "test samples",
label_pred: str = "SVR prediction",
label_truth: str = "ground truth",
title: str | None = None,
) -> dict[str, float]:
"""Train SVR on synthetic nonlinear data, plot fit, and report metrics.
Args:
n_samples: Number of data points sampled from the underlying function.
train_size: Fraction of data used for training.
xlabel: X-axis label for the plot.
ylabel: Y-axis label for the plot.
label_train: Legend label for training samples.
label_test: Legend label for test samples.
label_pred: Legend label for the SVR prediction line.
label_truth: Legend label for the ground-truth curve.
title: Optional title for the plot.
Returns:
Dictionary containing training and test RMSE values.
"""
japanize_matplotlib.japanize()
rng = np.random.default_rng(seed=42)
X = np.linspace(0.0, 6.0, n_samples, dtype=float)
y_true = np.sin(X) * 1.5 + 0.3 * np.cos(2 * X)
y_noisy = y_true + rng.normal(scale=0.2, size=X.shape)
X_train, X_test, y_train, y_test, y_true_train, y_true_test = train_test_split(
X[:, np.newaxis],
y_noisy,
y_true,
train_size=train_size,
random_state=42,
shuffle=True,
)
svr = make_pipeline(
StandardScaler(),
SVR(kernel="rbf", C=10.0, epsilon=0.1, gamma="scale"),
)
svr.fit(X_train, y_train)
train_pred = svr.predict(X_train)
test_pred = svr.predict(X_test)
grid = np.linspace(0.0, 6.0, 400, dtype=float)[:, np.newaxis]
grid_truth = np.sin(grid.ravel()) * 1.5 + 0.3 * np.cos(2 * grid.ravel())
grid_pred = svr.predict(grid)
fig, ax = plt.subplots(figsize=(10, 5))
ax.scatter(X_train, y_train, color="#1f77b4", alpha=0.6, label=label_train)
ax.scatter(X_test, y_test, color="#ff7f0e", alpha=0.6, label=label_test)
ax.plot(grid, grid_truth, color="#2ca02c", linewidth=2, label=label_truth)
ax.plot(grid, grid_pred, color="#d62728", linewidth=2, linestyle="--", label=label_pred)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
if title:
ax.set_title(title)
ax.legend()
fig.tight_layout()
plt.show()
return {
"train_rmse": float(mean_squared_error(y_train, train_pred, squared=False)),
"test_rmse": float(mean_squared_error(y_test, test_pred, squared=False)),
}
metrics = run_svr_demo(
xlabel="อินพุต x",
ylabel="เอาต์พุต y",
label_train="ข้อมูลฝึก",
label_test="ข้อมูลทดสอบ",
label_pred="เส้นคาดการณ์ของ SVR",
label_truth="ฟังก์ชันจริง",
title="การถดถอยไม่เชิงเส้นด้วย SVR",
)
print(f"RMSE บนชุดฝึก: {metrics['train_rmse']:.3f}")
print(f"RMSE บนชุดทดสอบ: {metrics['test_rmse']:.3f}")
วิเคราะห์ผลลัพธ์ #
- การใช้ pipeline ทำให้การสเกลด้วยค่าเฉลี่ยของชุดฝึกถูกนำไปใช้กับชุดทดสอบโดยอัตโนมัติ ป้องกัน data leakage
- การปรับ
epsilonและCช่วยกำหนดจำนวนจุดที่ถูกถือว่าเป็น outlier และระดับการลงโทษเมื่อออกนอกท่อ - พารามิเตอร์
gammaของ RBF ควบคุมความโค้ง: ค่ายิ่งสูงยิ่งจับรายละเอียดเฉพาะพื้นที่ ค่ายิ่งต่ำยิ่งเรียบ
เอกสารอ้างอิง #
- Smola, A. J., & Schölkopf, B. (2004). A Tutorial on Support Vector Regression. Statistics and Computing, 14(3), 199 E22.
- Vapnik, V. (1995). The Nature of Statistical Learning Theory. Springer.