From 2600c81adc5137ad7eaf6ffc0c44eddb62cf2666 Mon Sep 17 00:00:00 2001 From: jiangzhonglian Date: Fri, 22 Sep 2017 01:17:42 +0800 Subject: [PATCH 1/2] =?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]): From 7a665d85543196bd33b3898aa031f14fde5efbec Mon Sep 17 00:00:00 2001 From: jiangzhonglian Date: Fri, 22 Sep 2017 12:32:28 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=AE=8C=E6=88=90=20=E7=AC=AC6=E7=AB=A0=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=90=91=E9=87=8F=E6=9C=BASVM=E7=9A=84?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/6.支持向量机.md | 43 +++++++++++++++----------------- src/python/6.SVM/svm-complete.py | 2 +- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/docs/6.支持向量机.md b/docs/6.支持向量机.md index de2cb9ad..1b16f52b 100644 --- a/docs/6.支持向量机.md +++ b/docs/6.支持向量机.md @@ -11,8 +11,8 @@ ## 支持向量机 场景 -* 如果把所有的点看作地雷,那么我们(超平面)得找到最近所有的地雷,并保证我们离它最远。 -* 所以:选择D会比B、C分隔的效果要好很多。 +* 要给左右两边的点进行分类 +* 明显发现:选择D会比B、C分隔的效果要好很多。 ![线性可分](/images/6.SVM/SVM_3_linearly-separable.jpg) @@ -23,7 +23,7 @@ ![k_2](/images/6.SVM/k_2.jpg "k_2") -对于上述的苹果和香蕉,我们想象为2中水果类型的炸弹。(保证距离最近的炸弹,距离它们最远) +对于上述的苹果和香蕉,我们想象为2种水果类型的炸弹。(保证距离最近的炸弹,距离它们最远) 1. 寻找最大分类间距 2. 转而通过拉格朗日函数求优化的问题 @@ -74,10 +74,9 @@ This is the simplest kind of SVM (Called an LSVM) Support Vectors are those data * 类别标签用-1、1,是为了后期方便 \\(lable*(w^Tx+b)\\) 的标识和距离计算;如果 \\(lable*(w^Tx+b)>0\\) 表示预测正确,否则预测错误。 * 现在目标很明确,就是要找到`w`和`b`,因此我们必须要找到最小间隔的数据点,也就是前面所说的`支持向量`。 * 也就说,让最小的距离取最大.(最小的距离:就是最小间隔的数据点;最大:就是最大间距,为了找出最优超平面--最终就是支持向量) - * 怎么理解呢? 例如: 如果把所有的点看作地雷,那么我们(超平面)得找到最近所有的地雷,并保证我们离它最远。 - * 目标函数:\\(arg: max\left( min(lable*(w^Tx+b))*\frac{1}{||w||} \right) \\) + * 目标函数:\\(arg: max_{关于w, b} \left( min[lable*(w^Tx+b)]*\frac{1}{||w||} \right) \\) 1. 如果 \\(lable*(w^Tx+b)>0\\) 表示预测正确,也称`函数间隔`,\\(||w||\\) 可以理解为归一化,也称`几何间隔`。 - 2. 令 \\(lable*(w^Tx+b)>=1\\), 因为-1~1之间,得到的点是存在误判的可能性,所以要保障min=1,才能更好降低噪音数据影响。 + 2. 令 \\(lable*(w^Tx+b)>=1\\), 因为0~1之间,得到的点是存在误判的可能性,所以要保障 \\(min[lable*(w^Tx+b)]=1\\),才能更好降低噪音数据影响。 3. 所以本质上是求 \\(arg: max_{关于w, b} \frac{1}{||w||} \\);也就说,我们约束(前提)条件是: \\(lable*(w^Tx+b)=1\\) * 新的目标函数求解: \\(arg: max_{关于w, b} \frac{1}{||w||} \\) * => 就是求: \\(arg: min_{关于w, b} ||w|| \\) (求矩阵会比较麻烦,如果x只是 \\(\frac{1}{2}*x^2\\) 的偏导数,那么。。同样是求最小值) @@ -88,14 +87,14 @@ This is the simplest kind of SVM (Called an LSVM) Support Vectors are those data * 设g(x,y)=M-φ(x,y) # 临时φ(x,y)表示下文中 \\(label*(w^Tx+b)\\) * 定义一个新函数: F(x,y,λ)=f(x,y)+λg(x,y) * a为λ(a>=0),代表要引入的拉格朗日乘子(Lagrange multiplier) - * 那么: \\(L(w,b,\alpha)=\frac{1}{2} * ||w||^2 + \sum_{i=1}^{n} \alpha * [1 - label * (w^Tx+b)]\\) - * 因为:\\(label*(w^Tx+b)>=1, \alpha>=0\\) , 所以 \\(\alpha*[1-label*(w^Tx+b)]<=0\\) , \\(\sum_{i=1}^{n} \alpha * [1-label*(w^Tx+b)]<=0\\) + * 那么: \\(L(w,b,\alpha)=\frac{1}{2} * ||w||^2 + \sum_{i=1}^{n} \alpha_i * [1 - label * (w^Tx+b)]\\) + * 因为:\\(label*(w^Tx+b)>=1, \alpha>=0\\) , 所以 \\(\alpha*[1-label*(w^Tx+b)]<=0\\) , \\(\sum_{i=1}^{n} \alpha_i * [1-label*(w^Tx+b)]<=0\\) * 相当于求解: \\(max_{关于\alpha} L(w,b,\alpha) = \frac{1}{2} *||w||^2\\) * 如果求: \\(min_{关于w, b} \frac{1}{2} *||w||^2\\) , 也就是要求: \\(min_{关于w, b} \left( max_{关于\alpha} L(w,b,\alpha)\right)\\) * 现在转化到对偶问题的求解 * \\(min_{关于w, b} \left(max_{关于\alpha} L(w,b,\alpha) \right) \\) >= \\(max_{关于\alpha} \left(min_{关于w, b}\ L(w,b,\alpha) \right) \\) * 现在分2步 - * 先求: \\(min_{关于w, b} L(w,b,\alpha)=\frac{1}{2} * ||w||^2 + \sum_{i=1}^{n} \alpha * [1 - label * (w^Tx+b)]\\) + * 先求: \\(min_{关于w, b} L(w,b,\alpha)=\frac{1}{2} * ||w||^2 + \sum_{i=1}^{n} \alpha_i * [1 - label * (w^Tx+b)]\\) * 就是求`L(w,b,a)`关于[w, b]的偏导数, 得到`w和b的值`,并化简为:`L和a的方程`。 * 参考: 如果公式推导还是不懂,也可以参考《统计学习方法》李航-P103<学习的对偶算法> ![计算拉格朗日函数的对偶函数](/images/6.SVM/SVM_5_Lagrangemultiplier.png) @@ -111,9 +110,7 @@ This is the simplest kind of SVM (Called an LSVM) Support Vectors are those data * 这一结论十分直接,SVM中的主要工作就是要求解 alpha. -### SMO高效优化算法 - -SMO 是 SVM最流行的一种实现 +### SMO 高效优化算法 * SVM有很多种实现,最流行的一种实现是: `序列最小优化(Sequential Minimal Optimization, SMO)算法`。 * 下面还会介绍一种称为`核函数(kernel)`的方式将SVM扩展到更多数据集上。 @@ -215,7 +212,7 @@ def smoSimple(dataMatIn, classLabels, C, toler, maxIter): """smoSimple Args: - dataMatIn 数据集 + dataMatIn 特征集合 classLabels 类别标签 C 松弛变量(常量值),允许有些数据点可以处于分隔面的错误一侧。 控制最大化间隔和保证大部分的函数间隔小于1.0这两个目标的权重。 @@ -247,7 +244,7 @@ def smoSimple(dataMatIn, classLabels, C, toler, maxIter): # 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] + # 我们预测的类别 y[i] = 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]) @@ -325,7 +322,7 @@ def smoSimple(dataMatIn, classLabels, C, toler, maxIter): return b, alphas ``` -[完整代码地址:SVM简化版:应用简化版SMO算法处理小规模数据集](https://github.com/apachecn/MachineLearning/blob/master/src/python/6.SVM/svm-simple.py): +[完整代码地址: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): @@ -340,19 +337,19 @@ def smoSimple(dataMatIn, classLabels, C, toler, maxIter): * 如果觉得特征空间很装逼、很难理解。 * 可以把核函数想象成一个包装器(wrapper)或者是接口(interface),它能将数据从某个很难处理的形式转换成为另一个较容易处理的形式。 * 经过空间转换后:低维需要解决的非线性问题,就变成了高维需要解决的线性问题。 -* SVM 优化特别好的地方,在于所有的运算都可以写成内积(inner product: 是指2个向量相乘,得到单个标量 或者 数值);内核替换成核函数的方式被称为`核技巧(kernel trick)`或者`核"变电"(kernel substation)` +* SVM 优化特别好的地方,在于所有的运算都可以写成内积(inner product: 是指2个向量相乘,得到单个标量 或者 数值);内积替换成核函数的方式被称为`核技巧(kernel trick)`或者`核"变电"(kernel substation)` * 核函数并不仅仅应用于支持向量机,很多其他的机器学习算法也都用到核函数。最流行的核函数:径向基函数(radial basis function) * 径向基函数的高斯版本,其具体的公式为: ![径向基函数的高斯版本](/images/6.SVM/SVM_6_radial-basis-function.jpg) -### 项目案例: 手写数字识别的优化(无核函数) +### 项目案例: 手写数字识别的优化(有核函数) #### 项目概述 ```python -你的老板要求:你写的那个手写书别程序非常好,但是它占用内存太大。顾客无法通过无线的方式下载我们的应用。 -所以:我们可以考虑使用支持向量机,保留支持向量就行(knn需要保留所有的向量),就可以获得可比的效果。 +你的老板要求:你写的那个手写识别程序非常好,但是它占用内存太大。顾客无法通过无线的方式下载我们的应用。 +所以:我们可以考虑使用支持向量机,保留支持向量就行(knn需要保留所有的向量),就可以获得非常好的效果。 ``` #### 开发流程 @@ -430,7 +427,7 @@ def loadImages(dirName): > 分析数据:对图像向量进行目测 -> 训练算法:采用两种不同的核函数,并对径向基和函数采用不同的设置来运行SMO算法 +> 训练算法:采用两种不同的核函数,并对径向基核函数采用不同的设置来运行SMO算法 ```python def kernelTrans(X, A, kTup): # calc the kernel or transform data to a higher dimensional space @@ -447,7 +444,7 @@ def kernelTrans(X, A, kTup): # calc the kernel or transform data to a higher di m, n = shape(X) K = mat(zeros((m, 1))) if kTup[0] == 'lin': - # linear kernel: m*n * n*1 = m*1, 求y值 + # linear kernel: m*n * n*1 = m*1 K = X * A.T elif kTup[0] == 'rbf': for j in range(m): @@ -550,12 +547,12 @@ def testDigits(kTup=('rbf', 10)): print("the test error rate is: %f" % (float(errorCount) / m)) ``` -> 使用算法:一个图像识别的完整应用还需要一些图像处理的只是,这里并不打算深入介绍 +> 使用算法:一个图像识别的完整应用还需要一些图像处理的知识,这里并不打算深入介绍 [完整代码地址](https://github.com/apachecn/MachineLearning/blob/master/src/python/6.SVM/svm-complete.py): * * * -* **作者:[片刻](http://www.apache.wiki/display/~jiangzhonglian) [geekidentity](http://www.apache.wiki/display/~houfachao)** +* **作者:[片刻](http://cwiki.apachecn.org/display/~jiangzhonglian) [geekidentity](http://cwiki.apachecn.org/display/~houfachao)** * [GitHub地址](https://github.com/apachecn/MachineLearning): * **版权声明:欢迎转载学习 => 请标注信息来源于 [ApacheCN](http://www.apachecn.org/)** diff --git a/src/python/6.SVM/svm-complete.py b/src/python/6.SVM/svm-complete.py index b5d8bf0e..27923244 100644 --- a/src/python/6.SVM/svm-complete.py +++ b/src/python/6.SVM/svm-complete.py @@ -61,7 +61,7 @@ def kernelTrans(X, A, kTup): # calc the kernel or transform data to a higher di m, n = shape(X) K = mat(zeros((m, 1))) if kTup[0] == 'lin': - # linear kernel: m*n * n*1 = m*1, 求y值 + # linear kernel: m*n * n*1 = m*1 K = X * A.T elif kTup[0] == 'rbf': for j in range(m):