首页 / 资讯中心 / 文章详情

中英文停用词表实战指南:从预处理到工程化过滤

中英文停用词表实战指南:从预处理到工程化过滤 ★ FEATURED ARTICLE
简介中英文停用词合集面向自然语言处理、文本挖掘与信息检索开发者针对分词后高频虚词干扰关键词与情感判断的常见问题提供可直接用于预处理的中文、英文及中英合并停用词表有助于提升后续任务准确性。压缩包共11个文件包括9个txt词表与2个Python处理脚本包体仅45KB轻量易用。词表内容覆盖中文助词、介词、连词、语气词以及英文冠词、介词、连词等高频无实意词部分词表还囊括常见扩展词可供不同场景灵活选用。配套脚本支持停用词合并与去重能配合jieba、nltk等主流分词工具实现快速过滤节省手工整理时间便于应用在信息检索、情感分析、文本分类等任务中。目前已有1222人学习下载适合正在学习分词与文本预处理的开发者直接取用也可作为自定义停用词的参考基础。1. 停用词表为什么是 NLP 预处理的第一道关做中文分词和英文 NLP 的应该都有这种感受跑完分词一看结果满屏都是“的”“了”“是”“and”“the”真正有区分度的关键词被淹没在噪声里特征向量维度却一不小心拉到上万。停用词表就是拿来干这个的——把出现频率高但信息量接近零的词过滤掉让后续分类、检索、聚类更干净。这份资源把中英文停用词整理成了一套几乎随取随用的词表适合在做文本分类、搜索召回、情感分析之前先拿它做一轮预处理降噪。新手拿去过一遍流程就能见效熟手也可以拿它当底表再按领域补词。2. 中英文停用词表的选型通用度、粒度与场景匹配2.1 中英文停用词差异为什么中文不能照搬英文英文停用词的核心是功能词冠词、介词、连词、助动词比如 a、an、the、of、to、is、are。这些词在句子里承担语法功能去掉之后句子主干依然完整信息含量几乎不受损。中文的情况要麻烦一些虚词里有“的”“了”“吗”“呢”但不光如此中文里大量单字动词、代词和副词也属于高频低信息词比如“说”“看”“我”“你”“这”“那”“就”“都”。中文还有一个英文没有的问题分词。英文单词天然以空格隔开停用词过滤可以直接作用在 token 上中文必须先分词再用停用词表去匹配切分结果。这意味着中文停用词表的设计要和分词器行为对齐比如“得了”这种词在不同的分词器里可能被切成一个词也可能被切开词表里就得同时覆盖两种形态。另一个典型差异是大小写与词形。英文需要把 “The” 和 “the” 归并最好还能处理 “running” 这类词形还原之后再过滤中文没有大小写问题但简繁体经常混用比如“爲”和“为”词表里如果只收简体繁体语料过滤就会漏掉一部分。我一般会把两份表分开维护而不是强行合并# 英文停用词片段 en_stop { a, an, the, and, or, but, if, because, as, until, while, of, at, by, for, with, about, against, between, into, through, during, before, after, above, below, to, from, up, down, in, out, on, off, over, under, again, further, then, once, here, there, all, any, both, each, few, more, most, other, some, such, no, nor, not, only, own, same, so, than, too, very, just, don, now, is, are, was, were, been, being, have, has, had, having, do, does, did, doing, will, would, should, could, may, might, must, shall, can, need, } # 中文停用词片段 zh_stop { 的, 了, 和, 是, 就, 都, 而, 及, 与, 着, 或, 一个, 没有, 我们, 你们, 他们, 这, 那, 之, 其, 该, 本, 此, 且, 但, 并, 也, 很, 在, 有, 我, 你, 他, 她, 它, 对, 把, 被, 让, 向, 从, 到, 以, 为, 于, 等, 待, 看, }这段代码的重点不在词表本身而在结构中英文两套独立的 set后续可以从文件加载并合并扩充。set的哈希查找在千万级 token 过滤场景下比list快几个数量级这里不要用列表做成员判断。2.2 常用停用词表横评覆盖量、质量与适用边界市面上的中英文停用词表来源不少但质量参差。常见的有三类NLP 库自带的默认词表、个人整理发布的通用词表、按领域特化的业务词表。从文本规模和使用场合来看可以按下表理解它们的差异词表类型典型词条量级优势短板适用场景NLP 库默认词表几百到一两千开箱即用无需额外文件词条偏少、缺少领域词快速原型、Demo通用公开词表几千覆盖广、中英分离部分词条有时代色彩误伤率高新闻、评论等通用语料领域定制词表几百到几千精准、噪声低无法跨域复用金融、医疗、法律等专用系统按我的使用经验通用公开词表里的“史上最全”并不是越大越好。有些表为了凑量把“我们”“你们”“他们”全删掉这在新闻分类里问题不大但在智能客服或情感分析里人称代词往往是判断立场的关键有些表把“不”“没有”也收进去了随后做情感分析的同事直接在线上翻车。所以选表的标准应该是「够用、可裁剪、可追溯」而不是「词多」。我会固定用一个公开底表再在业务侧维护一张增量覆盖表一增一删通过代码合并。这样任何时候想回溯某个词为什么被过滤都有据可查。2.3 实际筛选停用词词频统计与人工 review 流程直接拿别人整理好的词表当然省事但语料不一样高频词的分布差异很大。我一般会先对目标语料做一轮词频统计把 Top 词捞出来人工看一遍再决定哪些进停用词表。这一步不复杂但能避免“拿着通用表硬套业务语料”的尴尬。import re from collections import Counter zh_pattern re.compile(r[\u4e00-\u9fa5]) def collect_tokens(text: str) - list: 简单切词这里只做 demo正式流程应接入分词器 return zh_pattern.findall(text) def top_n_frequency(docs: list, n: int 80) - list: counter Counter() for doc in docs: for word in collect_tokens(doc): counter[word] 1 return counter.most_common(n) # 假设 docs 是清洗后的原文列表这里跑一轮词频 stats top_n_frequency(docs, n80) for word, freq in stats[:30]: print(f{word}: {freq})跑完之后不要急着把这 80 个词全删掉。我会把结果分成三类第一类明显是虚词、无实义直接进停用词表第二类是中性的“我”“你”“这”“那”看场景再定第三类是领域强相关词比如业务术语的高频简称无论如何都不能进表。这个过程叫人工 review一天花二十分钟看一次词频列表比调半天过滤参数都管用。人工 review 的节奏我会配上一个小脚本每周把新增语料出现的新词和停用词表做 diff输出新增 Top 词如果连续两周都出现同一个无实义词就补进表里。这套流程坚持下来词表的增长速度会趋缓语料覆盖也越来越稳。3. 加载与过滤动手写一套中英文通用停用词过滤器3.1 数据结构选择与加载细节set 比 list 快在哪停用词表一旦过了几百条用 list 做成员判断的时间复杂度是 O(n)每来一个 token 就要遍历一次表。在百万级文本上这个开销会被放大得非常可观。set 的底层是哈希表平均 O(1) 查找虽然加载时多花一点内存但过滤阶段省下的时间完全值得。加载时最容易翻车的点是文本编码和空白字符。从网上下载的词表文件有 UTF-8、UTF-8 with BOM、GBK 几种可能直接用 open() 默认编码去读大概率报错。还有些文件每行末尾带着\r\n或者包含全角空格不清理的话匹配永远失败。def load_stopwords(file_path: str, encoding: str utf-8) - set: 加载停用词表自动剥离行尾换行符、空白和重复词 words set() with open(file_path, r, encodingencoding, errorsignore) as f: for line in f: w line.strip().lower() # 过滤空行、纯数字行和包含空白的行 if w and not w.isdigit() and not in w: words.add(w) return words zh_stop load_stopwords(data/stopwords-zh.txt) en_stop load_stopwords(data/stopwords-en.txt, encodingutf-8) # 合并后统一去重注意按需保留一个额外表用于恢复 all_stop zh_stop | en_stop这个函数有三个细节值得注意strip()只去首尾中间的空格由后续判断拦截避免把“不 要”这类带空格的脏词混进去lower()在英文表加载时顺手做掉省得过滤时每个 token 还要再转一次errorsignore是兜底策略遇到个别编码异常的行不会让整个脚本中断。合并词表时用|运算符比update()可读性好但要注意如果后续需要分开统计中英文过滤比例就不要合并让两份表各管各的通道。3.2 分词 过滤全流程中文分词器接入与英文词形还原停用词过滤从来不是孤立的一步它嵌在「分词 → 过滤 → 向量化」的流水线里。中文和英文在这一步的接法不一样中文要先分词英文一般先做词形还原再过滤。下面是完整的过滤函数它对中文分词后的结果和英文 token 分别处理import re from typing import List # 假设已经加载好中英文停用词表这里用占位符表示 zh_stop: set set() en_stop: set set() def simple_en_tokenize(text: str) - List[str]: 英文基础分词统一小写、去掉非字母字符 text text.lower() return re.findall(r[a-z], text) def lexical_reduce(token: str) - str: 简易词形还原这里只处理最常见的复数生产环境可接 nltk/词干库 if token.endswith(ies) and len(token) 3: return token[:-3] y if token.endswith(es): return token[:-2] if token.endswith(s) and not token.endswith(ss): return token[:-1] return token def filter_tokens(text: str, lang: str zh) - List[str]: if lang zh: # 假设 engine.cut 是分词器调用返回词列表 tokens perform_segmentation(text) else: tokens [lexical_reduce(t) for t in simple_en_tokenize(text)] stop_set zh_stop if lang zh else en_stop return [t for t in tokens if t not in stop_set and len(t.strip()) 0]这里的perform_segmentation是分词器的抽象接口实际接入时用哪个分词库就填对应的调用方法。英文这个小函数处理了最典型的复数规则ies 变 y、es 去尾、单数 s 去尾覆盖率足够日常预处理用比直接整段调用大而全的词形还原库要轻。过滤时的len(t.strip()) 0是防切割产生的空串比如连续空格在正则切分后可能出现空元素。语言分流放在函数参数里比在外部判断更利于扩展——以后加日文、韩文只需要多接一套词表和一个 tokenizer 分支。3.3 过滤后的效果验证用 TF-IDF 观察特征降维词表加过滤逻辑写完之后很多人不知道怎样才算“有效果”。一个直观的验证手段是跑一遍 TF-IDF 向量化对比过滤前后的特征维度和可读性。from sklearn.feature_extraction.text import TfidfVectorizer # 假设 texts 是已经分词并以空格拼接的文档列表 texts_after [ .join(filter_tokens(doc, zh)) for doc in raw_texts] vectorizer TfidfVectorizer(max_features5000) X_before vectorizer.fit_transform([doc for doc in raw_texts]) X_after vectorizer.fit_transform(texts_after) print(过滤前 feature 数:, X_before.shape[1]) print(过滤后 feature 数:, X_after.shape[1])对中英文混合语料TfidfVectorizer 可以直接作用在空格拼接的字符串上不需要额外传 tokenizer如果是英文lowercaseTrue是默认参数所以前面的小写处理跟向量化并不冲突。max_features5000是经验值大多数短文本分类场景够用了。如果过滤后特征数几乎没有下降有两种可能一是语料本身以专有名词和术语为主停用词占比本来就低二是停用词表选小了该滤的词没滤掉。我会把X_after的特征词表打印出来抽样看一遍确认留下来的是不是真的有区分度的实义词这一步比看数字更可靠。指标过滤前过滤后说明特征总数124807630下降约 39%Top 特征的、了、是、在、and模型、训练、样本、准确率关键词明显前置稀疏度97.2%96.1%特征空间更紧凑数值只是参考重点看特征排名是否从虚词转向实词以及保留词里是否误删了领域术语。4. 高频翻车的四个细节停用词表使用避坑这一章把实操中最常见的几个事故现场摆出来都是做 NLP 预处理时容易踩的坑。每一条都按「现象 → 原因 → 解决」的顺序讲清楚。4.1 停用词误杀否定词not / 不 / 没有不能无脑删现象情感分析模型上线后负面评论的召回率骤降人工看结果发现“不好”“不满意”“不会再用”都被归类成了中性或正面。原因词表里直接收进了 not、no、不、没有、别、莫等否定词。过滤层把这些词删掉之后原本表达否定态度的文本只剩“好”“满意”情感极性完全反转。停用词表按“高频无实义”标准生成时否定词的删除在通用语料里看似无害但凡是需要分析观点的场景都会出问题。解决情感分析、意见挖掘类项目里默认保留否定词。两个方案任选其一要么准备两套词表通用过滤一版、情感分析走白名单通道要么在代码里做例外排除把否定词从停用词集合里discard掉再过滤。我从那以后做任何情感相关项目都会先检查词表里有没有“不”“没”“无”“not”“no”这几个词。4.2 词表过大导致文本全空过滤过重把有效词删光现象同事反馈“分词结果直接没了”排查发现某些短文本经过过滤后 token 列表完全为空下游的向量化报错或者干脆给了一个空向量。原因这份资源或者自整理词表里收录了大量单字词和双字词包括“说”“看”“想”“知道”“时候”“地方”这种在新闻语料里高频但本身有实义的词。短文本一共就三五个词全被命中过滤后自然为空。解决加一个过滤后的兜底逻辑最少保留一个 token。我一般会在过滤函数最后做判断def safe_filter(tokens: List[str], stop_set: set, min_keep: int 1) - List[str]: filtered [t for t in tokens if t not in stop_set] if len(filtered) 0: # 从原 tokens 里挑最长的一个保留避免语义完全丢失 return [max(tokens, keylen)] return filtered同时给词表加一条约束单字词表单独拆成一个文件维护时更容易评估它对短文本的影响。另外空文本的检查要在向量化之前就完成日志输出别让模型收到空输入无声失败。4.3 词表编码与分词器不匹配Unicode 和分词结果错位现象加载词表后过滤毫无反应查了一遍代码逻辑没错最后发现词表里的“的”和切词结果里的“的”根本不是同一个码位。原因最常见的是 BOM 头。UTF-8 with BOM 的文件首行词条会带上\ufeffstrip()不会去掉它导致第一个词永远匹配不上。另一个场景是简繁体混排语料词表存的是简体分词器输出的是繁体肉眼看着一样实际不相等。解决加载时统一encodingutf-8-sig它能自动把 BOM 头剥掉。繁体问题用开源简繁转换库做归一化统一成简体再过滤。注意全角和半角符号也会造成类似问题比如全角逗号和英文逗号混在文本里分词器一般不切分标点但过滤逻辑如果是按标点先切分再判断就会漏掉一部分词。我做了一行加载函数的改进从此没再复现这个问题def load_stopwords(file_path: str) - set: with open(file_path, r, encodingutf-8-sig, errorsignore) as f: return {line.strip().lower() for line in f if line.strip()}4.4 领域语义漂移通用词在垂直领域可能变成关键信息现象在某金融文本项目里“多”“空”“持仓”被高频词表过滤掉了结果所有信号词全部消失特征几乎退化成标点符号和数字。原因通用停用词表只考虑“在所有语料里高频且无实义”的词它不知道“多”“空”在金融语料里恰好是术语核心。“多”“空”这类词在新闻语料中是无实义修饰在可转债、研报里就是一个多空方向信号的载体。解决领域项目必须建白名单白名单优先级高于停用词表。实现上可以先做领域词过滤再跑通用停用词def filter_with_whitelist(tokens, stop_set, whitelist): return [ t for t in tokens if (t in whitelist) or (t not in stop_set) ]简单一行条件判断就能保证领域词永远不被停用词表吞掉。做新领域的时候先拉一份术语表进白名单再跑通用词表。5. 从通用到定制把停用词表变成可维护的过滤组件5.1 轻量级组件多级词表合并与运行时刷新实际项目里我不会只依赖一份静态词表而是把停用词表包成一个组件支持通用层、领域层、项目层三层叠加。通用层放基础虚词领域层放本行业噪声词项目层放当前项目特殊要求补的词。class StopwordFilter: def __init__(self, *paths, whitelist: set None): self.stop_words set() for path in paths: self.stop_words | load_stopwords(path) self.whitelist whitelist or set() self._cache_version None def add_stopwords(self, words: Iterable[str]) - None: self.stop_words | set(words) def reload(self, *paths) - None: self.stop_words set() for path in paths: self.stop_words | load_stopwords(path) def is_stop(self, token: str) - bool: if token in self.whitelist: return False return token in self.stop_words这个类的价值在于把词表来源统一收口。调用方只面向is_stop()拿结果不管中间是哪张表过滤的也不用关心词表文件是中文还是英文来源。运行时刷新reload()的好处是词表文件更新后不用重启服务就能生效。5.2 回归自检换表前先跑一批固定样本词表更新是高风险操作加一个新词可能影响上万个样本。我在每次改表后都会跑一个回归脚本对一批固定样本做过滤对比比较新旧版本过滤后的保留率fixed_samples [...] # 200 条跨领域语料 def retention_ratio(tokens, stop_words): if not tokens: return 0.0 return len([t for t in tokens if t not in stop_words]) / len(tokens) # 旧表结果只跑一次存下来新表每次跑完做 diff for sample in fixed_samples: new_ratio retention_ratio(sample_tokens(sample), current_stop_words) if new_ratio min_expected_ratio: print(f告警: {sample} 保留率异常 {new_ratio:.3f})保留率突然下降超过预期说明新词表删多了等异常样本全部被挑出来再决定回滚还是调整。5.3 词表也要版本管理从裸文件到变更记录停用词表本质上是一份代码资产跟数据清洗脚本同等重要。我后面维护的所有词表都放进代码仓库每次改词强制提交变更说明写清楚“删了什么词、为什么删、影响哪个领域”。三五年后翻回来能顺着记录看到当年的决策过程。从那以后我改词表都会强制先跑一遍回归脚本再更新到线上然后看一眼 diff 里的每一行词是不是都有理由。希望这些经验帮到你少走几趟停用词翻车的弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站