作者:finallyliuyu 转载使用等请注明出处
在上一篇博文《Kmeans聚类之特征词选择DF》中我们已经给出了特征词选择的代码,这里我们将给出建立文档向量模型的代码,以及将文档向量模型写成Weka数据格式的代码。关于Weka数据格式等相关内容,请见:教程。
首先我们给出写Arff头文件的代码
void Preprocess::WriteHeadArff() { ofstream ofile(arffFileAddress,ios::binary); ofile<<"@relation aticle"<<endl; ofile<<"\n"; vector<string> myKeys=GetFinalKeyWords(); for(vector<string>::iterator it=myKeys.begin();it!=myKeys.end();it++) { //string temp="@attribute "+"'"+(*it)+"'"+" real"; string temp=""; temp+="@attribute "; temp+="'"; temp+=*(it); temp+="'"; temp+=" real"; /*strcpy(temp,"@attribute "); strcpy(temp,"'"); strcpy(temp,*(it)); strcpy(temp,"'"); strcpy(temp," real");*/ ofile<<temp<<endl; } ofile<<"\n"<<endl; ofile<<"@data"<<endl; ofile.close(); }
下面重点介绍采用TF-IDF权重建立文档向量模型:
在给出代码之前先简要介绍下什么是TF,DF
对于一个特定的Term t
TF,指的是它在吗某一篇文章中出现的次数;
DF,指的是整个文档集合中出现该词的文章篇数;
文档向量模型(Vector Space Model):向量。向量的属性为用《Kmeans聚类之特征词选择DF》中的特征词选择方法选定的特征词。
整个文档集合的VSM模型实际上是以矩阵的格式保存的。矩阵的每一行,代表一篇文章,是一个文档向量。
TF-IDF模型有很多权重计算模式:(注意:以下截图来自于计算所王斌老师的课件《现代信息检索》)在这里顺便给大家介绍一本十分不错的书《信息检索导论》 (Introduction to Information Retrieval)原版第一作者为斯坦福大学计算机语言学副教授Christopher D. Manning。该书由王斌老师翻译成中文,现在已经出版。
TF权重计算模式:
IDF权重计算模式:
归一化方式:
考虑到一篇文章可能完全不含有我们用DF选择法选择的特征词。
那么这篇文章的VSM就是{0,0,0,..0}
为了避免产生这种类型的稀疏数据,我采用的TF-IDF计算模式为
a-l-c。
大家对应上面三个表找一下,就找到相应的计算公式了。
下面开始建立文档向量模型:
获得每个特征词对应的maxTF和DF:
ector<pair<int,int> >Preprocess::GetfinalKeysMaxTFDF(map<string,vector<pair<int,int>>> &mymap) { vector<pair<int,int> >maxTFandDF; vector<string>myKeys=GetFinalKeyWords(); for(vector<string>::iterator it=myKeys.begin();it!=myKeys.end();it++) { int DF=mymap[*it].size(); int maxTF=0; for(vector<pair<int,int> >::iterator subit=mymap[*it].begin();subit!=mymap[*it].end();subit++) { if(subit->second>maxTF) { maxTF=subit->second; } } maxTFandDF.push_back(make_pair(maxTF,DF)); //find_if(mymap[*it].begin(),mymap[*it].end(), } return maxTFandDF; }
************************************************************************/ /* 文档向量模型归一化 */ /************************************************************************/ vector<pair<int,double> >Preprocess::NormalizationVSM(vector<pair<int,double> > tempVSM) { double sum=0; for(vector<pair<int,double> >::iterator vsmit=tempVSM.begin();vsmit!=tempVSM.end();++vsmit) { sum+=pow(vsmit->second,2); } for(vector<pair<int,double> >::iterator vsmit=tempVSM.begin();vsmit!=tempVSM.end();++vsmit) { vsmit->second/=sqrt(sum); } return tempVSM; }
有了上面的辅助函数,那么我们可以一边对文档集合建立文档向量模型,一边写arff文件的data部分了。
首先还要给出两个辅助函数,分别完成浮点数和整数字符串化的功能
/************************************************************************/ /* 将整数转化成字符串 */ /************************************************************************/ string Preprocess::do_fraction(int val) { ostringstream out; out<<val; string str= out.str(); //从流中取出字符串 str.swap(string(str.c_str()));//删除nul之后的多余字符 return str; }
/************************************************************************/ /* 将浮点数转化成指定精度的字符串 */ /************************************************************************/ string Preprocess::do_fraction(double val,int decplaces) { //int prec=numeric_limits<double>::digits10; char DECIMAL_POINT='.'; ostringstream out; //out.precision(prec); out<<val; string str=out.str(); size_t n=str.find(DECIMAL_POINT); if((n!=string::npos)&&n+decplaces<str.size()) { str[n+decplaces]='\0'; } str.swap(string(str.c_str())); return str; }
将一篇文档的VSM字符串化的函数:
************************************************************************/ /* 单个文档向量模型字符串化 */ /************************************************************************/ string Preprocess::FormatVSMtoString(vector<pair<int,double> > tempVSM) { string ret="{"; int commaindication=0; for(vector<pair<int,double> >::iterator vsmit=tempVSM.begin();vsmit!=tempVSM.end();++vsmit) { ret+=do_fraction(vsmit->first)+" "+do_fraction(vsmit->second,8); if(commaindication<tempVSM.size()-1) { ret+=","; } commaindication++; } ret+="}"; return ret; }
下面的函数调用上面的FormatVSMtoString 填写arff文件的data字段
/************************************************************************/ /* 将实验数据写成arff @data格式 */ /************************************************************************/ void Preprocess::VSMFormation(map<string,vector<pair<int,int>>> &mymap) { int corpus_N=endIndex-beginIndex+1; ofstream ofile1(articleIdsAddress,ios::binary);//保存文章编号的文件 ofstream ofile2(arffFileAddress,ios::binary|ios::app); vector<string> myKeys=GetFinalKeyWords(); vector<pair<int,int> >maxTFandDF=GetfinalKeysMaxTFDF(mymap); for(int i=beginIndex;i<=endIndex;i++) { vector<pair<int,double> >tempVSM; for(vector<string>::size_type j=0;j<myKeys.size();j++) { //vector<pair<int,int> >::iterator findit=find_if(mymap[myKeys[j]].begin(),mymap[myKeys[j]].end(),PredTFclass(i)); double TF=(double)count_if(mymap[myKeys[j]].begin(),mymap[myKeys[j]].end(),PredTFclass(i)); TF=0.5+0.5*(double)TF/(maxTFandDF[j].first); TF*=log((double)corpus_N/maxTFandDF[j].second); if(TF!=0) { tempVSM.push_back(make_pair(j,TF)); } } if(!tempVSM.empty()) { tempVSM=NormalizationVSM(tempVSM); string vsmStr=FormatVSMtoString(tempVSM); ofile1<<i<<endl; ofile2<<vsmStr<<endl; } tempVSM.clear(); } ofile1.close(); ofile2.close(); }
至此文档向量模型建立模块的代码已经介绍完毕。
未完,待续,下次我们将介绍如何从weka获得计算出的聚类中心,完成文本聚类。