<center> <img src = "https://i.imgur.com/MjdXyB5.jpg"> </center> ## Prerequisite Support vector machine 是將資料視為 $p$ 維度的向量,並希望透過 $(p-1)$ 的超平面來分開這些資料。而最佳超平面即是以最大間隔來把兩個類分開的超平面。 ### Hyperplane 超平面是 $n$ 維歐氏空間中的 $n-1$ 維的子空間。 超平面可由以下方程式定義: $$ a_0x_0 + \dots + a_nx_n = b $$ 其中 $a_0, \dots, a_n$ 是不全為零的常數。 ### Generalization error 是衡量演算法能夠預測以前未見過的數據的結果值的準確性的指標 ### Hinge loss 對於一個預期輸出 $t = \pm 1$ 分類結果 $y$ 的 Hinge loss 定義為 $$ l(y) = \max (0, 1 - t \cdot y) $$ Hinge loss 是一個用於訓練分類器的損失函數。Hinge loss 被用於「最大間格分類」,非常適合用於支持向量機 (SVM)。 ## Goal Support vector machine 在高維或無限維空間中構造超平面或超平面集合,此演算法可以用於分類、回歸或其他任務。 ## Background 以下情境先假設為分類任務 ### Linear Support vector machine 以下考慮 $n$ 個資料,$(\vec{x}_1,y_1), \dots, (\vec{x}_n, y_n)$,其中 $y_i$ 代表的是 $\vec{x}_i$ 所屬的類別。 而所要解決的最佳化問題是分隔每個資料點在正確一側,可以寫作 $$ y_i(\vec{w} \cdot \vec{x}_i - b) \ge 1, \ \ \ \text{for all } 1 \le i \le n $$ 也就是在 $y_i(\vec{w} \cdot \vec{x}_i - b) \ge 1$ 下,對於 $i = 1, \cdots, n$ 最小化 $\| \vec{w} \|$ 其中,最大間隔超平面完全是由最靠近它的那些 $\vec{x}_i$ 決定,而 $\vec{x}_i$ 也被稱為 support vector。 --- 若遇到資料線性不可分的情況,則必須使用 Hinge loss $$ \max (0, 1 - y_i (\vec{w} \cdot \vec{x_i} - b)) $$ 而當方條件式滿足時 $$ y_i(\vec{w} \cdot \vec{x}_i - b) \ge 1, \ \ \ \text{for all } 1 \le i \le n $$ 此函數為零。對於間隔的錯誤一側的資料,該函數的值與距間隔的距離成正比。 而所要最小化的 loss function 如下 $$ \left[\frac{1}{n} \sum_{i=1}^n \max (0, 1- y_i (\vec{w} \cdot \vec{x}_i) - b)\right] + \lambda \| \vec{w} \|^2 $$ 其中參數 $\lambda$ 是一個超參數,確保 $\vec{x}_i$ 位於間隔的正確一側之間的關係。 如此一來,對於足夠小的 $\lambda$,如果輸入資料是可以線性分類的,則軟間隔 SVM 與硬間隔 SVM 將表現相同,但即使不可線性分類,仍能用於分類任務。 ### Non-linear Support vector machine ![](https://hackmd.io/_uploads/Hkhc_yQ0h.png) <center> <a href="https://zh.wikipedia.org/zh-tw/%E6%94%AF%E6%8C%81%E5%90%91%E9%87%8F%E6%9C%BA#/media/File:Kernel_Machine.png">Kernel Function</a> </center><br> 透過 Kernel function 我們可以將低維非線性可分離函數計算成高維線性可分離函數,這樣的話在遇到非線性分類時,Support Vector Machine 還是可以進行分類任務。 ### Calculate Loss Function 我們可以透過 Sub-gradient descent 來計算 Support Vector Machine **Sub-gradient descent** $$ f(\vec{w},b) = \left[\frac{1}{n} \sum_{i=1}^n \max (0, 1- y_i (\vec{w} \cdot \vec{x}_i) - b)\right] + \lambda \| \vec{w} \|^2 $$ $f :\mathbb{R}^n \rightarrow \mathbb{R}$ 定義為 $\mathbb{R}^n$ 上的凸函數,其中使用下述格式更新 $$ x^{(k+1)} = x^{(k)} - \alpha_k g^{(k)} $$ ## Implementation ```python class SVM (object): def __init__(self, kernel='linear', C=1.0, gamma='auto'): self.kernel = kernel self.C = C self.gamma = gamma self.alpha = None self.b = None self.support_vector = None self.support_vector_label = None def _kernel(self, x1, x2): if self.kernel == 'linear': return x1 @ x2.T elif self.kernel == 'rbf': return np.exp(-self.gamma * np.linalg.norm(x1 - x2) ** 2) # gamma = 1 / (2 * sigma^2) elif self.kernel == 'poly': return (1 + x1 @ x2.T) ** self.gamma else: raise ValueError('kernel not supported') def fit(self, x, y): n_samples, n_features = x.shape K = np.zeros((n_samples, n_samples)) for i in range(n_samples): for j in range(n_samples): K[i, j] = self._kernel(x[i], x[j]) ''' define and solve QP problem min 1/2 x^T P x + q^T x s.t. Gx <= h Ax = b x >= 0 ''' P = np.outer(y, y) * K # P = y^T y K q = -1 * np.ones(n_samples) # q = -1 G = np.diag(np.ones(n_samples) * -1) # G = -I h = np.zeros(n_samples) # h = 0 A = y.T # A = y^T b = np.zeros(1) # b = 0 a = np.linalg.solve(np.dot(G, P) + np.eye(n_samples), np.dot(G, q)) # Lagrange multipliers sv = a > 1e-5 # support vectors ind = np.arange(len(a))[sv] # indices of support vectors self.alpha = a[sv] # Lagrange multipliers of support vectors self.support_vector = x[sv] # support vectors self.support_vector_label = y[sv] # labels of support vectors # Intercept # b = 1/n sum(y_n - sum(alpha_i y_i K(x_n, x_i))) self.b = 0 for n in range(len(self.alpha)): self.b += self.support_vector_label[n] self.b -= np.sum(self.alpha * self.support_vector_label * K[ind[n], sv]) self.b /= len(self.alpha) # Weight vector # w = sum(alpha_i y_i x_i) if self.kernel == 'linear': self.w = np.zeros(n_features) for n in range(len(self.alpha)): self.w += self.alpha[n] * self.support_vector_label[n] * self.support_vector[n] else: self.w = None def predict(self, x): if self.w is not None: return np.sign(x @ self.w + self.b) else: y_predict = np.zeros(len(x)) for i in range(len(x)): s = 0 for alpha, sv_y, sv in zip(self.alpha, self.support_vector_label, self.support_vector): s += alpha * sv_y * self._kernel(x[i], sv) y_predict[i] = s return np.sign(y_predict + self.b) def score(self, x, y): y_predict = self.predict(x) return np.mean(y_predict == y) ```   ## From Scratch ## More example [House Prices - Advanced Regression Techniques](https://www.kaggle.com/competitions/house-prices-advanced-regression-techniques/overview) ## Reference 歡迎更仔細閱讀以下相關內容以了解本篇知識 - [支持向量機](https://zh.wikipedia.org/zh-tw/%E6%94%AF%E6%8C%81%E5%90%91%E9%87%8F%E6%9C%BA) - [R語言自學日記(20)-機器學習(一):支持向量迴歸(Support Vector Regression)](https://medium.com/r-%E8%AA%9E%E8%A8%80%E8%87%AA%E5%AD%B8%E7%B3%BB%E5%88%97/r%E8%AA%9E%E8%A8%80%E8%87%AA%E5%AD%B8%E6%97%A5%E8%A8%98-20-%E6%A9%9F%E5%99%A8%E5%AD%B8%E7%BF%92-%E4%B8%80-%E6%94%AF%E6%8C%81%E5%90%91%E9%87%8F%E8%BF%B4%E6%AD%B8-support-vector-regression-30cd834a918)