最优化方法Python计算:有约束优化应用——近似线性可分问题支持向量机

发布于:2025-05-15 ⋅ 阅读:(11) ⋅ 点赞:(0)

二分问题的数据集 { ( 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)w01,但有少数样本点特征数据不满足此约束而交叉纠结,如下图所示。这样的问题,称为近似线性可分问题。图中的超平面 ( 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= z1z2zm ,构造优化问题
{ 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) min21wHw+czs.t. yi(xi,1)w1zi  zi0i=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= 1000010000100000 c = ( C C ⋮ C ) \boldsymbol{c}=\begin{pmatrix}C\\C\\\vdots\\C\end{pmatrix} c= CCC ,正则化参数 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} {min21wHws.t.yi(xi,1)w1i=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μμ1s.t.  μy=0   oμc.(2)
的最优解 μ 0 \boldsymbol{\mu}_0 μ0等价。其中, Q = X ⊤ X \boldsymbol{Q}=\boldsymbol{X}^\top\boldsymbol{X} Q=XX 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= isμ0iyixims1js(yjisμ0iyixjxi) .
其中,
s = { i ∣ 0 ≤ i ≤ m , 0 < μ 0 i < C } s=\{i|0\leq i\leq m, 0<\mu_{0_i}<C\} s={i∣0im,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 = C
        self.tagVal = np.sign
    def obj(self, mu):													#目标函数
        return 0.5 * (mu @ (self.Q @ mu)) - mu.sum()
    def ynormalize(self, y, trained):									#标签数据预处理
        if not trained:
            self.ymin = 0
            self.ymax = 1
        return (y - self.ymin) / (self.ymax - self.ymin)
    def fit(self, X, Y, mu = None):										#训练函数
        print("训练中...,稍候")
        m, n = X.shape
        self.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												#不等式约束函数1
        g2 = lambda x: self.C - x										#不等式约束函数2
        cons = [{'type': 'eq', 'fun': h},								#约束条件列表
                {'type': 'ineq', 'fun': g1},
                {'type': 'ineq', 'fun': g2}]
        res = minimize(self.obj, mu, constraints = cons)				#求解最优化问题
        self.mu0 = res.x
        self.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(maxyminy)+miny
    仍保持 y y y的值不变,进而可保持LineModel的predict函数代码不变(见博文《最优化方法Python计算:无约束优化应用——线性回归分类器》)。
  • 第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= x1x1x2x1xmx1x1x2x2x2xmx2x1xmx2xmxmxm
    第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= y1y1x1x1y2y1x2x1ymy1xmx1y1y2x1x2y2y2x2x2ymy2xmx2y1ymx1xmy2ymx2xmymymxmxm .
    - 第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 isμ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] = 1
        if X[i, j] == 'o':
            X[i, j] = -1
        if 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 x1,o1,b0
第9~16行的嵌套for循环完成9个格局数据按上述对应关系的类型转换。第18~19行将标签数据按映射
positive → 1 , negative → − 1 \text{positive}\rightarrow1, \text{negative}\rightarrow-1 positive1,negative1
进行类型转换。第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 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]
对其余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+2x919.03=0

写博不易,敬请支持:
如果阅读本文于您有所获,敬请点赞、评论、收藏,谢谢大家的支持!