本文转载自:http://www.cnblogs.com/phinecos/archive/2008/10/21/1316044.html,谢谢原作者!
源代码下载:NaviveBayesClassify.rar
Preface
文本的分类和聚类是一个比较有意思的话题,我以前也写过一篇blog《基于K-Means的文本聚类算法》,加上最近读了几本数据挖掘和机器学习的书籍,因此很想写点东西来记录下学习的所得。
在本文的上半部分《基于朴素贝叶斯分类器的文本分类算法(上)》一文中简单介绍了贝叶斯学习的基本理论,这一篇将展示如何将该理论运用到中文文本分类中来,具体的文本分类原理就不再介绍了,在上半部分有,也可以参见代码的注释。
文本特征向量
文本特征向量可以描述为文本中的字/词构成的属性。例如给出文本:
Good good study,Day day up.
可以获得该文本的特征向量集:{ Good, good, study, Day, day , up.}
朴素贝叶斯模型是文本分类模型中的一种简单但性能优越的的分类模型。为了简化计算过程,假定各待分类文本特征变量是相互独立的,即“朴素贝叶斯模型的假设”。相互独立表明了所有特征变量之间的表述是没有关联的。如上例中,[good]和[study]这两个特征变量就是没有任何关联的。
在上例中,文本是英文,但由于中文本身是没有自然分割符(如空格之类符号),所以要获得中文文本的特征变量向量首先需要对文本进行中文分词
中文分词
这里采用极易中文分词组件,这个中文分词组件可以免费使用,提供Lucene接口,跨平台,性能可靠。
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;
}
}
停用词处理
去掉文档中无意思的词语也是必须的一项工作,这里简单的定义了一些常见的停用词,并根据这些常用停用词在分词时进行判断。
/* *
* 停用词处理器
* @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 ;
}
}
训练集管理器
我们的系统首先需要从训练样本集中得到假设的先验概率和给定假设下观察到不同数据的概率。
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;
}
}
先验概率
先验概率是我们需要计算的两大概率值之一
/**
* 先验概率计算
* <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;
}
}
分类条件概率
这是另一个影响因子,和先验概率一起来决定最终结果
/**
* <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;
}
}
分类结果
用来保存各个分类及其计算出的概率值,
/**
* 分类结果
*/
public class ClassifyResult
{
public double probility; // 分类的概率
public String classification; // 分类
public ClassifyResult()
{
this .probility = 0 ;
this .classification = null ;
}
}
朴素贝叶斯分类器
利用样本数据集计算先验概率和各个文本向量属性在分类中的条件概率,从而计算出各个概率值,最后对各个概率值进行排序,选出最大的概率值,即为所属的分类。
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
![](https://i-blog.csdnimg.cn/blog_migrate/b854634c0904529d4018c4c3336be836.gif)
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
使用的测试文本:
中国网2月1日报道 美联社消息,微软公司提出以446亿美元现金加股票的价格收购搜索网站雅虎公司。
微软提出以每股31美元的价格收购雅虎。微软的收购报价较雅虎1月31日的收盘价19 . 18美元溢价62%。微软公司称雅虎公司的股东可以选择以现金或股票进行交易。
微软和雅虎公司在2006年底和2007年初已在寻求双方合作。而近两年,雅虎一直处于困境:市场份额下滑、运营业绩不佳、股价大幅下跌。对于力图在互联网市场有所作为的微软来说,收购雅虎无疑是一条捷径,因为双方具有非常强的互补性。 ( 小桥 )
使用mini版本的测试结果:
![](https://i-blog.csdnimg.cn/blog_migrate/b854634c0904529d4018c4c3336be836.gif)
IT: 2.8119528E-5
In process
![](https://i-blog.csdnimg.cn/blog_migrate/b854634c0904529d4018c4c3336be836.gif)
体育: 2.791735E-21
In process
![](https://i-blog.csdnimg.cn/blog_migrate/b854634c0904529d4018c4c3336be836.gif)
健康: 3.3188528E-12
In process
![](https://i-blog.csdnimg.cn/blog_migrate/b854634c0904529d4018c4c3336be836.gif)
军事: 2.532662E-19
In process
![](https://i-blog.csdnimg.cn/blog_migrate/b854634c0904529d4018c4c3336be836.gif)
招聘: 2.3753596E-17
In process
![](https://i-blog.csdnimg.cn/blog_migrate/b854634c0904529d4018c4c3336be836.gif)
教育: 4.2023427E-19
In process
![](https://i-blog.csdnimg.cn/blog_migrate/b854634c0904529d4018c4c3336be836.gif)
文化: 6.0595915E-23
In process
![](https://i-blog.csdnimg.cn/blog_migrate/b854634c0904529d4018c4c3336be836.gif)
旅游: 5.1286412E-17
In process
![](https://i-blog.csdnimg.cn/blog_migrate/b854634c0904529d4018c4c3336be836.gif)
汽车: 4.085446E-8
In process
![](https://i-blog.csdnimg.cn/blog_migrate/b854634c0904529d4018c4c3336be836.gif)
财经: 3.7337095E-10
此项属于[IT]
作者:洞庭散人
出处:http://phinecos.cnblogs.com/