利用TFIDF进行实时微博情感分类

一、思路分析

先来分析一下思路:

本项目所用的语料库是pos.txt和neg.txt两个文件,分别代表pos(积极)和neg(消极)类别,文件中有很多条已经分好类的微博,一整行为一条。

                                

1、计算tf

tf应该分类别计算。分别计算某个词在每个类别中的tf。这是什么意思呢?我们往下看。

某一个词在某种类别的tf=这类文件中这个词出现的次数/这类文件的总词数

比如:“开心”在pos类别中出现了50次,pos中一共有100个词。

          “开心”在neg类别中出现了30次,neg中一共有150个词。

      则“开心”在pos中的tf=50/100=1/2

           “开心”在neg中的tf=30/150=1/5

2、计算idf

idf应该以总的微博条数作为计算范围(但是本项目不是这样做的,原因稍后分析)。不理解?没关系,继续往下看。

idf表示一个词语的普遍重要程度。

在语料库中,每个词的区分能力是不一样的,如果包含某一个词的微博条数越少,则说明该词具有很好的区分能力,其idf就越大。

一般来说,带有强烈感情色彩的词比中性词具有更好的区分能力,其idf一般越大。

某个词的idf=log(微博总条数/出现该词的微博条数)

比如:在pos和neg中“开心”一共出现在70条微博中,而pos和neg加起来一共有500条微博

则“开心”的idf=log(500/70)

idf的不足:有时候,若一个词在一个类别中频繁的出现,说明该词条能很好的代表这个类的特征,具有很好的区分能力。但随着它出现的次数的增多,idf会变小,idf会变小表明其区分能力弱,这与实际情况不符。

3、计算tfidf

与tf一样tfidf也是以类为单位。

某个词在某种类别中的tfidf=这个词在这个类别中的tf * 这个词的idf

比如:“开心”在pos中的tfidf=1/2 * log(50/7)

        “开心”在neg中的tfidf=1/5 * log(50/7)

4、输入测试微博,进行分类

测试微博:我很开心和满足!

分词结果:“开心”、“满足”

通过语料库得出 :“开心”在pos中的tfidf=1/2 * log(50/7)

                            “开心”在neg中的tfidf=1/5 * log(50/7)

                            “满足”在pos中的tfidf=0.6

                                满足”在neg中的tfidf=0.5

    这句话的得分等于每个词的tfidf之和。在哪个类别中得分更高,这句话就被分为哪一类。    

    所以这句话的pos得分:P=1/2 * log(50/7)+ 0.6

                      neg得分:N=1/5 * log(50/7)+ 0.5

   因为P>N,所以这句话被归为pos类。


二、算法的程序实现

我们再来看一下如何写程序:

分类中的几个步骤

1 对我们的语料库(训练文本)进行分词

2 对分词之后的文本进行TF-IDF的计算(TF-IDF介绍可以参考这边文章http://blog.csdn.net/yqlakers/article/details/70888897)

3 利用计算好的TF-IDF进行分类


分词器:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';">package tfidf;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.File;  
  5. import java.io.FileReader;  
  6. import java.io.IOException;  
  7.   
  8. import org.apache.lucene.analysis.TokenStream;  
  9. import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;  
  10. import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;  
  11. import org.apache.lucene.analysis.util.CharArraySet;  
  12. import org.apache.lucene.util.Version;  
  13.   
  14. public class MMAnalyzer {  
  15.     public MMAnalyzer() {  
  16.         // TODO Auto-generated constructor stub  
  17.     }  
  18.     public String segment(String splitText, String str) throws IOException{  
  19.           
  20.         BufferedReader reader = new BufferedReader(new FileReader(new File("F:/Porject/Hadoop project/TDIDF/TFIDF/TFIDF/stop_words.txt")));  
  21.         String line = reader.readLine();  
  22.         String wordString = "";  
  23.         while(line!=null){  
  24.             //System.out.println(line);  
  25.             wordString = wordString + " " + line;  
  26.             line = reader.readLine();  
  27.         }  
  28.         String[] self_stop_words = wordString.split(" ");  
  29.         //String[] self_stop_words = { "我","你","他","它","她","的", "了", "呢", ",", "0", ":", ",", "是", "流","!"};    
  30.         CharArraySet cas = new CharArraySet(Version.LUCENE_46, 0true);    
  31.         for (int i = 0; i < self_stop_words.length; i++) {    
  32.             cas.add(self_stop_words[i]);    
  33.         }  
  34.         @SuppressWarnings("resource")  
  35.         SmartChineseAnalyzer sca = new SmartChineseAnalyzer(Version.LUCENE_46, cas);    
  36.         //分词器做好处理之后得到的一个流,这个流中存储了分词的各种信息.可以通过TokenStream有效的获取到分词单元  
  37.         TokenStream ts = sca.tokenStream("field", splitText);    
  38.         // 语汇单元对应的文本  
  39.         //CharTermAttribute ch = ts.addAttribute(CharTermAttribute.class);    
  40.         //Resets this stream to the beginning.  
  41.         ts.reset();    
  42.         // 递归处理所有语汇单元  
  43.         //Consumers (i.e., IndexWriter) use this method to advance the stream to the next token.  
  44.         String words = "";  
  45.         while (ts.incrementToken()) {    
  46.             String word = ts.getAttribute(CharTermAttribute.class).toString();  
  47.             System.out.println(word);  
  48.             words = words + word + ' ';  
  49.             //System.out.println(ch.toString());    
  50.         }    
  51.         ts.end();    
  52.         ts.close();    
  53.           
  54.           
  55.         return words;  
  56.     }  
  57. }</span>  

这里的分词采用的是SmartChineseAnalyzer分词器,加上自己去网上找了点stopwords的素材,通过这样的方式对微博文本进行分词



获取语料库信息,并计算分词的tfidf:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';">package tfidf;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.File;  
  5. import java.io.FileInputStream;  
  6. import java.io.FileNotFoundException;  
  7. import java.io.IOException;  
  8. import java.io.InputStreamReader;  
  9. import java.io.UnsupportedEncodingException;  
  10. import java.util.ArrayList;  
  11. import java.util.HashMap;  
  12. import java.util.List;  
  13. import java.util.Map;  
  14.   
  15. public class ReadFiles {  
  16.   
  17.     private static List<String> fileList = new ArrayList<String>();  
  18.     private static HashMap<String, HashMap<String, Float>> allTheTf = new HashMap<String, HashMap<String, Float>>();  
  19.     private static HashMap<String, HashMap<String, Integer>> allTheNormalTF = new HashMap<String, HashMap<String, Integer>>();  
  20.   
  21.     public static List<String> readDirs(String filepath) throws FileNotFoundException, IOException {  
  22.         try {  
  23.             File file = new File(filepath);  
  24.             if (!file.isDirectory()) {  
  25.                 System.out.println("Please input the name of the file:");  
  26.                 System.out.println("filepath: " + file.getAbsolutePath());  
  27.             } else if (file.isDirectory()) {  
  28.                 String[] filelist = file.list();  
  29.                 for (int i = 0; i < filelist.length; i++) {  
  30.                     File readfile = new File(filepath + "\\" + filelist[i]);  
  31.                     if (!readfile.isDirectory()) {  
  32.                         //System.out.println("filepath: " + readfile.getAbsolutePath());  
  33.                         fileList.add(readfile.getAbsolutePath());  
  34.                     } else if (readfile.isDirectory()) {  
  35.                         readDirs(filepath + "\\" + filelist[i]);  
  36.                     }  
  37.                 }  
  38.             }  
  39.   
  40.         } catch (FileNotFoundException e) {  
  41.             System.out.println(e.getMessage());  
  42.         }  
  43.         return fileList;  
  44.     }  
  45.   
  46.     public static String readFiles(String file) throws FileNotFoundException, IOException {  
  47.         StringBuffer sb = new StringBuffer();  
  48.         InputStreamReader is = new InputStreamReader(new FileInputStream(file), "utf-8");  
  49.         BufferedReader br = new BufferedReader(is);  
  50.         String line = br.readLine();  
  51.         while (line != null) {  
  52.             sb.append(line).append("\r\n");  
  53.             line = br.readLine();  
  54.         }  
  55.         br.close();  
  56.         return sb.toString();  
  57.     }  
  58.   
  59.     public static String[] cutWord(String file) throws IOException {  
  60.         String[] cutWordResult = null;  
  61.         String text = ReadFiles.readFiles(file);  
  62.         MMAnalyzer analyzer = new MMAnalyzer();  
  63.         //System.out.println("file content: "+text);  
  64.         //System.out.println("cutWordResult: "+analyzer.segment(text, " "));  
  65.         String tempCutWordResult = analyzer.segment(text, " ");  
  66.         cutWordResult = tempCutWordResult.split(" ");  
  67.         return cutWordResult;  
  68.     }  
  69.     //用来计算tf  
  70.     public static HashMap<String, Float> tf(String[] cutWordResult) {  
  71.         HashMap<String, Float> tf = new HashMap<String, Float>();  
  72.         int wordNum = cutWordResult.length;  
  73.         int wordtf = 0;  
  74.         for (int i = 0; i < wordNum; i++) {  
  75.             wordtf = 0;  
  76.             for (int j = 0; j < wordNum; j++) {  
  77.                 if (cutWordResult[i] != " " && i != j) {  
  78.                     if (cutWordResult[i].equals(cutWordResult[j])) {  
  79.                         cutWordResult[j] = " ";  
  80.                         wordtf++;  
  81.                     }  
  82.                 }  
  83.             }  
  84.             if (cutWordResult[i] != " ") {  
  85.                 tf.put(cutWordResult[i], (new Float(++wordtf)) / wordNum);  
  86.                 cutWordResult[i] = " ";  
  87.             }  
  88.         }  
  89.         return tf;  
  90.     }  
  91.       
  92.     public static HashMap<String, Integer> normalTF(String[] cutWordResult) {  
  93.         HashMap<String, Integer> tfNormal = new HashMap<String, Integer>();  
  94.         int wordNum = cutWordResult.length;  
  95.         int wordtf = 0;  
  96.         for (int i = 0; i < wordNum; i++) {  
  97.             wordtf = 0;  
  98.             if (cutWordResult[i] != " ") {  
  99.                 for (int j = 0; j < wordNum; j++) {  
  100.                     if (i != j) {  
  101.                         if (cutWordResult[i].equals(cutWordResult[j])) {  
  102.                             cutWordResult[j] = " ";  
  103.                             wordtf++;  
  104.   
  105.                         }  
  106.                     }  
  107.                 }  
  108.                 tfNormal.put(cutWordResult[i], ++wordtf);  
  109.                 cutWordResult[i] = " ";  
  110.             }  
  111.         }  
  112.         return tfNormal;  
  113.     }  
  114.       
  115.     public static Map<String, HashMap<String, Float>> tfOfAll(String dir) throws IOException {  
  116.         List<String> fileList = ReadFiles.readDirs(dir);  
  117.         for (String file : fileList) {  
  118.             HashMap<String, Float> dict = new HashMap<String, Float>();  
  119.             dict = ReadFiles.tf(ReadFiles.cutWord(file));  
  120.             allTheTf.put(file, dict);  
  121.         }  
  122.         return allTheTf;  
  123.     }  
  124.   
  125.     public static Map<String, HashMap<String, Integer>> NormalTFOfAll(String dir) throws IOException {  
  126.         List<String> fileList = ReadFiles.readDirs(dir);  
  127.         for (int i = 0; i < fileList.size(); i++) {  
  128.             HashMap<String, Integer> dict = new HashMap<String, Integer>();  
  129.             dict = ReadFiles.normalTF(ReadFiles.cutWord(fileList.get(i)));  
  130.             allTheNormalTF.put(fileList.get(i), dict);  
  131.         }  
  132.         return allTheNormalTF;  
  133.     }  
  134.   
  135.     public static Map<String, Float> idf(String dir) throws FileNotFoundException, UnsupportedEncodingException, IOException {  
  136.           
  137.         Map<String, Float> idf = new HashMap<String, Float>();  
  138.   
  139.         List<String> located = new ArrayList<String>();  
  140.         Map<String, HashMap<String, Integer>> allTheNormalTF = ReadFiles.NormalTFOfAll(dir);  
  141.         float Dt = 1;  
  142.         float D = allTheNormalTF.size();  
  143.         List<String> key = fileList;  
  144.         Map<String, HashMap<String, Integer>> tfInIdf = allTheNormalTF;  
  145.           
  146.         for (int i = 0; i < D; i++) {  
  147.               
  148.             HashMap<String, Integer> temp = tfInIdf.get(key.get(i));  
  149.               
  150.             for (String word : temp.keySet()) {  
  151.                 Dt = 1;  
  152.                 if (!(located.contains(word))) {  
  153.                     for (int k = 0; k < D; k++) {  
  154.                         if (k != i) {  
  155.                             HashMap<String, Integer> temp2 = tfInIdf.get(key.get(k));  
  156.                             if (temp2.keySet().contains(word)) {  
  157.                                 located.add(word);  
  158.                                 Dt = Dt + 1;  
  159.                                 continue;  
  160.                             }  
  161.                         }  
  162.                     }  
  163.                     idf.put(word, Log.log((1 + D) / Dt, 10));  
  164.                 }  
  165.             }  
  166.         }  
  167.         return idf;  
  168.     }  
  169.   
  170.     public static Map<String, HashMap<String, Float>> tfidf(String dir) throws IOException {  
  171.   
  172.         Map<String, Float> idf = ReadFiles.idf(dir);  
  173.         Map<String, HashMap<String, Float>> tf = ReadFiles.tfOfAll(dir);  
  174.   
  175.         for (String file : tf.keySet()) {  
  176.             Map<String, Float> singelFile = tf.get(file);  
  177.             for (String word : singelFile.keySet()) {  
  178.                 singelFile.put(word, (idf.get(word)) * singelFile.get(word));  
  179.             }  
  180.         }  
  181.         return tf;  
  182.     }  
  183. }</span>  

划重点!!!

在本项目中计算idf时,并不是使用的公式某个词的idf=log(微博总条数/出现该词的微博条数),而是使用下面这个公式

某个词的idf=log(文件的总数/出现该词的文件数)

这里的文件指的就是pos.txt和neg.txt两个类别文件

这样做可以减化程序,但idf计算得很粗糙,牺牲了分类的一部分精确性。我们做如下分析

还是以“我很开心和满足”这句话为例

假设“开心”的tfidf为5(pos)和1(neg),“满足”的tfidf为2(pos)和8(neg)

这句话的pos得分为5+2=7,neg得分为1+8=9。这句话被分为neg类。

如果改变“开心”idf(使idf扩大3倍),使得它的tfidf变为15(pos)和3(neg),“满足”不变

则这句话的pos得分为15+2=17,neg得分为3+8=11。这句话被分为neg类,发生误判!

注意:因为某一个词的idf不会因为类别而有所不同,换句话说在整个语料库中某个词的idf只有一个,不像它的tf有多少个类别就有多少个tf值(比如在第一部分思路分析中,“开心”的idf就等于log(500/70),而它的tf分别等于1/2(pos)和1/5(neg))。根据tfidf=tf * idf,只改变某个词的idf值,这个词的所有tfidf都会以相同的倍数变化。


计算:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';">package tfidf;  
  2.   
  3. public class Log {  
  4.   
  5.     public static float log(float value, float base) {  
  6.         return (float) (Math.log(value) / Math.log(base));  
  7.     }  
  8. </span>}  


分类:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';">package tfidf;  
  2.   
  3. import java.io.IOException;  
  4. import java.util.HashMap;  
  5. import java.util.Map;  
  6.   
  7. public class Classifier {  
  8.     public static String classify(String text, Map<String, HashMap<String, Float>> tfidf) throws IOException{  
  9.         String[] result = null;  
  10.         MMAnalyzer cutTextToWords = new MMAnalyzer();  
  11.         String tmpResult = cutTextToWords.segment(text, " ");  
  12.         result = tmpResult.split(" ");  
  13.         int len = result.length;  
  14.         double[] finalScores = new double[2];  
  15.         String[] name = new String[2];  
  16.         int k = 0;  
  17.         for(String fileName:tfidf.keySet()){  
  18.             //System.out.println(fileName);  
  19.             double scores = 0;  
  20.             HashMap<String, Float> perFile = tfidf.get(fileName);  
  21.             for (int i = 0; i<len; i++){  
  22.                 if (perFile.containsKey(result[i])){  
  23.                     scores += perFile.get(result[i]);  
  24.                 }  
  25.             }  
  26.             finalScores[k] = scores;  
  27.             name[k] = fileName;  
  28.             System.out.println(name[k]+" socres is: "+finalScores[k]);  
  29.             k++;  
  30.         }  
  31.         if(finalScores[0]>=finalScores[1]){  
  32.             //System.out.println(name[0]);  
  33.             return name[0];  
  34.         }else{  
  35.             //System.out.println(name[1]);  
  36.             return name[1];  
  37.         }  
  38.           
  39.     }  
  40. }</span>  

这里我得重点说一下 我这里的微博情感分类只有两类,正向和负向的情感分类,因此在读取的文件的时候只有类似neg.txt和pos.txt的两个提供情感分类语料库的文件。通过计算每个单词在每个语料库中的TF-IDF得分 ,再将待判断的句子中所有的分词的TFIDF得分相加并比较,得分大的即为分类的结果。


主函数:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';">package tfidf;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.BufferedWriter;  
  5. import java.io.File;  
  6. import java.io.FileReader;  
  7. import java.io.FileWriter;  
  8. import java.io.IOException;  
  9. import java.util.HashMap;  
  10. import java.util.Map;  
  11.   
  12. public class Main {  
  13.   
  14.     public static void main(String[] args) throws IOException {  
  15.       
  16.         BufferedReader reader = new BufferedReader(new FileReader(new File("h:/test.txt")));  
  17.         BufferedWriter writer = new BufferedWriter(new FileWriter(new File("h:/testResult.txt"), true));  
  18.         String line = reader.readLine();  
  19.         Map<String, HashMap<String, Float>> tfidf = ReadFiles.tfidf("F:/Porject/Hadoop project/TDIDF/TFIDF/TFIDF/dir");  
  20.           
  21.         while(line!=null){   
  22.             String bestFile = Classifier.classify(line, tfidf);  
  23.             writer.write(bestFile+"\t"+line);  
  24.             writer.flush();  
  25.             writer.newLine();  
  26.             System.out.println(bestFile+"\t"+line);  
  27.             line = reader.readLine();    
  28.         }  
  29.           
  30.         reader.close();  
  31.         writer.close();  
  32.         System.out.println("FINISHED!!!!");  
  33.     }  
  34. }</span>  
test.txt为待分类的数据 testResult.txt存储分类之后的数据结果
[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';">F:/Porject/Hadoop project/TDIDF/TFIDF/TFIDF/dir为存放分类语料库的目录,在我的这个目录下存放了</span>  
[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';">neg.txt pos.txt的文件,里面分别存放了负向和正向的已经分类好的微博语料。</span>  

三、结果分析

我的这个分类效果自己测试之后发现并不是很好,自己总结了一下主要原因 :

   首先在分词上stopwords的文本更加完善的话会有更好的效果,还有就是一个更重要的就是训练分类器的语料库的微博内容,它的质量直接影响着分类的好坏,因为从朴素贝叶斯算法的算理就可以明白这个道理,这个分分类的模型就是通过计算先验概率然后计算后验概率,然后进行分类。所以要改善这个分类的效果可以从这两个方面进行改善。

    另外就是刚刚提到的idf的计算方式。如果采用严格的idf计算公式进行计算,此算法的分类效果肯定会更好。

    还有一点要说的就是,我这个实时的分类,是先计算语料库中的先验概率,然后利用计算好的先验概率进行分类,并没有先把训练语料库中的先验概率先计算出来再存储,下次利用这个先验概率去分类的时候直接去读取这个文件即可。为了提高效率,不用每次在进行分类的时候都去对同一个语料库进行TDIDF计算,那可以将其计算的TFIDF存储起来,使用的时候读取即可,这样可以提高效率。

猜你喜欢

转载自blog.csdn.net/sofuzi/article/details/80338272