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

 

源代码下载:NaviveBayesClassify.rar 

Preface

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

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

文本特征向量

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

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]
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值