求TnaCorp的文档向量

我下载的是TanCorp-12预处理格式

step1.编码转换

刚下载下来的语料库编码是cp936即gb2312,所以下先转换成utf-8编码。转码工作在windows下进行,用到一个工具iconv.exe.

iconv *.txt -p E:\\TanCorp-12-Txt -f gb2312 -t utf-8 -v -s

step2.去掉文件的前3个字节

utf-8格式一个汉字是3个字节。Windows下记事本在保存utf-8文件时会在文件头加入3个字节:efbbbf--在上一步使用了iconv.exe后文开头也多出了这3个字节。在Linux下你可以用hexdump -C file查看文件的十六进制编码,或在vim中用:%!xxd也可以。

去掉文件的前3个字节方法是:先获取文件长度size,然后tail -c size-3 file > newfile即可。

#!/bin/bash
OPATH="TanCorp-12-Txt"
DPATH="/tmp"

find $OPATH -type d -exec mkdir -pv ${DPATH}/{} \;
find $OPATH -type f > tmpf
while read FILE
do
    newf=${DPATH}/$FILE
    touch $newf
    size=$(wc -c $FILE|sed 's/ .*//')
    let need=size-3 
	tail -c $need $FILE > $newf
done < tmpf
rm tmpf
echo "all done"

获取shell输出到标准输出上的值可以有两种方法--请注意第11行代码,这实际上也是sed向shell传值的方法:

1.size=$(wc -c $FILE|sed 's/ .*//')

2.size=`wc -c $FILE|sed 's/ .*//'`

step3.让文件名中包含它所属的类别

#!/bin/bash
DIR="TanCorp-12-Txt"

function process
{
	if [ $1 = $DIR ]
	then
		return
	fi
    count=0
    pn=$1
    class1=${pn#*/}			#先把第一个slash及其左边的内容去掉
    class=${class1%%-*}		#再把第一个-及其右边的内容去掉
	find $1 -type f > truef
	while read TF
	do
		mv $TF ${pn}/${class}-$((count=count+1))
	done < truef
	rm truef
}

find $DIR -type d > tmpf
while read FILE
do
    process $FILE
done < tmpf
rm tmpf
echo "all done"

step4.把单个文件合并为一行.

由于语料库每行末尾已经有了一个空格,所我我们只需要把换行符去掉即可。注意在TanCorp中换行用的是"\r\n"

#!/bin/bash
SPATH="/home/orisun"
DIR="TanCorp-12-Txt"
DPATH="/tmp"

find ${DIR} -type d -exec mkdir -pv ${DPATH}/{} \;
find ${DIR} -type f > tmp
while read FILE
do
	cat $FILE | tr -d "\r\n" > ${DPATH}/${FILE}
#	awk '{printf "%s",$0 >>bakfile}' bakfile=${DPATH}/${FILE} $FILE	#不用tr用awk也可以实现
done < tmp
rm tmp
echo "all done"

step5.统计文档中词汇出现的频数

#!/bin/bash
OPATH="TanCorp-12-Txt"
DPATH="/tmp"

find $OPATH -type d -exec mkdir -pv ${DPATH}/{} \;
find $OPATH -type f > tmpf
while read FILE
do
    dest=${DPATH}/${FILE}
    bname=${dest##*/}		#拿掉最后一个/及其左边的内容,即获取basename
    touch $dest
    awk 'BEGIN{RS=" ";}
   		{
			if(NR==1)
			{
				previous=fn;
			}
			if ($1!=previous)
			{
				printf "%s %d\n",previous,count>>newfile;
				previous=$1;
				count=1;
			}
			else
			{
				count++;
			}
    	}
    	END{printf "%s %d\n",previous,count>>newfile;}' newfile=$dest fn=$bname $FILE
done < tmpf
rm tmpf
echo "all done"

step6.去掉不成词的单个汉字

#!/bin/bash
DIR="TanCorp-12-Txt-3"
DPATH="/tmp"

find $DIR -type d -exec mkdir -pv ${DPATH}/{} \;
find $DIR -type f > tmpf
while read FILE
do
    sed '/^. /'d $FILE > ${DPATH}/$FILE
done < tmpf
rm tmpf
echo "all done"

step7.统计每个词的文档频率

为避免内存溢出的问题,这一步采用hadoop来完成。先对step6得到的结果进行以下处理,但是step6得到的结果还要保存下来,以后还要用。下面的操作是把文档合并成一行,去掉频数词。

#!/bin/bash
DIR="TanCorp-12-Txt-5"
DPATH="/tmp"

find $DIR -type d -exec mkdir -pv ${DPATH}/{} \;
find $DIR -type f > tmpf
while read FILE
do
   dest=${DPATH}/$FILE
   awk 'BEGIN{ORS=" ";} {print $1 >> fn;}' fn=$dest $FILE
done < tmpf
rm tmpf
echo "all done"

这样文档中的第一个词是文档名,每个词在文档中出现一次。在map阶段遇到一个词先判断在不在停用词表中,在就忽略,否则就抛出(单词,文档名);在reduce阶段统计一个词在多少个文档中出现过(专业术语叫做文档频率),如何文档频率小于4则忽略,否则把(单词,文档频率,包含单词的文档列表)写入文件。

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class DF {
	static HashSet<String> stopwords = new HashSet<String>();// 停用词集合
	// 初始化停用词表
	public static void init_SW(String filePath) {
		stopwords.clear();
		File swFile = new File(filePath);
		if (!swFile.exists()) {
			System.out.println("文件不存在,程序退出.");
			System.exit(2);
		}
		try {
			FileReader fr = new FileReader(swFile);
			BufferedReader br = new BufferedReader(fr);
			String word = null;
			while ((word = br.readLine()) != null) {
				stopwords.add(word);
			}
			br.close();
		} catch (IOException exp) {
			System.out.println("读取停用词时发生异常:" + exp.getMessage());
		}
	}

	static class DFMapper extends Mapper<LongWritable, Text, Text, Text> {
		public void map(LongWritable key, Text value, Context context)
				throws IOException, InterruptedException {
			String line = value.toString();
			String[] words = line.split("\\s+");
			String filename = words[0];
			for (int i = 1; i < words.length; i++) {
				if (!stopwords.contains(words[i])) {		//不能是停用词
					context.write(new Text(words[i]), new Text(filename));
				}
			}
		}
	}

	static class DFReducer extends Reducer<Text, Text, Text, Text> {
		public void reduce(Text key, Iterable<Text> fns, Context context)
				throws IOException, InterruptedException {
			ArrayList<String> al = new ArrayList<String>();
			StringBuffer buf = new StringBuffer();
			for (Text fn : fns) {
				al.add(fn.toString());
				buf.append(fn.toString()+" ");
			}
			int count=al.size();
			if(count<4)			//如果文档频率小于4,则认为不可能成为特征词
				return;
			context.write(key, new Text(String.valueOf(count)+" "+buf.toString()));
		}
	}

	public static void main(String[] args) throws Exception {
		init_SW("/home/orisun/stopword");
		Path inputPath1 = new Path("/home/orisun/TanCorp-12-Txt-6/c1-education");
		Path inputPath2 = new Path("/home/orisun/TanCorp-12-Txt-6/c2-entertainment");
		Path inputPath3 = new Path("/home/orisun/TanCorp-12-Txt-6/c3-health");
		Path inputPath4 = new Path("/home/orisun/TanCorp-12-Txt-6/c4-house");
		Path inputPath5 = new Path("/home/orisun/TanCorp-12-Txt-6/c5-region");
		Path inputPath6 = new Path("/home/orisun/TanCorp-12-Txt-6/c6-sports");
		Path inputPath7 = new Path("/home/orisun/TanCorp-12-Txt-6/c7-talents");
		// 输出路径在程序运行前不能存在
		Path outputPath = new Path("/home/orisun/matrix");
		Job job = new Job();
		job.setJarByClass(DF.class);
		FileInputFormat.addInputPath(job, inputPath1);
		FileInputFormat.addInputPath(job, inputPath2);
		FileInputFormat.addInputPath(job, inputPath3);
		FileInputFormat.addInputPath(job, inputPath4);
		FileInputFormat.addInputPath(job, inputPath5);
		FileInputFormat.addInputPath(job, inputPath6);
		FileInputFormat.addInputPath(job, inputPath7);
		FileOutputFormat.setOutputPath(job, outputPath);
		job.setMapperClass(DFMapper.class);
		job.setReducerClass(DFReducer.class);
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(Text.class);
		System.exit(job.waitForCompletion(true) ? 0 : 1);
	}
}

step8.计算信息增益,选取特征项

计算以下工作之前,你应该把TanCorp分成训练集和测试集,step8的工作是在训练集上进行的。

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;

public class CalIG {

	public void calIG(File matrixFile, File IGFile) {
		if (!matrixFile.exists()) {
			System.out.println("Matrix文件不存在.程序退出.");
			System.exit(2);
		}

		int category_num = 7; // 一共有7大分类
		int[] category_count = { 115,750,703,468,75,1403,304}; // 每个分类包含的文档数
		int doc_num = 0; // 全部文档数量
		for (int i = 0; i < category_num; i++)
			doc_num += category_count[i];

		double HC = getEntropy(category_count);

		try {
			FileReader fr = new FileReader(matrixFile);
			BufferedReader br = new BufferedReader(fr);
			PrintWriter pw = new PrintWriter(new FileOutputStream(IGFile));
			String line = null;
			while ((line = br.readLine()) != null) {
				String[] content = line.split("\\s+");
				int len = content.length;
				String term = content[0];
				int term_count = Integer.parseInt(content[1]); // 出现term的文档数量
				int[] term_class_count = new int[category_num];// 每个类别中出现term的文档数量
				int[] term_b_class_count = new int[category_num];// 每个类别中不出现term的文档数量
				for (int i = 2; i < len; i++) {
					String catstr = content[i].split("-")[0];
					int catint = Integer.parseInt(catstr.substring(1));
					term_class_count[catint - 1]++;
				}
				for (int i = 0; i < category_num; i++) {
					term_b_class_count[i] = category_count[i]
							- term_class_count[i];
				}

				double HCT = 1.0 * term_count / doc_num
						* getEntropy(term_class_count) + 1.0
						* (doc_num - term_count) / doc_num
						* getEntropy(term_b_class_count);
				double IG = HC - HCT;

				pw.println(term + "\t" + String.valueOf(term_count) + "\t"
						+ String.valueOf(IG));
				pw.flush();
			}
			br.close();
			pw.close();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	public double getEntropy(int[] arr) {
		int sum = 0;
		double entropy = 0.0;
		for (int i = 0; i < arr.length; i++) {
			sum += arr[i];
			entropy += arr[i] * Math.log(arr[i] + Double.MIN_VALUE)
					/ Math.log(2);
		}
		entropy /= sum;
		entropy -= Math.log(sum) / Math.log(2);
		return 0 - entropy;
	}

	public static void main(String[] args) throws Exception {
		Date currentTime = new Date();
		SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		System.out.println("Begin Time: " + formatter.format(currentTime));

		CalIG inst = new CalIG();
		File in = new File("/home/orisun/matrix/part-r-00000");
		File out = new File("/home/orisun/frequency1");
		inst.calIG(in, out);

		currentTime = new Date();
		System.out.println("End Time: " + formatter.format(currentTime));
	}
}

此时保存在/home/orisun/frequency1中的是:(单词  文档频率  信息增益值),我们要按照第3列对文件进行逆序(从大到小)排序:

sort -n -r -k3 frequency1 > frequency

注意sort命令按数字进行排序时不能识别科学计数法,即它认为9.9E-4比0.1大。

我们保留IG值最大的2000个词,其他的可以从frequency文件中删掉。

step9.按照TF-IDF计算文档向量

对于训练集

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.PrintWriter;
import java.util.HashMap;

public class Train_Vector {
	final static int N=3818;		//训练集文档总数目
	static int feanum=700;		//700个特征项
	static HashMap<String,Integer> feamap=new HashMap<String,Integer>();
	static int[] feafd=new int[feanum];
	
	static void initFea(File ff){
		if (!ff.exists()) {
			System.out.println("Matrix文件不存在.程序退出.");
			System.exit(2);
		}
		try{
			FileReader fr = new FileReader(ff);
			BufferedReader br = new BufferedReader(fr);
			String line=null;
			for(int i=0;i<feanum;i++){
				line=br.readLine();
				String[] conts=line.split("\\s+");
				feamap.put(conts[0], i);
				feafd[i]=Integer.parseInt(conts[1]);
			}
			br.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	static void calvec(File srcFile){
		if (!srcFile.exists()) {
			System.out.println("Matrix文件不存在.程序退出.");
			System.exit(2);
		}
		if(srcFile.isDirectory()){
			File[] childFiles=srcFile.listFiles();
			for(File child:childFiles){
				calvec(child);
			}
		}
		else if(srcFile.isFile()){
			double[] arr=new double[feanum];
			int df,tf,index;
			try{
				FileReader fr=new FileReader(srcFile);
				BufferedReader br=new BufferedReader(fr);
				String line=br.readLine();		//跳过第一行,因为第一行存储的是文件名
				while((line=br.readLine())!=null){
					String[] conts=line.split("\\s+");
					if(feamap.containsKey(conts[0])){
						tf=Integer.parseInt(conts[1]);
						index=feamap.get(conts[0]);
						df=feafd[index];
						arr[index]=Math.log(tf+1.0)*Math.log(N/df)/(Math.log(2.0)*Math.log(2.0));
					}
				}
				br.close();
			}catch (Exception e){
				e.printStackTrace();
			}
			double fenmu=0.0;
			for(int i=0;i<feanum;i++)
				fenmu+=Math.pow(arr[i], 2.0);
			fenmu=Math.pow(fenmu, 0.5);
			try{
				File out=new File(srcFile.getAbsoluteFile()+".vec");
				PrintWriter pw = new PrintWriter(new FileOutputStream(out));
				for(int i=0;i<feanum;i++)
					pw.printf("%f\t", arr[i]/fenmu);
				pw.flush();
				pw.close();
			}catch (Exception e){
				e.printStackTrace();
			}
			
		}
	}
	public static void main(String[]args)throws Exception {
		initFea(new File("/home/orisun/feature"));
		calvec(new File("/home/orisun/TanCorp-12-Txt-5"));
	}
}

对于测试集,特征项的权重则直接用w=tf*IG,对以上代码作轻微发动即可。

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.PrintWriter;
import java.util.HashMap;

public class TestVector {
	static int feanum=700;		//700个特征项
	static HashMap<String,Integer> feamap=new HashMap<String,Integer>();
	static double[] feaig=new double[feanum];
	
	static void initFea(File ff){
		if (!ff.exists()) {
			System.out.println("Matrix文件不存在.程序退出.");
			System.exit(2);
		}
		try{
			FileReader fr = new FileReader(ff);
			BufferedReader br = new BufferedReader(fr);
			String line=null;
			for(int i=0;i<feanum;i++){
				line=br.readLine();
				String[] conts=line.split("\\s+");
				feamap.put(conts[0], i);
				feaig[i]=Double.parseDouble(conts[2]);
			}
			br.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	static void calvec(File srcFile){
		if (!srcFile.exists()) {
			System.out.println("Matrix文件不存在.程序退出.");
			System.exit(2);
		}
		if(srcFile.isDirectory()){
			File[] childFiles=srcFile.listFiles();
			for(File child:childFiles){
				calvec(child);
			}
		}
		else if(srcFile.isFile()){
			double[] arr=new double[feanum];
			int tf,index;
			double ig;
			try{
				FileReader fr=new FileReader(srcFile);
				BufferedReader br=new BufferedReader(fr);
				String line=br.readLine();		//跳过第一行,因为第一行存储的是文件名
				while((line=br.readLine())!=null){
					String[] conts=line.split("\\s+");
					if(feamap.containsKey(conts[0])){
						tf=Integer.parseInt(conts[1]);
						index=feamap.get(conts[0]);
						ig=feaig[index];
						arr[index]=tf*ig;
					}
				}
				br.close();
			}catch (Exception e){
				e.printStackTrace();
			}
			try{
				File out=new File(srcFile.getAbsoluteFile()+".vec");
				PrintWriter pw = new PrintWriter(new FileOutputStream(out));
				for(int i=0;i<feanum;i++)
					pw.printf("%f\t", arr[i]);
				pw.flush();
				pw.close();
			}catch (Exception e){
				e.printStackTrace();
			}
			
		}
	}
	public static void main(String[]args)throws Exception {
		initFea(new File("/home/orisun/feature"));
		calvec(new File("/home/orisun/TanCorp-5-Test"));
	}
}

我们把向量文档挪到一个单独的文件夹下面去:

#!/bin/bash
DIR="TanCorp-12-Txt-5"
DPATH="/tmp"

find $DIR -type d -exec mkdir -pv /tmp/{} \;
find $DIR -type f -name *.vec -exec mv {} ${DPATH}/{} \;

step10.文本分类或聚类

测试文档和训练文档都已经转换成向量的形式了,至于用什么算法进行文件分类或聚类就看你的了。

posted @ 2012-02-18 22:35  张朝阳  阅读(899)  评论(0编辑  收藏  举报