寫在之前:
前些文章曾經(jīng)細(xì)數(shù)過從決策樹、貝葉斯算法等一些簡單的算法到神經(jīng)網(wǎng)絡(luò)(BP)、支持向量機(jī)(SVM)、adaboost等一些較為復(fù)雜的機(jī)器學(xué)習(xí)算法(對其中感興趣的朋友可以往前的博客看看),各種算法各有優(yōu)缺點(diǎn),基本上都能處理線性與非線性樣本集,然通觀這些算法來看,個人感覺對于數(shù)據(jù)(無論線性還是非線性)的分類上來說,里面比較好的當(dāng)數(shù)BP、SVM、adaboost元算法這三種了,由于前面在介紹相應(yīng)算法原理以及實(shí)驗的時候所用的樣本以及分類情況都是二分類的,對于多分類的情況未曾涉及過,而實(shí)際情況往往是分類多分類數(shù)據(jù)的樣本較多,本節(jié)旨在對BP、SVM、adaboost這三種個人感覺較好的算法進(jìn)行一個對比,同時實(shí)驗一個簡單的非線性多分類樣本。
一:理解與分析
既然是多分類樣本,首先對樣本需要理解,所謂多分類就是樣本集中不止2類樣本,至少3類才稱得上是多分類。比如下面一個二維非線性的多類樣本集(這也是后面我們實(shí)驗的樣本集):
每種顏色代表一類,可以看到共有5類,同時也可以看到是一個非線性的吧,這里可以就把五類分別設(shè)置為1~5類類標(biāo)簽。
好了,曾經(jīng)在單個算法介紹的時候,里面的實(shí)驗都是二分類的(也就是只有上述5類樣本中的兩類),二分類的方式很簡單,不是你就是我的這種模式,那么從二分類到多分類該怎么轉(zhuǎn)換呢?假如一個樣本不是我,那也可能不是你呀,可能是他它她對吧,這個時候該如何呢?
現(xiàn)在一般的方式都是將多分類問題轉(zhuǎn)化為二分類問題,因為前面許多算法在原理推導(dǎo)上都是假設(shè)樣本是二分類的,像SVM,整個推導(dǎo)過程以至結(jié)論都是相對二分類的,根本沒有考慮多分類,依次你想將SVM直接應(yīng)用于多分類是不可能的,除非你在從原理上去考慮多分類的情況,然后得到一個一般的公式,最后在用程序?qū)崿F(xiàn)這樣才可以。
那么多分類問題怎么轉(zhuǎn)化為二分類問題?很簡單,一個簡單的思想就是分主次,采取投票機(jī)制。轉(zhuǎn)化的方式有兩種,因為分類問題最終需要訓(xùn)練產(chǎn)生一個分類器,產(chǎn)生這個分類器靠的是訓(xùn)練樣本,前面的二分類問題實(shí)際上就是產(chǎn)生了一個分類器,而多分類問題根據(jù)訓(xùn)練集產(chǎn)生的可不止是一個分類器,而是多個分類器。
那第一種方式就是將訓(xùn)練樣本集中的某一類當(dāng)成一類,其他的所有類當(dāng)成另外一類,像上面的5類,我把最中間的一類當(dāng)成是第一類,并重新賦予類標(biāo)簽為1,而把四周的四類都認(rèn)為是第二類,并重新賦予類標(biāo)簽維-1,好了現(xiàn)在的問題是不是就是二分類問題了?是的。那二分類好辦,用之前的任何一個算法處理即可。好了,這是把最中間的當(dāng)成一類的情況下建立的一個分類器。同理,我們是不是也可以把四周任何一類自成一類,而把其他的統(tǒng)稱為一類呀?當(dāng)然可以,這樣依次類推,我們共建立了幾個分類器?像上面5類就建立了5個分類器吧,好了到了這我們該怎么劃分測試集的樣本屬于哪一類了?注意測試集是假設(shè)不知道類標(biāo)簽的,那么來了一個測試樣本,我把它依次輸入到上述建立的5個分類器中,看看最終它屬于哪一類的多,那它就屬于哪一類了吧。比如假設(shè)一個測試樣本本來是屬于中間的(假設(shè)為第5類吧),那么先輸入第五類自成一類的情況,這個時候發(fā)現(xiàn)它屬于第五類,記錄一下5,然后再輸入左上角(假設(shè)為1類)自成一類的情況,那么發(fā)現(xiàn)這個樣本時不屬于1類的,而是屬于2,3,4,5這幾類合并在一起的一類中,那么它屬于2,3,4,5中的誰呢?都有可能吧,那么我都記一下,此時記一下2,3,4,5。好了再到有上角,此時又可以記一下這個樣本輸入1,3,4,5.依次類推,最后把這5個分類器都走一遍,就記了好多1~5的標(biāo)簽吧,然后去統(tǒng)計他們的數(shù)量,比如這里統(tǒng)計1類,發(fā)現(xiàn)出現(xiàn)了3次,2,3,4都出現(xiàn)了3次,就5出現(xiàn)了5次,那么我們就有理由認(rèn)為這個樣本屬于第五類,那么現(xiàn)在想想是不是就把多類問題解決了呢?而這個過程參考這位大神博客中的一張圖表示就如下:
可以看到,其實(shí)黑實(shí)線本類是我們想要的理想分類面,而按照這種方式建立的分類面是帶陰影部分的那個分類面,那陰影部分里面表示什么呢?我們想想,假設(shè)一個樣本落在了陰影里面,比如我畫的那個紫色的點(diǎn),按照上面計算,發(fā)現(xiàn)它屬于三角形一類的2次,屬于正方形一類的2次,屬于圓形一類的1次,那這個時候你怎么辦?沒招,只能在最大的兩次中挑一個,運(yùn)氣好的認(rèn)為屬于三角形,挑對了,運(yùn)氣不好的挑了個正方形,分錯了。所以陰影部分是屬于模棱兩可的情況,這個時候只能挑其中一個了。
這是第一種方式,那還有第二種分類方式,思想類似,也是轉(zhuǎn)化為二分類問題,不過實(shí)現(xiàn)上不同。前面我們是挑一類自成一類,剩下的所有自成一類,而這里,也是從中挑一類自成一類,然剩下的并不是自成一類,而是在挑一類自成一類,也就是說從訓(xùn)練樣本中挑其中的兩類來產(chǎn)生一個分類器。像上述的5類,我先把1,2,類的訓(xùn)練樣本挑出來,訓(xùn)練一個屬于1,2,類的分類器,然后把1,3,挑出來訓(xùn)練一個分類器,再1,4再1,5再2,3,等等(注意2,1與1,2一樣的,所以省去了),那這樣5類樣本需要建立多少個分類器呢?n*(n-1)/2吧,這里就是5*4/2=10個分類器,可以看到比上面的5個分類器多了5個。而且n越大,多的就越多。好了建立完分類器,剩下的問題同樣采取投票機(jī)制,來一個樣本,帶到1,2建立的發(fā)現(xiàn)屬于1,屬于1類的累加器加一下,帶到1,3建立的發(fā)現(xiàn)也屬于1,在加一下,等等等等。最后看看5個類的累加器哪個最大就屬于哪一類。那么一個問題來了,會不會出現(xiàn)像上面那種情況,有兩個或者更多個累加器的值是一樣的呢?答案是有的,但是這種情況下,出現(xiàn)一樣的概率可比上述情況的概率小多了(比較是10個分類器來的結(jié)果,怎么也得比你5個的要好吧),同樣一個示意圖如下:
可以看到重疊部分就是中間那么一小塊,相比上面那種方式小了不少吧。
那么細(xì)比較這兩種方式,其實(shí)各有優(yōu)缺點(diǎn)。第一種方式由于建立的分類器少(n越大越明顯吧,兩者相差(n*(n-1)/2 - n)個分類器)。也就是在運(yùn)算的時候速度更快,而第二種方式雖然速度慢,但是精度高呀,而且現(xiàn)在計算機(jī)的速度也夠快了,可以彌補(bǔ)第二種方式的缺點(diǎn),所以個人更傾向于第二種方式了。
好了說完了理論部分,實(shí)踐起來吧,實(shí)踐是檢驗真理的唯一方法。
二:BP模式識別工具箱處理多分類實(shí)驗
首先采用神經(jīng)網(wǎng)絡(luò)算法來實(shí)驗,同時為了速度與準(zhǔn)確率,我們實(shí)驗matlab的神經(jīng)網(wǎng)絡(luò)工具箱,關(guān)于該工具箱怎么用,請參考:
機(jī)器學(xué)習(xí)之實(shí)戰(zhàn)matlab神經(jīng)網(wǎng)絡(luò)工具箱
為了實(shí)現(xiàn)較好的效果,這里我們直接使用matlab在BP下建立起來的模式識別工具箱(nprtool)。該工具箱的使用可以通過GUI界面直接操作,也可以命令操作,需要說明一點(diǎn)的就是數(shù)據(jù)的輸入形式,尤其是對于類標(biāo)簽的設(shè)置,在該工具箱下,類標(biāo)簽已經(jīng)不再是用數(shù)字1~5直接表示,而是用一個向量,比如類別1可以表示為[1,0,0,0,0],類別3可以表示為[0,0,1,0,0]這種表示方式。同時如果樣本輸入每一行表示一個樣本,那么類別就得像上面那一,每一行表示一個樣本類別。如果每一列為一個樣本,那么對應(yīng)的標(biāo)簽也是每一列一個樣本,下面實(shí)驗每一列表示一個樣本的樣本集:
%%
% * matlab模式識別工具箱的分類設(shè)計
% * 多類非線性分類
%
%%
clc
clear
close all
%% Load data
% * 數(shù)據(jù)預(yù)處理
data = load('data_test.mat');
data = data.data;
%選擇訓(xùn)練樣本個數(shù)
num_train = 200;%共500個樣本
%構(gòu)造隨機(jī)選擇序列
choose = randperm(length(data));
train_data = data(choose(1:num_train),:);
label_temp = train_data(:,end);
label_train = zeros(length(train_data),5);
%把輸出分類標(biāo)簽改為工具箱要求的格式
for i = 1:length(train_data)
label_train(i,label_temp(i)) = 1;
end
train_data = train_data(:,1:end-1)';
label_train = label_train';
%
test_data = data(choose(num_train+1:end),:);
label_temp = test_data(:,end);
label_test = zeros(length(test_data),5);
%把輸出分類標(biāo)簽改為工具箱要求的格式
for i = 1:length(test_data)
label_test(i,label_temp(i)) = 1;
end
test_data = test_data(:,1:end-1)';
label_test = label_test';
%%
% Create a Pattern Recognition Network
hiddenLayerSize = 10;
net = patternnet(hiddenLayerSize);
% 將訓(xùn)練集再按比例內(nèi)分為訓(xùn)練集、驗證集、測試集
net.divideParam.trainRatio = 70/100;
net.divideParam.valRatio = 15/100;
net.divideParam.testRatio = 15/100;
% Train the Network
[net,tr] = train(net,train_data,label_train);
% Test the Network
predict = net(test_data);
[~,predict] = max(predict);
%% show the result --testings
figure;
gscatter(test_data(1,:),test_data(2,:),predict);
[~,label_test] = max(label_test);
accuracy = length(find(predict==label_test))/length(test_data);
title(['predict the testing data and the accuracy is :',num2str(accuracy)]);
可以看到,其實(shí)程序開頭許多對數(shù)據(jù)進(jìn)行了訓(xùn)練樣本與測試樣本的選擇,同時對類標(biāo)簽進(jìn)行了變化。之間部分是建立模式識別的神經(jīng)網(wǎng)絡(luò)網(wǎng)路系統(tǒng),最后應(yīng)用這個網(wǎng)絡(luò)對測試集進(jìn)行測試,得到一個結(jié)果如下:
這是中間出來的網(wǎng)路結(jié)構(gòu):
可以看到,這是200個訓(xùn)練樣本300個測試樣本下的結(jié)果,該工具箱產(chǎn)生的準(zhǔn)確率是相當(dāng)高的。
其實(shí)對于該工具箱也可以通過GUI界面直接操作,不用編寫那么多代碼,但是你的輸入數(shù)據(jù)格式什么的都得提前轉(zhuǎn)換對才行。直接命令輸入nprtool就可以打開該工具箱的GUI,詳細(xì)的可以自行研究。
三:svm之libsvm處理多分類實(shí)驗
下面我們來通過svm方法進(jìn)行上述數(shù)據(jù)的分類。由于上面的BP部分直接采用工具箱函數(shù),并沒有涉及到前面我們說的兩種由二分類到多分類的方法,對于svm我們將把兩種方式都演示一遍。這里我會用到libsvm工具箱,關(guān)于該工具箱怎么使用請看:
解密SVM系列(五):matlab下libsvm的簡單使用
第一種:
%%
% * libsvm工具箱實(shí)驗
% * 多類非線性分類
%
%%
clc
clear
close all
%% Load data
% * 數(shù)據(jù)預(yù)處理--分兩類情況
data = load('data_test.mat');
data = data.data;
%選擇訓(xùn)練樣本個數(shù)
num_train = 200;
%構(gòu)造隨機(jī)選擇序列
choose = randperm(length(data));
train_data = data(choose(1:num_train),:);
gscatter(train_data(:,1),train_data(:,2),train_data(:,3));
label_train = train_data(:,end);
test_data = data(choose(num_train+1:end),:);
label_test = test_data(:,end);
%% svm的構(gòu)建與訓(xùn)練
for i = 1:5 %5類
%重新歸類
label_temp = label_train;
index1 = find(label_train == i);
index2 = find(label_train ~= i);
label_temp(index1) = 1;
label_temp(index2) = -1;
% 訓(xùn)練模型
model{i} = svmtrain(label_temp,train_data(:,1:end-1),'-t 2');
end
% 用模型來預(yù)測測試集的分類
predict = zeros(length(test_data),1);
for i = 1:length(test_data)
data_test = test_data(i,:);
addnum = zeros(1,5);
for j = 1:5
temp = svmpredict(1,data_test(:,1:end-1),model{j});
if temp > 0
addnum(j) = addnum(j) + 1;
else
addnum = addnum + 1;
addnum(j) = addnum(j) - 1;
end
end
[~,predict(i)] = max(addnum);
end
%% show the result--testing
figure;
gscatter(test_data(:,1),test_data(:,2),predict);
accuracy = length(find(predict==label_test))/length(test_data);
title(['predict the training data and the accuracy is :',num2str(accuracy)]);
結(jié)果如下:
下面進(jìn)行第二種方式:
%%
% * libsvm工具箱實(shí)驗
% * 多類非線性分類
%
%%
clc
clear
close all
%% Load data
% * 數(shù)據(jù)預(yù)處理
data = load('data_test.mat');
data = data.data;
%選擇訓(xùn)練樣本個數(shù)
num_train = 200;
%構(gòu)造隨機(jī)選擇序列
choose = randperm(length(data));
train_data = data(choose(1:num_train),:);
gscatter(train_data(:,1),train_data(:,2),train_data(:,3));
label_train = train_data(:,end);
test_data = data(choose(num_train+1:end),:);
label_test = test_data(:,end);
%% svm的構(gòu)建與訓(xùn)練
num = 0;
for i = 1:5-1 %5類
for j = i+1:5
num = num + 1;
%重新歸類
index1 = find(label_train == i);
index2 = find(label_train == j);
label_temp = zeros((length(index1)+length(index2)),1);
%svm需要將分類標(biāo)簽設(shè)置為1與-1
label_temp(1:length(index1)) = 1;
label_temp(length(index1)+1:length(index1)+length(index2)) = -1;
train_temp = [train_data(index1,:);train_data(index2,:)];
% 訓(xùn)練模型
model{num} = svmtrain(label_temp,train_temp(:,1:end-1),'-t 2');
end
end
% 用模型來預(yù)測測試集的分類
predict = zeros(length(test_data),1);
for i = 1:length(test_data)
data_test = test_data(i,:);
num = 0;
addnum = zeros(1,5);
for j = 1:5-1
for k = j+1:5
num = num + 1;
temp = svmpredict(1,data_test(:,1:end-1),model{num});
if temp > 0
addnum(j) = addnum(j) + 1;
else
addnum(k) = addnum(k) + 1;
end
end
end
[~,predict(i)] = max(addnum);
end
%% show the result--testing
figure;
gscatter(test_data(:,1),test_data(:,2),predict);
accuracy = length(find(predict==label_test))/length(test_data);
title(['predict the testing data and the accuracy is :',num2str(accuracy)]);
結(jié)果如下:
可以看到的是這兩種方式下的結(jié)果都挺好,準(zhǔn)確率都高,由于訓(xùn)練樣本隨機(jī)選擇,每一次的結(jié)果不會一樣。至于哪一種好,我覺得,當(dāng)樣本大的時候,在速度滿足要求的情況下,并且數(shù)據(jù)可能重疊的時候,第二種是好些的。
四:adaboost元算法處理多分類實(shí)驗
關(guān)于adaboost元算法的詳細(xì)原理與實(shí)現(xiàn)過程請看上節(jié):
機(jī)器學(xué)習(xí)之白話與實(shí)戰(zhàn)adaboost元算法
考慮到adaboost元算法并沒有去找相應(yīng)的軟件工具箱,所以這里就用自己編寫的函數(shù)來實(shí)現(xiàn)吧,在上述博客中涉及到了下面會使用到的兩個子函數(shù)buildSimpleStump和adaBoostTrainDs,限于篇幅,這里不再貼出來,要使用的朋友可以自行把那里的拷貝過來。
那么在基于上述的兩個子函數(shù)下,我們在編寫兩個函數(shù),一個是adaboost的訓(xùn)練函數(shù),一個是adaboost的預(yù)測函數(shù),函數(shù)如下:
訓(xùn)練函數(shù):
function model = adaboost_train(label,data,iter)
[model.dim,model.direction,model.thresh,model.alpha] = ...
adaBoostTrainDs(data,label,iter);
model.iter = iter;
預(yù)測函數(shù):
function predict = adaboost_predict(data,model)
h = zeros(model.iter,1);
for j = 1:model.iter
if model.direction(j) == -1
if data(model.dim(j)) <= model.thresh(j)
h(j) = -1;
else
h(j) = 1;
end
elseif model.direction(j) == 1
if data(model.dim(j)) <= model.thresh(j)
h(j) = 1;
else
h(j) = -1;
end
end
end
predict = sign(model.alpha'*h);
有了這兩個函數(shù)我們就可以進(jìn)行實(shí)驗了,這里我們只一第二種方式的多分類為例,函數(shù)同上面的svm類似,只不過把那里的訓(xùn)練模型函數(shù)與預(yù)測函數(shù)改到我們這里的這種,主函數(shù)如下:
%%
% * adaboost
% * 多類非線性分類
%
%%
clc
clear
close all
%% Load data
% * 數(shù)據(jù)預(yù)處理
data = load('data_test.mat');
data = data.data;
%選擇訓(xùn)練樣本個數(shù)
num_train = 200;
%構(gòu)造隨機(jī)選擇序列
choose = randperm(length(data));
train_data = data(choose(1:num_train),:);
gscatter(train_data(:,1),train_data(:,2),train_data(:,3));
label_train = train_data(:,end);
test_data = data(choose(num_train+1:end),:);
label_test = test_data(:,end);
%% adaboost的構(gòu)建與訓(xùn)練
num = 0;
iter = 30;%規(guī)定弱分類器的個數(shù)
for i = 1:5-1 %5類
for j = i+1:5
num = num + 1;
%重新歸類
index1 = find(label_train == i);
index2 = find(label_train == j);
label_temp = zeros((length(index1)+length(index2)),1);
%svm需要將分類標(biāo)簽設(shè)置為1與-1
label_temp(1:length(index1)) = 1;
label_temp(length(index1)+1:length(index1)+length(index2)) = -1;
train_temp = [train_data(index1,:);train_data(index2,:)];
% 訓(xùn)練模型
model{num} = adaboost_train(label_temp,train_temp,iter);
end
end
% 用模型來預(yù)測測試集的分類
predict = zeros(length(test_data),1);
for i = 1:length(test_data)
data_test = test_data(i,:);
num = 0;
addnum = zeros(1,5);
for j = 1:5-1
for k = j+1:5
num = num + 1;
temp = adaboost_predict(data_test,model{num});
if temp > 0
addnum(j) = addnum(j) + 1;
else
addnum(k) = addnum(k) + 1;
end
end
end
[~,predict(i)] = max(addnum);
end
%% show the result--testing
figure;
gscatter(test_data(:,1),test_data(:,2),predict);
accuracy = length(find(predict==label_test))/length(test_data);
title(['predict the testing data and the accuracy is :',num2str(accuracy)]);
這還是在200個訓(xùn)練樣本下300個測試樣本的一個結(jié)果如下:
可以看到在iter=30個弱分類器下的結(jié)果已經(jīng)是高的驚人了。
至此上述三種方法介紹完畢,上述三種方法對于監(jiān)督式的多分類問題來說確實(shí)都相當(dāng)好了。只要根據(jù)你的樣本來調(diào)節(jié)適當(dāng)參數(shù),感覺總是可以得到較好的結(jié)果的。敘述了這么多,喜歡的朋友頂一下吧~_~!同時也歡迎相互交流。
|