Weka学习之Filter(2)-StringToWordVector

为了更具体地展示Filter的用法和原理,我们分析一个名为StringToWordVector的Filter。它是我们在文本挖掘中用得比较普遍的一个类。作用是把字符串属性转换成一个个词属性,属性的值可以在参数中指定,比如0-1变量(代表这个词是否在该实例中出现),词频变量,log(1+词频)或者TF-IDF值。

下面是StringToWordVector的input方法源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Input an instance for filtering. Filter requires all
* training instances be read before producing output.
*
* @param instance the input instance.
* @return true if the filtered instance may now be
* collected with output().
* @throws IllegalStateException if no input structure has been defined.
*/
public boolean input(Instance instance) throws Exception {
 
if (getInputFormat() == null) {
throw new IllegalStateException("No input instance format defined");
}
if (m_NewBatch) {
resetQueue();
m_NewBatch = false;
}
if (isFirstBatchDone()) {
FastVector fv = new FastVector();
int firstCopy = convertInstancewoDocNorm(instance, fv);
Instance inst = (Instance)fv.elementAt(0);
if (m_filterType != FILTER_NONE) {
normalizeInstance(inst, firstCopy);
}
push(inst);
return true;
} else {
bufferInput(instance);
return false;
}
}

这个方法支持增量输入数据实例,对于首批的数据首先执行的是bufferInput方法。这个方法就是把实例instance加入到inputFormat的数据集dataset中。所有的instance添加完毕后,我们就开始进入到batchFinished方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
   * Signify that this batch of input to the filter is finished.
   * If the filter requires all instances prior to filtering,
   * output() may now be called to retrieve the filtered instances.
   *
   * @return true if there are instances pending output.
   * @throws IllegalStateException if no input structure has been defined.
   */
  public boolean batchFinished() throws Exception {
 
    if (getInputFormat() == null) {
      throw new IllegalStateException("No input instance format defined");
    }
 
    // We only need to do something in this method
    // if the first batch hasn't been processed. Otherwise
    // input() has already done all the work.
    if (!isFirstBatchDone()) {
 
      // Determine the dictionary from the first batch (training data)
      determineDictionary();
 
      // Convert all instances w/o normalization
      FastVector fv = new FastVector();
      int firstCopy=0;
      for(int i=0; i < m_NumInstances; i++) {
	firstCopy = convertInstancewoDocNorm(getInputFormat().instance(i), fv);
      }
 
      // Need to compute average document length if necessary
      if (m_filterType != FILTER_NONE) {
	m_AvgDocLength = 0;
	for(int i=0; i < fv.size(); i++) {
	  Instance inst = (Instance) fv.elementAt(i);
	  double docLength = 0;
	  for(int j=0; j < inst.numValues(); j++) { 	    if(inst.index(j)>=firstCopy) {
	      docLength += inst.valueSparse(j) * inst.valueSparse(j);
	    }
	  }
	  m_AvgDocLength += Math.sqrt(docLength);
	}
	m_AvgDocLength /= m_NumInstances;
      }
 
      // Perform normalization if necessary.
      if (m_filterType == FILTER_NORMALIZE_ALL) {
	for(int i=0; i < fv.size(); i++) {
	  normalizeInstance((Instance) fv.elementAt(i), firstCopy);
	}
      }
 
      // Push all instances into the output queue
      for(int i=0; i < fv.size(); i++) {
	push((Instance) fv.elementAt(i));
      }
    }
 
    // Flush the input
    flushInput();
 
    m_NewBatch = true;
    m_FirstBatchDone = true;
    return (numPendingOutput() != 0);
  }

注意到我们的determineDictionary()方法,这个方法的主要作用是:

1.确认停用词表;

2.对那些需要进行转换的字符串型属性值,按给定的tokenizer进行分词,记录单词对应的类词频和类文件数(即属于该类的文件中有多少文件包含这个单词的);

3.根据最小词频数(m_minTermFreq)和每个类最多保留单词数(m_WordsToKeep)过滤单词;

4.收集未转换属性作为新属性;

5.把第二步中符合条件的单词收集起来作为新属性;

6.计算每个单词在多少个文档中出现过,保存在m_DocsCounts数组中;

7.TreeMap类型成员变量m_Dictionary记录对;

8.设置outputFormat的新属性的结构体。

24-26行对每个实例调用convertInstancewoDocNorm(Instance instance, FastVector v)方法。该方法进行以下操作:

1.记录所有未参加转换的非0属性值到<新属性index,属性值> –> contained[a TreeMap type],变量firstCopy=未参加转换的属性个数+1;

2.对所有参见转换的属性值,

1)tokenize;

2)转换成小写,去词根;

3)把<新属性index, 词频[或者0-1变量,用于表征单词是否出现,如果设置变量m_OutputCounts==false的话]>—>contained,这个词频值在本次迭代中完成统计。

3.如果设置变量m_TFTransform为真,更新contained中Key大于等于firstCopy的值为val = Math.log(val+1),也即把原先记录的词频fij变成log(fij + 1),注意如果要达到这个效果只有把m_TFTransform 以及m_OutputCounts同时设置成true

4.如果设置变量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相乘了)。

5.把上面搜集的新属性的对应index和值,也就contained转换成values和indices数组,生成一个SparseInstance,添加到vector中,返回firstCopy。

然后convertInstancewoDocNorm迭代完成。firstCopy记录了第一个转换得到的新属性的index,而fv中包括了所有的已经转换完毕的SparseInstance。

29行开始判断是否进行对文本长度进行归一。最后设置m_NewBatchm_FirstBatchDone为真,并把所有转换完毕的SparseInstance加入到一个队列中,返回该队列长度。

batchFinished结束。

我们再回到第一个调用batchFinished的地方,即静态函数useFiler中,这里简单地把SparseInstance实例加入到outputFormat的dataset中。然后就返回这个dataset。

你可能感兴趣的:(Weka学习之Filter(2)-StringToWordVector)