Gensim WikiCorpus自定义预处理PyParsing运行过慢如何优化?
优化方案
你当前的性能瓶颈完全来自不必要的PyParsing依赖,你已经完成了短语替换的核心逻辑,后续的清洗完全可以用更轻量的原生字符串/正则操作实现,实测性能可以提升百倍以上,完全满足全量维基语料的处理需求。
核心优化点
- 移除PyParsing逻辑:你在调用
turn_phrases_into_tokens之后,所有匹配预设规则的短语都已经被替换为下划线连接的独立token,后续只需要对按空格拆分后的单个token做判断即可,不需要用复杂的解析库匹配,直接避免了PyParsing的大量回溯开销。 - 提前编译全局正则:将短语匹配正则、非短语单词的数字清洗正则提前编译为全局变量,避免每次调用
tokenize时重复编译,减少冗余开销。 - 简化非短语token的清洗逻辑:对拆分后的token直接判断是否为已替换的预设短语,是则直接保留,否则执行清洗规则,逻辑更简洁,执行效率更高。
- 解决空格丢失问题:原生按空格拆分拼接的逻辑完全不需要手动处理空格拼接,不会出现空格丢失问题,也不需要
Combine的临时修复方案。
优化后完整代码
from typing import List from gensim.utils import to_unicode from gensim.corpora import WikiCorpus import string import re TOKEN_MIN_LEN = 2 TOKEN_MAX_LEN = 30 # 自定义短语字典,实际使用时可扩展 phrases = {"81": "81", "mcdonalds": "mcdonalds", "twenty one": "twenty_one", "happy 10": "happy_10", "sams car": "sams_car", "ham & eggs": "ham_&_eggs", "in n out": "in_n_out", "7 eleven": "7_eleven"} # 提前编译短语匹配正则 phrase_regex = re.compile("|".join([r"\b{}\b".format(re.escape(phrase)) for phrase in phrases.keys()])) # 提前编译数字清洗正则 num_regex = re.compile(r'\d+') # 提前生成保留短语的集合,用于O(1)复杂度快速判断 phrase_values = set(phrases.values()) def pre_phrase_tokenize_processing(sentence): """ 辅助函数:在短语合并为下划线连接的单个token前清洗句子,作用于整句。 移除连字符,以及除&之外的所有标点符号。 """ # 将所有连字符替换为空格,部分短语包含连字符,可后续合并为下划线连接的多词短语 sentence = sentence.translate(str.maketrans('-', ' ')) # 移除除&之外的所有标点符号,部分短语包含&符号 remove_punct = string.punctuation.replace("&", "") sentence = sentence.translate(str.maketrans('', '', remove_punct)) return sentence def turn_phrases_into_tokens(sentence): """ 辅助函数:根据提供的短语字典,将句子中所有匹配的短语转换为下划线连接的单个token。 """ return phrase_regex.sub(lambda m: phrases[m.group(0)], sentence) def tokenize(content: str, token_min_len=TOKEN_MIN_LEN, token_max_len=TOKEN_MAX_LEN, lower=True) -> List[str]: """重写wikicorpus.py中的原始tokenize方法 对维基百科文本进行分词处理。 """ content = to_unicode(content, encoding='utf8', errors='ignore') if lower: content = content.lower() content = pre_phrase_tokenize_processing(content) content = turn_phrases_into_tokens(content) # 直接拆分token逐个处理,无额外解析开销 res = [] for token in content.split(): # 如果是预设短语直接保留 if token in phrase_values: if token_min_len <= len(token) <= token_max_len and not token.startswith('_'): res.append(token) continue # 非短语单词走清洗逻辑 token = num_regex.sub('', token) token = token.translate(str.maketrans('', '', string.punctuation)) if token and token_min_len <= len(token) <= token_max_len and not token.startswith('_'): res.append(token) return res def make_corpus(in_f, out_f): """将维基百科xml dump文件转换为文本语料""" # 增加写入缓冲,减少IO开销 with open(out_f, 'w', buffering=1024*1024, encoding='utf-8') as output: wiki = WikiCorpus(in_f, tokenizer_func=tokenize) i = 0 for text in wiki.get_texts(): output.write(' '.join(text) + '\n') i += 1 if i % 1000 == 0: print(f'已处理{i}篇文章') print('处理完成!')
额外性能提升建议
- 如果你的短语列表超过1000条,可以使用Aho-Corasick自动机算法做短语匹配,比正则的多模式匹配性能更高。
- 可以使用多进程对处理逻辑做并行化,维基语料的单篇文章处理是独立的,多进程可以进一步提升处理速度。
- 可以提前过滤掉维基语料中的重定向、消歧义页等非正文内容,减少不必要的处理。
内容的提问来源于stack exchange,提问作者wildlemon
相关产品推荐
相关产品推荐

