对于多分类来说,一对多的SVM分类器思想是给定一类标上正标签,其余剩下的类标上负标签,对应的将训练40次。我们用细胞元组来将训练数据和测试数据存储起来。
function data = divdata(index,X,n)
len = size(X,1);
if index == 1
data = [[X(1:n,:),ones(n,1)];[X(n+1:len,:),-ones(len-n,1)]];
elseif index == 80
data = [[X(1:len-n,:),-ones(len-n,1)];[X(len-n+1:len,:),ones(n,1)]];
else
data = [[X(1:n*(index-1),:),-ones(n*(index-1),1)];[X(n*index-n+1:n*index,:),ones(n,1)];[X(n*index+1:len,:),-ones(len-n*index,1)]];
end
end
function svm = svmTrain(X,Y,C)
% Options是用来控制算法的选项参数的向量,optimset无参时,
% 创建一个选项结构所有字段为默认值的选项
options = optimset;
options.largeScale = 'off'; %LargeScale指大规模搜索,off表示在规模搜索模式关闭
options.Display = 'off'; %表示无输出
%二次规划用来求解问题,使用quadprog
n = length(Y);
%使用线性核
H = (Y.*X)*(Y.*X)';
H=(H+H')/2;
f = -ones(n,1); %f'为1*n个-1
A = [];
b = [];
Aeq = Y';
beq = 0;
lb = zeros(n,1);
ub = C*ones(n,1);
a0 = zeros(n,1); % a0是解的初始近似值
a = quadprog(H,f,A,b,Aeq,beq,lb,ub,a0,options);
epsilon = 1e-9;
sv_label = find(abs(a)> epsilon);
svm.sva = a(sv_label);
svm.Xsv = X(sv_label,:);
svm.Ysv = Y(sv_label);
svm.svnum = length(sv_label);
svm.a = a;
end
%从人脸照片中随选择7张作为训练集
train_data = zeros(40*7,112*92);
test_data = zeros(40*3,112*92);
for i = 1:40
file_name = ['s',num2str(i)];
%随机选取同一文件夹下的7个图片
index = randperm(10);
for j = 1:7
%文件名拼接
name = ['att_faces\',file_name,'\',num2str(index(j)),'.pgm'];
image = double(imread(name));
%将矩阵展开为一行
train_data(7*(i-1)+j,:) = reshape(image,1,112*92);
end
for j = 1:3
%文件名拼接
name = ['att_faces\',file_name,'\',num2str(index(7+j)),'.pgm'];
image = double(imread(name));
test_data(3*(i-1)+j,:) = reshape(image,1,112*92);
end
end
%================PCA算法开始================
%(一)Step one: 中心化数据
numData = [train_data;test_data];
X = numData - repmat(mean(numData),40*10,1);
%(二)Step two: 求中心化数据的协方差矩阵
C = X'*X/size(X,1);
%(三)Step three: 计算特征向量和特征值
[V,D] = eig(C);
%(四)Step four: 将特征值和特征向量按降序排序
[dummy,order] = sort(diag(-D));
d=diag(D); %将特征值取出,构成一个列向量
E=V(:,order); %将特征向量按照特征值大小进行降序排列
newd=d(order); %将特征值构成的列向量按降序排列
%(五)Step five: 取前k行组成矩阵P,构成变换矩阵
%选取5个特征贡献率值来当作测试对象
Crate = [0.75 0.80 0.85 0.90 0.95];
sumd = sum(newd);
for ii = 1:length(Crate)
k = 0;
for j = 1:length(newd)
i = sum(newd(1:j,1),1)/sumd; %计算贡献率=前k个特征值之和/总特征值之和
if i > Crate(ii) %当贡献率大于95%时循环结束,并记下取多少个特征值
k = j;
break;
end
end
P = E(:,1:k);
newX = X*P;
%================SVM 实现多分类================
%================一对多SVM分类器===============
%(一)Step one: 重新分配数据
train_X = newX(1:280,:);
test_X = newX(281:400,:);
num = 40;
data1 = cell(num,1);
data2 = cell(num,1);
for i = 1:num
data1{i,1} = divdata(i,train_X,7);
data2{i,1} = divdata(i,test_X,3);
end
%(二)Step two: 训练数据
a = zeros(num,size(train_X,1));
w = zeros(num,size(train_X,2));
b = zeros(num,1);
for i = 1:num
svm = svmTrain(data1{i,1}(:,1:k),data1{i,1}(:,k+1),1);
a(i,:) = svm.a';
for j = 1:size(train_X,2)
w(i,j) = sum(a(i,:)'.*data1{i,1}(:,k+1).*data1{i,1}(:,j));
end
b(i,1) = sum(svm.Ysv-svm.Xsv*w(i,:)')/svm.svnum;
end
%(三)Step three: 测试数据
labels_num = zeros(size(test_X,1),num);
for i = 1:num
labels_num(:,i) = (test_X*w(i,:)')'+b(i,1);
end
%(四)Step four: 得出分类结果
labels = zeros(size(test_X,1),1);
for i = 1:size(test_X,1)
[~,index] = max(labels_num(i,:));
labels(i,1) = index;
end
%(五)Step five: 分析分类结果
compare = zeros(size(test_X,1),1);
for i = 1:num
compare((i-1)*3+1:(i-1)*3+3,1) = i;
end
success_num = length(find(compare==labels));
success_rate = success_num/size(test_X,1);
%打印分类正确率情况
fprintf('==========one-to-all==========\n');
fprintf(' k = %d\n',k);
fprintf(' Crate:%f\n test_data_num: %d\n',Crate(ii),size(test_X,1));
fprintf(' success_num: %f\n success_rate: %f\n',success_num,success_rate);
end
test1.m中的特征贡献率是决定最终维数的标准,因为在对矩阵求解特征值和特征向量时,有些特征值太小了,我们可以不考虑它们,因此可以用特征贡献率来决定我们选取的特征数,也即维数。
对于如何知道测试集某一数据是属于哪一类是这样判断的,因为40组数据在SVM训练器上得到的 ω \omega ω和 b b b是不一样的,因此对应的 ω T x + b \omega^Tx+b ωTx+b的值不同,若这个值是小于0的则不考虑,若这个值是大于0的,我们要找出这40组 ω \omega ω和 b b b中使得该值最大的 ω \omega ω和 b b b,其对应的训练集的正标签即为该 ω T x + b \omega^Tx+b ωTx+b最大嘛。
特征贡献率 | 维数 | 正确组数 | 正确率 |
---|---|---|---|
0.75 | 30 | 75 | 0.6250 |
0.80 | 44 | 66 | 0.5500 |
0.85 | 68 | 74 | 0.6167 |
0.90 | 111 | 80 | 0.6667 |
0.95 | 190 | 81 | 0.6750 |
采用一对一的SVM的思想是对于40个类,我们任意选出两个类作为二分类的SVM训练集,将选定的两个类分别标上正标签和负标签,使用实验五的svmTrain训练训练集,这时我们将训练40×(40-1)/2 = 780次。
%从人脸照片中随选择7张作为训练集
train_data = zeros(40*7,112*92);
test_data = zeros(40*3,112*92);
for i = 1:40
file_name = ['s',num2str(i)];
%随机选取同一文件夹下的7个图片
index = randperm(10);
for j = 1:7
%文件名拼接
name = ['att_faces\',file_name,'\',num2str(index(j)),'.pgm'];
image = double(imread(name));
%将矩阵展开为一行
train_data(7*(i-1)+j,:) = reshape(image,1,112*92);
end
for j = 1:3
%文件名拼接
name = ['att_faces\',file_name,'\',num2str(index(7+j)),'.pgm'];
image = double(imread(name));
test_data(3*(i-1)+j,:) = reshape(image,1,112*92);
end
end
%================PCA算法开始================
%(一)Step one: 中心化数据
numData = [train_data;test_data];
X = numData - repmat(mean(numData),40*10,1);
%(二)Step two: 求中心化数据的协方差矩阵
C = X'*X/size(X,1);
%(三)Step three: 计算特征向量和特征值
[V,D] = eig(C);
%(四)Step four: 将特征值和特征向量按降序排序
[dummy,order] = sort(diag(-D));
d=diag(D); %将特征值取出,构成一个列向量
E=V(:,order); %将特征向量按照特征值大小进行降序排列
newd=d(order); %将特征值构成的列向量按降序排列
%(五)Step five: 取前k行组成矩阵P,构成变换矩阵
%选取5个特征贡献率值来当作测试对象
Crate = [0.75 0.80 0.85 0.90 0.95];
sumd = sum(newd);
for ii = 1:length(Crate)
k = 0;
for j = 1:length(newd)
i = sum(newd(1:j,1),1)/sumd; %计算贡献率=前k个特征值之和/总特征值之和
if i > Crate(ii) %当贡献率大于95%时循环结束,并记下取多少个特征值
k = j;
break;
end
end
P = E(:,1:k);
newX = X*P;
%================SVM 实现多分类================
%这里采用一对一方法
%(一)Step one: 重新分配数据
train_X = newX(1:280,:);
test_X = newX(281:400,:);
num = 40*(40-1)/2;
data1 = cell(num,1);
data2 = cell(num,1);
index = 1;
two_labels = zeros(num,2);
for i = 1:39
for j = i+1:40
data1{index,1} = [[train_X(i*7-6:i*7,:),ones(7,1)];[train_X(j*7-6:j*7,:),-ones(7,1)]];
data2{index,1} = [[test_X(i*3-2:i*3,:),ones(3,1)];[test_X(j*3-2:j*3,:),-ones(3,1)]];
two_labels(index,1) = i;
two_labels(index,2) = j;
index = index + 1;
end
end
%(二)Step two: 训练数据
a = zeros(num,14);
w = zeros(num,size(train_X,2));
b = zeros(num,1);
for i = 1:num
svm = svmTrain(data1{i,1}(:,1:k),data1{i,1}(:,k+1),1);
a(i,:) = svm.a';
for j = 1:size(train_X,2)
w(i,j) = sum(a(i,:)'.*data1{i,1}(:,k+1).*data1{i,1}(:,j));
end
b(i,1) = sum(svm.Ysv-svm.Xsv*w(i,:)')/svm.svnum;
end
%(三)Step three: 测试数据
labels_num = zeros(size(test_X,1),num);
for i = 1:num
labels_num(:,i) = (test_X*w(i,:)')'+b(i,1);
end
%(四)Step four: 得出分类结果
%找出每个类别所在的分类器
labels_svm = zeros(40,39);
for i = 1:40
labels_svm(i,:) = find(two_labels(:,1)==i | two_labels(:,2)==i);
end
%统计每一类在各个分类器分类正确率
labels = zeros(size(test_X,1),1);
for i = 1:size(test_X,1)
pro = zeros(40,1);
for j = 1:40
for m = 1:39
if labels_num(i,labels_svm(j,m)) > 0 && two_labels(labels_svm(j,m),1) == j
pro(j,1) = pro(j,1) + 1;
elseif labels_num(i,labels_svm(j,m)) < 0 && two_labels(labels_svm(j,m),2) == j
pro(j,1) = pro(j,1) + 1;
end
end
end
[~,index] = max(pro);
labels(i,1) = index;
end
%(五)Step five: 分析分类结果
compare = zeros(size(test_X,1),1);
for i = 1:40
compare((i-1)*3+1:(i-1)*3+3,1) = i;
end
success_num = length(find(compare==labels));
success_rate = success_num/size(test_X,1);
%打印分类正确率情况
fprintf('==========one-to-all==========\n');
fprintf(' k = %d\n',k);
fprintf(' Crate:%f\n test_data_num: %d\n',Crate(ii),size(test_X,1));
fprintf(' success_num: %f\n success_rate: %f\n',success_num,success_rate);
end
对于一对一的SVM训练模型,如何划分每一个测试集属于哪一类思路是这样的,因为关于某一个类的训练次数会有39次(以第一个人脸为例,它会和剩下的39个不同的人脸进行组合训练),而对于每一个测试集,我们的780次训练将会得到相应的 ω \omega ω和 b b b,每个测试集的 ω T x + b \omega^Tx+b ωTx+b的值将会是不一样的。比较这40个类中,测试集在每个类对应的39次训练中 ω T x + b \omega^Tx+b ωTx+b为正数的次数最多的即为该类。例如某一测试集在第一类人脸中对应的39次训练对应的 ω T x + b \omega^Tx+b ωTx+b为正数的次数是30次,而在其余的人脸中对应的39次训练对应的 ω T x + b \omega^Tx+b ωTx+b为正数的次数少于30次,那么可以判断该测试集的最终结果就是第一类人脸。(讲的有点out口,需要自行体会和理解)
特征贡献率 | 维数 | 正确组数 | 正确率 |
---|---|---|---|
0.75 | 30 | 115 | 0.9583 |
0.80 | 44 | 115 | 0.9583 |
0.85 | 68 | 115 | 0.9583 |
0.90 | 111 | 115 | 0.9583 |
0.95 | 190 | 115 | 0.9583 |
从上述结果看来一对一模型比一对多模型的效果较好些,但总的来说这两者的效果都不是很理想的,高级教程中会有其它解决多分类的更高校的算法。一对多的不理想在于它的正负标签比例不均衡,而一对一的不理想在于它的训练次数大大增加了,因为目前只学了二分类,往后研究还得学习关于多分类的算法。