支持向量机的底层实现-Python

支持向量机数学原理

1. 支持向量机原理(一)线性支持向量机
2. 支持向量机原理(二)线性支持向量机的软间隔最大化模型
3. 支持向量机原理(三)线性不可分支持向量机与核函数
4. 支持向量机原理(四)SMO算法原理


应用简化版SMO算法处理小规模数据集  

  我们首先从一个已经准备好的文件”testSet.txt”中读入数据,然后运用简化版的SMO算法进行实验,简化版SMO的具体流程请看代码注释。文件”testSet.txt”的数据集如下图所示:
这里写图片描述
  

'''
    def loadDataSet(fileName):从数据集文件fileName中读入数据,返回数据列表dataMat和标签列表labelMat
'''
def loadDataSet(fileName):
    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

'''
    def selectJrand(i,m):从[1,m)中,随机返回一个不等于i的数
'''
def selectJrand(i,m):
    j=i #we want to select any J not equal to i
    while (j==i):
        j = int(random.uniform(0,m))
    return j

'''
    def clipAlpha(aj,H,L): 这个是剪辑函数,如果aj大于边界H,则返回H;如果aj小于边界L,则返回L;如果L<=aj<=H,则直接返回aj
'''
def clipAlpha(aj,H,L):
    if aj > H: 
        aj = H
    if L > aj:
        aj = L
    return aj

'''
    def smoSimple(dataMatIn, classLabels, C, toler, maxIter):简化版的SMO算法。
    dataMatIn是数据列表,classLabels是标签列表,toler是松弛因子,C是toler的惩罚系数,maxIter是最大迭代次数。
    该函数首先遍历每个ai,然后再寻找当前ai对应合适的aj。如果遍历所有的ai都没有找到合适的aj,则重新遍历ai,但是该过程的最大次数为maxIter。
    该函数返回列表alphas、最佳的b
'''
def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
    dataMatrix = mat(dataMatIn);
    labelMat = mat(classLabels).transpose() #将classLabels从行向量转化为列向量
    b = 0; m,n = shape(dataMatrix)
    alphas = mat(zeros((m,1)))
    iter = 0 #iter表示寻找ai-aj对的连续失败次数
    while (iter < maxIter):
        alphaPairsChanged = 0
        for i in range(m): #这是外循环,用于遍历所有的参数ai
            fXi = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b
                 # multiply(alphas,labelMat).T表示列向量alphas与列向量labelMat对应元素相乘,然后再转置为行向量
            Ei = fXi - float(labelMat[i])
            if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
                 # ‘if’检查是否违反KKT条件,如果没有违反则继续选择第二个参数aj
                j = selectJrand(i,m)  #随机从区间[i,m)中选出一个aj
                fXj = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b
                Ej = fXj - float(labelMat[j])
                alphaIold = alphas[i].copy(); alphaJold = alphas[j].copy();
                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 #重新选择ai
                eta = dataMatrix[i,:]*dataMatrix[i,:].T + dataMatrix[j,:]*dataMatrix[j,:].T - 2.0 * dataMatrix[i,:]*dataMatrix[j,:].T
                if eta <= 0: print ("eta<=0"); continue #eta<=0不能符号条件,进行下一轮,重新选择ai
                alphas[j] += labelMat[j]*(Ei - Ej)/eta #计算第二个参数aj
                alphas[j] = clipAlpha(alphas[j],H,L)
                if (abs(alphas[j] - alphaJold) < 0.00001): print ("j not moving enough"); continue
                alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j]) #通过aj和ai的关系,由aj求出ai
                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 #ai与aj已经求出,alphaPairsChanged += 1
                print ("iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
        if (alphaPairsChanged == 0): iter += 1 #如果遍历所有的ai都没有寻找到合适的aj,则表示寻找ai-aj对的连续失败次数iter+=1
        else: iter = 0 #找到了合适的ai-aj对,iter重置为0
        print ("iteration number: %d" % iter)
    return b,alphas

  使用如下代码运行上述简化版的SMO算法,算出SVM的b,和拉格朗日参数  (α1,α2,...,αm)αm=.

if __name__=='__main__':
    dataMatIn,classLabels=loadDataSet('testSet.txt')
    b,alphas=smoSimple(dataMatIn,classLabels,0.6,0.001,40)
    print(b,alphas1[alphas1>0])

  运行结果如下图所示:
  这里写图片描述


完整版的SMO算法处理小规模数据集  

  完整版的SMO算法的相关细节请查看支持向量机原理(四)SMO算法原理 。完整版的SMO算法实现如下代码:

'''
    class optStruct:不使用核函数的版本。结构体,里面存有数据集及其标签、还存有松弛因子、惩罚系数、所有的拉格朗日系数alphas、所有的参数Ei
'''
class optStruct:
    def __init__(self,dataMatIn, classLabels, C, toler):  # 使用参数初始化结构体
        self.X = dataMatIn
        self.labelMat = classLabels
        self.C = C
        self.tol = toler
        self.m = shape(dataMatIn)[0]
        self.alphas = mat(zeros((self.m,1)))
        self.b = 0
        self.eCache = mat(zeros((self.m,2))) #第一列是有效标志位,例如第k行的值为[1,Ek],则表明Ek已经计算过了。当[0,Ek],表示Ek还没有计算

'''
    def calcE(oS, k):不使用核函数的版本。使用结构体oS中的数据,计算Ek
'''
def calcEk(oS, k):
    fXk = float(multiply(oS.alphas,oS.labelMat).T*(oS.X*oS.X[k,:].T)) + oS.b
    Ek = fXk - float(oS.labelMat[k])
    return Ek

'''
    def selectJ(i, oS, Ei):不使用核函数的版本。给定ai和Ei,从结构体oS中的数据集中,找出能够使得|Ei-Ej|最大的aj,并返回下标j和Ej
'''
def selectJ(i, oS, Ei):
    maxK = -1; maxDeltaE = 0; Ej = 0
    oS.eCache[i] = [1,Ei]  #设置标志位为‘1’,表示该Ei已经计算好了
                           # #set valid #choose the alpha that gives the maximum delta E
    validEcacheList = nonzero(oS.eCache[:,0].A)[0] #返回array类型数组中非零值的下标
        #oS.eCache[:,0].A其中‘.A’的作用是将列向量oS.eCache[:,0]转化为array类型
        # 例如,b1 = array([True, False, True, False])
        # print(nonzero(b1)[0]),返回值是非零元素的下标,本句的结果是[0 2]
    if (len(validEcacheList)) > 1:
        for j in validEcacheList:
            if j == i: continue #j==i,继续寻找
            Ek = calcEk(oS, j)
            deltaE = abs(Ei - Ek)
            if (deltaE > maxDeltaE):
                maxJ = j; maxDeltaE = deltaE; Ej = Ek
        return maxK, Ej
    else:   #第一轮oS.eCache[i]的所有标志位都为'0',因为还没有开始计算
        j = selectJrand(i, oS.m)
        Ej = calcEk(oS, j)
    return j, Ej

'''
    def updateEk(oS, k):不使用核函数的版本。计算Ek,并添加入结构体oS中的oS.eCache[k]
'''
def updateEk(oS, k):#after any alpha has changed update the new value in the cache
    Ek = calcEk(oS, k)
    oS.eCache[k] = [1,Ek]

'''
    def innerL(i, oS):不使用核函数的版本。该函数给定参数ai的下标i和包含数据集的结构体oS,寻找出合适的参数aj并算出ai存入结构体oS中,并计算相应的参数b,最后返回参数0或1
    (1表示成功找到合适的参数对ai-aj)。函数innerL(i,oS)完全和函数smoSimple(dataMatIn, classLabels, C, toler, maxIter)一样,唯一
    的区别就是函数innerL(i,oS)使用了自己的数据结构oS、选择j时使用函数selectJ(i, oS, Ei)
'''
def innerL(i, oS):
    Ei = calcEk(oS, i)
    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)):
        j,Ej = selectJ(i, oS, Ei) #this has been changed from selectJrand
        alphaIold = oS.alphas[i].copy(); alphaJold = oS.alphas[j].copy();
        if (oS.labelMat[i] != oS.labelMat[j]):
            L = max(0, oS.alphas[j] - oS.alphas[i])
            H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
        else:
            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"); return 0
        eta =oS.X[i,:]*oS.X[i,:].T + oS.X[j,:]*oS.X[j,:].T-2.0 * oS.X[i,:]*oS.X[j,:].T
        if eta <= 0: print ("eta<=0"); return 0
        oS.alphas[j] += oS.labelMat[j]*(Ei - Ej)/eta
        oS.alphas[j] = clipAlpha(oS.alphas[j],H,L)
        updateEk(oS, j) #added this for the Ecache
        if (abs(oS.alphas[j] - alphaJold) < 0.00001): print ("j not moving enough"); return 0
        oS.alphas[i] += oS.labelMat[j]*oS.labelMat[i]*(alphaJold - oS.alphas[j])#update i by the same amount as j
        updateEk(oS, i) #added this for the Ecache                    #the update is in the oppostie direction
        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]): oS.b = b1
        elif (0 < oS.alphas[j]) and (oS.C > oS.alphas[j]): oS.b = b2
        else: oS.b = (b1 + b2)/2.0
        return 1
    else: return 0

'''
    def smoP(dataMatIn, classLabels, C, toler, maxIter): 该函数是不使用核函数的完整版SMO。
    参数ai首先在所有的alphas中遍历来寻找aj,然后参数ai再在非边界值的alphas中遍历来寻找aj,这两个过程交替进行。
    该函数最后返回结构体oS中的b和向量alphas
'''
def smoP(dataMatIn, classLabels, C, toler, maxIter):
    oS = optStruct(mat(dataMatIn),mat(classLabels).transpose(),C,toler)
    iter = 0 #变量iter统计两种情况,如果遍历所有的ai就iter+=1,或者遍历所有的非边界值ai就iter+=1
    entireSet = True; alphaPairsChanged = 0
    while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)):
        alphaPairsChanged = 0
        if entireSet:   #当entireSet==True,就遍历所有的alphas作为ai(i是从0到m-1)
            for i in range(oS.m):        
                alphaPairsChanged += innerL(i,oS)
                print ("fullSet, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
            iter += 1
        else:#遍历所有的非边界值的alphas作为ai
            nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0] #返回oS.alphas列向量中,非边界值的alphas下标
                 #nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]返回oS.alphas列向量中,非零元素值且在区间(0,C)的元素下标。
                 #例如a=array([-1,2,3])
                 # print(a>0)
                 # print(a<2.5)
                 # print((a>0)*(a<2.5))
                 # print(nonzero((a>0)*(a<2.5)))
                 # print(nonzero((a>0)*(a<2.5))[0])
                 #结果是
                 #[False  True  True]
                 #[ True  True False]
                 #[False  True False]
                 #(array([1], dtype=int64),)
                 #[1]
            for i in nonBoundIs: #nonBoundIs是非边界值的alphas下标,alphas下标与x的下标相等
                alphaPairsChanged += innerL(i,oS)
                print ("non-bound, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
            iter += 1
        if entireSet: entireSet = False #只要本轮是entireSet循环,无论是否找到参数对ai-aj,都关闭entireSet的循环
        elif (alphaPairsChanged == 0): entireSet = True  #无论本轮是entireSet循环还是非边界值循环,只要本轮找不到合适的参数对ai-aj,就打开entireSet循环
        print ("iteration number: %d" % iter)
    return oS.b,oS.alphas

  还是使用简化版SMO算法使用的数据集,我们运行以下代码,看看完整版的SMO算法效果如何:

if __name__=='__main__':
    dataMatIn,classLabels=loadDataSet('testSet.txt')
    b,alphas=smoP(dataMatIn,classLabels,0.6,0.001,40)
    print(b,alphas[alphas>0])

这里写图片描述
  

我们可以明显看出来,简化版SMO算法需要迭代40次,完整版SMO算法只需要迭代5次就能出结果。


在复杂数据上应用核函数 

  这里我们使用另一个数据集“testSetRBF.txt”来进行加上核函数的完整版SMO算法。本例子使用径向基核函数(RBF)作为核函数,其表达式为:

k(x,y)=exp(||xy||22σ2)

  代码实现的每一部分都已经标上注释,且在关键语句也给出了测试代码的注释来帮助理解,具体代码如下:

'''
    def kernelTrans(X, A, kTup):核函数。 X是数据集矩阵,A是某个样本Xi的行向量,kTup('type',参数delta)是控制核函数类型的元组。
    该函数根据不同的参数kTup,以不同的方式计算数据集矩阵X与样本A的径向基函数结果。
    例如X有m行(每行是一个样本的行向量),A是某个样本Xi的行向量,则该函数返回一个列向量[K1i,K2i,...,Kmi]  
    如果kTup[0]=='lin',则执行线性核。
    如果kTup[0]=='rbf',则执行径向基函数核
    其中kTup[1]是参数delta
'''
def kernelTrans(X, A, kTup): #calc the kernel or transform data to a higher dimensional space
    m,n = shape(X)
    K = mat(zeros((m,1)))
    if kTup[0]=='lin': K = X * A.T   #元组kTup是lin的话,就执行线性核
    elif kTup[0]=='rbf': #元组是径向基函数rbf的话,就执行rbf核
        for j in range(m):
            deltaRow = X[j,:] - A #行向量X[j,:]和行向量A对应元素相减
            K[j] = deltaRow*deltaRow.T #计算|xj-y|^2,xj和y分别表示两个样本
        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

'''
    class optStructK:使用核函数的版本。
    这是结构体,里面存有数据集及其标签、还存有松弛因子、惩罚系数、所有的拉格朗日系数alphas、所有的参数Ei。
    相比没有使用核函数的版本,里面新增一个核函数矩阵。
'''
class optStructK:
    def __init__(self,dataMatIn, classLabels, C, toler, kTup):  # Initialize the structure with the parameters 
        self.X = dataMatIn
        self.labelMat = classLabels
        self.C = C
        self.tol = toler
        self.m = shape(dataMatIn)[0]
        self.alphas = mat(zeros((self.m,1)))
        self.b = 0
        self.eCache = mat(zeros((self.m,2))) #first column is valid flag
        self.K = mat(zeros((self.m,self.m)))
        for i in range(self.m): #构建核函数矩阵
            self.K[:,i] = kernelTrans(self.X, self.X[i,:], kTup)

'''
    def calcEkK(oS, k):使用核函数的版本。从结构体oS中的数据集中,计算Ek
'''
def calcEkK(oS, k):
    fXk = float(multiply(oS.alphas,oS.labelMat).T*oS.K[:,k] + oS.b)
    Ek = fXk - float(oS.labelMat[k])
    return Ek

'''
    def selectJK(i, oS, Ei):使用核函数的版本。给定ai和Ei,从结构体oS中的数据集中,找出能够使得|Ei-Ej|最大的aj,并返回下标j和Ej
'''
def selectJK(i, oS, Ei):
    maxK = -1; maxDeltaE = 0; Ej = 0
    oS.eCache[i] = [1,Ei]  #设置标志位为‘1’,表示该Ei已经计算好了
                           # #set valid #choose the alpha that gives the maximum delta E
    validEcacheList = nonzero(oS.eCache[:,0].A)[0] #返回array类型数组中非零值的下标
        #oS.eCache[:,0].A其中‘.A’的作用是将列向量oS.eCache[:,0]转化为array类型
        # 例如,b1 = array([True, False, True, False])
        # print(nonzero(b1)[0]),返回值是非零元素的下标,本句的结果是[0 2]
    if (len(validEcacheList)) > 1:
        for j in validEcacheList:
            if j == i: continue #j==i,继续寻找
            Ek = calcEkK(oS, j)
            deltaE = abs(Ei - Ek)
            if (deltaE > maxDeltaE):
                maxJ = j; maxDeltaE = deltaE; Ej = Ek
        return maxK, Ej
    else:   #第一轮oS.eCache[i]的所有标志位都为'0',因为还没有开始计算
        j = selectJrand(i, oS.m)
        Ej = calcEkK(oS, j)
    return j, Ej

'''
    def updateEk(oS, k):使用核函数的版本。计算Ek,并将Ek相应的存入 oS.eCache[k]
'''
def updateEkK(oS, k):#after any alpha has changed update the new value in the cache
    Ek = calcEkK(oS, k)
    oS.eCache[k] = [1,Ek]

'''
    def innerLK(i, oS):使用核函数的版本。该函数给定参数ai的下标i和包含数据集的结构体oS,寻找出合适的参数aj并算出ai存入结构体oS中,并计算相应的参数b,最后返回参数0或1
    (1表示成功找到合适的参数对ai-aj)。函数innerL(i,oS)完全和函数smoSimple(dataMatIn, classLabels, C, toler, maxIter)一样,唯一
    的区别就是函数innerL(i,oS)使用了自己的数据结构oS、选择j时使用函数selectJ(i, oS, Ei)
'''
def innerLK(i, oS):
    Ei = calcEkK(oS, i)
    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)):
        j,Ej = selectJK(i, oS, Ei) #this has been changed from selectJrand
        alphaIold = oS.alphas[i].copy();
        alphaJold = oS.alphas[j].copy();
        if (oS.labelMat[i] != oS.labelMat[j]):
            L = max(0, oS.alphas[j] - oS.alphas[i])
            H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
        else:
            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"); return 0
        eta = oS.K[i,i] + oS.K[j,j]-2.0 * oS.K[i,j] #changed for kernel
        if eta <= 0: print ("eta<=0"); return 0
        oS.alphas[j] += oS.labelMat[j]*(Ei - Ej)/eta
        oS.alphas[j] = clipAlpha(oS.alphas[j],H,L)
        updateEkK(oS, j) #added this for the Ecache
        if (abs(oS.alphas[j] - alphaJold) < 0.00001): print ("j not moving enough"); return 0
        oS.alphas[i] += oS.labelMat[j]*oS.labelMat[i]*(alphaJold - oS.alphas[j])#update i by the same amount as j
        updateEkK(oS, i) #added this for the Ecache                    #the update is in the oppostie direction
        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]): oS.b = b1
        elif (0 < oS.alphas[j]) and (oS.C > oS.alphas[j]): oS.b = b2
        else: oS.b = (b1 + b2)/2.0
        return 1
    else: return 0

'''
    def smoPK(dataMatIn, classLabels, C, toler, maxIter,kTup=('lin', 0)):该函数是使用核函数的完整版SMO。
    参数ai首先在所有的alphas中遍历来寻找aj,然后参数ai再在非边界值的alphas中遍历来寻找aj,这两个过程交替进行。
    该函数最后返回结构体oS中的b和向量alphas
'''
def smoPK(dataMatIn, classLabels, C, toler, maxIter,kTup=('lin', 0)):    #full Platt SMO
    oS = optStructK(mat(dataMatIn),mat(classLabels).transpose(),C,toler, kTup)
    iter = 0
    entireSet = True; alphaPairsChanged = 0
    while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)):
        alphaPairsChanged = 0
        if entireSet:   #go over all
            for i in range(oS.m):        
                alphaPairsChanged += innerLK(i,oS)
                print ("fullSet, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
            iter += 1
        else:#go over non-bound (railed) alphas
            nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]
            for i in nonBoundIs:
                alphaPairsChanged += innerLK(i,oS)
                print ("non-bound, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
            iter += 1
        if entireSet: entireSet = False #toggle entire set loop
        elif (alphaPairsChanged == 0): entireSet = True  
        print ("iteration number: %d" % iter)
    return oS.b,oS.alphas

'''
    def calcWs(alphas,dataArr,classLabels):由数组alphas、数据集dataArr、标签列表classLabels,计算出参数w。
'''
def calcWs(alphas,dataArr,classLabels):
    X = mat(dataArr); labelMat = mat(classLabels).transpose()
    m,n = shape(X)
    w = zeros((n,1)) # w是n行一列的向量,n是一个样本的属性数字
    for i in range(m):
        w += multiply(alphas[i]*labelMat[i],X[i,:].T)
          #对于数组而言,multiply()和*一样,而对于矩阵而言,multiply是对应元素相乘,而*是矩阵乘法
          # 这里的乘法*是对于数组而言,第i号元素alphas[i]*labelMat[i]的结果是一个数字
          #X[i,:]是第i个样本Xi的所有属性组成的向量
          #例如, Xi=array([[1,2,3]])
          # print(Xi)
          # print(Xi.T)
          # print(multiply(3,Xi.T))
          #结果是:
          # [[1 2 3]]
          # [[1]
          #  [2]
          #  [3]]
          # [[3]
          #  [6]
          #  [9]]
    return w

'''
    def testRbf(delta=1.3):delta是径向基核函数的参数。
    该函数将'testSetRBF.txt'作为训练集,然后得出b,alphas。
    然后给出在训练集'testSetRBF.txt'的分类错误率。
    然后使用训练集得到的分割超平面,在测试集'testSetRBF2.txt'中计算分类错误率。
'''
def testRbf(delta=1.3):
    dataArr,labelArr = loadDataSet('testSetRBF.txt')
    b,alphas = smoPK(dataArr, labelArr, 200, 0.0001, 10000, ('rbf', delta)) #C=200 important
    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,:],('rbf', delta))
        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))
    dataArr,labelArr = loadDataSet('testSetRBF2.txt')
    errorCount = 0
    datMat=mat(dataArr); labelMat = mat(labelArr).transpose()
    m,n = shape(datMat)
    for i in range(m):
        kernelEval = kernelTrans(sVs,datMat[i,:],('rbf', delta))#计算支持向量与数据集的核函数结果
        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))

  运行如下代码并查看结果:

if __name__=='__main__':
    testRbf()

这里写图片描述


在手写数据集应用核函数  

  下面是读取‘手写数据集’的代码,并调用SVM算法进行测试:

'''
    def img2vector(filename):filename为32*32的图片,
    将32*32位数据按行为单位,摆成一个1024位的列向量
'''
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):获取文件夹dirName下的所有手写数据集文件数据,最后
    返回数据集矩阵trainingMat(每行有1024位,代表一个32*32的图片),
    类别列表hwLabels(数字'9'的标签是+1,其余数字的标签是-1)
'''
def loadImages(dirName):
    from os import listdir
    hwLabels = []
    trainingFileList = listdir(dirName)  #将文件夹dirName下的所有文件路径记录在列表trainingFileList
    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) #如果是数字'9',则归类为-1
        else: hwLabels.append(1) #如果数字不等于'9',则归类为 1
        trainingMat[i,:] = img2vector('%s/%s' % (dirName, fileNameStr))
    return trainingMat, hwLabels    

'''
    def testDigits(kTup=('rbf', 10)):使用带有核函数版本的SVM对手写数据集进行分类
'''
def testDigits(kTup=('rbf', 100)):
    dataArr,labelArr = loadImages('trainingDigits')
    b,alphas = smoPK(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)
        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))
    dataArr,labelArr = loadImages('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)
        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) )

  下图是使用线性核函数对手写数据集验证的代码和结果:
 这里写图片描述
 

猜你喜欢

转载自blog.csdn.net/made_in_china_too/article/details/79572451