From 2600c81adc5137ad7eaf6ffc0c44eddb62cf2666 Mon Sep 17 00:00:00 2001 From: jiangzhonglian Date: Fri, 22 Sep 2017 01:17:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=90=91=E9=87=8F=E6=9C=BA=E7=9A=84=E9=A1=B9=E7=9B=AE=E6=A1=88?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/6.支持向量机.md | 372 +++++++++++++++++++- src/python/6.SVM/svm-complete.py | 62 ++-- src/python/6.SVM/svm-complete_Non-Kernel.py | 12 +- src/python/6.SVM/svm-simple.py | 22 +- 4 files changed, 422 insertions(+), 46 deletions(-) diff --git a/docs/6.支持向量机.md b/docs/6.支持向量机.md index 5a7dcd2e..de2cb9ad 100644 --- a/docs/6.支持向量机.md +++ b/docs/6.支持向量机.md @@ -99,13 +99,13 @@ This is the simplest kind of SVM (Called an LSVM) Support Vectors are those data * 就是求`L(w,b,a)`关于[w, b]的偏导数, 得到`w和b的值`,并化简为:`L和a的方程`。 * 参考: 如果公式推导还是不懂,也可以参考《统计学习方法》李航-P103<学习的对偶算法> ![计算拉格朗日函数的对偶函数](/images/6.SVM/SVM_5_Lagrangemultiplier.png) -* 终于得到课本上的公式: \\(max\ \{\alpha\}\ \left( \sum_{i=1}^{m} \alpha_i - \frac{1}{2} \sum_{i, j=1}^{m} label_i·label_j·\alpha_i·\alpha_j· \right) \\) +* 终于得到课本上的公式: \\(max_{关于\alpha} \left( \sum_{i=1}^{m} \alpha_i - \frac{1}{2} \sum_{i, j=1}^{m} label_i·label_j·\alpha_i·\alpha_j· \right) \\) * 约束条件: \\(a>=0\\) 并且 \\(\sum_{i=1}^{m} a_i·label_i=0\\) > 松弛变量(slack variable) * 我们知道几乎所有的数据都不那么干净, 通过引入松弛变量来`允许数据点可以处于分隔面错误的一侧`。 -* 约束条件: \\(C>=a>=0,\ and\ \sum_{i=1}^{m} a_i·label_i=0\\) +* 约束条件: \\(C>=a>=0\\) 并且 \\(\sum_{i=1}^{m} a_i·label_i=0\\) * 这里常量C用于控制“最大化间隔”和“保证大部分点的函数间隔小于1.0” 这两个目标的权重。 * 常量C是一个常数,我们通过调节该参数得到不同的结果。一旦求出了所有的alpha,那么分隔超平面就可以通过这些alpha来表示。 * 这一结论十分直接,SVM中的主要工作就是要求解 alpha. @@ -164,14 +164,170 @@ SMO 是 SVM最流行的一种实现 使用数据类型:数值型和标称型数据。 ``` -> SVM简化版:应用简化版SMO算法处理小规模数据集 +### 课本案例(无核函数) -代码可参考 svm-simple.py +#### 项目概述 -> SVM完整版:使用完整 Platt SMO算法加速优化 +对小规模数据点进行分类 -代码可参考 svm-complete_Non-Kernel.py -* 优化点:选择alpha的方式不同。 +#### 开发流程 + +> 收集数据 + +文本文件格式: + +```python +3.542485 1.977398 -1 +3.018896 2.556416 -1 +7.551510 -1.580030 1 +2.114999 -0.004466 -1 +8.127113 1.274372 1 +``` + +> 准备数据 + +```python +def loadDataSet(fileName): + """ + 对文件进行逐行解析,从而得到第行的类标签和整个特征矩阵 + Args: + fileName 文件名 + Returns: + dataMat 特征矩阵 + labelMat 类标签 + """ + dataMat = [] + labelMat = [] + fr = open(fileName) + for line in fr.readlines(): + lineArr = line.strip().split('\t') + dataMat.append([float(lineArr[0]), float(lineArr[1])]) + labelMat.append(float(lineArr[2])) + return dataMat, labelMat +``` + +> 分析数据: 无 + +> 训练算法 + +```python +def smoSimple(dataMatIn, classLabels, C, toler, maxIter): + """smoSimple + + Args: + dataMatIn 数据集 + classLabels 类别标签 + C 松弛变量(常量值),允许有些数据点可以处于分隔面的错误一侧。 + 控制最大化间隔和保证大部分的函数间隔小于1.0这两个目标的权重。 + 可以通过调节该参数达到不同的结果。 + toler 容错率(是指在某个体系中能减小一些因素或选择对某个系统产生不稳定的概率。) + maxIter 退出前最大的循环次数 + Returns: + b 模型的常量值 + alphas 拉格朗日乘子 + """ + dataMatrix = mat(dataMatIn) + # 矩阵转置 和 .T 一样的功能 + labelMat = mat(classLabels).transpose() + m, n = shape(dataMatrix) + + # 初始化 b和alphas(alpha有点类似权重值。) + b = 0 + alphas = mat(zeros((m, 1))) + + # 没有任何alpha改变的情况下遍历数据的次数 + iter = 0 + while (iter < maxIter): + # w = calcWs(alphas, dataMatIn, classLabels) + # print("w:", w) + + # 记录alpha是否已经进行优化,每次循环时设为0,然后再对整个集合顺序遍历 + alphaPairsChanged = 0 + for i in range(m): + # print 'alphas=', alphas + # print 'labelMat=', labelMat + # print 'multiply(alphas, labelMat)=', multiply(alphas, labelMat) + # 我们预测的类别 y = w^Tx[i]+b; 其中因为 w = Σ(1~n) a[n]*lable[n]*x[n] + fXi = float(multiply(alphas, labelMat).T*(dataMatrix*dataMatrix[i, :].T)) + b + # 预测结果与真实结果比对,计算误差Ei + Ei = fXi - float(labelMat[i]) + + # 约束条件 (KKT条件是解决最优化问题的时用到的一种方法。我们这里提到的最优化问题通常是指对于给定的某一函数,求其在指定作用域上的全局最小值) + # 0<=alphas[i]<=C,但由于0和C是边界值,我们无法进行优化,因为需要增加一个alphas和降低一个alphas。 + # 表示发生错误的概率:labelMat[i]*Ei 如果超出了 toler, 才需要优化。至于正负号,我们考虑绝对值就对了。 + ''' + # 检验训练样本(xi, yi)是否满足KKT条件 + yi*f(i) >= 1 and alpha = 0 (outside the boundary) + yi*f(i) == 1 and 0 toler) and (alphas[i] > 0)): + + # 如果满足优化的条件,我们就随机选取非i的一个点,进行优化比较 + j = selectJrand(i, m) + # 预测j的结果 + fXj = float(multiply(alphas, labelMat).T*(dataMatrix*dataMatrix[j, :].T)) + b + Ej = fXj - float(labelMat[j]) + alphaIold = alphas[i].copy() + alphaJold = alphas[j].copy() + + # L和H用于将alphas[j]调整到0-C之间。如果L==H,就不做任何改变,直接执行continue语句 + # labelMat[i] != labelMat[j] 表示异侧,就相减,否则是同侧,就相加。 + if (labelMat[i] != labelMat[j]): + L = max(0, alphas[j] - alphas[i]) + H = min(C, C + alphas[j] - alphas[i]) + else: + L = max(0, alphas[j] + alphas[i] - C) + H = min(C, alphas[j] + alphas[i]) + # 如果相同,就没发优化了 + if L == H: + print("L==H") + continue + + # eta是alphas[j]的最优修改量,如果eta==0,需要退出for循环的当前迭代过程 + # 参考《统计学习方法》李航-P125~P128<序列最小最优化算法> + eta = 2.0 * dataMatrix[i, :]*dataMatrix[j, :].T - dataMatrix[i, :]*dataMatrix[i, :].T - dataMatrix[j, :]*dataMatrix[j, :].T + if eta >= 0: + print("eta>=0") + continue + + # 计算出一个新的alphas[j]值 + alphas[j] -= labelMat[j]*(Ei - Ej)/eta + # 并使用辅助函数,以及L和H对其进行调整 + alphas[j] = clipAlpha(alphas[j], H, L) + # 检查alpha[j]是否只是轻微的改变,如果是的话,就退出for循环。 + if (abs(alphas[j] - alphaJold) < 0.00001): + print("j not moving enough") + continue + # 然后alphas[i]和alphas[j]同样进行改变,虽然改变的大小一样,但是改变的方向正好相反 + alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j]) + # 在对alpha[i], alpha[j] 进行优化之后,给这两个alpha值设置一个常数b。 + # w= Σ[1~n] ai*yi*xi => b = yj- Σ[1~n] ai*yi(xi*xj) + # 所以: b1 - b = (y1-y) - Σ[1~n] yi*(a1-a)*(xi*x1) + # 为什么减2遍? 因为是 减去Σ[1~n],正好2个变量i和j,所以减2遍 + b1 = b - Ei- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i, :]*dataMatrix[i, :].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[i, :]*dataMatrix[j, :].T + b2 = b - Ej- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i, :]*dataMatrix[j, :].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[j, :]*dataMatrix[j, :].T + if (0 < alphas[i]) and (C > alphas[i]): + b = b1 + elif (0 < alphas[j]) and (C > alphas[j]): + b = b2 + else: + b = (b1 + b2)/2.0 + alphaPairsChanged += 1 + print("iter: %d i:%d, pairs changed %d" % (iter, i, alphaPairsChanged)) + # 在for循环外,检查alpha值是否做了更新,如果在更新则将iter设为0后继续运行程序 + # 知道更新完毕后,iter次循环无变化,才推出循环。 + if (alphaPairsChanged == 0): + iter += 1 + else: + iter = 0 + print("iteration number: %d" % iter) + return b, alphas +``` + +[完整代码地址:SVM简化版:应用简化版SMO算法处理小规模数据集](https://github.com/apachecn/MachineLearning/blob/master/src/python/6.SVM/svm-simple.py): + +[完整代码地址:SVM完整版,使用完整 Platt SMO算法加速优化,优化点:选择alpha的方式不同](https://github.com/apachecn/MachineLearning/blob/master/src/python/6.SVM/svm-complete_Non-Kernel.py): ## 核函数(kernel) 使用 @@ -190,15 +346,213 @@ SMO 是 SVM最流行的一种实现 ![径向基函数的高斯版本](/images/6.SVM/SVM_6_radial-basis-function.jpg) -## 支持向量机 项目实战 +### 项目案例: 手写数字识别的优化(无核函数) -### 项目实战: 手写数字识别的优化 +#### 项目概述 +```python +你的老板要求:你写的那个手写书别程序非常好,但是它占用内存太大。顾客无法通过无线的方式下载我们的应用。 +所以:我们可以考虑使用支持向量机,保留支持向量就行(knn需要保留所有的向量),就可以获得可比的效果。 +``` +#### 开发流程 +> 收集数据:提供的文本文件 +```python +00000000000000001111000000000000 +00000000000000011111111000000000 +00000000000000011111111100000000 +00000000000000011111111110000000 +00000000000000011111111110000000 +00000000000000111111111100000000 +00000000000000111111111100000000 +00000000000001111111111100000000 +00000000000000111111111100000000 +00000000000000111111111100000000 +00000000000000111111111000000000 +00000000000001111111111000000000 +00000000000011111111111000000000 +00000000000111111111110000000000 +00000000001111111111111000000000 +00000001111111111111111000000000 +00000011111111111111110000000000 +00000111111111111111110000000000 +00000111111111111111110000000000 +00000001111111111111110000000000 +00000001111111011111110000000000 +00000000111100011111110000000000 +00000000000000011111110000000000 +00000000000000011111100000000000 +00000000000000111111110000000000 +00000000000000011111110000000000 +00000000000000011111110000000000 +00000000000000011111111000000000 +00000000000000011111111000000000 +00000000000000011111111000000000 +00000000000000000111111110000000 +00000000000000000111111100000000 +``` +> 准备数据:基于二值图像构造向量 + +`将 32*32的文本转化为 1*1024的矩阵` + +```python +def img2vector(filename): + returnVect = zeros((1, 1024)) + fr = open(filename) + for i in range(32): + lineStr = fr.readline() + for j in range(32): + returnVect[0, 32 * i + j] = int(lineStr[j]) + return returnVect + +def loadImages(dirName): + from os import listdir + hwLabels = [] + print(dirName) + trainingFileList = listdir(dirName) # load the training set + m = len(trainingFileList) + trainingMat = zeros((m, 1024)) + for i in range(m): + fileNameStr = trainingFileList[i] + fileStr = fileNameStr.split('.')[0] # take off .txt + classNumStr = int(fileStr.split('_')[0]) + if classNumStr == 9: + hwLabels.append(-1) + else: + hwLabels.append(1) + trainingMat[i, :] = img2vector('%s/%s' % (dirName, fileNameStr)) + return trainingMat, hwLabels +``` + +> 分析数据:对图像向量进行目测 + +> 训练算法:采用两种不同的核函数,并对径向基和函数采用不同的设置来运行SMO算法 + +```python +def kernelTrans(X, A, kTup): # calc the kernel or transform data to a higher dimensional space + """ + 核转换函数 + Args: + X dataMatIn数据集 + A dataMatIn数据集的第i行的数据 + kTup 核函数的信息 + + Returns: + + """ + m, n = shape(X) + K = mat(zeros((m, 1))) + if kTup[0] == 'lin': + # linear kernel: m*n * n*1 = m*1, 求y值 + K = X * A.T + elif kTup[0] == 'rbf': + for j in range(m): + deltaRow = X[j, :] - A + K[j] = deltaRow * deltaRow.T + # 径向基函数的高斯版本 + K = exp(K / (-1 * kTup[1] ** 2)) # divide in NumPy is element-wise not matrix like Matlab + else: + raise NameError('Houston We Have a Problem -- That Kernel is not recognized') + return K + +def smoP(dataMatIn, classLabels, C, toler, maxIter, kTup=('lin', 0)): + """ + 完整SMO算法外循环,与smoSimple有些类似,但这里的循环退出条件更多一些 + Args: + dataMatIn 数据集 + classLabels 类别标签 + C 松弛变量(常量值),允许有些数据点可以处于分隔面的错误一侧。 + 控制最大化间隔和保证大部分的函数间隔小于1.0这两个目标的权重。 + 可以通过调节该参数达到不同的结果。 + toler 容错率 + maxIter 退出前最大的循环次数 + kTup 包含核函数信息的元组 + Returns: + b 模型的常量值 + alphas 拉格朗日乘子 + """ + + # 创建一个 optStruct 对象 + oS = optStruct(mat(dataMatIn), mat(classLabels).transpose(), C, toler, kTup) + iter = 0 + entireSet = True + alphaPairsChanged = 0 + + # 循环遍历:循环maxIter次 并且 (alphaPairsChanged存在可以改变 or 所有行遍历一遍) + while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)): + alphaPairsChanged = 0 + + # 当entireSet=true or 非边界alpha对没有了;就开始寻找 alpha对,然后决定是否要进行else。 + if entireSet: + # 在数据集上遍历所有可能的alpha + for i in range(oS.m): + # 是否存在alpha对,存在就+1 + alphaPairsChanged += innerL(i, oS) + # print("fullSet, iter: %d i:%d, pairs changed %d" % (iter, i, alphaPairsChanged)) + iter += 1 + + # 对已存在 alpha对,选出非边界的alpha值,进行优化。 + else: + # 遍历所有的非边界alpha值,也就是不在边界0或C上的值。 + nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0] + for i in nonBoundIs: + alphaPairsChanged += innerL(i, oS) + # print("non-bound, iter: %d i:%d, pairs changed %d" % (iter, i, alphaPairsChanged)) + iter += 1 + + # 如果找到alpha对,就优化非边界alpha值,否则,就重新进行寻找,如果寻找一遍 遍历所有的行还是没找到,就退出循环。 + if entireSet: + entireSet = False # toggle entire set loop + elif (alphaPairsChanged == 0): + entireSet = True + print("iteration number: %d" % iter) + return oS.b, oS.alphas +``` + +> 测试算法:便携一个函数来测试不同的和函数并计算错误率 + +```python +def testDigits(kTup=('rbf', 10)): + + # 1. 导入训练数据 + dataArr, labelArr = loadImages('input/6.SVM/trainingDigits') + b, alphas = smoP(dataArr, labelArr, 200, 0.0001, 10000, kTup) + datMat = mat(dataArr) + labelMat = mat(labelArr).transpose() + svInd = nonzero(alphas.A > 0)[0] + sVs = datMat[svInd] + labelSV = labelMat[svInd] + # print("there are %d Support Vectors" % shape(sVs)[0]) + m, n = shape(datMat) + errorCount = 0 + for i in range(m): + kernelEval = kernelTrans(sVs, datMat[i, :], kTup) + # 1*m * m*1 = 1*1 单个预测结果 + predict = kernelEval.T * multiply(labelSV, alphas[svInd]) + b + if sign(predict) != sign(labelArr[i]): errorCount += 1 + print("the training error rate is: %f" % (float(errorCount) / m)) + + # 2. 导入测试数据 + dataArr, labelArr = loadImages('input/6.SVM/testDigits') + errorCount = 0 + datMat = mat(dataArr) + labelMat = mat(labelArr).transpose() + m, n = shape(datMat) + for i in range(m): + kernelEval = kernelTrans(sVs, datMat[i, :], kTup) + # 1*m * m*1 = 1*1 单个预测结果 + predict = kernelEval.T * multiply(labelSV, alphas[svInd]) + b + if sign(predict) != sign(labelArr[i]): errorCount += 1 + print("the test error rate is: %f" % (float(errorCount) / m)) +``` + +> 使用算法:一个图像识别的完整应用还需要一些图像处理的只是,这里并不打算深入介绍 + +[完整代码地址](https://github.com/apachecn/MachineLearning/blob/master/src/python/6.SVM/svm-complete.py): * * * diff --git a/src/python/6.SVM/svm-complete.py b/src/python/6.SVM/svm-complete.py index f20977aa..b5d8bf0e 100644 --- a/src/python/6.SVM/svm-complete.py +++ b/src/python/6.SVM/svm-complete.py @@ -227,9 +227,15 @@ def innerL(i, oS): # 求 Ek误差:预测值-真实值的差 Ei = calcEk(oS, i) - # 约束条件 (KKT条件是解决最优化问题的时用到的一种方法。我们这里提到的最优化问题通常是指对于给定的某一函数,求其在指定作用域上的全局最小值。) + # 约束条件 (KKT条件是解决最优化问题的时用到的一种方法。我们这里提到的最优化问题通常是指对于给定的某一函数,求其在指定作用域上的全局最小值) # 0<=alphas[i]<=C,但由于0和C是边界值,我们无法进行优化,因为需要增加一个alphas和降低一个alphas。 # 表示发生错误的概率:labelMat[i]*Ei 如果超出了 toler, 才需要优化。至于正负号,我们考虑绝对值就对了。 + ''' + # 检验训练样本(xi, yi)是否满足KKT条件 + yi*f(i) >= 1 and alpha = 0 (outside the boundary) + yi*f(i) == 1 and 0 oS.tol) and (oS.alphas[i] > 0)): # 选择最大的误差对应的j进行优化。效果更明显 j, Ej = selectJ(i, oS, Ei) @@ -244,7 +250,7 @@ def innerL(i, oS): L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C) H = min(oS.C, oS.alphas[j] + oS.alphas[i]) if L == H: - print("L==H") + # print("L==H") return 0 # eta是alphas[j]的最优修改量,如果eta==0,需要退出for循环的当前迭代过程 @@ -263,7 +269,7 @@ def innerL(i, oS): # 检查alpha[j]是否只是轻微的改变,如果是的话,就退出for循环。 if (abs(oS.alphas[j] - alphaJold) < 0.00001): - print("j not moving enough") + # print("j not moving enough") return 0 # 然后alphas[i]和alphas[j]同样进行改变,虽然改变的大小一样,但是改变的方向正好相反 @@ -274,7 +280,7 @@ def innerL(i, oS): # 在对alpha[i], alpha[j] 进行优化之后,给这两个alpha值设置一个常数b。 # w= Σ[1~n] ai*yi*xi => b = yi- Σ[1~n] ai*yi(xi*xj) # 所以: b1 - b = (y1-y) - Σ[1~n] yi*(a1-a)*(xi*x1) - # 为什么减2遍? 因为是 减去Σ[1~n],当好2个变量i和j,所以减2遍 + # 为什么减2遍? 因为是 减去Σ[1~n],正好2个变量i和j,所以减2遍 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] 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] if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]): @@ -321,7 +327,7 @@ def smoP(dataMatIn, classLabels, C, toler, maxIter, kTup=('lin', 0)): for i in range(oS.m): # 是否存在alpha对,存在就+1 alphaPairsChanged += innerL(i, oS) - print("fullSet, iter: %d i:%d, pairs changed %d" % (iter, i, alphaPairsChanged)) + # print("fullSet, iter: %d i:%d, pairs changed %d" % (iter, i, alphaPairsChanged)) iter += 1 # 对已存在 alpha对,选出非边界的alpha值,进行优化。 @@ -330,7 +336,7 @@ def smoP(dataMatIn, classLabels, C, toler, maxIter, kTup=('lin', 0)): nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0] for i in nonBoundIs: alphaPairsChanged += innerL(i, oS) - print("non-bound, iter: %d i:%d, pairs changed %d" % (iter, i, alphaPairsChanged)) + # print("non-bound, iter: %d i:%d, pairs changed %d" % (iter, i, alphaPairsChanged)) iter += 1 # 如果找到alpha对,就优化非边界alpha值,否则,就重新进行寻找,如果寻找一遍 遍历所有的行还是没找到,就退出循环。 @@ -434,11 +440,12 @@ def testDigits(kTup=('rbf', 10)): svInd = nonzero(alphas.A > 0)[0] sVs = datMat[svInd] labelSV = labelMat[svInd] - print("there are %d Support Vectors" % shape(sVs)[0]) + # print("there are %d Support Vectors" % shape(sVs)[0]) m, n = shape(datMat) errorCount = 0 for i in range(m): kernelEval = kernelTrans(sVs, datMat[i, :], kTup) + # 1*m * m*1 = 1*1 单个预测结果 predict = kernelEval.T * multiply(labelSV, alphas[svInd]) + b if sign(predict) != sign(labelArr[i]): errorCount += 1 print("the training error rate is: %f" % (float(errorCount) / m)) @@ -497,27 +504,32 @@ def plotfig_SVM(xArr, yArr, ws, b, alphas): if __name__ == "__main__": - # 无核函数的测试 - # 获取特征和目标变量 - dataArr, labelArr = loadDataSet('input/6.SVM/testSet.txt') - # print labelArr + # # 无核函数的测试 + # # 获取特征和目标变量 + # dataArr, labelArr = loadDataSet('input/6.SVM/testSet.txt') + # # print labelArr - # b是常量值, alphas是拉格朗日乘子 - b, alphas = smoP(dataArr, labelArr, 0.6, 0.001, 40) - print '/n/n/n' - print 'b=', b - print 'alphas[alphas>0]=', alphas[alphas > 0] - print 'shape(alphas[alphas > 0])=', shape(alphas[alphas > 0]) - for i in range(100): - if alphas[i] > 0: - print dataArr[i], labelArr[i] - # 画图 - ws = calcWs(alphas, dataArr, labelArr) - plotfig_SVM(dataArr, labelArr, ws, b, alphas) + # # b是常量值, alphas是拉格朗日乘子 + # b, alphas = smoP(dataArr, labelArr, 0.6, 0.001, 40) + # print '/n/n/n' + # print 'b=', b + # print 'alphas[alphas>0]=', alphas[alphas > 0] + # print 'shape(alphas[alphas > 0])=', shape(alphas[alphas > 0]) + # for i in range(100): + # if alphas[i] > 0: + # print dataArr[i], labelArr[i] + # # 画图 + # ws = calcWs(alphas, dataArr, labelArr) + # plotfig_SVM(dataArr, labelArr, ws, b, alphas) - # # 有核函数的测试 + # 有核函数的测试 # testRbf(0.8) # 项目实战 # 示例:手写识别问题回顾 - # testDigits(('rbf', 20)) + testDigits(('rbf', 0.1)) + testDigits(('rbf', 5)) + testDigits(('rbf', 10)) + testDigits(('rbf', 50)) + testDigits(('rbf', 100)) + testDigits(('lin')) diff --git a/src/python/6.SVM/svm-complete_Non-Kernel.py b/src/python/6.SVM/svm-complete_Non-Kernel.py index fe1eafbb..3dd2dc58 100644 --- a/src/python/6.SVM/svm-complete_Non-Kernel.py +++ b/src/python/6.SVM/svm-complete_Non-Kernel.py @@ -176,9 +176,15 @@ def innerL(i, oS): # 求 Ek误差:预测值-真实值的差 Ei = calcEk(oS, i) - # 约束条件 (KKT条件是解决最优化问题的时用到的一种方法。我们这里提到的最优化问题通常是指对于给定的某一函数,求其在指定作用域上的全局最小值。) + # 约束条件 (KKT条件是解决最优化问题的时用到的一种方法。我们这里提到的最优化问题通常是指对于给定的某一函数,求其在指定作用域上的全局最小值) # 0<=alphas[i]<=C,但由于0和C是边界值,我们无法进行优化,因为需要增加一个alphas和降低一个alphas。 # 表示发生错误的概率:labelMat[i]*Ei 如果超出了 toler, 才需要优化。至于正负号,我们考虑绝对值就对了。 + ''' + # 检验训练样本(xi, yi)是否满足KKT条件 + yi*f(i) >= 1 and alpha = 0 (outside the boundary) + yi*f(i) == 1 and 0 oS.tol) and (oS.alphas[i] > 0)): # 选择最大的误差对应的j进行优化。效果更明显 j, Ej = selectJ(i, oS, Ei) @@ -223,7 +229,7 @@ def innerL(i, oS): # 在对alpha[i], alpha[j] 进行优化之后,给这两个alpha值设置一个常数b。 # w= Σ[1~n] ai*yi*xi => b = yj Σ[1~n] ai*yi(xi*xj) # 所以: b1 - b = (y1-y) - Σ[1~n] yi*(a1-a)*(xi*x1) - # 为什么减2遍? 因为是 减去Σ[1~n],当好2个变量i和j,所以减2遍 + # 为什么减2遍? 因为是 减去Σ[1~n],正好2个变量i和j,所以减2遍 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 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 if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]): @@ -260,6 +266,7 @@ def smoP(dataMatIn, classLabels, C, toler, maxIter): alphaPairsChanged = 0 # 循环遍历:循环maxIter次 并且 (alphaPairsChanged存在可以改变 or 所有行遍历一遍) + # 循环迭代结束 或者 循环遍历所有alpha后,alphaPairs还是没变化 while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)): alphaPairsChanged = 0 @@ -271,7 +278,6 @@ def smoP(dataMatIn, classLabels, C, toler, maxIter): alphaPairsChanged += innerL(i, oS) print("fullSet, iter: %d i:%d, pairs changed %d" % (iter, i, alphaPairsChanged)) iter += 1 - # 对已存在 alpha对,选出非边界的alpha值,进行优化。 else: # 遍历所有的非边界alpha值,也就是不在边界0或C上的值。 diff --git a/src/python/6.SVM/svm-simple.py b/src/python/6.SVM/svm-simple.py index 51fb4fb9..2b29f7aa 100644 --- a/src/python/6.SVM/svm-simple.py +++ b/src/python/6.SVM/svm-simple.py @@ -14,11 +14,11 @@ import matplotlib.pyplot as plt def loadDataSet(fileName): """ - 对文件进行逐行解析,从而得到第行的类标签和整个数据矩阵 + 对文件进行逐行解析,从而得到第行的类标签和整个特征矩阵 Args: fileName 文件名 Returns: - dataMat 数据矩阵 + dataMat 特征矩阵 labelMat 类标签 """ dataMat = [] @@ -103,11 +103,15 @@ def smoSimple(dataMatIn, classLabels, C, toler, maxIter): # 预测结果与真实结果比对,计算误差Ei Ei = fXi - float(labelMat[i]) - # 约束条件 (KKT条件是解决最优化问题的时用到的一种方法。我们这里提到的最优化问题通常是指对于给定的某一函数,求其在指定作用域上的全局最小值。) + # 约束条件 (KKT条件是解决最优化问题的时用到的一种方法。我们这里提到的最优化问题通常是指对于给定的某一函数,求其在指定作用域上的全局最小值) # 0<=alphas[i]<=C,但由于0和C是边界值,我们无法进行优化,因为需要增加一个alphas和降低一个alphas。 # 表示发生错误的概率:labelMat[i]*Ei 如果超出了 toler, 才需要优化。至于正负号,我们考虑绝对值就对了。 - # 如果你大于 负错误的概率,那么要求你小于C,这样才可以只优化一边 - # 如果你大于 正错误的概率,那么要求你大于0,这样才可以只优化一边 + ''' + # 检验训练样本(xi, yi)是否满足KKT条件 + yi*f(i) >= 1 and alpha = 0 (outside the boundary) + yi*f(i) == 1 and 0 toler) and (alphas[i] > 0)): # 如果满足优化的条件,我们就随机选取非i的一个点,进行优化比较 @@ -133,7 +137,7 @@ def smoSimple(dataMatIn, classLabels, C, toler, maxIter): # eta是alphas[j]的最优修改量,如果eta==0,需要退出for循环的当前迭代过程 # 参考《统计学习方法》李航-P125~P128<序列最小最优化算法> - eta = 2.0 * dataMatrix[i, :]*dataMatrix[j, :].T - dataMatrix[i, :]*dataMatrix[i,:].T - dataMatrix[j, :]*dataMatrix[j, :].T + eta = 2.0 * dataMatrix[i, :]*dataMatrix[j, :].T - dataMatrix[i, :]*dataMatrix[i, :].T - dataMatrix[j, :]*dataMatrix[j, :].T if eta >= 0: print("eta>=0") continue @@ -151,9 +155,9 @@ def smoSimple(dataMatIn, classLabels, C, toler, maxIter): # 在对alpha[i], alpha[j] 进行优化之后,给这两个alpha值设置一个常数b。 # w= Σ[1~n] ai*yi*xi => b = yj- Σ[1~n] ai*yi(xi*xj) # 所以: b1 - b = (y1-y) - Σ[1~n] yi*(a1-a)*(xi*x1) - # 为什么减2遍? 因为是 减去Σ[1~n],当好2个变量i和j,所以减2遍 - b1 = b - Ei- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[i,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[i,:]*dataMatrix[j,:].T - b2 = b - Ej- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[j,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[j,:]*dataMatrix[j,:].T + # 为什么减2遍? 因为是 减去Σ[1~n],正好2个变量i和j,所以减2遍 + b1 = b - Ei- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i, :]*dataMatrix[i, :].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[i, :]*dataMatrix[j, :].T + b2 = b - Ej- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i, :]*dataMatrix[j, :].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[j, :]*dataMatrix[j, :].T if (0 < alphas[i]) and (C > alphas[i]): b = b1 elif (0 < alphas[j]) and (C > alphas[j]):