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 记录对;具体类型为TreeMap, 每一个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 这个变量的具体应用,不知为什么,感觉也没必要。

 
相关变量如下:

     /** normalization: No normalization. */
    public static final int FILTER_NONE = 0;  
    /** normalization: Normalize all data. */
    public static final int FILTER_NORMALIZE_ALL = 1;
    /** normalization: Normalize test data only. */
    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 retrievalstemming 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

你可能感兴趣的:(WEKA学习)