支持向量机part2

8.3 非线性支持向量机与核函数

非线性数据集则使用非线性支持向量机,其主要特点是利用核技巧(kernel trick)。为此,先要介绍核技巧。

8.3.1 核技巧

  1. 非线性分类问题

    非线性分类问题是指通过利用非线性模型才能很好地进行分类的问题。

    如下图所示:

    ch08-4

    一般来说,对给定的一个训练数据集,其中,实例属于输入空间,,对应的标记有两类,i=1,2,…,N。如果能用中的一个超曲面将正负例正确分开,则称这个问题为非线性可分问题

    非线性问题的解决思路:对非线性数据集进行非线性变换,将非线性问题变换为线性问题,通过解变换后的线性问题的方法求解原来的非线性问题。

    对图8.7所示的例子,通过变换,将左图中的椭圆变换成右图中的直线,将非线性分类问题变换为线性分类问题:

    设原空间为,新空间为,定义从原空间到新空间的变换(映射):

    经过变换,原空间变换为新空间,原空间中的点相应地变换为新空间中的点,原空间中的椭圆:

    变换成为新空间中的直线:

    在变换后的新空间里,直线可以将变换后的正负实例点正确分开。

    这样,原空间的非线性可分问题就变成了新空间的线性可分问题

  2. 核函数的定义

    定义8.6(核函数)是输入空间(欧式空间的子集或离散集合),又设为特征空间(希尔伯特空间),如果存在一个从的映射:

    使得对所有,函数满足条件:

    则称为核函数,为映射函数。

    核技巧的思路:在学习与预测中只定义核函数,而不显式地定义映射函数。通常,直接计算比较容易,而通过计算并不容易。

  3. 核技巧在支持向量机中的应用

    在线性支持向量机的对偶问题的目标函数(8.37)中的内积可以用核函数来代替。此时对偶问题的目标函数为:

    同样,分类决策函数中的内积也可以用核函数代替,而分类决策函数式(8.56)变成:

    等价于经过映射函数将原来的输入空间变换到一个新的特征空间,将输入空间中的内积变换为特征空间中的内积

    在新的特征空间中学习线性支持向量机,当映射函数是非线性函数时,学习到的含有核函数的支持向量机是非线性分类模型。

8.3.2 正定核

函数满足什么条件才能成为核函数?(通常所说的核函数就是正定核函数)。

假设是定义在上的对称函数,并且对任意的关于的Gram矩阵是半正定的。可以依据函数,构成一个希尔伯特空间,其步骤是:

  1. 首先定义映射并构成向量空间S;
  2. 然后在S上定义内积构成内积空间
  3. 最后将S完备化构成希尔伯特空间

证明如下所示:

1.定义映射,构成向量空间S

先定义映射

根据这一映射,对任意,i=1,2,…,m,定义线性组合

考虑由线性组合为元素的集合。由于集合对加法和数乘运算是封闭的,所以构成一个向量空间

2.在上定义内积,使其成为内积空间

上定义一个运算*:对任意

定义运算*

证明运算*是空间的内积。为此要证:

其中,(8.74)~(8.76)由式(8.70)~式(8.72)及的对称性容易得到。

现证式(8.77)。由式(8.70)及式(8.73)可得:

由Gram矩阵的半正定性知上式右端非负,即

再证式(8.78)。充分性显然。为证必要性,首先证明不等式:

,则,于是,

其左端是的二次三项式,非负,其判别式小于等于0,即

于是式(8.79)得证。现证若,则。事实上,若

则按运算*的定义式(8.73),对任意的,有

于是,

由式(8.79)和式(8.77)有:

由式(8.80)有

此式表明,当时,对任意的x都有

至此,证明了为向量空间的内积。赋予内积的向量空间为内积空间。*因此是一个内积空间

既然*为的内积运算,那么仍然用表示,即若:

则,

3.将内积空间完备化为希尔伯特空间

由式(8.81)定义的内积可以得到范数:

因此,是一个赋范向量空间。根据泛函分析理论,对于不完备的赋范向量空间,一定可以使之完备化,得到完备的赋范向量空间

一个内积空间,当作为一个赋范向量空间是完备的时候,就是希尔伯特空间

核函数K具有再生性,即满足:

称为再生核。

4.正定核的充要条件

定理8.5(正定核的充要条件)是对称函数,则为正定核函数的充要条件是对任意,i=1,2,…,m, 对应的Gram矩阵

是半正定矩阵。

证明:

必要性。由于上的正定核,所以存在从到希尔伯特空间的映射使得:

于是,对任意,构造关于的Gram矩阵:

对任意,有

表明关于Gram矩阵是半正定的

充分性。已知对称函数对任意关于Gram矩阵是半正定的(即)。根据前面的结果,对给定的可以构造从到某个希尔伯特空间的映射:

由式(8.83)可知,

并且

由式(8.86)即得

表明上的核函数。

定义8.7(正定核的等价定义)是定义在上的对称函数,如果对任意,i=1,2,…,m,对应的Gram矩阵

是半正定矩阵,则称是正定核。

正定核的检验比较难,因为要求对任意,i=1,2,…,m,验证K对应的Gram矩阵是否为半正定的。

所以往往使用已有的核函数。另外,由Mercer定义可以得到Mercer核,正定核比Mercer核更具一般性。

8.3.3 常用核函数

1.多项式核函数

对应的支持向量机是一个p次多项式分类器。在此情形下,分类决策函数成为

2.高斯核函数

对应的支持向量机是高斯径向基函数分类器。在此情形下,分类决策函数成为:

3.字符串核函数

核函数不仅仅可以定义在欧式空间上,还可以定义在离散数据的集合上。比如,字符串核是定义在字符串集合上的核函数。字符串核函数在文本分类、信息检索、生物信息学等方面都有应用。

字符串核函数给出了字符串s和t中长度等于n的所有子串组成的特征向量的余弦相似度。直观上,两个字符串相同的子串越多,它们就越相似,字符串核函数的值就越大。字符串核函数可以由动态规划快速地计算

8.3.4 非线性支持向量分类机

如上所述,利用核技巧,可以将线性分类的学习方法应用到非线性分类问题中去。将线性支持向量机扩展到非线性支持向量机,只需将线性支持向量机对偶形式中的内积换成核函数。

定义8.8(非线性支持向量机) 从非线性分类训练集,通过核函数与软间隔最大化,或凸二次规划(8.95~8.97),学习得到的分类决策函数。

称为非线性支持向量,是正定核函数。

算法8.4(非线性支持向量机学习算法)

输入:训练数据集,其中,i=1,2,…,N;

输出:分类决策函数

(1)选取适当的核函数和适当的参数C,构造并求解最优化问题:

求得最优解

(2)选择的一个正分量,计算

(3)构造决策函数:

是正定核函数时,问题(8.95)~(8.97)是凸二次规划问题,解是存在的。

8.4 序列最小最优化算法

SMO(sequential minimal optimization)算法要解如下凸二次规划的对偶问题:

在这个问题中,变量是拉格朗日乘子,一个变量对应于一个样本点;变量的总数等于训练样本容量N。

SMO算法包括两个部分:求解两个变量二次规划的解析方法和选择变量的启发式方法。

8.4.1两个变量二次规划的求解方法

不失一般性,假设选择的两个变量是,其他变量是固定的。于是SMO的最优化问题(8.98)~(8.100)的子问题可以写成:

其中,,i,j=1,2,…,N,是常数,目标函数式(8.101)中省略了不含的常数项。

为了求解两个变量的二次规划问题(8.101)~(8.103):

  1. 首先分析约束条件
  2. 然后在此约束条件下求极小。

8.4.2 分析约束条件

由于只有两个变量,约束可以用二维空间中的图形表示,如下图所示:

ch08-5

不等式约束(8.103)使得在盒子内,等式约束(8.102)使在平行于盒子的对角线的直线上。因此要求的是目标函数在一条平行于对角线的线段上的最优值。

假设问题(8.101)~(8.103)的初始可行解为,最优解为,并且假设在沿着约束方向未经剪辑时的最优解为

由于满足不等式约束条件(8.103),所以最优值的取值范围必须满足条件:

其中,L与H是所在的对角线段端点的界。

如果,则

L式子max函数的第二项,对应于,而

H式子max函数的第二项,对应于,同理可得以上表达式。

如果,则

首先,求沿着约束方向未经剪辑即未考虑不等式约束(8.103)时的最优解

然后,再求剪辑后的解。我们用定理来叙述这个结果,为了叙述简单,记:

当i=1,2时,为函数g(x)对输入预测值与真实输出之差

定理8.6 最优化问题(8.101)~(8.103)沿着约束方向未经剪辑时的解是:

其中,

关于是输入空间到特征空间的映射,,i=1,2,由式(8.105)给出。

经剪辑后的解是

求得是(根据式(8.102))

证明:

引进记号:

目标函数可写成

,可将表示为:

代入式(8.110),得到只是的函数的目标函数:

求导数

令其为0,得到

代入,得到:

代入,于是得到:

要使其满足不等式约束必须将其限制在区间[L,H]内,从而得到的表达式(8.108)。由等式约束(8.102)得到的表达式(8.109)。于是得到最优化问题(8.101)~(8.103)的解()。

8.4.3 变量的选择方法

SMO算法在每个子问题中选择两个变量优化,其中至少一个变量是违反KKT条件的。

1.第 1个变量的选择

SMO称选择第1个变量的过程为外层循环。外层循环在训练样本中选取违反KKT条件最严重的样本点,并将其对应的变量作为第1个变量。具体地,检验训练样本点是否满足KKT条件,即

其中,

该检验是在指定阈值范围内进行的。在检验过程中,外层循环首先遍历所有满足条件的样本点,即在间隔边界上的支持向量点,检验它们是否满足KKT条件。如果这些样本点都满足KKT条件,那么遍历整个训练集,检验它们是否满足KKT条件。

2.第2个变量的选择

SMO称选择第2个变量的过程为内层循环。假设在外层循环中已经找到第1个变量,现在要在内层循环中找第2个变量第2个变量选择的标准是希望能使有足够大的变化

由式(8.106)和式(8.108)可知,是依赖于的。一种简单做法是选择,使其对应的最大。(因为已定,也确定了。如果是正的,那么选择最小的作为;如果是负的,那么选择最大的作为)。为节省时间,把所有保存到一个列表中

在特殊情况下,如果内层循环通过以上方法选择的不能使目标函数有足够的下降,那么采用以下启发式规则继续选择

启发式规则如下所示:

  1. 遍历在间隔边界上的支持向量点,依次将其对应的变量作为使用,直到目标函数有足够的下降(使用指定阈值来标识)。
  2. 若找不到合适的,那么遍历训练数据集
  3. 若仍找不到合适的,则放弃第1个,再通过外层循环寻求另外的

3.计算阈值b和差值

在每次完成两个变量的优化后,都要重新计算阈值b。当时,由KKT条件(8.112)可知:

于是,

的定义式(8.105)有

式(8.114)的前两项可写成:

代入式(8.114),可得:

同样,如果,那么,

如果同时满足条件,i=1,2,那么

如果是0或者C,那么以及它们之间的数都是符合KKT条件的阈值,这时选择他们的中点作为

在每次完成两个变量的优化之后,还必须更新对应的值,并将它们保存在列表中

关于值的更新要用到值,以及所有支持向量对应的

其中,S是所有支持向量的集合。

8.4.4 SMO算法

算法8.5(SMO算法)

输入:训练数据集,其中,,i=1,2,…,N,精度

输出:近似解

  1. 取初值,令k=0

  2. 选取优化变量,解析求解两个变量的最优化问题(8.101)~(8.103),求得最优解,更新

  3. 若在精度范围内满足停机条件

​ 其中,

​ 则转步骤4;否则令k=k+1,转步骤2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
class MySVM(object):
def __init__(self, kernel='linear', epsilon=0.00001, C=1000, n_iter=5000):
self.kernel = kernel #核函数
self.epsilon = epsilon #用于检验kkt条件的误差范围
self.C = C #式(8.103)
self.Max_Iteration=n_iter

def _init_parameters(self, features, labels):
self.X = features
self.Y = labels

self.b = 0.0
self.N,self.n = features.shape
self.alpha = np.zeros(self.N) #对偶算法学习参数
self.E = [self._E_(i) for i in xrange(self.N)] #误差

def _satisfy_KKT(self, i):
ygx = self.Y[i] * self._g_(i)
if abs(self.alpha[i])<self.epsilon: #公式(8.111)
return ygx>=1
elif abs(self.alpha[i]-self.C)<self.epsilon:# 公式(8.113)
return ygx <= 1
else: # 公式(7.112)
return abs(ygx-1)<self.epsilon


def is_stop(self):
"""
所有的样例都满足KKT条件
"""
result = 0
for in xrange(self.N):
result += self.alpha[i]*self.Y[i]
if result>self.epsilon:
return False

for i in xrange(self.N):
satisfy = self._satisfy_KKT(i)

if not satisfy:
return False
return True


def _select_two_parameters(self):
#第一部分是alphai满足条件(0,C)的样本点,即间隔边界上的支持向量点
i1_list_1 = [i for i in xrange(self.N) if self.alpha[i]>0 and self.alpha[i]<self.C]
#第二部分是当所有支持向量点满足KKT条件时,遍历其他所有非支持向量点
i1_list_2 = [i for i in xrange(self.N) if self.alpha[i]<=0 or self.alpha[i]>=self.C]

i1_list = i1_list_1
i1_list.extend(i1_list_2)

for i in i1_list:
if self._satisfy_KKT(i):
continue
#找到一个违反KKT条件的样本点
E1 = self.E[i]
max_ = (0,0)

for j in range(self.N): #选择一个使得|Ei-Ej|最大的j
if j==i:
continue

E2 = self.E[j]
if abs(E1-E2)>max_[0]:
max_ = (abs(E1-E2),j)

return i,max_[1]

def _K_(self, x1, x2):
'''
核函数,这里只实现线性核函数,相当于线性支持向量机

'''
if self.kernel=='linear':
return np.dot(x1,x2)

return np.dot(x1,x2)

def _g_(self, i):
'''公式(7.104)'''
result = self.b
for j in xrange(self.N):
result += self.alpha[j]*self.Y[j]*self._K_(self.X[i],self.X[j])

return result

def _E_(self, i):
'''公式(7.105)'''
return self._g_(i) - float(self.Y[i])

def train(self, features, labels):
self._init_parameters(features, labels)

for i in range(self.Max_Iteration):
i1, i2 = self._select_two_parameters()

if self.Y[i1]==self.Y[i2]:
L = max(0, self.alpha[i2]+self.alpha[i1]-self.C)
H = min(self.C, self.alpha[i2]+self.alpha[i1])
else:
#Y[i1]!=self.Y[i2]
L = max(0, self.alpha[i2]-self.alpha[i1])
H = min(self.C, self.C+self.alpha[i2]-self.alpha[i1])

E1 = self.E[i1]
E2 = self.E[i2]
#公式(7.107)
eta = self._K_(self.X[i1], self.X[i1])+self._K_(self.X[i2],self.X[i2])\
-2*self._K_(self.X[i1],self.X[i2])
#print eta
#未经剪辑的alpha2
alpha2_new_unc = self.alpha[i2]+self.Y[i2]*(E1-E2)/eta #公式(8.106)

#剪辑后的alpha2#公式(8.108)
if alpha2_new_unc>H:
alpha2_new = H
elif alpha2_new_unc<L:
alpha2_new = L
else:
alpha2_new = alpha2_new_unc

#剪辑后的alpha1;公式(8.109)
alpha1_new = self.alpha[i1]+self.Y[i1]*self.Y[i2]*(self.alpha[i2]-alpha2_new)

#公式(8.115)及公式(8.116)
b_new = 0
b1_new = -E1-self.Y[i1]*self._K_(self.X[i1], self.X[i1])*(alpha1_new-self.alpha[i1]) \
-self.Y[i2]*self._K_(self.X[i2], self.X[i1])*(alpha2_new-self.alpha[i2])+self.b

b2_new = -E2-self.Y[i1]*self._K_(self.X[i1], self.X[i2])*(alpha1_new-self.alpha[i1]) \
-self.Y[i2]*self._K_(self.X[i2], self.X[i2])*(alpha2_new-self.alpha[i2])+self.b

if alpha1_new>0 and alpha1_new<self.C:
b_new = b1_new
elif alpha2_new>0 and alpha2_new<self.C:
b_new = b2_new
else:
b_new = (b1_new+b2_new)/2

#更新alpha和b
self.alpha[i1] = alpha1_new
self.alpha[i2] = alpha2_new
self.b = b_new
#更新Ei
self.E[i1] = self._E_(i1)
self.E[i2] = self._E_(i2)

self.w = self.calcWs(features, labels)

def _predict_(self, feature):
result = self.b

for i in range(self.N):
result += self.alpha[i]*self.Y[i]*self._K_(feature, self.X[i])

if result>0:
return 1

return -1

def predict(self, features):
results = []

for feature in features:
results.append(self._predict_(feature))

return results

def calcWs(self, X, y):
'''
基于alpha计算w值
'''
m,n = X.shape
w = np.zeros(n)

for i in range(m):
#print(X[i,:].shape)
w += X[i,:].T*self.alpha[i]*y[i]
return w

def plotSVM(self, features, labels):
b = self.b
fig = plt.figure()
ax = fig.add_subplot(111)

uni_label = np.unique(labels)

feature_0 = np.array(features[labels==uni_label[0]])
feature_1 = np.array(features[labels==uni_label[1]])


ax.scatter(feature_0[:,0],feature_0[:,1],color='blue', marker='o',label=str(uni_label[0]))
ax.scatter(feature_1[:,0],feature_1[:,1],color='blue', marker='x',label=str(uni_label[1]))
#决策边界
x_min = min(features[:,0])
x_max = max(features[:,0])
x = np.arange(x_min, x_max, 1.2)

y_min = min(features[:,1])
y_max = max(features[:,1])
y = (-b-self.w[0]*x)/self.w[1]

x = x[y<y_max]
y = y[y<y_max]

ax.plot(x, y)

#找到支持向量,并在图中标红
for i in range(len(labels)):
if self.alpha[i] > 0.0 and labels[i]==uni_label[0]:
ax.plot(features[i,0], features[i,1], 'ro')
elif self.alpha[i] > 0.0:
ax.plot(features[i,0], features[i,1], 'rx')
plt.legend(loc='upper left')
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#另一个版本的与统计学习方法上有所区别

class SVM(object):
def __init__(self, C=1000, toler=0.001, n_iter=40):
self.C = C
self.toler = toler
self.n_iter = n_iter

def selectJrand(self,i,m):
'''
《统计学习方法》中对j的选择是使|Ei-Ej|最大;但是这里是随机选择一个
'''
j = i
while i==j:
j = int(np.random.uniform(0,m))
return j

def clipAlpha(self, aj, H, L):
'''
更新aj,公式(7.108)
'''
if aj>H:
return H
if aj<L:
return L
return aj

def _satisfy_KKT(self,i, Yi, gx):
ygx = Yi * gx
if abs(self.alphas[i])<self.toler:
return ygx>=1
elif abs(self.alphas[i]-self.C)<self.toler:
return ygx <= 1
else:
return abs(ygx-1)<self.toler

def _E_(self, gx, yi):
#式(7.105)
return gx-yi

def _K_(self, xi, xj):
'''
核函数
'''
return np.dot(xi, xj)

def fit(self, X, y):
m,n = X.shape
#初始化b和alphas(alpha有点类似权重值)
self.b = 0
self.alphas = np.zeros(m)

for cur_iter in range(self.n_iter):
alphaPairsChanged = 0

#遍历所有的样本点,找到第一个不满足KKT条件的样本点作为第1个变量
for i in range(m):
#求最优间隔分类器的净输入
output_i = self.predict_proba(X,y,i)#(式7.104)
error_i = self._E_(output_i,float(y[i]))
#判断是否满足KKT条件
if self._satisfy_KKT(i, y[i], output_i):
continue

j = self.selectJrand(i,m)
output_j = self.predict_proba(X,y,j)
error_j = self._E_(output_j,float(y[j]))

alphaIold = self.alphas[i]
alphaJold = self.alphas[j]

if y[i]!=y[j]:
L = max(0, self.alphas[j]-self.alphas[i])
H = min(self.C, self.C+self.alphas[j]-self.alphas[i])
else:
L = max(0, self.alphas[j]+self.alphas[i]-self.C)
H = min(self.C, self.alphas[j]+self.alphas[i])


#公式(7.107)
eta = self._K_(X[i,:],X[i,:])+self._K_(X[j,:],X[j,:])-2.0*self._K_(X[i,:],X[j,:])
#print eta
if eta < 0:
continue

#未经剪辑的alphaj:更新alphas[j]
alphaj_new = self.alphas[j]+y[j]*(error_i-error_j)/eta

#经过剪辑的alphaj
alphaj_new = self.clipAlpha(alphaj_new, H, L)
self.alphas[j] = alphaj_new

if(abs(self.alphas[j]-alphaJold)<0.00001):
continue

#更新alphas[i]
self.alphas[i] = alphaIold + y[j]*y[i]*(alphaJold-self.alphas[j])

#更新b
b1_new = self.b -error_i-y[i]*(self.alphas[i]-alphaIold)*self._K_(X[i,:],X[i,:])\
-y[j]*(self.alphas[j]-alphaJold)*self._K_(X[i,:],X[j,:])

b2_new = self.b-error_j-y[i]*(self.alphas[i]-alphaIold)*self._K_(X[i,:],X[j,:])\
-y[j]*(self.alphas[j]-alphaJold)*self._K_(X[j,:],X[j,:])

b_new = (b1_new+b2_new)/2

self.b = b_new
alphaPairsChanged += 1

if alphaPairsChanged == 0:
continue
else:
break

self.w = self.calcWs(X,y)
w_norm = np.linalg.norm(self.w)
self.w = self.w/w_norm
self.b = self.b/w_norm
return self

def predict_proba(self, X, y, i):
'''
公式(7.104)
'''
result = self.b
m,n = X.shape
for j in range(m):
result += self.alphas[j]*y[j]*self._K_(X[i,:],X[j,:])
return result

def calcWs(self, X, y):
'''
基于alpha计算w值
'''
m,n = X.shape
w = np.zeros(n)

for i in range(m):
#print(X[i,:].shape)
w += X[i,:].T*self.alphas[i]*y[i]
return w

def plotSVM(self, features, labels):
features = np.matrix(features)
#b = np.array(self.b)[0]
b = self.b
fig = plt.figure()
ax = fig.add_subplot(111)

uni_label = np.unique(labels)
features_1 = features[np.array(labels==uni_label[0]).ravel()]
features_0 = features[np.array(labels==uni_label[1]).ravel()]

ax.scatter(features_1[:,0].flatten().A[0], features_1[:,1].flatten().A[0], color='blue', marker='o', label=str(uni_label[0]))
ax.scatter(features_0[:,0].flatten().A[0], features_0[:,1].flatten().A[0], color='blue', marker='x', label=str(uni_label[1]))

#决策边界
x_min = min(features[:,0])
x_max = max(features[:,0])
print(x_min)
print(x_max)

x = np.arange(x_min, x_max, 0.1)
y = (-b-self.w[0]*x)/self.w[1]
ax.plot(x, y)

#找到支持向量,并在图中标红
for i in range(len(labels)):
if self.alphas[i] > 0.0 and labels[i]==uni_label[0]:
ax.plot(features[i,0], features[i,1], 'ro')
elif self.alphas[i] > 0.0:
ax.plot(features[i,0], features[i,1], 'rx')
plt.legend(loc='upper left')
plt.show()