最优化方法Python计算:有约束优化应用——近似线性可分问题支持向量机
二分问题的数据集 { ( x i , y i ) } \{(\boldsymbol{x}_i,y_i)\} {(xi,yi)}, i = 1 , 2 , ⋯ , m i=1,2,\cdots,m i=1,2,⋯,m中,特征数据 { x i } \{\boldsymbol{x}_i\} {xi}未必能被一块超平面按其标签值 y i ∈ { − 1 , 1 } y_i\in\{-1,1\} yi∈{−1,1}分隔,即二分问题未必是线性可分的。如果存在超平面 π \pi π: ( x ⊤ , 1 ) w 0 = 0 (\boldsymbol{x}^\top,1)\boldsymbol{w}_0=0 (x⊤,1)w0=0,使得大多数样本点的特征数据被 π \pi π按标签数据取值分隔,满足约束 y i ( x i , 1 ) w 0 ≥ 1 y_i(\boldsymbol{x}_i,1)\boldsymbol{w}_0\geq1 yi(xi,1)w0≥1,但有少数样本点特征数据不满足此约束而交叉纠结,如下图所示。这样的问题,称为近似线性可分问题。图中的超平面 ( x ⊤ , 1 ) w 0 = ± 1 (\boldsymbol{x}^\top,1)\boldsymbol{w}_0=\pm1 (x⊤,1)w0=±1称为软边界。
解决近似线性可分问题的支持向量机模型引入松弛变量 z = ( z 1 z 2 ⋮ z m ) \boldsymbol{z}=\begin{pmatrix}z_1\\z_2\\\vdots\\z_m\end{pmatrix} z= z1z2⋮zm ,构造优化问题
{ min 1 2 w ⊤ H w + c ⊤ z s.t. y i ( x i , 1 ) w ≥ 1 − z i z i ≥ 0 i = 1 , 2 , ⋯ m . ( 1 ) \begin{cases} \min\quad \frac{1}{2}\boldsymbol{w}^\top\boldsymbol{Hw}+\boldsymbol{c}^\top\boldsymbol{z}\\ \text{s.t.\ }\quad y_i(\boldsymbol{x}_i,1)\boldsymbol{w}\geq1-z_i\\ \quad\quad\ \ z_i\geq0 \end{cases}\quad i=1,2,\cdots m.\quad(1) ⎩ ⎨ ⎧min21w⊤Hw+c⊤zs.t. yi(xi,1)w≥1−zi zi≥0i=1,2,⋯m.(1)
其中, H = ( 1 0 ⋯ 0 0 0 1 ⋯ 0 0 ⋮ ⋮ ⋱ ⋮ ⋮ 0 0 ⋯ 1 0 0 0 ⋯ 0 0 ) \boldsymbol{H}=\begin{pmatrix}1&0&\cdots&0&0\\0&1&\cdots&0&0\\\vdots&\vdots&\ddots&\vdots&\vdots\\0&0&\cdots&1&0\\0&0&\cdots&0&0\end{pmatrix} H= 10⋮0001⋮00⋯⋯⋱⋯⋯00⋮1000⋮00 , c = ( C C ⋮ C ) \boldsymbol{c}=\begin{pmatrix}C\\C\\\vdots\\C\end{pmatrix} c= CC⋮C ,正则化参数 C > 0 C>0 C>0为松弛变量的罚因子。其值越大,模型对误分类的惩罚越强;反之,模型将侧重于分类间隔。当C足够大时,上述问题退化为问题。
{ min 1 2 w ⊤ H w s.t. y i ( x i ⊤ , 1 ) w ≥ 1 i = 1 , 2 , ⋯ , m . \begin{cases} \min\quad\frac{1}{2}\boldsymbol{w}^\top\boldsymbol{Hw}\\ \text{s.t.}\quad\quad y_i(\boldsymbol{x}_i^\top,1)\boldsymbol{w}\geq1\quad i=1,2,\cdots,m. \end{cases} {min21w⊤Hws.t.yi(xi⊤,1)w≥1i=1,2,⋯,m.
根据强对偶定理,可以证明二次规划问题(1)的最优解 w 0 \boldsymbol{w}_0 w0与其对偶问题
{ min 1 2 μ ⊤ Q μ − μ ⊤ 1 s.t. μ ⊤ y = 0 o ≤ μ ≤ c . ( 2 ) \begin{cases} \min\quad\frac{1}{2}\boldsymbol{\mu}^\top\boldsymbol{Q\mu}-\boldsymbol{\mu}^\top\boldsymbol{1}\\ \text{s.t.\ \ }\quad\boldsymbol{\mu}^\top\boldsymbol{y}=0\\ \quad\quad\ \ \ \boldsymbol{o}\leq\boldsymbol{\mu}\leq\boldsymbol{c} \end{cases}.\quad{(2)} ⎩ ⎨ ⎧min21μ⊤Qμ−μ⊤1s.t. μ⊤y=0 o≤μ≤c.(2)
的最优解 μ 0 \boldsymbol{\mu}_0 μ0等价。其中, Q = X ⊤ X \boldsymbol{Q}=\boldsymbol{X}^\top\boldsymbol{X} Q=X⊤X, X = ( y 1 x 1 , y 2 x 2 , ⋯ , y m x m ) \boldsymbol{X}=(y_1\boldsymbol{x}_1,y_2\boldsymbol{x}_2,\cdots,y_m\boldsymbol{x}_m) X=(y1x1,y2x2,⋯,ymxm), μ ∈ R m \boldsymbol{\mu}\in\text{R}^m μ∈Rm。等价的意义为
w 0 = ( ∑ i ∈ s μ 0 i y i x i 1 m s ∑ j ∈ s ( y j − ∑ i ∈ s μ 0 i y i x j ⊤ x i ) ) . \boldsymbol{w}_0=\begin{pmatrix}\sum\limits_{i\in s}\mu_{0_i}y_i\boldsymbol{x}_i\\\frac{1}{m_s}\sum\limits_{j\in s}\left(y_j-\sum\limits_{i\in s}\mu_{0_i}y_i\boldsymbol{x}_j^\top\boldsymbol{x}_i\right)\end{pmatrix}. w0= i∈s∑μ0iyixims1j∈s∑(yj−i∈s∑μ0iyixj⊤xi) .
其中,
s = { i ∣ 0 ≤ i ≤ m , 0 < μ 0 i < C } s=\{i|0\leq i\leq m, 0<\mu_{0_i}<C\} s={i∣0≤i≤m,0<μ0i<C}
为支持向量下标集, m s m_s ms为 s s s支持向量个数。
下列代码实现通过求解二次规划问题(2)的近似线性可分问题支持向量机模型。
import numpy as np
from scipy.optimize import minimize
class ALineSvc(Classification, LineModel): #近似线性可分支持向量机分类器def __init__(self, C = 1e+4): #构造函数self.C = Cself.tagVal = np.signdef obj(self, mu): #目标函数return 0.5 * (mu @ (self.Q @ mu)) - mu.sum()def ynormalize(self, y, trained): #标签数据预处理if not trained:self.ymin = 0self.ymax = 1return (y - self.ymin) / (self.ymax - self.ymin)def fit(self, X, Y, mu = None): #训练函数print("训练中...,稍候")m, n = X.shapeself.scalar = (len(X.shape) == 1)self.X, self.Y = self.pretreat(X, Y)if not isinstance(mu, np.ndarray):if mu == None:mu = np.random.random(m)else:mu = np.array([mu] * m)Qk = np.array([[self.X[i] @ self.X[j] #内积矩阵for j in range(m)]for i in range(m)])self.Q=np.outer(self.Y, self.Y) * Qk #系数矩阵h = lambda x: self.Y @ x #等式约束条件函数g1 = lambda x: x #不等式约束函数1g2 = lambda x: self.C - x #不等式约束函数2cons = [{'type': 'eq', 'fun': h}, #约束条件列表{'type': 'ineq', 'fun': g1},{'type': 'ineq', 'fun': g2}]res = minimize(self.obj, mu, constraints = cons) #求解最优化问题self.mu0 = res.xself.support_ = np.where((self.mu0 > 1e-5) & #支持向量下标(self.mu0 < self.C))[0]self.w0 = (self.mu0[self.support_] * self.Y[self.support_]) @\self.X[self.support_,0:n] #模型参数前n项b0 = (self.Y[self.support_]-(self.mu0[self.support_] * self.Y[self.support_])\@ Qk[np.ix_(self.support_, self.support_)]).mean() #模型参数最末项self.w0 = np.append(self.w0, b0) #整合模型参数self.coef_, self.intercept_ = self.coef_inte() #计算超平面系数和截距print("%d次迭代后完成训练。"%res.nit)
程序中第3~44行定义的近似线性可分支持向量机分类器类ALineSvc继承了Classification(见博文《最优化方法Python计算:无约束优化应用——线性回归分类器》)和LineModel(见博文《最优化方法Python计算:无约束优化应用——线性回归模型》)的属性与方法。类定义体中
- 第4~6行定义构造函数,设置松弛变量上限C的默认值为 1 0 4 10^4 104。第6行将标签值函数tagVal设置为Numpy的sign函数,以适于计算分类预测值。
- 第7~8行定义问题(2)的目标函数obj,返回值为 1 2 μ ⊤ Q μ − μ ⊤ 1 \frac{1}{2}\boldsymbol{\mu}^\top\boldsymbol{Q}\boldsymbol{\mu}-\boldsymbol{\mu}^\top\boldsymbol{1} 21μ⊤Qμ−μ⊤1。
- 第9~11行重载标签数据归一化函数ynormalize。由于近似线性可分支持向量机模型中的标签数据不需要归一化,所以在训练时将self.ymin和self.ymax设置为0和1。第12行返回归一化后的标签数据。做了这样的调整,我们在进行预测操作时,按式
y = y ⋅ ( max y − min y ) + min y y=y\cdot(\max y-\min y)+\min y y=y⋅(maxy−miny)+miny
仍保持 y y y的值不变,进而可保持LineModel的predict函数代码不变(见程序4.1及其说明)。 - 第14~44行重载训练函数fit。对比程序4.1中定义的父类fit函数可见第15~23行的代码是保持一致的。
- 第24~26行计算内积矩阵 Q k = ( x 1 ⊤ x 1 x 1 ⊤ x 2 ⋯ x 1 ⊤ x m x 2 ⊤ x 1 x 2 ⊤ x 2 ⋯ x 2 ⊤ x m ⋮ ⋮ ⋱ ⋮ x m ⊤ x 1 x m ⊤ x 2 ⋯ x m ⊤ x m ) \boldsymbol{Q}_k=\begin{pmatrix}\boldsymbol{x}_1^\top\boldsymbol{x}_1&\boldsymbol{x}_1^\top\boldsymbol{x}_2&\cdots&\boldsymbol{x}_1^\top\boldsymbol{x}_m\\ \boldsymbol{x}_2^\top\boldsymbol{x}_1&\boldsymbol{x}_2^\top\boldsymbol{x}_2&\cdots&\boldsymbol{x}_2^\top\boldsymbol{x}_m\\\vdots&\vdots&\ddots&\vdots\\\boldsymbol{x}_m^\top\boldsymbol{x}_1&\boldsymbol{x}_m^\top\boldsymbol{x}_2&\cdots&\boldsymbol{x}_m^\top\boldsymbol{x}_m\end{pmatrix} Qk= x1⊤x1x2⊤x1⋮xm⊤x1x1⊤x2x2⊤x2⋮xm⊤x2⋯⋯⋱⋯x1⊤xmx2⊤xm⋮xm⊤xm
第27行计算目标函数系数矩阵 Q = ( y 1 y 1 x 1 ⊤ x 1 y 1 y 2 x 1 ⊤ x 2 ⋯ y 1 y m x 1 ⊤ x m y 2 y 1 x 2 ⊤ x 1 y 2 y 2 x 2 ⊤ x 2 ⋯ y 2 y m x 2 ⊤ x m ⋮ ⋮ ⋱ ⋮ y m y 1 x m ⊤ x 1 y m y 2 x m ⊤ x 2 ⋯ y m y m x m ⊤ x m ) . \boldsymbol{Q}=\begin{pmatrix}y_1y_1\boldsymbol{x}_1^\top\boldsymbol{x}_1&y_1y_2\boldsymbol{x}_1^\top\boldsymbol{x}_2&\cdots&y_1y_m\boldsymbol{x}_1^\top\boldsymbol{x}_m\\ y_2y_1\boldsymbol{x}_2^\top\boldsymbol{x}_1&y_2y_2\boldsymbol{x}_2^\top\boldsymbol{x}_2&\cdots&y_2y_m\boldsymbol{x}_2^\top\boldsymbol{x}_m\\ \vdots&\vdots&\ddots&\vdots\\ y_my_1\boldsymbol{x}_m^\top\boldsymbol{x}_1&y_my_2\boldsymbol{x}_m^\top\boldsymbol{x}_2&\cdots&y_my_m\boldsymbol{x}_m^\top\boldsymbol{x}_m \end{pmatrix}. Q= y1y1x1⊤x1y2y1x2⊤x1⋮ymy1xm⊤x1y1y2x1⊤x2y2y2x2⊤x2⋮ymy2xm⊤x2⋯⋯⋱⋯y1ymx1⊤xmy2ymx2⊤xm⋮ymymxm⊤xm .
- 第28~30行定义等式约束条件函数h和不等式约束条件函数g1和g2。第31~33行构造约束条件列表cons。第34行调用minimize函数求解优化问题(2),返回值赋予res。第35行将res的x属性赋予mu0,为问题(2)的最优解 μ 0 \boldsymbol{\mu}_0 μ0。
- 第36~37行按条件
0 < μ 0 i < C , i = 1 , 2 , ⋯ , m 0<\mu_{0_i}<C, i=1, 2, \cdots, m 0<μ0i<C,i=1,2,⋯,m
查找支持向量的下标集 s s s并赋予属性support_。
- 第38~39行计算原问题(1)的最优解 w 0 \boldsymbol{w}_0 w0的前 n n n个元素 ∑ i ∈ s μ 0 i y i x i \sum\limits_{i\in s}\mu_{0_i}y_i\boldsymbol{x}_i i∈s∑μ0iyixi。第40~41行计算原问题(1)的最优解 w 0 \boldsymbol{w}_0 w0的最后一个元素 b 0 b_0 b0。第43行连接两部分构成 w 0 \boldsymbol{w}_0 w0赋予属性w0。
- 第44行调用父类的coef_inte函数(见博文《最优化方法Python计算:无约束优化应用——线性回归模型》)计算决策面的系数coef_与截距intercept_。
综合案例
井字棋是很多人儿时喜爱的游戏。方形棋盘被井字形的4条直线分成9个空格。两个玩家各执标有一符:或为“ × \times ×”,或为“ ∘ \circ ∘”的棋子,轮流在棋盘中放置一子。一方所执符号棋子占据棋盘中一直线(水平或竖直或对角)上的三个空格(见题图),即胜出。文件tic-tac-toe.csv(来自UC Irvine Machine Learning Repository)罗列了958例棋盘格局及其胜负判断
x | x | x | x | o | o | x | o | o | positive |
---|---|---|---|---|---|---|---|---|---|
x | x | x | x | o | o | o | x | o | positive |
⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ |
b | b | b | b | o | o | x | x | x | positive |
x | x | o | x | x | o | o | b | o | negative |
⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ | ⋯ \cdots ⋯ |
o | x | o | o | x | x | x | o | x | negative |
o | o | x | x | x | o | o | x | x | negative |
文件中,每一行表示一种棋盘格局及对该格局胜负方的判定。前9个数据表示按行优先排列的棋盘格局,第10个数据“positive”表示执“ × \times ×”者胜,“negative”表示执“ ∘ \circ ∘”者胜。例如,第一行中前9个数据“x x x x o o x o o”表示棋盘格局
由于执“ × \times ×”者占满了第1行和第1列,故判断执“ × \times ×”者胜出,即第10个数据为“positive”。注意,格局数据中“b”表示对应格子未置入任何棋子。例如,数据“b b b b o o x x x”表示棋盘格局
下列代码试图用文件中的70例数据训练一个ALineSvc支持向量机分类模型,用其余数据对其进行测试。
import numpy as np
data = np.loadtxt('tic-tac-toe.csv', #读取数据文件delimiter = ',', dtype = str)
X = np.array(data) #转换为数组
Y = X[:, 9] #读取标签数据
X = np.delete(X, [9], axis = 1) #去掉标签列
m, n=X.shape
print('共有%d个数据样本'%m)
for i in range(m):for j in range(n):if X[i, j] == 'x':X[i, j] = 1if X[i, j] == 'o':X[i, j] = -1if X[i, j] == 'b':X[i, j] = 0
X = X.astype(float)
Y = np.array([1 if y == 'positive' else #类别数值化-1 for y in Y]).astype(int)
a = np.arange(m) #数据项下标
m1=70 #训练集样本容量
np.random.seed(3489)
print('随机抽取%d个样本作为训练数据。'%(m1))
train = np.random.choice(a,m1,replace=False)
test = np.setdiff1d(a,train) #检测数据下标
tictactoe = ALineSvc(C = 10) #创建模型
tictactoe.fit(X[train], Y[train]) #训练模型
print('系数:', tictactoe.coef_)
print('截距:%.4f'%tictactoe.intercept_)
support = tictactoe.support_ #支持向量下标
print('支持向量:%s'%support)
acc=tictactoe.score(X[test], Y[test]) * 100
print('对其余%d个样本数据测试,正确率:%.2f'%(m - m1,acc) + '%')
程序的第2~6行从文件中读取数据并转换为数组X,并从中拆分出标签数据Y。由于表示格局的特征数据和表示胜负的标签数据都是字符型,应转换为数值类型。对特征数据,作映射:
x → 1 , o → − 1 , b → 0 \text{x}\rightarrow1, \text{o}\rightarrow-1,\text{b}\rightarrow0 x→1,o→−1,b→0
第9~16行的嵌套for循环完成9个格局数据按上述对应关系的类型转换。第18~19行将标签数据按映射
positive → 1 , negative → − 1 \text{positive}\rightarrow1, \text{negative}\rightarrow-1 positive→1,negative→−1
进行类型转换。第20~25行从958个棋盘格局数据中随机选取70(m1)个作为训练数据集X[train]、Y[train],其余888个作为训练数据集X[test]、Y[test]。第26行声明ALineSvc类对象tictactoe作为支持向量机分类模型,设置罚项系数C为10.0。第27行调用tictactoe的fit函数用X[train]、Y[train]对其进行训练。第32行调用tictactoe的score函数用X[test]、Y[test]对其进行测试。运行程序,输出
共有958个数据样本
随机抽取100个样本作为训练数据。
训练中...,稍候
15次迭代后完成训练。
系数: [2. 2. 2. 2. 2. 2. 2. 2. 2.]
截距:-19.0286
支持向量:[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 2324 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 4748 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69]
对其余888个样本数据测试,正确率:98.31%
意为用70个训练数据,经过15次迭代完成训练。对其余888个数据对训练所得模型测试,正确率为98.31%。训练给出了决策面
π : 2 x 1 + 2 x 2 + 2 x 3 + 2 x 4 + 2 x 5 + 2 x 6 + 2 x 7 + 2 x 8 + 2 x 9 − 19.03 = 0 \pi:2x_1+2x_2+2x_3+2x_4+2x_5+2x_6+ 2x_7+2x_8+2x_9-19.03=0 π:2x1+2x2+2x3+2x4+2x5+2x6+2x7+2x8+2x9−19.03=0
写博不易,敬请支持:
如果阅读本文于您有所获,敬请点赞、评论、收藏,谢谢大家的支持!