将二值化器与sklearn模型一起保存

7

我正在构建一个包含2个组件的服务。在组件1中,我使用sklearn创建一个Pipeline来训练机器学习模型。使用joblib.dump(实际上是numpy_pickle.dump)对该模型进行序列化。组件2在云端运行,加载(1)训练的模型,并使用它来标记输入的文本。

在训练期间(组件1),我遇到了一个问题,我需要首先对我的数据进行二值化处理,因为它是文本数据,这意味着模型是基于二值化的输入进行训练的,然后使用二值化器创建的映射进行预测。当(2)基于模型进行预测时,我需要得到这个映射,以便输出实际的文本标签。

我尝试将二值化器添加到管道中,如下所示,认为模型会自己拥有这个映射:

p = Pipeline([
('binarizer', MultiLabelBinarizer()),
('vect', CountVectorizer(min_df=min_df, ngram_range=ngram_range)), 
('tfidf', TfidfTransformer()), 
('clf', OneVsRestClassifier(clf))
])

但是我遇到了以下错误:
model = p.fit(training_features, training_tags)
*** TypeError: fit_transform() takes 2 positional arguments but 3 were given

我的目标是确保二值化器和模型绑定在一起,以便使用者知道如何解码模型的输出。
有哪些现有的范例可以实现这个目标?我应该将二值化器与模型一起序列化到创建的其他对象中吗?还有其他方法可以将二值化器传递给 Pipeline,而不必这样做,并且如果我这样做了,是否能够从模型中获取映射关系?
1个回答

9
你的直觉是正确的,将MultiLabelBinarizer添加到流水线中是解决这个问题的正确方法。这应该有效,但是MultiLabelBinarizer.fit_transform不接受现在成为sklearn评估器标准的fit_transform(self, X, y=None)方法签名。相反,它有一个独特的fit_transform(self, y)签名,我以前从未注意过。由于这种差异,当你在流水线上调用fit时,它会尝试将training_tags作为第三个位置参数传递给一个只有两个位置参数的函数,这不起作用。
解决这个问题比较棘手。我能想到的最干净的方法是创建自己的MultiLabelBinarizer,覆盖fit_transform并忽略它的第三个参数。尝试以下代码:
class MyMLB(MultiLabelBinarizer):
    def fit_transform(self, X, y=None):
        return super(MultiLabelBinarizer, self).fit_transform(X)

尝试将这个方法添加到你的管道中,取代MultiLabelBinarizer,看看会发生什么。如果你可以适应这个管道,你最后可能会遇到的问题是,你的新MyMLB类必须可以在任何系统上被导入,以解压缩训练好的、已打包的管道对象。最简单的方法是将MyMLB放入它自己的模块中,并在将要解压缩和执行模型的远程机器上放置一个拷贝。这应该可以解决问题。
我误解了MultiLabelBinarizer的工作方式。它是输出的转换器,而不是输入的转换器。这不仅解释了该类的另一种fit_transform()方法签名的替代方式,而且也使其根本无法与仅限于转换输入和预测输出的单一分类管道的概念兼容。然而,一切并没有结束!
根据你的问题,你已经熟悉了将你的模型序列化到磁盘上,形式为某种形式的.pkl文件。你应该能够序列化一个已训练的MultiLabelBinarizer,并解包它并用它来解包你的管道的输出。我知道你正在使用joblib,但我会把这个样本代码写成你正在使用pickle。我相信这个想法仍然适用。
X = <training_data>
y = <training_labels>

# Perform multi-label classification on class labels.
mlb = MultiLabelBinarizer()
multilabel_y = mlb.fit_transform(y)

p = Pipeline([
('vect', CountVectorizer(min_df=min_df, ngram_range=ngram_range)), 
('tfidf', TfidfTransformer()), 
('clf', OneVsRestClassifier(clf))
])

# Use multilabel classes to fit the pipeline.
p.fit(X, multilabel_y)

# Serialize both the pipeline and binarizer to disk.
with open('my_sklearn_objects.pkl', 'wb') as f:
    pickle.dump((mlb, p), f)

然后,在将.pkl文件发送到远程计算机后...
# Hydrate the serialized objects.
with open('my_sklearn_objects.pkl', 'rb') as f:
    mlb, p = pickle.load(f)

X = <input data> # Get your input data from somewhere.

# Predict the classes using the pipeline
mlb_predictions = p.predict(X)

# Turn those classes into labels using the binarizer.
classes = mlb.inverse_transform(mlb_predictions)

# Do something with predicted classes.
<...>

这是做这件事的典范吗?据我所知,是的。不仅如此,如果你想把它们放在一起(我认为这是个好主意),你可以像上面的例子一样将它们序列化为 tuple ,让它们保持在一个文件中。不需要序列化自定义对象或类似的东西。

通过 pickle et al. 进行模型序列化是 sklearn 所推荐的保存评估器的方式,可以在运行之间移动它们以及在计算机之间传输。我以前成功地多次使用过这个过程,包括在生产系统中。


谢谢您的建议!我认为这很接近,但还没有完全解决。我使用了您建议的类定义作为管道的第一步,但是我得到了*** AttributeError: 'numpy.ndarray' object has no attribute 'lower'的错误。我认为最后一行应该以fit_transform(y)结束,因为我想转换标签,而不是输入文本,但即使我修复了它,由于_pre_transform的编写方式(标签最终覆盖了Xt = transform.fit_transform(Xt, y, **fit_params_steps[name])中的训练数据),我仍然会遇到相同的错误。也许我还是漏掉了什么? - LateCoder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接