關於weka中Instances的屬性刪除


最近這些天一直在用weka實現一個算法,也是從這次開始接觸weka,剛上手難免有些磕磕絆絆,這次實現也是遇到了各種各樣的問題,其中的一個就 和weka中的Instances有關,剛剛把程序跑起來了,因為數據比較多,沒有個一天兩天估計是跑不完了,趁這個空閑時間,把我遇到的問題及解決方法 記錄下來,一是這樣可能會幫助到其他人,還有就是也方便我自己以后查閱,畢竟俗話說得好,爛筆頭勝過好記性。

首先,weka是從事數據挖掘相關研究的人一定會接觸到的一個軟件,這個軟件由新西蘭waikato大學編寫,里面實現了種類繁多的算法,這里就不一一介紹,相信用過weka的人都知道我在說什么。

這篇隨筆將會從三個具體的和屬性選擇相關的實際問題出發,給出具體的解決辦法及代碼。在討論實際問題之前,先來說說為什么需要屬性刪除,也就是需求的問題。

 

一、為什么需要屬性刪除

我們知道,在做交叉驗證的時候,在訓練集上做過什么處理,這些處理必須原封不動地施加到測試集上,這樣分類得到的結果才是可信的。

大 數據時代已經來臨,我們經常會遇到那種實例多,維度高(屬性多)的大數據,處理這樣的數據時,從效果和效率兩方面來考慮,我們都需要先對數據進行預處理, 最常見的一種預處理的方式就是屬性選擇,也就是我現在一直在做的東西,所謂屬性選擇,從字面上理解,就是從高維的數據中選擇出一部分的屬性,用這部分屬性 代替屬性總體,這樣一來,很顯然能夠減少數據處理的時間,另外,如果算法選擇得當,我們能得到更好的結果,比如說,分類的精度更高。

如果我 們用屬性選擇后的訓練集構建了一個分類器,我們怎么測試這個分類器的好壞呢,還是要靠實驗說話,在測試集上測試這個分類器的精度,前面說過,在分類的時候 在訓練集上的處理必須原封不動的施加到測試集上,訓練集上屬性選擇了,測試集也要屬性選擇,並且選出來的屬性必須和訓練集上選出來的屬性完全一致,為了得 到屬性選擇后的測試集,顯然,除了訓練集上選擇出來的屬性,測試集上的其他屬性都要刪除。

 

二、三個具體的問題

為了更好的描述這幾個問題,我們定義一些變量,這些變量在三個問題中通用。

train:訓練集   test:測試集
trainInstance:在訓練集上屬性選擇之后返回的訓練集 testInstances:依據trainInstances在test上刪除相應的屬性返回的測試集
trainIndex:在訓練集上屬性選擇之后返回的訓練集的下標數組

1.屬性選擇算法返回的是trainInstances,這個時候,為了得到testInstances,可以使用下面這段代碼

public static Instances delAttr(Instances model, Instances origin){
        boolean flag = false;
        ArrayList<Integer> al = new ArrayList<Integer>();
        for(int q = 0; q < origin.numAttributes() - 1; q++){
            String temp2 = origin.attribute(q).name();
            for(int x = 0; x < model.numAttributes() - 1; x++){
                String temp1 = model.attribute(x).name();  
                if(temp1.equals(temp2)){
                    flag = true;
                    break;
                }
            }
            if(flag)
            {
                flag = false;
                continue;
            }
            else
//                dataCopy.deleteAttributeAt(q);   //you can not do like this
                al.add(new Integer(q));
        }
        
        for(int q = 0; q < al.size(); q++){
            int deltemp = al.get(q) - q;        // pay attention to this line
            origin.deleteAttributeAt(deltemp);
        }

        return origin;
    }

model相當於trainInstances,origin相當於test,返回的origin相當於testInstances.

上面這段代碼的思想無非就是通過屬性的名字把需要刪除的屬性的下標全部存放在一個ArrayList的數據結構中,然后根據這個ArrayList刪除對應的屬性,返回新構造的Instances.

注意上面我寫的兩行注釋,因為你刪除一條屬性之后,排在這條屬性之后的所有屬性的index都是會變化的(-1),這就相當於你從String刪除一個字符一樣,知道這一點,你就知道為什么第二條注釋對應的刪除方法可以正常工作了。

注意:如果你用的數據集中有兩個或多個屬性的名字完全一樣,這種方法會把擁有這個名字的所有屬性都保留,但是,有可能你真正需要的只是其中一個。

 

2.直接使用1返回的結果交給分類器,往往會報錯,其中的原因有點微妙,解釋如下:

假 設train有四個屬性,分別是{attr1,attr2,attr3,attr4},attr4是類標簽,test也是這樣 在train上屬性選擇,返回trainInstances{attr3,attr2,attr4},把trainInstances和test交給1, 返回的是testInstances{attr2,attr3,attr4},把testInstances交給分類器,報錯,報錯的原因是 trainInstances和testInstances的屬性錯位了.

這種情況下,我們需要交換兩個屬性的位置,下面這段代碼解決了這個問題

public static Instances sort(Instances model, Instances process){
      Attribute attr;
      for(int i = 0; i < model.numAttributes()-1; i++){
          for(int j = 0; j < process.numAttributes()-1; j++){
              if(process.attribute(j).name().equals(model.attribute(i).name())){
                  if(j!=i){
                     attr = process.attribute(j);
                     process.insertAttributeAt(attr, i);
                     for(int k = 0; k < process.numInstances(); k++){
                         process.instance(k).setValue(i, process.instance(k).stringValue(j+1));    //pay attention to j+1
                     }
                     break;
                  }
              }
          }
      }
      for(int i = process.numAttributes() - 2; i > model.numAttributes()-2; i--){
          process.deleteAttributeAt(i);;
      }
      return process;
  }

 

model相當於trainInstances,process相當於1中返回的testInstances,2中返回的process就是能和trainInstances的每個屬性對應起來的testInstances。

這段代碼的思想是找到 process中每個屬性在model中的對應位置,然后再process的對應位置插入這個屬性,插入屬性的時候需要把屬性值復制過去。

注意因為我用到的數據都是nominal類型的,所以在復制屬性值的時候選的是stringValue這個方法,如果是numeric或者其他的類型需要選擇對應的方法,這個在weka的Instance類中可以找到。

 

3.屬性選擇算法返回的是下標數組trainIndex

3和1的不同之處在於省去了自己用屬性名去匹配的這個步驟,也因此,即使數據集中有多個屬性的名字完全一致,也沒有影響,因為返回的是下標。

這種情況可以用下面的代碼:

public static Instances delAttr(ArrayList<Integer> al, Instances inst){
        for(int i = inst.numAttributes() - 1; i > -1; i--){    //delete from back to forward
            if(!al.contains(i))
                inst.deleteAttributeAt(i);
        }
        
        return inst;
    }

 

注意:這段代碼我是從后往前刪的,這種刪除方法不會出現問題,因為每次刪除的都是需要刪除的屬性的最后一個,對前面需要刪除的屬性的下標不會有任何影響。

注意:拿到屬性下標之后,先看看是不是排序過,沒有排序的話,最好先排序一下,按從小到大的順序排,這樣雖然需要花費一點額外的時間,有時卻能避免意想不到的錯誤,具體原因我說不清楚,但是我遇到過。

 

三、總結

很顯然,第三種情況最簡單,代碼清晰,花費的時間也相對較少,所以在屬性選擇之后能夠直接拿到下標數組最好,否則將拿到的結果轉化成下標數組也可以。

 

 


免責聲明!

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



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