简答题文本自动评分
要求是这样的:
给定一段中文答案, 和标准的中文文字的答案做比对,最终得到完整的分数.
因为用户的答案中涉及到中文, 所以就必须使用中文分词器, 最终选定的是HanLP ,非常的方便, 资源链接如下:
https://github.com/hankcs/HanLP/tree/1.x 可以自行学习使用.
首先项目中引入HanLP的maven坐标:
<dependency><groupId>com.hankcs</groupId><artifactId>hanlp</artifactId><version>portable-1.7.7</version>
</dependency>
我们使用HanLP分词之后, 使用 向量余弦算法计算两个文本的相似性 ,下面就是写了一个分词的工具类:
package com.taohan.online.exam.util;import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;/*** 分词工具类*/
public class HanlpUtil {/*** 判断标点符号正则表达式, 去掉标点符号*/private static Pattern PATTERN = Pattern.compile("\\pP");public static void main(String[] args) {System.out.println(cosine("天气预报说,明天会下雨,你明天早上去上班的时候记得带上伞。","你明天早上去上班的时候记得带上伞,天气预报说的可能会下雨。"));}/*** 0.5543** 词向量余弦算法计算文本相似度** @return*/public static double cosine(String userAnswer, String standAnswer) {List<String> originWord = getWords(standAnswer);List<String> targetWord = getWords(userAnswer);Map<String, int[]> wordDict = new HashMap<>();for (String word : originWord) {if (!wordDict.containsKey(word)) {int[] value = new int[2];value[0] = 1;wordDict.put(word, value);} else {wordDict.get(word)[0] += 1;}}for (String word : targetWord) {if (!wordDict.containsKey(word)) {int[] value = new int[2];value[1] = 1;wordDict.put(word, value);} else {wordDict.get(word)[1] += 1;}}int dictNum = 0, originNum = 0, targetNum = 0;for (Map.Entry<String, int[]> entry : wordDict.entrySet()) {int origin = entry.getValue()[0];int des = entry.getValue()[1];originNum += origin * origin;targetNum += des * des;dictNum += origin * des;}double sqrt = Math.sqrt(originNum * targetNum);BigDecimal scale = new BigDecimal(dictNum).divide(new BigDecimal(sqrt), 4, BigDecimal.ROUND_HALF_UP).setScale(4, BigDecimal.ROUND_HALF_UP);return scale.doubleValue();}/*** 分词** @param str* @return*/public static List<String> getWords(String str) {List<String> list = new ArrayList<>();if (StringUtils.isBlank(str)) {return list;}List<Term> segment = HanLP.segment(str);for (Term term : segment) {//https://github.com/hankcs/HanLP/tree/1.xif (!PATTERN.matcher(term.word).matches()) {list.add(term.word);}}return list;}}
中文分词就解决了, 还有向量余弦算法计算文本相似度 的问题也解决了,
下面是一个用户答案的评分的工具类, 以及其使用的示例,
用户输入用户的答案, 用户答案和标准答案的多个关键字进行比较, 得到关键词双向匹配得分, 再和标准答案进行两个答案的文本比较得到相似性得分, , 两个得分计算平均值, 得到最后的得分.
package com.taohan.online.exam.util;import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;public class CompareUtil {public static void main(String[] args) {String userAnswer = "多态是在程序还没运行时不知道调用哪个函数,在程序执行中,根据情况动态确定,操作很灵活";String standAnswer = "多态是在程序还没运行时不知道调用函数,在程序执行,根据情况动态确定";List<String> keywords = Stream.of("多态", "程序", "函数名", "参数", "运行情况").collect(Collectors.toList());double score = getUserAnswerScore(userAnswer, standAnswer, keywords);System.out.println(score);}/*** 获取最后的得分** @param userAnswer 用户答案* @param standAnswer 标准答案* @param keywords 关键词* @return*/public static double getUserAnswerScore(String userAnswer, String standAnswer, List<String> keywords) {if (StringUtils.equals(userAnswer, standAnswer)) {return 1.0;}if (StringUtils.isBlank(userAnswer)) {return 0.0;}double textSameScore = HanlpUtil.cosine(userAnswer, standAnswer);double score = 0;if (keywords != null && keywords.size() > 0) {score = getCompareScore(keywords, userAnswer);} else {score = textSameScore;}//计算两者的平均数BigDecimal scale = new BigDecimal(textSameScore).add(new BigDecimal(score)).divide(new BigDecimal(2), 4, BigDecimal.ROUND_HALF_UP).setScale(4, BigDecimal.ROUND_HALF_UP);return scale.doubleValue();}/*** 0.4254* 获得关键词双向匹配得分** @param keywords* @param userAnswer* @return*/public static double getCompareScore(List<String> keywords, String userAnswer) {List<BigDecimal> list = new ArrayList<>();for (String keyword : keywords) {BigDecimal precent = getPrecent(userAnswer, keyword);list.add(precent);}BigDecimal sum = new BigDecimal("0");for (BigDecimal bigDecimal : list) {sum = sum.add(bigDecimal);}BigDecimal scale = sum.divide(new BigDecimal(list.size()), 4, BigDecimal.ROUND_HALF_UP).setScale(4, BigDecimal.ROUND_HALF_UP);return scale.doubleValue();}/*** 比较两个字符串的相似度** @param answer* @param oneWord* @return*/public static BigDecimal getPrecent(String answer, String oneWord) {int index = 0;if (oneWord == null || oneWord.length() < 1) {return new BigDecimal(0);}if (answer.indexOf(oneWord) != -1) {index = oneWord.length();} else {int length = oneWord.length();for (int i = 0; i < length; i++) {String a = oneWord.substring(i, length - 1);String b = oneWord.substring(i + 1, length);if (answer.indexOf(a) != -1) {index = a.length();break;}if (answer.indexOf(b) != -1) {index = b.length();break;}}}if (index == 0) {for (int i = 0; i < oneWord.length(); i++) {if (answer.contains(String.valueOf(oneWord.charAt(i)))) {index = 1;}}}BigDecimal decimal = new BigDecimal(index).divide(new BigDecimal(oneWord.length()), 2, BigDecimal.ROUND_HALF_UP).setScale(4, BigDecimal.ROUND_HALF_UP);return decimal;}}
其实就是两个工具类的事情, 很简单.
这个是别人花钱雇我写的, 为了保证原创性, 暂时不会公开出来(大概4个月后会公开). 仅供大家参考