如何在scikit-learn的TfidfVectorizer中添加同义词识别?
嘿,这个需求我太熟了!分两种场景给你解决方案,刚好覆盖你举的house/houses这类词形变化,以及更广义的同义词映射:
方案1:用词形还原搞定单复数/时态这类词形变化
你举的house和houses其实属于词形变化,用「词形还原(Lemmatization)」就能轻松把它们统一成同一个词。推荐用NLTK的WordNetLemmatizer,它能根据词性精准还原单词,比简单的词干提取(Stemming)效果更自然。
具体步骤:
- 自定义一个分词器,先做基础分词,再对每个token做词形还原
- 把这个分词器传给
TfidfVectorizer的tokenizer参数
代码示例:
from sklearn.feature_extraction.text import TfidfVectorizer from nltk.stem import WordNetLemmatizer import nltk # 第一次运行需要下载NLTK的必要数据 nltk.download('wordnet') nltk.download('averaged_perceptron_tagger') lemmatizer = WordNetLemmatizer() def lemmatize_token(token): # 获取词性标签,让还原更准确(比如区分名词house和动词house) pos_tag = nltk.pos_tag([token])[0][1][0].lower() # 只保留名词/动词/形容词/副词的标签,其他默认按名词处理 pos = pos_tag if pos_tag in ['n', 'v', 'a', 'r'] else 'n' return lemmatizer.lemmatize(token, pos=pos) def custom_tokenizer(text): # 先用Tfidf默认的分词器拆分文本 base_tokens = TfidfVectorizer().build_tokenizer()(text) # 对每个token做词形还原 return [lemmatize_token(token) for token in base_tokens] # 实例化TfidfVectorizer,使用自定义分词器 tfidf = TfidfVectorizer(tokenizer=custom_tokenizer) # 测试一下效果 corpus = ["I own a house", "They bought three houses", "She is housing a friend"] X = tfidf.fit_transform(corpus) print(tfidf.get_feature_names_out()) # 输出会包含house(houses被还原成house,housing作为动词也会被还原成house)
方案2:自定义同义词映射处理广义同义词
如果除了词形变化,你还需要把真正的同义词(比如house和home、apartment)统一成同一个术语,那就需要做一个同义词映射字典,在分词后替换对应的词。
可以把同义词映射和词形还原结合起来,效果更好:
from sklearn.feature_extraction.text import TfidfVectorizer from nltk.stem import WordNetLemmatizer import nltk import re nltk.download('wordnet') nltk.download('averaged_perceptron_tagger') # 定义你的同义词映射:键是要替换的词,值是统一的目标词 synonym_map = { "home": "house", "apartment": "house", "bungalow": "house" } lemmatizer = WordNetLemmatizer() def lemmatize_token(token): pos_tag = nltk.pos_tag([token])[0][1][0].lower() pos = pos_tag if pos_tag in ['n', 'v', 'a', 'r'] else 'n' return lemmatizer.lemmatize(token, pos=pos) def custom_tokenizer(text): base_tokens = TfidfVectorizer().build_tokenizer()(text) # 先做词形还原,再替换同义词 lemmatized_tokens = [lemmatize_token(token) for token in base_tokens] # 替换同义词:如果token在映射里就替换,否则保留原词 final_tokens = [synonym_map.get(token.lower(), token) for token in lemmatized_tokens] return final_tokens # 如果你想在文本层面替换(避免分词后漏处理),可以用preprocessor def custom_preprocessor(text): text = text.lower() for syn, target in synonym_map.items(): # 用正则匹配整个单词,避免替换到单词的一部分(比如把"household"误改) text = re.sub(r'\b' + re.escape(syn) + r'\b', target, text) return text # 实例化时同时传入preprocessor和tokenizer tfidf = TfidfVectorizer(preprocessor=custom_preprocessor, tokenizer=custom_tokenizer) # 测试效果 corpus = ["I live in a house", "My home is cozy", "He rents an apartment"] X = tfidf.fit_transform(corpus) print(tfidf.get_feature_names_out()) # 输出只会有house,因为home、apartment都被统一成house了
额外注意事项
- 停用词处理:如果用了自定义分词器,记得手动过滤停用词。可以通过
TfidfVectorizer(stop_words='english').get_stop_words()获取默认停用词列表,在分词时过滤。 - 大小写问题:建议在预处理阶段把文本转成小写,避免
House和house被当成两个不同的词。 - 正则替换的准确性:用
\b匹配单词边界,防止替换到单词的子串(比如把houses替换成house时,不会影响household)。
内容的提问来源于stack exchange,提问作者Federico Caccia
相关产品推荐
相关产品推荐

