使用 Dill 序列化 scikit-learn/statsmodels 模型有哪些陷阱?

What are the pitfalls of using Dill to serialise scikit-learn/statsmodels models?

我需要序列化 ​​scikit-learn/statsmodels 模型,以便所有依赖项(代码 + 数据)都打包在一个人工制品中,并且该人工制品可用于初始化模型并进行预测。使用 pickle module 不是一个选项,因为这只会处理数据依赖性(不会打包代码)。所以,我一直在用Dill进行实验。为了使我的问题更准确,以下是我构建模型并坚持使用的示例。

from sklearn import datasets
from sklearn import svm
from sklearn.preprocessing import Normalizer
import dill

digits = datasets.load_digits()
training_data_X = digits.data[:-5]
training_data_Y = digits.target[:-5]
test_data_X = digits.data[-5:]
test_data_Y = digits.target[-5:]

class Model:
    def __init__(self):
        self.normalizer = Normalizer()
        self.clf = svm.SVC(gamma=0.001, C=100.)
    def train(self, training_data_X, training_data_Y):
        normalised_training_data_X = normalizer.fit_transform(training_data_X)
        self.clf.fit(normalised_training_data_X, training_data_Y)
    def predict(self, test_data_X):
        return self.clf.predict(self.normalizer.fit_transform(test_data_X))  

model = Model()
model.train(training_data_X, training_data_Y)
print model.predict(test_data_X)
dill.dump(model, open("my_model.dill", 'w'))

与此相对应,这是我如何初始化持久模型(在新会话中)并进行预测。请注意,此代码未明确初始化或了解 class Model.

import dill
from sklearn import datasets

digits = datasets.load_digits()
training_data_X = digits.data[:-5]
training_data_Y = digits.target[:-5]
test_data_X = digits.data[-5:]
test_data_Y = digits.target[-5:]

with open("my_model.dill") as model_file:
    model = dill.load(model_file)

print model.predict(test_data_X)

有没有人用过莳萝是这样的?。这个想法是让数据科学家为他们实现的每个模型扩展 ModelWrapper class,然后围绕它构建持久化模型的基础设施,将模型部署为服务并管理模型的整个生命周期。

class ModelWrapper(object):
    __metaclass__ = abc.ABCMeta
    def __init__(self, model):
        self.model = model
    @abc.abstractmethod
    def predict(self, input):
        return
    def dumps(self):
        return dill.dumps(self)
    def loads(self, model_string):
        self.model = dill.loads(model_string)

除了安全隐患(任意代码执行)和像 scikit-learn 这样的模块必须安装在为模型服务的机器上的要求之外,这种方法还有其他缺陷吗?任何评论或建议都会很有帮助。

我认为 YHat and Dato 采取了类似的方法,但为了类似的目的推出了自己的 Dill 实现。

好的开始,在你的示例代码中 pickle 可以正常工作,我一直使用 pickle 来打包模型并在以后使用它,除非你想直接将模型发送到另一个服务器或把interpreter state留着,因为那是Dill擅长而pickle做不到的。它还取决于您的代码、您使用的类型等,pickle 可能会失败,Dill 更稳定。

Dill 主要基于 pickle,因此它们非常相似,有些事情您应该考虑/研究:

  1. Dill

    的限制

    framegeneratortraceback标准类型无法打包。

  2. cloudpickle 也可能是解决您的问题的好主意,它在酸洗对象方面有更好的支持(比泡菜,并不比 Dill 更好)并且您可以轻松地酸洗代码还有。

一旦目标机器加载了正确的库,(也要小心不同的 python 版本,因为它们可能会导致代码出错),Dillcloudpickle,只要你不使用不受支持的标准类型。

希望这对您有所帮助。

我使用 picklescikit-learn 打包高斯过程 (GP)。

主要原因是因为使用 pickle 构建 GP 需要很长时间来构建和加载速度更快。因此,在我的代码初始化中,我检查模型的数据文件是否已更新并在必要时重新生成模型,否则只需从 pickle!

反序列化它

我会按照各自的顺序使用pickledillcloudpickle

请注意 pickle 包括 protocol 关键字参数,一些值可以显着加快和减少内存使用! 最后,如有必要,我使用来自 CPython STL 的压缩包 pickle 代码。

我是 dill 作者。 dill 的构建完全是为了做你正在做的事情......(在 class 实例中保持数值适合以进行统计)然后可以将这些对象分发到不同的资源并且 运行 以令人尴尬的并行方式时尚。所以,答案是肯定的——我有和你一样的 运行 代码,使用 mystic and/or sklearn.

请注意,sklearn 的许多作者使用 cloudpickle 来启用 sklearn 对象的并行计算,而不是 dilldill 可以 pickle 比 cloudpickle 更多类型的对象,但是 cloudpickle 在 pickle 将引用全局字典作为闭包的一部分的对象方面稍微好一点(在撰写本文时)- - 默认情况下,dill 通过引用执行此操作,而 cloudpickle 物理存储依赖项。但是,dill 有一个 "recurse" 模式,其作用类似于 cloudpickle,因此使用此模式时的差异很小。 (要启用 "recurse" 模式,请执行 dill.settings['recurse'] = True,或使用 recurse=True 作为 dill.dump 中的标志)。另一个细微差别是 cloudpickle 包含对 scikits.timeseriesPIL.Image 等内容的特殊支持,而 dill 则没有。

从好的方面来说,dill 不会通过引用 pickle classes,因此通过 pickle class 实例,它会序列化 class 对象本身 --这是一个很大的优势,因为它序列化了 class 派生的 classes 的实例 classifiers,models, and etc from sklearn in their exact state at the time of pickling ... 所以如果你对 class 对象的修改,实例仍然正确地取消选择。 dillcloudpickle 相比还有其他优势,除了更广泛的对象(通常是更小的 pickle)之外——但是,我不会在这里列出它们。你问的是陷阱,差异不是陷阱

主要陷阱:

  • 您应该将 class 所指的任何东西安装在 远程机器,以防万一 dill(或 cloudpickle)通过 参考.

  • 您应该尝试使您的 classes 和 class 方法成为 尽可能自包含(例如,不要引用定义在 来自 classes 的全局范围)。

  • sklearn 对象可以很大,所以把很多对象保存到一个 泡菜并不总是一个好主意......你可能想使用 klepto 它有一个用于缓存和归档的 dict 接口,并使您能够配置归档接口以单独存储每个键值对(例如每个文件一个条目)。