今年,和同学一起参加了大赛,目的嘛,无非是为了锻炼能力,积累一些项目经验,最好拿个奖。在选题目时,看到自然语言交流系统时,感觉很新鲜也很有意思,所以也没怎么想,就报了这个题目。所以7月份的暑假我们就开始了我们的项目。
刚开始做时,一点头绪都没有,整天的上网找资料,各种谷歌百度,各种博客论坛,搜索的关键词也无非是智能自然语言交流、智能机器人、中文问答系统等等等等。而我们的思路也是些零散的,例如我们知道会用到分词,会用到语法分析、语义分析、关键词提取等自然语言处理策略,但我们还是不知道该如何下手。就这样查了一个多周的资料,也纠结了一个多周,最终决定了,先做分词。但做分词,怎么做?一个句子怎么能分出词来?想了半天,不会,上网查,看有什么好的算法,结果发现分词算法很复杂,都是中科院ictclas分词,ikanalyzer分词,还有什么庖丁解牛,先把源码下载下来看看,事实证明,看不太懂。不管了,调一下直接用吧,结果Java项目调的好好的,而一建立Android项目总是崩溃,看了logcat发现是OOM错误,也就是超内存,其实也是,分词的处理过程很耗资源,手机的内存与CPU根本受不了,那怎么办?自己搭建个服务器,把手机当客户端?算了,反正算法是人家的,自己搭服务器也不能把人家的变成自己的,查了一下,复旦NLP提供webservice方式调用,那问题不就解决了吗,把处理字符串提交给webservice接口,就可以得到分词结果了。
分词的问题解决了,那我们分词有什么用啊,怎么根据问题找答案?难道要一个问题对应一个答案?那也不智能啊。看了几篇国内和国外的关于智能自动问答系统研究的论文,主要涉及到对文法的分析处理、问题分析、常问问题集匹配、信息检索以及答案抽取等内容。
看了好几天的论文,虽然没能学到太多,但知道了接下来要做的就是常问问题集的匹配。
而要做常问问题集匹配,就需要建一个常问问题库(FAQ)。但我没学过数据库啊,怎么建啊,学,先把最基本的学会够用就行。写了几天终于把常问问题库建立起来了,库里一个问题对应一个答案。而匹配嘛,先找到问题的重点,也就是根据分词结果找到问题的一个或多个关键词,根据这些关键词来匹配答案。而这时你就要问了,那如果我问的问题没有关键词呢,比如“你好”、“谢谢”等,根本就提取不到关键词,怎么匹配?对于这些简单问题,或者说是一些短语,我们就全文模糊匹配,检索包含问题串的问题进行匹配。
做完了常问问题集匹配模块,发现自己面对一个更难的问题:对于用户的问题,常问问题库里没有怎么办?这时,我想如果我是这个机器人,我应该怎么办呢?记得去年,有一次我小侄女问我“西游记里的裙钗是谁啊?”,我一想,我连裙钗是什么都不知道更别说是谁了,便问宿舍同学,谁知道西游记里的裙钗是谁啊,结果没人知道,好吧,既然没人知道,我就把电脑打开,上网搜索西游记里的裙钗是谁,都说裙钗是牛魔王的老婆,红孩儿他妈铁扇公主是也,于是我就把这个答案发给了我小侄女。而对于这个机器人来说,对于它不知道的问题,肯定不可能去问其他手机你知不知道啊,你知不知道啊。那就直接上网搜吧,而搜索引擎又不会直接返回答案,而是给你很多与搜索关键词有关的网页,需要你自己去找一个你想进入的网页进入,然后获取到你想要的信息。
而这个问题的难点很多,机器人怎么把问题Post给搜索引擎,搜索引擎搜索完后机器人又怎么知道哪个网页更有可能存在我想要的信息,即使找到了包含我们想要信息的网页,网页中那么多信息,哪些是我们想要的,即使找到了我们想要的信息,机器人又该怎么去组织语言去描述答案。这些问题困扰了我很久。后来我想到用Jsoup网页解析包,对HTML网页进行解析,于是我想到将问题关键词或问题Post给百度知道网页,对百度知道的搜索结果页面进行解析,我看了一下搜索结果页面的源文件,发现每个链接都有一个“标题”,而这个标题也代表着该链接网页的主要信息,因此我们要做的就是比较一下问题与这个标题的相似度,找一个相似度比较高,也就是最有可能包含答案信息的链接(而实际设计中,因为手机解析HTML网页时很费时间与内存,处理过程大约要耗时3、4秒左右,而这3、4秒虽然很短,但在用户看来就像卡死一样,为了不影响性能这里没有采用相似度计算,而是采用模糊匹配进行匹配),然后进入这个链接。
而对于这个答案页面,可以看到对于所提问的问题有一个满意答案,其属性为"div [class=line content]",我们可以提取里面的内容,我们做的是直接将答案返回给主程序,因为我始终没能写出好的、比较满意的自然语言生成算法(这也是很多研究院和公司的目标)。自然语言生成算法涉及到很多问题,比如说话人说话的语境(我们所说的上下文),说话的心情,说话的语调等等都会影响到问题的真正意义,而作为机器人想要精确了解问题的真正意思并且给出一个既符合逻辑又令说话人满意的答案有相当大的工作要做,不过我相信,只要我们不断地探索,不断地去努力去研究,我们就能设计出像科幻电影里的机器人一样智能、甚至有自己的思想的智能机器人。
在大赛答辩时,我还是很有自信的,因为在之前我看过几个其他队的参赛演示视频,他们的智能机器人都是问一个问题,给一个他们已经事先编辑好的答案,只不过在检索答案时的检索策略不同而已,而且很多队还加了很多像发送短信、打电话、放音乐等等鸡肋,看了半天也没有看到一个能让我好奇与崇拜的项目。虽然答辩时我们是第三个答辩有些吃亏,但我觉得凭借我们的亮点一定会是二等奖,而且有可能一等奖,但是在获奖文档中我却看到我们仅得了三等奖,而一个队得了个一等奖。我又看了一遍他们的演示视频,虽然他们为机器人设计了3中不同性格,对于相同问题,不同性格会给与不同的回答,但这些回答都是他们事先就已经写好的,根本就没体现出智能来嘛,他们还加了一些什么地图,星座等功能,只能说看起来很强大很华丽。始终不明白我们为什么只获得了三等奖,也始终不明白这些老师出智能自然语言交流题目的初衷和目的,但有一点我们明白了:智能自然语言交流的智能体现在了哪里。大赛后我也和命题老师交流了,他也承认这次的项目很雷同,没有什么创意,也肯定、鼓励了我。
这次大赛算是结束了,而这个系统的主要算法是我写的,所以没能获得一等奖都是我的错,我为这次“失败”负全部责任,重要的是,我始终因为这个对我的队友感觉特别愧疚,有时甚至冲动的想跑到他们面前表达歉意,却每每又张不开口,不知道怎样表达。
不管怎样,我们最终完成了整个项目,我们也收获了很多,我们获得了友谊,我们学得了知识,我们锻炼了能力。抛开所有的抱怨,抛开所有的失落,我们要更加努力,去提升自己,去锻炼自己,让自己变得更加优秀。最爱食指的诗与大家共勉:当我的紫葡萄化为深秋的露水,当我的鲜花依偎在别人的情怀,我依然固执地用凝霜的枯藤,在凄凉的大地上写下:相信未来。我要用手指那涌向天边的排浪,我要用手掌那托住太阳的大海,摇曳着曙光那枝温暖漂亮的笔杆,用孩子的笔体写下:相信未来。
最后是搜索引擎部分的代码,大家可以看一下,有什么好的自然语言生成算法或思路,可以交流一下,互相学习:
public String get_bd_zhidao(String question){
StringBuffer sff = new StringBuffer();
String myString = new String("");
String back = new String("");
String search_key = new String(question);//四叶草的英文是什么?
String ans_url = new String("http://zhidao.baidu.com");
search_key= URLEncoder.encode(search_key);
search_key= search_key.replaceAll("%25", "");//全文搜索
if(search_key.indexOf('') != -1)
search_key= search_key.replace(' ', '+');//多关键词处理
HttpGet getMethod = newHttpGet("http://zhidao.baidu.com/index/?fr=index_search&word="+search_key);//百度知道关键词搜索URL
HttpClient httpClient = new DefaultHttpClient();//客户端
try{
HttpResponse response = httpClient.execute(getMethod); //发起GET请求
back= EntityUtils.toString(response.getEntity(), "gbk");
}catch(ClientProtocolException e) {//异常处理
//TODO Auto-generated catch block
back= "Client Protocol Exception";
e.printStackTrace();
}catch (IOException e) {
//TODO Auto-generated catch block
back= "Client Protocol Exception";
e.printStackTrace();
}
myString= back;
String html = myString;
Document doc = Jsoup.parse(html);//解析HTML文档
Elements links = doc.getElementsByClass("a2");//获取所有搜索结果网页链接
String title_pre = new String("");
int count = 1;//辅助变量
for(Element link : links){
title_pre= link.text();
StringregEx="[`~!@#$%^&*()+=|{}':;',//[//].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
Patternp = Pattern.compile(regEx);//正则表达式过滤
Matcherm = p.matcher(title_pre);
title_pre= m.replaceAll("").trim();
if((count== 1) && (question.indexOf(title_pre)!= -1) &&(link.getElementsByTag("a").attr("href").indexOf("question")!= -1)){//如果是百度知道网页链接&&link.getElementsByTag("a").attr("href").indexOf("question")!= -1
sff.append(link.getElementsByTag("a").attr("href"));
count = 0;
}
}
myString = ans_url+sff.toString();//得到结果页链接URL
StringBuffer sff2 = new StringBuffer();
String myString2 = new String("");
myString2= posturl(myString);
String html2 = myString2;
Document doc2 = Jsoup.parse(html2);
Element links2 = doc2.select("div [class=line content]").first();//获取满意答案class
String title = doc.head().getElementsByTag("title").text();
String regEx="[`~!@#$%^&*()+=|{}':;',//[//].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
Pattern p = Pattern.compile(regEx);//正则表达式过滤
Matcher m = p.matcher(title);
title= m.replaceAll("").trim();
//for(Element link : links){
if(links2!= null){//如果有“满意答案”,获取
sff2.append("").append(links2.text()).append("\n ");
//}
myString2 = sff2.toString();
if(myString2.indexOf("提问者评价")!=-1){//过滤提问者评价
intindex = myString2.indexOf("提问者评价");
myString2= myString2.substring(0, index);
}
if(myString2.indexOf("评论")!=-1){//过滤评论
int index = myString2.indexOf("评论");
myString2= myString2.substring(0, index);
}
}
else{//没有满意答案,置空
myString2= "";
}
return myString2;//返回答案
}
public String posturl(String url) {
InputStream is = null;
String result = "";
try{
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();
is= entity.getContent();
}catch (Exception e) {
return"Fail to establish http connection!" + e.toString();
}
try{
BufferedReader reader = new BufferedReader(new InputStreamReader(is, "gbk"));
StringBuilder sb = new StringBuilder();
String line = null;
while((line = reader.readLine()) != null) {
sb.append(line+ "\n");
}
is.close();
result= sb.toString();
}catch (Exception e) {
return"Fail to convert net stream!";
}
return result;
}