第6章 支持向量机~简化版 SMO 算法

前言

  • 1996年,John Platt 发布了一个称为 SMO 的强大算法,用于训练 SVMSMO 表示序列最小化(Sequential Minimal Optimization)。Platt 的 SMO 算法是将大优化问题分解为多个小优化问题来求解的。这些小优化问题往往很容易求解,并且对它们进行顺序求解的结果与将它们作为整体来求解的结果是完全一致的。在结果完全相同的同时,SMO 算法的求解时间短很多。
  • SMO 算法的目标是求出一系列 alphab,一旦求出了这些 alpha,就很容易计算出权重向量 w 并得到分隔超平面。
  • SMO 算法的工作原理是:每次循环中选择两个 alpha 进行优化处理。一旦找到一对合适的 alpha,那么就增大其中一个同时减小另一个。这里所谓的“合适”就是指两个 alpha 必须要符合一定的条件,条件之一就是这两个 alpha 必须要在间隔边界之外,而其第二个条件则是这两个 alpha 还没有进行过区间化处理或者不在边界上。

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

  • Platt SMO 算法的完整实现需要大量代码。在接下来的第一个例子中,我们将会对算法进行简化处理,以便了解算法的基本工作思路,之后再基于简化版给出完整版。
  • 简化版代码虽然量少但执行速度慢。Platt SMO 算法中的外循环确定要优化的最佳 alpha 对。而简化版却会跳过这一部分,首先在数据集上遍历每一个 alpha, 然后在剩下的 alpha 集合中随机选择另一个 alpha 从而构建 alpha 对。这里有一点相当重要,就是我们要同时改变两个 alpha 。之所以这样做是因为我们有一个约束条件:
  • 由于改变一个 alpha 可能会导致该约束条件失效,因此我们总是同时改变两个 alpha
  • 为此,我们将构建一个辅助函数,用于在某个区间范围内随机选择一个整数。同时,我们也需要另一个辅助函数,用于在数值太大时对其进行调整。

数据集

  • 数据集如下所示,共计 100 行 3 列。
  • 图像化显示一下数据集
def plotFigure():
    x, y = loadDataSet('testSet.txt')
    xarr = np.array(x)
    n = np.shape(x)[0]
    x1 = []; y1 = []
    x2 = []; y2 = []
    for i in np.arange(n):
        if int(y[i]) == 1:
            x1.append(xarr[i,0]); y1.append(xarr[i,1])
        else:
            x2.append(xarr[i,0]); y2.append(xarr[i,1])
    
    plt.scatter(x1, y1, s = 30, c = 'r', marker = 's')
    plt.scatter(x2, y2, s = 30, c = 'g')
    plt.show()

SMO 算法中的辅助函数

# 加载数据
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 
# i 是第一个 alpha 的下标,m 是所有 alpha 的数目
# 只要函数值不等于输入值 i,函数就会进行随机选择
def selectJrand(i, m):
    j = i
    while(j == i):
        j = int(np.random.uniform(0, m))
    return j    
# 用于调整大于 H 或者小于 L 的 alpha 值
def clipAlpha(aj, H, L):
    if aj > H:
        aj = H
    if L > aj:
        aj = L
    return aj

简化版 SMO 算法

伪代码如下
创建一个alpha向量并将其初始化为O向量
当迭代次数小于最大迭代次数时(外循环):
   对数据集中的每个数据向量(内循环):
      如果该数据向量可以被优化:
         随机选择另外一个数据向量
         同时优化这两个向量
         如果两个向量都不能被优化,退出内循环
   如果所有向量都没被优化,增加迭代数目,继续下一次循环
# 5个输入参数分别为 dataMatIn=数据集,classLabels=类别标签,常数 C,toler=容错率 和 maxIter=退出前的最大循环次数
def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
    dataMatrix = np.mat(dataMatIn) # (100, 2)
    labelMat = np.mat(classLabels).transpose() # (100, 1)
    b = 0
    m, n = dataMatrix.shape # m = 100, n = 2
    alphas = np.mat(np.zeros((m, 1)))
    
    # iter 存储在没有任何alpha改变的情况下遍历数据集的次数,当该变量达到输入值maxIter时,函数结束运行并退出
    iter = 0
    while (iter < maxIter):
        # 每次循环中先将 alphaPairsChanged 设置为0,然后再对整个集合顺序遍历
        # 变量 alphaPairsChanged 用于记录 alpha 是否已经进行优化
        alphaPairsChanged = 0
        for i in range(m):
            # fXi 是我们预测的类别
            # alphas的shape = (100, 1), labelMat的shape=(100,1),multiply()再转置的shape = (1,100)
            # dataMatrix得到shape = (100,2),dataMatrix[i:]的shape=(1,2),相乘之后shape=(100,1)
            # fXi的shape= (1,100)(100,1) + b = 一个数字
            fXi = float(np.multiply(alphas, labelMat).T * (dataMatrix * dataMatrix[i,:].T)) + b
            # Ei为对输入 xi 的预测值和真实输出值 yi 之差
            Ei = fXi - float(labelMat[i])
            # 如果 alpha 可以更改进入优化过程
            if ((labelMat[i] * Ei < -toler) and (alphas[i] < C)) or \
               ((labelMat[i] * Ei > toler) and (alphas[i] > 0)):
                    # 随机选择第二个 alpha
                    j = selectJrand(i, m)
                    fXj = float(np.multiply(alphas, labelMat).T * (dataMatrix * dataMatrix[j,:].T)) + b
                    Ej = fXj - float(labelMat[j])
                    # python中是引用传递,不适用copy方法,将看不到新旧数值的变化
                    alphaIoId = alphas[i].copy()
                    alphaJoId = alphas[j].copy()
                    # 保证 alpha 在 0~C 之间
                    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 = 2.0 * dataMatrix[i,:] * dataMatrix[j,:].T - \
                        dataMatrix[i,:] * dataMatrix[i,:].T - \
                        dataMatrix[j,:] * dataMatrix[j,:].T
                    # eta 是用于做分母的,不能为0
                    if eta >= 0:
                        print('eta >= 0')
                        continue
                    # 更新 alphas[j] 数值
                    alphas[j] -= labelMat[j] * (Ei - Ej) / eta
                    alphas[j] = clipAlpha(alphas[j], H, L)
                    if (abs(alphas[j] - alphaJoId) < 0.00001):
                        # print('j is not moving enough')
                        continue
                    # 更新 alphas[i] 数值
                    alphas[i] += labelMat[j] * labelMat[i] * (alphaJoId - alphas[j])
                    b1 = b - Ei - labelMat[i] * (alphas[i] - alphaIoId) * \
                        dataMatrix[i,:] * dataMatrix[i,:].T - \
                        labelMat[j] * (alphas[j] - alphaJoId) * \
                        dataMatrix[i,:] * dataMatrix[j,:].T
                    b2 = b - Ej - labelMat[i] * (alphas[i] - alphaIoId) * \
                        dataMatrix[i,:] * dataMatrix[j,:].T - \
                        labelMat[j] * (alphas[j] - alphaJoId) * \
                        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))
        if (alphaPairsChanged == 0):
            iter += 1
        else:
            iter = 0
        # print('iteration number: %d' % iter)
    return b, alphas
# w 的计算
def calcWs(alphas, dataArr, labelArr):
    X = np.mat(dataArr)     # (100, 2)
    labelMat = np.mat(labelArr).transpose() #(100, 1)
    m, n = np.shape(X)      # m = 100, n = 2
    w = np.zeros((n, 1))    # (100, 1)
    for i in range(m):
        w += np.multiply(alphas[i] * labelMat[i], X[i,:].T)
    return w
# 画出完整分类图
def plotFigure(weights, b):
    x, y = loadDataSet('testSet.txt')
    xarr = np.array(x)
    n = np.shape(x)[0]
    x1 = []; y1 = []
    x2 = []; y2 = []
    for i in np.arange(n):
        if int(y[i]) == 1:
            x1.append(xarr[i,0]); y1.append(xarr[i,1])
        else:
            x2.append(xarr[i,0]); y2.append(xarr[i,1])
    
    plt.scatter(x1, y1, s = 30, c = 'r', marker = 's')
    plt.scatter(x2, y2, s = 30, c = 'g')
    
     # 画出 SVM 分类直线
    xx = np.arange(0, 10, 0.1) 
    # 由分类直线 weights[0] * xx + weights[1] * yy1 + b = 0 易得下式
    yy1 = (-weights[0] * xx - b) / weights[1]
    # 由分类直线 weights[0] * xx + weights[1] * yy2 + b + 1 = 0 易得下式
    yy2 = (-weights[0] * xx - b - 1) / weights[1]
    # 由分类直线 weights[0] * xx + weights[1] * yy3 + b - 1 = 0 易得下式
    yy3 = (-weights[0] * xx - b + 1) / weights[1]
    plt.plot(xx, yy1.T)
    plt.plot(xx, yy2.T)
    plt.plot(xx, yy3.T)
    
    # 画出支持向量点
    for i in range(n):
        if alphas[i] > 0.0:
            plt.scatter(xarr[i,0], xarr[i,1], s = 150, c = 'none', alpha = 0.7, linewidth = 1.5, edgecolor = 'red')

    plt.xlim((-2, 12))
    plt.ylim((-8, 6))
    plt.show()
# 主函数
if __name__ == '__main__':
    dataArr, labelArr = loadDataSet('/home/gcb/data/testSet.txt')
    b, alphas = smoSimple(dataArr, labelArr, 0.6, 0.001, 40) 
    w = calcWs(alphas, dataArr, labelArr)
    plotFigure(w, b)
    print(b)
    print(alphas[alphas > 0]) # 支持向量对应的 alpha > 0
    print(w)
  • 最终实现的分类结果

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容