📄 postagging.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 + -