支持向量机(SVM)---------------------------------------------------------
支持向量机(support vector machines, SVM)是一种监督二分类模型,具有完善的数学理论,其目标函数具有良好的凸性,可直接运用凸优化方法一次性找到最佳分类超平面。
设
是数据向量
和
的内积,可以是线性内积(线性核函数),也可以是高斯内积(高斯核函数)。若为高斯内积,则为非线性SVM。
由内积表达式,有
则SVM的优化目标可写为:
其中,
和
分别是第
个和第
个训练集数据向量的标签,易得
和
是需要优化的变量。
找到分类超平面以后,数据向量
的预测公式可写为
其中,
是数据向量
所属类别。
序列最小优化(SMO)------------------------------------------------------
原始论文:J. C. Platt, Sequential Minimal Optimization:A Fast Algorithm for Training Support Vector Machines (1998) Microsoft Research
传统的SVM凸优化计算效率很低。1998年,来自微软研究院的John C. Platt提出了目前使用最为广泛的快速SVM算法——SMO(Sequential Minimal Optimization,序列最小优化)算法,已用于LibSVM软件包中。该算法每次选择两个
,固定其他
进行优化,直到收敛。
SMO算法实际上与支持向量机本身没有任何关系,它只是一种解决函数凸优化问题的快速方法,所以,该方法的思想一样可以用于解决其他凸函数的凸优化问题。
SMO算法在每一次迭代中,分为内循环和外循环。在外循环中,算法逐一选择每个
。若算法在外循环中的某一步选择了
,则此时进入内循环,在所有
中选择一个作为
。
根据《机器学习实战》这本书的介绍:
1、如果
是随机不重复选择的,则叫做简易SMO算法,此时得到的分类超平面是全局最佳。
2、如果根据原始SMO算法里的启发式方法,若找到一个
,使得
与
的误差
相差最大,则目标函数就能以最快速度收敛,此时得到的分类超平面是在最佳分类超平面附近,且每次算法得到的分类边界都不一样。
不失一般性,假设在某次迭代中,选择了
,
,在SVM的目标函数J中,将与
和
有关的项单独写出,可构成SMO的目标函数
,为
其中
由于
与
和
无关,所以是一个常数,单独写出来。
因此,SVM的目标变为
现在,可找出
和
之间的关系表达式。由
,则
注意,
是
中的项,所以是常数。两边同乘
,有
即
对于
,令
注意,
和
与
和
无关,所以都是常数。则
将(1)式代入
,经化简,有
由于
中的量
都是常量,所以到此,SVM的多元函数优化问题,转化为了一元函数优化问题,即函数
只与
有关,即
显然,对于一元函数优化问题,首先需要求函数的一阶导函数,即
经化简,有
注意到,
中的
和
形式不够简洁,考察其是否可与前次迭代的结果联系起来,从而减少运算量。
首先,给出SVM对数据向量
的预测结果
表达式
观察
和
的形式,发现其与
较为相似,可对其进行改写,有
必须注意,上述两个式子中的
都是上一次迭代计算出的结果,为了与本次迭代计算出的所有新变量进行区分,令上一次迭代的
分别为
,则
将(1)式代入,消去
,同时为统一形式,令
表示
,化简,有
代入
,有
化简,有
令
,有
因
,则有
即
令
则
此时
的表达式可由上一次迭代计算好的
表示,即
到此为止,
计算出来了,但是还从没有考虑过
的取值范围问题。然而在SVM优化问题中,
的取值范围规定为
,且满足
。
接下来对 进行范围限制
接下来,必须对
的取值范围加以限制,以满足SVM优化问题的约束条件。
已知,
,则
会出现4种情况,即
在四种情况中,只有
是变化的,现在需要讨论在不同
下的情况。
A. 对于情况①,
,方程为
,绘制出其图像
图中,直线
从下到上扫过方形区域,
是直线与
轴的交点,即直线的截距。由图知,可以将
的取值范围分为四个部分看待:
1、当
时,为图中最下面的那条直线的情况,直线与方形区域没有交点。将
代入①式,有
,则
2、当
时,直线扫过方形区域下半部分。将
代入①式,有
则
3、当
时,直线扫过方形区域上半部分。将
代入①式,有
,则
4、当
时,为图中最上方的直线的情况,直线与方形区域没有交点。将
代入①式,有
,则
综上,有
将该不等式组写成一个不等式,有
注意到,对于情况①,虽然
,但是此时的
没有完全计算完,而对
的范围规定又需要
,这就造成了矛盾。为了避免这种情况,令
,则
B. 对于情况②,
,方程为
,绘制出其图像
图中,直线
从下到上扫过方形区域,
是直线与
轴的交点,即直线的截距。由图知,可以将
的取值范围分为四个部分看待:
1、当
时,为图中最下面的那条直线的情况,直线与方形区域没有交点。将
代入②式,有
,
,则
2、当
时,直线扫过方形区域下半部分。将
代入②式,有
,
则
3、当
时,直线扫过方形区域上半部分。将
代入②式,有
,
,则
4、当
时,为图中最上方的直线的情况,直线与方形区域没有交点。将
代入②式,有
,
则
综上,有
将该不等式组写成一个不等式,有
将②式中的
代入,有
C. 对于情况③,
,方程为
,可以发现方程中的
与情况②的
差一个负号,所以只需将
代替情况②中的所有
,即可得到
将③式中的
代入,有
D. 对于情况④,
,方程为
,可以发现方程中的
与情况①的
差一个负号,所以只需将
代替情况①中的所有
,即可得到
将④式中的
代入,有
综上讨论,可以得到
的取值范围,即
将
按照上述范围进行裁剪,任何大于或小于上下界的
都强制赋值为上界的值或下界的值。
现在完成了对 的完整计算。
由
和
之间的关系表达式
,有
现在,需要对
进行表达。
在本次迭代中,
始终与
无关,所以
是常数,没有发生变化,所以
,所以有
计算出了
和
后,就可以计算偏置
了。
现在开始计算偏置
若
,可知
是支持向量,有
即
即
即
则
又
即
令
,则
代回
的表达式,有
若
,可知
是支持向量,与上述同理,有
若
都在开区间(0, C)中,即如果
都是支持向量,则
实际上表达的是同一个超平面的偏置,即
若
都在开区间(0, C)以外,则
要么不是支持向量,要么就是软间隔引入的误分类点,此时
到此, 都计算完成。
等算法执行完外循环时,即可得到所有的 和最佳的 ,则可确定分类超平面。
●═══════════════════════════════我是分割线═════════════════════════════●
下面给出代码
代码基于Matlab R2018b,翻译自《机器学习实战》SVM一章的Python代码,如果存在任何不能运行的问题,请仔细检查程序中的函数用法是否匹配您所使用的Matlab版本。
简化版SMO:
clear all
close all
clc
%% 读数据
% 人工数据集
filename = '您想要测试的数据集txt文件,每一行是每一个数据点的横坐标,纵坐标和标签,标签只能是1或-1';
[dataMat, labelMat] = loadDataSet(filename);
[N, D] = size(dataMat);
% 绘图
if D == 2
figure(1)
indices0 = find(labelMat == -1);
indices1 = find(labelMat == 1);
dataMat0 = dataMat(indices0, :);
dataMat1 = dataMat(indices1, :);
scatter(dataMat0(:, 1), dataMat0(:, 2), 50, 'k.');
hold on;
scatter(dataMat1(:, 1), dataMat1(:, 2), 50, 'k+');
hold on;
end
%% 运行简易SMO算法,执行SVM分类
% 赋超参数
kTup = {'RBF', 10}; % 线性核为'linear',高斯核为'RBF',10为高斯核标准差
C = 10; % 控制软间隔中误分类程度的参数,C越大,误分类越少,或软间隔越“硬”,即支持向量的位置与分类超平面越近
toler = 0.001; % 检查
maxIter = 200;
[b, alphas] = smoSimple(dataMat, labelMat, C, toler, maxIter, kTup);
% 得到支持向量
supportVectorsIndices = find(alphas > 0);
supportVectors = dataMat(supportVectorsIndices, :);
% 计算运用核函数后任意两条数据的内积kernelEval
errorCount = 0;
for i = 1: N
kernelEval(:, i) = kernelTrans(supportVectors, dataMat(i, :), kTup);
predict = kernelEval(:, i)' * (labelMat(supportVectorsIndices) .* alphas(supportVectorsIndices)) + b;
if sign(predict) ~= sign(labelMat(i))
errorCount = errorCount + 1;
end
end
fprintf('训练误差为: %.2f%%\n', errorCount / N * 100);
% 绘图
accBound = 0.1; % 决定分类线的精度。值越小,分类线越精确,同时绘制速度越慢
plotSVM(kTup, D, dataMat, labelMat, b, alphas, supportVectors, supportVectorsIndices, accBound);
%% 生成线性可分数据集
function [feature, category]=generate_sample(step,error,b1)
aa=3*rand(1); %斜率
bb=3; %截距
rr =error;
s=step;
x1(:,1) = -2.5:s:2.5;
n = length(x1(:,1));
x1(:,2) = aa.*x1(:,1) + bb + b1 + rr*abs(randn(n,1));
y1 = ones(n,1);
x2(:,1) = -2.5:s:2.5;
x2(:,2) = aa.*x2(:,1) + bb - b1 - rr*abs(randn(n,1));
y2 = -ones(n,1);
feature=[x1;x2];
category=[y1;y2];
end
%% 若数据维数为2,则可绘图
function plotSVM(kTup, D, dataMat, labelMat, b, alphas, supportVectors, supportVectorsIndices, accBound)
if D == 2
% 分类线位于所有预测值为0的数据点位置上。
% 1、对屏幕空间的矩形范围(至少包含所有训练数据点)内的所有坐标点进行预测
% 点与点的间隔越小,分类边界线越精确
% 2、画出预测值为0的点
% 3、将所有标出的点连起来,构成高维分类超平面在二维上的投影边界
% 第2和第3步可以用contour函数直接实现
% 构建屏幕空间坐标集合screenCorMat,第一页为X坐标,第二页为Y坐标
screenCorX = min(dataMat(:, 1)): accBound: max(dataMat(:, 1));
screenCorY = min(dataMat(:, 2)): accBound: max(dataMat(:, 2));
M = length(screenCorY);
N = length(screenCorX);
screenCorX = repmat(screenCorX, [M , 1]);
screenCorY = repmat(screenCorY',[1, N]);
screenCorMat = cat(3, screenCorX, screenCorY);
% 平铺屏幕空间所有点,便于预测
screenCorMat = reshape(screenCorMat, [M * N, 2]);
% 对屏幕空间所有点进行预测,得到预测结果矩阵predictCor
for i = 1: M * N
kernelCor(:, i) = kernelTrans(supportVectors, screenCorMat(i, :), kTup);
predictCor(i) = kernelCor(:, i)' * (labelMat(supportVectorsIndices) .* alphas(supportVectorsIndices)) + b;
end
predictCor = reshape(predictCor, [M, N]);
% 绘制预测结果为0的所有点,连起来,构成分类边界线
figure(1), contour(screenCorX, screenCorY, predictCor, [0, 0], 'k--'); hold on;
hold on;
% 圈出所有支持向量
figure(1), scatter(supportVectors(:, 1), supportVectors(:, 2), 100, 'k'); hold on;
% 绘制出边界点所在曲线
figure(1), contour(screenCorX, screenCorY, predictCor, [1 1], 'r--'); hold on;
figure(1), contour(screenCorX, screenCorY, predictCor, [-1 -1], 'r--');
else
fprintf('数据维数高于2维,无法绘图\n');
end
end
%% 读取数据函数
function [dataMat, labelMat] = loadDataSet(filename)
fid = fopen(filename);
format = [repmat('%f', [1, 3])];
data = cell2mat(textscan(fid, format));
dataMat = data(:, 1: end - 1);
labelMat = data(:, end);
end
%% 在1到m之间随机选取一个整数j
function j = selectJrand(i, m)
j = i;
while (j == i)
j = randperm(m, 1);
end
end
%% 调整大于H或小于L的alpha,即掐头去尾
function aj = clipAlpha(aj, H, L)
% 令大于H的alpha等于H
if aj > H
aj = H;
end
% 令小于L的alpha等于L
if aj < L
aj = L;
end
end
%% 简化版SMO(序列最小优化)算法
function [b, alphas] = smoSimple(dataMat, labelMat, C, toler, maxIter, kTup)
b = 0;
[N, D] = size(dataMat);
alphas = zeros(N, 1);
iter = 0;
for i = 1: N
kernalData(:, i) = kernelTrans(dataMat, dataMat(i, :), kTup);
end
while (iter < maxIter)
alphaPairsChanged = 0; % 记录alpha已经优化的数量
for i = 1: N
fXi = (alphas .* labelMat)' * kernalData(:, i) + b; % fXi为预测结果
Ei = fXi - labelMat(i); % Ei为使用alpha_i计算时的预测误差
% 接下来的if语句判断某条数据是否符合KKT条件
if ((labelMat(i) * Ei < -toler) && (alphas(i) < C)) || ((labelMat(i) * Ei > toler) && (alphas(i) > 0))
% 若alpha可更改,则执行如下语句
% 随机选一个alpha的下标j
j = selectJrand(i, N);
% 使用选出来的alpha_j计算预测结果fXj
fXj = (alphas .* labelMat)' * kernalData(:, j) + b;
Ej = fXj - labelMat(j); % Ej为使用alpha_j计算时的预测误差
alphaIold = alphas(i);
alphaJold = alphas(j);
% 计算alpha_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));
end
if L == H
fprintf('上下界相等,跳过此步循环,不再计算alpha_j\n');
continue;
end
eta = 2.0 * kernalData(i, j) - kernalData(i, i) - kernalData(j, j);
if eta >= 0
fprintf("eta >= 0\n");
continue;
end
alphas(j) = alphas(j) - labelMat(j) * (Ei - Ej) / eta;
alphas(j) = clipAlpha(alphas(j), H, L);
if (abs(alphas(j) - alphaJold) < 0.00001)
fprintf("新alpha_j与旧alpha_j没有变化,重新选alpha_i\n");
continue;
end
% 至此,alpha_j计算完成
% 根据alpha_i和alpha_j的关系式,计算alpha_i
alphas(i) = alphaIold + labelMat(j) * labelMat(i) * (alphaJold - alphas(j));
% 计算分类超平面的偏置
b1 = b - Ei - labelMat(i) * (alphas(i) - alphaIold) * kernalData(i, i) - ...
labelMat(j) * (alphas(j) - alphaJold) * kernalData(i, j);
b2 = b - Ej - labelMat(i) * (alphas(i) - alphaIold) * kernalData(i, j) - ...
labelMat(j) * (alphas(j) - alphaJold) * kernalData(j, j);
if (0 < alphas(i)) && (C > alphas(i))
b = b1;
elseif (0 < alphas(j)) && (C > alphas(j))
b = b2;
else
b = (b1 + b2) / 2;
end
alphaPairsChanged = alphaPairsChanged + 1;
fprintf('迭代 %d 中,第一个alpha的序号i: %d, 改变的alpha值对: %d\n', iter, i, alphaPairsChanged);
end
end
if alphaPairsChanged == 0
iter = iter + 1;
else
iter = 0;
end
fprintf('迭代: %d\n', iter);
end
end
%% 核函数
function K = kernelTrans(X, A, kTup)
m = size(X, 1);
if ismember(kTup{1}, 'linear')
K = X * A';
elseif ismember(kTup{1}, 'RBF')
deltaRow = X - repmat(A, [m, 1]);
K = exp(-sum(deltaRow .^ 2, 2) ./ (kTup{2} ^ 2));
else
error('无法识别的核函数.');
end
end
测试分类效果
1、线性核分类
2、非线性核分类
完整版SMO:
clear all
close all
clc
%% 读数据
% 人工数据集
filename = '您想要测试的数据集txt文件,每一行是每一个数据点的横坐标,纵坐标和标签,标签只能是1或-1';
[dataMat, labelMat] = loadDataSet(filename);
[N, D] = size(dataMat);
% 绘图
if D == 2
figure(1)
indices0 = find(labelMat == -1);
indices1 = find(labelMat == 1);
dataMat0 = dataMat(indices0, :);
dataMat1 = dataMat(indices1, :);
scatter(dataMat0(:, 1), dataMat0(:, 2), 50, 'k.');
hold on;
scatter(dataMat1(:, 1), dataMat1(:, 2), 50, 'k+');
hold on;
end
%% 使用完整版SMO --------------
% 构造一个数据结构保存重要值
tic;
kTup = {'linear', 1};
% kTup = {'linear'};
optStruct.X = dataMat;
optStruct.labelMat = labelMat;
optStruct.C = 10;
optStruct.tol = 0.0001;
optStruct.m = size(dataMat, 1);
optStruct.D = size(dataMat, 2);
optStruct.alphas = zeros(optStruct.m, 1);
optStruct.b = 0;
optStruct.eCache = zeros(optStruct.m, 2);
optStruct.K = zeros(optStruct.m); % 存储任意两条数据的内积
for i = 1: optStruct.m
optStruct.K(:, i) = kernelTrans(optStruct.X, optStruct.X(i, :), kTup);
end
% 更新数据结构
maxIter = 40;
optStruct = smoP(optStruct, maxIter);
% 得到支持向量
supportVectorsIndices = find(optStruct.alphas > 0);
supportVectors = dataMat(supportVectorsIndices, :);
% 计算运用核函数后任意两条数据的内积kernelEval
errorCount = 0;
for i = 1: optStruct.m
kernelEval(:, i) = kernelTrans(supportVectors, optStruct.X(i, :), kTup);
predict = kernelEval(:, i)' * (optStruct.labelMat(supportVectorsIndices) .* optStruct.alphas(supportVectorsIndices)) + optStruct.b;
if sign(predict) ~= sign(optStruct.labelMat(i))
errorCount = errorCount + 1;
end
end
fprintf('训练集误分率: %.2f%%\n', errorCount / optStruct.m * 100);
toc
% 绘图
accBound = 0.1; % 决定分类线的精度。值越小,分类线越精确,同时绘制速度越慢
plotSVM(kTup, optStruct, supportVectors, supportVectorsIndices, accBound);
%% 若数据维数为2,则可绘图
function plotSVM(kTup, oS, supportVectors, supportVectorsIndices, accBound)
if oS.D == 2
% 分类线位于所有预测值为0的数据点位置上。
% 1、对屏幕空间的矩形范围(至少包含所有训练数据点)内的所有坐标点进行预测
% 点与点的间隔越小,分类边界线越精确
% 2、画出预测值为0的点
% 3、将所有标出的点连起来,构成高维分类超平面在二维上的投影边界
% 第2和第3步可以用contour函数直接实现
% 构建屏幕空间坐标集合screenCorMat,第一页为X坐标,第二页为Y坐标
screenCorX = min(oS.X(:, 1)): accBound: max(oS.X(:, 1));
screenCorY = min(oS.X(:, 2)): accBound: max(oS.X(:, 2));
M = length(screenCorY);
N = length(screenCorX);
screenCorX = repmat(screenCorX, [M , 1]);
screenCorY = repmat(screenCorY',[1, N]);
screenCorMat = cat(3, screenCorX, screenCorY);
% 平铺屏幕空间所有点,便于预测
screenCorMat = reshape(screenCorMat, [M * N, 2]);
% 对屏幕空间所有点进行预测,得到预测结果矩阵predictCor
for i = 1: M * N
kernelCor(:, i) = kernelTrans(supportVectors, screenCorMat(i, :), kTup);
predictCor(i) = kernelCor(:, i)' * (oS.labelMat(supportVectorsIndices) .* oS.alphas(supportVectorsIndices)) + oS.b;
end
predictCor = reshape(predictCor, [M, N]);
% 绘制预测结果为0的所有点,连起来,构成分类边界线
figure(1), contour(screenCorX, screenCorY, predictCor, [0, 0], 'k--'); hold on;
hold on;
% 圈出所有支持向量
figure(1), scatter(supportVectors(:, 1), supportVectors(:, 2), 100, 'k'); hold on;
% 绘制出边界点所在曲线
figure(1), contour(screenCorX, screenCorY, predictCor, [1 1], 'r--'); hold on;
figure(1), contour(screenCorX, screenCorY, predictCor, [-1 -1], 'r--');
else
fprintf('数据维数高于2维,无法绘图\n');
end
end
%% 读取数据函数
function [dataMat, labelMat] = loadDataSet(filename)
fid = fopen(filename);
format = [repmat('%f', [1, 3])];
data = cell2mat(textscan(fid, format));
dataMat = data(:, 1: end - 1);
labelMat = data(:, end);
end
%% 在1到m之间随机选取一个整数j
function j = selectJrand(i, m)
j = i;
while (j == i)
j = randperm(m, 1);
end
end
%% 调整大于H或小于L的alpha,即掐头去尾
function aj = clipAlpha(aj, H, L)
% 令大于H的alpha等于H
if aj > H
aj = H;
end
% 令小于L的alpha等于L
if aj < L
aj = L;
end
end
%% ---------------------完整版SMO算法 ------------------------
%% 对于给定alpha,计算误差E
function Ek = calcEk(oS, k)
fXk = (oS.alphas .* oS.labelMat)' * oS.K(:, k) + oS.b; % 对x_k的预测值
Ek = fXk - oS.labelMat(k); % x_k的预测值与x_k的标签的误差
end
%% 选择j,保证每次优化采用最大步长
function [returnValue, Ej] = selectJ(i, oS, Ei)
maxK = -1;
maxDeltaE = 0;
Ej = 0;
oS.eCache(i, :) = [1, Ei];
% E_i=f(x_i)-y_i,即真实预测值与标签的误差,E_j同理;
% 找出在之前迭代中计算出的所有误差,即Ei和Ej
validEcacheList = find(oS.eCache(:, 1) ~= 0);
if length(validEcacheList) > 1
% 如果之前迭代中计算好的误差多于1个
% 则以下循环实际上是在所有误差中,找出与alpha_i的误差Ei相差最大的alpha_j的误差Ej
for a = 1: length(validEcacheList)
k = validEcacheList(a);
if k == i
continue;
end
Ek = calcEk(oS, k);
deltaE = abs(Ei - Ek);
if deltaE > maxDeltaE
% 如果找到的新alpha_j计算出的误差比上一次找到的alpha_j的误差更大
% 则令新误差对应的alpha_j为找到的alpha_j
maxK = k;
% 则令新误差为此时更大的误差
maxDeltaE = deltaE;
Ej = Ek;
end
end
returnValue = maxK;
return;
else
% 如果目前不存在1个以上的有效误差,则在所有alpha中随机选一个作为alpha_j
% 一般会发生于某次迭代中第一次寻找alpha_j时
j = selectJrand(i, oS.m);
Ej = calcEk(oS, j);
returnValue = j;
end
end
%% 计算误差值,放入缓存
function oS = updateEk(oS, k)
Ek = calcEk(oS, k);
oS.eCache(k, :) = [1, Ek];
end
%% 寻找决策边界
function [flag, oS] = innerL(i, oS)
Ei = calcEk(oS, i);
if ((oS.labelMat(i) * Ei < -oS.tol) && (oS.alphas(i) < oS.C)) || ((oS.labelMat(i) * Ei > oS.tol) && (oS.alphas(i) > 0))
[j, Ej] = selectJ(i, oS, Ei);
alphaIold = oS.alphas(i);
alphaJold = oS.alphas(j);
% 求alpha的上下界
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));
end
if L == H
fprintf('alpha的上界和下界相同,重新选第一个alpha\n');
flag = 0;
return;
end
eta = 2.0 * oS.K(i, j) - oS.K(i, i) - oS.K(j, j);
if eta >= 0
fprintf("eta >= 0\n");
flag = 0;
return;
end
oS.alphas(j) = oS.alphas(j) - oS.labelMat(j) * (Ei - Ej) / eta;
oS.alphas(j) = clipAlpha(oS.alphas(j), H, L);
% 更新误差缓存
oS = updateEk(oS, j);
if (abs(oS.alphas(j) - alphaJold) < 0.00001)
fprintf("新alpha_j与旧alpha_j没有变化,重新选第一个alpha\n");
flag = 0;
return;
end
oS.alphas(i) = alphaIold + oS.labelMat(j) * oS.labelMat(i) * (alphaJold - oS.alphas(j));
% 更新误差缓存
oS = updateEk(oS, i);
% 求分类超平面的偏置
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)) && (oS.C > oS.alphas(i))
oS.b = b1;
elseif (0 < oS.alphas(j)) && (oS.C > oS.alphas(j))
oS.b = b2;
else
oS.b = (b1 + b2) / 2;
end
flag = 1;
else
flag = 0;
end
end
%% SMO
function oS = smoP(oS, maxIter)
iter = 0;
entireSet = true(1);
alphaPairsChanged = 0;
while (iter < maxIter) && ((alphaPairsChanged > 0) || entireSet)
alphaPairsChanged = 0;
if entireSet
% 遍历所有值
for i = 1: oS.m
[value, oS] = innerL(i, oS);
alphaPairsChanged = alphaPairsChanged + value;
end
fprintf('迭代 %d 中,第一个alpha的序号i: %d, 改变的alpha值对: %d\n', iter, i, alphaPairsChanged);
iter = iter + 1;
else
% 找出所有在0到C之间的alpha
nonBoundIs = find((oS.alphas > 0) .* (oS.alphas < oS.C) ~= 0);
for a = 1: length(nonBoundIs)
i = nonBoundIs(a);
[value, oS] = innerL(i, oS);
alphaPairsChanged = alphaPairsChanged + value;
fprintf('边界alpha, 迭代: %d,第一个alpha的序号i: %d, 改变的alpha值对: %d\n', iter, i, alphaPairsChanged);
end
iter = iter + 1;
end
if entireSet
% 遍历所有alpha,且有alpha值对发生改变后,置entireSet为假,以便于在下一次迭代中只更新不为0的alpha
entireSet = false(1);
elseif alphaPairsChanged == 0
% 若没有更改的alpha值对,则置entireSet为真,即重新在所有alpha中找新的alpha值对进行优化
entireSet = true(1);
end
fprintf('迭代: %d\n', iter);
end
end
%% 核函数
function K = kernelTrans(X, A, kTup)
m = size(X, 1);
if ismember(kTup{1}, 'linear')
K = X * A';
elseif ismember(kTup{1}, 'RBF')
deltaRow = X - repmat(A, [m, 1]);
K = exp(-sum(deltaRow .^ 2, 2) ./ (kTup{2} ^ 2));
else
error('核函数不识别.');
end
end
测试分类效果
1、线性核分类
2、非线性核分类
●═══════════════════════════════我是分割线═════════════════════════════●