分词工具调研
一、背景
调研了两种分词工具:
Ansj:https://github.com/NLPchina/ansj_seg
HanLP(https://github.com/hankcs/HanLP#7-极速词典分词)
最终选择了HanLP
二、Ansj
-
利用DicAnalysis可以自定义词库:
val forest = DicLibrary.get() if(forest == null){ DicLibrary.put(DicLibrary.DEFAULT, DicLibrary.DEFAULT, new Forest()) } for(b<-brandDict){ DicLibrary.insert(DicLibrary.DEFAULT, b, "n", 1) } for(c<-cateDict){ DicLibrary.insert(DicLibrary.DEFAULT, c, "n", 1) } // 自定义词库和停用词等,需要通过广播将词表发送给各节点 val stopBC = spark.sparkContext.broadcast(stop) val dicBC = spark.sparkContext.broadcast(DicLibrary.get(DicLibrary.DEFAULT)) val parse = DicAnalysis.parse(keywordDealed, dicBC.value).recognition(stopBC.value) // 抽取分词结果,不附带词性 val words = for(i<-Range(0,parse.size())) yield parse.get(i).getName val filterWords: Seq[String] = words.map(_.trim).filter(x => x.length > 1)
-
但是自定义词库存在局限性,导致有些情况无效:
比如:“不好用“的正常分词结果:“不好,用”。
(1)当自定义词库”好用“时,词库无效,分词结果不变。
(2)当自定义词库“不好用”时,分词结果为:“不好用”,即此时自定义词库有效。
- 由于版本问题,可能DicAnalysis, ToAnalysis等类没有序列化,导致读取hdfs数据出错
此时需要继承序列化接口
case class myAnalysis() extends DicAnalysis with Serializable
val seg = new myAnalysis()
二、HanLP
-
同样可以通过CustomDictionary自定义词库:
for(b<-brandDict){ CustomDictionary.add(b, "nz 1024 n 1") } for(c<-cateDict){ CustomDictionary.add(c, "nz 1024 n 1") }
但是在统计分词中,并不保证自定义词典中的词一定被切分出来,因此用户可在理解后果的情况下通过
StandardTokenizer.SEGMENT.enableCustomDictionaryForcing(true)强制生效
- 并发问题:
CustomDictionary是全局变量,不能在各节点中更改,否则会出现并发错误。
但是HanLP.segment(sentence),只有一个参数,不能指定CustomDictionary,导致在各个excutors计算的时候全局CustomDictionary无效。
由于CustomDictionary是全局变量,因此我采用一个方式:每个分区都对CustomDictionary加锁并添加一次词库,性能影响较小:
//对词库加锁
def addCustomWord(){
CustomDictionary.dat.synchronized{
for(b<-brandDictBC.value){
CustomDictionary.add(b, "nz 1024 n 1")
}
for(c<-cateDictBC.value){
CustomDictionary.add(c, "nz 1024 n 1")
}
}
}
//每个分区都要添加词库,并设置强制生效
def trialIterator(iter: Iterator[(String,String,String,String,String)]) ={
addCustomWord
StandardTokenizer.SEGMENT.enableCustomDictionaryForcing(true)
//分词逻辑
iter.map{
case (key_word, user_log_acct, request_tm, pvid, skuid)=>
val keywordDealed = Utils.sbc2dbcCase(key_word).toLowerCase()
val wordArr = HanLP.segment(keywordDealed).toArray().map{
case term:Term =>
term.word
}
(key_word,wordArr, user_log_acct, request_tm, pvid, skuid)
}
}
//主逻辑,调用上面方法
val splitKeyword = showLogTable.as[(String,String,String,String,String)]
.mapPartitions(trialIterator)
.toDF("key_word","key_word_split","user_log_acct","request_tm","pvid","skuid")