基于朴素贝叶斯分类器的文本分类算法(下)

 

源代码下载:NaviveBayesClassify.rar html

Prefacejava

文本的分类和聚类是一个比较有意思的话题,我之前也写过一篇blog基于K-Means的文本聚类算法》,加上最近读了几本数据挖掘和机器学习的书籍,所以很想写点东西来记录下学习的所得。算法

在本文的上半部分《基于朴素贝叶斯分类器的文本分类算法(上)》一文中简单介绍了贝叶斯学习的基本理论,这一篇将展现如何将该理论运用到中文文本分类中来,具体的文本分类原理就再也不介绍了,在上半部分有,也能够参见代码的注释。app

文本特征向量机器学习

文本特征向量能够描述为文本中的字/词构成的属性。例如给出文本:ide

Good good study,Day day up.性能

能够得到该文本的特征向量集:{ Good, good, study, Day, day , up.}学习

朴素贝叶斯模型是文本分类模型中的一种简单但性能优越的的分类模型。为了简化计算过程,假定各待分类文本特征变量是相互独立的,即朴素贝叶斯模型的假设。相互独立代表了全部特征变量之间的表述是没有关联的。如上例中,[good][study]这两个特征变量就是没有任何关联的。测试

在上例中,文本是英文,但因为中文自己是没有天然分割符(如空格之类符号),因此要得到中文文本的特征变量向量首先须要对文本进行中文分词网站

中文分词

      这里采用极易中文分词组件,这个中文分词组件能够无偿使用,提供Lucene接口,跨平台,性能可靠。

package com.vista;
import java.io.IOException;      
import jeasy.analysis.MMAnalyzer;

/* *
* 中文分词器
*/
public   class  ChineseSpliter 
{
    
/* *
    * 对给定的文本进行中文分词
    * @param text 给定的文本
    * @param splitToken 用于分割的标记,如"|"
    * @return 分词完毕的文本
    
*/
    
public   static  String split(String text,String splitToken)
    {
        String result 
=   null ;
        MMAnalyzer analyzer 
=   new  MMAnalyzer();      
        
try       
        {
            result 
=  analyzer.segment(text, splitToken);    
        }      
        
catch  (IOException e)      
        {     
            e.printStackTrace();     
        }     
        
return  result;
    }
}

停用词处理

      去掉文档中无心思的词语也是必须的一项工做,这里简单的定义了一些常见的停用词,并根据这些经常使用停用词在分词时进行判断。

package com.vista;

/* *
* 停用词处理器
* @author phinecos 

*/
public   class  StopWordsHandler 
{
    
private   static  String stopWordsList[]  = { " " " 咱们 " , " " , " 本身 " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , " " , "" }; // 经常使用停用词
     public   static  boolean IsStopWord(String word)
    {
        
for ( int  i = 0 ;i < stopWordsList.length; ++ i)
        {
            
if (word.equalsIgnoreCase(stopWordsList[i]))
                
return   true ;
        }
        
return   false ;
    }
}

训练集管理器

      咱们的系统首先须要从训练样本集中获得假设的先验几率和给定假设下观察到不一样数据的几率。

package  com.vista;
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.util.Properties;
import  java.util.logging.Level;
import  java.util.logging.Logger;
/**
* 训练集管理器
*/
public   class  TrainingDataManager 
{
    
private  String[] traningFileClassifications; // 训练语料分类集合
     private  File traningTextDir; // 训练语料存放目录
     private   static  String defaultPath  =   " D:\\TrainningSet " ;
    
    
public  TrainingDataManager() 
    {
        traningTextDir 
=   new  File(defaultPath);
        
if  ( ! traningTextDir.isDirectory()) 
        {
            
throw   new  IllegalArgumentException( " 训练语料库搜索失败! [ "   + defaultPath  +   " ] " );
        }
        
this .traningFileClassifications  =  traningTextDir.list();
    }
    
/**
    * 返回训练文本类别,这个类别就是目录名
    * 
@return  训练文本类别
    
*/
    
public  String[] getTraningClassifications() 
    {
        
return   this .traningFileClassifications;
    }
    
/**
    * 根据训练文本类别返回这个类别下的全部训练文本路径(full path)
    * 
@param  classification 给定的分类
    * 
@return  给定分类下全部文件的路径(full path)
    
*/
    
public  String[] getFilesPath(String classification) 
    {
        File classDir 
=   new  File(traningTextDir.getPath()  + File.separator  + classification);
        String[] ret 
=  classDir.list();
        
for  ( int  i  =   0 ; i  <  ret.length; i ++
        {
            ret[i] 
=  traningTextDir.getPath()  + File.separator  + classification  + File.separator  + ret[i];
        }
        
return  ret;
    }
    
/**
    * 返回给定路径的文本文件内容
    * 
@param  filePath 给定的文本文件路径
    * 
@return  文本内容
    * 
@throws  java.io.FileNotFoundException
    * 
@throws  java.io.IOException
    
*/
    
public   static  String getText(String filePath)  throws  FileNotFoundException,IOException 
    {
        InputStreamReader isReader 
= new  InputStreamReader( new  FileInputStream(filePath), " GBK " );
        BufferedReader reader 
=   new  BufferedReader(isReader);
        String aline;
        StringBuilder sb 
=   new  StringBuilder();
        
while  ((aline  =  reader.readLine())  !=   null )
        {
            sb.append(aline 
+   "   " );
        }
        isReader.close();
        reader.close();
        
return  sb.toString();
    }
    
/**
    * 返回训练文本集中全部的文本数目
    * 
@return  训练文本集中全部的文本数目
    
*/
    
public   int  getTrainingFileCount()
    {
        
int  ret  =   0 ;
        
for  ( int  i  =   0 ; i  <  traningFileClassifications.length; i ++ )
        {
            ret 
+= getTrainingFileCountOfClassification(traningFileClassifications[i]);
        }
        
return  ret;
    }
    
/**
    * 返回训练文本集中在给定分类下的训练文本数目
    * 
@param  classification 给定的分类
    * 
@return  训练文本集中在给定分类下的训练文本数目
    
*/
    
public   int  getTrainingFileCountOfClassification(String classification)
    {
        File classDir 
=   new  File(traningTextDir.getPath()  + File.separator  + classification);
        
return  classDir.list().length;
    }
    
/**
    * 返回给定分类中包含关键字/词的训练文本的数目
    * 
@param  classification 给定的分类
    * 
@param  key 给定的关键字/词
    * 
@return  给定分类中包含关键字/词的训练文本的数目
    
*/
    
public   int  getCountContainKeyOfClassification(String classification,String key) 
    {
        
int  ret  =   0 ;
        
try  
        {
            String[] filePath 
=  getFilesPath(classification);
            
for  ( int  j  =   0 ; j  <  filePath.length; j ++
            {
                String text 
=  getText(filePath[j]);
                
if  (text.contains(key)) 
                {
                    ret
++ ;
                }
            }
        }
        
catch  (FileNotFoundException ex) 
        {
        Logger.getLogger(TrainingDataManager.
class .getName()).log(Level.SEVERE,  null ,ex);
    
        } 
        
catch  (IOException ex)
        {
            Logger.getLogger(TrainingDataManager.
class .getName()).log(Level.SEVERE,  null ,ex);
        }
        
return  ret;
    }
}

先验几率

      先验几率是咱们须要计算的两大几率值之一

package  com.vista;
/**
* 先验几率计算
* <h3>先验几率计算</h3>
* P(c<sub>j</sub>)=N(C=c<sub>j</sub>)<b>/</b>N <br>
* 其中,N(C=c<sub>j</sub>)表示类别c<sub>j</sub>中的训练文本数量;
* N表示训练文本集总数量。
*/
public   class  PriorProbability 
{
    
private   static  TrainingDataManager tdm  = new  TrainingDataManager();
    
/**
    * 先验几率
    * 
@param  c 给定的分类
    * 
@return  给定条件下的先验几率
    
*/
    
public   static   float  calculatePc(String c)
    {
        
float  ret  =  0F;
        
float  Nc  =  tdm.getTrainingFileCountOfClassification(c);
        
float  N  =  tdm.getTrainingFileCount();
        ret 
=  Nc  /  N;
        
return  ret;
    }
}

分类条件几率

      这是另外一个影响因子,和先验几率一块儿来决定最终结果

package  com.vista;

/**
* <b>类</b>条件几率计算
*
* <h3>类条件几率</h3>
* P(x<sub>j</sub>|c<sub>j</sub>)=( N(X=x<sub>i</sub>, C=c<sub>j
* </sub>)+1 ) <b>/</b> ( N(C=c<sub>j</sub>)+M+V ) <br>
* 其中,N(X=x<sub>i</sub>, C=c<sub>j</sub>)表示类别c<sub>j</sub>中包含属性x<sub>
* i</sub>的训练文本数量;N(C=c<sub>j</sub>)表示类别c<sub>j</sub>中的训练文本数量;M值用于避免
* N(X=x<sub>i</sub>, C=c<sub>j</sub>)太小所引起的问题;V表示类别的总数。
*
* <h3>条件几率</h3>
* <b>定义</b> 设A, B是两个事件,且P(A)>0 称<br>
* <tt>P(B∣A)=P(AB)/P(A)</tt><br>
* 为在条件A下发生的条件事件B发生的条件几率。

*/

public   class  ClassConditionalProbability 
{
    
private   static  TrainingDataManager tdm  =   new  TrainingDataManager();
    
private   static   final   float  M  =  0F;
    
    
/**
    * 计算类条件几率
    * 
@param  x 给定的文本属性
    * 
@param  c 给定的分类
    * 
@return  给定条件下的类条件几率
    
*/
    
public   static   float  calculatePxc(String x, String c) 
    {
        
float  ret  =  0F;
        
float  Nxc  =  tdm.getCountContainKeyOfClassification(c, x);
        
float  Nc  =  tdm.getTrainingFileCountOfClassification(c);
        
float  V  =  tdm.getTraningClassifications().length;
        ret 
=  (Nxc  +   1 /  (Nc  +  M  +  V);  // 为了不出现0这样极端状况,进行加权处理
         return  ret;
    }
}

分类结果

      用来保存各个分类及其计算出的几率值,

package  com.vista;
/**
* 分类结果
*/
public   class  ClassifyResult 
{
    
public   double  probility; // 分类的几率
     public  String classification; // 分类
     public  ClassifyResult()
    {
        
this .probility  =   0 ;
        
this .classification  =   null ;
    }
}

朴素贝叶斯分类器

      利用样本数据集计算先验几率和各个文本向量属性在分类中的条件几率,从而计算出各个几率值,最后对各个几率值进行排序,选出最大的几率值,即为所属的分类。

package  com.vista;
import  com.vista.ChineseSpliter;
import  com.vista.ClassConditionalProbability;
import  com.vista.PriorProbability;
import  com.vista.TrainingDataManager;
import  com.vista.StopWordsHandler;
import  java.util.ArrayList;
import  java.util.Comparator;
import  java.util.List;
import  java.util.Vector;

/**
* 朴素贝叶斯分类器
*/
public   class  BayesClassifier 
{
    
private  TrainingDataManager tdm; // 训练集管理器
     private  String trainnigDataPath; // 训练集路径
     private   static   double  zoomFactor  =   10.0f ;
    
/**
    * 默认的构造器,初始化训练集
    
*/
    
public  BayesClassifier() 
    {
        tdm 
= new  TrainingDataManager();
    }

    
/**
    * 计算给定的文本属性向量X在给定的分类Cj中的类条件几率
    * <code>ClassConditionalProbability</code>连乘值
    * 
@param  X 给定的文本属性向量
    * 
@param  Cj 给定的类别
    * 
@return  分类条件几率连乘值,即<br>
    
*/
    
float  calcProd(String[] X, String Cj) 
    {
        
float  ret  =   1.0F ;
        
//  类条件几率连乘
         for  ( int  i  =   0 ; i  < X.length; i ++ )
        {
            String Xi 
=  X[i];
            
// 由于结果太小,所以在连乘以前放大10倍,这对最终结果并没有影响,由于咱们只是比较几率大小而已
            ret  *= ClassConditionalProbability.calculatePxc(Xi, Cj) * zoomFactor;
        }
        
//  再乘以先验几率
        ret  *=  PriorProbability.calculatePc(Cj);
        
return  ret;
    }
    
/**
    * 去掉停用词
    * 
@param  text 给定的文本
    * 
@return  去停用词后结果
    
*/
    
public  String[] DropStopWords(String[] oldWords)
    {
        Vector
< String >  v1  =   new  Vector < String > ();
        
for ( int  i = 0 ;i < oldWords.length; ++ i)
        {
            
if (StopWordsHandler.IsStopWord(oldWords[i]) == false )
            {
// 不是停用词
                v1.add(oldWords[i]);
            }
        }
        String[] newWords 
=   new  String[v1.size()];
        v1.toArray(newWords);
        
return  newWords;
    }
    
/**
    * 对给定的文本进行分类
    * 
@param  text 给定的文本
    * 
@return  分类结果
    
*/
    @SuppressWarnings(
" unchecked " )
    
public  String classify(String text) 
    {
        String[] terms 
=   null ;
        terms
=  ChineseSpliter.split(text,  "   " ).split( "   " ); // 中文分词处理(分词后结果可能还包含有停用词)
        terms  =  DropStopWords(terms); // 去掉停用词,以避免影响分类
        
        String[] Classes 
=  tdm.getTraningClassifications(); // 分类
         float  probility  =   0.0F ;
        List
< ClassifyResult >  crs  =   new  ArrayList < ClassifyResult > (); // 分类结果
         for  ( int  i  =   0 ; i  < Classes.length; i ++
        {
            String Ci 
=  Classes[i]; // 第i个分类
            probility  =  calcProd(terms, Ci); // 计算给定的文本属性向量terms在给定的分类Ci中的分类条件几率
            
// 保存分类结果
            ClassifyResult cr  =   new  ClassifyResult();
            cr.classification 
=  Ci; // 分类
            cr.probility  =  probility; // 关键字在分类的条件几率
            System.out.println( " In process. " );
            System.out.println(Ci 
+   " "   +  probility);
            crs.add(cr);
        }
        
// 对最后几率结果进行排序
        java.util.Collections.sort(crs, new  Comparator() 
        {
            
public   int  compare( final  Object o1, final  Object o2) 
            {
                
final  ClassifyResult m1  =  (ClassifyResult) o1;
                
final  ClassifyResult m2  =  (ClassifyResult) o2;
                
final   double  ret  =  m1.probility  -  m2.probility;
                
if  (ret  <   0
                {
                    
return   1 ;
                } 
                
else  
                {
                    
return   - 1 ;
                }
            }
        });
        
// 返回几率最大的分类
         return  crs.get( 0 ).classification;
    }
    
    
public   static   void  main(String[] args)
    {
        String text 
=   " 微软公司提出以446亿美圆的价格收购雅虎中国网2月1日报道 美联社消息,微软公司提出以446亿美圆现金加股票的价格收购搜索网站雅虎公司。微软提出以每股31美圆的价格收购雅虎。微软的收购报价较雅虎1月31日的收盘价19.18美圆溢价62%。微软公司称雅虎公司的股东能够选择以现金或股票进行交易。微软和雅虎公司在2006年末和2007年初已在寻求双方合做。而近两年,雅虎一直处于困境:市场份额下滑、运营业绩不佳、股价大幅下跌。对于力图在互联网市场有所做为的微软来讲,收购雅虎无疑是一条捷径,由于双方具备很是强的互补性。(小桥) " ;
        BayesClassifier classifier 
=   new  BayesClassifier(); // 构造Bayes分类器
        String result  =  classifier.classify(text); // 进行分类
        System.out.println( " 此项属于[ " + result + " ] " );
    }
}

训练集与分类测试

做为测试,这里选用Sogou实验室的文本分类数据,我只使用了mini版本。迷你版本有10个类别 ,共计100篇文章,总大小244KB

使用的测试文本:

微软公司提出以446亿美圆的价格收购雅虎

中国网2月1日报道 美联社消息,微软公司提出以446亿美圆现金加股票的价格收购搜索网站雅虎公司。

微软提出以每股31美圆的价格收购雅虎。微软的收购报价较雅虎1月31日的收盘价19
. 18美圆溢价62%。微软公司称雅虎公司的股东能够选择以现金或股票进行交易。

微软和雅虎公司在2006年末和2007年初已在寻求双方合做。而近两年,雅虎一直处于困境:市场份额下滑、运营业绩不佳、股价大幅下跌。对于力图在互联网市场有所做为的微软来讲,收购雅虎无疑是一条捷径,由于双方具备很是强的互补性。
( 小桥 )

使用mini版本的测试结果:

In process .
IT:
2.8119528E-5
In process
.
体育:
2.791735E-21
In process
.
健康:
3.3188528E-12
In process
.
军事:
2.532662E-19
In process
.
招聘:
2.3753596E-17
In process
.
教育:
4.2023427E-19
In process
.
文化:
6.0595915E-23
In process
.
旅游:
5.1286412E-17
In process
.
汽车:
4.085446E-8
In process
.
财经:
3.7337095E-10 此项属于[IT]