随笔 - 266  文章 - 0  评论 - 789 

作者: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权重计算模式:

QQ截图未命名

IDF权重计算模式:

QQ截图未命名2

归一化方式:

QQ截图未命名3

考虑到一篇文章可能完全不含有我们用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获得计算出的聚类中心,完成文本聚类。
posted on 2010-09-03 19:18 finallyly 阅读(1957) 评论(23) 编辑 收藏

 回复 引用 查看   
#1楼[楼主] 2010-09-03 19:20 | finallyliuyu      
自己做沙发,接着写下一篇
 回复 引用 查看   
#2楼 2010-09-03 23:25 | clound      
看不懂,还是坐板凳吧。
 回复 引用 查看   
#3楼 2010-12-22 21:24 | 博学之      
楼主在吗 我在写一篇论文关于VSM这块需要像您请教 怎么联系您啊?
 回复 引用 查看   
#4楼[楼主] 2010-12-23 10:44 | finallyliuyu      
@博学之
就像这样留言就可以了,我有时间就挂博客的。另外把问题描述的细致一些,别太宽泛了。

 回复 引用   
#5楼 2011-03-13 00:58 | helen0301[未注册用户]
把程序运行起来,发现Arff文件中数据部分总写的是-1.#IND。调试时候发现是归一化的时候出了问题。在归一化的函数里,sum值也是-1.#IND,但是vsmit->second这个值是正确的,检查了很久,还是不知道问题出在哪?不知道您是否遇到过这样的问题?
 回复 引用 查看   
#6楼[楼主] 2011-03-13 10:01 | Finallyly      
引用helen0301:把程序运行起来,发现Arff文件中数据部分总写的是-1.#IND。调试时候发现是归一化的时候出了问题。在归一化的函数里,sum值也是-1.#IND,但是vsmit->second这个值是正确的,检查了很久,还是不知道问题出在哪?不知道您是否遇到过这样的问题?

-1.#IND 是由于数据计算错误(比如/0),导致的溢出现象。
看了你的描述出现的问题可能有两方面。
1.sum的值为零导致的错误。
2.你遍历下NormalizationVSM(vector<pair<int,double> > tempVSM)中tempVSM的所有second,会发现某一个second的值是不正确的,也为-1.#IND形式。 出现这个问题的原因在于,你在建立词典(constructmap)的时候,把一些没有在训练集文档集合中出现过的词加进去了。为了避免这个原因,我才设置了preprocess的戴参构造函数。其中的beginindex,endindex是数据库中最小的articleId号和最大的articleId号。

后续改进版本已经解决这个问题,但由于我是在做实验过程中改进的,代码比较乱,现在还不便发布。 考虑到你是在做毕业设计,你可以用我在博客园发布的一个中文新闻语料库,详见http://www.cnblogs.com/finallyliuyu/archive/2010/09/11/1824091.html 下载MDF版本,这份新闻语料,我自己做过一些处理,虽然还有疏漏。(如果使用的话,可在毕设论文附录上指明下资源出处。)
另外祝你好运,有问题我们再联系。

 回复 引用   
#7楼 2011-03-13 12:54 | helen0301[未注册用户]
@Finallyly
词袋子模型也是根据训练集文档建立起来的,我想问是否是在constructmap中的获得好词goodWordsinPieceArticle这一过程中可能加进了训练集文档中没有出现过的词呢?此外,我把endIndex调成了40,这个错误就消失了。不是很理解。
不好意思,这么多问题麻烦您~~刚刚接触,觉得很多东西都要学习。

 回复 引用   
#8楼 2011-03-13 13:12 | helen0301[未注册用户]
@Finallyly
已经换了您提供的新数据集,但是还有同样的问题。还在尝试解决~~

 回复 引用 查看   
#9楼[楼主] 2011-03-13 13:44 | Finallyly      
@helen0301
goodWordsinPieceArticle这一过程中可能加进了训练集文档中没有出现过的词呢

答:应该不会,这个就是调用ICTCLAS分词,去掉停用词。可能会在vsmconstruction(函数名,也可能是vsmformation)这里有个vsm权重计算公式。 你看看getmaxTFDF是否出现了乱码?
或者有没有除0?
我以前也遇到过。

 回复 引用 查看   
#10楼[楼主] 2011-03-13 13:47 | Finallyly      
@helen0301
vector<string> myKeys=GetFinalKeyWords();
vector<pair<int,int> >maxTFandDF=GetfinalKeysMaxTFDF(mymap);

 回复 引用   
#11楼 2011-03-13 14:10 | helen0301[未注册用户]
@Finallyly
我在ARFF文件的属性(关键词)部分里面看到
@attribute '  在' real
@attribute '  这' real
@attribute '  “' real
不知道是不是和我把宽窄字符变换部分省去了有关,出错的几次情况都是包含这几条的,没有出错的情况没有。这会不会导致我现在的错误?

 回复 引用 查看   
#12楼[楼主] 2011-03-13 14:23 | Finallyly      
你constructmap 之后,save了没?
如果save 了,用ultraedit打开那个保存mymap的文件,检索这两个词,看看能不能检索到? 我看
@attribute '  在' real
@attribute '  这' real
@attribute '  “' real
好像是没有trim过。。
我在写程序的时候,对于每个分出来的词,或者从数据库/硬盘文本文件里面读出的词,都trim 过一遍了。就是把词的前后空格去掉。这样可以避免程序把"好人"和“ 好人”,“好人 ”识别成不同的词。

 回复 引用   
#13楼 2011-03-13 14:41 | helen0301[未注册用户]
@Finallyly
我把宽窄字符的部分加上,问题应该是解决了。。。谢谢您的帮助~~呵呵^^

 回复 引用 查看   
#14楼[楼主] 2011-03-13 15:51 | Finallyly      
@helen0301
好,不客气。

 回复 引用   
#15楼 2011-06-08 17:21 | supera01[未注册用户]

1.为什么保存向量的时候每个向量还用大括号括起来?weka中的arff文件都没有用大括号的吧?
2.tempVSM.push_back(make_pair(j,TF));
为什么还要保存j到arff中,只保存TFIDF值不行吗?
谢谢!

 回复 引用 查看   
#16楼[楼主] 2011-06-08 20:35 | Finallyly      
@supera01
我这里采用的是稀疏数据格式的写法。你不标注,怎么知道是哪个标号下的tf*idf值?

 回复 引用   
#17楼 2011-06-10 15:49 | supera01[未注册用户]
@Finallyly
你的代码中用的TFIDF公式不可能出现值为0的情况吧?
TF=0.5+0.5*(double)TF/(maxTFandDF[j].first);
TF*=log((double)corpus_N/maxTFandDF[j].second);
这样的话,就没有必要用稀疏矩阵了吧?
谢谢!!!

 回复 引用 查看   
#18楼[楼主] 2011-06-10 18:09 | Finallyly      
@supera01
这里采用的公式确实不会出现0的情况。但是tf,idf权重结合方式有多种,我这么做是从实验的角度为其他情况做预留。

 回复 引用   
#19楼 2011-06-10 23:46 | supera01[未注册用户]
哦,知道了。你的文章对我入门帮助很大。感谢!
最近用java实现你的预处理部分,然后weka进行分类的抛异常了。
java.io.IOException: Unable to determine structure as arff (Reason: java.io.IOException: premature end of line, read Token[EOL], line 200).
好像是读入生成的arff格式的vsm文件时出的错。
请问你知道具体的原因吗?再次感谢。

 回复 引用 查看   
#20楼[楼主] 2011-06-11 20:15 | Finallyly      
@supera01
估计是这个问题,就是里面出现了非法格式的数据。如#IND等等。
产生这个数据的原因很可能是(1)因为NormilizeVSM模块除零
(2)词典中词,有些并不出现。
你调试定位下看看吧。我也遇到过同样的问题,后来修改了。对给你造成的麻烦表示道歉。确实一些问题在实现代码时没有考虑周全。

 回复 引用   
#21楼 2011-06-11 21:25 | supera01[未注册用户]
已经解决,是我的attribute属性名没有加引号!
可能还会参照你的IG法和CHI-square法进行特征选择,遇到问题时请你帮忙哦,呵呵。

 回复 引用 查看   
#22楼[楼主] 2011-06-12 13:17 | Finallyly      
@supera01
聚类,怎么能用IG,chi法呢?

 回复 引用   
#23楼 2011-06-18 09:32 | supera01[未注册用户]
@Finallyly
我是做分类。

发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1817322 NzVeyIEiO8c=