大规模语料处理中Python词形还原方法的提速优化咨询
词形还原大规模语料处理性能优化问题
我近期测试了多种词形还原(lemmatization)方法,计划用于超大规模语料处理场景。以下是我使用的实现方法与测试结果,咨询两个核心问题:
- 是否有可落地的技巧提升这类方法的运行速度?目前测试结果显示,支持词性标注的spaCy处理速度最快(为优先选用方案),其次是lemminflect。
- 当前的实现思路是否存在问题?目前所有处理函数均通过pandas的
.apply()方法作用于存储文本的DataFrame对象。
现有实现代码
def prepareString_nltk_current(x): lemmatizer = WordNetLemmatizer() x = re.sub(r"[^0-9a-z]", " ", x) if len(x)==0: return '' tokens = word_tokenize(x) tokens = [lemmatizer.lemmatize(word).strip() for word in tokens if word not in stop_words] if len(tokens)==0: return '' return ' '.join(map(str,tokens)) def prepareString_pattern(x): error = 'Error' x = re.sub(r"[^0-9a-z.,;]", " ", x) if len(x)==0: return '' try: return " ".join([lemma(wd) if wd not in ['this', 'his'] else wd for wd in x.split()]) except StopIteration: return error def prepareString_pattern(x): error = 'Error' x = re.sub(r"[^0-9a-z.,;]", " ", x) if len(x)==0: return '' try: return " ".join([lemma(wd) if wd not in ['this', 'his'] else wd for wd in x.split()]) except StopIteration: return error def prepareString_spacy_pretrained(x): if len(x)==0: return '' doc = nlp(x) return re.sub(r"[^0-9a-zA-Z]", " ", " ".join(str(token.lemma) for token in doc)).lower() def get_wordnet_pos(word): lemmatizer = WordNetLemmatizer() """Map POS tag to first character lemmatize() accepts""" tag = nltk.pos_tag([word])[0][1][0].upper() tag_dict = {"J": 'a', "N": 'n', "V": 'v', "R": 'r'} return lemmatizer.lemmatize(word, tag_dict.get(tag, 'n')) def prepareString_nltk_pos(x): tokens = word_tokenize(x) if len(x)==0: return '' return " ".join(get_wordnet_pos(w) for w in tokens) def prepareString_textblob(x): sent = TextBlob(x) tag_dict = {"J": 'a', "N": 'n', "V": 'v', "R": 'r'} words_and_tags = [(w, tag_dict.get(pos[0], 'n')) for w, pos in sent.tags] return " ".join([wd.lemmatize(tag) for wd, tag in words_and_tags]) def prepareString_genism(x): return " ".join([wd.decode('utf-8').split('/')[0] for wd in lemmatize(x)]) def prepareString_leminflect(x): doc = nlp(x) return " ".join([str(x._.lemma) for x in doc]) def prepareString_pattern_pos(x): s = parsetree(x, tags=True, lemmata=True) for sentence in s: return re.sub(r"[^0-9a-zA-Z]", " ", " ".join([str(x._.lemma()) for x in doc])).lower()
测试结果

解答
现有实现的明显问题
- 重复初始化重对象:
prepareString_nltk_current、get_wordnet_pos每次调用都会新建WordNetLemmatizer实例,这类初始化开销很高,放在逐行执行的逻辑里会浪费大量算力,所有模型、工具类实例都应该全局初始化一次,不要放到处理函数内部。 - 词性标注逻辑效率极低:
get_wordnet_pos对每个单独的词调用一次nltk.pos_tag,而该接口原生支持批量传入分词列表,逐词调用的速度比批量处理慢10倍以上。 - 正则未预编译:所有
re.sub调用都没有提前编译正则模式,每次执行都会重复编译表达式,产生无意义开销。 - 代码存在显性bug:
prepareString_pattern被重复定义两次;prepareString_leminflect参数和循环变量重名,且调用的x._.lemma属性写法有误;prepareString_pattern_pos内部引用了未定义的doc变量,运行会直接报错。 - 串行处理瓶颈:pandas的
.apply()默认是单进程串行执行,处理大规模语料时无法利用多核CPU性能,资源利用率极低。
针对优先选型spaCy的提速技巧
- 禁用无用管道组件:加载spaCy模型时如果只需要词形还原功能,直接关闭不需要的parser、ner等组件,代码示例:
nlp = spacy.load("en_core_web_sm", disable=["parser", "ner", "attribute_ruler"]),单这一项就能提速2~3倍。 - 用
nlp.pipe批量处理:不要逐行传入单个文本调用nlp(),将所有文本整理为列表传入nlp.pipe(texts, batch_size=2000)做批量推理,处理效率比逐句处理高1~2倍,处理完成后再和原DataFrame对齐即可。 - 启用GPU加速:如果有NVIDIA显卡,安装对应CUDA版本的spaCy后调用
spacy.require_gpu()开启GPU推理,大规模语料场景下速度可提升5~10倍。 - 直接调用官方属性:现有代码用
str(token.lemma)取的是词元的整数哈希值,应该直接用token.lemma_获取字符串形式的词元,省去类型转换步骤。
通用提速方案
- 提前预编译所有用到的正则表达式,用
re.compile()生成匹配对象后再调用sub方法。 - 替换串行
.apply()为并行执行:使用pandarallel等工具,仅需两行初始化代码即可将逐行逻辑自动分配到多个CPU核心执行,速度提升幅度和CPU核心数基本持平。 - 预处理阶段提前过滤空文本、长度过短的无意义文本,不要传入后续处理流程浪费算力。
- 停用词过滤尽量前置,减少后续词形还原环节需要处理的token数量。
内容的提问来源于stack exchange,提问作者Mike Zoucha
相关产品推荐
相关产品推荐

