SVM

SVM是深度学习之前的一种最常用的监督学习方法, 可以用来做分类也可以做回归. 它的本质和感知机类似, 但是额外增加了大间隔的优化目标. 结合前面提到的核技巧, 我们可以把SVM推广到非线性. 这样实现的分类器将有着非常好的性质, 让它一度成为"最优解".

LibSVM

在线性二分类SVM中,我们不止设置一个决策平面wTx+b=0w^Tx+b = 0wTx+b=0,还会有两个支持平面wTx+b=−1w^Tx+b = -1wTx+b=−1和wTx+b=1w^Tx+b = 1wTx+b=1. 如果我们假设数据是线性可分的, 那么决策平面一定位于两类样本之间. 而现在我们想从两类样本之间确定一个最好的超平面,这个超平面满足的性质是到两类样本的最小距离最大.很自然的, 我们可以看出这个超平面到两类样本的最小距离应该相等. 这时上面的支持超平面就派上用场了. 设到超平面最近的两个样本是x+x_+x+​和x−x_-x−​, 样本距离d=wTx+b∣∣w∣∣d=\frac{w^T x+b}{||w||}d=∣∣w∣∣wTx+b​, 也就是wTx++b=−(wTx−+b)=Cw^T x_++b = -(w^T x_-+b) = CwTx+​+b=−(wTx−​+b)=C. 又因为w的缩放对超平面的性质没有影响, 我们不妨让C = 1, 即两个样本x+x_+x+​和x−x_-x−​会分别落在wTx+b=1w^Tx+b = 1wTx+b=1和wTx+b=−1w^Tx+b = -1wTx+b=−1超平面上. 其他样本都在这两个超平面以外.
如果用上面的一系列假设, 我们的损失函数就应该是: 如果正样本越过了wTx+b=1w^Tx+b = 1wTx+b=1超平面, 即wTx+b<1w^Tx+b < 1wTx+b<1那么它受到随距离线性增长的惩罚. 同样如果正样本越过了wTx+b=−1w^Tx+b = -1wTx+b=−1超平面, 即wTx+b>−1w^Tx+b > -1wTx+b>−1也受到惩罚. 如果我们设y是样本标签,正样本为+1,负样本为-1. 则我们有保证两类样本到决策超平面的最小距离相等的新型感知机
L(w,b)=ReLU(−yi(wTxi+b)+1)∣∣w∣∣L(w,b) = \frac{ReLU(-y_i (w^T x_i+b)+1)}{||w||} L(w,b)=∣∣w∣∣ReLU(−yi​(wTxi​+b)+1)​
同样的道理, 等价于
L(w,b)=ReLU(−yi(wTxi+b)+1)L(w,b) = ReLU(-y_i (w^T x_i+b)+1) L(w,b)=ReLU(−yi​(wTxi​+b)+1)
到此还不算结束, 因为我们还想要这个最小距离最大化. 我们知道d=wTx+b∣∣w∣∣d=\frac{w^T x+b}{||w||}d=∣∣w∣∣wTx+b​, 而$|w^T x_++b| = |w^T x_-+b| = 1 ,即, 即,即d=\frac{1}{||w||}$, 也就是最大化距离, 只需要最小化w即可. 最小化一个参数常用的做法是正则化, 也就是对w施加L2的惩罚(L1的惩罚性质较差, 容易让w为0).这样我们就得到了最大化最小距离的损失.
minimize12∣∣w∣∣22minimize\qquad \frac{1}{2}||w||_2^2 minimize21​∣∣w∣∣22​
我们把上面两个损失函数加起来, 并给感知机正确分类的一项乘上一个常数系数, 用来调控(正确分类) vs (最大化间隔)两个目标的重要程度.
minimizeL(w,b)=12∣∣w∣∣22+C∗ReLU(−yi(wTxi+b)+1)minimize\quad L(w,b) = \frac{1}{2}||w||_2^2+C*ReLU(-y_i (w^T x_i+b)+1) minimizeL(w,b)=21​∣∣w∣∣22​+C∗ReLU(−yi​(wTxi​+b)+1)
优化这个无约束的优化问题就能得到线性的二分类SVM, 我们用Pytorch的自动求导来实现梯度下降, 优化这个目标函数看看效果.

import torch
import numpy as np
import matplotlib.pyplot as plt
import torch.nn.functional as F
import torch.nn as nn
import matplotlib
import randomtorch.manual_seed(2020)
n = 50Xp = torch.randn(n,2)
Xp[:,0] = (Xp[:,0]+3)
Xp[:,1] = (Xp[:,1]+4)
A = torch.tensor([[-1.,0.8],[2,1]])
Xp = Xp.mm(A)Xn = torch.randn(n,2)
Xn[:,0] = (Xn[:,0]-1)
Xn[:,1] = (Xn[:,1]-2)
A = torch.tensor([[-0.5,1],[1,0.5]])
Xn = Xn.mm(A)X = torch.cat((Xp,Xn),axis = 0)
y = torch.cat((torch.ones(n),-1*torch.ones(n)))
y = y.reshape(-1,1)class LibSVM:def __init__(self, C = 1, lr = 0.01):'''超参数包括松弛变量C和学习率lr要学习的参数是一个线性超平面的权重和偏置'''self.C = Cself.lr = lrself.weights = Noneself.bias = Nonedef train(self):self.weights.requires_grad = Trueself.bias.requires_grad = Truedef eval(self):self.weights.requires_grad = Falseself.bias.requires_grad = Falsedef fit(self, X, y, max_iters = 1000):'''X是数据张量, size(n,m)数据维度m, 数据数目ny是二分类标签, 只能是1或-1'''n,m = X.shapey = y.reshape(-1,1)self.weights = torch.randn(m,1)self.bias = torch.randn(1)self.train()for step in range(max_iters):out = X.mm(self.weights)+self.bias # 前向计算# 损失计算loss = 0.5*self.weights.T.mm(self.weights)+\self.C*torch.sum(F.relu(-y*out+1))# 自动求导loss.backward()# 梯度下降self.weights.data -= self.lr*self.weights.grad.dataself.bias.data -= self.lr*self.bias.grad.dataself.weights.grad.data.zero_()self.bias.grad.data.zero_()return lossdef predict(self, x, raw = False):self.eval()out = x.mm(self.weights)+self.biasif raw: return outelse: return torch.sign(out)def show_boundary(self, low1, upp1, low2, upp2):# meshgrid制造平面上的点x_axis = np.linspace(low1,upp1,100)y_axis = np.linspace(low2,upp2,100)xx, yy = np.meshgrid(x_axis, y_axis)data = np.concatenate([xx.reshape(-1,1),yy.reshape(-1,1)],axis = 1)data = torch.tensor(data).float()# 用模型预测out = self.predict(data, raw = True).reshape(100,100)out = out.numpy()Z = np.zeros((100,100))Z[np.where(out>1)] = 1.Z[np.where(out<-1)] = -1.# 绘制支持边界plt.figure(figsize=(8,6))colors = ['aquamarine','seashell','palegoldenrod']plt.contourf(xx, yy, Z, cmap=matplotlib.colors.ListedColormap(colors))# 绘制决策边界slope = (-self.weights[0]/self.weights[1]).data.numpy()b = (-self.bias/self.weights[1]).data.numpy()xx = np.linspace(low1, upp1)yy = slope*xx+bplt.plot(xx,yy, c = 'black')model = LibSVM()
model.fit(X,y)
model.show_boundary(-5,12,-5,10)
plt.scatter(X[:n,0].numpy(),X[:n,1].numpy() , marker = '+', c = 'r')
plt.scatter(X[n:,0].numpy(),X[n:,1].numpy() , marker = '_', c = 'r')

Kernel SVM

有时, 我们需要的是超越超平面的决策边界, 它应该能容忍非线性可分的两类样本, 并给出非线性的解. 这一点仅靠上面的方法是无法实现的, 为此我们可以把核技巧带到LibSVM中, 得到二分类的KerbSVM. 复习一下核方法, 我们使用核方法的核心思想是在向量内积得到的标量上进行非线性变换, 从而间接实现高维映射.
那么怎么把内积带到上面的线性模型里呢? 观察到权重w和数据点x的维度相同, 那么如果我们把w写成x的线性组合, 就能得到x和x的内积. 即
w=∑i=1NαixiTw = \sum_{i=1}^{N}\alpha_i x_i^T w=i=1∑N​αi​xiT​
y=∑i=1NαixxiTy = \sum_{i=1}^{N} \alpha_i x x_i^T y=i=1∑N​αi​xxiT​
在内积上套用核函数就有非线性的广义线性模型, 常用的非线性核函数有高斯核, 多项式核等等
k(x,y)=(xyT)dk(x,y) = (xy^T)^d k(x,y)=(xyT)d
k(x,y)=exp(−σ∣∣x−y∣∣2)k(x,y) = exp(-\sigma||x-y||^2) k(x,y)=exp(−σ∣∣x−y∣∣2)
y=∑i=1Nαiϕ(x)ϕ(xi)T+b=∑i=1Nαik(x,xi)+by = \sum_{i=1} ^{N} \alpha_i \phi(x) \phi(x_i)^T+b = \sum_{i=1} ^{N} \alpha_i k(x,x_i)+b y=i=1∑N​αi​ϕ(x)ϕ(xi​)T+b=i=1∑N​αi​k(x,xi​)+b
把这个新的计算式代入之前的损失函数, 得到新的, 核函数版本的bSVM的损失函数
minimizeL(w,b)=12∣∣w∣∣22+C∗ReLU(−yi(∑i=1Nαik(x,xi)+b)+1)=12∑i=1N∑j=1Nαiαjk(xi,xj)+C∗ReLU(−yi(∑i=1Nαik(x,xi)+b)+1)minimize\quad L(w,b) = \frac{1}{2}||w||_2^2+C* ReLU(-y_i (\sum_{i=1}^{N} \alpha_i k(x,x_i)+b)+1) = \frac{1}{2}\sum_{i=1}^{N}\sum_{j=1}^{N}\alpha_i \alpha_j k(x_i,x_j)+C* ReLU(-y_i (\sum_{i=1} ^{N} \alpha_i k(x,x_i)+b)+1) minimizeL(w,b)=21​∣∣w∣∣22​+C∗ReLU(−yi​(i=1∑N​αi​k(x,xi​)+b)+1)=21​i=1∑N​j=1∑N​αi​αj​k(xi​,xj​)+C∗ReLU(−yi​(i=1∑N​αi​k(x,xi​)+b)+1)
优化它, 得到的就是n维向量alpha和一个偏置b. 那么你可能会说, 如果是这样, 我们的数据集越大, 要计算的alpha不也会越大吗? 而且要执行运算的话, 是不是就需要保存训练时使用的全部xix_ixi​, 对每个xix_ixi​都和当前x做核函数运算再做线性组合, 才能得到结果呢? 事实也是如此, 因此, 在大规模数据上执行核SVM算法时, 我们并不会用所有的xix_ixi​来做线性组合产生w, 我们可以只取一部分的xix_ixi​来减少运算量.

class KerbSVM:def __init__(self, C = 1, sigma = None, lr = 0.01):self.C = Cself.lr = lrself.sigma = sigmaself.weights = Noneself.bias = Nonedef train(self):self.weights.requires_grad = Trueself.bias.requires_grad = Truedef eval(self):self.weights.requires_grad = Falseself.bias.requires_grad = Falsedef ker_func(self, x1, x2, sigma = None):'''高斯核函数, sigma是高斯核的带宽x1, x2是矩阵, 函数计算x1和x2的核矩阵'''n1,n2 = x1.shape[0],x2.shape[0]if sigma is None:sigma = 0.5/x1.shape[1]K = torch.zeros((n1,n2))for i in range(n2):square = torch.sum((x1-x2[i])**2, axis = 1)K[:,i] = torch.exp(-square)return Kdef fit(self, X, y, max_iters = 1000, print_info = False):'''X是数据张量, size(n,m)数据维度m, 数据数目ny是二分类标签, 只能是1或-1'''n,m = X.shapey = y.reshape(-1,1)self.weights = torch.zeros((n,1))self.bias = torch.zeros(1)self.train()# 计算数据集X的核矩阵K_mat = self.ker_func(X,X,self.sigma)for step in range(max_iters):out = K_mat.mm(self.weights)+self.bias # 前向计算# 损失计算rloss = 0.5*self.weights.T.mm(K_mat).mm(self.weights)closs = self.C*torch.sum(F.relu(-y*out+1))# 自动求导loss = rloss+clossloss.backward()# 梯度下降self.weights.data -= self.lr*self.weights.grad.dataself.bias.data -= self.lr*self.bias.grad.dataself.weights.grad.data.zero_()self.bias.grad.data.zero_()if (step+1)%20==0 and print_info:print("step %d, regularization loss: %.4f, classification loss: %.4f"%(step+1,rloss,closs))self.X = Xreturn lossdef predict(self, x, raw = False):self.eval()out = self.ker_func(x,self.X).mm(self.weights)+self.biasif raw: return outelse: return torch.sign(out)def show_boundary(self, low1, upp1, low2, upp2):# meshgrid制造平面上的点x_axis = np.linspace(low1,upp1,100)y_axis = np.linspace(low2,upp2,100)xx, yy = np.meshgrid(x_axis, y_axis)data = np.concatenate([xx.reshape(-1,1),yy.reshape(-1,1)],axis = 1)data = torch.tensor(data).float()# 用模型预测out = self.predict(data, raw = True).reshape(100,100)out = out.numpy()Z = np.zeros((100,100))Z[np.where(out>0)] = 1.Z[np.where(out>1)] += 1.Z[np.where(out<-1)] = -1.# 绘制支持边界plt.figure(figsize=(8,6))colors = ['aquamarine','seashell','paleturquoise','palegoldenrod']plt.contourf(xx, yy, Z, cmap=matplotlib.colors.ListedColormap(colors))model = KerbSVM(C=1, lr = 0.01)
model.fit(X,y,max_iters = 2000)
model.show_boundary(-5,12,-5,10)
plt.scatter(X[:n,0].numpy(),X[:n,1].numpy() , marker = '+', c = 'r')
plt.scatter(X[n:,0].numpy(),X[n:,1].numpy() , marker = '_', c = 'r')

对偶SVM

上面的梯度下降优化方法是可行的, 但是考虑到SVM对超参数很敏感, 我们需要不断地尝试调整参数才能找到一组比较好的参数(比如上面的松弛变量C和高斯核带宽sigma). 这就要求我们可能需要进行多次的交叉验证. 但是多次交叉验证的训练开销相对大, 这就成为了SVM的应用上最大的阻碍.
但是我们对SVM的几十年的研究并不是白费的, 研究者们总结出了一套把SVM转为对偶问题的方法, 并在约束的对偶问题里进行基于二次规划的坐标下降优化. 我们还会用一些启发式的方法帮助我们快速找到解.
我们先推导一下对偶形式的SVM, 我们改写SVM的优化目标, 把ReLU的hinge loss那一项写成一般的不等式约束形式, 同时优化目标函数是w的L2-norm.
minimize12∣∣w∣∣2+C∑i=1Nξiminimize\quad \frac{1}{2}||w||^2+C\sum_{i=1}^N\xi_i minimize21​∣∣w∣∣2+Ci=1∑N​ξi​
s.t.yi(wTxi+b)≥1−ξis.t.\quad y_i(w^Tx_i+b)\ge 1-\xi_i s.t.yi​(wTxi​+b)≥1−ξi​
ξi≥0\xi_i \ge 0 ξi​≥0
其中xi是和hinge loss的意义相似的, 把不等式约束软化的方法. 我们用xi放松对边界的限制, 但是随着xi增加, 我们会受到额外的L1惩罚. 解它的对偶问题有
Lagrangian:
L(w,b,ξ,α,r)=12∣∣w∣∣2+C∑i=1Nξi+∑i=1Nαi[1−ξi−yi(wTxi+b)]−∑i=1NriξiL(w,b,\xi,\alpha,r) = \frac{1}{2}||w||^2+C\sum_{i=1}^N\xi_i+\sum_{i=1}^N\alpha_i[1-\xi_i-y_i(w^Tx_i+b)]-\sum_{i=1}^Nr_i\xi_i L(w,b,ξ,α,r)=21​∣∣w∣∣2+Ci=1∑N​ξi​+i=1∑N​αi​[1−ξi​−yi​(wTxi​+b)]−i=1∑N​ri​ξi​
pd2zeros:
∂L∂w=w−∑i=1Nαiyixi=0\frac{\partial{L}}{\partial{w}} = w-\sum_{i=1}^N\alpha_i y_ix_i=0 ∂w∂L​=w−i=1∑N​αi​yi​xi​=0
∂L∂b=−∑i=1Nαiyi=0\frac{\partial{L}}{\partial{b}} = -\sum_{i=1}^N\alpha_i y_i=0 ∂b∂L​=−i=1∑N​αi​yi​=0
∂L∂ξi=C−αi−ri=0\frac{\partial{L}}{\partial{\xi_i}} = C-\alpha_i-r_i=0 ∂ξi​∂L​=C−αi​−ri​=0
回代原优化问题
minimize∑i=1Nαi−12∑i=1N∑j=1NαiαjyiyjxiTxjminimize\quad \sum_{i=1}^N\alpha_i -\frac{1}{2}\sum_{i=1}^N\sum_{j=1}^N\alpha_i\alpha_jy_iy_jx_i^Tx_j minimizei=1∑N​αi​−21​i=1∑N​j=1∑N​αi​αj​yi​yj​xiT​xj​
这是另一种推导SVM的线路, 因为是有约束的凸优化形式, 我们可以很自然的把问题推广为对偶问题
maximizeα,rminw,b,ξL(w,b,ξ,α,r)maximize_{\alpha,r}\quad min_{w,b,\xi}L(w,b,\xi,\alpha,r) maximizeα,r​minw,b,ξ​L(w,b,ξ,α,r)
αi≥0,ri≥0\alpha_i\ge 0, r_i\ge 0 αi​≥0,ri​≥0
∑i=1Nαiyi=0\sum_{i=1}^N\alpha_i y_i=0 i=1∑N​αi​yi​=0
C−αi−ri=0C-\alpha_i-r_i=0 C−αi​−ri​=0
凸优化的对偶问题最优解和原问题相同, 我们可以直接处理对偶问题
maximizeα∑i=1Nαi−12∑i=1N∑j=1NαiαjyiyjxiTxjmaximize_{\alpha}\quad \sum_{i=1}^N\alpha_i -\frac{1}{2}\sum_{i=1}^N\sum_{j=1}^N\alpha_i\alpha_jy_iy_jx_i^Tx_j maximizeα​i=1∑N​αi​−21​i=1∑N​j=1∑N​αi​αj​yi​yj​xiT​xj​
0≥αi≤C0\ge\alpha_i\le C 0≥αi​≤C
∑i=1Nαiyi=0\sum_{i=1}^N\alpha_i y_i=0 i=1∑N​αi​yi​=0
因为有向量内积, 我们还能很自然引入核函数实现非线性
maximizeα∑i=1Nαi−12∑i=1N∑j=1Nαiαjyiyjk(xi,xj)maximize_{\alpha}\quad \sum_{i=1}^N\alpha_i -\frac{1}{2}\sum_{i=1}^N\sum_{j=1}^N\alpha_i\alpha_jy_iy_jk(x_i,x_j) maximizeα​i=1∑N​αi​−21​i=1∑N​j=1∑N​αi​αj​yi​yj​k(xi​,xj​)
到这里, 我们推翻了前面的无约束版本的SVM, 引入了这种对偶版本的SVM. 对一般的问题, 我们也不知道一个凸优化的原问题和对偶问题哪一个比较容易求解, 但是SVM的对偶问题是极为特殊的形式, 我们要优化的是对偶问题里的alpha, 一旦求到了alpha的最优解, 我们就拿到了最终的SVM计算式
y=∑i=1Nαiyik(x,xi)+by = \sum_{i=1}^N\alpha_i y_ik(x,x_i)+b y=i=1∑N​αi​yi​k(x,xi​)+b
b可以选择非0alpha对应的数据点计算=1或者=-1的恒等式得到. 那么任务就是如何求解对偶问题, 这个对偶问题由一个等式约束, 一个不等式约束和一个优化目标组成. 现在, 我们就讲一下怎么用高效的方法解这个约束优化.

SMO算法

SMO使用坐标下降的方法求解该问题, 每个优化step, 我们选择所有alpha中的两个alpha, 改变它们而固定其他. 又因为存在等式约束∑i=1Nαiyi=0\sum_{i=1}^N\alpha_i y_i=0∑i=1N​αi​yi​=0, 把它代入优化目标后要处理的变量只有一个,而且是一个关于该变量的二次函数. 剩下的二次函数和不等式约束组成了一个二次规划问题.
maximizeαi,αjQ(αi,αj)maximize_{\alpha_i, \alpha_j} Q(\alpha_i, \alpha_j) maximizeαi​,αj​​Q(αi​,αj​)
0≥αi≤C0\ge\alpha_i\le C 0≥αi​≤C
0≥αj≤C0\ge\alpha_j\le C 0≥αj​≤C
∑i=1Nαiyi=0\sum_{i=1}^N\alpha_i y_i=0 i=1∑N​αi​yi​=0
我们每次优化都计算这个二次规划的最优解, 把它更新到新的迭代点, 而且我们保证这样的更新比梯度上升更有效, 因为它每次更新都保证让损失函数上升.
好, 那么现在的问题就是求这个二次规划的闭式解, 只要有了闭式解, 可以想象, 我们就能通过多次调用闭式解就完成优化, 而不需要执行反向传播和梯度下降的多次迭代.
首先我们计算无约束时的最优解, 这个过程其实就是初中的二次函数极值问题
Q(α1,α2)=α1+α2−12K1,1y12α12−12K2,2y22α22−K1,2y1y2α1α2−y1α1∑irestαiyiKi,1−y2α2∑irestαiyiKi,2+CQ(\alpha_1, \alpha_2)=\alpha_1+\alpha_2-\frac{1}{2}K_{1,1}y_1^2\alpha_1^2-\frac{1}{2}K_{2,2}y_2^2\alpha_2^2 - K_{1,2}y_1y_2\alpha_1\alpha_2-y_1\alpha_1\sum_{i}^{rest}\alpha_iy_iK_{i,1}-y_2\alpha_2\sum_{i}^{rest}\alpha_iy_iK_{i,2}+C Q(α1​,α2​)=α1​+α2​−21​K1,1​y12​α12​−21​K2,2​y22​α22​−K1,2​y1​y2​α1​α2​−y1​α1​i∑rest​αi​yi​Ki,1​−y2​α2​i∑rest​αi​yi​Ki,2​+C
我们由等式约束可以写出
α1y1+α2y2=ζ=−∑irestαiyi\alpha_1y_1+\alpha_2y_2 = \zeta = -\sum_i^{rest}\alpha_iy_i α1​y1​+α2​y2​=ζ=−i∑rest​αi​yi​
α1=ζy1−α2y1y2\alpha_1= \zeta y_1-\alpha_2y_1y_2 α1​=ζy1​−α2​y1​y2​
回代到二次函数Q中, 得到一元二次函数
Q(α1,α2)=−12K1,1(ζ−y12α12)2−12K2,2α22−K1,2(ζ−α2y2)y2α2−(ζ−α2y2)∑irestαiyiKi,1−y2α2∑irestαiyiKi,2+α1+α2+CQ(\alpha_1, \alpha_2)=-\frac{1}{2}K_{1,1}(\zeta-y_1^2\alpha_1^2)^2-\frac{1}{2}K_{2,2}\alpha_2^2 - K_{1,2}(\zeta-\alpha_2y_2)y_2\alpha_2-(\zeta- \alpha_2y_2)\sum_{i}^{rest}\alpha_iy_iK_{i,1}-y_2\alpha_2\sum_{i}^{rest}\alpha_iy_iK_{i,2}+\alpha_1+\alpha_2+C Q(α1​,α2​)=−21​K1,1​(ζ−y12​α12​)2−21​K2,2​α22​−K1,2​(ζ−α2​y2​)y2​α2​−(ζ−α2​y2​)i∑rest​αi​yi​Ki,1​−y2​α2​i∑rest​αi​yi​Ki,2​+α1​+α2​+C
∂Q∂α2=−(K1,1+K2,2−2K1,2)α2+K1,1ζy2−K1,2ζy2+y2∑irestαiyiKi,1−y2∑irestαiyiKi,2−y1y2+y22=0\frac{\partial{Q}}{\partial{\alpha_2}} = -(K_{1,1}+K_{2,2}-2K_{1,2})\alpha_2+K_{1,1}\zeta y_2-K_{1,2}\zeta y_2+ y_2\sum_{i}^{rest}\alpha_iy_iK_{i,1}- y_2\sum_{i}^{rest}\alpha_iy_iK_{i,2}- y_1y_2+y_2^2 = 0 ∂α2​∂Q​=−(K1,1​+K2,2​−2K1,2​)α2​+K1,1​ζy2​−K1,2​ζy2​+y2​i∑rest​αi​yi​Ki,1​−y2​i∑rest​αi​yi​Ki,2​−y1​y2​+y22​=0
剩下的就是纯粹的计算, 算到最后我们发现, 新的α2\alpha_2α2​可以用旧的α2\alpha_2α2​表示.
α2new=α2old+y2(E1−E2)η\alpha_2^{new} = \alpha_2^{old}+\frac{y_2(E_1-E_2)}{\eta} α2new​=α2old​+ηy2​(E1​−E2​)​
其中E是预测值与真实值的误差Ei=f(xi)−yiE_i = f(x_i)-y_iEi​=f(xi​)−yi​, η=K1,1+K2,2−K1,2\eta = K_{1,1}+K_{2,2}-K_{1,2}η=K1,1​+K2,2​−K1,2​
下一步就是考虑约束, 给出约束下的极值点. 我们考虑的约束是所谓"方形约束".


我们额外计算一下上下界, 并作clip就能计算出α2\alpha_2α2​的值了, 然后我们用恒等式把α1\alpha_1α1​的值也算出来, 这样就结束了一次更新. SMO的步骤大致如此

选择要更新的参数

每次更新我们都会面对所有n个α\alphaα, 那么要更新的是哪个呢. 我们从误差出发, 我们上面定义的E是预测偏移标签的值, 我们永远选择误差最大的那一个α\alphaα作为α2\alpha_2α2​更新, α1\alpha_1α1​的话随机选取即可.

class SVC:def __init__(self, C = 1, sigma = None):self.C = Cself.sigma = sigmaself.weights = Noneself.bias = Nonedef ker_func(self, x1, x2, sigma = None):'''高斯核函数, sigma是高斯核的带宽x1, x2是矩阵, 函数计算x1和x2的核矩阵'''n1,n2 = x1.shape[0],x2.shape[0]if sigma is None:sigma = 0.5/x1.shape[1]K = np.zeros((n1,n2))for i in range(n2):square = np.sum((x1-x2[i])**2, axis = 1)K[:,i] = np.exp(-square)return Kdef predict(self, x, raw = False):out = self.ker_func(x,self.X).dot(self.weights*self.y)+self.biasif raw: return outelse: return np.sign(out)def fit(self, X, y, max_iters = 100):'''X是数据张量, size(n,m)数据维度m, 数据数目ny是二分类标签, 只能是1或-1'''n,m = X.shapeself.X = Xy = y.reshape(-1,1)self.y = yself.weights = np.zeros((n,1))self.bias = np.zeros(1)# 计算数据集X的核矩阵K_mat = self.ker_func(X,X,self.sigma)alpha = self.weightsb = self.biasC = self.Cfor t in range(max_iters):#首先为各变量计算errcon1 = alpha > 0con2 = alpha < Cy_pred = K_mat.dot(alpha)+berr1 = y * y_pred - 1err2 = err1.copy()err3 = err1.copy()err1[(con1 & (err1 <= 0)) | (~con1 & (err1 > 0))] = 0err2[((~con1 | ~con2) & (err2 != 0)) | ((con1 & con2) & (err2 == 0))] = 0err3[(con2 & (err3 >= 0)) | (~con2 & (err3 < 0))] = 0# 算出平方和并取出使得“损失”最大的 idxerr = err1 ** 2 + err2 ** 2 + err3 ** 2i = np.argmax(err)j = iwhile j==i:j = random.randint(0,n-1)#为i和j计算函数值E = f(x)-ye1 = self.predict(X[i:i+1],raw = True)-y[i]e2 = self.predict(X[j:j+1],raw = True)-y[j]#计算内积差dK = K_mat[i][i]+K_mat[j][j]-2*K_mat[i][j]#计算上下界if y[i]!=y[j]:L = max(0,alpha[i]-alpha[j])H = min(C,C+alpha[i]-alpha[j])else:L = max(0,alpha[i]+alpha[j]-C)H = min(C,alpha[i]+alpha[j])#更新参数的最优值alpha1_new = alpha[i]-y[i]*(e1-e2)/dK#考虑上下界后的更新值alpha1_new = np.clip(alpha1_new, L, H)alpha[j:j+1] += y[i]*y[j]*(alpha[i]-alpha1_new)alpha[i:i+1] = alpha1_new#找支持向量并修正bb -= bindices = [i for i in range(n) if 0<alpha[i]<C]for i in indices:b+=(1/y[i]-np.sum(alpha*K_mat[i]*y))if len(indices):b /= len(indices)# 保留支持向量和对应的权值'''indices = np.where(alpha[:,0]>0)self.X = self.X[indices]self.y = self.y[indices]self.weights = self.weights[indices]'''return alpha, bdef show_boundary(self, low1, upp1, low2, upp2):# meshgrid制造平面上的点x_axis = np.linspace(low1,upp1,100)y_axis = np.linspace(low2,upp2,100)xx, yy = np.meshgrid(x_axis, y_axis)data = np.concatenate([xx.reshape(-1,1),yy.reshape(-1,1)],axis = 1)# 用模型预测out = self.predict(data, raw = True).reshape(100,100)Z = np.zeros((100,100))Z[np.where(out>0)] = 1.Z[np.where(out>1)] += 1.Z[np.where(out<-1)] = -1.# 绘制支持边界plt.figure(figsize=(8,6))colors = ['aquamarine','seashell','paleturquoise','palegoldenrod']plt.contourf(xx, yy, Z, cmap=matplotlib.colors.ListedColormap(colors))model = SVC()
model.fit(X.numpy(),y.numpy(),max_iters = 500)
model.show_boundary(-5,12,-5,10)
plt.scatter(X[:n,0].numpy(),X[:n,1].numpy() , marker = '+', c = 'r')
plt.scatter(X[n:,0].numpy(),X[n:,1].numpy() , marker = '_', c = 'r')


求解对偶问题的SMO算法比起传统优化方法获得的解是更稳定和高效的, 如果使用比上面更好的启发方法, 收敛将会更快(参考sklearn).

SVM优缺点和使用注意事项

优点

  1. SVM是凸优化, 比神经网络的解更加稳定, 理论基础好.
  2. 在高维数据上很高效(计算的本质是矩阵运算, 数据维度越高越易并行化, 类似神经网络)
  3. 能处理非线性数据, 比线性分类器性能更好

缺点
4. 在大规模数据上不高效, 数据越大要考虑的alpha越多.
5. 不容易应用于多分类, SVM一般通过构造多个二分类器才能实现多分类, 而神经网络只需要单个模型.
6. 需要调参以防止核函数带来的过拟合

使用指南
7. 标准化数据, 把数据缩放到-1,1或0,1区间会让SVM更方便处理数据(尤其是多项式核)
8. 只保留一部分支持向量以减少内存的开销.
9. 在较多分类问题或极大规模数据里避免使用SVM
10. SVM对样本不平衡的敏感度很高, 可以尝试在SVM中添加数据的权重项来增强SVM在不平衡样本上的表现.

实践: 线性SVM分类乳腺癌样本

from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn import datasetsX,y = datasets.load_breast_cancer(return_X_y=True)y = 2*y-1
# 我们设置较高的test_size来观察过拟合的发生
X_train, X_test, y_train, y_test = train_test_split( X, y, test_size = 0.2, random_state = 1)#数据标准化处理
sc_X = StandardScaler()
X_train = sc_X.fit_transform(X_train)
X_test = sc_X.transform(X_test)model = LibSVM(C = 5)
model.fit(torch.tensor(X_train).float(),torch.tensor(y_train).float(),max_iters = 1000)y_pred = model.predict(torch.tensor(X_test).float()).numpy()
accuracy_score(y_pred, y_test)0.9736842105263158

实践: 核SVM分类iris样本

class Multi_class_SVC:def __init__(self, num_class, C = 1, sigma = None):'''使用ovo的构造方式, 每两个类别之间构造一个二分类器'''self.num = num_classself.models = [[SVC(C = 1, sigma = None) for _ in range(i+1, num_class)]for i in range(num_class)]def fit(self, X, y, max_iters = 100):for i in range(self.num):for j in range(i+1, self.num):j_ = j-(i+1)indices_i = np.where(y==i)[0]indices_j = np.where(y==j)[0]data = np.concatenate([X[indices_i],X[indices_j]],axis = 0)labels = np.concatenate([-np.ones(len(indices_i)),np.ones(len(indices_j))],axis = 0)self.models[i][j_].fit(data,labels,max_iters)def predict(self, x):'''采用投票制'''n = x.shape[0]votes = np.zeros((n,self.num))for i in range(self.num):for j in range(i+1, self.num):j_ = j-(i+1)y_pred_part = self.models[i][j_].predict(x).squeeze()indices = np.zeros(n).astype(np.int)indices[y_pred_part==-1] = iindices[y_pred_part==1] = jvotes[range(n),indices] += 1return np.argmax(votes, axis = 1)X,y = datasets.load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split( X, y, test_size = 0.2, random_state = 1)model = Multi_class_SVC(3,C = 5)
model.fit(X_train,y_train,max_iters = 500)
y_pred = model.predict(X_test)
accuracy_score(y_pred, y_test)1.0

备注

尽管上面的梯度下降是由torch自动求导得到的, 但是如果要自己手算导数也并不是困难的事情. 以kernel SVM的损失为例
∂L∂α=12(Kα+diag(K))−C(yiK:j)if−yi(∑i=1Nαik(x,xi)+b)+1≥0\frac{\partial L}{\partial \alpha}= \frac{1}{2}(K\alpha+diag(K))-C(y_i K_{:j}) \qquad if \ -y_i (\sum_{i=1} ^{N} \alpha_i k(x,x_i)+b)+1 \ge 0∂α∂L​=21​(Kα+diag(K))−C(yi​K:j​)if −yi​(i=1∑N​αi​k(x,xi​)+b)+1≥0
∂L∂α=12(Kα+diag(K))if−yi(∑i=1Nαik(x,xi)+b)+1<0\frac{\partial L}{\partial \alpha}= \frac{1}{2}(K\alpha+diag(K)) \qquad if \ -y_i (\sum_{i=1} ^{N} \alpha_i k(x,x_i)+b)+1 \lt 0∂α∂L​=21​(Kα+diag(K))if −yi​(i=1∑N​αi​k(x,xi​)+b)+1<0
∂L∂b=−C∗yiif−yi(∑i=1Nαik(x,xi)+b)+1≥0\frac{\partial L}{\partial b}= -C*y_i \qquad if \ -y_i (\sum_{i=1} ^{N} \alpha_i k(x,x_i)+b)+1 \ge 0∂b∂L​=−C∗yi​if −yi​(i=1∑N​αi​k(x,xi​)+b)+1≥0
∂L∂b=0if−yi(∑i=1Nαik(x,xi)+b)+1<0\frac{\partial L}{\partial b}= 0 \qquad if \ -y_i (\sum_{i=1} ^{N} \alpha_i k(x,x_i)+b)+1 \lt 0∂b∂L​=0if −yi​(i=1∑N​αi​k(x,xi​)+b)+1<0

统计学习方法(1) 梯度下降法和SMO算法实现SVM相关推荐

  1. 机器学习中梯度下降法和牛顿法的比较

    在机器学习的优化问题中,梯度下降法和牛顿法是常用的两种凸函数求极值的方法,他们都是为了求得目标函数的近似解.在逻辑斯蒂回归模型的参数求解中,一般用改良的梯度下降法,也可以用牛顿法.由于两种方法有些相似 ...

  2. 八、梯度下降法和拟牛顿法

    1.梯度 2.梯度上升和梯度下降 3.梯度下降算法详解 3.1 直观解释 3.2 梯度下降相关概念 3.3 梯度下降的矩阵描述 3.4 梯度下降的算法调优 4.梯度下降法大家族 5.梯度下降法和其他无 ...

  3. 梯度下降法和牛顿法计算开根号

    梯度下降法和牛顿法计算开根号 本文将介绍如何不调包,只能使用加减乘除法实现对根号x的求解.主要介绍梯度下降和牛顿法者两种方法,并给出 C++ 实现. 梯度下降法 思路/步骤 转化问题,将 x \sqr ...

  4. 梯度下降法和Sklearn实现线性回归

    1.线性回归模型 线性回归 (linear regression)是-种线性模型, 它假设输入变量 x和单个输出变量 y 之间存在线性关系.具体来说,利用线性回归模型,可以从-组输入变量 x 的线性组 ...

  5. 关于梯度下降法和牛顿法的数学推导

    作者:LogM 本文原载于 https://blog.csdn.net/qq_28739605/article/details/80862810,不允许转载~ 文章难免有错误之处,请在原文评论处指出~ ...

  6. 梯度下降算法_批梯度下降法,Minibatch梯度下降法和随机梯度下降法之间的区别...

    什么是梯度下降法? 梯度下降法是一种机器学习中常用的优化算法,用来找到一个函数(f)的参数(系数)的值,使成本函数(cost)最小. 当参数不能解析计算时(如使用线性代数),并且必须通过优化算法搜索时 ...

  7. GBDT与xgb区别,以及梯度下降法和牛顿法的数学推导

    为什么要介绍梯度下降法和牛顿法那? 这里提及两个算法模型GBDT和XGBoost,两个都是boosting模型. GBDT和xgb的目标函数是不同的,同时针对其目标函数中的误差函数 L(θ) 的拟合方 ...

  8. 梯度下降法和随机梯度下降法

    1. 梯度 在微积分里面,对多元函数的参数求∂偏导数,把求得的各个参数的偏导数以向量的形式写出来,就是梯度.比如函数f(x,y), 分别对x,y求偏导数,求得的梯度向量就是(∂f/∂x, ∂f/∂y) ...

  9. 梯度下降法和最速下降法的细微差别

    "所谓的梯度方向只是起始点(xk)的梯度方向,并不一定是起始点和终点之间其他点的梯度方向" 原文链接: 梯度下降法和最速下降法的细微差别

最新文章

  1. 网络空间安全Windows系统命令行学习笔记
  2. SpringMVC背景介绍及常见MVC框架比较
  3. Tungsten Fabric SDN — 基于 Tags 的安全访问控制策略
  4. buuctf [GKCTF 2021]你知道apng吗 <apng图片格式的考察>
  5. Linux 上扩展swap分区
  6. java proguard 使用_一步步教你使用Proguard混淆Java源代码
  7. mysql rpm包安装指定路径_安装rpm包时指定路径
  8. 数据结构实验:一元多项式计算器
  9. (3)JavaScript 的注释
  10. 零基础入门专利代理考试需要了解的,持续更新ing
  11. 力扣438.找到字符串中所有字母异位词(JavaScript)
  12. Android 完全退出应用程序实现代码
  13. oracle表分析效果怎么看,Oracle 索引与表分析几种方法
  14. Qt 遍历目录下所有图片
  15. 6.3创建自己执行的二进制文件
  16. 快讯丨业界首本云网络图书发布
  17. Linux开发环境搭建之cmake安装
  18. 金融衍生品数据分析_大数据_numpy,matplotlib,pandas学习
  19. 内网通过代理服务器访问高德地图服务的方法
  20. k8s中亲和性与反亲和性

热门文章

  1. ImageMagick快速入门
  2. sed 匹配非常规空格[[:space:]]+
  3. 2022新超级蜘蛛池站群优化网站系统源码
  4. redis系列(一)
  5. Spring-day1
  6. ZBrush中绘制层是什么意思?
  7. JAVA实现Date日期加一天
  8. 蓝桥杯单片机超声波(距离稳定增长)
  9. html保存到桌面的代码,H5创建webApp保存到桌面(代码教程)
  10. MT40A1G8SA-062E AAT:E内存颗粒D9XSP芯片