初试身手—融入自己的中文分词器
现在准备着手写一个真正意义上的聚类搜素了。一开始担心老外的carrot2对中文会进行“歧视”,后来发现原来carrot2还是比较重视中文的,在有一个org.carrot2.filter.lingo.local.ChineseLingoLocalFilterComponent的类,专门用来为中文提供分词操作。再次往下细看,底层的分词造作在org.carrot2.util.tokenizer.parser.jflex.JeZHWordSplit中实现的,采用的是基于lucene的MMAnalyer 。我没有使用过这种分词器,不知道它的消歧机制和切分效率如何,于是想拿经常使用的分词器来做个比较。于是,必须建立一个自己的中文filter组建。 以往经常使用的是中科院的java改良版(还是很慢)和c++版本的mmseg,由于使用的是自己家是windows平台的,所以只好用中科院的java改良版。
1. 首先在org.carrot2.util.tokenizer.parser中新加一个分析器就叫KellyWordSplit:
package org.carrot2.util.tokenizer.parser;
import org.apache.lucene.analysis.ictcals.FMNM;
import org.carrot2.util.tokenizer.parser.jflex.PreprocessedJFlexWordBasedParserBase;
public class KellyWordSplit extends PreprocessedJFlexWordBasedParserBase {
//public Segment seg = null;
public KellyWordSplit() {
// try {
// seg = new Segment(1, new File(".").getCanonicalPath()
// + File.separator+"dic"+File.separator);
// } catch (IOException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
}
@Override
public String preprocess(String input) {
System.out.println("cut:"+input);
return FMNM.ICTCLASCut(input) ;
}
}
然后再在这个包中建立一个解析工厂:ICTCALWordBasedParserFactory
package org.carrot2.util.tokenizer.parser;
import org.apache.commons.pool.BasePoolableObjectFactory;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.SoftReferenceObjectPool;
public class ICTCALWordBasedParserFactory {
/** Chinese tokenizer factory */
public static final ICTCALWordBasedParserFactory ChineseSimplified = new KellyICTCALWordBasedParserFactory();
/** Parser pool */
protected ObjectPool parserPool;
/** No public constructor */
private ICTCALWordBasedParserFactory() {
// No public constructor
}
public WordBasedParserBase borrowParser() {
try {
return (WordBasedParserBase) parserPool.borrowObject();
} catch (Exception e) {
throw new RuntimeException("Cannot borrow a parser", e);
}
}
/**
* @param parser
*/
public void returnParser(WordBasedParserBase parser) {
try {
parserPool.returnObject(parser);
} catch (Exception e) {
throw new RuntimeException("Cannot return a parser", e);
}
}
/**
* @author Stanislaw Osinski
* @version $Revision: 2122 $
*/
private static class KellyICTCALWordBasedParserFactory extends
ICTCALWordBasedParserFactory {
public KellyICTCALWordBasedParserFactory() {
parserPool = new SoftReferenceObjectPool(
new BasePoolableObjectFactory() {
public Object makeObject() throws Exception {
return new KellyWordSplit();
}
});
}
}
}
接上面阐述,从以上两种聚类的结构和效率来看,其实carrot2自带的MMAnalyer的效果都还不错,没有特殊需求可以不用加入自己的分词组建。
融入系统
Carrot2针对来自lucene的搜索源提供了专门的输入组建LuceneLocalInputComponent,看了它里面的结构,我觉得并不符合我这套系统的搜索架构
,换句话说LuceneLocalInputComponent太过“傻瓜”化,对于需要高性能的应用并不适合。于是我决定使用carrot2的直接输入输出组建ArrayInputComponent和ArrayOutputComponent,俗话说“最基本的也是最灵活的”真的是不错!此外我选用lingo算法的过滤组建。Ok,一切就绪,马上着手组建。一下是主要程序片段:
/**
* @param documentList:原信息
* @return ArrayOutputComponent.Result 下午03:55:03
*/
public ArrayOutputComponent.Result cluster(
List<RawDocumentSnippet> documentList) {
final HashMap params = new HashMap();
params
.put(ArrayInputComponent.PARAM_SOURCE_RAW_DOCUMENTS,
documentList);
// params
// .put(ArrayInputComponent.,
// documentList);
ProcessingResult pResult;
try {
pResult = controller.query("direct-feed-lingo", query, params);
return (ArrayOutputComponent.Result) pResult.getQueryResult();
} catch (MissingProcessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null
到这儿一切主要的步骤就差不多了,剩下的就是如何组装聚类结果并返回了。我选择了以xml的方式返回。一下是主要片段:
/**
* 将结果组装成xml中,并返回
*
* @param result
* @return String 上午11:17:08
*/
public String wrapperResult(ArrayOutputComponent.Result result,
ClusterObject co) {
if (result == null) {
return null;
}
StringBuilder sb = new StringBuilder();
final List clusters = result.clusters;
int size = clusters.size();
if (size > 0) {
sb.append("<CLUSTERS_SIZE>");
sb.append(size);
sb.append("</CLUSTERS_SIZE>");
int num = 1;
for (Iterator i = clusters.iterator(); i.hasNext(); num++) {
wrapperCluster(sb, 0, (RawCluster) i.next(), co);
}
}
return sb.toString();
}
多嘴说说
其实carrot2是一个做实时聚类的开源项目,它聚类的输入类型是数组,即将所有要聚类的数据一次性输入,这样无疑对大数据量的聚类操作是不合适的。所以carrot2适合做新闻发布系统等实时聚类的项目。本人草草的看了一下源码,发现carrot2的主要聚类操作在MultilingualClusteringContext和MultilingualFeatureExtractionStrategy
;特征值采用VSM(vector space model向量空间模型),提取主要一下方法来完成private Feature[] extractSingleTerms()
rivate Feature[] extractPhraseTerms(int[] indexMapping)。
实施聚类需要边搜索边聚类,这无比给搜索性能带来负面影响。为了提高聚类和搜索的效率,我预备从框架上做一个新调整,思路如下:就是专门开一个聚类/分类的进程。第一步,搜索进程将聚类信息传递给聚类/分类进程后,就可以去做自己的事情了,如组装xml(结果中带有一个key定义该此搜索嘴硬的聚类结果值)等等。第二步,当聚类/分类进程收到聚类信息后,开始聚类/分类操作,组装聚类的结果。第三步,这一步可以有两种实现方式:一种是当前台显示层接收到搜索结果后,根据结果xml中的聚类Key值去聚类/分类进程拿聚类结果,这种方式在于前台可以尽快的显示搜索结果,而且如果聚类/分类进程和搜索进程不在一台服务器上,还可以减少搜索进程的并发负担,因为它可以快速的返回减少在搜索服务器的停留时间。但是这种方式会增大前台显示的通讯负担和显示效果,因为一次搜索前台会提出两次请求,而且搜索结果和左侧聚类会分两次先后显现,即异步显示;另一种方式就是在返回结果前由后台搜索进程从聚类/分类进程中取结果,并组装返回,它的好处是减少了前台的通讯次,而且两中结果(聚类和搜索)会同时显现,感官上会好接受一些。但这种方式的不足在于一次搜索的时间会变长,即用户等待结果的时间会变长。
啊,java的世界真是广阔无垠啊,开源真是个促进技术发展的好东西。原来有一个叫weka的开源项目,早已在数据挖掘界众人皆知了,它里面有着很多data mining的算法实现,对于大数据量也很使用,所以接下来,我将对weka展开“攻击”o(∩_∩)o…。