一 线性可分
给定一个数据集T = {(X1,Y1), (X2 ,Y2) ,… ,(XN ,YN)}
如果存在某个超平面 ω * x+b=0 在 wx+b>0的情况下 y都等于+1 在 wx+b<0的情况下 y 都等于-1 则认为数据集T是线性可分的,否则认为数据集T是线性不可分的。
二 支持向量机模型介绍
在这幅图上,我们假设红色的点是正例(y=+1) 蓝色的点是负例(y=-1) 很明显我们可以找到一条直线将图中数据按照它们的标签 (y) 完美切割 ,但是这样可供选择的直线很多,我们在图上已经给出了三条,那该选择哪一条呢? 支持向量机就是为了解决这个问题而生的。
直观上来看我们会选择第二条直线,来做为一个分割线
看下面这张图
在这张图中我们可以发现如果现在有一个正样本 a 的话 直线1会把它误分为 负样本
再看下面这张图
在这张图中我们可以发现如果现在有一个负样本 b 的话 直线3会把它误分为正样本
通过以上我们也发现 直线1 和 直线3 对稍微有点偏差的样本容忍度不高,会将它们错误分类。 而 直线2 对两类样本包容度较好 。 而支持向量机就是要找到像直线2这样的最优分割直线 ,使离分割直线最近的样本与分割直线的间隔要尽可能大,尽最大可能的可以容忍偏差样本。
只不过当我们的空间不是像上图那样的二维平面,而是更高维的空间时,支持向量机也从寻找一条最优分割线 转变为寻找一个最优分割面。
一般地, 当训练数据集线性可分时,存在无穷个分离超平面可将两类数据正确分开。线性可分支持向量机利用间隔最大化求最优分离超平面,这时,解是唯一的。
支持向量
在线性可分情况下,训练数据集的样本点中与分离超平面距离最近的样本点的实例称为支持向量。
在H1和H2上的点就是支持向量
三 函数间隔与几何间隔
接下来我们介绍两个概念 函数间隔和几何间隔
首先看这样一幅图 在这张图上有一条直线将两类数据 分开 我们定义直线上方的圆圈 为正样本, 直线下方的 × 为负样本。
图中有三个“正" 样本 A , B, C 。点 A 距分离超平面较远,若预测该点为正类,就比较确信预测是正确的点。 C 距分离超平面较近,若预测该点为正类就不那么确信。点 B 介于点 A 与 C 之间,预测其为正类的确信度也在 A 与 C 之间。 我们可以用距离分离平面的远近来作为分类确信度的一个衡量。
设我们的分离超平面为 w*x +b =0 其中 w(w是一个向量 由w1,w2,w3,…wn 组成) 是分离超平面的法向量, b 是分离超平面的截距。根据点到平面的距离公式
我们发现对于给定的分离超平面, 对于任意点,在上述距离公式中 , 分母是一样的
分子
可以相对的去衡量样本到分离超平面的远近。而 ω . x+b 的符号与类标记 y 的符号是否一致能够表示分类是否正确。所以可用量 y(ω • x + b) 来表示分类的正确性及确信度,这就是函数间隔。
下面给出函数间隔的定义
在选择分离超平面时仅仅有函数间隔是不够的 当我们把 w,b 扩大为 2w, 2b ,分离超平面 wx+b=0 和 分离超平面 2wx+ 2b=0 仍然是一个平面 但是我们发现将参数扩大后,函数间隔竟然扩大了两倍。所以我们需要对平面的法向量加一些约束,使得间隔是确定的。 为此引进了几何间隔。
给出几何间隔的定义
L2范数定义如下
我们发现在正确分类的情况下, 几何间隔就是点到平面的距离公式
四 线性可分支持向量机
下面给出线性可分支持向量机的定义
间隔最大化
线性支持向量机学习的基本思想是求解能够正确划分训练数据集并且几何间隔最大的分离超平面。
间隔最大化的直观解释是:对训练数据集找到几何间隔最大的超平面意味着以充分大的确信度对训练数据进行分类。也就是说,不仅将正负实例点分开,而且对最难分的实例点(离超平面最近的点)也有足够大的确信度将它们分开。这样的超平面应该对未知的新实例有很好的分类预测能力。
求解几何间隔最大的分离超平面可以表示为如下的约束最优化问题
根据 函数间隔和几何间隔的关系
我们可以将上述约束最优化问题转换成如下约束最优化问题
我们清楚 wx+b =0 和 awx+ ab=0 是一个超平面。 所以我们可以通过用a来对超平面进行适当放缩来使我们的支持向量(离超平面最近的样本点) 到超平面的函数间隔等于1
凸优化问题
所以我们上述求解的最优化问题是一个凸二次规划问题
线性可分支持向量机学习算法
我们发现线性可分支持向量机学习算法中关键步骤是求解约束最优化问题,来得到最优解。
求解约束最优化问题
在看下面内容前请先看原始问题与对偶问题 了解原问题,对偶问题,原问题与对偶问题的关系,以及KKT条件
首先我们建立拉格朗日函数将带约束的求极值问题,转换为不带约束的求极值问题。我们为每一个不等式引进拉格朗日乘子,拉格朗日函数如下
根据拉格朗日对偶性,原始问题的对偶问题是极大极小问题:
所以,为了得到对偶问题的解, 需要先求 L(w, b,α) 对 ω, b 的极小,再求对 α 的极大。
求 L(w, b,α) 对 ω, b 的极小需要对 ω, b求偏导,具体步骤如下所示:
定理 设 α*= (α1* ,α2* …αl *)T 是对偶问题的解,则 存在下标 j,使得 αj * > 0,并可按下式求得原始最优化问题 的解 w * , b * :
证明如下
线性可分支持向量机学习算法
输入: 线性可分训练集 T={(x1,y1), (x2, y2), (xn, yn)}
输出:分离超平面和分类决策函数。
1 构造井求解约束最优化问题
2 计算 w* b*
3 求得分离超平面
分离决策函数
五 线性支持向量机
我们在上面介绍的线性可分支持向量机是针对线性可分的数据而设计的,但是其对线性不可分的数据是不适用的 。
下面这张图上的样本是线性可分的 ,按照我们之前的定义,定义支持向量到分割超平面的函数间隔为1 对于线性可分的数据来说,它的非支持向量样本点到分割超平面的函数间隔都是大于1 的。
在上面那张图的基础上加上几个"异常样本点" 我们发现这些异常样本点到超平面的函数间隔并不满足大于1 我们观察到这时的数据已经不再线性可分
但其实除去这些"异常样本点" 我们其余的样本点仍是线性可分的, 如何让这些"异常样本点" 也满足函数间隔大于等于1的约束条件呢 ?
解决办法是对每一个异常样本点(xi,yi) 引进一个松弛变量
使得函数间隔加上松弛变量大于等于 1。
这样我们的约束条件就变成了
C>0 是一个惩罚参数,C 值大时对误分类的惩罚增大, C 值小时对误分类的惩罚减小。
最小化该函数的意义 一 是使尽量小,也就是使间隔尽可能大。二是使误分类点的个数尽量小(我认为这里的意思是因为只有误分类点才需要松弛变量,所以最小化松弛变量的和就相当于使误分类点的个数尽量小)。 C是调和二者的参数。
线性支持向量机的学习问题是如下的凸二次规划问题
下面给出线性支持向量机的定义
线性支持向量机学习的对偶算法
下面来学习一个定理 可以通过 α 去求出 w,b
定理 设 α*= (α1* , α2 *,…, αl * )T 是对偶最优化问题的解,则 存在下标 j,使得 αj * > 0,并可按下式求得原始最优化问题的解 w * ,b * :
由 (7.52) 可得
对于这个式子
若存在 αj * , 0 < αj * < C,则 yj (w * xj +b * ) -1 =0
所以
线性支持向量机学习算法
(1)选择惩罚参数 C> 0,构造并求解凸二次规划问题
(2) 选择α * 的一个分量 αj * 适合条件 0 <αj * < C,计算
(3) 求得分离超平面
分类决策函数:
六 非线性支持向量机
非线性分类问题
非线性分类问题是指通过利用非线性模型才能更好地进行分类的问题。
如下图所示 图中的数据无法用直线将样本进行分割,但是利用一个椭圆可以将其中的正负样本分离开
非线性问题难以求解,所以一般选择进行一个非线性变换,将非线性问题变换为线性问题,通过解变换后的线性问题的方法求解原来的非线性问题。
如我们可以找到一个变换, 将上图中的椭圆变换成下图的直线
用线性分类方法求解非线性分类问题分为两步 :首先使用一个变换将原空间的数据映射到新空间。然后在新空间里用线性分类学习方法从训练数据 中学习分类模型。
核函数
常用的核函数
将核函数应用在支持向量机
我们注意到在线性支持向量机的对偶问题中,无论是目标函数还是决策函数(分离超平面)都只涉及输入实例与实例之间的内积。
非线性支持向量机学习算法
七 序列最小最优化算法(SMO)
SMO 算法是一种启发式算法,其基本思路是:如果所有变量的解都满足此最优化问题的 KKT 条件 (Karush-Kuhn-Tucker conditions) ,那么这个最优化问题的解就得到了。因为 KKT 条件是该最优化问题的充分必要条件。否则,选择两个变量,固 定其他变量,针对这两个变量构建一个二次规划问题。这个二次规划问题关于这两个变量的解应该更接近原始二次规划问题的解,因为这会使得原始二次规划问题的目标函数值变得更小。重要的是,这时子问题可以通过解析方法求解,这样就可以大大提 高整个算法的计算速度。子问题有两个变量, 一个是违反 KKT 条件最严重的那一个, 另一个由约束条件自动确定。如此, SMO 算法将原问题不断分解为子问题并对子问题求解,进而达到求解原问题的目的。
一次选择两个变量是因为要满足约束条件
如果我们选择了违反KKT条件最严重的变量α2 那么另一个变量α1根据约束条件为
SMO算法的优化子问题写成如下形式
因为当y1=y2 时 α1+α2=k(常数)
当y1! =y2 时 α1-α2=k(常数)
我们可以把原来对两个变量的优化问题转换为对一个变量的优化问题 考虑转换为 对α2的优化问题
下面,首先求沿着约束方向未经剪辑时 α2 的最优解
α2 new,unc
然后再求剪辑后的的解 α2 new。
我们用定理来叙述这个结果。
记
令
证明如下
我们得到α2 的解,使其满足约束
则
变量的选择方法
第一个变量的选择
SMO算法第一个变量的选择是一个外层循环,外层循环在训练样本中选取
违反KKT条件最严重的样本,将其对应的αi 作为第一个变量。 具体是检验训练样本点(xi,yi) 是否满足KKT条件
其中
该检验是在 ε 范围内进行的。首先检查0<αi<C 的样本点 (支持向量点) 如果这些样本点都满足KKT, 就遍历整个训练集,检验其余样本点是否满足KKT条件
第二个变量的选择
在外层循环中我们已经找到了第一个变量α1, 在内层循环中我们要找第二个变量 α2 。它的选择标准是希望α2有足够的变化。
根据式子
我们知道α2 依赖于|E1 - E2| ,在选择α2 时 一种简单的做法是使其对应的 |E1 - E2|最大,因为在之前我们已经选择了第一个变量α1
根据
α1确定 E1也确定。 如果 E1 是正的,那么选择最小的 Ei 作为 E2; 如果 E1 是负的,那么选择最大 的Ei作为 E2。
如果内层循环通过以上方法选择的α2不能使目标函数有足够的 下降,那么采用以下启发式规则继续选择α2。遍历在间隔边界上的支持向量点,依次 将其对应的变量作为α2试用,直到目标函数有足够的下降。若找不到合适的α2,那么遍历训练数据集:若仍找不到合适的α2, 则放弃第 1 个α1,再通过外层循环寻求另 外的α1。
参考 李航《统计学习方法》
八 代码实现
class SVM:
def __init__(self,X_train,Y_train,X_test,Y_test,kernel,d,sigma,C,e):
# 训练集特征向量
self.X_train=X_train
# 训练集标签
self.Y_train=Y_train
# 测试集特征向量
self.X_test=X_test
# 测试集标签
self.Y_test=Y_test
# 拉格朗日乘子
self.a=np.array(self.init_a())
# 偏置 b
self.b=0
# 核函数
self.kernel=kernel
# 多项式核函数的参数 d
self.d=d
# 高斯核函数参数
self.sigma=sigma
# 惩罚参数
self.C = C
#选择a2使目标函数下降必须大于e
self.e=e
# 预测值与真实值的误差Ei
self.Ei = self.cal_Ei()
def init_a(self):
#初始化a a的每一个元素初始化为一个二值列表 第一个值是a[i]的大小 第二个值是a[i] 的下标i 因为之后可能涉及到切片 打乱a的下标
a=[]
for i in range(self.X_train.shape[0]):
a.append([0,i])
return a
def select_kernel(self,x1,x2):
#实现核函数
if self.kernel=='linear':
return self.linear_kernel(x1,x2)
elif self.kernel=='poly':
return self.poly_kernel(x1,x2)
else:
return self.rbf_kernel(x1,x2)
def linear_kernel(self, x1, x2):
#实现线性核函数
return np.dot(x1.T, x2)
def poly_kernel(self,x1,x2):
#实现多项式核函数
return np.power(np.dot(x1.T, x2) + 1, self.d)
def rbf_kernel(self,x1,x2):
#实现高斯核函数
return np.exp(-(self.euclidean_distance(x1, x2) ** 2) / (2*self.sigma ** 2))
def euclidean_distance(self,x1,x2):
#实现欧氏距离公式
sum=0
for i in range(x1.shape[0]):
sum += np.power(x1[i]-x2[i],2)
return np.sqrt(sum)
def cal_gx(self,x):
#计算样本x的预测值 g(x)
gx=0
#d = self.a[(self.a[:, 0] > 0 and self.a[:, 0] < self.C), :]
#left = self.Train[(self.Train[:, i] <= j), :]
d = self.a[(self.a[:, 0] > 0), :]
d=d[(d[:,0]<self.C),:]
for i in range(d.shape[0]):
gx+= self.d[i][0]*self.Y_train[d[i][1]][0]*self.select_kernel(self.X_train[d[i][1]],x)
gx+=self.b
return gx
def cal_Ei(self):
# 计算Ei的初始值
Ei=[0]*self.X_train.shape[0]
for i in range(self.X_train.shape[0]):
Ei[i]=self.cal_gx(self.X_train[i])-self.Y_train[i][0]
return Ei
def satisfy_kkt(self,i):
# 检查第i个样本点是否满足kkt条件
if self.a[i][0] ==0 and self.Y_train[i][0]*self.cal_gx(self.X_train[i])>=1:
return True
elif (self.a[i][0] >0 and self.a[i][0]<self.C ) and self.Y_train[i][0]*self.cal_gx(self.X_train[i])==1:
return True
elif self.a[i][0]==self.C and self.Y_train[i][0]*self.cal_gx(self.X_train[i])<=1:
return True
else:
return False
def target_function(self,index1,index2):
#计算李航统计学习方法7.101式 目标函数
W=1/2*self.select_kernel(self.X_train[index1],self.X_train[index1])*(self.a[index1][0]**2)
W+=1/2*self.select_kernel(self.X_train[index2],self.X_train[index2])*(self.a[index2][0]**2)
W+=self.Y_train[index1][0]*self.Y_train[index2][0]*self.select_kernel(self.X_train[index1],self.X_train[index2])*self.a[index1][0]*self.a[index2][0]
W -=self.a[index1][0]
W -= self.a[index2][0]
for i in range(self.X_train.shape[0]):
if i != index1 and i != index2:
W+=self.Y_train[index1][0]*self.a[index1][0]*self.Y_train[i][0]*self.a[i][0]*self.select_kernel(self.X_train[index1],self.X_train[i])
W += self.Y_train[index2][0] * self.a[index2][0] * self.Y_train[i][0] * self.a[i][0] * self.select_kernel(self.X_train[index2], self.X_train[i])
return W
def select_variable(self):
#smo算法选择第一个变量
# 间隔边界上的支持向量
#data1=self.a[(self.a[:, 0] >0 and self.a[:, 0] <self.C), :]
data1 = self.a[(self.a[:, 0] > 0), :]
data1 = data1[(data1[:, 0] < self.C), :]
# 其余样本点
data2_1 = self.a[(self.a[:, 0] == 0), :]
data2_2 =self.a[(self.a[:, 0] == self.C),:]
data2 = np.concatenate((data2_1, data2_2), axis=0)
index_1=0
index_2=0
a1_old=0
a2_old=0
for i in range(data1.shape[0]):
#首先检查所有间隔边界上的支持向量是否满足kkt条件
# 发现不满足kkt条件的支持向量
if self.satisfy_kkt(data1[i][1]) == False:
# true 代表仍有不满足kkt条件的变量 self.data1[i][1]是它的下标
index_1=data1[i][1]
index_2=self.select_second_variable(index_1) #得到第二个变量
W1=self.target_function(index_1,index_2)
a1_new, a2_new, a1_old, a2_old=self.cal_update_a1_a2(index_1,index_2)
self.a[index_1][0] = a1_new # 更新其值
self.a[index_2][0] = a2_new
W2=self.target_function(index_1,index_2)
self.a[index_1][0] = a1_old # 更新其值
self.a[index_2][0] = a2_old
if W1-W2>self.e:
return True,index_1,index_2
else: #更新失败
for j in range(data1.shape[0]): #遍历间隔边界上的支持向量依次作为a2
if index_1!=data1[j][1]:
index_2=data1[j][1]
W1 = self.target_function(index_1, index_2)
a1_new, a2_new, a1_old, a2_old = self.cal_update_a1_a2(index_1, index_2)
self.a[index_1][0] = a1_new # 更新其值
self.a[index_2][0] = a2_new
W2 = self.target_function(index_1, index_2)
self.a[index_1][0] = a1_old # 更新其值
self.a[index_2][0] = a2_old
if W1 - W2 > self.e:
return True,index_1, index_2
for j in range(data2.shape[0]): # 遍历整个训练集依次作为a2
if index_1!=data2[j][1]:
index_2 =data2[j][1]
W1 = self.target_function(index_1, index_2)
a1_new, a2_new, a1_old, a2_old = self.cal_update_a1_a2(index_1, index_2)
self.a[index_1][0] = a1_new # 更新其值
self.a[index_2][0] = a2_new
W2 = self.target_function(index_1, index_2)
self.a[index_1][0] = a1_old # 更新其值
self.a[index_2][0] = a2_old
if W1 - W2 > self.e:
return True,index_1, index_2
for i in range(data2.shape[0]):
# 如果所有的间隔边界上的支持向量都满足kkt条件 遍历整个训练集检查它们是否满足kkt条件
# 发现不满足kkt条件的样本点
if self.satisfy_kkt(data2[i][1]) == False:
# true 代表仍有不满足kkt条件的变量 self.data1[i][1]是它的下标
index_1 = data2[i][1]
index_2 = self.select_second_variable(index_1) # 得到第二个变量
W1 = self.target_function(index_1, index_2)
a1_new, a2_new, a1_old, a2_old = self.cal_update_a1_a2(index_1, index_2)
self.a[index_1][0] = a1_new # 更新其值
self.a[index_2][0] = a2_new
W2 = self.target_function(index_1, index_2)
self.a[index_1][0] = a1_old # 更新其值
self.a[index_2][0] = a2_old
if W1 - W2 > self.e:
return True, index_1, index_2
else: # 更新失败
for j in range(data1.shape[0]): # 遍历间隔边界上的支持向量依次作为a2
if index_1 != data1[j][1]:
index_2 = data1[j][1]
W1 = self.target_function(index_1, index_2)
a1_new, a2_new, a1_old, a2_old = self.cal_update_a1_a2(index_1, index_2)
self.a[index_1][0] = a1_new # 更新其值
self.a[index_2][0] = a2_new
W2 = self.target_function(index_1, index_2)
self.a[index_1][0] = a1_old # 更新其值
self.a[index_2][0] = a2_old
if W1 - W2 > self.e:
return True, index_1, index_2
for j in range(data2.shape[0]): # 遍历整个训练集依次作为a2
if index_1 != data2[j][1]:
index_2 = data2[j][1]
W1 = self.target_function(index_1, index_2)
a1_new, a2_new, a1_old, a2_old = self.cal_update_a1_a2(index_1, index_2)
self.a[index_1][0] = a1_new # 更新其值
self.a[index_2][0] = a2_new
W2 = self.target_function(index_1, index_2)
self.a[index_1][0] = a1_old # 更新其值
self.a[index_2][0] = a2_old
if W1 - W2 > self.e:
return True, index_1, index_2
# 表示所有的变量都满足kkt条件
return False,-1,-1
def select_second_variable(self,index_1):
#smo算法选择第二个变量
#select_first,index_1=self.select_first_variable()
max_absolute_E = 0
maei=0
max_E=0
maxei=0
min_E=sys.maxsize
minei=0
# 如果已经选择了第一个变量
E1 = self.Ei[index_1]
for i in range(len(self.Ei)):
# 遍历self.Ei 选取最大的Ei 最小的Ei 绝对值最大的Ei 并且记录它们的下标
if i!=index_1:
#保证第二个变量和第一个变量不是相同的
ae=math.fabs(self.Ei[i])
if ae>max_absolute_E:
max_absolute_E=ae
maei=i
if self.Ei[i]>max_E:
max_E=self.Ei[i]
maxei=i
if self.Ei[i]<min_E:
min_E=self.Ei[i]
minei=i
if E1==0:
#如果E1等于0 就返回绝对值最大的Ei的下标
return maei
elif E1>0:
#如果E1>0 返回值最小的Ei的下标
return minei
else:
#如果E1<0 返回值最大的Ei的下标
return maxei
def cal_boundary(self,index_1,index_2):
#求a2 new 的取值范围 (L,H)
a1_old=self.a[index_1][0]
a2_old=self.a[index_2][0]
if self.Y_train[index_1][0]!=self.Y_train[index_2][0]:
# y1不等于y2的情况
L = max(0,(a2_old-a1_old))
H=min(self.C,(self.C+a2_old-a1_old))
else:
#y1 等于 y2的情况
L=max(0,(a2_old+a1_old-self.C))
H=min(self.C,(a2_old+a1_old))
return L,H
def cal_update_a1_a2(self,index_1,index_2):
#计算更新后的a1,a2
x1=self.X_train[index_1]
x2=self.X_train[index_2]
y1=self.Y_train[index_1][0]
y2 =self.Y_train[index_2][0]
a1_old = self.a[index_1][0]
a2_old = self.a[index_2][0]
E1=self.Ei[index_1]
E2=self.Ei[index_2]
k=self.select_kernel(x1,x1)+self.select_kernel(x2,x2)-2*self.select_kernel(x1,x2)
a2_new = a2_old + y2*(E1-E2)/k
L,H=self.cal_boundary(index_1,index_2)
if a2_new>H:
a2_new=H
elif a2_new <L:
a2_new=L
a1_new=a1_old+y1*y2*(a2_old-a2_new)
return a1_new,a2_new,a1_old,a2_old
def update_b_Ei(self,index_1,index_2,a1_old,a2_old):
#在每次完成两个变量的优化后,都要重新计算一下b
x1 = self.X_train[index_1]
x2 = self.X_train[index_2]
y1 = self.Y_train[index_1][0]
y2 = self.Y_train[index_2][0]
a1_new= self.a[index_1][0]
a2_new= self.a[index_2][0]
E1 = self.Ei[index_1]
E2 = self.Ei[index_2]
b1_new= -E1 - y1 * self.select_kernel(x1,x1) * (a1_new - a1_old) - y2 * self.select_kernel(x2,x1) * (a2_new-a2_old) + self.b
b2_new = -E2 - y1 * self.select_kernel(x1, x2) * (a1_new - a1_old) - y2 * self.select_kernel(x2, x2) * (a2_new - a2_old) + self.b
if a1_new>0 and a1_new<self.C:
self.b=b1_new
elif a2_new>0 and a2_new<self.C:
self.b=b2_new
else:
self.b=(b1_new+b2_new)/2
# 支持向量机集合
# data1 = self.a[(self.a[:, 0] > 0 and self.a[:, 0] < self.C), :]
sum1=0
sum2=0
g1=self.cal_gx(x1)
g2 = self.cal_gx(x2)
self.Ei[index_1]=g1-y1
self.Ei[index_2] =g2 - y2
def smo(self):
#smo算法
select_variable,index_1,index_2=self.select_variable()
i=0
while select_variable: #如果仍有变量没有满足kkt条件
print(i)
i+=1
a1_new,a2_new,a1_old,a2_old=self.cal_update_a1_a2(index_1,index_2)
self.a[index_1][0] = a1_new#更新其值
self.a[index_2][0] = a2_new
self.update_b_Ei(index_1,index_2,a1_old,a2_old)
print(self.predict())
select_variable, index_1, index_2 = self.select_variable()
def predict(self):
right=0
#data1 = self.a[(self.a[:, 0] > 0 and self.a[:, 0] < self.C), :]
for i in range(self.X_test.shape[0]):
f=0
f=self.cal_gx(self.X_test[i])
if f>0:
y=1
else:
y=-1
if y==self.Y_test[i][0]:
right+=1
return right/self.X_test.shape[0]