📄 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
FILE *tracefile; // 定义文件指针,全局变量,记录分词和词性标注过程
extern CString Separator; // 词界标记
#define MAXSENTLENGTH 20000 // 句子长度
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)
{
// tag1 == 0 时指的是虚设的句子起始词标记 $$
// 理论上,TagFreqs[0]不应该等于0,句子起始位置在训练语料中是可以记数的。
// 但程序在训练语料参数时有可能实际上没有记录TagFreqs[0]的值
// TagFreqs[i]=0 时指的是某一个词性标记(编号为i)在训练语料中一次也没有出现。
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); // 开放词性标记被加入到pOpenTags中
}
pTags->Add(line); // 开放词性标记也被加入到pTags中
}
tagFile.Close();
int n=pTags->GetSize();
int m=pOpenTags->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其中开放词性标记%d个\n",n-1,m); // 不算虚设的标记n-1
CString tempTag = "";
for(int k=0;k<pTags->GetSize();k++)
{
if (tempTag == "")
tempTag = pTags->GetAt(k);
else
tempTag = tempTag + " | " + pTags->GetAt(k);
}
if(pOpenTags->GetSize()==0)
line+="\n 没有定义开放类,将无法猜测新词的词性";
if (tempTag != "")
line+= tempTag;
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[MAXSENTLENGTH];
int n=pTags->GetSize();
while(trainFile.ReadString(line,MAXSENTLENGTH)) {
CString s(line);
s.TrimLeft();
s.TrimRight();
int lastID=0; // lastID表示上一个词性标记,lastID等于0表示这是句子起始词虚设的标记$$。
while(!s.IsEmpty())
{// 对当前行的每个词进行处理
CString curword,curtag; // curword:当前词,curtag:当前词的词性标记
curword=s.SpanExcluding(" ");// 函数的参数是一个空格,假定分词语料是以空格作为分界符
curword.TrimLeft();
curword.TrimRight();
int wordLength = curword.GetLength();
s=s.Mid(wordLength);
s.TrimLeft();
int i=curword.ReverseFind('/'); // 从后往前取/ ,可处理 1/3/m 这样的情形
if(i<0) { // 这种情况是训练语料中存在错误
lastID=0;
continue;
}
CorpusSize++;
if(i<wordLength)
curtag=curword.Mid(i+1);
else
AfxMessageBox(curword); // 如果 i大于等于w的长度,报错
if (curtag == "n]nt") // 为处理ICL人民日报标注语料中 [ ... /n]nt这类标记情况
curtag="n";
if(i<wordLength)
curword=curword.Left(i);
else
AfxMessageBox(curword);
pDict.Insert(curword,curtag); // 将当前读入的词语插入到词库中
if (curtag=="/w")
AfxMessageBox(curword);
int curID=GetIndexOf(curtag);
if(curID>0)
{// 按照目前的假设,curtag是当前词性标记集中的一个元素。除非训练语料中的标记超出标记集,否则curID 肯定大于0
// 理论上,curID应该可以等于0,表示当前词是句子起始位置,此时curID代表了虚设的标记 $$
// 但实际上 curID 不会等于0,因为目前的训练语料中并没有在句子开始位置人为地加上 ##/$$ 这样标记
// 变通的做法:在下面给lastID赋值的时候,通过判别句末标点的方式,间接地来获取 $$ 标记的频次
TagFreqs[curID]++;
if(lastID>=0)
Matrix[lastID*n+curID]++;
if(lastID==0)
TagFreqs[lastID]++;// 上一个标记是虚设的句子起始标记,TagFreqs[0]需要加1次记数。
}
if (curtag == "w" && (curword == "。" || curword == "?" || curword == "!"))
{// 认为碰到了句子结束标志,因此也意味着是下一个句子的开始标志
lastID = 0; // 这样假设有一定的问题,因为!?都可不一定是结句符号,此外,有的…是起结句作用的,没有包含进来
}
else
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];
}
}
}
int CCoMatrix::GetIndexOf(CString tag)
{// 将词性标记转换成序号
if (tag=="")
return -1;
for(int i=0;i<pTags->GetSize();i++) {
if(tag == (pTags->GetAt(i)))
break;
}
if(i>=pTags->GetSize()) {
CString msg;
msg="当前词的词性标记<"+tag+">不在当前词性标记集范围内";
AfxMessageBox(msg); // 调试信息
i=i-1; // 用当前数组中最后一个词性标记序号作为当前词词性标记的序号
}
return i;
}
CString CCoMatrix::GetTagOf(uchar i)
{ // 将序号转换成词性标记
CString tag;
if(i<pTags->GetSize())
tag=pTags->GetAt(i);
else
{
tag = i;
tag = "当前词的词性标记序号: " + tag;
tag = tag + " 超出范围";
AfxMessageBox(tag);
tag="";
// AfxMessageBox("当前词的词性标记序号%d超出范围!!!",i); // 调试的时候用
}
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()
{
Words[0]="##"; // 第0个词置空
Tags[0][0]=0; // 第0个标记是虚设的,即句子开始位置的虚拟词性标记:$$
Tags[0][1]=0; // 0还有一个含义是标示结束
Freqs[0][0]=0.0;
sumFee[0][0]=0.0;
GoodPrev[0][0]=0;
CurLength=1;
}
CSpan::~CSpan()
{
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -