使用Porter、Lancaster、SnowballStemmer出现词干提取错误的技术咨询
遇到的问题
从新闻文本里抽了一段内容,用NLTK的Porter、Snowball、Lancaster三种词干提取器处理后,结果出现不少不符合预期的情况,尤其是Lancaster就算改了内置规则,还是会把很多词过度截断,比如iran变成ir、million变成mil。
测试代码与输出
测试文本
ds = "happiness john mccain furious iran treated us say justice yahoo settles email privacy said classactiosunnistan us said allied safe zone plan take territ blow million al jazeera america finally calls us navy sailors held iranian military signs n"
Porter 和 Snowball 的处理代码
from nltk.stem import PorterStemmer, SnowballStemmer # Porter 词干提取 porter_stemmer = PorterStemmer() def porter_stem(text): return ' '.join([porter_stemmer.stem(word) for word in text.split()]) print(porter_stem(ds)) # Snowball 词干提取(英文) snowball_stemmer = SnowballStemmer("english") def snowball_stem(text): return ' '.join([snowball_stemmer.stem(word) for word in text.split()]) print(snowball_stem(ds))
修改规则后的 Lancaster 处理代码
from nltk.stem import LancasterStemmer lancaster_stemmer = LancasterStemmer() # 移除内置规则里索引为12的'e1>'规则 temp_rules = list(lancaster_stemmer._rule_tuple) temp_rules.pop(12) lancaster_stemmer._rule_tuple = tuple(temp_rules) def lancaster_stem(text): return ' '.join([lancaster_stemmer.stem(word) for word in text.split()]) # 注:原代码里用了ds2,这里统一用测试文本ds print(lancaster_stem(ds))
各工具的输出结果
PorterStemmer 输出
happi john mccain furiou iran treat us say justic yahoo settl email privaci said classactiosunnistan us said alli safe zone plan take territ blow million al jazeera america final call us navi sailor held iranian militari sign n
SnowballStemmer 输出
happi mcpain john mccain furious iran treat us sai justic yahoo settl email privaci classactiosunnistan us alli safe zone plan take territ blow million al jazeera america final call us navi sailor held iranian militari sign n
LancasterStemmer 输出
happy mcpain john mccain fury ir tre us sai justice yahoo settle email priv classactiosun us al safe zone plan take territ blow mil al jazeer americ fin cal us navy sail held ir milit sign n
问题原因拆解
- PorterStemmer:它本身是轻量级规则设计,不会过度截断,但词干形态可能不符合常规拼写(比如
happiness→happi、furious→furiou),这是它的规则特性,不是bug,只是输出的词干比较“精简”。 - SnowballStemmer:作为Porter的改进版,对部分词保留更完整(比如
furious→furious),但出现mccain→mcpain的错误,是因为它的规则里有针对cc的替换逻辑,误触发了。 - LancasterStemmer:它属于激进型的词干提取器,天生就倾向于大幅截断词汇。就算你改了一条规则,其他规则还是会触发过度截断:
iran→ir:匹配了移除an后缀的规则million→mil:触发了移除lion后缀的规则treated→tre:把过去式后缀砍得太狠了
解决办法
1. 深度定制Lancaster规则
Lancaster的规则是按优先级顺序执行的,要避免特定词被截断:
- 先打印内置规则列表(
print(lancaster_stemmer._rule_tuple)),找到导致目标词被截断的那条规则,要么移除,要么调整它的优先级(把不希望触发的规则往后放)。 - 比如针对
iran,可以加一条优先保留完整词的规则,或者删掉匹配an后缀的规则(注意这条规则可能影响其他词)。
2. 换用更合适的工具
如果激进截断不符合你的需求,换个工具更省心:
- SnowballStemmer:整体表现均衡,误处理少,适合大多数英文场景。
- WordNetLemmatizer:和词干提取不同,它会把词还原成词典里的标准原形(比如
happiness→happiness、treated→treat),结果更符合自然语言,但最好配合词性标注用,效果才好。
3. 给特定词加修正映射
针对新闻领域的高频词汇,可以手动加个修正字典,把错误的词干改回来:
stem_fixes = { 'ir': 'iran', 'mil': 'million', 'tre': 'treat', 'furiou': 'furious' } def fix_stemmed_text(stemmed_text): words = stemmed_text.split() return ' '.join([stem_fixes.get(word, word) for word in words]) # 用法示例 fixed_porter = fix_stemmed_text(porter_stem(ds)) print(fixed_porter)
内容的提问来源于stack exchange,提问作者shin

