10. 模型持久性#
持续性方法 |
优点 |
风险/缺点 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
训练scikit-learning模型后,希望有一种方法可以持久化该模型以供将来使用,而无需重新训练。根据您的用例,有几种不同的方法可以持久化scikit-learn模型,在这里我们帮助您决定哪一种最适合您。为了做出决定,您需要回答以下问题:
持久化后您是否需要Python对象,或者您只需要持久化来服务于模型并从中获取预测?
如果您只需要提供模型,而不需要进一步研究Python对象本身,那么 ONNX 可能是最适合您的。请注意,并非所有型号都受ONNX支持。
如果ONNX不适合您的用例,下一个问题是:
您是否绝对信任模型的来源,或者关于持久化模型的来源是否存在任何安全问题?
如果您有安全问题,那么您应该考虑使用 skops.io 这将返回Python对象,但与此不同 pickle
基于持久化解决方案,加载持久化模型不会自动允许执行任意代码。请注意,这需要手动调查持久化文件,其中 skops.io
允许你做。
其他解决方案假设您绝对信任要加载的文件的源,因为它们在加载持久化文件时都容易受到任意代码执行的影响,因为它们都在背后使用pickle协议。
您是否关心加载模型并在磁盘上的内存映射对象有益的进程之间共享模型的性能?
如果是,那么您可以考虑使用 joblib .如果这对您来说不是一个主要问题,那么您可以使用内置 pickle
module.
如果是,那么您可以使用 cloudpickle 它可以序列化某些无法序列化的对象 pickle
或 joblib
.
10.1. 工作流程概述#
在典型的工作流程中,第一步是使用scikit-learn和scikit-learn兼容库训练模型。请注意,对scikit-learn和第三方估计器的支持因不同的持久性方法而有所不同。
10.1.1. 训练并坚持模型#
创建适当的模型取决于您的用例。例如,我们在这里训练一个 sklearn.ensemble.HistGradientBoostingClassifier
在虹膜数据集上::
>>> from sklearn import ensemble
>>> from sklearn import datasets
>>> clf = ensemble.HistGradientBoostingClassifier()
>>> X, y = datasets.load_iris(return_X_y=True)
>>> clf.fit(X, y)
HistGradientBoostingClassifier()
训练好模型后,您可以使用所需的方法将其持久化,然后可以在单独的环境中加载模型,并从给定的输入数据中获取预测。这里有两条主要的路径,这取决于你如何坚持和计划服务模型:
ONNX :你需要一个
ONNX
运行时和安装有适当依赖项的环境来加载模型并使用运行时来获取预测。该环境可以是最小的,甚至不一定需要安装Python来加载模型和计算预测。还注意到onnxruntime
通常需要比Python少得多的RAM来计算小模型的预测。skops.io
,pickle
,joblib
, cloudpickle :您需要一个安装了适当依赖项的Python环境来加载模型并从中获取预测。此环境应该具有相同的 packages 和相同的 versions 作为模型训练的环境。请注意,这些方法都不支持加载用不同版本的scikit-learn训练的模型,可能还可以使用不同版本的其他依赖项(例如numpy
和scipy
.另一个问题是在不同的硬件上运行持久化模型,在大多数情况下,您应该能够在不同的硬件上加载持久化模型。
10.2. ONNX#
ONNX
, or Open Neural Network Exchange 格式最适合需要持久化模型,然后使用持久化的工件来获取预测而无需加载Python对象本身的用例。在服务环境需要精简和最小化的情况下,它也很有用,因为 ONNX
运行时不需要 python
.
ONNX
is a binary serialization of the model. It has been developed to improve the usability of the interoperable representation of data models. It aims to facilitate the conversion of the data models between different machine learning frameworks, and to improve their portability on different computing architectures. More details are available from the ONNX tutorial .将scikit-learn模型转换为 ONNX
sklearn-onnx 开发了然而,并不是所有的scikit-learn模型都被支持,它仅限于核心scikit-learn,不支持大多数第三方估计器。人们可以为第三方或自定义估计器编写自定义转换器,但是这样做的文档很少,并且这样做可能具有挑战性。
使用ONNX#
将模型转换为 ONNX
format, you need to give the converter some information about the input as well, about which you can read more here
from skl2onnx import to_onnx
onx = to_onnx(clf, X[:1].astype(numpy.float32), target_opset=12)
with open("filename.onnx", "wb") as f:
f.write(onx.SerializeToString())
您可以在Python中加载模型并使用 ONNX
获取预测的运行时::
from onnxruntime import InferenceSession
with open("filename.onnx", "rb") as f:
onx = f.read()
sess = InferenceSession(onx, providers=["CPUExecutionProvider"])
pred_ort = sess.run(None, {"X": X_test.astype(numpy.float32)})[0]
10.3. skops.io
#
skops.io
避免使用 pickle
并且仅加载具有默认或用户信任的函数类型和引用的文件。因此,它提供了比 pickle
, joblib
,而且 cloudpickle .
使用skops#
API与 pickle
,您可以按照 documentation 使用 skops.io.dump
和 skops.io.dumps
import skops.io as sio
obj = sio.dump(clf, "filename.skops")
你可以用 skops.io.load
和 skops.io.loads
.但是,您需要指定您信任的类型。您可以使用以下方法获取转储对象/文件中现有的未知类型 skops.io.get_untrusted_types
,检查其内容后,将其传递给加载函数::
unknown_types = sio.get_untrusted_types(file="filename.skops")
# investigate the contents of unknown_types, and only load if you trust
# everything you see.
clf = sio.load("filename.skops", trusted=unknown_types)
请在上报告与此格式相关的问题和功能请求 skops issue tracker .
10.4. pickle
, joblib
,而且 cloudpickle
#
这三个模块/包使用 pickle
引擎盖下的协议,但略有变化:
pickle
是Python标准库中的一个模块。它可以序列化和反序列化任何Python对象,包括自定义Python类和对象。joblib
效率高于pickle
当使用大型机器学习模型或大型numpy数组时。cloudpickle 可以序列化某些不能被序列化的对象
pickle
或joblib
,例如用户定义的函数和拉姆达函数。例如,当使用FunctionTransformer
并使用自定义函数来转换数据。
使用 pickle
, joblib
,或者 cloudpickle
#
根据您的用例,您可以选择这三种方法之一来持久化和加载scikit-learn模型,并且它们都遵循相同的API::
# Here you can replace pickle with joblib or cloudpickle
from pickle import dump
with open("filename.pkl", "wb") as f:
dump(clf, f, protocol=5)
使用 protocol=5
建议减少内存使用,并使其更快地存储和加载任何存储为模型中的拟合属性的大型NumPy数组。你也可以通过 protocol=pickle.HIGHEST_PROTOCOL
这相当于 protocol=5
在Python 3.8及更高版本中(在撰写本文时)。
稍后需要时,您可以从持久化文件中加载相同的对象::
# Here you can replace pickle with joblib or cloudpickle
from pickle import load
with open("filename.pkl", "rb") as f:
clf = load(f)
10.5. 安全性和可维护性限制#
pickle
(和 joblib
和 cloudpickle
扩展),设计上有许多记录在案的安全漏洞,并且只有在制品(即pickle文件)来自可信且经过验证的来源时才应使用。您永远不应该从不受信任的来源加载pickle文件,类似于您永远不应该从不受信任的来源执行代码。
另请注意,任意计算可以使用 ONNX
格式,因此建议使用 ONNX
在沙箱环境中以防止计算和内存漏洞利用。
另请注意,没有受支持的方法来加载用不同版本的scikit-learn训练的模型。同时使用 skops.io
, joblib
, pickle
,或者 cloudpickle ,使用scikit-learn的一个版本保存的模型可能会在其他版本中加载,但是,这完全不支持并且不建议。还应该记住,对此类数据执行的操作可能会产生不同且意想不到的结果,甚至导致Python进程崩溃。
为了用未来版本的scikit-learn重建类似的模型,应沿着picked模型保存额外的元数据:
训练数据,例如对不可变快照的引用
用于生成模型的Python源代码
scikit-learn的版本及其依赖项
在训练数据上获得的交叉验证分数
这应该可以检查交叉验证分数是否在与以前相同的范围内。
除了一些例外之外,假设使用相同版本的依赖项和Python,持久化模型应该可以跨操作系统和硬件架构移植。如果您遇到不可移植的估计器,请在GitHub上打开问题。持久化模型通常使用Docker等容器部署在生产中,以冻结环境和依赖项。
如果您想了解更多有关这些问题的信息,请参阅这些讲座:
Adrin Jalali: Let's exploit pickle, and skops to the rescue! | PyData Amsterdam 2023 .
Alex Gaynor: Pickles are for Delis, not Software - PyCon 2014 .
10.5.1. 在生产中复制培训环境#
如果所使用的依赖项的版本可能因训练而异,则在使用训练后的模型时可能会导致意外行为和错误。为了防止出现此类情况,建议在培训和生产环境中使用相同的依赖项和版本。可以借助包管理工具(例如 pip
, mamba
, conda
, poetry
, conda-lock
, pixi
等。
在更新的软件环境中加载用旧版本的scikit-learn库及其依赖项训练的模型并不总是可能的。相反,您可能需要使用所有库的新版本重新训练模型。因此,在训练模型时,记录训练食谱(例如Python脚本)和训练集信息以及有关所有依赖项的元数据非常重要,以便能够为更新后的软件自动重建相同的训练环境。
InconsistentVersionWarning#
当估计器加载的scikit-learn版本与估计器的pickled版本不一致时, InconsistentVersionWarning
被提出。可以捕捉此警告以获取估算器腌制的原始版本::
from sklearn.exceptions import InconsistentVersionWarning
warnings.simplefilter("error", InconsistentVersionWarning)
try:
with open("model_from_previous_version.pickle", "rb") as f:
est = pickle.load(f)
except InconsistentVersionWarning as w:
print(w.original_sklearn_version)
10.5.2. 服务模型文物#
训练scikit-learn模型后的最后一步是为模型服务。一旦训练的模型成功加载,它就可以用于管理不同的预测请求。根据规范,这可能涉及使用容器化或其他模型部署策略将模型部署为Web服务。
10.6. 总结要点#
根据模型持久性的不同方法,每种方法的要点可以总结如下:
ONNX
:它为持久任何机器学习或深度学习模型(scikit-learn除外)提供了统一的格式,并且对于模型推断(预测)很有用。然而,它可能会导致与不同框架的兼容性问题。skops.io
:经过训练的scikit-learning模型可以轻松共享并投入生产skops.io
.与基于pickle
因为除非用户明确要求,否则它不会加载任意代码。此类代码需要打包并可在目标Python环境中导入。joblib
:高效的内存映射技术使在多个Python进程中使用相同的持久化模型时速度更快mmap_mode="r"
.它还提供了简单的快捷方式来压缩和解压缩持久化对象,而无需额外的代码。然而,当从不受信任的来源加载模型时,它可能会像任何其他基于Pickle的持久性机制一样触发恶意代码的执行。pickle
:它是Python原生的,大多数Python对象都可以使用序列化和反序列化pickle
,包括自定义Python类和函数,只要它们是在可以导入目标环境的包中定义的。而pickle
可用于轻松保存和加载scikit-learn模型,在从不受信任的来源加载模型时可能会触发恶意代码的执行。pickle
如果模型被持久化,那么在内存方面也可以非常高效protocol=5
但它不支持内存映射。cloudpickle: 它的装载效率与
pickle
和joblib
(没有内存映射),但提供了额外的灵活性来序列化自定义Python代码(例如Lambda表达式以及交互式定义的函数和类)。使用自定义Python组件(例如sklearn.preprocessing.FunctionTransformer
它包装在训练脚本本身中或更一般地在任何可导入的Python包之外定义的函数。注意 cloudpickle 不提供向前兼容性保证,您可能需要相同版本的 cloudpickle 加载持久化模型以及用于定义模型的所有库的相同版本。与其他基于Pickle的持久性机制一样,它可能会在从不受信任的来源加载模型时触发恶意代码的执行。