自然语言理解-实验报告
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
分词系统工程报告
课程:自然语言理解
:王佳淼
学号:2011914
班级:信息安全11-1
日期:2013-11-2
实验一宋词字统计
一.研究背景
本实验所涉及的研究背景是利用计算机来“鉴赏”宋词。
主要针对宋词这种特殊的汉语诗歌体裁,开展了有关自动生成算法及其实现方法的探索性研究。
通过对大量语料的学习,来自动生成宋词。
由于宋词自身的特性,能够在经过大量预料学习后,利用在宋词当中出现频率较高的词语或者单字排列组合来生成宋词。
二.实验所采用的开发平台及语言工具
实验在WIN7的环境下利用VC++编程。
三.系统设计
(1)算法基本思想
从文本中字符,判断是否为中文字符(全角字符),若为全角字符则根据需要继续读取,即读取两个或三个字。
利用map容器来存储统计结果。
(2)流程图
(3)代码
①字统计
#include <map>
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
//宋词的统计
void display_map(map<string, int> &wmap); ofstream ofs("c:\\fenciout2.txt");
int main()
{
cout<<"开始:"<<endl;
const char *szInputFileName="c:\\Text.txt";
ifstream ifs(szInputFileName);
string szTemp;
string str1,str2,str3,str;
char a[10];//
map<string, int> wmap;
//统计双字的
while (ifs)
{
while(ifs.peek()=='\n') ifs.get();
while(ifs.peek()==' ') ifs.get();
while(!ifs.peek()&0x80) ifs.get();
ifs.get(a,3,' ');
if(a[0]&0x80)
{
str1=a;
while(ifs.peek()=='\n') continue;
while(ifs.peek()==' ') continue;
while(!ifs.peek()&0x80) ifs.get();
ifs.get(a,3,' ');
if(a[0]&0x80) str2=a;
if(str1!=","&&str1!="。
"&&str1!="?"&&str1!="!"&&str1!=""&&str2!=","&&str2!="。
"&&str2!="?"&&str2!="!"&&str2!="")
{
szTemp=str1+str2;
wmap[szTemp]++;
}
if(ifs.peek()!='\n'&&ifs.peek()!=' ') ifs.seekg(-2,ios::cur);
}
}
ifs.close();
//三字
ifstream ifs1(szInputFileName);
while (ifs1)
{
while(ifs1.peek()=='\n') ifs1.get();
while(ifs1.peek()==' ') ifs1.get();
while(!ifs1.peek()&0x80) ifs1.get();
ifs1.get(a,3,' ');
if(a[0]&0x80)
{
str1=a;
while(ifs1.peek()=='\n') continue;
while(ifs1.peek()==' ') continue;
while(!ifs1.peek()&0x80) ifs1.get();
ifs1.get(a,3,' ');
if(a[0]&0x80) str2=a;
else continue;
while(ifs1.peek()=='\n') continue;
while(ifs1.peek()==' ') continue;
while(!ifs1.peek()&0x80) ifs1.get();
ifs1.get(a,3,' ');
if(a[0]&0x80) str3=a;
else continue;
if(str1!=","&&str1!="。
"&&str1!="?"&&str1!="!"&&str1!=""&&str2!=","&&str2!="。
"&&str2!="?"&&str2!="!"&&str2!=""&&str3!=","&&str3!="。
"&&str3!="?"&&str3!="!"&&str3!="")
{
szTemp=str1+str2+str3;
wmap[szTemp]++;
}
if(ifs1.peek()!='\n'&&ifs1.peek()!=' ') ifs1.seekg(-4,ios::cur);
}
}
ifs1.close();
//单字
ifstream ifs2(szInputFileName);
while (ifs2)
{
while(ifs2.peek()=='\n') ifs2.get();
while(ifs2.peek()==' ') ifs2.get();
while(!ifs2.peek()&0x80) ifs2.get();
ifs2.get(a,3,' ');
if(a[0]&0x80)
{
str=a;
}
if(str!=","&&str!="。
"&&str!="?"&&str!="!"&&str!="")
{
szTemp=str;
wmap[szTemp]++;
}
}
ifs2.close();
display_map(wmap);
return false;
}
void display_map(map<string, int> &wmap)
{
int sum=0;
map<string, int>::const_iterator map_it;
for (map_it=wmap.begin(); map_it!=wmap.end();map_it++) //统计总数
{
sum+=map_it->second;
}
for (map_it=wmap.begin(); map_it!=wmap.end();map_it++) //
计算词频
{
cout<<"(\""<<map_it->first<<"\","<<map_it->second<<")";
cout<<(double)map_it->second/(sum-1)<<endl;
}
for (map_it=wmap.begin(); map_it!=wmap.end();map_it++) //计算词频
{
ofs<<"(\""<<map_it->first<<"\","<<map_it->second<<")";
ofs<<(double)map_it->second/sum<<endl;
}
}
②宋词生成
#include <map>
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
void display_map(map<string, int> &wmap);
void ci(int a);
map<string, int> wmap;
map<string, int> wmapp;
int main()
{
cout<<"开始:"<<endl;
const char *szInputFileName="c:\\Text.txt"; ifstream ifs(szInputFileName);
string szTemp,szTempp;
string str1,str2,str;
char a[10];//
//统计双字的
while (ifs)
{
while(ifs.peek()=='\n') ifs.get();
while(ifs.peek()==' ') ifs.get();
ifs.get(a,3,' ');
if(a[0]&0x80)
{
str1=a;
while(ifs.peek()=='\n') continue;
while(ifs.peek()==' ') continue;
ifs.get(a,3,' ');
if(a[0]&0x80) str2=a;
if(str1!=","&&str1!="。
"&&str1!="?"&&str1!="!"&&str1!=""&&str2!=","&&str2!="。
"&&str2!="?"&&str2!="!"&&str2!="")
{
szTemp=str1+str2;
wmap[szTemp]++;
}
if(ifs.peek()!='\n'&&ifs.peek()!=' ') ifs.seekg(-2,ios::cur);
}
}
ifs.close();
// display_map(wmap);
//单字
ifstream ifs1(szInputFileName);
while (ifs1)
{
while(ifs1.peek()=='\n') ifs1.get();
while(ifs1.peek()==' ') ifs1.get();
ifs1.get(a,3,' ');
if(a[0]&0x80)
{
str=a;
}
if(str!=","&&str!="。
"&&str!="?"&&str!="!"&&str!="")
{
szTemp=str;
wmapp[szTemp]++;
}
}
ifs1.close();
// display_map(wmapp);
int as;
while(1)
{
cout<<"请随意输入一个数来创造宋词:(输入0时退出)"<<endl;
cin>>as;
if(as==0) return 0;
ci(as);
cout<<endl<<endl;
cout<<"-----------------------------------------------------------------------------"<<endl;
cout<<endl<<endl;
}
return false;
}
void ci(int a)
{
cout<<" 如梦令"<<endl;
map<string, int>::iterator map_it;
for(int i=0;i<3;i++)
{
map_it=wmap.begin();
for(int j=0;j<(rand()+a*2)%(wmap.size());j++) map_it++;
cout<<map_it->first;
}
cout<<"。
";
for(i=0;i<3;i++)
{
map_it=wmap.begin();
for( int j=0;j<rand()%(a);j++) map_it++;
cout<<map_it->first;
}
cout<<"。
";
for(i=0;i<2;i++)
{
map_it=wmap.begin();
for( int j=0;j<rand()%(wmap.size());j++) map_it++;
cout<<map_it->first;
}
map_it=wmapp.begin();
for( int j=0;j<(rand()+a*3)%(wmapp.size());j++) map_it++; cout<<map_it->first;
cout<<",";
for(i=0;i<3;i++)
{
map_it=wmap.begin();
for( int j=0;j<(rand()+a)%(wmap.size());j++) map_it++;
cout<<map_it->first;
}
cout<<"。
";
map_it=wmap.begin();
for( j=0;j<(2*rand()+a)%(wmap.size());j++) map_it++;
string temp=map_it->first;
cout<<temp<<"。
"<<temp<<"。
";
for(i=0;i<3;i++)
{
map_it=wmap.begin();
for( int j=0;j<(a*rand())%(wmap.size());j++) map_it++;
cout<<map_it->first;
}
cout<<"。
"<<endl;
}
void display_map(map<string, int> &wmap)
{
map<string, int>::const_iterator map_it;
for (map_it=wmap.begin(); map_it!=wmap.end();map_it++)
{
cout<<"(\""<<map_it->first<<"\","<<map_it->second<<")"<<endl;
}
}
四.系统演示与分析
(1)截图
①字统计:
②宋词生成(如梦令):
(2)结果分析
五.对次实验的感想、意见和建议
通过此次实验,能熟练利用MAP容器,为接下来的两个实验打好基础。
也能对汉字进行操作,清楚的了解到汉字在计算机中的存储
方式有别于字符型。
利用位运算&来与0x80进行比对以区分全角字符和半角字符。
但因为将字作为了map的key值,所以输出的列并不是按照出现频率由大到小,这是可以进行改进的方面。
在宋词生成的程序中,主要是利用随机输入的数字来产生伪随机数,以便利用产生的伪随机数选择不同出现频率的词组来生成宋词。
我觉得可以改进的方面是,在统计时就能用程序自动获取不同词牌名的不同格式,在生成宋词时能选择不同的词牌名以生成不同宋词,使程序更加智能。
实验二人民日报词频统计
一.研究背景
词是自然语言中能够独立运用的最小单位,是自然语言处理的基本单位。
自动词法分析就是利用计算机对自然语言的形态(morphology) 进行分析,判断词的结构和类别等。
而词频统计是分词系统的基础和关键,只有在正确、准确、快速的词频统计的情况下才能很好的进行分词算法。
二.实验所采用的开发平台及语言工具
实验在WIN7的环境下利用VC++编程。
三.系统设计
(1)算法基本思想
从文本中字符,判断是否为中文字符(全角字符),若为全角字符则根据需要继续读取,即读取两个或三个字。
利用map容器来存储统计结果。
(2)流程图
(3)代码
#include <map>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
//人民日报的词频统计
typedef pair<string,int> PAIR;
vector<PAIR> vecpair;
void display_map(map<string, int> &wmap);
ofstream ofs("c:\\fenciout.txt");
int cmp(const PAIR& x, const PAIR& y)//vector的sort()降序排列{
return x.second>y.second;
}
int main()
{
cout<<"开始:"<<endl;
const char *szInputFileName="c:\\cidian.txt";
ifstream ifs(szInputFileName); string szTemp;
string str1,str2,str3,str;
char a[100];//
map<string, int> wmap;
map<string, int> wmapok;
//统计
while (ifs)
{
szTemp="";
int i=0;
ifs>>a[i];//逐个字符读入
if(ifs.eof())
break;
if(!(a[i]&0x80))//非中文
continue;
if(!(a[i]&0xB0))//除去空格
{
ifs>>a[i];
continue;
}
while(a[i]!='/')//读取
szTemp+=a[i];
i++;
ifs>>a[i];
}
wmap[szTemp]++;//对应字频加1
}
ifs.close();
//map 根据value排序
map<string, int>::const_iterator curr;
for(curr=wmap.begin();curr!=wmap.end();++curr)
{
vecpair.push_back(make_pair(curr->first,curr->second));
}
sort(vecpair.begin(),vecpair.end(),cmp);//调用排序函数,则vecpair 中的first是词,而且降序排列
for(int i = 0 ;i<vecpair.size();i++)
{
ofs<<" < "<<vecpair[i].first<<" , "<<vecpair[i].second<<" > "<<endl;
}
display_map(wmap);
}
void display_map(map<string, int> &wmap)
{
int sum=0;
map<string, int>::const_iterator map_it;
for (map_it=wmap.begin(); map_it!=wmap.end();map_it++) //统计总数
{
sum+=map_it->second;
}
for (map_it=wmap.begin(); map_it!=wmap.end();map_it++) //计算词频,在界面上输出,此时是无序的
{
cout<<"(\""<<map_it->first<<"\","<<map_it->second<<")";
cout<<(double)map_it->second/sum<<endl;
}
}
四.系统演示与分析
(1)截图
排序后:
(2)结果分析
五.对次实验的感想、意见和建议
在这次实验后,能含有分词字符的文件进行读取,利用语料来训练程序,并将结果保存在文件当中,以便实验三可以使用。
而且能够进行排序,按照value的值,即词语出现次数由高到低进行排序。
美中不足的是,程序运行速度有待提高。
由于map是按照键值排序的,
所以引入了vector,并利用vector的sort函数进行排序,熟悉了这两种容器的使用,提高了对c++库函数的认识。
实验三人民日报分词
一.研究背景
自动词法分析就是利用计算机对自然语言的形态(morphology) 进行分析,判断词的结构和类别等。
自动分词是汉语句子分析的基础,而且,词语的分析具有广泛的应用(词频统计,词典编纂,文章风格研究等)
二.模型方法
正向最大匹配算法(Forward MM, FMM)。
这种算法使得程序简单易行,开发周期短,而且仅需要很少的语言资源(词表),不需要任何词法、句法、语义资源。
三.系统设计
(1)算法基本思想
FMM 算法描述:
(1) 令i=0,当前指针pi 指向输入字串的初始位置,执行下面的操作:
(2) 计算当前指针pi到字串末端的字数(即未被切分字串的长度)n,如果n=1,转(4),结束算法。
否则,令m=词典中最长单词的字数,如果n<m, 令m=n;
(3)从当前pi起取m个汉字作为词wi,判断:
(a) 如果wi 确实是词典中的词,则在wi 后添加一个切分标志,转
(c);
(b) 如果wi 不是词典中的词且wi 的长度大于1,将wi从右端去掉一个字,转(a)步;否则(wi 的长度等于1),则在wi 后添加一个切分标志(单字),执行(c)步;
(c) 根据wi 的长度修改指针pi的位置,如果pi指向字串末端,转
(4),否则,i=i+1,返回(2);
(4) 输出切分结果,结束分词程序。
(2)流程图
图1.分词流程图
图2.FMM算法流程图
(3)代码
#include <map>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
//人民日报的词频统计
typedef pair<string,int> PAIR;
vector<PAIR> vecpair;
ofstream ofs("c:\\fenciout.txt");
int cmp(const PAIR& x, const PAIR& y)
{
return x.second>y.second;
}
int main()
{
cout<<"开始:"<<endl;
const char *szInputFileName="c:\\cidian.txt";
ifstream ifs(szInputFileName);
string szTemp;
string str1,str2,str3,str;
char a[100];//
map<string, int> wmap; map<string, int> wmapok;
//统计
while (ifs)
{
szTemp="";
int i=0;
ifs>>a[i];//逐个字符读入
if(ifs.eof())
break;
if(!(a[i]&0x80))//非中文
continue;
if(!(a[i]&0xB0))//除去空格
{
ifs>>a[i];
continue;
}
while(a[i]!='/')
{
szTemp+=a[i];
i++;
ifs>>a[i];
}
wmap[szTemp]++;//对应字频加1
}
ifs.close();
//map 根据value排序
map<string, int>::const_iterator curr;
for(curr=wmap.begin();curr!=wmap.end();++curr)
{
vecpair.push_back(make_pair(curr->first,curr->second));
}
sort(vecpair.begin(),vecpair.end(),cmp);//调用排序函数,则vecpair 中的first是词,而且降序排列
for(int i = 0 ;i<vecpair.size();i++)
{
ofs<<vecpair[i].first<<endl;
}
ofs.close();
ifstream infile2;
infile2.open("c:\\fenciout.txt",ios::in);
if(!infile2)
{
cerr<<"文件打开错误"<<endl;
exit(1);
}
vector<string>arr1,arr2;//1是读取词典,2是存储分好的句子
string word;
string text="是中国领土不可分割的一部分,这是大势所趋。
";
cout<<"原句是:是中国领土不可分割的一部分,这是大势所趋。
"<<endl<<endl;
while(getline(infile2,word))
{
arr1.push_back(word);
}
string temp1=text;
string temp2=temp1;
int p1=0;
while(strlen(temp2.c_str())!=0)
{
for(int i=strlen(temp2.c_str());i>=0;i-=2)
{
if(find(arr1.begin(),arr1.end(),temp1)!=arr1.end())
{
temp1+="/";
arr2.push_back(temp1);
p1=i;
break;
}
else
{
temp1=temp2.substr(0,i-2);
}
}
temp2=temp2.substr(p1);//还没有分的赋值给temp1
temp1=temp2;
}
cout<<"分词结果为:"<<endl;
for(vector<string>::iterator iter=arr2.begin();iter!=arr2.end();++iter) {
cout<<*iter;
}
cout<<endl<<endl<<endl;
return;
}
四.系统演示与分析(1)截图
对于语料库中没有出现的词语:
(2)结果分析
在测试的围,对于语料库中含有的句子能完美分词,但对于一些句子中含有非语料库词语的情况并不能很好的完成分词。
但这一缺陷可以通过加大语料库,加强语料训练来弥补。
对于未登录词语,在程序中并没有加以实现,所以在测试中并没有进行着方面测试。
五.对次实验的感想、意见和建议
通过这次实验,能顺利对测试句子进行分词。
利用实验二的词频统计结果和FMM算法对语句进行分词操作,由于FMM算法的缺陷,以及语料信息不全等众多因素,分词时,召回率不是很高。
可以考虑采用双向最大匹配算法来进行分词,确信可以弥补这一缺陷,尽可能大的消除奇异。
这次实验使得我对FMM算法的认识有了一定程度上的加深,对分词的各类算法都有了较深入的接触。
感想与建议
自然语言理解是人工智能研究的一个重要方面,可以认为它一门新兴的边缘学科。
它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。
我是信息安全的学生,这门课程其实并不是信息安全专业的特色课程。
选修这门课程的原因有两个,一是因为对人工智能研究的好奇和兴趣,人工智能有很好很广阔的发展前景,我相信这门课程会对我以后的发展起到不小的帮助;二是因为在做与此相关的创新项目,项目是关于在语音中屏蔽敏感非法字符的。
通过选修这门课程,我能熟练对汉字进行操作和比对,对分词中经常遇到的各类歧义情况有了深入的了解,例如交集型歧义和组合型歧义在分词时候应该注意什么,对于未登录词的处理方法等。
晓老师为人风趣,讲课条理清楚,容全面,深入浅出的讲解了自然语言理解这门课程。
在第一节课上课的时候还带了360随身WIFI,活跃课堂气氛,增强了师生互动。
PPT上的例子不仅能很好的反应自然语言理解这门课程的特色,而且就来自于生活当中,十分有趣,让人忍俊不禁。
晓老师也为我们提供了很多的课外相关书籍以及实验所需的各类语料信息,这让我们可以更好的在课外学习并深入领悟自然语言理解这门课程。
但由于课程的时间安排问题,使得很多的课时被挤掉,所以对于自然语言理解的讲解有限。
老师在课堂上对于很多问题都是点到为止,例如在讲到宋词的自动生成时,老师讲解了关于宋词字统计,以
及生成简易宋词的方法,但对于如何生成完美的,能符合平仄结构、满足押韵的方法并没有详细讲解。
我还希望老师能够提供一些良好的关于汉语分词系统的开源代码,以便我们能够更加深入的了解各类分词算法在实际生活中的运行。
虽然有一些小方面还可以进行改进,但总的来说,这是一门让我受益良多的课程,不仅提高了我理论上的知识水平,开阔了我的眼界,而且通过实验课程,提升了我的编程水平,训练了我的编程思维,增强了我以后的社会竞争力。