基於樸素貝葉斯分類器的文本分類算法(下)

 

源代碼下載: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]
相關文章
相關標籤/搜索