当前位置:   article > 正文

SVM SMO算法代码详细剖析

ssmo算法

前言

0aaa36de157986b4c5547f49bcaaf6d1.gif

一:本文要结合SVM理论部分来看即笔者另一篇:

SVM原理从头到尾详细推导

二:有了理论部分下面就是直接代码啦,本文用四部分进行介绍:最简版的SMO,改进版platt SMO,核函数,sklearn库的SVM,采取的顺序是先给代码及结果,然后分析。

三:这里代码大部分来自于Peter Harrington编写的Machine Learning in Action

其网络资源:https://www.manning.com/books/machine-learning-in-action

四:代码中需要注意的一点就是采用启发式来寻找需要优化的  

简版SMO算法

14e0a0b76b2212ffe9aa56e753233a90.gif

这里有两个py文件,一个是用来构造SVM的,一个是用来测试的:

MySVM:

  1. # -*- coding: utf-8 -*-
  2. import random
  3. import numpy as np
  4. import matplotlib.pyplot as plt
  5. #辅助函数一
  6. def selectJrand(i, m):
  7. j = i
  8. while (j == i):
  9. j = int(random.uniform(0, m))
  10. return j
  11. #辅助函函数二
  12. def clipAlpha(aj,H,L):
  13. if aj > H:
  14. aj = H
  15. if L > aj:
  16. aj = L
  17. return aj
  18. #最简版本SMO算法
  19. def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
  20. dataMatrix = np.mat(dataMatIn);
  21. labelMat = np.mat(classLabels).transpose()
  22. b = 0;
  23. m,n = np.shape(dataMatrix)
  24. alphas = np.mat(np.zeros((m,1)))
  25. iter_num = 0
  26. while (iter_num < maxIter):
  27. alphaPairsChanged = 0
  28. for i in range(m):
  29. #注意一
  30. fXi = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b
  31. Ei = fXi - float(labelMat[i])
  32. if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
  33. j = selectJrand(i,m)
  34. fXj = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b
  35. Ej = fXj - float(labelMat[j])
  36. alphaIold = alphas[i].copy();
  37. alphaJold = alphas[j].copy();
  38. if (labelMat[i] != labelMat[j]):
  39. L = max(0, alphas[j] - alphas[i])
  40. H = min(C, C + alphas[j] - alphas[i])
  41. else:
  42. L = max(0, alphas[j] + alphas[i] - C)
  43. H = min(C, alphas[j] + alphas[i])
  44. if L==H:
  45. print("L==H");
  46. continue
  47. #注意二
  48. eta = 2.0 * dataMatrix[i,:]*dataMatrix[j,:].T - dataMatrix[i,:]*dataMatrix[i,:].T - dataMatrix[j,:]*dataMatrix[j,:].T
  49. if eta >= 0:
  50. print("eta>=0");
  51. continue
  52. alphas[j] -= labelMat[j]*(Ei - Ej)/eta
  53. alphas[j] = clipAlpha(alphas[j],H,L)
  54. if (abs(alphas[j] - alphaJold) < 0.00001): print("alpha_j变化小,不需要更新"); continue
  55. #注意三
  56. alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j])
  57. #注意四
  58. b1 = b - Ei- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[i,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[i,:]*dataMatrix[j,:].T
  59. b2 = b - Ej- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[j,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[j,:]*dataMatrix[j,:].T
  60. if (0 < alphas[i]) and (C > alphas[i]): b = b1
  61. elif (0 < alphas[j]) and (C > alphas[j]): b = b2
  62. else: b = (b1 + b2)/2.0
  63. alphaPairsChanged += 1
  64. print("第%d次迭代 样本:%d, alpha优化次数:%d" % (iter_num,i,alphaPairsChanged))
  65. if (alphaPairsChanged == 0):
  66. iter_num += 1
  67. else: iter_num = 0
  68. print("迭代次数: %d" % iter_num)
  69. #注意五
  70. return b,alphas
  71. def calcWs(dataMat, labelMat, alphas):
  72. alphas, dataMat, labelMat = np.array(alphas), np.array(dataMat), np.array(labelMat)
  73. w = np.dot((np.tile(labelMat.reshape(1, -1).T, (1, 2)) * dataMat).T, alphas)
  74. return w.tolist()
  75. def showClassifer(dataMat,labelMat,alphas, w, b):
  76. data_plus = []
  77. data_minus = []
  78. #注意六
  79. for i in range(len(dataMat)):
  80. if labelMat[i] > 0:
  81. data_plus.append(dataMat[i])
  82. else:
  83. data_minus.append(dataMat[i])
  84. data_plus_np = np.array(data_plus)
  85. data_minus_np = np.array(data_minus)
  86. plt.scatter(np.transpose(data_plus_np)[0], np.transpose(data_plus_np)[1], s=30, alpha=0.7)
  87. plt.scatter(np.transpose(data_minus_np)[0], np.transpose(data_minus_np)[1], s=30, alpha=0.7)
  88. #注意七
  89. x1 = max(dataMat)[0]
  90. x2 = min(dataMat)[0]
  91. a1, a2 = w
  92. b = float(b)
  93. a1 = float(a1[0])
  94. a2 = float(a2[0])
  95. y1, y2 = (-b- a1*x1)/a2, (-b - a1*x2)/a2
  96. plt.plot([x1, x2], [y1, y2])
  97. #注意八
  98. for i, alpha in enumerate(alphas):
  99. if 0.6>abs(alpha) > 0:
  100. x, y = dataMat[i]
  101. plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='red')
  102. if 0.6==abs(alpha) :
  103. x, y = dataMat[i]
  104. plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='yellow')
  105. plt.show()

接着是测试函数(MyTest):

  1. # -*- coding: utf-8 -*-#
  2. import MySVM as svm
  3. x=[[1,8],[3,20],[1,15],[3,35],[5,35],[4,40],[7,80],[6,49],[1.5,25],[3.5,45],[4.5,50],[6.5,15],[5.5,20],[5.8,74],[2.5,5]]
  4. y=[1,1,-1,-1,1,-1,-1,1,-1,-1,-1,1,1,-1,1]
  5. b,alphas = svm.smoSimple(x,y,0.6,0.001,40)
  6. w = svm.calcWs(x,y,alphas)
  7. svm.showClassifer(x,y,alphas, w, b)

运行结果:

 aab5a7f453647a749f9cb7b526d3d282.png

,,,,,,,,,,,,,

 787194e1720d9a8587e31d6752bd552a.png

 c470990ba5ad0e0e2b541b3364784db9.png

‍接下来一步步分析MySVM中的代码

首次看两个简单的辅助函数,第一个函数的作用就是用来选择  对的(即寻找i,j这一对)

第二个函数就是为了将  规划到[0,C]范围内,对应到理论推导部分的:

接下来就是smo算法的最简版本:

注意一下面的fXi对应推导公式的即w的更新:

可能这里和上篇最后给出w的更新形式上看上去有点不对应,其实是一样的,推导部分即最后一张图是:

而这里的其实在程序就是对应的就是如下三步:

  1. fXi = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b
  2. alphas[j] -= labelMat[j]*(Ei - Ej)/eta
  3. alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j])

紧接着的if这里就是启发式选择,即寻找那些误差过大(正间隔和否间隔)且在(0,C)范围内的  进行优化,选择误差大的进行优化我们很容易理解,那为什么要选择(0,C)范围内,而不选择边界值呢(值等于0或C),那是因为它们已经在边界啦,因此不再能够减少或者增大具体细节请看推导部分,该部分包括L和H为什么要这样赋值,以及为什么L==H的时候要返回都有讲到。

注意二的部分就是类似我们在推导部分的  更新,只不过这里有一步如果变化太小我们就不更新了,直接跳过,只不过原公式中的  ,所以推导中原本  是加上后面的,而这里是减即:本质是一样的啦,当然  更新方向要相反,所以代码中对应的是+

alphas[j] -= labelMat[j]*(Ei - Ej)/eta

至于为什么eta >= 0为什么要跳过该次循环,请看推导部分,只不过因为  所以原来是过滤掉<=0,这里是>=0

注意三部分就是类似  的更新即大小和  相同,方向相反

注意四的部分应该很直观啦,看我们推导的b的更新结论一目了然

注意五返回的b是一个实数,alphas是一个[m,1]矩阵

最后说一下smo函数的alphaPairsChanged和iter_num以及maxIter的参数意义,maxIter是最外部的大循环,是人为设定的最大循环次数,循环为最大次数后就强行结束返回,在每一个大循环下都有一个for循环,用以遍历一遍所有的  ,遍历完这一遍所有的  后,alphaPairsChange用以记录看有多少对被优化啦,如果alphaPairsChange不为0,即这一遍走下来后,我们进行了优化,也就代表目前  还不够好,所以我们将iter_num设为0,继续优化,当alphaPairsChange为0时,说明我们这一遍走下来,说明  都很好啦,没有优化的必要啦,我们将iter_num加一,接着下一遍再去整体看看  ,如果还是alphaPairsChange为0,恩恩,不错,不错,将iter_num再加一,如果iter_num到了maxIter即连续进行了maxIter遍整体(for)观察都没发现需要优化的  

,说明足够好了,返回吧!!!!!!一旦中间出现意外,发现有需要优化的,就至少说明有不完善的地方,那么我们立马让iter_num为0,即一定要达到连续遍历maxIter次都没发现不足,我们才放心,才返回,发现瑕疵立马iter_num=0从头开始,怎么样?就是这么严格,这也是上面运行结果开始的时候为什么迭代次数一直都是0,后面趋于收敛,迭代次数连续增加,直到maxIter结束返回

正是因为如此,可以想象得的到带来的结果就是 时间复杂度太高,所以有了后来改进版本的Platt SMO,后面介绍

接下来的calcWs函数作用是:根据训练出来的  生成w:

对应的公式就是:

目前我们已经训练除了SVM模型,即得到了我们想要的w和b,对应的步骤就在上面所说的黄色部分

接下来可视化看一下结果showClassifer:

注意六的部分就是我们把原始点画出来,不同的颜色代表不同的分离(橙色的点对应的标签是-1,蓝色的点对应的标签是1)

注意七的部分就是画出训练出来的超平面

y1, y2 = (-b- a1*x1)/a2, (-b - a1*x2)/a2

这个很好理解啦,超平面是:

所以:

程序中为了让超平面尽可能的横穿整个数据点,所以选取了所有点中x坐标最大和最小的点即x1和x2:

然后利用上面的公式,计算出了对应的纵坐标

注意八的部分就是画出向量机点,和那些我们“忽略”的点,依据是推导的:

即在点在两条间隔线外则,对应前面的系数  为0,在两条间隔线里面的对应的系数为C,在两条间隔线上的点对应的系数在0和C之间。至于为什么请看上篇的推导细节

带有红色圆圈的是支持向量机点即间隔线上的点,带有黄色的点是间隔线内的点

Platt SMO

680dd8afff4ca23621f8d6b258aa826e.gif

‍其是SMO算法的一个改进版,速度更快。

其主要变化的地方有两个

一:在使用启发式方法选择了一个  

后,我们会去选择另外一个与之对应是吧,即‍

‍‍

但是改进的的SMO算法中,这里也使用启发式来选择,即选择与Ei误差最大的Ej即选择最大步长,简单来说就是找最需要优化的j,而不是像最简版本那样,毫无目的的随机去选择,所以对应到推导公式里面就是  和  都采用启发式来寻找

二:改进后的算法是采用在“非边界值”和“边界值”范围内交替遍历优化的

下面来看一下具体代码:

smoP:

  1. # -*- coding: utf-8 -*-
  2. from numpy import *
  3. import matplotlib.pyplot as plt
  4. import random
  5. def loadDataSet(filename):
  6. dataMat=[]
  7. labelMat=[]
  8. fr=open(filename)
  9. for line in fr.readlines():
  10. lineArr=line.strip().split('\t')
  11. dataMat.append([float(lineArr[0]),float(lineArr[1])])
  12. labelMat.append(float(lineArr[2]))
  13. return dataMat,labelMat
  14. class optStruct:
  15. def __init__(self,dataMatIn, classLabels, C, toler, kTup):
  16. self.X = dataMatIn
  17. self.labelMat = classLabels
  18. self.C = C
  19. self.tol = toler
  20. self.m = shape(dataMatIn)[0]
  21. self.alphas = mat(zeros((self.m,1)))
  22. self.b = 0
  23. self.eCache = mat(zeros((self.m,2)))
  24. def selectJrand(i,m):
  25. j=i
  26. while (j==i):
  27. j=int(random.uniform(0,m))
  28. return j
  29. def clipAlpha(aj,H,L):
  30. if aj>H:
  31. aj=H
  32. if L>aj:
  33. aj=L
  34. return aj
  35. def calcEk(oS, k):
  36. fXk = float(multiply(oS.alphas,oS.labelMat).T*(oS.X*oS.X[k,:].T) + oS.b)
  37. Ek = fXk - float(oS.labelMat[k])
  38. return Ek
  39. def selectJ(i, oS, Ei):
  40. maxK = -1
  41. maxDeltaE = 0
  42. Ej = 0
  43. oS.eCache[i] = [1,Ei]
  44. validEcacheList = nonzero(oS.eCache[:,0].A)[0]
  45. if (len(validEcacheList)) > 1:
  46. for k in validEcacheList:
  47. if k == i:
  48. continue
  49. Ek = calcEk(oS, k)
  50. deltaE = abs(Ei - Ek)
  51. if (deltaE > maxDeltaE):
  52. maxK = k
  53. maxDeltaE = deltaE
  54. Ej = Ek
  55. return maxK, Ej
  56. else:
  57. j = selectJrand(i, oS.m)
  58. Ej = calcEk(oS, j)
  59. return j, Ej
  60. def updateEk(oS, k):
  61. Ek = calcEk(oS, k)
  62. oS.eCache[k] = [1,Ek]
  63. def innerL(i, oS):
  64. Ei = calcEk(oS, i)
  65. if ((oS.labelMat[i]*Ei < -oS.tol) and (oS.alphas[i] < oS.C)) or ((oS.labelMat[i]*Ei > oS.tol) and (oS.alphas[i] > 0)):
  66. j,Ej = selectJ(i, oS, Ei)
  67. alphaIold = oS.alphas[i].copy()
  68. alphaJold = oS.alphas[j].copy()
  69. if (oS.labelMat[i] != oS.labelMat[j]):
  70. L = max(0, oS.alphas[j] - oS.alphas[i])
  71. H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
  72. else:
  73. L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C)
  74. H = min(oS.C, oS.alphas[j] + oS.alphas[i])
  75. if L==H:
  76. print("L==H")
  77. return 0
  78. eta = 2.0 * oS.X[i,:]*oS.X[j,:].T-oS.X[i,:]*oS.X[i,:].T-oS.X[j,:]*oS.X[j,:].T
  79. if eta >= 0:
  80. print("eta>=0")
  81. return 0
  82. oS.alphas[j] -= oS.labelMat[j]*(Ei - Ej)/eta
  83. oS.alphas[j] = clipAlpha(oS.alphas[j],H,L)
  84. updateEk(oS, j)
  85. if (abs(oS.alphas[j] - alphaJold) < oS.tol):
  86. print("j not moving enough")
  87. return 0
  88. oS.alphas[i] += oS.labelMat[j]*oS.labelMat[i]*(alphaJold - oS.alphas[j])
  89. updateEk(oS, i)
  90. b1 = oS.b - Ei- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.X[i,:]*oS.X[i,:].T - oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.X[i,:]*oS.X[j,:].T
  91. b2 = oS.b - Ej- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.X[i,:]*oS.X[j,:].T - oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.X[j,:]*oS.X[j,:].T
  92. if (0 < oS.alphas[i]<oS.C):
  93. oS.b = b1
  94. elif (0 < oS.alphas[j]<oS.C):
  95. oS.b = b2
  96. else:
  97. oS.b = (b1 + b2)/2.0
  98. return 1
  99. else:
  100. return 0
  101. def calcWs(dataMat, labelMat, alphas):
  102. alphas, dataMat, labelMat = array(alphas), array(dataMat), array(labelMat)
  103. w = dot((tile(labelMat.reshape(1, -1).T, (1, 2)) * dataMat).T, alphas)
  104. return w.tolist()
  105. def smoP(dataMatIn, classLabels, C, toler, maxIter,kTup=('lin', 0)):
  106. oS = optStruct(mat(dataMatIn),mat(classLabels).transpose(),C,toler, kTup)
  107. iter = 0
  108. entireSet = True
  109. alphaPairsChanged = 0
  110. while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)):
  111. alphaPairsChanged = 0
  112. if entireSet:
  113. for i in range(oS.m):
  114. alphaPairsChanged += innerL(i,oS)
  115. print("fullSet, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
  116. iter += 1
  117. else:
  118. nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]
  119. for i in nonBoundIs:
  120. alphaPairsChanged += innerL(i,oS)
  121. print("non-bound, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
  122. iter += 1
  123. if entireSet:
  124. entireSet = False
  125. elif (alphaPairsChanged == 0):
  126. entireSet = True
  127. print("iteration number: %d" % iter)
  128. return oS.b,oS.alphas
  129. def showClassifer(dataMat,labelMat,alphas, w, b):
  130. data_plus = []
  131. data_minus = []
  132. for i in range(len(dataMat)):
  133. if labelMat[i] > 0:
  134. data_plus.append(dataMat[i])
  135. else:
  136. data_minus.append(dataMat[i])
  137. data_plus_np = array(data_plus)
  138. data_minus_np = array(data_minus)
  139. plt.scatter(transpose(data_plus_np)[0], transpose(data_plus_np)[1], s=30, alpha=0.7)
  140. plt.scatter(transpose(data_minus_np)[0], transpose(data_minus_np)[1], s=30, alpha=0.7)
  141. x1 = max(dataMat)[0]
  142. x2 = min(dataMat)[0]
  143. a1, a2 = w
  144. b = float(b)
  145. a1 = float(a1[0])
  146. a2 = float(a2[0])
  147. y1, y2 = (-b- a1*x1)/a2, (-b - a1*x2)/a2
  148. plt.plot([x1, x2], [y1, y2])
  149. for i, alpha in enumerate(alphas):
  150. if 0.6>abs(alpha) > 0:
  151. x, y = dataMat[i]
  152. plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='red')
  153. if 50==abs(alpha) :
  154. x, y = dataMat[i]
  155. plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='yellow')
  156. plt.show()

接着还是测试函数(MyTest):

  1. # -*- coding: utf-8 -*-#
  2. import smoP as svm
  3. x=[[1,8],[3,20],[1,15],[3,35],[5,35],[4,40],[7,80],[6,49],[1.5,25],[3.5,45],[4.5,50],[6.5,15],[5.5,20],[5.8,74],[2.5,5]]
  4. y=[1,1,-1,-1,1,-1,-1,1,-1,-1,-1,1,1,-1,1]
  5. b,alphas = svm.smoP(x,y,50,0.001,40)
  6. w = svm.calcWs(x,y,alphas)
  7. svm.showClassifer(x,y,alphas, w, b)

‍‍

 30a2fc074b0060d4d5f3aae772dc7b1a.png

,,,,,,,,,,,

 6858340692ae5477dcc66e95d6e00d75.png

aae15e903ddd8d84d5260d1e19d7e1f6.png

这里首先optStruct函数定义了一个类作为数据结构来存储一些信息,这里面的alphas就是我们的  ,eCache第一列就是一个是否有效的标志位,第二列存储着误差值E,总之这个结构体的定义就是为了作为一个整体,方便调用,管理。

calcEk和最简版本没什么差别,只不过我们已经定义了结构体,所以直接可以调用结构体便可得到一些信息,所以下面所有代码都是这样,比如C我们可以直接用oS.C等等

selectJ和最简版本不一样啦,这里也就是我们说的用启发式来寻找j,这里的:

if (len(validEcacheList)) > 1:

主要是防止第一次循环的时候,如果是第一次那么就随机选择,之后都使用启发式来选择

 updateEk就是用来在计算完i和j的Ei和Ej后更新数据结构中的的eCache

innerL和最简版本的smoSimple内循环(就是for循环下面的代码:用来优化  和b的核心代码)一模一样,只不过这里要把一些东西,改为数据结构中定义的,而且这里的selectJ已经采用了启发式寻找

接下来就是我们的smoP,也是Platt SMO利用主循环封装整个算法的过程,其和最简版本一样,也是两个循环:

外训练也是使用了一个maxIter,同时使用了iter来记录遍历次数(对应最简版本的iter_num),但是两者含义却不一样,这里的iter就是单纯的代表一次循环,而不管循环内部具体做了什么,它没有被清0这个过程,随着程序运行一直是个累加的过程,上面运行结果也可以看到iter是一直递增的,这也是Platt SMO之所以能够加快算法的一个重要原因,而最简版本的iter_num要肩负着连续这一条件,同时这里的外循环相对于最简版本的的外循环多了一个退出条件即:遍历整个集合都没发现需要改变的  (说明都优化好啦,退出吧)

再来看一下内循环,这里对应着两种情况,一种是在全集上面遍历([0,C]),另一种是非边界上面((0,C)),通过

  1. if entireSet:
  2. entireSet = False
  3. elif (alphaPairsChanged == 0):
  4. entireSet = True

使两种情况交替遍历

其他部分包括W的获得,可视化什么的就和最简版本一样啦,不再重复介绍啦

核函数

59cb4fe5384c18e9d26f9838e19d3f20.gif

核函数的作用细节请看推导部分,核函数种类很多,这里看一下最常用的径向基高斯(RBF)核函数 

下面来简单说一下部分代码(这里只说不同的地方,相同的地方不再重述)

  1. # -*- coding: utf-8 -*-
  2. from numpy import *
  3. import matplotlib.pyplot as plt
  4. def loadDataSet(filename):
  5. dataMat=[]
  6. labelMat=[]
  7. fr=open(filename)
  8. for line in fr.readlines():
  9. lineArr=line.strip().split(',')
  10. dataMat.append([float(lineArr[0]),float(lineArr[1])])
  11. labelMat.append(float(lineArr[2]))
  12. return dataMat,labelMat
  13. def selectJrand(i,m):
  14. j=i
  15. while (j==i):
  16. j=int(random.uniform(0,m))
  17. return j
  18. def clipAlpha(aj,H,L):
  19. if aj>H:
  20. aj=H
  21. if L>aj:
  22. aj=L
  23. return aj
  24. def kernelTrans(X, A, kTup):
  25. m,n = shape(X)
  26. K = mat(zeros((m,1)))
  27. if kTup[0]=='lin':
  28. K = X * A.T
  29. elif kTup[0]=='rbf':
  30. for j in range(m):
  31. deltaRow = X[j,:] - A
  32. K[j] = deltaRow*deltaRow.T
  33. K = exp(K/(-1*kTup[1]**2))
  34. else:
  35. raise NameError('Houston We Have a Problem -- That Kernel is not recognized')
  36. return K
  37. class optStruct:
  38. def __init__(self,dataMatIn, classLabels, C, toler, kTup):
  39. self.X = dataMatIn
  40. self.labelMat = classLabels
  41. self.C = C
  42. self.tol = toler
  43. self.m = shape(dataMatIn)[0]
  44. self.alphas = mat(zeros((self.m,1)))
  45. self.b = 0
  46. self.eCache = mat(zeros((self.m,2)))
  47. self.K = mat(zeros((self.m,self.m)))
  48. for i in range(self.m):
  49. self.K[:,i] = kernelTrans(self.X, self.X[i,:], kTup)
  50. def calcEk(oS, k):
  51. fXk = float(multiply(oS.alphas,oS.labelMat).T*oS.K[:,k] + oS.b)
  52. Ek = fXk - float(oS.labelMat[k])
  53. return Ek
  54. def selectJ(i, oS, Ei):
  55. maxK = -1
  56. maxDeltaE = 0
  57. Ej = 0
  58. oS.eCache[i] = [1,Ei]
  59. validEcacheList = nonzero(oS.eCache[:,0].A)[0]
  60. if (len(validEcacheList)) > 1:
  61. for k in validEcacheList:
  62. if k == i:
  63. continue
  64. Ek = calcEk(oS, k)
  65. deltaE = abs(Ei - Ek)
  66. if (deltaE > maxDeltaE):
  67. maxK = k
  68. maxDeltaE = deltaE
  69. Ej = Ek
  70. return maxK, Ej
  71. else:
  72. j = selectJrand(i, oS.m)
  73. Ej = calcEk(oS, j)
  74. return j, Ej
  75. def updateEk(oS, k):
  76. Ek = calcEk(oS, k)
  77. oS.eCache[k] = [1,Ek]
  78. def innerL(i, oS):
  79. Ei = calcEk(oS, i)
  80. if ((oS.labelMat[i]*Ei < -oS.tol) and (oS.alphas[i] < oS.C)) or ((oS.labelMat[i]*Ei > oS.tol) and (oS.alphas[i] > 0)):
  81. j,Ej = selectJ(i, oS, Ei)
  82. alphaIold = oS.alphas[i].copy()
  83. alphaJold = oS.alphas[j].copy()
  84. if (oS.labelMat[i] != oS.labelMat[j]):
  85. L = max(0, oS.alphas[j] - oS.alphas[i])
  86. H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
  87. else:
  88. L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C)
  89. H = min(oS.C, oS.alphas[j] + oS.alphas[i])
  90. if L==H:
  91. print("L==H")
  92. return 0
  93. eta = 2.0 * oS.K[i,j] - oS.K[i,i] - oS.K[j,j]
  94. if eta >= 0:
  95. print("eta>=0")
  96. return 0
  97. oS.alphas[j] -= oS.labelMat[j]*(Ei - Ej)/eta
  98. oS.alphas[j] = clipAlpha(oS.alphas[j],H,L)
  99. updateEk(oS, j)
  100. if (abs(oS.alphas[j] - alphaJold) < oS.tol):
  101. print("j not moving enough")
  102. return 0
  103. oS.alphas[i] += oS.labelMat[j]*oS.labelMat[i]*(alphaJold - oS.alphas[j])
  104. updateEk(oS, i)
  105. b1 = oS.b - Ei- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.K[i,i] - oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.K[i,j]
  106. b2 = oS.b - Ej- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.K[i,j]- oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.K[j,j]
  107. if (0 < oS.alphas[i]<oS.C):
  108. oS.b = b1
  109. elif (0 < oS.alphas[j]<oS.C):
  110. oS.b = b2
  111. else:
  112. oS.b = (b1 + b2)/2.0
  113. return 1
  114. else:
  115. return 0
  116. def smoP(dataMatIn, classLabels, C, toler, maxIter,kTup=('lin', 0)):
  117. oS = optStruct(mat(dataMatIn),mat(classLabels).transpose(),C,toler, kTup)
  118. iter = 0
  119. entireSet = True
  120. alphaPairsChanged = 0
  121. while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)):
  122. alphaPairsChanged = 0
  123. if entireSet:
  124. for i in range(oS.m):
  125. alphaPairsChanged += innerL(i,oS)
  126. print("fullSet, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
  127. iter += 1
  128. else:
  129. nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]
  130. for i in nonBoundIs:
  131. alphaPairsChanged += innerL(i,oS)
  132. print("non-bound, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
  133. iter += 1
  134. if entireSet:
  135. entireSet = False
  136. elif (alphaPairsChanged == 0):
  137. entireSet = True
  138. print("iteration number: %d" % iter)
  139. return oS.b,oS.alphas
  140. def testRbf(data_train,data_test):
  141. dataArr,labelArr = loadDataSet(data_train)
  142. b,alphas = smoP(dataArr, labelArr, 200, 0.0001, 10000, ('rbf', 0.2))
  143. datMat=mat(dataArr)
  144. labelMat = mat(labelArr).transpose()
  145. svInd=nonzero(alphas)[0]
  146. sVs=datMat[svInd]
  147. labelSV = labelMat[svInd]
  148. print("there are %d Support Vectors" % shape(sVs)[0])
  149. m,n = shape(datMat)
  150. errorCount = 0
  151. for i in range(m):
  152. kernelEval = kernelTrans(sVs,datMat[i,:],('rbf', 1.3))
  153. predict=kernelEval.T * multiply(labelSV,alphas[svInd]) + b
  154. if sign(predict)!=sign(labelArr[i]):
  155. errorCount += 1
  156. print("the training error rate is: %f" % (float(errorCount)/m))
  157. dataArr_test,labelArr_test = loadDataSet(data_test)
  158. errorCount_test = 0
  159. datMat_test=mat(dataArr_test)
  160. labelMat = mat(labelArr_test).transpose()
  161. m,n = shape(datMat_test)
  162. for i in range(m):
  163. kernelEval = kernelTrans(sVs,datMat_test[i,:],('rbf', 0.1))
  164. predict=kernelEval.T * multiply(labelSV,alphas[svInd]) + b
  165. if sign(predict)!=sign(labelArr_test[i]):
  166. errorCount_test += 1
  167. print("the test error rate is: %f" % (float(errorCount_test)/m))
  168. return dataArr,labelArr,alphas
  169. def showClassifer(dataMat,labelMat,alphas):
  170. data_plus = []
  171. data_minus = []
  172. for i in range(len(dataMat)):
  173. if labelMat[i] > 0:
  174. data_plus.append(dataMat[i])
  175. else:
  176. data_minus.append(dataMat[i])
  177. data_plus_np = array(data_plus)
  178. data_minus_np = array(data_minus)
  179. plt.scatter(transpose(data_plus_np)[0], transpose(data_plus_np)[1], s=30, alpha=0.7)
  180. plt.scatter(transpose(data_minus_np)[0], transpose(data_minus_np)[1], s=30, alpha=0.7)
  181. for i, alpha in enumerate(alphas):
  182. if abs(alpha) > 0:
  183. x, y = dataMat[i]
  184. plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='red')
  185. plt.show()

MyTest:

  1. # -*- coding: utf-8 -*-
  2. import smoPrbf as svm
  3. traindata='C:\\Users\\asus-\\Desktop\\train_data.csv'
  4. testdata='C:\\Users\\asus-\\Desktop\\test_data.csv'
  5. TraindataArr,TrainlabelArr,alphas = svm.testRbf(traindata,testdata)
  6. svm.showClassifer(TraindataArr,TrainlabelArr,alphas)

当  时:

 025cad6b6a02eb83be9cef6bc18ed661.png

c7e352d14066ae808b86067599247025.png

 当  时:

 36a4ac37d4114ef8e2b92dbef3c6257b.png

c7569246049fd4d09cc6a221231ad979.png

kernelTrans函数的作用就是核函数的计算部分,对应到推导公式是:

这里的kTup就是指定使用什么核函数,kTup[0]参数是核函数类型,kTup[1]是核函数需要的超参数,注意这里只支持线性和径向基高斯(RBF)核函数 两种.

optStruct函数增加了一个字段即K,其是一个m*m的矩阵。注意它的含义:

我们的核函数是  即拿来一个点x,要和所有样本  做运算,这里的行代表的意义就是所有样本,列代表的是x所以这里是:

elf.K[:,i] = kernelTrans(self.X, self.X[i,:], kTup)

innerL变化的部分是:

eta = 2.0 * oS.K[i,j] - oS.K[i,i] - oS.K[j,j]

对比之前的:

eta = 2.0 * oS.X[i,:]*oS.X[j,:].T-oS.X[i,:]*oS.X[i,:].T-oS.X[j,:]*oS.X[j,:].T

就可以更好的理解为什么在optStruct结构体中K字段要这么设计

同理变化的地方还有:

  1. b1 = oS.b - Ei- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.K[i,i] - oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.K[i,j]
  2. b2 = oS.b - Ej- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.K[i,j]- oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.K[j,j]

calcEk变化的地方:

fXk = float(multiply(oS.alphas,oS.labelMat).T*oS.K[:,k] + oS.b)

简单来说就是原先有  的地方都要换成核函数的内积形式即  

testRbf这里主要作用就是使用了训练集去训练SVM模型,然后分别统计该模型在训练集上和测试集上的错误率。

注意这里在通过  构建权重w时是只用到是支持向量机那些点即  那些点,其实SVM的原理不就是使用这些向量机来构建的模型的嘛,那些远离间隔线的点我们是用不到的,对我们没什么作用。所以先筛选出哪些点是向量机:

svInd=nonzero(alphas)[0]

程序也会将向量机的个数打印出来。

最后讨论一下rbf的超参数意义即

值对应在在代码的:

b,alphas = smoP(dataArr, labelArr, 200, 0.0001, 10000, ('rbf', 0.2))

b,alphas = smoP(dataArr, labelArr, 200, 0.0001, 10000, ('rbf', 1.2))

通过上面的实验我们可以大体看出随着  的增加,支持向量机的个数在减少,由原来的43个减少到25(红色圆圈的点就是支持向量机),  的取值存在一个最优解,当  太大,支持向量机太少,也就是说我们利用了很少的点去决策,显然结果不好,正如上面体现的那样,测试集的错误在上升,当  太小,支持向量机太多,也就是我们基本利用了所有样本点,其实这个时候已经退化到类似KNN啦,因为KNN就是利用了到所有样本点的距离来决策的,可能会有这样的疑问?不对呀?使用KNN时不是会指定利用多少个点吗?不是利用所有点呀?哈哈哈,仔细想想,它的过程是先计算到全部样本的距离,然后再从中选择K个最近距离的点来进行比较的,所以它每次要用到的是全部样本点,而SVM是一旦训练出  后,在之后的决策中就只使用  的样本,即使用部分点,这回明白了吧,再者SVM本质也是和KNN一样使用距离来决策的,所以才说当支持向量机太多的时候,我们不就是使用全部样本点通过计算距离来决策的嘛,这和KNN特别相似,当然啦,说了半天这也不是什么重要的事情,只是为了增加SVM

的理解,最重要的还是要通过调试找到RBF最佳的超参数值。

总结

a613b6f672712885cf4bd4464aa30a4c.gif

关于更多sklearn的SVM更多调用请看笔者之前写的一篇博客:

python_sklearn机器学习算法系列之SVM支持向量机算法_爱吃火锅的博客-CSDN博客

当然还有其他机器学习的调用

  1. # -*- coding: utf-8 -*-
  2. from numpy import *
  3. import matplotlib.pyplot as plt
  4. from sklearn import metrics
  5. from sklearn import svm
  6. def loadDataSet(filename):
  7. dataMat=[]
  8. labelMat=[]
  9. fr=open(filename)
  10. for line in fr.readlines():
  11. lineArr=line.strip().split(',')
  12. dataMat.append([float(lineArr[0]),float(lineArr[1])])
  13. labelMat.append(float(lineArr[2]))
  14. return dataMat,labelMat
  15. traindata='C:\\Users\\asus-\\Desktop\\train_data.csv'
  16. testdata='C:\\Users\\asus-\\Desktop\\test_data.csv'
  17. x_train,y_train = loadDataSet(traindata)
  18. x_test,y_test = loadDataSet(testdata)
  19. clf1 = svm.SVC(C=0.8, kernel='rbf', gamma=10, decision_function_shape='ovr')
  20. clf2 = svm.SVC(C=0.8, kernel='rbf', gamma=20, decision_function_shape='ovr')
  21. clf1.fit(x_train, y_train)
  22. clf2.fit(x_train, y_train)
  23. y_predict1=clf1.predict(x_test)
  24. y_predict2=clf2.predict(x_test)
  25. print('--------------------- gamma=10--------------------------------')
  26. print(metrics.classification_report(y_test,y_predict1))
  27. print('--------------------- gamma=20--------------------------------')
  28. print(metrics.classification_report(y_test,y_predict2))

 1be9ad2b7bd091e5ad25b0fbd8b014f7.png

结尾给一下我们用的数据集,方便大家实验:

Train_data:

-0.2148240.662756-1
-0.061569-0.0918751
0.4069330.648055-1
0.223650.1301421
0.2313170.766906-1
-0.7488-0.531637-1
-0.5577890.375797-1
0.207123-0.0194631
0.2864620.71947-1
0.1953-0.1790391
-0.152696-0.153031
0.3844710.653336-1
-0.11728-0.1532171
-0.2380760.0005831
-0.4135760.1456811
0.490767-0.680029-1
0.199894-0.1993811
-0.3560480.53796-1
-0.392868-0.1252611
0.353588-0.0706171
0.0209840.92572-1
-0.475167-0.346247-1
0.0749520.0427831
0.394164-0.0582171
0.6634180.436525-1
0.4021580.577744-1
-0.449349-0.0380741
0.61908-0.088188-1
0.268066-0.0716211
-0.0151650.3593261
0.539368-0.374972-1
-0.3191530.629673-1
0.6944240.64118-1
0.0795220.1931981
0.253289-0.2858611
-0.035558-0.0100861
-0.4034830.474466-1
-0.0343120.995685-1
-0.5906570.438051-1
-0.098871-0.0239531
-0.2500010.1416211
-0.0129980.525985-1
0.1537380.491531-1
0.388215-0.656567-1
0.0490080.0134991
0.0682860.3927411
0.7478-0.06663-1
0.004621-0.0429321
-0.70160.190983-1
0.055413-0.024381
0.035398-0.3336821
0.2117950.0246891
-0.0456770.1729071
0.5952220.20957-1
0.2294650.2504091
-0.0892930.0681981
0.3843-0.176571
0.834912-0.110321-1
-0.3077680.503038-1
-0.777063-0.348066-1
0.017390.1524411
-0.293382-0.1397781
-0.2032720.2868551
0.957812-0.152444-1
0.004609-0.0706171
-0.7554310.096711-1
-0.5264870.547282-1
-0.2468730.833713-1
0.185639-0.0661621
0.8519340.456603-1
-0.8279120.117122-1
0.233512-0.1062741
0.583671-0.709033-1
-0.4870230.62514-1
-0.4489390.1767251
0.155907-0.1663711
0.3342040.381237-1
0.081536-0.1062121
0.2272220.527437-1
0.759290.33072-1
0.204177-0.0235161
0.5779390.403784-1
-0.5685340.442948-1
-0.011520.0211651
0.875720.422476-1
0.297885-0.632874-1
-0.0158210.0312261
0.541359-0.205969-1
-0.689946-0.508674-1
-0.3430490.841653-1
0.523902-0.436156-1
0.249281-0.71184-1
0.1934490.574598-1
-0.257542-0.753885-1
-0.0216050.158081
0.601559-0.727041-1
-0.7916030.095651-1
-0.908298-0.053376-1
0.122020.850966-1
-0.725568-0.292022-1

Test_data:

0.676771-0.486687-1
0.0084730.186071
-0.7277890.594062-1
0.1123670.2878521
0.383633-0.0380681
-0.927138-0.032633-1
-0.842803-0.423115-1
-0.003677-0.3673381
0.443211-0.698469-1
-0.4738350.0052331
0.6167410.590841-1
0.557463-0.373461-1
-0.498535-0.223231-1
-0.2467440.2764131
-0.76198-0.244188-1
0.641594-0.479861-1
-0.659140.52983-1
-0.054873-0.23891
-0.089644-0.2446831
-0.431576-0.481538-1
-0.0995350.728679-1
-0.1884280.1564431
0.2670510.3181011
0.222114-0.528887-1
0.0303690.1133171
0.3923210.0260891
0.298871-0.915427-1
-0.034581-0.1338871
0.4059560.206981
0.144902-0.605762-1
0.274362-0.4013381
0.397998-0.780144-1
0.0378630.1551371
-0.010363-0.004171
0.5065190.486619-1
0.000082-0.0206251
0.057761-0.155141
0.027748-0.553763-1
-0.413363-0.74683-1
0.0815-0.0142641
0.047137-0.4912711
-0.2674590.024771
-0.148288-0.532471-1
-0.225559-0.2016221
0.77236-0.518986-1
-0.440670.688739-1
0.329064-0.0953491
0.97017-0.010671-1
-0.689447-0.318722-1
-0.465493-0.227468-1
-0.049370.4057111
-0.1661170.2748071
0.0544830.0126431
0.0213890.0761251
-0.104404-0.914042-1
0.2944870.440886-1
0.107915-0.493703-1
0.0763110.438861
0.370593-0.728737-1
0.409890.306851-1
0.2854450.474399-1
-0.870134-0.161685-1
-0.654144-0.675129-1
0.285278-0.76731-1
0.049548-0.0009071
0.030014-0.0932651
-0.1288590.2788651
0.3074630.0856671
0.023440.2986381
0.053920.2353441
0.0596750.533339-1
0.8171250.016536-1
-0.1087710.4772541
-0.1181060.0172841
0.2883390.1954571
0.567309-0.200203-1
-0.2024460.4093871
-0.330769-0.2407971
-0.4223770.480683-1
-0.2952690.3260171
0.2611320.0464781
-0.492244-0.319998-1
-0.3844190.099171
0.101882-0.781145-1
0.234592-0.3834461
-0.020478-0.901833-1
0.3284490.1866331
-0.150059-0.4091581
-0.155876-0.843413-1
-0.098134-0.1367861
0.110575-0.1972051
0.2190210.0543471
0.0301520.2516821
0.033447-0.1228241
-0.686225-0.020779-1
-0.911211-0.262011-1
0.5725570.377526-1
-0.073647-0.519163-1
-0.28183-0.797236-1
-0.5552630.126232-1
  1. 推荐阅读:
  2. 我的2022届互联网校招分享
  3. 我的2021总结
  4. 浅谈算法岗和开发岗的区别
  5. 互联网校招研发薪资汇总
  6. 2022届互联网求职现状,金910快变成铜910!!
  7. 公众号:AI蜗牛车
  8. 保持谦逊、保持自律、保持进步
  9. 发送【蜗牛】获取一份《手把手AI项目》(AI蜗牛车著)
  10. 发送【1222】获取一份不错的leetcode刷题笔记
  11. 发送【AI四大名著】获取四本经典AI电子书
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/177263?site
推荐阅读
相关标签
  

闽ICP备14008679号