天天看點

Weka學習 -- StringToWordVector 源碼學習(1)

代碼整個執行流程

  1. 參數設定
  2. input資料,設定資料格式
  3. batchFinished(),處理資料(Tokenzier,Stemming,Stopwords)
  4. determineDictionary();  統計計算(TF,IDF)
  5. 歸一化
  6. output

一些變量和方法的作用

  • m_Dictionary , m_DocsCounts  變量與 m_OutputCounts變量 意義

public TreeMap m_Dictionary = new TreeMap(); //TreeMap類型成員變量m_Dictionary 記錄<word,新屬性index>對;具體類型為TreeMap<String,Integer>, 每一個String 的word所映射的index。

private int[] m_DocsCounts ; // 計算每個單詞在多少個文檔中出現過,儲存在這個數組。數組index是word對應映射的index(與m_Dictionary對應)。 private boolean m_OutputCounts = false;   //控制m_Dictionary 中的Integer是輸出0/1(表示單詞是否在文檔中出現),還是count單詞在文檔中出現的次數。 一般要将它設定為true。

  • m_minTermFreq 和 m_WordsToKeep

     根據最小詞頻數( m_minTermFreq )和每個類最多保留單詞數( m_WordsToKeep )過濾單詞;另一種過濾單詞的方法是通過stopwordlist,見下面。

  • 内部類Count 類及變量的意義

裡有兩個變量public int count, docCount;,count是word在一個文檔中出現的次數,docCount指的是這個word在幾個文檔中出現過。 定義這個内部類及變量計算中間量,主要是為了計算TF*IDF友善。

  • 修改具體的TF*IDF公式:

在函數convertInstancewoDocNorm裡面如下的兩段代碼中修改即可。 // Doing TFTransform if (m_TFTransform == true) { .....//在這裡修改, 源代碼為 tf(t,d)= log( f(t,d) +1 ).加一是為了防止tf(t,d)=0

}

// Doing IDFTransform if (m_IDFTransform == true) { .....//在這裡修改, 源代碼是讓idf(t,D)= log( |D| / |{d \in D : t\ind}| ). D表示所有的文檔集。 log(value)中的value肯定大于等于1  //當然這裡需保證分母不為0,即word至少在一個文檔中出現過,否則可 idf(t,D)=   log ( |D| / |{d \in D : t\ind}|+1 ).  ....// 源代碼直接讓val (t,d)= tf(t,d) * log( |D| / |{d \in D : t\ind}| )   }

注意1:這裡還有一個boolean變量m_OutputCounts,若要用TFIDF公式必須将m_OutputCounts設定為TRUE. 同時,m_TFTransform 和m_IDFTransform 一般隻設定其中一個為true,否則的話就是兩個log相乘。 當然也可以根據需要具體修改(TFIDF具體資訊wiki即可) 經典的TF*IDF設定: 設定變量m_IDFTransform為真,更新更新contained中Key大于等于firstCopy的值為val=val*Math.log( m_NumInstances / (double) m_DocsCounts[index.intValue()] ),也即把原先記錄的詞頻fij變成fij*log(文檔數/該單詞在多少個文檔中出現過),就是我們用的TF-IDF。注意如果要達到這個效果隻有把m_IDFTransform 以及m_OutputCounts同時設定成true,并保持m_TFTransform為false(否則的話就是兩個log相乘了)。

注意2: TFIDF沒有展現單詞位置資訊 ,如在一段文字中,處在首句的在一篇文章中,處在首段和末段的段落比較重要。在實際應用中,可以根據不同的位置設定相應的權重。

  • Normalization

    對詞頻或TFIDF進行歸一化,主要是為了消除不同文本長度的影響。歸一化主要針對TF(t,d_單詞頻率(單詞t在文檔d中出現的次數)的來進行歸一化。 常用方法:

方法1:TF(t,d)=  (單詞t在文檔d中出現的次數)/ (文檔d中的總單詞數);  方法2:  TF(t,d)= (單詞t在文檔d中出現的次數)/ (在文檔d中出現次數最多的詞的出現次數)。 方法3(Weka采用): TFIDF(t,d) ,即 value = value * m_AvgDocLength / docLength(d).注意這裡的value是經過 m_TFTransform  和 m_TFTransform  作用過的值(如果設定為true的話,具體看代碼就可以知道)。

當然我們也可以修改StringToWordVector代碼,使其支援前兩種歸一化的方法。下面說下 Weka中相關設定方法: 方法1:通過set方法設定 filter .setNormalizeDocLength( new  SelectedTag(StringToWordVector. FILTER_NORMALIZE_ALL , StringToWordVector. TAGS_FILTER )); // FILTER_NORMALIZE_ALL 可以換位  FILTER_NORMALIZE_TEST_ONLY 或  FILTER_NONE 方法2: 通過參數字元串設定:       String optionStr= "-R first-last  -W  1000 -prune-rate -1.0 -C -I -N 1" ; //-N 1 表示采用 FILTER_NORMALIZE_ALL=1 歸一化方法。

      filter.setOptions(Utils.splitOptions(optionStr));  

注意:StringToWordVector類中到是沒有 FILTER_NORMALIZE_TEST_ONLY 這個變量的具體應用,不知為什麼,感覺也沒必要。

相關變量如下:

    public static final int FILTER_NONE = 0;  

         public static final int FILTER_NORMALIZE_ALL = 1;          public static final int FILTER_NORMALIZE_TEST_ONLY = 2;  

更多關于Normalize的通路: weka.filters.unsupervised.attribute.Normalize -S 1.0 -T 0.0 對于資料集中的attribute進行歸一化。即對某一列數值型資料進行歸一化。忽略nominal類型的列。

weka.filters.unsupervised.instance.Normalize 忽略nominal類型的列和class index的列。

  • Tokenzier
StringToWordVector中,預設的tokenzier。 -tokenizer  weka.core.tokenizers.WordTokenizer -delimiters " \r\n\t.,;:\'\"()?!"   Tokenzier的作用就是對于一個長的String,周遊掃描一遍,按那些字元進行切分。 若需要按句子為機關進行切分,可以建立新的Tokenzier,選擇- delimiters 為". ! \n ? "或中文句子結束符号“。!?”等
  • Stemmer

In linguistic morphology and information retrieval, stemming is the process for reducing inflected (or sometimes derived) words to their stem, base or rootform—generally a written word form.  如能将cats,catlike,catty等word都轉化為詞根cat。

這個Stemmmer更像是中文的同義詞轉換,同一個此類的詞包。

StringToWordVector裡面的Stemmer主要有兩種LovinsStemmer (writeren by Lovins 1968)和 SnowballStemmer(Written by Martin Porter1980,extend at 2000,建議用這個,但不可直接使用,需要下載下傳包。)兩類。這Weka裡面自帶的Stemmer都是針對英語語言的,自己可以根據需要進行更改。此外還有PTStemmer等,下載下傳相應的jar包都可以使用。具體使用參考http://weka.wikispaces.com/Stemmers

常用的Stemming算法: LookUp算法: 最簡單的算法,建一個lookup table,如 cats -> cat. 若查詢的詞是cats則傳回cat。特點:簡單易用,但table表量比較大。 如何建立LookUp table: ①對于英語而言可以用構詞法簡單生成look up table,如将root word “run” 生成“runs,running, runned, runnly,然後查詢詞典,将其中invalid derived words 去掉。②對于漢語的話,就相當于把同義詞轉換為同一個詞? 這樣也不太好,應該是把類型”吃了嗎”,“吃飯了嗎”,“吃飯了沒”轉化為同一個詞。

Affix算法: ①Suffix-stripping 算法: 根據單詞的時态規則将是時态詞轉換為詞根。 如将以ed,ing,ly結尾的詞去掉其字尾,生成詞根。動詞的時态大部分有規則,一部分沒規則,對于沒規則的可以建一個lookup表。二者相結合。

當然還有其他Hybird算法和Multilingual 多語言算法。

對于Stemming也會存在UnderStemming 和 OverStemming兩個問題,用時注意。

參考資料: http://en.wikipedia.org/wiki/Stemming  (recommending) http://weka.wikispaces.com/Stemmers

  • StopWords

這一個過程最好IKAnalyzer分詞階段就過濾掉。當然對于派生詞或中午近義詞等,在這裡進行過濾會stoplist會更短一點(因為word已經經過Stemming了)。

  • StringToWordVector預設不采用任何停止詞。
  • 設定采用預設停止詞rainbow(英文)m_useStoplist。

當然你可以通過設定變量 private boolean m_useStoplist;  等于true( filter.setUseStoplist( true );) ,采用Weka自帶的預設停止詞 weka.core.StopWords(英文)。  停止詞rainbow清單:  http://www.cs.cmu.edu/~mccallum/bow/rainbow/可參考,也可以在程式裡面檢視StopWords的    public   Stopwords ()方法 。

  • 自定義停止詞。覆寫預設停止詞File m_Stopwords

通過 StringToWordVector. setStopwords( new  File( stopwordfile ));方法可以設定自定義的停止詞,同時預設的停止詞不在生效。(知道這種效果就先用着,具體實作代碼待細看^_^)

  • Tokenizer、Stemming與 Stopwords的順序:

(從 determineDictionary()函數可以看出來)

  1. 首先Tokenizer,根據單詞分割符取出word。若預設的 WordTokenizer 采用" \r\n\t.,;:\'\"()?!"  等英文分割符。這一部會把"boy."等後面的‘.'句号或感歎号等都去掉。也可彌補IKAnalyzer中文分詞不足。
  2. 然後Stemming,取出詞根。
  3. 最後在輪到Stopwords。stopwords.is(word)做詞根word在stopword的list中,則取出。應該不會對此單詞進行map映射和在vector出現。filter.setStopwords(new File(stopwordfile));  這樣設定一下,就可以将stopwordfile中的word(一行一個)當成stopword啦。

  • SparseInstance

Sparse ARFF files are very similar to ARFF files, but data with value 0 are not be explicitly represented (but這句話的意思就是值為0的屬性并不顯示).

舉個例子StringToWordVector生成下面這個執行個體集,其中index為0(第一個屬性)為class屬性(類别在第一列)。:

{1 1,2 2,3 3,4 1,5 1,6 1,7 1} {0 class2,8 1,9 1,10 1,11 1,12 1} {0 class3,2 2,3 3,6 1,7 1,13 1}  

對于第1個執行個體(第一行)由于第一個類的名稱不是class1,而是預設生産值0,即 0 class1 生成了0 0,是以不顯示。下面的‘1 1’ 表示第2個字段的值為1; ‘2 2’ 表示第3個屬性字段的值為3. 哈哈 明白了嗎。 這裡若一個屬性的值是未知的missing。則應該表示為?。 如3 ?. Note that the omitted values in a sparse instance are 0, they are not "missing" values! If a value is unknown, you must explicitly represent it with a question mark (?).

在Weka裡面,所有的string和nominal屬性的資料值都會映射為數值,這樣做主要是為了計算的高效性。都會把第一個string或nominal的值存儲為0。  SparseInstances這裡這樣表示并不是一個bug,可以視為一個‘display’bug。你儲存arff資料會發現和你讀取的資料是一樣的。

  官方介紹: http://weka.wikispaces.com/ARFF+%28book+version%29#Sparse ARFF files

引用參考文獻: http://quweiprotoss.blog.163.com/blog/static/40882883201103051150347/

  • 其他
  1. StrngToWordVector預設隻對所有的屬性進行字元串轉word向量,你可以設定指定的屬性進行轉換。

Range m_SelectedRange 變量就是,可以在進行參數設定

  1. StringToWordVector會将所有的非轉換的屬性放在處理後的Instances的前面, 其中firstCopy 在函數中的意思是表明前面有多少個非處理的屬性。如有3個不用處理的屬性,firstCopy就是3.

其他參考資料:

若自己讀代碼有困難,可以參考: http://quweiprotoss.blog.163.com/blog/static/4088288320100164563922/

http://quweiprotoss.blog.163.com/blog/static/4088288320100165343974/

轉載請注明出處: http://blog.csdn.net/acema/article/details/38050839