標籤:分類器 weka 源碼 演算法 機器學習
Bagging部分比較簡單,演算法和代碼放到一起寫了。
一、Bagging演算法
嚴格來看Bagging並不能算是一種分類演算法,Bagging和Boosting一樣,是一種組合基本分類器的方法,也就是使用多個基分類器來擷取更為強大的分類器,其核心思想是有放回的抽樣。
Bagging演算法的訓練流程:
1、從樣本集中有放回的抽樣M個樣本。
2、用這M個樣本訓練基分類器C。
3、重複這個過程X次,得到若干個基分類器。
Bagging演算法的預測流程:
1、對於新傳入執行個體A,用這X個新分類器得到一個分類結果的列表。
2、若待分類屬性是數值型(迴歸),求這個列表的算數平均值作為結果返回。
3、若待分類屬性是枚舉類型(分類),按這個列表對分類結果進行投票,返回票數最高的。
二、Weka代碼實現
(1)基分類器
Weka中的預設基分類器使用的是REPTree,也就是Fast decision tree learner,至於這個具體是個什麼,後面我再寫文章進行分析。
public Bagging() { m_Classifier = new weka.classifiers.trees.REPTree(); }
(2)構建過程BuildClassifier
整個BuildClassifier都是圍繞標m_CalcOutOfBag來展開的,這個m_CalcOutOfBag標識的意思是:是否計算OutofBag的錯誤比例。
假如我們對訓練集M進行抽樣,抽樣的數量和M的數量是一樣的,那麼肯定會有一些樣本並沒有被抽到(為什嗎?因為是有放回的抽樣),這個標識就是用來評測這些沒抽到的樣本的準確率,如果沒有這個標,那麼這個準確率到後面就不會被計算了。
if (m_CalcOutOfBag && (m_BagSizePercent != 100)) { throw new IllegalArgumentException("Bag size needs to be 100% if " + "out-of-bag error is to be calculated!"); } int bagSize = data.numInstances() * m_BagSizePercent / 100; Random random = new Random(m_Seed); boolean[][] inBag = null; if (m_CalcOutOfBag) inBag = new boolean[m_Classifiers.length][]; for (int j = 0; j < m_Classifiers.length; j++) { Instances bagData = null; // create the in-bag dataset if (m_CalcOutOfBag) { inBag[j] = new boolean[data.numInstances()]; // bagData = resampleWithWeights(data, random, inBag[j]); bagData = data.resampleWithWeights(random, inBag[j]); } else { bagData = data.resampleWithWeights(random); if (bagSize < data.numInstances()) { bagData.randomize(random); Instances newBagData = new Instances(bagData, 0, bagSize); bagData = newBagData; } }這一部分是抽樣,首先如果有m_CalcOutOfBag標,則必須要求抽樣比例是100%。
其次算出要抽樣的大小。
inBag數組是用來記錄Instances中哪些樣本被抽到了哪些沒被抽到。
data.resampleWithWeight就是進行有放回的抽樣。
if (m_Classifier instanceof Randomizable) { ((Randomizable) m_Classifiers[j]).setSeed(random.nextInt()); } // build the classifier m_Classifiers[j].buildClassifier(bagData);接著是構建分類樹的過程,調用具體classifier的buildClassifier方法。
最後是計算OutOfBag的過程,代碼我已寫注釋。
if (getCalcOutOfBag()) { //如果有這個標就計算 double outOfBagCount = 0.0; //錯誤的權重和 double errorSum = 0.0;//錯誤的偏差值的和 boolean numeric = data.classAttribute().isNumeric();//是否是連續數值 for (int i = 0; i < data.numInstances(); i++) { double vote;//代表投票結果 double[] votes;//代表投票 if (numeric) votes = new double[1];//如果是數值,則取平均數,計算平均數的過程一個數組單元就夠了 else votes = new double[data.numClasses()];//否則則要進行投票 // determine predictions for instance int voteCount = 0; for (int j = 0; j < m_Classifiers.length; j++) { if (inBag[j][i]) continue;//如果已經被採樣,就忽略,因為要計算的是OutOfBag voteCount++;//記錄有多少樣本被計算 if (numeric) { votes[0] = m_Classifiers[j].classifyInstance(data.instance(i));//數值型則直接把預測結果累加 } else {
double[] newProbs = m_Classifiers[j].distributionForInstance(data .instance(i)); for (int k = 0; k < newProbs.length; k++) { votes[k] += newProbs[k]; //枚舉型則要把所有枚舉機率進行累加 } } } // "vote" if (numeric) { vote = votes[0]; if (voteCount > 0) { vote /= voteCount; // 數值型取均值 } } else { if (Utils.eq(Utils.sum(votes), 0)) { } else { Utils.normalize(votes);//歸一化 } vote = Utils.maxIndex(votes); // 選出最大的index } outOfBagCount += data.instance(i).weight();//累加權重 if (numeric) { errorSum += StrictMath.abs(vote - data.instance(i).classValue()) * data.instance(i).weight();//累加錯誤偏差 } else { if (vote != data.instance(i).classValue()) errorSum += data.instance(i).weight();//如果是枚舉就對出錯進行計數 } } m_OutOfBagError = errorSum / outOfBagCount;//最後取個平均值 } else { m_OutOfBagError = 0;//如果沒有那個標就不計算了 }
三、根據權重進行無放回抽樣的過程
也就是 data.resampleWithWeights(random, inBag[j]);這個方法,感覺看了一下還挺有意思的,就放上來剖析一下。
重載形式有3個,前兩個都會調用第三個:
public Instances resampleWithWeights(Random random, double[] weights) { return resampleWithWeights(random, weights, null); }
public Instances resampleWithWeights(Random random, boolean[] sampled) { double[] weights = new double[numInstances()]; for (int i = 0; i < weights.length; i++) { weights[i] = instance(i).weight(); } return resampleWithWeights(random, weights, sampled); }
public Instances resampleWithWeights(Random random, double[] weights, boolean[] sampled) { if (weights.length != numInstances()) { throw new IllegalArgumentException("weights.length != numInstances."); } Instances newData = new Instances(this, numInstances()); if (numInstances() == 0) { return newData; } // Walker's method, see pp. 232 of "Stochastic Simulation" by B.D. Ripley double[] P = new double[weights.length]; System.arraycopy(weights, 0, P, 0, weights.length); Utils.normalize(P); double[] Q = new double[weights.length]; int[] A = new int[weights.length]; int[] W = new int[weights.length]; int M = weights.length; int NN = -1; int NP = M; for (int I = 0; I < M; I++) { if (P[I] < 0) { throw new IllegalArgumentException("Weights have to be positive."); } Q[I] = M * P[I]; if (Q[I] < 1.0) { W[++NN] = I; } else { W[--NP] = I; } } if (NN > -1 && NP < M) { for (int S = 0; S < M - 1; S++) { int I = W[S]; int J = W[NP]; A[I] = J; Q[J] += Q[I] - 1.0; if (Q[J] < 1.0) { NP++; } if (NP >= M) { break; } } // A[W[M]] = W[M]; } for (int I = 0; I < M; I++) { Q[I] += I; } for (int i = 0; i < numInstances(); i++) { int ALRV; double U = M * random.nextDouble(); int I = (int) U; if (U < Q[I]) { ALRV = I; } else { ALRV = A[I]; } newData.add(instance(ALRV)); if (sampled != null) { sampled[ALRV] = true; } newData.instance(newData.numInstances() - 1).setWeight(1); } return newData; }
這個所謂的
Walker's method, see pp. 232 of "Stochastic Simulation" by B.D. Ripley
我找了半天也不知道是個啥演算法,代碼也沒啥注釋,大體一看沒看懂,等下次有機會再把這個函數的演算法補上吧。
Weka演算法Classifier-meta-Bagging源碼分析