spark分词工具

分词工具调研
一、背景
调研了两种分词工具:

Ansj:https://github.com/NLPchina/ansj_seg
HanLP(https://github.com/hankcs/HanLP#7-极速词典分词)
最终选择了HanLP

二、Ansj

  1. 利用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)
    
  2. 但是自定义词库存在局限性,导致有些情况无效:

比如:“不好用“的正常分词结果:“不好,用”。

(1)当自定义词库”好用“时,词库无效,分词结果不变。

(2)当自定义词库“不好用”时,分词结果为:“不好用”,即此时自定义词库有效。

  1. 由于版本问题,可能DicAnalysis, ToAnalysis等类没有序列化,导致读取hdfs数据出错

此时需要继承序列化接口

case class myAnalysis() extends DicAnalysis with Serializable
val seg = new myAnalysis()

二、HanLP

  1. 同样可以通过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)强制生效
  1. 并发问题:

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")

猜你喜欢

转载自blog.csdn.net/weixin_40901056/article/details/89349095