package ruc.irm.similarity.sentence.morphology; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ruc.irm.similarity.sentence.SegmentProxy; import ruc.irm.similarity.sentence.SegmentProxy.Word; import ruc.irm.similarity.sentence.SentenceSimilarity; import ruc.irm.similarity.word.WordSimilarity; import ruc.irm.similarity.word.hownet2.concept.XiaConceptParser; /** * 基于语义的词形和词序句子相似度计算 * * 《中文信息相似度计算理论与方法》5.4.3小节所介绍的基于词形和词序的句子相似度计算算法 * 在考虑语义时,无法直接获取OnceWS(A, B),为此,通过记录两个句子的词语匹配对中相似度 * 大于某一阈值的词语对最为相同词语,计算次序相似度。 * * @author <a href="mailto:iamxiatian@gmail.com">夏天</a> * @organization 中国人民大学信息资源管理学院 知识工程实验室 * */ public class SemanticSimilarity implements SentenceSimilarity { private static Logger LOG = LoggerFactory.getLogger(SemanticSimilarity.class); /** 词形相似度占总相似度的比重 */ private final double LAMBDA1 = 0.8; /** 词序相似度占总相似度的比重 */ private final double LAMBDA2 = 0.2; /** 如果两个词语的相似度大于了该阈值, 则作为相同词语,计算词序相似度 */ private final double GAMMA = 0.6; /** 词语相似度的计算 */ private WordSimilarity wordSimilarity = null; private static String FILTER_CHARS = "  ,。;?《》()|!,.;?<>|_^…!"; private static SemanticSimilarity instance = null; public static SemanticSimilarity getInstance(){ if(instance == null){ instance = new SemanticSimilarity(); } return instance; } private SemanticSimilarity(){ LOG.debug("used hownet wordsimilarity."); this.wordSimilarity = XiaConceptParser.getInstance(); //this.segmenter = SegmentFactory.getInstance().getParser(); } /** * 滤掉词串中的空格、标点符号 * @param word_list * @return */ private String[] filter(String[] word_list){ List<String> results = new ArrayList<String>(); for(String w:word_list){ if(!FILTER_CHARS.contains(w)){ results.add(w.toLowerCase()); } } return results.toArray(new String[results.size()]); } /** * 计算两个句子的相似度 * @see ruc.irm.similarity.Similaritable */ public double getSimilarity(String firstSen,String secondSen){ //LOG.debug(segmenter.segmentToString(firstSen)); //LOG.debug(segmenter.segmentToString(secondSen)); String[] firstList = filter(segment(firstSen)); String[] secondList = filter(segment(secondSen)); return calculate(firstList,secondList); } /** * 获取两个集合的词形相似度, 同时获取相对于第一个句子中的词语顺序,第二个句子词语的顺序变化次数 * @param firstList * @param secondList * @return */ public double calculate(String[] firstList, String[] secondList){ if(firstList.length == 0 || secondList.length == 0){ return 0; } //首先计算出所有可能的组合 double[][] scores = new double[firstList.length][secondList.length]; //代表第1个句子对应位置是否已经被使用, 默认为未使用,即false boolean[] firstFlags = new boolean[firstList.length]; //代表第2个句子对应位置是否已经被使用, 默认为未使用,即false boolean[] secondFlags = new boolean[secondList.length]; //PSecond的定义参见书中5.4.3节, 为避免无必要的初始化数组, //数组中0值表示在第一个句子中没有对应的相似词语,大于0的值 //则表示在第一个句子中的位置(从1开始编号了) int[] PSecond = new int[secondList.length]; for(int i=0; i<firstList.length; i++){ //firstFlags[i] = false; for(int j=0; j<secondList.length; j++){ scores[i][j] = wordSimilarity.getSimilarity(firstList[i], secondList[j]); } } double total_score = 0; //从scores[][]中挑选出最大的一个相似度,然后减去该元素(通过Flags数组表示),进一步求剩余元素中的最大相似度 while(true){ double max_score = 0; int max_row = -1; int max_col = -1; //先挑出相似度最大的一对:<row, column, max_score> for(int i=0; i<scores.length; i++){ if(firstFlags[i]) continue; for(int j=0; j<scores[i].length; j++){ if(secondFlags[j]) continue; if(max_score<scores[i][j]){ max_row = i; max_col = j; max_score = scores[i][j]; } } } if(max_row>=0) { total_score += max_score; firstFlags[max_row] = true; secondFlags[max_col] = true; if(max_score>=GAMMA) { PSecond[max_col] = max_row+1; } } else { break; } } double wordSim = (2*total_score) / (firstList.length + secondList.length); int previous = 0; int revOrdCount = 0; int onceWSSize = 0; for(int i=0; i<PSecond.length; i++) { if(PSecond[i]>0) { onceWSSize++; if(previous>0 && (previous>PSecond[i])) { revOrdCount++; } previous = PSecond[i]; } } double ordSim = 0; if(onceWSSize==1) { ordSim = 1; } else if(onceWSSize == 0) { ordSim = 0; } else { ordSim = 1.0 - revOrdCount*1.0/(onceWSSize-1); } System.out.println("wordSim ==> " + wordSim + ", ordSim ==> " + ordSim); return LAMBDA1*wordSim+LAMBDA2*ordSim; } public String[] segment(String sentence){ List<Word> list = SegmentProxy.segment(sentence); String[] results = new String[list.size()]; for(int i=0; i<list.size(); i++){ results[i] = list.get(i).getWord(); } return results; } }