从原始文档到KNN分类算法实现(二)

1.复旦语料库train集中有6个类是多于1000个文档的,把它们取出来,分别删减至1000个文档,构成接下来我们要进行实验的训练集。
2.执行Predeal.java,它依次进行三项操作:把文件重命名为顺序的数字编号;把文件编号(即文件名)写入文档开头,用一个空格与正文内容隔开,去除正文每行末的换行符,把整个文档合并为一行,去除正文行间的空格;用ICTCLAS进行中文分词.由于一次运行对6000个文件进行分词程序会中断退出。所以只好一次只处理1000个文件。
这里去除停用词的方法我多说一句,程序中的代码是if (word.length()>=2 && (!Predeal.stopwords.contains(word))),即我们不统计单个的数字、字母和汉字,也可避免把空格统计进去,所以用停用词表中全部是长度大于1的词语,并且根据语料库的情况多加了一些词,比如“期号、出处、原刊、原文、正文、标题”等。

View Code
  1 /**
  2  * Author: Orisun
  3  * Date: Sep 2, 2011
  4  * FileName: Predeal.java
  5  * Function: 去除每行末的换行符,把整个文档合并为一行,去除行间的空格;用ICTCLAS进行中文分词;把文件重命名为顺序的数字编号,把该编号写入文档开头,用空格与正文内容隔开。
  6  */
  7 
  8 import java.io.BufferedReader;
  9 import java.io.BufferedWriter;
 10 import java.io.File;
 11 import java.io.FileReader;
 12 import java.io.FileWriter;
 13 import java.io.IOException;
 14 import java.util.HashSet;
 15 
 16 import ICTCLAS.I3S.AC.ICTCLAS50;
 17 
 18 public class Predeal {
 19     //文档顺序编号
 20     public static int seqnum=0;
 21     // 停用词集合
 22     public static HashSet<String> stopwords = new HashSet<String>();
 23 
 24     // 初始化停用词表
 25     public static void init_SW(String filePath) {
 26         File swFile = new File(filePath);
 27         if(!swFile.exists()){
 28             System.out.println("文件不存在,程序退出.");
 29             System.exit(2);
 30         }
 31         try {
 32             FileReader fr = new FileReader(swFile);
 33             BufferedReader br = new BufferedReader(fr);
 34             String word = null;
 35             while ((word = br.readLine()) != null) {
 36                 stopwords.add(word);
 37             }
 38             br.close();
 39         } catch (IOException exp) {
 40             System.out.println("读取停用词时发生异常:" + exp.getMessage());
 41         }
 42     }
 43     
 44     //把文件重命名为顺序的数字编号
 45     public void renameDoc(File srcFile){
 46         if(srcFile.isDirectory()){
 47             File[] childFiles=srcFile.listFiles();
 48             for(File child:childFiles){
 49                 renameDoc(child);
 50             }
 51         }
 52         else if(srcFile.isFile()){
 53             String newname=String.valueOf(seqnum);            
 54             if(!srcFile.renameTo(new File(srcFile.getParent(),newname))){
 55                 System.out.println("文件重命名失败,程序退出。");
 56                 System.exit(2);
 57             }
 58             seqnum++;
 59         }
 60     }
 61     //把文件编号(即文件名)写入文档开头,用一个空格与正文内容隔开。去除正文每行末的换行符,把整个文档合并为一行,去除正文行间的空格.
 62     public void delWrapAndSpace(File srcFile){
 63         if(srcFile.isDirectory()){
 64             File[] childFiles=srcFile.listFiles();
 65             for(File child:childFiles){
 66                 delWrapAndSpace(child);
 67             }
 68         }
 69         else if(srcFile.isFile()){
 70             String filename=srcFile.getName();
 71             //文档开头写入文件名加空格
 72             StringBuffer content=new StringBuffer(filename+" ");
 73             try{
 74                 FileReader fr=new FileReader(srcFile);
 75                 BufferedReader br=new BufferedReader(fr);
 76                 String line;
 77                 while((line=br.readLine())!=null){        //readLine()并不读取末尾的换行符
 78                     content.append(line.replaceAll("\\s+",""));        //StringBuffer会自动扩容.同时去除行间的空格
 79                 }
 80                 br.close();
 81                 FileWriter fw=new FileWriter(srcFile);
 82                 BufferedWriter bw=new BufferedWriter(fw);
 83                 bw.write(content.toString());
 84                 bw.flush();
 85                 bw.close();
 86             }catch (Exception e){
 87                 e.printStackTrace();
 88             }
 89         }
 90     }
 91 
 92     //用ICTCLAS进行中文分词.一次运行对6000个文件进行分词程序会中断退出。所以只好一次只处理1000个文件。
 93     public void wordSeg(File srcFile) {
 94         if (srcFile.isDirectory()) {
 95             File[] childFiles = srcFile.listFiles();
 96             for (File child : childFiles) {
 97                 wordSeg(child);
 98             }
 99         } else if (srcFile.isFile()) {
100             try {
101                 ICTCLAS50 ictc = new ICTCLAS50();
102                  // 指定Configure.xml和Data directory的存储位置
103                 String argu = "/home/orisun/master/ICTCLAS50_Linux_RHAS_32_JNI/API";
104                 if (ictc.ICTCLAS_Init(argu.getBytes("GB2312")) == false) { // UTF-8
105                     System.out.println("Init Fail!");
106                     return;
107                 }
108                 // 这里的参数“3”指定了待分词的文件是UTF-8编码
109                 if (ictc.ICTCLAS_FileProcess(srcFile.getAbsolutePath()
110                         .getBytes(), 3, 0, srcFile.getAbsolutePath().getBytes()) == false) {
111                     System.out.println(srcFile + "文件分词失败.");
112                     return;
113                 }
114                 ictc.ICTCLAS_Exit(); // 释放分词组件资源
115             } catch (Exception e) {
116                 e.printStackTrace();
117             }
118         }
119     }
120     
121     public static void main(String[] args){
122         Predeal predeal=new Predeal();
123         //训练文本集
124         File train=new File("/home/orisun/train");
125         if(!train.exists()){
126             System.out.println("文件不存在,程序退出.");
127             System.exit(2);
128         }
129         predeal.renameDoc(train);
130         predeal.delWrapAndSpace(train);
131         predeal.wordSeg(new File(train,"C19"));    
132         predeal.wordSeg(new File(train,"C31"));
133         predeal.wordSeg(new File(train,"C32"));
134         predeal.wordSeg(new File(train,"C33"));
135         predeal.wordSeg(new File(train,"C38"));
136         predeal.wordSeg(new File(train,"C39"));
137     }
138 }

3.WordDocMatrix.java使用Hadoop的MapReduce技术,生成word-doc矩阵。在map阶段进行一项预处理--去除停用词,并报告一条“单词--文件名"。在reduce阶段统计每个单词在各个文档中出现的次数,当单词在至少4个文档中出现时把它记入文件matrix/part-r-0000.
由于采用了hadoop技术不能在Eclipse里面直接运行,具体方法是:
(1)进行工程的bin文件夹
    cd ~/workspace/TextClassify/bin
(2)对所有class文件进行打包
    jar -cvf ~/WordDocMatrix.jar *.class
(3)把用户根目录下的matrix文件删除,把~/train下面的6个文件夹下面的所有文本文件移动到~/train下,删除6个分类文件夹。
(4)回到用户根目录,使用hadoop运行jar包
    cd ~
    hadoop jar WordDocMatrix.jar WordDocMatrix
由于是在单机上采用standalone模式运行hadoop,所以执行时间特别长,大约用了310分钟(1G内存),生成的part-r-0000文件有44222行,这也就是说在训练集中去除食用词后有44222个单词至少在4篇文本中出现过,接下来我们要计算这44222个单词的增益值,并对其进行排序。
当然不采用hadoop行动时间肯定会大大缩短,但在实际中由于word-doc矩阵非常大,程序运行一两分钟后就内存溢出了。

View Code
 1 /**
 2  * Author: Orisun 
 3  * Date: Sep 2, 2011 
 4  * FileName: WordDocMatrix.java 
 5  * Function: 使用Hadoop的MapReduce技术,生成word-doc矩阵。在map阶段进行一项预处理--去除停用词,并报告一条“单词--文件名"。在reduce阶段统计每个单词在各个文档中出现的次数,当单词在至少4个文档中出现时把它记入文件。
 6  */
 7 import java.io.IOException;
 8 import java.util.ArrayList;
 9 import java.util.HashSet;
10 
11 import org.apache.hadoop.fs.Path;
12 import org.apache.hadoop.io.LongWritable;
13 import org.apache.hadoop.io.Text;
14 import org.apache.hadoop.mapreduce.Job;
15 import org.apache.hadoop.mapreduce.Mapper;
16 import org.apache.hadoop.mapreduce.Reducer;
17 import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
18 import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
19 
20 public class WordDocMatrix {
21 
22     public static int docnum = 6000; // 训练文档总数
23     public static int clanum = 6; // 训练分类总数
24 
25     static class WordDocMatrixMapper extends
26             Mapper<LongWritable, Text, Text, Text> {
27         public void map(LongWritable key, Text value, Context context)
28                 throws IOException, InterruptedException {
29             // 经过Predeal后,一行就是一个文档的全部内容
30             String line = value.toString();
31             String[] words = line.split("\\s+");
32             String fname = words[0];
33             for (int i = 1; i < words.length; i++) {
34                 String word = words[i];
35                 //不统计单个的数字、字母和汉字,也可避免把空格统计进去
36                 if (word.length()>=2 && (!Predeal.stopwords.contains(word))) { // 去除停用词
37                     context.write(new Text(word), new Text(fname));
38                 }
39             }
40         }
41     }
42 
43     static class WordDocMatrixReducer extends Reducer<Text, Text, Text, Text> {
44         public void reduce(Text key, Iterable<Text> files,Context context) throws IOException, InterruptedException {
45             ArrayList<Integer> al = new ArrayList<Integer>(docnum);
46             for (int i = 0; i < docnum; i++)
47                 al.add(0);
48             HashSet<Integer> set=new HashSet<Integer>();
49             boolean flag=false;
50             for (Text file : files) {
51                 int index = Integer.parseInt(file.toString());
52                 int ori = al.get(index);
53                 al.set(index, ori + 1);
54                 if(!flag){
55                     set.add(index);
56                     if(set.size()>3){
57                         flag=true;
58                     }
59                 }
60             }        
61             if(flag){
62                 StringBuffer count = new StringBuffer();
63                 for(int i=0;i<al.size();i++){
64                     count.append(al.get(i)+" ");
65                 }
66                 context.write(key, new Text(count.toString()));
67             }
68         }
69     }
70 
71     public static void main(String[] args) throws Exception {
72         Predeal.init_SW("/home/orisun/master/StopWordTable_ZH");
73         Path inputPath=new Path("/home/orisun/train");
74         Path outputPath=new Path("/home/orisun/matrix");
75         Job job = new Job();
76         job.setJarByClass(WordDocMatrix.class);
77         FileInputFormat.addInputPath(job, inputPath);
78         FileOutputFormat.setOutputPath(job, outputPath);
79         job.setMapperClass(WordDocMatrixMapper.class);
80         job.setReducerClass(WordDocMatrixReducer.class);
81         job.setOutputKeyClass(Text.class);
82         job.setOutputValueClass(Text.class);
83         System.exit(job.waitForCompletion(true) ? 0 : 1);
84     }
85 }

4.运行FeatureSelect.java,计算每个单词的信息增益值,选择IG大的作为特征项。如果选用HashMap来存储单词及它对应的IG值,很快会内存溢出,所以我选择使用BerkelyDB来存储。
BerkelyDB是一个嵌入式数据库,它适合于管理海量的、简单的数据。Key/Value是BerkelyDB进行数据管理的基础。BerkelyDB底层实现采用B树,可以看成能够存储海量数据的HashMap。
程序计算了44222个单词的IG值,并把IG最大的300个特征项输出到文件,总共也只用了几分钟,确实挺快的!

View Code
  1 /**
  2  * Author: Orisun
  3  * Date: Sep 3, 2011
  4  * FileName: FeatureSelect.java
  5  * Function: 读取word-doc矩阵,计算每个词的信息增益值,排序。输出IG最大的前300个特征项。
  6  */
  7 
  8 import java.io.BufferedReader;
  9 import java.io.BufferedWriter;
 10 import java.io.File;
 11 import java.io.FileNotFoundException;
 12 import java.io.FileReader;
 13 import java.io.FileWriter;
 14 import java.util.ArrayList;
 15 import java.util.Collections;
 16 import java.util.Comparator;
 17 import java.util.Iterator;
 18 import java.util.Map;
 19 import java.util.Map.Entry;
 20 
 21 import com.sleepycat.bind.EntryBinding;
 22 import com.sleepycat.bind.serial.SerialBinding;
 23 import com.sleepycat.bind.serial.StoredClassCatalog;
 24 import com.sleepycat.collections.StoredMap;
 25 import com.sleepycat.je.Database;
 26 import com.sleepycat.je.DatabaseConfig;
 27 import com.sleepycat.je.DatabaseException;
 28 import com.sleepycat.je.Environment;
 29 import com.sleepycat.je.EnvironmentConfig;
 30 
 31 public class FeatureSelect {
 32     private Environment env;
 33     protected Database database; // 用来存放url队列的数据库
 34     protected Database catalogdatabase; // 用来创建StoredClassCatalog实例的数据库
 35     private static final String CLASS_CATALOG = "java_class_catalog"; // catalogdatabase的数据库名
 36     protected StoredClassCatalog javaCatalog; // StoredClassCatalog实例用来序列化对象
 37     StoredMap<String, Double> FeaDB = null;
 38 
 39     public FeatureSelect(String homeDirectory) throws DatabaseException,
 40             FileNotFoundException {
 41         EnvironmentConfig envConfig = new EnvironmentConfig(); // 环境配置
 42         envConfig.setTransactional(true); // 允许事务
 43         envConfig.setAllowCreate(true); // 当环境配置不存在时就创建
 44         env = new Environment(new File(homeDirectory), envConfig); // 创建环境
 45 
 46         DatabaseConfig dbConfig0 = new DatabaseConfig(); // 数据库配置
 47         dbConfig0.setTransactional(true); // 允许事务
 48         dbConfig0.setAllowCreate(true); // 当数据库不存在时就创建
 49         catalogdatabase = env.openDatabase(null, CLASS_CATALOG, dbConfig0);
 50         javaCatalog = new StoredClassCatalog(catalogdatabase);
 51         
 52         DatabaseConfig dbConfig = new DatabaseConfig(); // 数据库配置
 53         dbConfig.setTransactional(true); // 允许事务
 54         dbConfig.setAllowCreate(true); // 当数据库不存在时就创建
 55         database = env.openDatabase(null, "URL", dbConfig); // 打开数据库
 56         
 57         EntryBinding<String> keyBinding = new SerialBinding<String>(
 58                 javaCatalog, String.class);
 59         EntryBinding<Double> valueBinding = new SerialBinding<Double>(
 60                 javaCatalog, Double.class);
 61         FeaDB = new StoredMap<String, Double>(database, keyBinding,
 62                 valueBinding, true);
 63     }
 64 
 65     public void close() throws DatabaseException {
 66         database.close(); // 关闭存放url的数据库
 67         javaCatalog.close(); // 关闭用来序列化对象的javaCatalog类
 68         env.close(); // 关闭环境
 69     }
 70 
 71     public void calIG(File matrixFile) {
 72         if (!matrixFile.exists()) {
 73             System.out.println("Matrix文件不存在.程序退出.");
 74             System.exit(2);
 75         }
 76 
 77         double entropy = Math.log(WordDocMatrix.clanum);
 78         try {
 79             FileReader fr = new FileReader(matrixFile);
 80             BufferedReader br = new BufferedReader(fr);
 81             String line = null;
 82             while ((line = br.readLine()) != null) {
 83                 String[] content = line.split("\\s+");
 84                 String word = content[0];
 85                 ArrayList<Short> al = new ArrayList<Short>(WordDocMatrix.docnum);
 86                 for (int i = 0; i < WordDocMatrix.docnum; i++) {
 87                     short count = Short.parseShort(content[i + 1]);
 88                     al.add(count);
 89                 }
 90                 int category = WordDocMatrix.docnum / WordDocMatrix.clanum;
 91                 int wcount = 0; // 出现word的文档的文档数量
 92                 int[] wcount_class = new int[WordDocMatrix.clanum];// 每个类别中出现单词word的文档数
 93                 double pw = 0.0; // 出现word的文档占全部文档的比重
 94                 double[] pcw = new double[WordDocMatrix.clanum]; // 在单词word出现时各个类别所占的比重
 95                 double[] pcw_b = new double[WordDocMatrix.clanum]; // 在单词word不出现时各个类别所占的比重
 96                 for (int i = 0; i < WordDocMatrix.clanum; i++) {
 97                     for (int j = 0; j < category; j++) {
 98                         if (al.get(j + i * category) > 0) {
 99                             wcount_class[i]++;
100                         }
101                     }
102                     wcount += wcount_class[i];
103                 }
104                 pw = 1.0 * wcount / WordDocMatrix.docnum;
105                 for (int i = 0; i < WordDocMatrix.clanum; i++) {
106                     pcw[i] = 1.0 * wcount_class[i] / wcount;
107                     pcw_b[i] = 1.0 * (category - wcount_class[i])
108                             / (WordDocMatrix.docnum - wcount);
109                 }
110                 double d1 = 0.0;
111                 double d2 = 0.0;
112                 for (int i = 0; i < WordDocMatrix.clanum; i++) {
113                     d1 += pcw[i] * Math.log(pcw[i] + Double.MIN_VALUE);
114                     d2 += pcw_b[i] * Math.log(pcw_b[i] + Double.MIN_VALUE);
115                 }
116                 double ig = entropy + pw * d1 + (1.0 - pw) * d2;
117                 FeaDB.put(word, ig);
118             }
119         } catch (Exception e) {
120             e.printStackTrace();
121         }
122     }
123 
124     // Map按value进行排序(从大到小)
125     public ArrayList<Entry<String, Double>> sort() {
126         ArrayList<Entry<String, Double>> al = new ArrayList<Entry<String, Double>>();
127         //从数据库中读取数据
128         if(!FeaDB.isEmpty()){
129             Iterator<Entry<String,Double>> iter=FeaDB.entrySet().iterator();
130             while(iter.hasNext()){
131                 Entry<String,Double> entry=iter.next();
132                 al.add(entry);
133             }
134         }
135         Collections.sort(al, new Comparator<Map.Entry<String, Double>>() {
136             public int compare(Map.Entry<String, Double> o1,
137                     Map.Entry<String, Double> o2) {
138                 double res = o2.getValue() - o1.getValue();
139                 if (res < 0)
140                     return -1;
141                 else if (res > 0)
142                     return 1;
143                 else
144                     return 0;
145             }
146         });
147         return al;
148     }
149 
150     public static void main(String[] args) throws Exception{
151         FeatureSelect fs = new FeatureSelect("/home/orisun/develop/workspace");
152         fs.calIG(new File("/home/orisun/matrix/part-r-00000"));
153         ArrayList<Entry<String, Double>> al = fs.sort();
154         fs.close();
155         Iterator<Entry<String, Double>> iter = al.iterator();
156         int n = 0;
157         File file = new File("/home/orisun/features");
158         try {
159             file.createNewFile();
160             FileWriter fw = new FileWriter(file);
161             BufferedWriter bw = new BufferedWriter(fw);
162             while (iter.hasNext() && n++ < 300) {
163                 Entry<String, Double> entry = iter.next();
164                 bw.write(entry.getKey() + "\t");
165                 bw.write(String.valueOf(entry.getValue()));
166                 bw.newLine();
167             }
168             bw.flush();
169             bw.close();
170         } catch (Exception e) {
171             e.printStackTrace();
172         }
173     }
174 }

遇到内存溢出时我分别采取了两种解决方案:Hadoop和Berkely DB。由于Berkely DB出乎意料地快,而Hadoop独立模式又灰常地慢,所以可以考虑把WordDocMatrix.java采用Berkely DB来实现。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值