Spark做词性标注遇到的问题及解决方法

来源:互联网 发布:沙特军事知乎 编辑:程序博客网 时间:2024/05/17 19:18

在用spark做中文分词、词性标注的时候遇到了一些问题,记录一下场景及解决方法。

场景是这样的,我希望用spark做词性标注,这可以用jieba分词的POSTokenizer,本来只要集群的每个节点上的pyspark包路径都安装jieba就可以了。但问题在于,我需要用自定义的词典创建Tokenizer,目的是想令Tokenizer只切出词典中有的词,其他的词都过滤。起初的方案是在client 模式的driver program代码里用本地词典路径初始化Tokenizer,然后把这个Tokenizer对象绑定到需要传递给rdd.map的函数中,代码大概是

from functools import partialfrom jieba import Tokenizerdef func(text, tokenizer):    tokenizer.cut(text)    ...tokenizer = Tokenizer(local_dict_path)func_bind_tokenizer = partial(func, tokenizer=tokenizer)rdd.map(func_bind_tokenizer)

不过这种方法是行不通的,原因是spark需要把func_bind_tokenizer这个闭包分发给各个worker,因此需要把这个闭包包括的对象都序列化。pyspark中的序列化方式是通过cPickle,而tokenizer因为包含添加新词的功能,因此带有锁,所以无法序列化, 这个闭包也就没办法分发给各worker了。

解决方法

解决方法有两种,第一种是修改jieba的代码,去除锁机制,并且自己保证添加新词的时候不会出错。这种方法就不细说了, 说说第二种不需要改代码的方法。

不考虑spark的架构如何,最好能够把词典分发到每个worker,然后在每个worker初始化一个tokenizer,这样就不需要把tokenizer序列化了。但spark是把task分发给各executor执行,我们并不能直接与worker联系。所以只好退而求其次在task粒度初始化tokenizer。因为每个partition对应一个task,所以用mapPartitions方法针对partition粒度执行函数,用SparkContext.addFile()方法将本地词典提交到集群,让集群分发到各个节点。这样每个task都会初始化一个tokenizer,只要词典不是太大,task数不是太多,都不用担心资源消耗的问题。代码大概如下

from jieba import Tokenizerfrom jieba.posseg import POSTokenizerfrom functools import partialfrom pyspark import SparkFilesdef func_partition(iterator, dict_name):    # 获取各节点上词典文件的绝对路径    dict_path = SparkFiles.get(dict_name)    tokenizer = Tokenizer(dict_path)    pseg = POSTokenizer(tokenizer)    for text in iterator:        tokens = pseg.cut(text, HMM=False)        for token in tokens:            yield token.word, token.flag# sc 是实例化的SparkContextdict = 'user_dict'# 将本地词典文件添加到集群sc.addFile(local_dict_path)partial_func_partition = partial(func_partition, dict_name=dict)# 执行mapPartitionrdd.mapPartitions(partial_func_partition)
原创粉丝点击