且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

利用mmSeg4j分词实现网页文本倾向性分析

更新时间:2022-09-16 15:34:22

利用mmSeg4j分词实现网页文本倾向性分析

        最近一直在做网页情感倾向性分析的工作,找了一些论文,发现基于机器学习的算法在项目中不太合适,于是自己鼓捣了一套基于中文分词和正负面词库的分析算法。

       原理很简单:

       文章倾向性 =  ∑(出现的正面词汇 * 权重) —∑(出现的负面词汇 * 权重)。
 
       在这个基础上对于负面新闻再加上相关性判断。

       在中文分词方面选择了mmSeg4j,没别的原因,就是之前一直用这个,相对来说性能非常不错,但有些词汇需要自己添加到他的words.dic文件中。mmSeg4j下载地址:http://code.google.com/p/mmseg4j/

      在正式编码之前规划了3个文本文件:

  1. neg_words: 配置负面词汇,每个词一行,格式为“太差-1”。“-”后面的数字作为负面词汇的权重。
  2. pos_words:配置的正面词汇,配置方式与负面词汇类似。
  3. rel_words: 相关词汇表,每行一个词即可,增加这个配置文件是为了识别出于特定内容相关的文本情感。如:仅关心近期与“万科”有关的分析。

      在工程启动时将这三个文件加载到一个对象中(单例)代码如下:

 


  1. import java.io.BufferedReader;  
  2. import java.io.FileNotFoundException;  
  3. import java.io.FileReader;  
  4. import java.io.IOException;  
  5. import java.util.ArrayList;  
  6. import java.util.HashMap;  
  7. import java.util.List;  
  8. import java.util.Map;  
  9.  
  10. import org.springframework.stereotype.Component;  
  11.  
  12. import com.yidatec.vis.psms.commons.PSMSConstants;  
  13.  
  14. /**  
  15.  * 加载词汇  
  16.  * @author William Xu  
  17.  */ 
  18.  
  19. @Component 
  20. public class TrendencyWordsLoader {  
  21.       
  22.     private Map<String, Integer> negWordMap;  
  23.     private Map<String, Integer> posWordMap;  
  24.     private List<String> refWordList;     
  25.       
  26.     public TrendencyWordsLoader(){  
  27.         loadWords();  
  28.     }  
  29.       
  30.     private void loadWords(){  
  31.           
  32.         negWordMap = new HashMap<String, Integer>();  
  33.         posWordMap = new HashMap<String, Integer>();  
  34.         refWordList = new ArrayList<String>();  
  35.           
  36.         try {  
  37.               
  38.             FileReader fr = new FileReader(this.getClass().getClassLoader().getResource(PSMSConstants.NEG_WORDS_PATH).getFile());             
  39.             BufferedReader br = new BufferedReader(fr);   
  40.               
  41.             String line = null;  
  42.               
  43.             while((line = br.readLine()) != null){  
  44.                   
  45.                 String[] words = line.split("-");                 
  46.                 negWordMap.put(words[0], Integer.parseInt(words[1]));  
  47.             }  
  48.               
  49.             fr = new FileReader(this.getClass().getClassLoader().getResource(PSMSConstants.POS_WORDS_PATH).getFile());            
  50.             br = new BufferedReader(fr);              
  51.             line = null;  
  52.               
  53.             while((line = br.readLine()) != null){  
  54.                   
  55.                 String[] words = line.split("-");                 
  56.                 posWordMap.put(words[0], Integer.parseInt(words[1]));  
  57.             }  
  58.               
  59.             fr = new FileReader(this.getClass().getClassLoader().getResource(PSMSConstants.REL_WORDS_PATH).getFile());            
  60.             br = new BufferedReader(fr);              
  61.             line = null;  
  62.               
  63.             while((line = br.readLine()) != null){                
  64.                 refWordList.add(line);  
  65.             }  
  66.               
  67.             br.close();  
  68.             fr.close();  
  69.               
  70.         } catch (FileNotFoundException e) {  
  71.             e.printStackTrace();  
  72.         } catch (NumberFormatException e) {  
  73.             e.printStackTrace();  
  74.         } catch (IOException e) {  
  75.             e.printStackTrace();  
  76.         }   
  77.     }  
  78.       
  79.     public Map<String, Integer> getNegWordMap() {  
  80.         return negWordMap;  
  81.     }  
  82.  
  83.     public Map<String, Integer> getPosWordMap() {  
  84.         return posWordMap;  
  85.     }  
  86.       
  87.     public List<String> getRefWordList() {  
  88.         return refWordList;  
  89.     }  
  90. }  

加载词汇表后,就可以使用mmSeg4j对网页文本进行分词,并进行分析了,代码如下:

 


  1. import java.io.IOException;  
  2. import java.io.Reader;  
  3. import java.io.StringReader;  
  4. import java.util.ArrayList;  
  5. import java.util.HashMap;  
  6. import java.util.List;  
  7. import java.util.Map;  
  8. import java.util.Set;  
  9.  
  10. import org.springframework.beans.factory.annotation.Autowired;  
  11. import org.springframework.stereotype.Component;  
  12.  
  13. import com.chenlb.mmseg4j.ComplexSeg;  
  14. import com.chenlb.mmseg4j.Dictionary;  
  15. import com.chenlb.mmseg4j.MMSeg;  
  16. import com.chenlb.mmseg4j.Word;  
  17. import com.yidatec.vis.psms.entity.SolrQueryResult;  
  18.  
  19. @Component 
  20. public class TrendencyAnalyser {  
  21.  
  22.     @Autowired 
  23.     TrendencyWordsLoader wordLoader;  
  24.  
  25.     protected static final Dictionary dic = Dictionary.getInstance();  
  26.     protected static final ComplexSeg seg = new ComplexSeg(dic);  
  27.  
  28.     /**  
  29.      * 正序阈值  
  30.      */ 
  31.     private final int PS_THRESHOLD = 50;  
  32.  
  33.     /**  
  34.      * 逆序阈值  
  35.      */ 
  36.     private final int NS_THRESHOLD = 30;  
  37.  
  38.     /**  
  39.      * 整片文章分词Map  
  40.      */ 
  41.     private Map<String, List<Word>> segments = null;  
  42.     private List<Word> negs = null;  
  43.     private List<Word> poses = null;  
  44.     private List<Word> rels = null;  
  45.  
  46.     public int analyzeTrendency(String title, String content) {  
  47.  
  48.         try {  
  49.  
  50.             boolean flag = isRelTitle(title);  
  51.  
  52.             if (flag) {  
  53.  
  54.                 int titleTendency = getTitleTrendency();  
  55.  
  56.                 if (titleTendency < 0) {  
  57.                     return SolrQueryResult.NEGATIVE_NATURE;  
  58.                 } else if (titleTendency > 0) {  
  59.                     return SolrQueryResult.POSITIVE_NATURE;  
  60.                 }  
  61.             }  
  62.  
  63.             clearAll();  
  64.  
  65.             initSegmentsMap(new StringReader(title + " " + content));  
  66.  
  67.             parseNegWordsMap();  
  68.  
  69.             parsePosWordsMap();  
  70.  
  71.             int result = analyzeContentsTrendency();  
  72.  
  73.             if (flag) { // 标题相关,仅判断文本倾向性  
  74.  
  75.                 if (result < 0) {  
  76.  
  77.                     return SolrQueryResult.NEGATIVE_NATURE;  
  78.  
  79.                 } else if (result == 0) {  
  80.  
  81.                     return SolrQueryResult.NEUTRAL_NATURE;  
  82.  
  83.                 } else {  
  84.  
  85.                     return SolrQueryResult.POSITIVE_NATURE;  
  86.  
  87.                 }  
  88.  
  89.             } else { // 标题无关,需要复杂的矩阵算法  
  90.  
  91.                 parseRelWordsMap();  
  92.  
  93.                 if (result < 0) {  
  94.  
  95.                     if (analyzeTrendencyByMatrix()) {  
  96.  
  97.                         return SolrQueryResult.NEGATIVE_NATURE;  
  98.  
  99.                     } else {  
  100.  
  101.                         return SolrQueryResult.NEUTRAL_NATURE;  
  102.  
  103.                     }  
  104.  
  105.                 } else if (result == 0) {  
  106.  
  107.                     return SolrQueryResult.NEUTRAL_NATURE;  
  108.  
  109.                 } else {  
  110.  
  111.                     return SolrQueryResult.POSITIVE_NATURE;  
  112.  
  113.                 }  
  114.  
  115.             }  
  116.  
  117.         } catch (IOException e) {  
  118.             return SolrQueryResult.NEUTRAL_NATURE;  
  119.         }  
  120.     }  
  121.  
  122.     private void clearAll() {  
  123.  
  124.         if (segments != null) {  
  125.             segments.clear();  
  126.         }  
  127.         if (negs != null) {  
  128.             negs.clear();  
  129.         }  
  130.         if (poses != null) {  
  131.             poses.clear();  
  132.         }  
  133.     }  
  134.  
  135.     /**  
  136.      * 是否是倾向性相关标题  
  137.      *   
  138.      * @param title  
  139.      * @return  
  140.      */ 
  141.     private boolean isRelTitle(String title) {  
  142.  
  143.         try {  
  144.  
  145.             initTitleSegmentsMap(new StringReader(title));  
  146.  
  147.             List<String> relWords = wordLoader.getRefWordList();  
  148.  
  149.             for (String word : relWords) {  
  150.  
  151.                 if (segments.containsKey(word)) {  
  152.                     return true;  
  153.                 }  
  154.  
  155.             }  
  156.  
  157.         } catch (IOException e) {  
  158.             return false;  
  159.         }  
  160.  
  161.         return false;  
  162.  
  163.     }  
  164.  
  165.     /**  
  166.      * 获取标题倾向性  
  167.      *   
  168.      * @param title  
  169.      * @return  
  170.      */ 
  171.     private int getTitleTrendency() {  
  172.  
  173.         parseNegWordsMap();  
  174.         parsePosWordsMap();  
  175.  
  176.         return analyzeContentsTrendency();  
  177.  
  178.     }  
  179.  
  180.     /**  
  181.      * 判断整篇文章的倾向性  
  182.      *   
  183.      * @param title  
  184.      * @param content  
  185.      * @return  
  186.      */ 
  187.     private int analyzeContentsTrendency() {  
  188.  
  189.         int negScore = 0;  
  190.         int posScore = 0;  
  191.  
  192.         if (negs != null && negs.size() > 0) {  
  193.  
  194.             for (Word word : negs) {  
  195.                 negScore += wordLoader.getNegWordMap().get(word.getString());  
  196.             }  
  197.  
  198.         }  
  199.  
  200.         if (poses != null && poses.size() > 0) {  
  201.  
  202.             for (Word word : poses) {  
  203.                 posScore += wordLoader.getPosWordMap().get(word.getString());  
  204.             }  
  205.         }  
  206.  
  207.         return posScore - negScore;  
  208.     }  
  209.  
  210.     /**  
  211.      * 交叉矩阵判断文本倾向性  
  212.      *   
  213.      * @return  
  214.      */ 
  215.     private boolean analyzeTrendencyByMatrix() {  
  216.  
  217.         if (rels == null || rels.size() == 0) {  
  218.             return false;  
  219.         }  
  220.  
  221.         if (negs == null || negs.size() == 0) {  
  222.             return false;  
  223.         }  
  224.  
  225.         for (int i = 0; i < rels.size(); i++) {  
  226.  
  227.             for (int j = 0; j < negs.size(); j++) {  
  228.  
  229.                 Word relWord = rels.get(i);  
  230.                 Word negWord = negs.get(j);  
  231.  
  232.                 if (relWord.getStartOffset() < negWord.getStartOffset()) {  
  233.  
  234.                     if (negWord.getStartOffset() - relWord.getStartOffset()  
  235.                             - relWord.getLength() < PS_THRESHOLD) {  
  236.  
  237.                         return true;  
  238.  
  239.                     }  
  240.  
  241.                 } else {  
  242.                     if (relWord.getStartOffset() - negWord.getStartOffset()  
  243.                             - negWord.getLength() < NS_THRESHOLD) {  
  244.                         return true;  
  245.                     }  
  246.                 }  
  247.  
  248.             }  
  249.  
  250.         }  
  251.  
  252.         return false;  
  253.  
  254.     }  
  255.  
  256.     /**  
  257.      * 先对标题进行分词  
  258.      *   
  259.      * @param reader  
  260.      * @throws IOException  
  261.      */ 
  262.     private void initTitleSegmentsMap(Reader reader) throws IOException {  
  263.  
  264.         segments = new HashMap<String, List<Word>>();  
  265.  
  266.         MMSeg mmSeg = new MMSeg(reader, seg);  
  267.  
  268.         Word word = null;  
  269.  
  270.         while ((word = mmSeg.next()) != null) {  
  271.  
  272.             if (segments.containsKey(word.getString())) {  
  273.  
  274.                 segments.get(word.getString()).add(word);  
  275.             }  
  276.  
  277.             List<Word> words = new ArrayList<Word>();  
  278.  
  279.             words.add(word);  
  280.  
  281.             segments.put(word.getString(), words);  
  282.  
  283.         }  
  284.     }  
  285.  
  286.     /**  
  287.      * 对正文进行分词  
  288.      *   
  289.      * @param reader  
  290.      * @throws IOException  
  291.      */ 
  292.     private void initSegmentsMap(Reader reader) throws IOException {  
  293.  
  294.         if (segments == null) {  
  295.             segments = new HashMap<String, List<Word>>();  
  296.         }  
  297.  
  298.         MMSeg mmSeg = new MMSeg(reader, seg);  
  299.  
  300.         Word word = null;  
  301.  
  302.         while ((word = mmSeg.next()) != null) {  
  303.  
  304.             if (segments.containsKey(word.getString())) {  
  305.  
  306.                 segments.get(word.getString()).add(word);  
  307.             }  
  308.  
  309.             List<Word> words = new ArrayList<Word>();  
  310.  
  311.             words.add(word);  
  312.  
  313.             segments.put(word.getString(), words);  
  314.  
  315.         }  
  316.  
  317.     }  
  318.  
  319.     /**  
  320.      * 解析负面词汇  
  321.      */ 
  322.     private void parseNegWordsMap() {  
  323.  
  324.         Map<String, Integer> negMap = wordLoader.getNegWordMap();  
  325.         Set<String> negKeys = negMap.keySet();  
  326.  
  327.         for (String negKey : negKeys) {  
  328.  
  329.             List<Word> negWords = segments.get(negKey);  
  330.  
  331.             if (negWords != null) {  
  332.  
  333.                 if (negs == null) {  
  334.                     negs = new ArrayList<Word>();  
  335.                 }  
  336.  
  337.                 negs.addAll(negWords);  
  338.  
  339.             }  
  340.  
  341.         }  
  342.  
  343.     }  
  344.  
  345.     /**  
  346.      * 解析正面词汇  
  347.      */ 
  348.     private void parsePosWordsMap() {  
  349.  
  350.         Map<String, Integer> posMap = wordLoader.getPosWordMap();  
  351.         Set<String> posKeys = posMap.keySet();  
  352.  
  353.         for (String posKey : posKeys) {  
  354.  
  355.             List<Word> posWords = segments.get(posKey);  
  356.  
  357.             if (posWords != null) {  
  358.  
  359.                 if (poses == null) {  
  360.                     poses = new ArrayList<Word>();  
  361.                 }  
  362.  
  363.                 poses.addAll(posWords);  
  364.  
  365.             }  
  366.  
  367.         }  
  368.     }  
  369.  
  370.     /**  
  371.      * 解析相关词汇  
  372.      */ 
  373.     private void parseRelWordsMap() {  
  374.  
  375.         List<String> refWords = wordLoader.getRefWordList();  
  376.  
  377.         for (String word : refWords) {  
  378.  
  379.             List<Word> relWords = segments.get(word);  
  380.  
  381.             if (relWords != null) {  
  382.  
  383.                 if (rels == null) {  
  384.                     rels = new ArrayList<Word>();  
  385.                 }  
  386.  
  387.                 rels.addAll(relWords);  
  388.  
  389.             }  
  390.         }  
  391.  
  392.     }  
  393.  
  394. }  

这里面用了一些策略:

  1. 先分析标题,如果标题中出现相关词汇,仅需判断正文倾向性即可。
  2. 如果标题中出现相关词汇,并且标题存在倾向,以标题倾向为准。
  3. 如果上述都不成立,则合并标题与正文,一起进行分词与情感词汇识别。
  4. 对于通篇识别为负面情感的文章需要进一步判断相关性。
  5. 采用距离矩阵的方式判断相关性。
  6. 需要设定正向最大距离阈值与反向最大距离阈值。

本文转自william_xu 51CTO博客,原文链接:http://blog.51cto.com/williamx/863110,如需转载请自行联系原作者