作者:finallyliuyu转载请注明作者和来源
step by step 文本分类二
写在前面
孟子有言“尽信书不如无书”;陆游有句名诗“纸上得来终觉浅,绝知此事要躬行”。上面两句诗也正是我要分享此篇博文的目的。联系我个人的学习经验。
书本上、老师和学术大牛总会“吹嘘”数学的重要性。可是对于我这样一个资历尚浅的小学生,鹦鹉学舌般地跟着摇旗呐喊“The power of mathematics”,
似乎有点不伦不类,没有实践,没有调查就没有发言权。其实目前网络上有很多可以验证“数学的威力”的开源软件。如weka,libsvm。可是这些软件
发布的测试数据都是“格式化”以后的数据,打开这些数据,我看不到这些数据有任何语义,和实际问题有任何联系。于是就有了本系列博文。
从文本分类问题体会数学的魅力。
捻指算来,开始用C++写程序也不过一个月之久,所以如果我代码的变量命名、函数命名等比较混乱,或是可读性差,欢迎留言指出具体问题和解决方案。
就当是读者免费为我做个code review 吧。呵呵。在这一个月中我要感谢园友,尤其是嗷嗷,还有QQ群友 super口,第九比特,阁子,小峰,王斌老师等人(抱歉有些人记不清了,时间隔得)
有点久),在他们的建议下,我已经将我最初版本的代码做了大幅度的修改。
此系列博文按照文本分类的步骤组织,尽可能详细讲解每个步骤。最后将给出程序清单以及详细说明、源代码下载地址、训练语料库和测试语料库下载地址等
 
·获取语料库
本系列实验采用的新闻语料库由我个人编写的新闻网页正文解析器所获得。关于解析器算法请参见见博文《新闻类网页正文提取》
关于语料库基本情况介绍以及下载地址请参见博文献给热衷于自然语言处理的业余爱好者的中文新闻分类语料库》
本系列实验的语料库存储在数据库FinallyCorpus中。测试语料库的表名称为TestingCorpus,训练语料库的表名为TraingCorpus
 本实验:语料库共有history,culture,reading,military,society&law,entertainment六个类别。训练语料库中每个类别有文章1000篇;测试语料库中每个类别有文章100篇。
训练语料库中共有新闻6000篇。测试语料中共有新闻600篇。
 
 
获取字典
相关函数:
int ConstructDictionary(DICTIONARY& mymap,FUNCSEG seg,string tablename);
void SaveDictionary(DICTIONARY& mymap,char *address);                 
void LoadDictionary(DICTIONARY& mymap,char *address);
主函数调用:
Preprocess::FUNCSEG seg=&Preprocess::goodWordsinPieceArticle;
         int beginIndex=1;
         int endIndex=58232;
         DICTIONARY mymap;
         Preprocess p(beginIndex,endIndex);
         p.ConstructDictionary(mymap,seg,"TrainingCorpus");
         p.SaveDictionary(mymap,"F:\\finallyliuyu\\dict.dat");

运行情况截图:

 

结果的部分截图:(数据结构含义:比如和睦这个词第一行term,第二行term出现在多少篇文章中,第三行:在id为多少的文章中出现过几次)

 

 

·         特征词选择

    代码中实现了两种特征词选择算法(DF法和chi-square法)。这里仅给出对chi-square特征词选择算法的调用。

使用chi-square特征词选择法,首先需要构造出contingency table的数据结构。关于卡方特征词选择法更详细的资料请见《菜鸟进阶:C++实现卡方特征词选择算法》

涉及的函数有:

//获得每个词的contingencytable
int GetContingencyTable(DICTIONARY& mymap, vector<string> classLabels,CONTINGENCY& contingencyTable,string tablename);

void SaveContingencyTable(CONTINGENCY& contingencyTable,char *address);
void LoadContingencyTable(CONTINGENCY& contingencyTable,char *address);

主函数中调用代码如下:

vector<string>labels;
         labels.push_back("history");
         labels.push_back("culture");
         labels.push_back("reading");
         labels.push_back("military");
         labels.push_back("society&law");
         labels.push_back("entertainment");
p.LoadDictionary(mymap,"F:\\finallyliuyu\\dict.dat");
         p.GetContingencyTable(mymap,labels,contingenyTable,"TrainingCorpus");
         p.SaveContingencyTable(contingenyTable,"F:\\finallyliuyu\\contingency.dat");
p.LoadDictionary(mymap,"F:\\finallyliuyu\\dict.dat");
         p.GetContingencyTable(mymap,labels,contingenyTable,"TrainingCorpus");
         p.SaveContingencyTable(contingenyTable,"F:\\finallyliuyu\\contingency.dat");

 

 

contingencytable的数据结构:

 

 

上面的数据表明:文化类的1000篇文章中有5篇出现了韩寒;读书类的1000篇文章中有27篇出现了韩寒;社会与法制类的1000篇文章中有一篇文章出现了韩寒

 

调用chi-square算法遴选特征词并保持到本地硬盘上

思路:先针对各个类别遴选特征词,将针对各个类别遴选出的特征词求并集。

涉及到的函数有:

//计算卡方值 改成double以免溢出
 double CalChiSquareValue(double N11,double N10,double N01,double N00);
 //Chi方法对分别计算词典中的每个词对每个类别的卡方值
 vector<pair<string,double> >ChiSquareFeatureSelectionForPerclass(DICTIONARY& mymap,CONTINGENCY& contingencyTable,string classLabel);        
//计算卡方值 改成double以免溢出
double CalChiSquareValue(double N11,double N10,double N01,double N00);
//Chi方法对分别计算词袋子模型中的词对每个类别的卡方值
  vector<pair<string,double> >ChiSquareFeatureSelectionForPerclass(DICTIONARY& mymap,CONTINGENCY& contingencyTable,string classLabel);
//对整全部类别调用卡方分布方法特征词选择算法
void ChiSquareFeatureSelection(DICTIONARY& mymap,CONTINGENCY& contingencyTable,int N,char * address);
主函数中调用:
p.LoadContingencyTable(contingenyTable,"F:\\finallyliuyu\\contingency.dat");
 //2000表示选取2000个特征词,即每个文档的VSM模型都是一个1*2000的向量
 p.ChiSquareFeatureSelection(labels,mymap,contingenyTable,2000,"F:\\finallyliuyu\\keywords.dat");
 
部分特征词截图:
 

·         文本分类

这里采用KNN分类算法,有关KNN分类器的设计详情请见:《菜鸟进阶:C++实现KNN文本分类算法》

分类之前,我们先要针对测试集和训练集分别建立VSM模型

涉及到的代码有:

测试集建立VSM模型

int GetManyVSM(int begin,int end,DICTIONARY& mymap,DOCMATRIX& testingsetVSM,char *address);//获得待分类文档的VSM模型

训练集建立VSM模型

int VSMConstruction(DICTIONARY& mymap,DOCMATRIX& traingsetVSM,char* address);

序列化以及反序列化

void SaveVSM(DOCMATRIX& VSMmatrix,char *dest);
void LoadVSM(DOCMATRIX& VSMmatrix,char *dest);

主函数中调用如下:

p.LoadDictionary(mymap,"F:\\finallyliuyu\\dict.dat");
         p.LoadContingencyTable(contingenyTable,"F:\\finallyliuyu\\contingency.dat");
         //为训练集建立VSM模型
         p.VSMConstruction(mymap,trainingSetVSM,"F:\\finallyliuyu\\keywords.dat");
         p.SaveVSM(trainingSetVSM,"F:\\finallyliuyu\\trainingVSM.dat");
         //为测试集建立VSM模型
         p.GetManyVSM(1,600,mymap,testingSetVSM,"F:\\finallyliuyu\\keywords.dat");
         p.SaveVSM(testingSetVSM,"F:\\finallyliuyu\\testingVSM.dat");
            

 

运行结果截图:

 

 

 

开始分类:

涉及到的函数有:

//对一篇文章进行分类

string KNNClassificationCell(int N,vector<double> vsm,vector<string>catigorization,DOCMATRIX& trainingsetVSM);//KNN分类

//对整个测试文档集进行分类

void KNNclassifier(DOCMATRIX& trainingsetVSM,DOCMATRIX& testingsetVSM,vector<string>catigorization,int N,RESULTINFO& classifyResults);

主函数调用:

p.LoadVSM(trainingSetVSM,"F:\\finallyliuyu\\trainingVSM.dat");
         p.LoadVSM(testingSetVSM,"F:\\finallyliuyu\\testingVSM.dat");
         p.KNNclassifier(trainingSetVSM,testingSetVSM,labels,50,classifyResults);

测试代码:

 

/***以下代码仅供测试使用********/
         int j=5;
         int articleId=classifyResults[j].first;
         ArticleInfo articleInfo;
         GetArticleInfoById(articleId,"TestingCorpus",articleInfo,&p);
         cout<<"ID号为"<<articleId<<"的文章"<<endl;
         cout<<"标题是:"<<articleInfo.ArticleName<<endl;
         cout<<"所属类别是:"<<articleInfo.Categorization<<endl;
         cout<<"KNN分类器归为类别"<<classifyResults[j].second<<endl;
         int k;
         cin>>k;
         cout<<"ID号为"<<articleId<<"的文章"<<endl;
         cout<<"标题是:"<<articleInfo.ArticleName<<endl;
         cout<<"正文内容如下"<<endl;
         cout<<articleInfo.ArticleText<<endl;
         

 

 

 

顺便让大家看一下文章正文,看看我的网页正文提取算法还可以吧。

 

posted on 2010-09-29 14:55  finallyly  阅读(8384)  评论(22编辑  收藏  举报