random forest 隨機森林(高亮!用於分類) matlab實現
阿新 • • 發佈:2019-02-19
最近要用到隨機森林,於是乎對它的原理了解了一番,並做了一下演算法的實現。本次實現是用於分類問題的,如果是迴歸問題,分裂規則不一樣,我還沒有實現.....
下面的原理摘自別人的筆記,如果瞭解決策樹CART的構建規則ID3或者C4.5的話,這部分原理的內容應該還比較容易理解。
------------------------------------我是分割線------------------------------------------------
function [tree,discrete_dim] = train_C4_5(S, inc_node, Nu, discrete_dim) % Classify using Quinlan's C4.5 algorithm % Inputs: % training_patterns - Train patterns 訓練樣本 每一列代表一個樣本 每一行代表一個特徵 % training_targets - Train targets 1×訓練樣本個數 每個訓練樣本對應的判別值 % test_patterns - Test patterns 測試樣本,每一列代表一個樣本 % inc_node - Percentage of incorrectly assigned samples at a node 一個節點上未正確分配的樣本的百分比 % inc_node為防止過擬合,表示樣本數小於一定閾值結束遞迴,可設定為5-10 % 注意inc_node設定太大的話會導致分類準確率下降,太小的話可能會導致過擬合 % Nu is to determine whether the variable is discrete or continuous (the value is always set to 10) % Nu用於確定變數是離散還是連續(該值始終設定為10) % 這裡用10作為一個閾值,如果某個特徵的無重複的特徵值的數目比這個閾值還小,就認為這個特徵是離散的 % Outputs % test_targets - Predicted targets 1×測試樣本個數 得到每個測試樣本對應的判別值 % 也就是輸出所有測試樣本最終的判別情況 %NOTE: In this implementation it is assumed that a pattern vector with fewer than 10 unique values (the parameter Nu) %is discrete, and will be treated as such. Other vectors will be treated as continuous % 在該實現中,假設具有少於10個無重複值的特徵向量(引數Nu)是離散的。 其他向量將被視為連續的 train_patterns = S(:,1:end-1)'; train_targets = S(:,end)'; [Ni, M] = size(train_patterns); %M是訓練樣本數,Ni是訓練樣本維數,即是特徵數目 inc_node = inc_node*M/100; % 5*訓練樣本數目/100 if isempty(discrete_dim) %Find which of the input patterns are discrete, and discretisize the corresponding dimension on the test patterns %查詢哪些輸入模式(特徵)是離散的,並離散測試模式上的相應維 discrete_dim = zeros(1,Ni); %用於記錄每一個特徵是否是離散特徵,初始化都記為0,代表都是連續特徵, %如果後面更改,則意味著是離散特徵,這個值會更改為這個離散特徵的無重複特徵值的數目 for i = 1:Ni %遍歷每個特徵 Ub = unique(train_patterns(i,:)); %取每個特徵的不重複的特徵值構成的向量 Nb = length(Ub); %得到無重複的特徵值的數目 if (Nb <= Nu) %如果這個特徵的無重複的特徵值的數目比這個閾值還小,就認為這個特徵是離散的 %This is a discrete pattern discrete_dim(i) = Nb; %得到訓練樣本中,這個特徵的無重複的特徵值的數目 存放在discrete_dim(i)中,i表示第i個特徵 % dist = abs(ones(Nb ,1)*test_patterns(i,:) - Ub'*ones(1, size(test_patterns,2))); % %前面是把測試樣本中,這個特徵的那一行復製成Nb行,Nb是訓練樣本的這個特徵中,無重複的特徵值的數目 % %後面是把這幾個無重複的特徵值構成的向量複製成測試樣本個數列 % %求這兩個矩陣相應位置差的絕對值 % [m, in] = min(dist); %找到每一列絕對差的最小值,構成m(1×樣本數目) 並找到每一列絕對差最小值所在行的位置,構成in(1×樣本數目) % %其實,這個in的中每個值就是代表了每個測試樣本的特徵值等於無重複的特徵值中的哪一個或者更接近於哪一個 % %如=3,就是指這個特徵值等於無重複的特徵值向量中的第3個或者更接近於無重複的特徵值向量中的第3個 % test_patterns(i,:) = Ub(in); %得到這個離散特徵 end end end %Build the tree recursively 遞迴地構造樹 % disp('Building tree') flag = []; tree = make_tree(train_patterns, train_targets, inc_node, discrete_dim, max(discrete_dim), 0, flag); function tree = make_tree(patterns, targets, inc_node, discrete_dim, maxNbin, base, flag) %Build a tree recursively 遞迴地構造樹 [N_all, L] = size(patterns); %%L為當前的樣本總數,Ni為特徵數目 if isempty(flag) N_choose = randi([1,N_all],1,0.5*sqrt(N_all));%從所有特徵中隨機選擇m個 Ni_choose = length(N_choose); flag.N_choose = N_choose; flag.Ni_choose = Ni_choose; else N_choose = flag.N_choose; Ni_choose = flag.Ni_choose; end Uc = unique(targets); %訓練樣本對應的判別標籤 無重複的取得這些標籤 也就是得到判別標籤的個數 tree.dim = 0; %初始化樹的分裂特徵為第0個 %tree.child(1:maxNbin) = zeros(1,maxNbin); tree.split_loc = inf; %初始化分裂位置是inf if isempty(patterns) return end %When to stop: If the dimension is one or the number of examples is small % inc_node為防止過擬合,表示樣本數小於一定閾值結束遞迴,可設定為5-10 if ((inc_node > L) | (L == 1) | (length(Uc) == 1)) %如果剩餘訓練樣本太小(小於inc_node),或只剩一個,或只剩一類標籤,退出 H = hist(targets, length(Uc)); %統計樣本的標籤,分別屬於每個標籤的數目 H(1×標籤數目) [m, largest] = max(H); %得到包含樣本數最多的那個標籤的索引,記為largest 包含多少個樣本,記為m tree.Nf = []; tree.split_loc = []; tree.child = Uc(largest);%姑且直接返回其中包含樣本數最多一類作為其標籤 return end %Compute the node's I for i = 1:length(Uc) %遍歷判別標籤的數目 Pnode(i) = length(find(targets == Uc(i))) / L; %得到當前所有樣本中 標籤=第i個標籤 的樣本的數目 佔樣本總數的比例 存放在Pnode(i)中 end % 計算當前的資訊熵(分類期望資訊) % 例如,資料集D包含14個訓練樣本,9個屬於類別“Yes”,5個屬於類別“No”,Inode = -9/14 * log2(9/14) - 5/14 * log2(5/14) = 0.940 Inode = -sum(Pnode.*log(Pnode)/log(2)); %For each dimension, compute the gain ratio impurity %對於每維,計算雜質的增益比 對特徵集中每個特徵分別計算資訊熵 %This is done separately for discrete and continuous patterns %對於離散和連續特徵,分開計算 delta_Ib = zeros(1, Ni_choose); %Ni是特徵維數 用於記錄每個特徵的資訊增益率 split_loc = ones(1, Ni_choose)*inf; %初始化每個特徵的分裂值是inf for i_idx = 1:Ni_choose%遍歷每個特徵 i = N_choose(i_idx); data = patterns(i,:); %得到當前所有樣本的這個特徵的特徵值 Ud = unique(data); %得到無重複的特徵值構成向量Ud Nbins = length(Ud); %得到無重複的特徵值的數目 if (discrete_dim(i)) %如果這個特徵是離散特徵 %This is a discrete pattern P = zeros(length(Uc), Nbins); %Uc是判別標籤的個數 判別標籤個數×無重複的特徵值的數目 for j = 1:length(Uc) %遍歷每個標籤 for k = 1:Nbins %遍歷每個特徵值 indices = find((targets == Uc(j)) & (patterns(i,:) == Ud(k))); % &適用於矩陣間的邏輯運算 &&不適用,只能用於單個元素 &的意思也是與 %找到 (樣本標籤==第j個標籤 並且 當前所有樣本的這個特徵==第k個特徵值) 的樣本個數 P(j,k) = length(indices); %記為P(j,k) end end Pk = sum(P); %取P的每一列的和,也就是得到當前所有樣本中,這個特徵的特徵值==這個特徵值的樣本數目 Pk(1×特徵值數目)表示這個特徵的特徵值等於每個特徵值的樣本數目 P1 = repmat(Pk, length(Uc), 1); %把Pk複製成 判別標籤個數 行 P1 = P1 + eps*(P1==0); %這主要在保證P1作被除數時不等於0 P = P./P1; %得到當前所有樣本中,這個特徵的值等於每個特徵值且標籤等於每個標籤的樣本,佔當前這個特徵值中的樣本的比例 Pk = Pk/sum(Pk); %得到當前所有樣本中,這個特徵的值等於每個特徵值的樣本,佔當前樣本總數的比例 info = sum(-P.*log(eps+P)/log(2)); %對特徵集中每個特徵分別計算資訊熵 info(1×特徵值數目) delta_Ib(i_idx) = (Inode-sum(Pk.*info))/(-sum(Pk.*log(eps+Pk)/log(2))); %計算得到當前特徵的資訊增益率 %計算資訊增益率(GainRatio),公式為Gain(A)/I(A), %其中Gain(A)=Inode-sum(Pk.*info)就是屬性A的資訊增益 %其中I(A)=-sum(Pk.*log(eps+Pk)/log(2))為屬性A的所包含的資訊 else %對於連續特徵(主要要找到合適的分裂值,使資料離散化) %This is a continuous pattern P = zeros(length(Uc), 2); % P(判別標籤數目×2) 列1代表前..個樣本中的標籤分佈情況 列2代表除前..個樣本之外的標籤分佈情況 這個..就是各個分裂位置 %Sort the patterns [sorted_data, indices] = sort(data); %data裡存放的是當前所有訓練樣本的這個特徵的特徵值 從小到大排序 sorted_data是排序好的資料 indices是索引 sorted_targets = targets(indices); %當然,判別標籤也要隨著樣本順序調整而調整 %Calculate the information for each possible split 計算分裂資訊度量 I = zeros(1,Nbins); delta_Ib_inter = zeros(1, Nbins); for j = 1:Nbins-1 P(:, 1) = hist(sorted_targets(find(sorted_data <= Ud(j))) , Uc); %記錄<=當前特徵值的樣本的標籤的分佈情況 P(:, 2) = hist(sorted_targets(find(sorted_data > Ud(j))) , Uc); %記錄>當前特徵值的樣本的標籤的分佈情況 Ps = sum(P)/L; %sum(P)是得到分裂位置前面和後面各有樣本數佔當前樣本總數的比例 P = P/L; %佔樣本總數的比例 Pk = sum(P); %%sum(P)是得到分裂位置前面和後面各有多少個樣本 比例 P1 = repmat(Pk, length(Uc), 1); %把Pk複製成 判別標籤個數 行 P1 = P1 + eps*(P1==0); info = sum(-P./P1.*log(eps+P./P1)/log(2)); %計算資訊熵(分類期望資訊) I(j) = Inode - sum(info.*Ps); %Inode-sum(info.*Ps)就是以第j個樣本分裂的的資訊增益 delta_Ib_inter(j) = I(j)/(-sum(Ps.*log(eps+Ps)/log(2))); %計算得到當前特徵值的資訊增益率 end [~, s] = max(I); %找到資訊增益最大的劃分方法 delta_Ib(i)中存放的是對於當前第i個特徵而言,最大的資訊增益作為這個特徵的資訊增益 s存放這個劃分方法 delta_Ib(i_idx) = delta_Ib_inter(s); %得到這個分類特徵值對應的資訊增益率 split_loc(i_idx) = Ud(s); %對應特徵i的劃分位置就是能使資訊增益最大的劃分值 end end %Find the dimension minimizing delta_Ib %找到當前要作為分裂特徵的特徵 [m, dim] = max(delta_Ib); %找到所有特徵中最大的資訊增益對應的特徵,記為dim dims = 1:Ni_choose; %Ni特徵數目 dim_all = 1:N_all; dim_to_all = N_choose(dim); tree.dim = dim_to_all; %記為樹的分裂特徵 %Split along the 'dim' dimension Nf = unique(patterns(dim_to_all,:)); %得到選擇的這個作為分裂特徵的特徵的那一行 也就是得到當前所有樣本的這個特徵的特徵值 Nbins = length(Nf); %得到這個特徵的無重複的特徵值的數目 tree.Nf = Nf; %記為樹的分類特徵向量 當前所有樣本的這個特徵的特徵值 tree.split_loc = split_loc(dim); %把這個特徵的劃分位置記為樹的分裂位置 可是如果選擇的是一個離散特徵,split_loc(dim)是初始值inf啊??? %If only one value remains for this pattern, one cannot split it. if (Nbins == 1) %無重複的特徵值的數目==1,即這個特徵只有這一個特徵值,就不能進行分裂 H = hist(targets, length(Uc)); %統計當前所有樣本的標籤,分別屬於每個標籤的數目 H(1×標籤數目) [m, largest] = max(H); %得到包含樣本數最多的那個標籤的索引,記為largest 包含多少個樣本,記為m tree.Nf = []; %因為不以這個特徵進行分裂,所以Nf和split_loc都為空 tree.split_loc = []; tree.child = Uc(largest); %姑且將這個特徵的標籤就記為包含樣本數最多的那個標籤 return end if (discrete_dim(dim_to_all)) %如果當前選擇的這個作為分裂特徵的特徵是個離散特徵 %Discrete pattern for i = 1:Nbins %遍歷這個特徵下無重複的特徵值的數目 indices = find(patterns(dim_to_all, :) == Nf(i)); %找到當前所有樣本的這個特徵的特徵值為Nf(i)的索引們 tree.child(i) = make_tree(patterns(dim_all, indices), targets(indices), inc_node, discrete_dim(dim_all), maxNbin, base, flag);%遞迴 %因為這是個離散特徵,所以分叉成Nbins個,分別針對每個特徵值裡的樣本,進行再分叉 end else %Continuous pattern %如果當前選擇的這個作為分裂特徵的特徵是個連續特徵 indices1 = find(patterns(dim_to_all,:) <= split_loc(dim)); %找到特徵值<=分裂值的樣本的索引們 indices2 = find(patterns(dim_to_all,:) > split_loc(dim)); %找到特徵值>分裂值的樣本的索引們 if ~(isempty(indices1) | isempty(indices2)) %如果<=分裂值 >分裂值的樣本數目都不等於0 tree.child(1) = make_tree(patterns(dim_all, indices1), targets(indices1), inc_node, discrete_dim(dim_all), maxNbin, base+1, flag);%遞迴 %對<=分裂值的樣本進行再分叉 tree.child(2) = make_tree(patterns(dim_all, indices2), targets(indices2), inc_node, discrete_dim(dim_all), maxNbin, base+1, flag);%遞迴 %對>分裂值的樣本進行再分叉 else H = hist(targets, length(Uc)); %統計當前所有樣本的標籤,分別屬於每個標籤的數目 H(1×標籤數目) [m, largest] = max(H); %得到包含樣本數最多的那個標籤的索引,記為largest 包含多少個樣本,記為m tree.child = Uc(largest); %姑且將這個特徵的標籤就記為包含樣本數最多的那個標籤 tree.dim = 0; %樹的分裂特徵記為0 end end
用於測試的程式碼:
function [result] = statistics(tn, rnode, PValue, discrete_dim) TypeName = {'1','2'}; TypeNum = [0 0]; test_patterns = PValue(:,1:end-1)'; class_num = length(TypeNum); type = zeros(tn,size(test_patterns,2)); for i = 1:tn %對測試向量進行投票,共有tn棵樹 type(tn,:) = vote_C4_5(test_patterns, 1:size(test_patterns,2), rnode{i,1}, discrete_dim, class_num); % if strcmp( type(tn,:),TypeName{1}) % TypeNum(1) = TypeNum(1) + 1; % else % TypeNum(2) = TypeNum(2) + 1; % end end result = mode(type,1)';%統計每列出現最多的數 分類問題 end
每棵樹的決策程式碼:
function targets = vote_C4_5(patterns, indices, tree, discrete_dim, Uc) %Classify recursively using a tree targets = zeros(1, size(patterns,2)); %設定每個樣本的初始預測標籤都是0 if (tree.dim == 0) %這說明達到了樹的葉子節點 %Reached the end of the tree targets(indices) = tree.child; %得到樣本對應的標籤是tree.child return end %This is not the last level of the tree, so: %First, find the dimension we are to work on dim = tree.dim; %得到分裂特徵 dims= 1:size(patterns,1); %得到特徵索引 %And classify according to it 根據得到的決策樹對測試樣本進行分類 if (discrete_dim(dim) == 0) %如果當前分裂特徵是個連續特徵 %Continuous pattern in = indices(find(patterns(dim, indices) <= tree.split_loc)); %找到當前測試樣本中這個特徵的特徵值<=分裂值的樣本索引 targets = targets + vote_C4_5(patterns(dims, :), in, tree.child(1), discrete_dim(dims), Uc); %對這部分樣本再分叉 in = indices(find(patterns(dim, indices) > tree.split_loc)); %找到當前測試樣本中這個特徵的特徵值>分裂值的樣本索引 targets = targets + vote_C4_5(patterns(dims, :), in, tree.child(2), discrete_dim(dims), Uc); %對這部分樣本再分叉 else %如果當前分裂特徵是個離散特徵 %Discrete pattern Uf = unique(patterns(dim,:)); %得到這個樣本集中這個特徵的無重複特徵值 for i = 1:length(Uf) %遍歷每個特徵值 if any(Uf(i) == tree.Nf) %tree.Nf為樹的分類特徵向量 當前所有樣本的這個特徵的特徵值 in = indices(find(patterns(dim, indices) == Uf(i))); %找到當前測試樣本中這個特徵的特徵值==分裂值的樣本索引 targets = targets + vote_C4_5(patterns(dims, :), in, tree.child(find(Uf(i)==tree.Nf)), discrete_dim(dims), Uc);%對這部分樣本再分叉 end end end %END
測試結果:
完整code及資料鏈接戳
我的資源