|
线性模型
线性可分训练集
一个训练数据集线性可分是指: ,使对 ,有
即(公式1)
对于线性可分的数据集,我们需要划一条线来分割两类不同的样本。但是分割的线有无数条,我们怎么判断哪一条线更好呢?
很多人认为第二条线是最好的。但是因为根据免费午餐定理,这三条曲线是一样好的。那么我们为什么会认为第二条曲线是最好的呢?这是因为我们在研究问题之前,对此问题存在先验假设。有很多先验假设认为第二条直线比其余两条要好。我们只考虑其中一种假设,即假设训练样本的位置在特征空间有测量误差。如下图所示:
假设红色的叉和圆圈的真实位置为红色虚线圆圈,则线3和1都会分类错误,而2不会,这说明2号线更能抵御训练样本位置的误差。
那么2号线是怎么画出来的呢?
支持向量机的创造者Vapnik是这样回答的,它首先将直线向一侧平行移动,直到它叉到一个或几个样本为止;之后再向另一侧移动,直到叉到一个或多个样本未知。
我们要找的2号线找的是使得间隔最大的且位于间隔中间的线。
在多维的情况下,直线变为超平面。
之后我们将支持向量机转化为一个优化问题,优化问题为:

那么这是怎么得到的呢?那面我们详细讨论一下:
事实一: 与 是同一个平面, 。即若 满足公式1,则 也满足公式一。
公式1: 事实二:点到平面的距离公式。
向量到超平面 的距离:

当为支持向量时,我们要做的就是最大化。
根据事实1,我们可以用 去缩放:

最终使在支持向量上上,有:
此时支持向量与平面距离:

因为最大化 相当于最小化 ,所以得到上述的目标函数。
下面看约束条件是如何得到的。
因为在上面的描述中我们有,对于所有的支持向量,我们有
所以对于非支持向量,我们有:

又因为:,所以综上我们有

这样我们就得到了上面提到的优化问题。
这个优化问题为凸优化问题中的二次优化问题。
二次规划问题:
这样就会导致要么无解,要么只有一个极值。
非线性可分
我们改写目标函数和约束条件,使其变为:

其中 称为松弛变量, 称为正则项。
非线性问题
对于下图所示的问题,我们不能找到一个很好的直线将两类分开:
但是我们可以将其映射到高维空间中的点,然后在高维空间中寻找直线。
我们定义一个从低维到高维的映射:

其中为低维向量,而为一个高维映射。
下面我们举一个例子:如下图所示的异或问题
这个问题我们在二维空间里无法找到一条直线将其分开。
在上图中我们令四个点分别为:


我们令

则经过映射得到:


我们可以令

来达到区分的目的。
有证明显示:在越高维度情况下,找打一个线性超平面来将样本分开的概率越大。我们如何选取 ,我们将选择为无限维。但是为无限维,将为无限维,优化问题将不可做。
我们可以不知道无限维映射的显式表达,我们只要知道一个核函数:

下面的优化问题:
仍然可解。
在SVM中常用的核函数:

为高斯核函数

为多项式核函数,为阶数。
核函数 必须满足某些条件才能被拆成内积的形式:
能被写成 的充要条件为:

,有$$\sum_{i=1}^N\sum_{i=1}^Nc_ic_jK(x_i,x_j)\ge 0$$
原问题和对偶问题
原问题
最小化:
限制条件:
对偶问题
定义:

对偶问题的定义
最大化: , 表示下界
限制条件:
原问题和对偶问题解的关系
定理:如果 是原问题的解,而 是对偶问题的解,则有:

证明:

定义:

叫做原问题与对偶问题的间距。对于某些特定优化问题,可以证明: 。
强对偶定理:若为凸函数,且 ,则此优化问题的原问题与对偶问题的间距为。即

此时我们易得对 :
- 或者

- 或者

这被称为KKT条件。
利用对偶问题求解SVM
我们先复习一下原问题:
根据原问题的定义形式,我们将上述问题改写:

凸函数定义:

我们SVM的对偶问题为:
最大化:

限制条件:
我们要想求得 ,首先要最优化 ,对 函数求偏导:

将其代入,得到:

其中 。
所以对偶优化问题变为:
最大化:
限制条件:
这也是一个凸优化问题,解这个问题有一个标准的算法(SMO)算法。
这样我们就可以求出 ,但是我们要求的是和,那么我们应该如何求出 呢,我们可以用之前得到的 ,但问题是我们并不知道 。
但是在判断样本属于哪一类的时候我们并不需要知道,假设有测试样本,我们知道:
而

但是应该怎么算呢?
应用KKT条件,我们有
我们取一个 ,
此时 ,因为 。也可以找到所有不等于的,求得取平均。
SMO算法
最大化:
限制条件:
因为我们要优化的变量()很多,所以我们每次迭代只选择几个变量进行更新;又因为我们的是有约束的优化问题,所以每次更新可能会破坏我们的约束条件,为了不破坏我们的约束条件,我们每次至少选择两个变量进行优化。因此我们每次选择两个变量来进行优化。
两个变量二次规划的求解过程
- 选择两个变量,其他变量固定
- SMO将对偶问题转化成一系列子问题

- 根据约束条件,可以表示为的函数
- 优化问题有解析解
- 基于初始可行解
,可以得到
两个变量,约束条件用二维空间中的图形表示:
下面首先考虑第一种情况,根据不等式条件的取值范围:
其中

同理对于第二种情况,根据不等式条件的取值范围:
其中

下面开始求解,求得的过程为:
- 先求沿着约束方向未经剪辑时的

- 再求剪辑后的
我们记
,即我们的判别表达式,令:
为输入的预测值和真实输出 的差。
为了简便,引进记号:

目标函数写成:

由 及 ,我们得 ,代入上式得到只是的函数的目标函数:

对求导并令其等于,得:

将 代入:

将 代入:

我们之后对解进行剪辑:

得到的解:

关于KKT条件,我们有:

下面我们计算阈值和
由KKT条件,如果 ,则



的表达式与 相结合,得:

同理,如果 ,则

如果同时满足条件 ,那么 。如果是或者 ,那么 以及它们之间的数都是符合KKT条件的阈值,这时选择它们的中点作为。
在每次完成两个变量的优化之后,还必须更新对应的值,并将它们保存在列表中。值的更新要用到值,以及所有支持向量对应的 :

其中, 是所有支持向量的集合。
关于此方面的解释
变量的启发式选择
SMO算法在每个子问题中选择两个变量优化,其中至少一个变量是违反KKT条件的。
- 第一个变量的选择:外循环
- 违反KKT条件最严重的样本点
- 检验样本点是否满足KKT条件:
\end{array}$$
该检验是在 范围内进行的。在检验过程中,外层循环首先遍历满足条件 的样本点,即在间隔边界上的支持向量点,检验它们是否满足KKT条件,如果这些样本点都满足KKT条件,那么遍历整个训练集,检验他们是否满足KKT条件。
- 第二个变量的检查:内循环
- 选择的标准是希望能使目标函数有足够大的变化
- 即对应
最大
- 如果内循环通过上述方法找到的点不能使目标函数有足够大的下降,则:遍历间隔边界上的样本点,测试目标函数下降
- 如果下降不大,则遍历所有样本点
- 如果依然下降不大,则丢弃外循环点,重新选择
算法实现
def kernal(a, b):
# 高斯核, s为方差
return a.dot(b)
class SVM:
def __init__(self,data,label):
self.data = data
self.label = label
self.len = data.shape[0]
self.E = np.zeros((self.len,1))
self.alpha = np.random.random((self.len,1))
self.b = 0
def f(self,i):
# 传入索引
s = 0
for k in range(self.len):
s += self.alpha[k]*self.label[k]*kernal(self.data[i,:],self.data[k,:])
return s + self.b
def error(self):
# 计算E
for j in range(self.len):
E = 0
for i in range(self.len):
E += self.alpha*self.label*kernal(self.data[i,:],self.data[j,:])
self.E[j] = E + self.b - self.label[j]
def bound(self, i,j,alpha_i, alpha_j, C):
# 传入两个索引,为需要优化的alpha的索引
# 求解alpha_j的范围
# C为正则化项系数
if self.label != self.label[j]:
L, H = np.max([0, alpha_j-alpha_i]), np.min([C, C+alpha_j-alpha_i])
else:
L, H = np.max([0, alpha_j+alpha_i-C]), np.min([C, alpha_j+alpha_i])
return (L,H)
def update(self, i,j, C):
# 更新alpha和b
# 传入索引
eta = kernal(self.data[i,:],self.data[i,:]) + kernal(self.data[j,:],self.data[j,:]) - 2*kernal(self.data[i,:],self.data[j,:])
# 下面需要补充逻辑关系
alpha_old_i = self.alpha
alpha_old_j = self.alpha[j]
self.alpha[j] = self.alpha[j] + self.label[j]*(self.E-self.E[j])/eta
L, H = self.bound(i,j,alpha_old_i,alpha_old_j,C)
if self.alpha[j] >= H:
self.alpha[j] = H
elif self.alpha[j] <= L:
self.alpha[j] = L
else:
self.alpha[j] = self.alpha[j]
self.alpha = self.alpha + self.label*self.label[j]*(alpha_old_j-self.alpha[j])
b1 = self.b - self.E - self.label*(self.alpha-alpha_old_i)*kernal(self.data[i,:],self.data[i,:])-self.label[j]*(self.alpha[j]-alpha_old_j)*kernal(self.data[j,:],self.data[i,:])
b2 = self.b - self.E[j] - self.label*(self.alpha-alpha_old_i)*kernal(self.data[i,:],self.data[j,:])-self.label[j]*(self.alpha[j]-alpha_old_j)*kernal(self.data[j,:],self.data[j,:])
self.b = (b1+b2)/2
self.error()
def smo(self, epsilon, max_iter, C):
for k in range(max_iter):
I = np.intersect1d(np.argwhere(self.alpha>0),np.argwhere(self.alpha<C))
d = []
for i in I:
d.append(np.abs(self.label*self.f(i)-1))
i = np.argmax(np.array(d))
j = np.argmax(self.E-self.E)
self.update(i,j,C)
ge = np.array([i for i in range(self.len) if self.alpha>=1])
eq = np.array([i for i in range(self.len) if self.alpha==1])
le = np.array([i for i in range(self.len) if self.alpha<=1])
Ge = []
Eq = []
Le = []
for ig in ge:
Ge.appned(self.label[ig]*self.f(ig))
for ie in eq:
Eq.append(self.label[ie]*self.f(ie))
for il in le:
Le.append(self.label[il]*self.f(il))
Ge = np.array(Ge)
Eq = np.array(Eq)
Le = np.array(Le)
ne = np.sum(np.abs(Eq-1)>epsilon)
ng1 = np.sum(np.abs(Ge-1)<0)
ng2 = np.sum(np.abs(Ge-1)>epsilon)
nl1 = np.sum(np.abs(Le-1)>0)
nl2 = np.sum(np.abs(Le-1)>epsilon)
if (ne+ng1+ng2+nl1+nl2)==0:
break
def predict(self, x):
s = 0
for k in range(self.len):
s += self.alpha[k]*self.label[k]*kernal(self.data[k,:],x)
s = s + self.b
if s >=0:
return 1
else:
return -1
本文使用 Zhihu On VSCode 创作并发布 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|