注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Koala++'s blog

计算广告学 RTB

 
 
 

日志

 
 

Weka开发[49]——Weka中文文本分类示例  

2011-01-30 17:11:50|  分类: 机器学习 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

          时不时有人问我有中文的文本数据,怎么分类的问题。我以前给别人做过一个中文分类的程序,预处理部分是用wvtool写的,不过交易的时候,对方说我不能将有关的代码公布出来,所以我也就没有贴出来。现在我写一篇只用weka进行文本分类的例子。

         第零步,准备你需要的工具,weka.jarlucene-core.jarIKAnalyzer.jar,把它们加到工程中。分词包你喜欢用什么自己选,不必非用IKAnalyzer.jar

         第一步,你要有中文的数据集,如果你已经有了任务,自不必说。如果没有,那一定要选一个公认的最好,我以前是用搜狗的文本分类数据集,后来发现搜狗的数据好像也不怎么被人承认。看网上说,北京大学建立的人民日报语料库、清华大学建立的现代汉语语料库这两个数据集似乎比较正式点,但人民日报这个数据集我感觉实在不怎么样,并且它毕竟是人民日报呀,能不选就不选。现在汉语语料库找了两下没找到。谭松波先生的数据集要一个声明,懒得写。感觉最方便的还是复旦的一个数据集:http://www.nlp.org.cn/docs/20030623/25/tc-corpus-train.rar。这个数据集我感觉不好的一点是它不是从同一个源上找的。

比如在Art类别中,它的文档是下面这种(有删除):

文献号 1-2340

【原文出处】中国图书评论

【原刊地名】沈阳

【原刊期号】199510

【原刊页号】61-62

【分 号】Z1

【分 名】出版工作、图书评介

  】杨小民

【复印期号】199602

  】图书评论应当重视对书籍装帧艺术的评价

 

    图书评论是近代报刊业兴起后,在世界各国得到长足发展的一种新型评论体裁。而不论是书评理论还是书评实践都有一个不小的疏漏,即忽

视了图书的形式因素。因为图书是内容与形式的综合体,忽视了“图书形式”这一重要方面,会导致在图书评论活动中忽视对图书的出版形式这

一重要方面的品评论述,而这对于出版物的达到基本要求:“形神俱佳”(“形”指书装艺术,“神”指内容叙述)或最高要求“尽善尽美”(

“尽善”指内容而言,“尽美”指形式而言)无疑是有缺憾的。

 // Koala++省略

                                    (本文责任编辑  韩忠良)*

         而在Philosophy类别中有下面的这种:

发信人: warmoon (宁静致远), 信区: philosophy

  : 哲学的用处

发信站: 日月光华站 (Thu Apr 22 08:10:28 1999),  WWW-POST@134.68.57.63

             哲学的用处 (季羡林)

我曾在很多文章中说到过自己的一个偏见:我最害怕哲学和哲学家,有一千个哲学家

,就有一千种哲学,有的哲学家竟沦为修辞学家。我怀疑,这样的哲学究竟有什么用

处。

// Koala++省略

 

--

  我的灵魂象被裹在蚕茧里的蛹,在黑暗中痛苦与挣扎,几乎被茧壳窒息。但

窒息我的茧壳却给予我充足的养料,让我的灵魂逐渐成长,壮大,使它有一天终

于有力量挣脱躯壳,自由飞舞在人世间

Who said night is always cold?

--

         是的,如果借助这些特殊的信息,比如我发现有日月光华站”(假设它是一个词),那它就是属于哲学类别的,但这明显是不对的嘛。如果要用这个数据集,我认为应该自己写程序去把无关的信息去掉,比如这些头信息,还有尾部的本文责任编辑,个人签名。

         第二步,数据集要准备成weka能处理的结构,这很好做到,你把数据集压缩了就行了,因为它要求的格式是,一个类别的文件放一个文件夹下(你可以参考我weka[48])。但是还有一个问题,你的机器往往没那么多内存去处理这个数据集,那么你可以选几个类别出来,在每个类别中放几十个文档来做就可以了。

         第三步,分词,在wvtool里你可以继承它的分词类,使用自己的逻辑,weka也是可以的。但是最方便的还是直接分词。

         我的做法很简单,把源文件夹下的文件全部分好词,再保存到另一个文件中,下面是我的实现代码(呵呵,见笑了,其实我不懂java)

package preprocess;

 

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.File;

import java.io.FileReader;

import java.io.FileWriter;

import java.io.IOException;

 

import org.apache.lucene.analysis.Analyzer;

import org.apache.lucene.analysis.TokenStream;

import org.apache.lucene.analysis.tokenattributes.TermAttribute;

import org.wltea.analyzer.lucene.IKAnalyzer;

 

public class Segmenter {

    private String sourceDir;

    private String targetDir;

   

    Segmenter( String source, String target ) {

       sourceDir = source;

       targetDir = target;

    }

   

    public void segment() {

       segmentDir( sourceDir, targetDir );

    }

   

    public void segmentDir( String source, String target ) {

       File[] file = (new File( source )).listFiles();

       for (int i = 0; i < file.length; i++) {

           if (file[i].isFile()) {

              segmentFile( file[i].getAbsolutePath(), target +

File.separator + file[i].getName() );

           }

           if (file[i].isDirectory()) {

              String _sourceDir = source + File.separator +

file[i].getName();

              String _targetDir = target + File.separator +

file[i].getName();

              (new File(_targetDir)).mkdirs();

              segmentDir( _sourceDir, _targetDir );

           }

       }

    }

   

    public void segmentFile( String sourceFile, String targetFile ) {

       try {

           FileReader fr = new FileReader( sourceFile );

           BufferedReader br = new BufferedReader(fr);

   

           FileWriter fw = new FileWriter( targetFile );

           BufferedWriter bw = new BufferedWriter(fw );

          

           Analyzer analyzer = new IKAnalyzer(); 

           TokenStream tokenStream = analyzer.tokenStream("", br );

           TermAttribute termAtt = (TermAttribute) tokenStream

                  .getAttribute(TermAttribute.class);

 

           while (tokenStream.incrementToken()) {

              bw.write( termAtt.term() );

              bw.write(' ');

           }

 

           bw.close();

           fw.close();

       } catch( IOException e ) {

           e.printStackTrace();

       }

    }

   

    public static void main( String[] args ) throws Exception {

       Segmenter segmenter = new Segmenter( "train", "train_segmented" );

       segmenter.segment();

    }

}      

第四步,把TextDirectoryLoader复制一份到自己的包中,weka本身的实现在字符集方面似乎有点问题,但我又没兴趣去理解(总是有更值得去了解的事嘛),以下做法仅供参考:把下面几行代码注释掉:

/*int c;

while ((c = is.read()) != -1) {

    txtStr.append((char) c);

}*/

         在它下面添加几行代码:

FileReader fr = new FileReader( txt );

BufferedReader br = new BufferedReader(fr);

String line;

while( (line = br.readLine()) != null ) {

    txtStr.append( line + "\n" );

}

         第五步,使用weka wiki中的例子将数据集转换成arff格式:

package preprocess;

 

import java.io.BufferedWriter;

import java.io.File;

import java.io.FileWriter;

import java.util.Random;

 

import weka.classifiers.Evaluation;

import weka.classifiers.bayes.NaiveBayes;

import weka.core.Instances;

import weka.core.stemmers.NullStemmer;

import weka.filters.Filter;

import weka.filters.unsupervised.attribute.StringToWordVector;

 

public class TextCategorizationTest {

  public static void main(String[] args) throws Exception {

      String filename = "D:\\workspace\\text_mining\\test"; 

     

    // convert the directory into a dataset

    TextDirectoryLoader loader = new TextDirectoryLoader();

    loader.setDirectory(new File( filename ));

    Instances dataRaw = loader.getDataSet();

    //System.out.println("\n\nImported data:\n\n" + dataRaw);

    {

    FileWriter fw = new FileWriter( "dataRaw.arff" );

       BufferedWriter bw = new BufferedWriter(fw );

      

       bw.write( dataRaw.toString() );

      

       bw.close();

       fw.close();

    }

 

    StringToWordVector filter = new StringToWordVector();

    filter.setStemmer( new NullStemmer() );

    filter.setInputFormat(dataRaw);

    Instances dataFiltered = Filter.useFilter(dataRaw, filter);

    {

       FileWriter fw = new FileWriter( "dataFiltered.arff" );

       BufferedWriter bw = new BufferedWriter(fw );

      

       bw.write( dataFiltered.toString() );

      

       bw.close();

       fw.close();

    }

    //System.out.println("\n\nFiltered data:\n\n" + dataFiltered);

    dataFiltered.setClassIndex( 0 );

 

    // train J48 and output model

    NaiveBayes classifier = new NaiveBayes();

    /*classifier.buildClassifier(dataFiltered);

    System.out.println("\n\nClassifier model:\n\n" + classifier);*/

    Evaluation eval = new Evaluation(dataFiltered);

    eval.crossValidateModel(classifier, dataFiltered, 3, new Random(1));

    System.out.println(eval.toClassDetailsString());

    System.out.println(eval.toSummaryString());

    System.out.println(eval.toMatrixString());

  }

}

         唯一要提醒的是TextDirectoryLoader这个类当然是要用第四步修改的类,不然出来的全是乱码。你可以把dawDatadataFilted打印出来看一下。

         最后我想解释一下产生的arff文件,它的类别在第一列,别搞错了。有的weka使用者可能以前没有见过压缩格式的arff,我举一个压缩格式的例子:{1 1,6 1,7 1,12 1,13 1},它表示第2个字段值为1,第7个字段值为1,第8个字段值为1,第13个字段值为1,第14个字段值为1。如果你用文本编辑器打开最后产生的arff文件,你可能会糊涂,怎么搞的,第一个类别没有?其实是第一个类别它的离散值就是0,所以不显示。别激动,呵呵。

 

  评论这张
 
阅读(16907)| 评论(16)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017