在训练阶段,主要完成词频的统计工作。读取训练集,统计出每个词属于该分类下出现的次数,用于后续求解每个词出现在各个类别下的概率,即词汇与主观分类情感之间的关系:
private static void train(){
Map<String,Integer> parameters = new HashMap<>();
try(BufferedReader br = new BufferedReader(new FileReader(trainingData))){ //训练集数据
String sentence;
while(null!=(sentence=br.readLine())){
String[] content = sentence.split("\t| “); //以tab或空格分词
parameters.put(content[0],parameters.getOrDefault(content[0],0)+1);
for (int i = 1; i < content.length; i++) {
parameters.put(content[0]+”-"+content[i], parameters.getOrDefault(content[0]+"-"+content[i], 0)+1);
}
}
}catch (IOException e){
e.printStackTrace();
}
saveModel(parameters);
}
将训练好的模型保存到文件中,可以方便在下次使用时不用重复进行模型的训练:
private static void saveModel(Map<String,Integer> parameters){
try(BufferedWriter bw =new BufferedWriter(new FileWriter(modelFilePath))){
parameters.keySet().stream().forEach(key->{
try {
bw.append(key+"\t"+parameters.get(key)+"\r\n");
} catch (IOException e) {
e.printStackTrace();
}
});
bw.flush();
}catch (IOException e){
e.printStackTrace();
}
}
查看保存好的模型,数据的格式如下:
好评-免费送 3
差评-真烦 1
好评-礼品 3
差评-脏乱差 6
好评-解决 15
差评-挨宰 1
……
这里对训练的模型进行保存,所以如果后续有同样的分类任务时,可以直接在训练集的基础上进行计算,对于分类速度要求较高的任务,能够有效的提高计算的速度。
3、加载模型
加载训练好的模型:
private static HashMap<String, Integer> parameters = null; //用于存放模型
private static Map<String, Double> catagory=null;
private static String[] labels = {“好评”, “差评”, “总数”,“priorGood”,“priorBad”};private static void loadModel() throws IOException {
parameters = new HashMap<>();
List parameterData = Files.readAllLines(Paths.get(modelFilePath));
parameterData.stream().forEach(parameter -> {
String[] split = parameter.split("\t");
String key = split[0];
int value = Integer.parseInt(split[1]);
parameters.put(key, value);
});
calculateCatagory(); //分类
}
对词进行分类,统计出好评及差评的词频总数,并基于它们先计算得出先验概率:
//计算模型中类别的总数
public static void calculateCatagory() {
catagory = new HashMap<>();
double good = 0.0; //好评词频总数
double bad = 0.0; //差评的词频总数
double total; //总词频
for (String key : parameters.keySet()) {
Integer value = parameters.get(key);
if (key.contains("好评-")) {
good += value;
} else if (key.contains("差评-")) {
bad += value;
}
}
total = good + bad;
catagory.put(labels[0], good);
catagory.put(labels[1], bad);
catagory.put(labels[2], total);
catagory.put(labels[3],good/total); //好评先验概率
catagory.put(labels[4],bad/total); //差评先验概率
}
查看执行完后的统计值:
“好评”对应的词汇出现的总次数是46316个,“差评”对应的词汇出现的总次数是77292个,训练集词频总数为123608个,并可基于它们计算出它们的先验概率:
该文档属于某个类别的条件概率= 该类别的所有词条词频总数 / 所有词条的词频总数
4、测试阶段
测试阶段,加载我们提前准备好的测试集,对每一行分词后的评论语句进行主观情感的预测:
private static void predictAll() {
double accuracyCount = 0.;//准确个数
int amount = 0; //测试集数据总量
try (BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilePath))) {
List<String> testData = Files.readAllLines(Paths.get(testFilePath)); //测试集数据
for (String instance : testData) {
String conclusion = instance.substring(0, instance.indexOf("\t")); //已经打好的标签
String sentence = instance.substring(instance.indexOf("\t") + 1);
String prediction = predict(sentence); //预测结果
bw.append(conclusion + " : " + prediction + "\r\n");
if (conclusion.equals(prediction)) {
accuracyCount += 1.;
}
amount += 1;
}
//计算准确率
System.out.println("accuracyCount: " + accuracyCount / amount);
} catch (Exception e) {
e.printStackTrace();
}
}
在测试中,调用下面的predict方法进行分类判断。在计算前,再来回顾一下上面的公式,在程序中进行简化运算:
对于同一个预测样本,分母相同,所以我们可以只比较分子的大小。对分子部分进行进一步简化,对于连乘预算,我们可以对其进行对数操作,变成各部分相加:
这样对于概率的大小比较,就可以简化为比较 先验概率和各个似然概率分别取对数后相加的和。先验概率我们在之前的步骤中已经计算完成并保存,所以这里只计算各词汇在分类条件下的似然概率即可。predict方法的实现如下:
private static String predict(String sentence) {
String[] features = sentence.split(" ");
String prediction;
//分别预测好评和差评
double good = likelihoodSum(labels[0], features) + Math.log(catagory.get(labels[3]));
double bad = likelihoodSum(labels[1], features) + Math.log(catagory.get(labels[4]));
return good >= bad?labels[0]:labels[1];
}
在其中调用likelihood方法计算似然概率的对数和:
//似然概率的计算
public static double likelihoodSum(String label, String[] features) {
double p = 0.0;
Double total = catagory.get(label) + 1;//分母平滑处理
for (String word : features) {
Integer count = parameters.getOrDefault(label + “-” + word, 0) + 1;//分子平滑处理
//计算在该类别的情况下是该词的概率,用该词的词频除以类别的总词频
p += Math.log(count / total);
}
return p;
}
在计算似然概率的方法中,如果出现在训练集中没有包括的词汇,那么会出现它的似然概率为0的情况,为了防止这种情况,对分子分母进行了分别加一的平滑操作。
最后在主函数中调用上面的步骤,最终如果计算出基于样本的好评概率大于等于差评概率,那么将它分类划入“好评”,反之划入“差评”类别,到此就完成了训练和测试的全过程:
public static void main(String[] args) throws IOException {
train();
loadModel();
predictAll();
}
USB Microphone https://www.soft-voice.com/
Wooden Speakers https://www.zeshuiplatform.com/