goal:
實現一個決策樹分類器。 分類器的性能將通過對提供的數據集進行10倍(10-fold)交叉驗證來評估。 決策樹和交叉驗證在課程中進行了介紹。
environment
MATLAB R2019b
dataset
本次實驗使用的數據集是一個葡萄酒數據集,該文件是一個逗號分隔文件(csv),該數據集通常用於評估分類算法,其中分類任務是確定葡萄酒質量是否超過7。
我們將葡萄酒質量得分映射到0和1的二元類。從0到6(含)的葡萄酒得分被映射爲0,葡萄酒得分爲7及以上映射到1。我們要做的是數據集執行二進制分類。
每行用12列描述一種葡萄酒:前11列描述該葡萄酒的特徵(詳細信息),最後一欄是葡萄酒的質量(0/1)。
1 - fixed acidity
2 - volatile acidity
3 - citric acid
4 - residual sugar
5 - chlorides
6 - free sulfur dioxide
7 - total sulfur dioxide
8 - density
9 - pH
10 - sulphates
11 - alcohol
Output variable (based on sensory data):
12 - quality (score between 0 and 10)
數據集獲取地址:
http://archive.ics.uci.edu/ml/datasets/Wine+Quality
(順便推薦一下這個網址,這是加利福尼亞大學的實驗室數據集網站,裏面也有很多其他非常優質的數據集)
(當然,如果下不下來可以評論裏私聊~~)
C4.5
這裏採用C4.5的方式來實現
(1) C4.5算法是決策樹分類領域的一種較爲經典的算法,下圖展示了算法的流程:
(2)與ID3相比主要的改進:用信息增益率來選擇屬性。ID3選擇屬性用的是子樹的信息增益,這裏可以用很多方法來定義信息,ID3使用的是熵(entropy, 熵是一種不純度度量準則),也就是熵的變化值,而C4.5用的是信息增益率。
信息增益和信息增益比,這裏不再展開,網絡上有很多優秀的博主來對此進行介紹
在決策樹構造過程中進行剪枝,因爲某些具有很少元素的結點可能會使構造的決策樹過擬合(Overfitting),如果不考慮這些結點可能會更好。
(3)實現:
數據預處理:
一種的隨機抽取訓練集和測試集的方式:
%隨機採樣的方法來分開訓練樣本和測試樣本,並分開前11個屬性值和第12列的label:
winedata=csvread('ex6Data.csv',1,0,[1,0,20,11])
train_index=randperm(length(winedata),floor(length(winedata)/4*3));
traindata=winedata(train_index,:);
train_features=traindata(:,1:(size(traindata,2))-1)
train_targets=traindata(:,12)'
另一種是10折的交叉驗證的方式
使用這種方法,我們將數據集隨機分成10份,使用其中9份進行訓練而將另外1份用作測試。該過程可以重複10次,每次使用的測試數據不同。
(1)每一次迭代中留存其中一個桶。第一次迭代中留存桶1,第二次留存桶2,其餘依此類推。
(2)用其他9個桶的信息訓練分類器(第一次迭代中利用從桶2到桶10的信息訓練分類器)。
(3)利用留存的數據來測試分類器並保存測試結果。
交叉驗證實現:在每次循環中,去400i-400(i+1)部分行的數據集作爲測試集,其他部分的數據集作爲訓練集。
testdata=csvread('ex6Data.csv',400*(i-1)+1,0,[400*(i-1)+1,0,400*i,11]);
if i==1
traindata=csvread('ex6Data.csv',401,0,[401,0,4000,11]);
elseif i==10
traindata=csvread('ex6Data.csv',1,0,[1,0,3600,11]);
else
traindata1=csvread('ex6Data.csv',1,0,[1,0,400*(i-1),11]);
traindata2=csvread('ex6Data.csv',400*i+1,0,[400*i+1,0,4000,11]);
traindata=[traindata1;traindata2];
end
說明:
與2折或3折交叉驗證相比,基於10折交叉驗證得到的結果可能更接近於分類器的真實性能。之所以這樣,是因爲每次採用90%而不是2折交叉驗證中僅僅50%的數據來訓練分類器。
遞歸構造決策樹
核心部分(完整代碼見後面)
%計算當前節點的信息熵
Inode = -sum(Pnode.*log2(Pnode));
el= zeros(1, fea);
%記錄每個特徵的信息增益率
location= ones(1, fea)*inf;
for i = 1:fea
%遍歷每個特徵
info= sum(-node.*log(eps+node)/log(2));
%每個特徵分別計算信息熵,eps是爲了防止對數爲1
el(i) = (Inode-sum(rocle.*info))/(-sum(rocle.*log(eps+rocle)/log(2)));
%信息增益率
[~, s] = max(I); %求所有分割點的最大信息增益率
el(i) = spl(s);
location(i) = pe(s);
el(i) = spl(s);
location(i) = pe(s);
%對應特徵i的劃分位置就是能使信息增益最大的劃分值
處理完成後,從工作區中可以看到生成的決策樹以及各個變量:
剪枝
%定義一個閾值(pruning)(這裏爲2),當達到某個節點的實例個數小於閾值時就可以停止決策樹的生長。
if ((pruning > L) || (L == 1) ||(length(ale) == 1))
return
將隨機採樣所得到的訓練樣本用來構造決策樹。依次對訓練樣本的各個特徵計算信息增益率,選擇具有最大信息增益率的特徵作爲當前樣本的分割特徵,對決策樹進行分支。
計算準確率
由於採取的是隨機抽樣,可以看到連續10次的準確率
accuracy(i,1)=cal_accuracy(test_targets,test_targets_predict1)
最後得到的結果如下:
如果我們取100的交叉驗證,可以得到100次的準確率圖表如下所示,可以看到分類準備率基本在0.8以上:
直接以數據形式輸出這課決策數,可以看到如下:
可視化部分:
關於決策樹的可視化,中文網絡上的有效生成很少,建議看matlab的官方教程:
https://ww2.mathworks.cn/help/stats/view-decision-tree.html
效果如下所示:
核心代碼只有一行:
view(t,'mode','graph')
部分剪枝以後:
代碼:
所有函數代碼(並不是我的git)都在這裏,但是main.m我做了修改,如下:
clear;
clc;
% traindata = textread('TrainData.txt');
% testdata = textread('TestData.txt');
% winedata=textscan('WineData.txt');
winedata=csvread('winedata.csv',1,0,[1,0,4000,11]);%這裏可以自己補一下csv文件的讀取方法
%
runtime=10;sum=0;
for i=1:runtime
% tic;%用於計算程序運行時間
%train_index=randperm(length(winedata),floor(length(winedata)/4*3));%隨機採樣,3/4數據作爲訓練樣本,其餘的作爲測試樣本
%test_index=setdiff(linspace(1,length(winedata),length(winedata)),train_index);
%traindata=winedata(train_index,:);%訓練樣本
%testdata=winedata(test_index,:);%測試樣本
testdata=csvread('wineData.csv',400*(i-1)+1,0,[400*(i-1)+1,0,400*i,11]);
if i==1
traindata=csvread('wineData.csv',401,0,[401,0,4000,11]);
elseif i==10
traindata=csvread('wineData.csv',1,0,[1,0,3600,11]);
else
traindata1=csvread('wineData.csv',1,0,[1,0,400*(i-1),11]);
traindata2=csvread('wineData.csv',400*i+1,0,[400*i+1,0,4000,11]);
traindata=[traindata1;traindata2];
end
train_features=traindata(:,1:(size(traindata,2))-1);
train_targets=traindata(:,12)';
test_features=testdata(:,1:(size(traindata,2))-1);
test_targets=testdata(:,12)';
test_targets_predict1 = C4_5(train_features', train_targets, test_features'); %調用C4.5算法用於分類
t=fitrtree(train_features,train_targets');%調用CART算法用於分類
view(t,'mode','graph')
%計算決策樹預測的準確度
fprintf('time=%d',i)
accuracy(i,1)=cal_accuracy(test_targets,test_targets_predict1)
sum=sum+accuracy(i,1);
end
sum/10
plot(accuracy(:,1));
hold on;
plot(repmat(mean(accuracy(:,1)),length(accuracy(:,1)),1),'r--');
legend('準確度','準確度均值');
title('C4.5算法分類準確度');
xlabel('測試次數');
ylabel('分類準確度');
ylim([0,1.2]);
grid;
補充1:
csvread()函數有三種使用方法:
1、M = csvread(‘filename’)
2、M = csvread(‘filename’, row, col)
3、M = csvread(‘filename’, row, col, range)
第一種方法中,直接輸入文件名,將數據讀到矩陣M中。這裏要求csv文件中只能包含數字。
第二種方法中,除了文件名,還指定了開始讀取位置的行號(row)和列號(col)。這裏,行號、列號以0開始計數。也就是說,row=0, col=0表示從文件中第一個數開始讀。
第三種方法中,range限定了讀取的範圍。range = [R1 C1 R2 C2],這裏(R1,C1)是讀取區域的左上角,(R2,C2)是讀取區域的右下角。在使用這種方法時,要求row, col等於range中的前兩項。
注意:csv文件中的空項,讀到矩陣中時,會初始化爲0.
補充2:矩陣的拼接
(在這個的實現中,我個人覺得使用拼接比用並和交效果更好,因爲數據集中存在這重複的數據)
參考:
https://jingyan.baidu.com/article/6525d4b1a97799ac7d2e94e9.html
命令C=[A B]來拼接矩陣A和矩陣B,此類拼接爲橫向拼接,左邊爲矩陣A,右邊爲矩陣B。
命令C=[A,B]也可以用於矩陣的橫向拼接,與上一個命令的效果相同,運行結果如下圖所示。
接下來使用命令C=[A;B]來拼接矩陣A和矩陣B,此類拼接爲縱向拼接,上邊爲矩陣A,下邊爲矩陣B。
本文參考
https://blog.csdn.net/lin_limin/article/details/80998689(決策樹)
https://www.cnblogs.com/BlameKidd/p/9735102.html(交叉驗證
)
http://archive.ics.uci.edu/ml/
https://jingyan.baidu.com/article/6525d4b1a97799ac7d2e94e9.html(矩陣拼接)
https://jingyan.baidu.com/article/aa6a2c149ccf240d4c19c407.html(條件語句)
https://ww2.mathworks.cn/help/stats/view-decision-tree.html