1、引言
最近在学习sklearn库中SVM算法中C-SVC多分类的相关应用,但是在sklearn中关于如何提取训练后的参数,并脱离原有的sklearn库,甚至脱离原有的python开发环境,在新的平台和系统中使用训练后的参数完成前向推理,是本文所需要讲述的内容。由于笔者主要从事于嵌入式平台(包括但不限于ARM、FPGA,目前主要是异构平台)中机器学习算法的相关应用与加速设计,所以需要对于算法的每一个步骤和细节都具有深刻的了解。而目前从网上查阅到的相关资料对于sklearn多分类的解释不够清晰,综合查阅到的中文资料和英文资料,在此处做一个详细的说明。
2、sklearn多分类示例
首先以二维平面内通过聚类生成四组数据,分别隶属于四个不同的类别,对应的代码如下所示,最终生成目标图像如图1所示。
1 importnumpy as np2 importmatplotlib.pyplot as plt3 from sklearn importsvm4 from sklearn.datasets importmake_blobs5
6 n_samples = [10, 10, 10, 10]7 centers = [[0.0, 0.0], [2.0, 2.0], [4.0, 4.0], [6.0, 6.0]]8 cluster_std = [0.4, 0.4, 0.4, 0.4]9
10 x, y = make_blobs(n_samples=n_samples, centers=centers, cluster_std=cluster_std, random_state=0, shuffle=False)11
12 plt.scatter(x[:, 0], x[:, 1], c=y, cmap=plt.cm.rainbow, edgecolors='k')13
14 plt.show()
图1 聚类方法生成四组不同的数据
为了更加能够简化多分类的示例,此处直接使用线性核函数的方法,对于sklearn中C-SVC算法中多分类方法,网上给出的多分类示例包含有一对一(ovo)、一对多(ovr)、有向无环图等方法,但是此处需要说明的是,sklearn中的SVM算法底层的实现采用了台湾大学林智仁教授等发表的LibSVM运行库,而LibSVM中采用的多分类方法为一对一(ovo)的多分类方法(之前笔者误以为sklearn中能够支持不同的多分类方法,对于后面提到的提取训练参数并自行完成前向推理过程产生了误解,导致好几天没能理解参数函数)。因此,由于sklearn中C-SVC算法只能够支持一对一的多分类方法,只需要按照一对一的理论方法来理解给出的参数即可。
接下来给出笔者进行二维平面内多分类的示例,示例程序如下:
1 importnumpy as np2 importmatplotlib.pyplot as plt3 from sklearn importsvm4 from sklearn.datasets importmake_blobs5
6 n_samples = [10, 10, 10, 10]7 centers = [[0.0, 0.0], [2.0, 2.0], [4.0, 4.0], [6.0, 6.0]]8 cluster_std = [0.4, 0.4, 0.4, 0.4]9
10 x, y = make_blobs(n_samples=n_samples, centers=centers, cluster_std=cluster_std, random_state=0, shuffle=False)11
12 clf = svm.SVC(kernel='linear', C=10, gamma=0.5, decision_function_shape='ovo')13 clf.fit(x, y)14
15 xx = np.linspace(-1, 7, 400)16 yy = np.linspace(-1, 7, 400)17 XX, YY =np.meshgrid(xx, yy)18 XY =np.vstack([XX.ravel(), YY.ravel()]).T19 Z =clf.predict(XY).reshape(XX.shape)20 plt.pcolormesh(XX, YY, Z, cmap=plt.cm.rainbow)21
22 plt.scatter(x[:, 0], x[:, 1], c=y, cmap=plt.cm.rainbow, edgecolors='k')23 plt.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=100, facecolors='none', edgecolors='k')24
25 print('clf.support_vectors_')26 print(clf.support_vectors_)27
28 print('clf.n_support_')29 print(clf.n_support_)30
31 print('clf.support_')32 print(clf.support_)33
34 print('clf.dual_coef_')35 print(clf.dual_coef_)36
37 print('clf.coef_')38 #print(clf.coef_)
39
40 print('clf.intercept_')41 print(clf.intercept_)42
43 print(clf.decision_function([[1, 1]]))44 print(clf.predict([[1, 1]]))45
46 plt.show()
最终对图1中的样本点分类结果如图2所示,其中边缘多一个圈的样本点为对应的支持向量样本点。从图2中结果可以看出,线性核函数的多分类结果能够区分出对应的边界,符合预期的分类。
图2 线性多分类结果
3、sklearn多分类参数说明及应用
首先是对C-SVC前向推理的决策函数f(x)进行说明,如公式(1):
(1)
其中K(xi,x)为核函数;αiyi为核函数对应的系数,也是前向推理过程中最重要的参数,sklearn中对于核函数系数的存储方式不是特别好理解,也是本文所侧重说明的一个点;m为支持向量样本的个数,简单的理解,SVM顾名思义支持向量机,其中决策函数(也就是分割超平面)由支持向量来决定;b为决策函数对应的偏置项,属于支持向量累加完成后需要添加的偏置。
对于多分类(>=3)中一对一的分类方法,简述为每次选择样本空间中的任意两类构造分类模型,例如4个类别[0,1,2,3],分别对[0,1][0,2][0,3][1,2][1,3][2,3]构建6个分类模型。因此根据排列组合可知,若类别的数目为n,则需要构建的分类模型可用公式(2)来表示:
(2)
当n=2时为二分类问题,只需要构造一个分类模型,不属于多分类需要讨论的范畴。因此对于给定的类别n,需要构建决策函数的数目也由公式(2)来决定。
当多分类对应的多个决策函数确定后,输入新的测试样本,例如坐标(x,y),大小为(0,0);经过决策函数进行投票,例如4分类中[0,1][0,2][0,3][1,2][1,3][2,3],在对应的决策函数中,
每一个决策函数的结果均小于0,则每个决策函数投票结果为[0,0,0,1,1,2],那么最终结果判断输入坐标(0,0)属于类别0,此处不排除投票出现相同的投票结果。
在对应的决策函数中,每一个决策函数的结果均大于0,表明投票给两个类别中的第一个类别,而非第二类。即对应每个决策函数投票结果为[0,0,0,1,1,2],那么最终结果判断输入坐标(0,0)属于类别0,此处不排除投票出现相同的投票结果。(@我爱吃桔子 感谢指出错误)
-------------------------
接下来就是重点了
-------------------------
假定输入样本的特征大小为n_features(例如二维平面输入为坐标,则特征为2),样本的类别为n,样本的数目为n_samples,训练后得到的支持向量个数为n_Svs。
在sklearn中需要注意的训练结果如下:
clf.support_vectors_
支持向量的表述,对应公式(1)核函数K(xi,x)中的xi,在实际的前向推理中,每个核函数只需要计算一次,而计算结果需要参与到多个模型分类中,该矩阵的维度为(n_SVs, n_features)。
0.391495
0.896357
0.978804
2.261447
2.613112
2.587744
1.644886
1.207681
3.580579
3.431993
4.026607
4.120989
5.674741
5.309487
clf.n_support_
每个类别所对应支持向量的个数,矩阵的维度为(1, n),若每一个类别对应的支持向量个数为n_SVi,每个类别支持向量个数的和为总支持向量的个数, 即公式(3):
(3)
1
3
2
1
clf.support_
支持向量clf.support_vectors_对应在n_samples里面的索引,在前向推理过程中不需要是用到此参数,矩阵的维度为(1,n_SVs)。
1
10
14
16
20
28
31
clf.dual_coef_
每一组支持向量求得的核函数所对应的系数αiyi,矩阵的维度为(n-1, n_SVs),后文将对此进行详细的介绍。
1.331435
-0.43355
0
-0.89788
-0.12048
0
-0.0422
0.120484
0
1.213041
0
-1.21304
0
-0.11918
0.042204
0
0.119179
0
0
0.484178
-0.48418
clf.coef_
线性核函数合并同类项后的系数,只有当线性核的时候才能够调用此参数,非线性核函数不能够合并同类项,因此不存在此参数。矩阵的维度为(n*(n-1)/2, n_features)
-1.38003
-0.87137
-0.38423
-0.3055
-0.22298
-0.18625
-1.17358
-1.02411
-0.36488
-0.32437
-0.79799
-0.57544
clf.intercept_
每个决策函数对应的偏置,对应公式(1)中的b,矩阵的维度为(1, n*(n-1)/2)。
2.321663
1.424266
1.254244
6.716821
2.792877
6.584147
当输入一个样本,样本的矩阵维度为(1,n_features)时,以线性核函数为例,如果输入的是(x0,x1),则生成的核函数矩阵为图3,矩阵的维度为(n_SVs, 1):
图3 核矩阵示意图
而上述核函数矩阵根据类别分为4类,每个类别对应的数量与clf.n_support_对应,分块后矩阵的维度为(n,1),如图4所示:
图4 核矩阵分块示意图
而对于核函数系数矩阵clf.dual_coef_,同样可以按照上述分类方式进行分块,分块后矩阵的维度为(n-1,n),如图5所示:
图5 系数矩阵分块示意图
根据图4和图5所示分块方式进行多分类,所以重点其实是公式(1)中
的计算过程。根据公式(2)可知,如果是四分类问题,采用一对一的分类方法需要计算6个决策函数,对于如何使用系数矩阵与核函数矩阵进行相乘,从而构造6个决策函数,笔者所查阅的资料没有给出一个形象的过程解释,因此本文再次给出详细的解释。在系数矩阵中,默认的决策函数按照[0,1][0,2][0,3][1,2][1,3][2,3]的决策方法,第一步首先提取系数矩阵中(0,0)和(0,1)中的数据,完成矩阵的点乘操作,得到累加后结果,即第一个决策函数计算完成;然后提取系数矩阵中(0,1)和(0,3)中的数据,对应第二个决策函数;说了这么多,我觉得还是用图片来便是更形象一点。看图6中的说明,展示了怎样构建一对一条件下的决策函数。
图6 一对一决策函数计算示意图
同样可以直接计算矩阵,然后按照图6中构造决策函数的方法将矩阵中元素选择求和,如图7所示:
图7 直接矩阵运算示意图
构造决策函数的过程可以用图8中的伪代码来表明:
1 f_i=0;2 for(i=0;i
图8 决策函数构建伪代码
最后得到的决策函数个数刚好与公式(2)的大小一致,接下来只需要与每一个决策函数的偏置相加,通过判断结果与0的关系,得到该决策函数的投票结果,最后统计投票结果,即可得到多分类结果。
4、一对一多分类程序
1 importnumpy as np2
3 sv = [[0.39149519, 0.89635728],4 [0.97880407, 2.26144744],5 [2.61311169, 2.58774351],6 [1.6448857, 1.20768141],7 [3.58057881, 3.43199283],8 [4.02660689, 4.12098876],9 [5.67474149, 5.30948696]]10 nv = [1, 3, 2, 1]11 a = [[1.33143525, -0.43355065, -0., -0.89788459, -0.12048409, -0., -0.04220442],12 [0.12048409, 0., 1.21304109, 0., -1.21304109, -0., -0.11917911],13 [0.04220442, 0., 0.11917911, 0., 0., 0.48417772, -0.48417772]]14 b = [2.32166302, 1.42426621, 1.25424389, 6.71682103, 2.79287738, 6.58414668]15 cs = [0, 1, 2, 3]16
17 X = [5, 6]18
19 k = [np.dot(vi, X) for vi insv]20
21 print('k')22 print(k)23
24 start = [sum(nv[:i]) for i in range(len(nv))] #nv=[1,3,2,1] start=[0,1,4,6]
25 end = [start[i] + nv[i] for i in range(len(nv))] #end = [1,4,6,7]
26
27 '''
28 c = [sum(a[i][q] * k[q] for q in range(start[j], end[j])) +29 sum(a[j - 1][p] * k[p] for p in range(start[i], end[i]))30 for i in range(len(nv)) for j in range(i + 1, len(nv))]31 print('c')32 print(c)33 '''
34
35 c = np.zeros(int(4*(4-1)/2))36 ci =037 for i inrange(len(nv)):38 for j in range(i + 1, len(nv)):39 sum1 =040 sum2 =041 for q inrange(start[j], end[j]):42 sum1 = sum1 + a[i][q] *k[q]43 for p inrange(start[i], end[i]):44 sum2 = sum2 + a[j - 1][p] *k[p]45 print('i:' + str(i) + ';j:' +str(j))46 print(sum1 +sum2)47 c[ci] = sum1+sum248 ci = ci+1
49
50
51 decision = [sum(x) for x inzip(c, b)]52 print('decision')53 print(decision)54
55
56 votes = [(i if decision[p] > 0 else j) for p, (i, j) inenumerate((i, j)57 for i in range(4)58 for j in range(i + 1, 4))]59
60 print('votes')61 print(votes)62
63 print(cs[max(set(votes), key=votes.count)])
对异构计算和机器学习算法加速的可以关注我发表的文献,不定期更新: