SVM(6)——序列最小最优化算法(SMO)代码

凸二次规划问题具有全局最优解,但当训练样本很大时,往往会变得非常低效。SMO的基本思路是:如果所有变量都满足KKT条件时,就可以得到这个最优化问题的解。

一、代码
根据李航统计学习方法第一版的公式进行编写,与sklearn的svm进行对比

import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC

iris = datasets.load_iris()
X = iris["data"][:100, (0, 1)]  # 提取前100行的第一二列
y = iris["target"][:100]
y = np.array([1 if i == 1 else -1 for i in y])
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)  # 百分之20作为测试集


class SVM:
    def __init__(self, max_iter=100, kernel='linear'):
        self.max_iter = max_iter
        self._kernel = kernel

    def init_args(self, features, labels):
        self.m, self.n = features.shape
        self.X = features
        self.Y = labels
        self.b = 0.0

        # 将Ei保存在一个列表里
        self.alpha = np.ones(self.m)
        self.E = [self._E(i) for i in range(self.m)]
        # 松弛变量
        self.C = 1.0

    # g(x)预测值,输入xi; 公式(7.104)
    def _g(self, i):
        r = self.b
        for j in range(self.m):
            r += self.alpha[j] * self.Y[j] * self.kernel(self.X[i], self.X[j])
        return r

    # 7.5(3)停机条件
    def _KKT(self, i):
        y_g = self._g(i) * self.Y[i]
        if self.alpha[i] == 0:
            return y_g >= 1
        elif 0 < self.alpha[i] < self.C:
            return y_g == 1
        else:
            return y_g <= 1

    # 核函数
    def kernel(self, x1, x2):
        if self._kernel == 'linear':
            return sum([x1[k] * x2[k] for k in range(self.n)])
        elif self._kernel == 'poly':
            return (sum([x1[k] * x2[k] for k in range(self.n)]) + 1) ** 2

        return 0

    # E(x)为g(x)对输入xi的预测值和真实值y的差;公式(7.105)
    def _E(self, i):
        return self._g(i) - self.Y[i]

    # 7.4.2两个变量的选择方法
    def _init_alpha(self):
        # 第一个变量
        # 外层循环首先遍历所有满足0
        index_list = [i for i in range(self.m) if 0 < self.alpha[i] < self.C]
        # 如果都满足,遍历整个训练集,检验是否满足KKT
        non_satisfy_list = [i for i in range(self.m) if i not in index_list]
        index_list.extend(non_satisfy_list)

        for i in index_list:
            if self._KKT(i):
                continue
            # 第二个变量,内循环找出alpha2,希望alpha2有足够大的变化,|E1-E2|最大
            E1 = self.E[i]
            # 如果E1是+,选择最小的作为E2;如果E1是负的,选择最大的作为E2
            if E1 >= 0:
                j = min(range(self.m), key=lambda x: self.E[x])
            else:
                j = max(range(self.m), key=lambda x: self.E[x])
            return i, j

    # 公式(7.108)
    def _compare(self, _alpha, L, H):
        if _alpha > H:
            return H
        elif _alpha < L:
            return L
        else:
            return _alpha

    def fit(self, features, labels):
        self.init_args(features, labels)

        for t in range(self.max_iter):
            # train
            i1, i2 = self._init_alpha()

            # 边界P126
            if self.Y[i1] == self.Y[i2]:
                L = max(0, self.alpha[i1] + self.alpha[i2] - self.C)
                H = min(self.C, self.alpha[i1] + self.alpha[i2])
            else:
                L = max(0, self.alpha[i2] - self.alpha[i1])
                H = min(self.C, self.C + self.alpha[i2] - self.alpha[i1])

            E1 = self.E[i1]
            E2 = self.E[i2]
            # eta=K11+K22-2K12;公式7.107
            eta = self.kernel(self.X[i1], self.X[i1]) + self.kernel(self.X[i2], self.X[i2]) - 2 * self.kernel(
                self.X[i1], self.X[i2])
            if eta <= 0:
                continue

            alpha2_new_unc = self.alpha[i2] + self.Y[i2] * (E1 - E2) / eta  # P128
            alpha2_new = self._compare(alpha2_new_unc, L, H)  # 公式7.108

            alpha1_new = self.alpha[i1] + self.Y[i1] * self.Y[i2] * (self.alpha[i2] - alpha2_new)  # 公式(7.109)
            # 公式(7.115) 及 公式(7.116)
            b1_new = -E1 - self.Y[i1] * self.kernel(self.X[i1], self.X[i1]) * (alpha1_new - self.alpha[i1]) - self.Y[
                i2] * self.kernel(
                self.X[i2],
                self.X[i1]) * (alpha2_new - self.alpha[i2]) + self.b  # 公式7.115
            b2_new = -E2 - self.Y[i1] * self.kernel(self.X[i1], self.X[i2]) * (alpha1_new - self.alpha[i1]) - self.Y[
                i2] * self.kernel(
                self.X[i2],
                self.X[i2]) * (alpha2_new - self.alpha[i2]) + self.b  # 公式7.116

            # 如果alpha1_new,alpha2_new同时满足(0,C)的开区间,那么b1_new=b2_new
            # 否则,选取中的作为b_new
            if 0 < alpha1_new < self.C:
                b_new = b1_new
            elif 0 < alpha2_new < self.C:
                b_new = b2_new
            else:
                # 选择中点
                b_new = (b1_new + b2_new) / 2

            # 更新参数
            self.alpha[i1] = alpha1_new
            self.alpha[i2] = alpha2_new
            self.b = b_new

            self.E[i1] = self._E(i1)
            self.E[i2] = self._E(i2)
        return ('train done!')

    def predict(self, data):
        r = self.b
        for i in range(self.m):
            r += self.alpha[i] * self.Y[i] * self.kernel(data, self.X[i])
        return 1 if r > 0 else -1

    def score(self, X_test, y_test):
        right_count = 0
        for i in range(len(X_test)):
            result = self.predict(X_test[i])
            if result == y_test[i]:
                right_count += 1
        return right_count / len(X_test)


svm = SVM(max_iter=200)
svm.fit(X_train, y_train)
score1 = svm.score(X_test, y_test)
print(score1)

# 与sklearn中的svm对比
clf = SVC()
clf.fit(X_train, y_train)
scroe2 = clf.score(X_test, y_test)
print(scroe2)

二、结果
SVM(6)——序列最小最优化算法(SMO)代码_第1张图片

你可能感兴趣的:(机器学习推导,支持向量机,机器学习)