在训练阶段,主要完成词频的统计工作。读取训练集,统计出每个词属于该分类下出现的次数,用于后续求解每个词出现在各个类别下的概率,即词汇与主观分类情感之间的关系:

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/