☰
中文停用词表详解:从.doc解析到分词过滤实战
2026/10/15 4:10:36 网站建设 项目流程

简介:哈工大停用词表是一份面向中文自然语言处理的基础词表,主要服务于分词前的文本预处理环节,解决“的、了、和”等高频虚词与符号干扰语义分析的问题。适用于入门 NLP 的开发者、研究文本挖掘的学生,以及在信息检索、情感分析、文本分类等任务中需要精简特征集的技术人员。资源包为单个 doc 文档,压缩后仅 39KB,轻量便于直接导入分词系统或人工扩展。目前已有 3260 人学习下载。该词表由哈工大社会计算与信息检索研究中心整理,除了常见停用词外,还收录了各类标点符号、数字及非文字字符,覆盖范围较广,可有效减少预处理步骤的无意义干扰,帮助后续分词、词性标注与依存分析等任务聚焦在关键实词上。依托 LTP 等工具链使用时,能进一步提升中文文本分析与模型训练的整体效率,是一份实用且入门友好的基础资源。

1. 为什么一份十余年前的 .doc 停用词表到今天还有人在用?

做中文分词和文本去噪的同学,大概率在某个文件夹里见过一份名为“停用词表.doc”的老文件。它常被圈内人归到某高校NLP团队整理的那一版,词表只有一千多个词,却几乎覆盖了中文文本里最常见的一批虚词、代词、助词、语气词和标点。为什么一份十几年前、格式还是 .doc 的旧表,至今仍是很多文本预处理管线的默认起点?因为它踩中了中文停用词的一个核心痛点:通用性。无论你做舆情分析、客服问答、搜索引擎分词还是评论情感分析,先跑一遍这份词表,噪声能去掉一大截。这篇文章不吹不黑,讲清楚这份词表怎么解析、怎么接入分词流程、怎么扩展,以及最常见的翻车点。适合正在搭文本预处理管线的同学,也适合被 .doc 编码折磨过的老手。

2. 先弄懂停用词表的底层逻辑:停哪些词、按什么标准停

2.1 停用词不是在“删词”,是在“降噪”

很多初学者第一次接触停用词表,以为它的作用是“把没意义的词删掉”。这个理解不算错,但容易误导。真正的问题是:什么叫“没意义”?单独看,“的、了、是、在”确实没有实义,但在句子里它们承担语法功能。删除它们不是为了减少存储,而是为了在向量化、检索、聚类时减少那些区分度极低的维度。信息检索里有个概念叫逆文档频率(IDF),一个词在越多文档里出现,它的IDF越低,对文档区分的贡献越小。停用词就是IDF趋近于0的那批词,删掉它们不会丢信息,反而让高频噪声不再覆盖真正的主题。

中文和英文的停用词逻辑不完全一样。英文有空格分词,停用词表主要处理冠词、介词、be动词;中文分词本身就是难题,停用词处理通常发生在分词之后,而且中文的“的、地、得”“你、我、他”“这、那”这类词在口语和书面语中都高频出现。某高校这份停用词表之所以被广泛使用,是因为它把通用性做到了极致:不只收虚词,还收了很多在通用场景下区分度很低的实词,比如“进行”“通过”“使用”等。这类词单独看有实义,但在海量文本里过于泛化,进入特征空间会稀释真正的主题词。

从工程角度看,停用词过滤本质是一个集合查找操作。分词产出的每个token,都在停用词集合里查一遍,命中就丢弃。这个操作本身不复杂,复杂度是O(1),但前置的分词质量会直接影响过滤效果。如果分词把一个城市名切成了两个单字,停用词表再准也救不回来。所以,停用词表并不是一个独立的“删词工具”,它和分词器、词表、语料分布是绑在一起的。一个成熟的预处理流程,应该把分词词典、停用词表、词性过滤串起来,而不是只堆一个几千词的停用词表。

判断一个词该不该进停用词表,我一般会用两个粗糙但有效的标准:第一,它在语料中的文档频率是否超过某个阈值,比如80%的文档都出现;第二,它被删掉后是否会让主要信息丢失。比如“我们”在高频出现时删掉没问题,但在“我们公司”这种场景下,“我们”可能携带主体信息。这就是为什么后面要讲领域裁剪,通用停用词表不能直接搬到所有业务里。先用这份表跑通基线,再根据业务数据做减法,是最稳的路线。

2.2 通用停用词与领域停用词的边界

通用停用词表解决的是“绝大多数文本都不需要的词”,但业务场景里还有一类词,在通用语料里不算高频,在特定领域却泛滥成灾。比如电商评论里,“质量”“价格”“发货”“客服”这类词几乎每条评论都出现;政务文本里,“进一步”“加强”“落实”“推动”出现频率极高。如果把这类词全留在特征里,聚类和分类的结果会被这些领域套话主导,真正的观点词反而被淹没。

这就是通用停用词表和领域停用词表的边界:通用表负责虚词和超高频泛化实词,领域表负责“在本领域内信息量为零”的词。某高校这份停用词表本质上是通用表,它里面几乎没有电商、医疗、财经等垂直词的影子。所以你直接拿它做通用文本预处理没问题,做垂直领域分析,就必须额外构建一份领域停用词表,否则效果会很差。

具体操作上,我习惯把停用词表拆成两层:第一层是原版通用表,第二层是领域补充表。在代码里维护两个文件,加载时合并成一个set。这样升级其中一层不会影响另一层,也方便不同团队共享基础表。领域补充表通常是从真实语料里统计出来的,后面有一章专门讲怎么挖。要注意的是,领域停用词表不能只靠人工整理,人工能列出的词永远是少数,而且会带有主观偏见。最好用数据驱动的方式,从语料的词频、文档频次、TF-IDF排名里找出那些“出现很多次但几乎不携带领域信息”的词。

边界还有一种情况:同一个词在不同任务里可能是停用词,也可能不是。比如“客服”在销售话术分析里可能是核心对象,但在评论情感分析里就是噪声。所以不要试图维护一份“万能领域停用词表”,领域表应该跟着任务走。我做过一个模拟项目X,早期把领域表做得过大,结果分类模型的F1不升反降,后来把表收缩到原来的三分之一,效果才回来。这让我意识到,停用词表更像是一个“排除清单”,而不是“黑名单大全”,词越少越可控。

2.3 这份停用词表的典型结构与词性分布

打开这份 .doc 的原始文件,你会发现它的内部结构并不统一。我见过三个流传较广的版本:最朴素的一款是一行一个词,纯文本段落,按首字母或拼音排序;另一款是表格结构,第一列是词,第二列是词性标注;还有一款每个词后面带一个逗号或空格,词与词之间用分隔符断开。因为源头不可考,不同人手转出来的格式五花八门,这也是为什么网上能找到各种清洗版、去重版、补全版。拿到手的第一件事不是直接用,而是先确认它的分隔方式,不然按行读取会漏掉一堆词。

从词性分布来看,这份表大致可以分成五类:一类是代词和称谓,如“你、我、他、她、它、我们、你们、他们”;一类是介词和连词,如“在、从、对、关于、和、与、或者”;一类是助词和语气词,如“的、了、着、过、呢、吧、吗、啊”;一类是高频泛化动词,如“进行、通过、使用、获得”;还有一类是标点符号,包括中英文逗号、句号、引号、括号等。标点符号进停用词表在很多工程里是被忽略的,但分词器通常会把标点当成独立token输出,如果不在停用词表里处理,特征空间会平白多出几百个维度。

这份表最被低估的价值是它的“标点覆盖”和“单字词覆盖”。中文分词在遇到“地得”这类单字助词时,经常切出一些孤零零的单字,比如“就”“又”“都”“还”。这些单字在通用停用词表里基本都有,能极大减少后续向量化的噪声。不过也正因为单字覆盖广,它会把一些有实义的单字也误伤,比如“稳”“绿”“热”。所以使用时要清楚:这是一份偏向保守的停用词表,宁可多杀不可放过。在做关键词抽取时,建议在这个基础上额外保留单字实词,避免主题被掏空。

从数量上看,不同版本差异很大。精简版可能只有几百词,完整版通常在一千到两千词之间。我建议拿到后先去重、排序、统计词数,再决定是否裁剪。不要直接拿一个几万词的“超级停用词表”,那会把好端端的主题词都过滤掉。通用的原则是:停用词表越短,越安全;越长,越需要审。某高校这份表的另一个好处是它不带词性标注,可以直接用,少了词性映射的麻烦;代价是它无法做“仅过滤动词”“仅过滤虚词”这种精细操作。如果你需要更细的控制,可以在表里追加一列词性,或者按词性拆成多个文件。

3. 把 .doc 停用词表转成能用的 UTF-8 文本:完整命令与脚本

3.1 从 .doc 到 .txt 的三种常见做法

.doc 是较早期的二进制文档格式,跟现在常见的 .docx 不一样,直接用文本编辑器打开全是乱码。转换工具的选择会影响后续清洗成本。我试过三种常见做法,先说结论:桌面环境用 LibreOffice 最省心,服务器上没图形界面就用 antiword,偶尔才用 Pandoc。

antiword 是一个专门解析 .doc 的命令行小工具,体积小,速度快,但对表格型 .doc 支持不好。如果这份停用词表是表格结构,antiword 可能只输出第一列,或者漏掉后面的词性标注。LibreOffice 的 headless 模式能完整解析 .doc 的表格、段落、样式,转换成纯文本时可以选择保留表格文本,所有单元格内容按顺序输出。Pandoc 本身不直接解析 .doc,它依赖 LibreOffice 或 antiword 做中间转换,所以如果你只有 Pandoc,最终质量取决于它调用的后端。

我一般这么选:在 Windows 上装 LibreOffice,用命令行批量处理;在 Linux 服务器上用 antiword 应急;如果 .doc 里有复杂表格,就一定要用 LibreOffice。另外提醒一句,如果原文件是 GBK 编码,转换出来的 txt 可能是 GBK 而不是 UTF-8,后面清洗脚本要特别注意编码,不然会多出一堆乱码。这个坑我在模拟项目X里踩过,当时整整浪费了一个下午,最后才发现是编码问题。

3.2 用 LibreOffice 命令行批量转换

在安装好 LibreOffice 之后,可以用以下命令把 .doc 转成纯文本:

# 用 LibreOffice 无头模式把停用词表.doc 转成纯文本 soffice --headless --convert-to txt:Text \ --outdir ./stopwords_txt 停用词表.doc

命令里的--headless表示不启动图形界面,适合在服务器或脚本里调用;--convert-to txt:Text指定使用纯文本过滤器,其中txt是输出格式,Text是 LibreOffice 内部的过滤器标识;--outdir指定输出目录,建议单独建一个文件夹,避免生成文件混在源码目录里。

转换后的文件名默认是“停用词表.txt”,内容可能包含段落换行、表格单元格换行以及一些不可见空格。如果原始 .doc 是表格布局,转换出来的 txt 会把每个单元格的文本按顺序拼在一起,词与词之间可能是换行符,也可能是空格,这决定了后面清洗脚本的写法。我的习惯是转换后先用文本编辑器打开看一眼,确认分隔符是空格还是换行,再写清洗规则。

如果机器上没装 LibreOffice,用 antiword 也可以应急:

antiword 停用词表.doc > stopwords_raw.txt

antiword 默认输出到 stdout,所以需要重定向。注意 antiword 对中文支持一般,如果输出乱码,可以加-m参数指定映射文件,或者换 LibreOffice。不要在一棵树上吊死,工具不对就立刻换。

3.3 清洗与去重:脚本处理细节

转换出来的 txt 通常混杂着空行、重复词、带分隔符的词,直接使用会把整个停用词集合搞脏。下面这段 Python 脚本可以完成去空行、去重、拆分一行多词、排序输出:

# 清洗从 .doc 转出的停用词表 # 输入:转好的 txt(可能是 GBK 或 UTF-8) # 输出:一行一个词、无重复、按字典序排列 def clean_stopwords(src_path, dst_path, encoding='utf-8'): words = set() # 有些版本一行包含多个词,分隔符可能是空格、逗号、制表符 with open(src_path, 'r', encoding=encoding, errors='ignore') as f: for line in f: # 去掉行首尾空白和换行 line = line.strip() if not line: continue # 统一替换中文逗号、顿号为空格,再按空白切分 line = line.replace(',', ' ').replace('、', ' ').replace(',', ' ') parts = line.split() for part in parts: word = part.strip(' \t\r\n“”‘’\"\'') if word and word not in words: words.add(word) # 排序输出,避免每次运行顺序不同 with open(dst_path, 'w', encoding='utf-8') as f: for word in sorted(words): f.write(word + '\n') return len(words) if __name__ == '__main__': n = clean_stopwords('停用词表.txt', 'stopwords_clean.txt', encoding='utf-8') print(f'清洗后词数: {n}')

这段脚本有几个关键点。第一,用set去重,天然保证唯一性,而且后面的in判断是 O(1),词表再大也不怕。第二,用replace把中文逗号、顿号、英文逗号统一替换成空格,再按split()切分,这样一行里的多个词都能被拆出来,而不是只取第一个。第三,errors='ignore'用于跳过无法解码的字节,避免整个程序因为一个乱码字符崩溃。不过要注意,如果编码设置错了,errors='ignore'会静默丢弃很多字符,所以清洗前最好先确认编码。

参数说明:src_path是转换出来的原始文本路径,dst_path是输出路径,encoding可以传gbk或utf-8。如果发现转换后的文本乱码,就先用file命令看编码,或者用一个小脚本分别尝试两种编码读取,看哪个的词数更合理。

3.4 验证转换结果:词数、空行、是否包含常见停用词

清洗完一个停用词表,别急着拿去用,先做三件事:数词数、检查空行、抽查常见词。最简单的检查命令是:

wc -l stopwords_clean.txt

因为上面脚本已经去重并排序,输出文件里每一行就是一个词,所以wc -l的行数就是词数。如果你发现行数少得离谱,比如只有几十行,那一定是清洗脚本把词吞了,多半是分隔符没处理好,或者编码不对。如果行数多到离谱,比如几万行,那可能是原文件里混入了大量带空格的长短语,需要再过滤。

再用一小段 Python 做常见词检查:

# 验证清洗后的停用词表 common = ['的', '了', '是', '在', '你', '我', '他'] with open('stopwords_clean.txt', 'r', encoding='utf-8') as f: words = [line.strip() for line in f if line.strip()] missing = [w for w in common if w not in words] if missing: print('缺失:', missing) else: print('常见词齐全') print(f'总词数: {len(words)}')

如果缺失“的”或“了”,说明清洗时编码错误或者原文件漏了内容,这时候要回到转换步骤排查。如果词数正常、常见词齐全,这份表就可以进入下一步,接入分词流程了。我每次都会把验证脚本留在项目里,因为停用词表是会被反复替换的,换表后跑一遍验证,能省很多排查时间。

4. 在分词流程里接入停用词过滤:最小可复现代码

4.1 加载停用词表的正确姿势

很多初学者加载停用词表时只做了一行readlines()就当成词表,结果分词后一个词都过滤不掉。原因很简单:文件里的每一行末尾都有换行符,如果不strip(),你加载出来的词是“的\n”而不是“的”。停用词过滤是精确匹配,多一个\n或者\r都会导致匹配失败。正确姿势如下:

# 加载停用词表为 set def load_stopwords(path): stopwords = set() with open(path, 'r', encoding='utf-8') as f: for line in f: word = line.strip() if word: stopwords.add(word) return stopwords

这里对每一行都做strip(),它会去掉行首尾的换行、空格、制表符。if word会跳过空行。用set而不是list,一方面去重,另一方面让后续的in判断更快。如果词表很大,list的in是 O(n),在每篇文档几千个 token 的场景下会变得很慢,而set几乎是无感的。

这个函数的输入path指的是 UTF-8 编码的停用词表文件。如果你手上的文件是 GBK,先转码保存成 UTF-8,不要在这里做编码转换,因为open()的encoding参数只能指定一个编码,混用容易出错。我的习惯是:所有停用词表统一用 UTF-8 存储,新增词表时在加载前用iconv或 Python 转一次码,避免在业务代码里写两套编码分支。

4.2 用 jieba 分词后过滤停用词的最小实现

接下来是一个可以直接运行的完整示例,用 jieba 做中文分词,再用上面加载的停用词表做过滤:

import jieba def load_stopwords(path): stopwords = set() with open(path, 'r', encoding='utf-8') as f: for line in f: word = line.strip() if word: stopwords.add(word) return stopwords def filter_text(text, stopwords): # cut_all=False 使用精确模式;HMM=True 开启新词发现 tokens = jieba.cut(text, cut_all=False, HMM=True) filtered = [t for t in tokens if t.strip() and t not in stopwords] return filtered if __name__ == '__main__': sw = load_stopwords('stopwords_clean.txt') sample = "今天天气不错,我们去公园散步吧。" print(filter_text(sample, sw))

运行这段代码,你大概率会看到类似['今天天气', '不错', '公园', '散步']的输出。“今天天气”可能会被 jieba 切成一个整体,也可能切成“今天”和“天气”,这取决于 jieba 的词典和版本。逗号、句号、句末语气词“吧”都在停用词表里,所以被过滤掉了。这里的关键是jieba.cut的两个参数:cut_all=False表示精确模式,不会输出全模式那种冗余组合;HMM=True表示开启隐马尔可夫模型,这对于识别不在词典里的新词很有用,但也可能把一些词切得奇怪。如果你发现过滤结果不理想,可以先关掉HMM试试。

参数说明:filter_text里的列表推导式先做t.strip()判断,剔除空白 token;再判断t not in stopwords,这一步就是核心过滤。有人会问要不要先做词性过滤,比如只保留名词和动词。我的建议是:第一版流程先只用停用词表,跑通后再加词性过滤,因为词性标注会增加计算成本,而且词性工具本身也会出错,能不加就不加。

4.3 参数与阈值:什么时候停用词会误伤

停用词过滤看起来简单,但“一刀切”会带来几个经典误伤。第一个是否定词误伤。“不”“没”“没有”这组词在很多通用停用词表里是存在的,但在情感分析场景里,它们直接决定情感极性。比如“我不喜欢这个产品”,分词后过滤掉“不”,剩下“我 喜欢 这个 产品”,情感从负变正,这属于严重翻车。解决方法是:在情感分析任务里,加载停用词表后手动把否定词从set里删掉,或者干脆不为情感分析任务使用这类通用表。

第二个误伤是专名分词被切碎。假设有一家品牌名是“某青”两个字,分词时被切成了“某”和“青”,而“某”刚好在停用词表里,过滤后品牌名就消失了。要解决这个问题,得从分词组入手,比如用 jieba 的自定义词典把品牌名强制组成一个词:

jieba.load_userdict('userdict.txt')

userdict.txt里一行一个词,可以包含词频和词性,格式是“词 词频 词性”,比如“某青 100 n”。这样 jieba 在分词时会优先把“某青”当作整体输出,后续再过滤停用词就不会切到单字了。

第三个误伤是单字实词被误删。中文里有大量单字实词,比如“家”“爱”“绿”“稳”,它们在通用停用词表里可能没有,但某些翻录版本里会混入很多单字。如果你发现过滤后重要关键词大量丢失,可以在过滤时加一个长度门槛:

filtered = [t for t in tokens if len(t) > 1 and t not in stopwords]

这段代码会过滤所有单字 token,保留长度大于 1 的词。代价是单字实词也会被误杀,所以这个参数要谨慎开启。我一般会先跑一版不加长度过滤的结果,检查被过滤掉的单字里有没有高频实词,如果有,就把这些实词加进自定义词典,让分词把它们组到多字词里,而不是简单粗暴地过滤单字。

5. 停用词表避坑指南:5 个常见翻车现场

5.1 现象:用 Pandoc 转出来的 txt 全是乱码

有次我在一台 Linux 服务器上尝试用 Pandoc 把这份 .doc 转成 txt,结果输出的文件里全是“锟斤拷”和口口口。原因是 Pandoc 本身不解析 .doc,它调用了 antiword,而 antiword 默认按系统 locale 输出编码,服务器是 UTF-8,但 .doc 里是 GBK 中文,于是两边对不上。解决:先确认源头编码,再用iconv转换。命令如下:

iconv -f GBK -t UTF-8 乱码.txt > stopwords_utf8.txt

如果iconv报非法字符,说明文件里混了其他编码,可以用-c忽略非法字符:

iconv -f GBK -t UTF-8 -c 乱码.txt > stopwords_utf8.txt

不过-c会静默丢字符,所以转换完一定要再看看词数。更省事的方案是直接用 LibreOffice headless 转换,它对中文编码的处理比 antiword 稳得多,我后来就很少用 Pandoc 转 .doc 了。

5.2 现象:按行读取后词表长度少了一半

清洗脚本最早写成按行strip()后直接加入set,结果原本一千多词的词表只剩五百多。原因是原文件里一行可能包含多个词,词与词之间用中文逗号或空格分隔,按行读取时把整行当成一个词,再一排序输出,看起来“少了一半”。解决:在清洗脚本里先把行内的中文逗号、顿号、英文逗号统一替换成空格,再按空白切分。前面的clean_stopwords已经处理了这个问题。这里要提醒的是,不同版本的 .doc 转出来分隔符不一样,不要假设永远是“一行一个词”。最稳妥的做法是先打开转换后的文本文件看前 20 行,确认分隔符类型,再决定清洗规则。

5.3 现象:把停用词表直接交给 Spark 后内存溢出

在单机 Python 里用set存放停用词表没有问题,但到了 Spark 分布式环境,每个 executor 的每个 task 都会复制一份停用词表对象,如果表里有大量字符串,内存开销会成倍增长。我在模拟项目X里就遇到过,十几个 executor 同时加载同一份大词表,还没开始分词就 OOM。解决:把停用词表做成广播变量,让每个 executor 只保留一份只读副本。伪代码如下:

# 在 Spark 中广播停用词集 stopwords_bc = spark.sparkContext.broadcast(stopwords_set) def filter_func(tokens): sw = stopwords_bc.value return [t for t in tokens if t not in sw]

广播变量在网络传输和执行任务时只传递一次,不会跟着每个闭包重复序列化。如果你的停用词表本身很大,超过几十 MB,还可以考虑把它压缩成某种紧凑格式,比如用frozenset或存成字典映射,但通常没必要。另外,不要在 driver 端用一个普通的 Python 集合然后直接传给map闭包,那样会在每次任务分发时重新序列化,又慢又耗内存。

5.4 现象:过滤后城市名、品牌名等专名被误删

有一类问题很难从停用词表本身排查:分词阶段把专名切碎了,导致其中的单字字符命中停用词表。比如“某北方城市名”六个字,分词器切成了“某北方”“城市名”,其中“北方”如果恰好是停用词,那专名就没了。解决思路不是去改停用词表,而是调整分词。先收集业务里的高频专名,做成自定义词典,让分词器把它们整体切出来。jieba 支持自定义词典:

# userdict.txt 每行格式:词 词频 词性 某北方城市名 100 n 某品牌名 50 nz

然后在代码里加载:

jieba.load_userdict('userdict.txt')

加载后再次分词,专名就会作为一个整体出现,停用词过滤不会命中它的子串。注意,自定义词典的词频不能设得太低,否则 jieba 可能仍然优先按默认词典切分。如果专名特别长,还可以在代码里先用正则把专名保护起来,替换成占位符,分词后再还原,但这步操作较复杂,一般只有专名比例高的场景才需要。

5.5 现象:明明在停用词表里,却过滤不掉

这是最让人抓狂的一类问题:你打开文件看到了“的”,停用词表里也有“的”,但分词后“的”就是过滤不掉。原因通常是不可见字符。一种情况是原文件里“的”后面带了全角空格,strip()对全角空格无能为力;另一种情况是清洗时把中英文逗号替换成了空格,但全角空格被当成词的一部分保留了。排查方法是用repr()打印 token 的十六进制表示:

word = '的' stopword = '的\u3000' # 从文件里读出来的词 print(repr(word), repr(stopword))

如果发现stopword里有\u3000,也就是全角空格,或者\xa0,也就是不间断空格,说明词条本身不干净。解决:在清洗阶段对每个词再做一次更彻底的字符清理,丢弃包含不可见空白符的词,或者把这些全角空格统一替换成空字符串。我的做法是在clean_stopwords里加一行:

word = word.replace('\u3000', '').replace('\xa0', '').strip()

这样能去掉绝大多数不可见字符。另外,如果分词产出的 token 是“的。”这种带标点的形式,那也不是停用词表的锅,而是标点过滤还没接入,需要让标点也进停用词表,或者在过滤时用正则先把标点剥掉。

弄完这五条,你会发现所有坑几乎都集中在编码、分隔符、不可见字符和分词粒度上。所以我的排错顺序永远是:先查编码,再查分隔符,最后查不可见字符。这个顺序能解决九成以上的停用词表问题。

6. 进阶:自己生成一份领域停用词表并持续迭代

领域停用词表不能靠拍脑袋,最实用的方法是基于语料统计。常见做法是:拿一批代表性的业务文档,按同一个流程分词、过滤通用停用词,然后统计每个词的“文档频率”和“总词频”。文档频率超过某个阈值,比如 60% 的文档都出现,这类词大概率是领域套话。用 TF-IDF 的思路看,这类词的 TF-IDF 排名会非常靠后,可以自动提取出来,再人工过滤一遍。

生成首版领域停用词表时,我会把候选词按“文档频率”从高到低排序,优先看前 100 个高频词。里面通常能看到“我们”“公司”“产品”“问题”“进行”这一批词,其中“公司”“产品”可能在你这个领域里真的有业务含义,所以不能直接全删。人工审查这一步不能省,脚本只能给候选,决定权在人。我见过有人直接把统计结果当停用词表用,结果把核心业务词删掉,模型效果一落千丈。

维护方面,领域停用词表至少每个季度更新一次。因为语言会变,业务会变,上季度的领域套话可能下季度变成关键词。我自己的习惯是把停用词表放在代码仓库里,每次更新都走评审,提交信息里写明“新增某词,原因:文档频率超过 70% 且与业务无关”。这样团队其他人能追溯每一个词的来源,不会出现一个没人敢动的“祖宗词表”。

还可以给停用词表加一层“豁免名单”。比如在业务里,“价格”虽然是高频词,但在价格分析任务里是最核心的要素,那就在代码里对这条任务单独排除它。这比把词从停用词表里删掉更安全,因为别的任务可能仍然需要停用它。

最后分享一个血泪经验:我早期做文本聚类时,直接把网上找的一份几万词的停用词表倒进流程,结果聚类出来的全是“我们、你们、他们”。后来花了整整一天做清洗,发现那份表里混入了大量带语义的实词,删掉后效果立刻正常。从此我给自己立了个规矩:停用词表想加词可以,但每次加完必须跑一遍全量验证集,对比去掉这个词前后的指标变化;指标没变好,这个词就不会被保留。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询