实验目的
使用免费的中文分词语料库,如人民日报语料库PKU,使用语料库中的常见词编写一个句子,使用二元语法(即每个词只与和它相邻的前一个词有关)在语料库中对句子中的词进行词频统计,输出句子的出现概率。
实验内容
对给定的pku_training.txt语料库处理,构建一个二元语言模型,二元语言模型是指当前词语出现的概率只与前一个词有关
对语料首先进行处理,加上头和尾:
那么句子出现的概率就可以按照下面的式子计算:
要做的工作就是在语料库中统计各个词语出现的词频,同时要计算出给定的语句的先验概率,这个先验概率是基于2-gram的
实验过程
首先是读取整个语料文件并进行分句:
/*
* 读取并返回给定路径文件中文本的字符串句子形式
* */
public static String getTxtString(String path) {
StringBuilder sBuilder = new StringBuilder();
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(path));
char[] buffer = new char[512];//取决于你最长那一句有多长
int len;
while ((len = br.read(buffer)) != -1) {
String line = new String(buffer, 0, len);//这是读取到的每行,包含很多句号逗号问好感叹号,需要进一步分句
line = line.trim();//去除读进来的字符串的尾部空格,不然split之后会多一个只有空格的空串
String[] strArr = line.split("[,。?!]");//处理完一行按照,。?!‘"先分成句子
for (String s : strArr) {
s = s.replaceAll("\\pP", "");//去除每一句中的所有标点符号
s = "<BOS> " + s + "<EOS> ";//加上首尾
// System.out.println(s);
sBuilder.append(s);//最后加入sBuilder中
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (br != null) br.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return sBuilder.toString();
}
这里采用的是根据,。!?进行分句,因为一般中文句子都是用这几个标点符号进行结句的。
下面是分句产生的部分句子,据统计,一共118454条句子:
然后是根据空格进行切词,并统计各个词语出现的次数:
/*
* 传入一句一句的文本字符串,计算总文本的各个单词词频
* */
public static Map<String, Integer> getCounter(String txtString) {
//用Map记录字符串及其出现的次数
Map<String, Integer> treeMap = new TreeMap<>();
/*
* \s表示表格、换行等空白区域
* \u3000表示中文空格
* */
String[] strArr = txtString.split("[\\s\u3000]+");//通过空格切分
for (String s : strArr) {
treeMap.put(s, treeMap.getOrDefault(s, 0) + 1);//记录每个词语出现的次数
}
// treeMap.forEach((key, value) -> System.out.println(key + ": " + value));
return treeMap;
}
分词结果部分截取如下:
在这之后就可以计算先验概率了:
/*
* 传入input字符串和语料库的文件路径(TXT格式),使用2-gram得到这句在语料库中的先验概率
* */
public static double prior_probability(String input, String file_path) {
input = "<BOS> " + input + "<EOS> ";
String[] input_strings = input.split("[\\s\u3000?‘“:(),、!;。]+");//首先将输入的句子分成词组
int[] count_inputs = new int[input_strings.length - 1];//由2-gram可知,加上<BOS><EOS>一共需要词数-1个先验概率
int[] count_txts = new int[input_strings.length - 1];//词语在语料库中出现的次数
String txtString = NLP02.getTxtString(file_path);//从文件路径中读取字符串
Map<String, Integer> treeMap = NLP02.getCounter(txtString);
String[] strArr = txtString.split("[\\s\u3000]+");//获取语料库的词组
/*
* 下面双重循环是为了匹配词语和词语前面一个词语都相同出现的次数
* */
for (int i = 0; i < input_strings.length - 1; i++) {
count_inputs[i] = 0;
for (int j = 0; j < strArr.length - 1; j++) {
if (strArr[j].equals(input_strings[i]) && strArr[j + 1].equals(input_strings[i + 1])) {
count_inputs[i]++;
}
}
count_txts[i] = treeMap.get(input_strings[i]);
}
double p = 1;
String process = "Process:";
for (int i = 0; i < input_strings.length - 1; i++) {
p *= (double) count_inputs[i] / count_txts[i];//句子出现的概率
process += (" " + count_inputs[i] + "/" + count_txts[i] + " *");//计算过程
}
process = process.substring(0, process.length() - 2);
System.out.println(process);
return p;
}
结果展示
这里测试的句子是语料库中的原句:
“忙碌 了 一 年 的 农民 就 准备 迎接 新年 !”
可以看出能够正确计算出各个词的词频,同时也能计算出先验概率,因此也可以计算出需要的句子概率,由于语料库比较大,所以句子出现的概率较低。