⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 postagging.cpp

📁 中文信息处理方面的一个源码。此为一个词性标注软件
💻 CPP
字号:
#include "stdafx.h"
#include "math.h" // 包含log函数的定义

#include "POSTagging.h"
#include "MyDictionary.h"
#include "MyFileApp.h"

#include "MPWordSeg.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

extern CString Separator;    // 词界标记

CMyDictionary pDict; // 定义一个词典类全局变量
CCoMatrix coMatrix; // 定义一个词性转移矩阵类全局变量


////////////////////////////////////////////////
// CCoMatrix类的成员函数

CCoMatrix :: CCoMatrix()
{
	TagFreqs=NULL;
	Matrix=NULL;
}

CCoMatrix :: ~CCoMatrix() 
{
	Clean();
}

BOOL CCoMatrix :: Ready()
{
	return CorpusSize>0;
}

double CCoMatrix :: GetCoProb(uchar tag1, uchar tag2)
{
	if(TagFreqs[tag1]==0) 
		return 0.00001;
	uchar n=pTags->GetSize();
	return 0.3 * TagFreqs[tag2]/(double) CorpusSize + 0.7*Matrix[tag1*n+tag2]/(double)TagFreqs[tag1];
}

double CCoMatrix::GetWordProb(double wtFreq, uchar tag)
{
	if(TagFreqs[tag]>0)
		return (double)(wtFreq+1)/TagFreqs[tag];
	else
		return 0.00001;
}

void CCoMatrix :: Create(CStdioFile & tagFile)
{
	if(Ready())
		Clean();
	
	CString line;
	pTags=new CStringArray;
	pTags->Add("$%");
	pOpenTags=new CStringArray;
	
	while(pTags->GetSize()<256 && tagFile.ReadString(line)) {
		int i=line.Find(';');
		if(i>=0)
			line=line.Left(i);
		else
			continue;
		line.TrimLeft();
		line.TrimRight();
		if(line.IsEmpty())
			continue;
		if(line.Left(1)=="#" && pOpenTags->GetSize()<10) {
			line=line.Mid(1);
			pOpenTags->Add(line);
		}
		pTags->Add(line);
	}
	tagFile.Close();
	
	int n=pTags->GetSize();

	if (n==1)
	{// 未获取任何标记,只有一个虚设的标记
		AfxMessageBox("打开词性标记文件错误");
		possetModified=FALSE; // 尚未打开词性标记
		CorpusSize=0; // 尚未训练语料,即模型参数值均为0
		return;
	}

	TagFreqs=new int[n]; // 建立数组,存放词性标记的频度
	Matrix=new int[n*n]; // 建立数组,存放词性标记的转移频度
	
	for(int i=0;i<n;i++) { // 数组初始化,均赋值为0
		TagFreqs[i]=0;
		for(int j=0;j<n;j++)
			Matrix[i*n+j]=0;
	}

	possetModified=TRUE; // 已创建词性标记集
	CorpusSize=0; // 尚未训练语料,即模型参数值均为0

	line.Format("共有%d个词性标记",n-1); // 不算虚设的标记n-1
	
	if(pOpenTags->GetSize()==0)
		line+="\n 没有定义开放类,将无法猜测新词的词性";
	
	AfxMessageBox(line);
}

void CCoMatrix::Clean()
{ // 清除此次词性标注操作的内容,并将词性标记集写盘保存
	if(Ready()) {
		CFile tf;
		char buf[512];

		if(tf.Open((const char *) FileName, CFile::modeWrite|CFile::modeCreate)) {//原书没有modeCreate,使得无法创建保存文件
			CArchive ar(&tf, CArchive::store, 512, buf);
			Serialize(ar);
		}
	}

	if(pTags)
		delete pTags;
	if(pOpenTags)
		delete pOpenTags;
	if(TagFreqs)
		delete [] TagFreqs;
	if(Matrix)
		delete [] Matrix;

	possetModified = FALSE;
}

void CCoMatrix :: AddCorpus(CStdioFile &trainFile)
{ // 增加训练语料文件
	char line[3000];
	int n=pTags->GetSize();
	while(trainFile.ReadString(line,3000)) {
		CString s(line);
		s.TrimLeft();
		s.TrimRight();
		int lastID=0;
		while(!s.IsEmpty()) {
			CString w,t;
			w=s.SpanExcluding(" ");// 函数的参数是一个空格,假定分词语料是以空格作为分界符
			s=s.Mid(w.GetLength());
			s.TrimLeft();
			int i=w.Find('/');
			if(i<0) {
				lastID=0;
				continue;
			}
			CorpusSize++;
			t=w.Mid(i+1);
			w=w.Left(i);

			pDict.Insert(w,t);
			int curID=GetIndexOf(t);
			if(curID>0) {
				TagFreqs[curID]++;
				if(lastID>=0)
					Matrix[lastID*n+curID]++;
			}
			lastID=curID;
		}
	}
	trainFile.Close();
}

IMPLEMENT_SERIAL(CCoMatrix, CObject, 0)

void CCoMatrix::Serialize(CArchive &ar)
{ // 数据序列化函数
	if(ar.IsStoring() && !possetModified && !Ready()) // 如果存盘时标记集和语料库都没有修改过,直接返回
		return;

	if(!ar.IsStoring()) { // 如果不是存盘,就创建pTags和pOpenTags在内存中存放词性标记信息
		pTags=new CStringArray;
		pOpenTags=new CStringArray;
	}

	pTags->Serialize(ar); // 一般标记集序列化
	pOpenTags->Serialize(ar); // 开放标记集序列化
	
	int n=pTags->GetSize(); // 取标记计数组长度
	
	if(!ar.IsStoring()) { // 如果不是存盘,就申请内存来存放标记频度以及标记转移矩阵的数据
		TagFreqs=new int[n];
		Matrix=new int[n*n];
	}
	
	if(ar.IsStoring()) { // 如果是存盘,将内存数组中的频度信息写入到文件中
		ar << CorpusSize;
		for(int i=0;i<pTags->GetSize();i++) {
			ar << TagFreqs[i];
			for(int j=0;j<pTags->GetSize();j++)
				ar << Matrix[i*n+j];
		}
	}
	else { // 如果是读盘,就将文件中的频度信息读入到内存数组中
		ar >> CorpusSize;
		for(int i=0;i<pTags->GetSize();i++) {
			ar >> TagFreqs[i];
			for (int j=0; j<pTags->GetSize();j++)
				ar >> Matrix[i*n+j];
		}
	}
}

uchar CCoMatrix::GetIndexOf(CString tag)
{// 将词性标记转换成序号
	
	if (tag=="")
		return -1;

	CString curtag;
	for(uchar i=0;i<pTags->GetSize();i++) {
		curtag=pTags->GetAt(i);
		if(tag==curtag)
			break;
	}
	if(i>=pTags->GetSize()) {
		CString msg;
		msg="当前词的词性标记<"+tag+">不在当前词性标记集范围内\n序号超出数组范围";
		AfxMessageBox(msg); // 调试信息
		i=i-1; // 用当前数组中最后一个词性标记序号作为当前词词性标记的序号
	}
	return i;
}

CString CCoMatrix::GetTagOf(uchar i)
{ // 将序号转换成词性标记
	CString tag;
	if(i<pTags->GetSize())
		tag=pTags->GetAt(i);
	else 
		AfxMessageBox("当前词的词性标记序号超出范围!!!"); // 调试的时候用
	return tag;

}

int CCoMatrix::GetTagFreq(uchar i)
{// 求某种标记的频度
	return TagFreqs[i];
}

void CCoMatrix::WriteTo(CStdioFile out)
{// 将词性标记转移矩阵输出到文本文件
	CString s, curTag1, curTag2,tmp;

	s.Format("%d",CorpusSize);
	out.WriteString("训练语料总词数:"+s+"\n");
	
	int n=pTags->GetSize(); // 取标记计数组长度

	for(int i=0;i<pTags->GetSize();i++) {
		curTag1=GetTagOf(i);
		for(int j=0;j<pTags->GetSize();j++) {
			curTag2=GetTagOf(j);
			s=curTag1+"->"+curTag2+": ";
			tmp.Format("%d",Matrix[i*n+j]);
			s=s+tmp+"\n";
			out.WriteString(s);
		}
	}	
}

// 词性转移矩阵类函数代码结束
////////////////////////////////////////////////////////

////////////////////////////////////////////////////////
// 词性排歧函数部分开始

CSpan::CSpan()
{
	Tags[0][0]=0; // 第0个标记是虚设的
	Tags[0][1]=0;
	Freqs[0][0]=0.0;
	sumFee[0][0]=0.0;
	CurLength=1;
}

CSpan::~CSpan()
{
}

void CSpan::GetFrom(CString &s)
{// 从字串中获取若干个词,s的内容被改变
	CObArray a;
	BOOL isName = FALSE;

	s.TrimLeft();
	s.TrimRight();
	for (uchar i=1; i<20 && !s.IsEmpty(); i++) {
		Words[i]=s.SpanExcluding("/"); // 把斜杠左边的字符串读到Words数组中
	//	if(Words[i]==""){ // 如果当前串是以 / 打头的,Words[i]就是一个空串
	//		s=s.Mid(2);
	//		break;
	//	}

		int wlen=Words[i].GetLength();
		if(wlen<s.GetLength()){
			s=s.Mid((Words[i].GetLength())+1);
			s.TrimLeft();

			if (s.Left(2)=="nr"){
				isName = TRUE;
				if (s.GetLength()>3) // s长度必须大于3才能执行下面语句
					s = s.Mid(3);				
				else
					s = "";
			}
		}
		else {
			s="";
			break;
		}
		
		if (!isName) {// 如果当前词不是姓名
			pDict.GetFreq(Words[i],a); // 查找词语Word[i]在词典中的各个标记及出现次数,返回值为词性标记个数
			if(a.GetSize()==0)
				GuessTag(i);
			else {
				for(uchar j=0;j<10 && j<a.GetSize();j++) { // 添加当前词各词性标记到数组中
					CTagFreq * p=(CTagFreq *) a.GetAt(j);
					Tags[i][j]=coMatrix.GetIndexOf(p->Tag); // 将当前词的当前标记转换成数组序号
					Freqs[i][j]=p->Freq; // 从词典中读取当前词以当前标记出现的次数
				}
				Tags[i][j]=0; // 将当前词词性标记数组尾部置0
				Freqs[i][j]=0; // 将当前词的词性标记频度数组尾部置0
				if(j==1) { // 如果当前词只有一个词性标记,就停止取词
					i++;
					break;
				}
			}
		}
		else {// 如果当前词是姓名
			Tags[i][0]=coMatrix.GetIndexOf("nr");
			Tags[i][1]=0; // 当前词只有一个词性标记,将数组末尾置0
			Freqs[i][0]=1; 
			i++; // 序号增加1
			break;
		}
	}
	CurLength=i; // 词的个数(包括虚设的开头词或从上一个span中继承下来的第0号词
}

void CSpan::GuessTag(char i)
{// 猜测第i个词的词性
	for (uchar k=0;k<coMatrix.pOpenTags->GetSize();k++) {
		Tags[i][k]=coMatrix.GetIndexOf(coMatrix.pOpenTags->GetAt(k));
		Freqs[i][k]=1;
	}
	Tags[i][k]=0;
}

void CSpan::Reset()
{// 重置CSpan各成员变量的值,为新的标注做准备
	Tags[0][0]=Tags[CurLength-1][0];
	Tags[0][1]=0;
	Freqs[0][0]=0;
	CurLength=1;
}

void CSpan::Disamb()
{// 排除词性歧义,进行词性标注
	uchar i,j,k,m;
	double minFee=0, tmp=0, curWordFee=0;

	for(i=1;i<CurLength;i++) { // 对每一个词,i是词的序号计算
		for(j=0;Tags[i][j]>0;j++) { // 对当前词的每一个标记计算,j代表第i个词的第j个词性标记
			m=11; // 假定一个词最多只有10个词性标记,因此11号是虚设的标记序号
			for(k=0;Tags[i-1][k]>0;k++) { // 对当前词的前一个词的每个标记,计算费用,确定当前词当前标记的最佳前驱标记
				tmp=coMatrix.GetCoProb(Tags[i-1][k],Tags[i][j]); // 从前一个词的第k个标记向当前词的第j个标记转移的概率
				tmp=-log(tmp);

				tmp=tmp+sumFee[i-1][k]; // 加上前一个词以第k个词性标记出现的累计费用
				
				if(m>10||tmp<minFee) {
					m=k; // 当前词的前一个词的第k个标记是最佳前趋标记
					minFee=tmp; // 将tmp赋给最小费用
				}
			}
			
			GoodPrev[i][j]=m;
			curWordFee=coMatrix.GetWordProb(Freqs[i][j],Tags[i][j]); // 当前词取当前标记的费用
			sumFee[i][j] = minFee + (-log(curWordFee)); // 当前词取当前标记的累计费用
		}
	}
	Tags[i-1][0]=Tags[i-1][j-1]; // 保证Span中最后一个词的最佳标记能够记录下来
	                             // 参见下面WriteTo()函数中的说明 2002-12-13 zwd
}

void CSpan::WriteTo(CStdioFile out)
{// 将词性标记结果输出到结果文件中
	CString s="";
	for(int i=CurLength-1,j=0;i>0;i--) {
		s=Words[i]+'/'+coMatrix.GetTagOf(Tags[i][j])+"  "+s;
		j=GoodPrev[i][j];
	}
	out.WriteString(s);
	// 这里默认了span中最后一个词一定是单词性标记的词。
	// 从后往前输出span中各个词的词性标记,这样,
	// 当span的最后一个词也是兼类词时,就会
	// 取第0个标记 Tags[i][j](j一开始等于0), 但这时候并没有反映
	// 最后一个词真正应该取的标记值
	// 例子: 把这篇报道编辑一下
	// 最后一个词“下”是兼类词f,q,v,在disamb()函数中可以正确判断
	// “下”取q的概率最大,但在词典中,f是“下”的第一个词性标记,
	// 这样输出结果总是 f, 而不是q
	// 如果把例子改为:把这篇报道编辑一下。
	// 这时候“下”后面有一个标点,因而“下”不是span 的最后一个词
	// disamb()处理的正确结果可以在输出时反映出来。
	// 2002-12-13 zwd
}

// 词性排歧函数代码结束
/////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////
// 训练及标注单个文件的函数部分开始

void TrainFile(CString FileName)
{ // 对单个文件进行词性标注训练的函数
	FILE *in;
	in=fopen((const char *)FileName,"rt");
	if(!in) {
		AfxMessageBox("无法打开语料文件"+FileName);
		return;
	}
	CStdioFile inFile(in);
	coMatrix.AddCorpus(inFile);
}

void TaggingFile(CString FileName)
{ // 对语料进行词性标注
	FILE * in, *out;
	in=fopen((const char *)FileName,"rt");
	if(!in) {
		AfxMessageBox("无法打开语料文件"+FileName);
		return;
	}
	out=fopen((const char *)ChangeExt(FileName,"pos"),"wt");
	if(!out) {
		AfxMessageBox("无法创建词性标注文件");
		fclose(in);
		return;
	}
	CStdioFile inFile(in), outFile(out);
	CSpan span;
	char line[2000];
	while(inFile.ReadString(line,2000)) {
		CString s(line);
		s.TrimLeft();
		s.TrimRight();
		
		if (s.Find(Separator)<0) // 如果没有进行分词
			s=SegmentSentenceMP(s); // 做分词处理

		while(!s.IsEmpty()) {
			span.GetFrom(s);
			span.Disamb();
			span.WriteTo(out);
			span.Reset();
		}
		outFile.WriteString("\n");
	}
	inFile.Close();
	outFile.Close();
}

// 训练及标注单个文件的函数部分结束
////////////////////////////////////////////////////

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -