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

如何序列化含自定义分词器的scikit-learn TfidfVectorizer?

解决Pickle序列化带自定义Tokenizer的TfidfVectorizer问题

你遇到的问题本质是pickle对函数的序列化机制导致的:pickle不会保存函数的代码本身,只保存函数的名称和它所在的模块路径。当你加载时,Python需要在当前运行环境中找到完全同名、同模块的函数定义,否则就会抛出AttributeError。

下面给你几个可行的解决方案,按推荐程度排序:

1. 使用Cloudpickle替代Pickle(最省心)

Cloudpickle是pickle的增强版,能直接序列化自定义函数、lambda表达式这类pickle搞不定的对象,不需要额外调整代码结构。

步骤:

  • 先安装cloudpickle:
pip install cloudpickle
  • 保存TfidfVectorizer时替换pickle为cloudpickle:
import cloudpickle
import os

dest = os.path.join('classifier','pkl_object')
# 保存整个tfidf对象(包含tokenizer)
with open(os.path.join(dest,'vect.pkl'),'wb') as f:
    cloudpickle.dump(tfidf, f)
  • 加载时同样用cloudpickle:
import cloudpickle
import os

with open(os.path.join('classifier','pkl_object','vect.pkl'),'rb') as file:
    vect = cloudpickle.load(file)

这样不需要担心函数定义的位置问题,cloudpickle会把函数的代码一起序列化。

2. 将自定义Tokenizer放到独立模块中(最规范)

把custom_tokenizer函数放到一个单独的Python模块里,训练和加载时都从这个模块导入,确保pickle保存的函数引用在加载环境中存在。

步骤:

  • 创建一个名为tokenizers.py的文件,把tokenizer和依赖的对象都放进去:
# tokenizers.py
# 确保这里导入vect_tokenizer和lemmatizer,比如:
# from nltk.tokenize import word_tokenize as vect_tokenizer
# from nltk.stem import WordNetLemmatizer
# lemmatizer = WordNetLemmatizer()

def custom_tokenizer(doc):
    tokens = vect_tokenizer(doc)
    return [lemmatizer.lemmatize(token) for token in tokens]
  • 训练时从模块导入函数:
from tokenizers import custom_tokenizer
tfidf = TfidfVectorizer(tokenizer=custom_tokenizer, stop_words="english")

# 训练完成后保存tfidf
import pickle
import os
dest = os.path.join('classifier','pkl_object')
with open(os.path.join(dest,'vect.pkl'),'wb') as f:
    pickle.dump(tfidf, f, protocol=4)
  • 加载时先导入模块中的函数,再加载pickle文件:
from tokenizers import custom_tokenizer  # 必须先导入,让Python能找到这个函数
import pickle
import os

with open(os.path.join('classifier','pkl_object','vect.pkl'),'rb') as file:
    vect = pickle.load(file)

3. 用类实现Tokenizer(更灵活)

把tokenizer的逻辑包装成一个类,实现__call__方法,这样pickle可以序列化类实例(会保存实例的状态),不需要依赖外部函数定义。

步骤:

  • 定义Tokenizer类:
class CustomTokenizer:
    def __init__(self, vect_tokenizer, lemmatizer):
        self.vect_tokenizer = vect_tokenizer
        self.lemmatizer = lemmatizer
    
    def __call__(self, doc):
        tokens = self.vect_tokenizer(doc)
        return [self.lemmatizer.lemmatize(token) for token in tokens]
  • 训练时创建类实例并传入TfidfVectorizer:
# 假设你已经有vect_tokenizer和lemmatizer的实例
tokenizer = CustomTokenizer(vect_tokenizer, lemmatizer)
tfidf = TfidfVectorizer(tokenizer=tokenizer, stop_words="english")

# 训练后保存tfidf
import pickle
import os
dest = os.path.join('classifier','pkl_object')
with open(os.path.join(dest,'vect.pkl'),'wb') as f:
    pickle.dump(tfidf, f, protocol=4)
  • 加载时直接用pickle加载即可,不需要额外导入:
import pickle
import os

with open(os.path.join('classifier','pkl_object','vect.pkl'),'rb') as file:
    vect = pickle.load(file)

补充:你之前代码的小问题

你保存Vectorizer时,先dump了custom_tokenizer又dump了best_vector,但加载时只做了一次pickle.load(),应该对应两次load才能拿到两个对象:

# 错误的加载方式
with open(..., 'rb') as file:
    vect = pickle.load(file)  # 只拿到了custom_tokenizer,不是tfidf对象

# 正确的加载方式(如果要保留你原来的保存逻辑)
with open(..., 'rb') as file:
    loaded_tokenizer = pickle.load(file)
    loaded_vect = pickle.load(file)

不过更推荐直接保存整个tfidf对象,而不是分开保存tokenizer和vectorizer。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:44:35