上一篇文章写了如何用爬虫抓取文章形成语料库,这一篇就来讲获得语料库之后如何对之进行预处理,使之转化为质量较高、计算机可分类处理的语料库。
语料库预处理的意义是什么呢,为什么预处理占了文本分类的大部分工作量?
这是因为,计算机如果直接接收大量文本信息,会极大的降低运算效率,最重要的是,文本中还可能含有很多不合法字符或者毫无意义的其他字符,这些在文本分词之后会形成大量冗余的词汇,影响分类器效果。所以,在特征表达之前,一定要进行文本预处理。
语料库一共10个类别,每个大概1000篇左右的文章。因为内容都是中文的,所以分词时没办法像英文一样,直接按空格就分开。
这里要用到一个中文分词的工具ICTCLAS(还有其他的如jieba等)。它是由中科院开发的,支持多种语言,但是不支持Mac操作系统。这里就用Java来进行分词。
先来下载这个分词工具,然后查看目录,Data是我们要加入到项目中的文件夹,直接将它导入到项目中就好,lib是需要的库,根据操作系统的不同,用户自行导入不同的库到项目中即可。sample是一些例子,里面有不同编程语言的例子。
首先创建项目,然后在代码中定义方法接口,需要什么方法就添加什么接口方法:
public interface CLibrary extends Library {
CLibrary Instance = (CLibrary) Native.loadLibrary("resources/NLPIR", CLibrary.class);
public boolean NLPIR_Init(byte[] sDataPath, int encoding, byte[] sLicenceCode);
public String NLPIR_ParagraphProcess(String sSrc, int bPOSTagged);
public String NLPIR_GetKeyWords(String sLine,int nMaxKeyLimit,boolean bWeightOut);
public String NLPIR_GetLastErrorMsg();
public void NLPIR_Exit();
}
先读入相应的库NLPIR,它在下载好的文件夹中lib里面,是windows操作系统就用dll的,linux就用so的。
Resources是我自己创建的文件夹,里面就是装一些资源的,这个只是一个路径,用户自己定义就好,只要能够定位到NLPIR.dll和NLPIR.lib。
第二个是初始化,分词之前一定要初始化,具体怎么调用后面会讲。第三个就是分词。第四个是获取关键词,第五个是出错的话查看错误信息,最后一个Exit就是退出。
下面是具体遍历文件夹对每一个文档进行分词并保存的代码:
private void wordsSegmentation(String dirname) {
Vector<string> dirs = new Vector<string>();
File root_dir = new File(dirname);
FileReader fr;
FileWriter fw;
if (!root_dir.exists()) {
System.err.println("数据集文件夹不存在!");
return;
}
File[] classes = new File(root_dir.getPath()).listFiles();
for (int i = 0;i < classes.length;i++) {
if (classes[i].isDirectory()) {
dirs.add(classes[i].getName());
}
}
String argu = "";
try {
if (!CLibrary.Instance.NLPIR_Init(argu.getBytes("UTF-8"),
1, "0".getBytes("UTF-8"))) {
System.err.println("初始化失败!" + CLibrary.Instance.NLPIR_GetLastErrorMsg());
}
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
for (int i = 0;i < dirs.size();i++) {
File[] files = new File(dataSetPath + "\\" + dirs.get(i)).listFiles();
for (int j = 0;j < files.length;j++) {
if (files[j].getName().contains("txt") && files[j].getName().length() > 4) {
// prevent empty file as a txt
try {
fr = new FileReader(files[j]);
BufferedReader br = new BufferedReader(fr);
String input = "";
String text = "";
while ((text = br.readLine()) != null) {
input = input + text;
}
br.close();
fr.close();
String newtext = null;
newtext = CLibrary.Instance.NLPIR_ParagraphProcess(input, 1);
System.out.println("分词结果为:\n" + newtext);
newtext = getNounAndVerb(newtext);
System.out.println("取名词和动词结果为:\n" + newtext);
fw = new FileWriter(segmentedDataSet + "\\" + dirs.get(i) + "\\" + files[j].getName());
// for windows
BufferedWriter bw = new BufferedWriter(fw);
bw.write(newtext);
bw.close();
fw.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
CLibrary.Instance.NLPIR_Exit();
}
参数是根目录的路径名,然后将根目录下的所有文件夹放入dirs中,做后续遍历处理。
之所以这样做,是因为之前做语料库的时候,是把每一类的文章放在一个文件夹下,然后把这些文件夹一起放到了一个目录下,所以才有上边遍历文件夹的做法,如果只是遍历文件的话,直接找到文件遍历即可。
然后是初始化工具,如果失败会提示出错信息。接下来就是循环,对每一个文件进行分词并存储到segmentedDataSet文件夹下对应的类文件夹的新文件里。
分词结束后查看分词结果吧。每个词带有词性后缀。
之后还要做一些处理,取名词、动词或者其他(根据用户需求),去停用词,加入用户词典。getNounAndVerb这个函数是我写的用来取名词和动词的,分词后会有词性后缀,所以根据词性后缀就可以取到想要的词。
private String getNounAndVerb(String text) {
String result = "";
String[] new_words = text.split(" ");
for (int i = 0;i < new_words.length;i++) {
int indexSlash = new_words[i].lastIndexOf("/");
if (indexSlash != -1) {
String suffix = new_words[i].substring(indexSlash, new_words[i].length());
if (suffix.contains("n") || suffix.contains("v")) {
String word = new_words[i].substring(0, indexSlash);
result = result + word + " ";
}
}
}
if (result.length() > 1) {
result = result.substring(0, result.length() - 1);
}
// delete the last space
new_words = null;
return result;
}
先取得后缀,然后判断是不是包含相应的字符,是的话就连接字符串,最后去掉最后的一个空格。返回新字符串。
在这之后的工作就要用到一个文本分类工具Weka了。有关Weka的具体用法将在下一章讲到,包括去停用词等等。
这里的预处理其实不够好,还有很多好的方法能够将最后的字典维度降到很低。比如把没有意义的名词去掉,还有很多英文名词重复性很强.
那么更多有关Weka的内容还是放到下一章吧,这次就更新到这里。