一、思路分析
先来分析一下思路:
本项目所用的语料库是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进行分类
分词器:
- "font-family:'KaiTi_GB2312';">package tfidf;
-
- import java.io.BufferedReader;
- import java.io.File;
- import java.io.FileReader;
- import java.io.IOException;
-
- import org.apache.lucene.analysis.TokenStream;
- import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
- import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
- import org.apache.lucene.analysis.util.CharArraySet;
- import org.apache.lucene.util.Version;
-
- public class MMAnalyzer {
- public MMAnalyzer() {
-
- }
- public String segment(String splitText, String str) throws IOException{
-
- BufferedReader reader = new BufferedReader(new FileReader(new File("F:/Porject/Hadoop project/TDIDF/TFIDF/TFIDF/stop_words.txt")));
- String line = reader.readLine();
- String wordString = "";
- while(line!=null){
-
- wordString = wordString + " " + line;
- line = reader.readLine();
- }
- String[] self_stop_words = wordString.split(" ");
-
- CharArraySet cas = new CharArraySet(Version.LUCENE_46, 0, true);
- for (int i = 0; i < self_stop_words.length; i++) {
- cas.add(self_stop_words[i]);
- }
- @SuppressWarnings("resource")
- SmartChineseAnalyzer sca = new SmartChineseAnalyzer(Version.LUCENE_46, cas);
-
- TokenStream ts = sca.tokenStream("field", splitText);
-
-
-
- ts.reset();
-
-
- String words = "";
- while (ts.incrementToken()) {
- String word = ts.getAttribute(CharTermAttribute.class).toString();
- System.out.println(word);
- words = words + word + ' ';
-
- }
- ts.end();
- ts.close();
-
-
- return words;
- }
- }
这里的分词采用的是SmartChineseAnalyzer分词器,加上自己去网上找了点stopwords的素材,通过这样的方式对微博文本进行分词
获取语料库信息,并计算分词的tfidf:
- "font-family:'KaiTi_GB2312';">package tfidf;
-
- import java.io.BufferedReader;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.UnsupportedEncodingException;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- public class ReadFiles {
-
- private static List fileList = new ArrayList();
- private static HashMap> allTheTf = new HashMap>();
- private static HashMap> allTheNormalTF = new HashMap>();
-
- public static List readDirs(String filepath) throws FileNotFoundException, IOException {
- try {
- File file = new File(filepath);
- if (!file.isDirectory()) {
- System.out.println("Please input the name of the file:");
- System.out.println("filepath: " + file.getAbsolutePath());
- } else if (file.isDirectory()) {
- String[] filelist = file.list();
- for (int i = 0; i < filelist.length; i++) {
- File readfile = new File(filepath + "\\" + filelist[i]);
- if (!readfile.isDirectory()) {
-
- fileList.add(readfile.getAbsolutePath());
- } else if (readfile.isDirectory()) {
- readDirs(filepath + "\\" + filelist[i]);
- }
- }
- }
-
- } catch (FileNotFoundException e) {
- System.out.println(e.getMessage());
- }
- return fileList;
- }
-
- public static String readFiles(String file) throws FileNotFoundException, IOException {
- StringBuffer sb = new StringBuffer();
- InputStreamReader is = new InputStreamReader(new FileInputStream(file), "utf-8");
- BufferedReader br = new BufferedReader(is);
- String line = br.readLine();
- while (line != null) {
- sb.append(line).append("\r\n");
- line = br.readLine();
- }
- br.close();
- return sb.toString();
- }
-
- public static String[] cutWord(String file) throws IOException {
- String[] cutWordResult = null;
- String text = ReadFiles.readFiles(file);
- MMAnalyzer analyzer = new MMAnalyzer();
-
-
- String tempCutWordResult = analyzer.segment(text, " ");
- cutWordResult = tempCutWordResult.split(" ");
- return cutWordResult;
- }
-
- public static HashMap tf(String[] cutWordResult) {
- HashMap tf = new HashMap();
- int wordNum = cutWordResult.length;
- int wordtf = 0;
- for (int i = 0; i < wordNum; i++) {
- wordtf = 0;
- for (int j = 0; j < wordNum; j++) {
- if (cutWordResult[i] != " " && i != j) {
- if (cutWordResult[i].equals(cutWordResult[j])) {
- cutWordResult[j] = " ";
- wordtf++;
- }
- }
- }
- if (cutWordResult[i] != " ") {
- tf.put(cutWordResult[i], (new Float(++wordtf)) / wordNum);
- cutWordResult[i] = " ";
- }
- }
- return tf;
- }
-
- public static HashMap normalTF(String[] cutWordResult) {
- HashMap tfNormal = new HashMap();
- int wordNum = cutWordResult.length;
- int wordtf = 0;
- for (int i = 0; i < wordNum; i++) {
- wordtf = 0;
- if (cutWordResult[i] != " ") {
- for (int j = 0; j < wordNum; j++) {
- if (i != j) {
- if (cutWordResult[i].equals(cutWordResult[j])) {
- cutWordResult[j] = " ";
- wordtf++;
-
- }
- }
- }
- tfNormal.put(cutWordResult[i], ++wordtf);
- cutWordResult[i] = " ";
- }
- }
- return tfNormal;
- }
-
- public static Map> tfOfAll(String dir) throws IOException {
- List fileList = ReadFiles.readDirs(dir);
- for (String file : fileList) {
- HashMap dict = new HashMap();
- dict = ReadFiles.tf(ReadFiles.cutWord(file));
- allTheTf.put(file, dict);
- }
- return allTheTf;
- }
-
- public static Map> NormalTFOfAll(String dir) throws IOException {
- List fileList = ReadFiles.readDirs(dir);
- for (int i = 0; i < fileList.size(); i++) {
- HashMap dict = new HashMap();
- dict = ReadFiles.normalTF(ReadFiles.cutWord(fileList.get(i)));
- allTheNormalTF.put(fileList.get(i), dict);
- }
- return allTheNormalTF;
- }
-
- public static Map idf(String dir) throws FileNotFoundException, UnsupportedEncodingException, IOException {
-
- Map idf = new HashMap();
-
- List located = new ArrayList();
- Map> allTheNormalTF = ReadFiles.NormalTFOfAll(dir);
- float Dt = 1;
- float D = allTheNormalTF.size();
- List key = fileList;
- Map> tfInIdf = allTheNormalTF;
-
- for (int i = 0; i < D; i++) {
-
- HashMap temp = tfInIdf.get(key.get(i));
-
- for (String word : temp.keySet()) {
- Dt = 1;
- if (!(located.contains(word))) {
- for (int k = 0; k < D; k++) {
- if (k != i) {
- HashMap temp2 = tfInIdf.get(key.get(k));
- if (temp2.keySet().contains(word)) {
- located.add(word);
- Dt = Dt + 1;
- continue;
- }
- }
- }
- idf.put(word, Log.log((1 + D) / Dt, 10));
- }
- }
- }
- return idf;
- }
-
- public static Map> tfidf(String dir) throws IOException {
-
- Map idf = ReadFiles.idf(dir);
- Map> tf = ReadFiles.tfOfAll(dir);
-
- for (String file : tf.keySet()) {
- Map singelFile = tf.get(file);
- for (String word : singelFile.keySet()) {
- singelFile.put(word, (idf.get(word)) * singelFile.get(word));
- }
- }
- return tf;
- }
- }
划重点!!!
在本项目中计算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。这句话被分为pos类,发生误判!
注意:因为某一个词的idf不会因为类别而有所不同,换句话说在整个语料库中某个词的idf只有一个,不像它的tf有多少个类别就有多少个tf值(比如在第一部分思路分析中,“开心”的idf就等于log(500/70),而它的tf分别等于1/2(pos)和1/5(neg))。根据tfidf=tf * idf,只改变某个词的idf值,这个词的所有tfidf都会以相同的倍数变化。
计算:
- "font-family:'KaiTi_GB2312';">package tfidf;
-
- public class Log {
-
- public static float log(float value, float base) {
- return (float) (Math.log(value) / Math.log(base));
- }
- }
分类:
- "font-family:'KaiTi_GB2312';">package tfidf;
-
- import java.io.IOException;
- import java.util.HashMap;
- import java.util.Map;
-
- public class Classifier {
- public static String classify(String text, Map> tfidf) throws IOException{
- String[] result = null;
- MMAnalyzer cutTextToWords = new MMAnalyzer();
- String tmpResult = cutTextToWords.segment(text, " ");
- result = tmpResult.split(" ");
- int len = result.length;
- double[] finalScores = new double[2];
- String[] name = new String[2];
- int k = 0;
- for(String fileName:tfidf.keySet()){
-
- double scores = 0;
- HashMap perFile = tfidf.get(fileName);
- for (int i = 0; i
- if (perFile.containsKey(result[i])){
- scores += perFile.get(result[i]);
- }
- }
- finalScores[k] = scores;
- name[k] = fileName;
- System.out.println(name[k]+" socres is: "+finalScores[k]);
- k++;
- }
- if(finalScores[0]>=finalScores[1]){
-
- return name[0];
- }else{
-
- return name[1];
- }
-
- }
- }
这里我得重点说一下 我这里的微博情感分类只有两类,正向和负向的情感分类,因此在读取的文件的时候只有类似neg.txt和pos.txt的两个提供情感分类语料库的文件。通过计算每个单词在每个语料库中的TF-IDF得分 ,再将待判断的句子中所有的分词的TFIDF得分相加并比较,得分大的即为分类的结果。
主函数:
- "font-family:'KaiTi_GB2312';">package tfidf;
-
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.File;
- import java.io.FileReader;
- import java.io.FileWriter;
- import java.io.IOException;
- import java.util.HashMap;
- import java.util.Map;
-
- public class Main {
-
- public static void main(String[] args) throws IOException {
-
- BufferedReader reader = new BufferedReader(new FileReader(new File("h:/test.txt")));
- BufferedWriter writer = new BufferedWriter(new FileWriter(new File("h:/testResult.txt"), true));
- String line = reader.readLine();
- Map> tfidf = ReadFiles.tfidf("F:/Porject/Hadoop project/TDIDF/TFIDF/TFIDF/dir");
-
- while(line!=null){
- String bestFile = Classifier.classify(line, tfidf);
- writer.write(bestFile+"\t"+line);
- writer.flush();
- writer.newLine();
- System.out.println(bestFile+"\t"+line);
- line = reader.readLine();
- }
-
- reader.close();
- writer.close();
- System.out.println("FINISHED!!!!");
- }
- }
test.txt为待分类的数据 testResult.txt存储分类之后的数据结果
- "font-family:'KaiTi_GB2312';">F:/Porject/Hadoop project/TDIDF/TFIDF/TFIDF/dir为存放分类语料库的目录,在我的这个目录下存放了
- "font-family:'KaiTi_GB2312';">neg.txt pos.txt的文件,里面分别存放了负向和正向的已经分类好的微博语料。
三、结果分析
我的这个分类效果自己测试之后发现并不是很好,自己总结了一下主要原因 :
首先在分词上stopwords的文本更加完善的话会有更好的效果,还有就是一个更重要的就是训练分类器的语料库的微博内容,它的质量直接影响着分类的好坏,因为从朴素贝叶斯算法的算理就可以明白这个道理,这个分分类的模型就是通过计算先验概率然后计算后验概率,然后进行分类。所以要改善这个分类的效果可以从这两个方面进行改善。
另外就是刚刚提到的idf的计算方式。如果采用严格的idf计算公式进行计算,此算法的分类效果肯定会更好。
还有一点要说的就是,我这个实时的分类,是先计算语料库中的先验概率,然后利用计算好的先验概率进行分类,并没有先把训练语料库中的先验概率先计算出来再存储,下次利用这个先验概率去分类的时候直接去读取这个文件即可。为了提高效率,不用每次在进行分类的时候都去对同一个语料库进行TDIDF计算,那可以将其计算的TFIDF存储起来,使用的时候读取即可,这样可以提高效率。