You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Gensim FastText执行build_vocab时触发MemoryError内存溢出异常

问题根因

你遇到的MemoryError和迭代器的文件关闭逻辑没有直接关系,核心触发点有两个:

  • Gensim词表构建的机制误区:你设置的max_vocab_size=100_000、min_count=20是全量语料扫描完成后才会生效的剪枝规则,在scan_vocab阶段,程序会把语料中出现过的所有token全部存入字典做计数,不管这个token出现1次还是1000次,不会提前做过滤。如果你的语料里存在数百万甚至上千万个唯一token(比如分词错误产生的长串、乱码、随机数字/字符组合),哪怕最后只留10万词,扫描阶段的字典就会把64GB内存占满。
  • 迭代器的读文件逻辑缺陷:你用f.read()一次性把整个文件加载到内存,只要单个文件体积超过几个G,哪怕后续处理完释放,内存峰值也很容易触发OOM。with语句仅负责关闭文件句柄,不会自动回收你读入内存的文本、分词结果等Python对象。
排查步骤

按顺序做以下检查快速定位问题:

  • 单独运行你的语料迭代器,用collections.Counter统计全量语料的唯一token总数,同时抽样打印10~20个文件的分词结果,检查是否存在长度超过20的无意义长串、整段文本未被切开、大量乱码/纯数字/特殊符号组合的垃圾token。
  • 运行build_vocab时实时监控内存:如果内存是线性缓慢上涨到阈值,说明是总唯一token数过多;如果内存是读取某个文件时突然跳涨,说明是单个文件体积过大,一次性读入导致峰值超标。
可落地解决方案

1. 重构迭代器,降低单次内存占用+提前过滤无效token

不要一次性读全文件,改成逐行读取逐行分词,同时在迭代阶段就过滤掉明显无意义的token,从源头减少进入词表统计的无效内容:

class MyIterator:
    def __init__(self, file_list, verbose=False):
        self.file_list = file_list
        self.verbose = verbose

    def __iter__(self):
        total_files = len(self.file_list)
        for idx, (lang, file_path) in enumerate(self.file_list):
            tokenizer = Tokenizer()
            # 逐行读文件,不要一次性加载全量内容
            with open(file_path, "r", encoding="utf-8") as f:
                for line in f:
                    line = line.strip()
                    if not line:
                        continue
                    try:
                        if self.verbose and idx % 100 == 0:
                            print(f"Processing {idx+1}/{total_files} - lang: {lang}")
                        tokens = tokenizer.tokenize(line)
                        # 提前过滤无效token:长度不符合要求、纯数字、纯符号的直接丢弃
                        tokens = [
                            t for t in tokens 
                            if 2 <= len(t) <= 15  # 阈值可按业务调整
                            and not t.isdigit()
                            and not all(c in "!@#$%^&*()_+-=[]{}|;':\",./<>?`~" for c in t)
                        ]
                        if tokens:
                            yield tokens
                    except Exception as e:
                        if self.verbose:
                            print(f"Parse error on {file_path}: {str(e)}")
                        continue

提前把所有待处理文件路径整理成列表传入迭代器,不要每次迭代都遍历目录:

# 提前整理全量文件路径
languages = [
    lang for lang in os.listdir(ds_path) 
    if not lang.startswith(".") and lang != "markdown"
]
all_files = []
for lang in languages:
    lang_dir = os.path.join(ds_path, lang)
    for fname in os.listdir(lang_dir):
        if not fname.startswith("."):
            all_files.append((lang, os.path.join(lang_dir, fname)))

2. 分批次构建词表,避免扫描阶段词表无限膨胀

不要一次性扫描全量语料建表,先抽部分语料初始化词表,再增量更新剩余语料——增量更新时Gensim会自动按max_vocab_size、min_count规则剪枝,不会堆积垃圾token:

import random
random.shuffle(all_files)
# 抽20%的文件做初始词表构建
init_split = int(len(all_files) * 0.2)
init_iter = MyIterator(all_files[:init_split], verbose=True)
model.build_vocab(corpus_iterable=init_iter)

# 增量更新剩余语料
rest_iter = MyIterator(all_files[init_split:], verbose=True)
model.build_vocab(corpus_iterable=rest_iter, update=True)

3. 参数调优进一步降低内存占用

  • 适当提高min_count阈值:从20调到30~50,出现次数极低的罕见词对FastText模型效果增益极小,却会占用大量词表内存。
  • 缩小子词n-gram范围:FastText默认min_n=3, max_n=6,如果不需要特别细的字符级特征,可以改成min_n=3, max_n=5,能减少30%左右的子词表内存占用。
  • 多语言语料不要混训:不同语言的token、字符n-gram完全不通用,混训会让词表大小翻数倍,建议按语言拆分单独训练模型。

内容的提问来源于stack exchange,提问作者random_display_name

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 16:48:17