Weka中BP神經網絡的實踐(參數調整以及結果分析)


廢話:

周日講了下神經網絡,本來想的是以理論和實踐相結合,前面講講神經網絡,后面簡單講下在weka中怎么使用BP神經網絡,可惜最后時間不夠,而且姥姥的興趣點跑到凸優化那里去了,所以沒有講成實踐的部分,有點郁悶的。為了不浪費了,就把這部分講稿拿出來和大家分享一下,也希望對大家實踐神經網絡有所幫助。因為是講稿,講的要比寫的多,所以很多地方口語化和省略比較嚴重,大家湊合着看吧。

實踐部分講稿正文:


Weka是什么?
Weka是由新西蘭懷卡托大學用Java開發的數據挖掘常用軟件,Weka是懷卡托智能分析系統的縮寫。Weka限制在GNU通用公眾證書的條件下發布,它幾乎可以運行在所有操作系統平台上,包括Linux、Windows、Macintosh等。

Weka中BP神經網絡的實踐:

Weka中的神經網絡使用多層多層感知器實現BP神經網絡。讓我們看看weka自帶的幫助文件是怎么描述的:
BP神經網絡在weka中是分屬這個部分的weka.classifiers.functions.MultilayerPerceptron
其是一個使用了反向傳播(backpropagation)的分類器。
你可以手工構造這個網絡,用算法創建它,或者兩者兼備。 這個網絡可以在訓練的過程中被監視和修改。
網絡中的節點是Sigmoid的,除了當類別(class)是數值屬性(numeric)的,這時輸出節點變成了unthresholded linear units。
注:sigmoid如下圖


關於里面參數的配置如下圖



下面我們來看各個參數的具體意義:
GUI
彈出一個GUI界面。其允許我們在神經網絡訓練的過程中暫停和做一些修改(altering)

  • 按左鍵添加一個節點(node)(節點將被自動選擇以保證沒有其他的節點被選擇)
  • 選中一個節點:左鍵單擊
  • 連接一個節點:首先選中一個起始節點,然后點擊一個結束節點或者空白區域(這將創建一個新節點並與起始節點連接)。在連接后節點的狀態將保持不變。
  • 刪除連接:選擇一個連接的節點並且右鍵單擊另一個節點
  • 刪除節點:右鍵單擊一個節點
  • 取消選擇:左鍵單擊節點或者在空白區域右鍵單擊
  • 標簽(label)提供的原始輸入(raw input)在左邊
  • 紅色的節點是隱層(hidden layers)
  • 橙色的節點是輸出節點(output nodes)
  • 在右邊的標簽展示的是輸出節點表示的類別。要注意的是對於一個數值屬性的類別來說,輸出節點將自動的做成一個unthresholded的線性單元


更改神經網絡只能在網絡沒有啟動(running)的時候做,這條規則也適用於學習速率(learning rate)和其他在控制面板上的區域。

  • 您可以在任何時候結束網絡
  • 網絡在一開始是自動暫停的
  • 有一個關於網絡up了和error的運行時提示。注意的是這個錯誤值(error value或者誤差值吧)是基於網絡的計算值的變化的
  • 一旦網絡訓練完畢它會再次停止並且等待結果是否被接受還是繼續訓練


注意的是如果沒有設置GUI,這個網絡將不需要任何的交互(interaction)

autoBuild
添加網絡中的連接和隱層

debug
設置為True分類器將輸出額外的信息到控制台(console)

decay
這將導致學習的速率的降低。其將初始的學習速率除以迭代次數(epoch number)去決定當前的學習速率。這對於停止神經網絡背離目標輸出有幫助,也提高了general performance。要注意的是衰退的學習速率不會顯示在GUI中。如果學習速率在GUI中被改變,這將被視為初始的學習速率。

hiddenLayers
定義神經網絡的隱層。這是一個正整數的列表。1 for each hidden layer.用逗號分隔。如果沒有隱層就在這里輸入0。這只被用於自動構建是設置了的。也有通用符 'a' = (attribs + classes) / 2, 'i' = attribs, 'o' = classes , 't' = attribs + classes

learningRate
Weights被更新的數量

momentum
當更新weights時設置的動量

normalizeAttributes
將正則化(normalize)屬性。這個能提高網絡的performance。其並不依賴於class是不是數值屬性的。其也會正則化名詞性(nominal)的屬性(當他們被nominal to binary filter run過后),這樣名詞性屬性是在-1和1之間

normalizeNumericClass
將會正則化class如果其實數值屬性的。這也可以提高網絡的performance,其將class正則化到-1和1之間。注意的是這僅僅是內部的,輸出會被轉換回原始的范圍。

reset
這將允許網絡用一個更低的學習速率復位。如果網絡偏離了答案其將會自動的用更低的學習速率復位並且重新訓練。只有當GUI沒有被set的時候這個選項才是available的。
注意的是如果這個網絡偏離了並且沒有被允許去reset其將在訓練的步驟失敗並且返回一個錯誤信息

seed
Seed用於初始化隨機數的生成。隨機數被用於設定節點之間連接的初始weights,並且用於shuffling訓練集

trainingTime
訓練的迭代次數。如果設置的是非0那么這個網絡能夠終止的比較早

validationSetSize
Validation set的百分比,訓練將持續直到其觀測到在validation set上的誤差已經一直在變差,或者訓練的時間已經到了
如果validation set設置的是0那么網絡將一直訓練直到達到迭代的次數

validationThreshold
用於終止validation testing。這個值用於決定在訓練終止前在一行內的validation set error可以變差多少次

經過上面大家對於所有的參數有了一個大概的了解。
下面我們做一個簡單的實驗,也讓大家有個直觀的認識。
我們使用的是比較簡單的Iris的數據集,其數據集簡要描述如下:

“iris以鳶尾花的特征作為數據來源,數據集包含150個數據集,分為3類,每類50個數據,每個數據包含4個屬性,是在數據挖掘、數據分類中非常常用的測試集、訓練集

三類分別為:setosa, versicolor, virginica

數據包含4個獨立的屬性,這些屬性變量測量植物的花朵,比如萼片和花瓣的長度等.”
使用orange canvas做出來的統計如下:


我們設置其為十次十折(cross-validation folds 10
初始的網絡如圖

簡單起見我們讓其自動訓練了,其分類結果的輸出如下(篇幅起見只寫出一部分):
=== Classifier model (full training set) ===

Sigmoid Node 0
Inputs Weights
Threshold -0.8317075956319961
Node 3 0.03364022925166845
Node 4 2.8158475746401312
Node 5 -2.9282184592731384
這以部分主要顯示各個神經元的一些weight和threshold。可以看到這里對應的是右上角的黃色節點,其weight分布對應的node3 4 5是左邊的紅色隱層節點的weight

Sigmoid Node 3
Inputs Weights
Threshold 0.050063835158229715
Attrib sepallength -0.22160556669500697
Attrib sepalwidth -0.03621419764855014
Attrib petallength -0.3759501432512545
Attrib petalwidth -0.5363064140945599
這個顯示的是對應的是左邊綠色輸入向量的weight

=== Stratified cross-validation ===
=== Summary ===

Correctly Classified Instances 146 97.3333 %
Incorrectly Classified Instances 4 2.6667 %
Kappa statistic 0.96
Mean absolute error 0.0327
Root mean squared error 0.1291
Relative absolute error 7.3555 %
Root relative squared error 27.3796 %
Total Number of Instances 150

這一部分顯示的是其各個evaluation的指標,可以看到分類效果還是不錯的。

后面還有一些TP FP 准確率 召回率 F-Measure和ROC Area等指標的展示,以及
=== Confusion Matrix ===

a b c <-- classified as
50 0 0 | a = Iris-setosa
0 48 2 | b = Iris-versicolor
0 2 48 | c = Iris-virginica
在這個矩陣我們可以看出Iris-versicolor Iris-virgca之間有些混淆 

接下來我們就可以用上面的一些指標做優化分析了,不過這個和神經網絡無關,會單獨講,在此就不再贅述。

附jacksonislwj整理的源代碼
http://blog.csdn.net/jacksonislwj/article/details/6376449

貼在下面,大家有興趣的可以好好到上面鏈接的博客研究下,也可以直接展開看

BP源代碼
  public void buildClassifier(Instances i) throws Exception {
     // 驗證數據類型是否為算法所支持的類型   
     getCapabilities().testWithFail(i);
  
     // 讀入數據集中的數據    
     i = new Instances(i);    
      
     // 刪除不完整的數據    
     i.deleteWithMissingClass();   
    // 如果只有類屬性,轉化為特殊處理
    if (i.numAttributes() == 1) {
      System.err.println(
      "Cannot build model (only class attribute present in data!), "
      + "using ZeroR model instead!");
      m_ZeroR = new weka.classifiers.rules.ZeroR();
      m_ZeroR.buildClassifier(i);
      return;
    }
    else {
      m_ZeroR = null;
    }
    
    // 初始化遍歷數據集的次數
    m_epoch = 0;
    /** Shows the error of the epoch that the network just finished. */
    m_error = 0;
    
    // 初始化訓練集
    m_instances = null;
    // 初始化正在經過神經網絡的實例
    m_currentInstance = null;
    // 初始化控制面板
    m_controlPanel = null;
    
    // 初始化顯示神經元的面板
    m_nodePanel = null;
    
    // 初始化輸出單元
    m_outputs = new NeuralEnd[0];
    // 初始化輸入單元
    m_inputs = new NeuralEnd[0];
    // 初始化屬性個數
    m_numAttributes = 0;
    // 初始化類的分類數
    m_numClasses = 0;
    // 初始化節點的連接邏輯
    m_neuralNodes = new NeuralConnection[0];
    // 是否需要停止神經網絡
    m_stopIt = true;
    // 神經網絡是否已經停止
    m_stopped = true;
    // 設置訓練集
    m_instances = new Instances(i);
    
    // 用一個隨機化種子產生的隨機數
    m_random = new Random(m_randomSeed);
    // 重新排列數據集
    m_instances.randomize(m_random);
    // 如果需要把數據集中的名詞屬性轉化成二元屬性
    if (m_useNomToBin) {
      
      // 創建一個名詞轉二元屬性的過濾器
      m_nominalToBinaryFilter = new NominalToBinary();
      // 使用過濾器過濾所有實例
      m_nominalToBinaryFilter.setInputFormat(m_instances);
      m_instances = Filter.useFilter(m_instances,m_nominalToBinaryFilter);
    }
    // 屬性的個數是數據集的屬性個數減1,因為不包括類屬性
    m_numAttributes = m_instances.numAttributes() - 1;
    // 類的分類數是數據集類的分類數
    m_numClasses = m_instances.numClasses();
 
    // 歸一化類屬性到區間[-1,1]
    setClassType(m_instances);
       
    //初始化測試集
    Instances valSet = null;
    //numinval is needed later
    int numInVal = (int)(m_valSize / 100.0 * m_instances.numInstances());
    if (m_valSize > 0) {
      if (numInVal == 0) {
    numInVal = 1;
      }
      valSet = new Instances(m_instances, 0, numInVal);
    }
    ///////////
    // 創建輸入單元
    setupInputs();
    
    // 創建輸出單元
    setupOutputs();
    // 如果自動生成神經網絡    
    if (m_autoBuild) {
      // 自動生成隱含層
      setupHiddenLayer();
    }
   
    // 如果類屬性是數值屬性
    if (m_numeric) {
      setEndsToLinear();
    }
    // 初始化向后傳播誤差
    double right = 0;
    // 初始化停止繼續重復訓練的參數
    double driftOff = 0;
    // 上次訓練的向后傳播誤差
    double lastRight = Double.POSITIVE_INFINITY;
    
    // 最小的向后傳播誤差
    double bestError = Double.POSITIVE_INFINITY;
    
    // 加權學習率
    double tempRate;
    // 訓練集的總權重
    double totalWeight = 0;
    // 測試集的總權重
    double totalValWeight = 0;
    // 保存學習率的臨時變量
    double origRate = m_learningRate; //only used for when reset
    
    //確保至少有一個實例被訓練
    if (numInVal == m_instances.numInstances()) {
      numInVal--;
    }
    if (numInVal < 0) {
      numInVal = 0;
    }
    // 計算實例集的總權重
    for (int noa = numInVal; noa < m_instances.numInstances(); noa++) {
      if (!m_instances.instance(noa).classIsMissing()) {
    totalWeight += m_instances.instance(noa).weight();
      }
    }
    // 計算測試集的總權重
    if (m_valSize != 0) {
      for (int noa = 0; noa < valSet.numInstances(); noa++) {
    if (!valSet.instance(noa).classIsMissing()) {
      totalValWeight += valSet.instance(noa).weight();
    }
      }
    }
    m_stopped = false;
     
    // 遍歷數據集m_numEpochs次
    for (int noa = 1; noa < m_numEpochs + 1; noa++) {
      
      // 初始化向后傳播誤差
      right = 0;
      // 遍歷每個元組
      for (int nob = numInVal; nob < m_instances.numInstances(); nob++){
    
    // 取出當前的實例
    m_currentInstance = m_instances.instance(nob);
    
        // 如果當前實例有類屬性
    if (!m_currentInstance.classIsMissing()) {
       
      // 重新初始化神經網絡
      resetNetwork();
      // 計算加權和
      calculateOutputs();
    
      // 如果要進行權衰減,就隨着遍歷訓練集的次數增多降低學習率
          // 引入權值是因為公式要適應多個值的屬性
      tempRate = m_learningRate * m_currentInstance.weight();  
      if (m_decay) {
        tempRate /= noa;
      }
      
          // 累加向后傳播的誤差
      right += (calculateErrors() / m_instances.numClasses()) *
        m_currentInstance.weight();
          // 更新神經網絡權值
      updateNetworkWeights(tempRate, m_momentum);
      
    }
    
      }
     
      // 計算向后傳播的平均誤差
      right /= totalWeight;
      // 如果向后傳播的平均誤差超過了一定的范圍或者是一個非法值
      if (Double.isInfinite(right) || Double.isNaN(right)) {
        
    // 如果沒有設置自動重置神經網絡
    if (!m_reset) {
      // 清空數據集
      m_instances = null;
          // 拋出異常:因為學習率過大造成神經網絡無法被訓練,請調低學習率
      throw new Exception("Network cannot train. Try restarting with a" +
                  " smaller learning rate.");
    }
    // 如果設置了自動重置神經網絡
    else {
      // 如果學習率太小拋出異常
      if (m_learningRate <= Utils.SMALL)
        throw new IllegalStateException(
        "Learning rate got too small (" + m_learningRate 
        + " <= " + Utils.SMALL + ")!");
      
      // 將學習率調整為當前的一半 
      m_learningRate /= 2;
      // 重新構建神經網絡
      buildClassifier(i);
      m_learningRate = origRate;
      m_instances = new Instances(m_instances, 0);      
      return;
    }
      }

      // 用獨立的測試集測試
      // 如果測試集不為空
      if (m_valSize != 0) {
        // 如果向后傳播誤差為0
    right = 0;
     
        // 遍歷測試集的每個實例
    for (int nob = 0; nob < valSet.numInstances(); nob++) {
          // 取出每個當前實例
      m_currentInstance = valSet.instance(nob);
          // 如果確定了類值
      if (!m_currentInstance.classIsMissing()) {
        // 重新初始化神經網絡
        resetNetwork();
        // 計算加權和
        calculateOutputs();
            // 累加向后傳播誤差
        right += (calculateErrors() / valSet.numClasses()) 
          * m_currentInstance.weight();
        
      }
      
    }
    // 如果當前的誤差小於上次的誤差
    if (right < lastRight) {
      
      // 如果當前的誤差小於之前最小的誤差則更新
      if (right < bestError) {
        // 最小的誤差為當前誤差
        bestError = right;
        // 儲存當前的一組權值
        for (int noc = 0; noc < m_numClasses; noc++) {
          m_outputs[noc].saveWeights();
        }
        
            // 初始化不更新權值的次數
        driftOff = 0;
      }
    
    }
    // 不更新權值的次數加一
    else {
      driftOff++;
    }
        // 最后一次更新的權值為當前權值
    lastRight = right;
    // 如果權值超過了一定次數(初始20次)沒有被更新或者實例已經達到被要求遍歷的次數
    if (driftOff > m_driftThreshold || noa + 1 >= m_numEpochs) {
      // 儲存最終的權值
      for (int noc = 0; noc < m_numClasses; noc++) {
            m_outputs[noc].restoreWeights();
          }
      
          // 實例已經全部通過
      m_accepted = true;
    }
    // 得到測試集的向后傳播平均誤差
    right /= totalValWeight;
      }
      
      // 保存實際遍歷實例集的次數
      m_epoch = noa;
      // 保存向后傳播錯誤率
      m_error = right;
   
    }
  }
  // 自動生成隱含層的策略
  private void setupHiddenLayer()
  {
    // 新建一個字符串拆分的實例
    StringTokenizer tok = new StringTokenizer(m_hiddenLayers, ",");
    // 每層隱含層的節點數
    int val = 0;
    // 上一層隱含層的節點數
    int prev = 0;
    // 隱含層數
    int num = tok.countTokens();
    String c;
    // 對於每層隱含層
    for (int noa = 0; noa < num; noa++) {
      // 得到該層的節點分布信息
      c = tok.nextToken().trim();
      // 如果是“a”代表的信息,該層的節點數為所有屬性總數的一半(默認值)
      if (c.equals("a")) {
    val = (m_numAttributes + m_numClasses) / 2;
      }
      // 如果是“i”代表的信息,即輸入單元,該層的節點數為所有非類屬性個數
      else if (c.equals("i")) {
    val = m_numAttributes;
      }
      // 如果是“o”代表的信息,即輸出口,該層節點數為所有類屬性個數
      else if (c.equals("o")) {
    val = m_numClasses;
      }
      // 如果是“t”代表的信息,該層節點為所有屬性總數
      else if (c.equals("t")) {
    val = m_numAttributes + m_numClasses;
      }
      // 否則為分布信息為其他值,那么節點數為分布信息的字符串轉化成浮點數再取整所得到的數為該層節點數
      else {
    val = Double.valueOf(c).intValue();
      }
      // 對於當前隱含層的每個節點
      for (int nob = 0; nob < val; nob++) {
    // 新建節點
    NeuralNode temp = new NeuralNode(String.valueOf(m_nextId), m_random,m_sigmoidUnit);
    
    // 節點編號增加
    m_nextId++;
    
    // 計算節點坐標並添加節點
    temp.setX(.5 / (num) * noa + .25);
    temp.setY((nob + 1.0) / (val + 1));
    addNode(temp);
    
    // 如果不是第一層隱含層
    if (noa > 0) {
      
      //將當前隱含層中的每個節點與上一層相連
      for (int noc = m_neuralNodes.length - nob - 1 - prev;
           noc < m_neuralNodes.length - nob - 1; noc++) {
        NeuralConnection.connect(m_neuralNodes[noc], temp);
      }
    }
      }
   // 保存當前隱含層的節點數下次循環用    
      prev = val;
    }
    
    // 同上
    tok = new StringTokenizer(m_hiddenLayers, ",");
    // 這次沒有省去頭和尾,也就是說包含了所有的輸入單元和輸出單元
    c = tok.nextToken();
    if (c.equals("a")) {
      val = (m_numAttributes + m_numClasses) / 2;
    }
    else if (c.equals("i")) {
      val = m_numAttributes;
    }
    else if (c.equals("o")) {
      val = m_numClasses;
    }
    else if (c.equals("t")) {
      val = m_numAttributes + m_numClasses;
    }
    else {
      val = Double.valueOf(c).intValue();
    }
    
    // 如果沒有隱含層
    if (val == 0) {
      // 直接連接每個輸入單元與每個輸出單元
      for (int noa = 0; noa < m_numAttributes; noa++) {
    for (int nob = 0; nob < m_numClasses; nob++) {
      NeuralConnection.connect(m_inputs[noa], m_neuralNodes[nob]);
    }
      }
    }
    // 如果有隱含層
    else {
      // 就把輸入單元與第一層隱含層的每個單元連接
      for (int noa = 0; noa < m_numAttributes; noa++) {
    for (int nob = m_numClasses; nob < m_numClasses + val; nob++) {
      NeuralConnection.connect(m_inputs[noa], m_neuralNodes[nob]);
    }
    }
    
      // 把隱含層的最后一層的每個單元與輸出單元連接
      for (int noa = m_neuralNodes.length - prev; noa < m_neuralNodes.length;
       noa++) {
    for (int nob = 0; nob < m_numClasses; nob++) {
      NeuralConnection.connect(m_neuralNodes[noa], m_neuralNodes[nob]);
    }
      }
    }
    
  }
   // 計算某單元的輸出值
   public double outputValue(boolean calculate) {
      
      // 如果該單元之前沒有被計算過且已經做好了計算的准備
      if (Double.isNaN(m_unitValue) && calculate) {
    
        // 如果是輸入單元
    if (m_input) {
          
          // 如果沒有與其他單元相連接,輸出為0
      if (m_currentInstance.isMissing(m_link)) {
        m_unitValue = 0;
      }
      
          // 如果與其他單元相連接,輸出值為與之相連接的單元的值
      else {
        m_unitValue = m_currentInstance.value(m_link);
      }
    }
    
    // 如果不是輸出單元
    else {
      // 初始化輸出值
      m_unitValue = 0;
      // 輸出值為連接到此單元的輸出的加權和
      for (int noa = 0; noa < m_numInputs; noa++) {
        m_unitValue += m_inputList[noa].outputValue(true);
       
      }
      // 如果類屬性是數值屬性,且需要進行正常化,那么正常化到[-1,1]區間
      if (m_numeric && m_normalizeClass) {
        m_unitValue = m_unitValue * 
          m_attributeRanges[m_instances.classIndex()] + 
          m_attributeBases[m_instances.classIndex()];
      }
    }
      }
      return m_unitValue;
      
      
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM