【问题标题】:How to properly pickle sklearn pipeline when using custom transformer使用自定义变压器时如何正确腌制sklearn管道
【发布时间】:2020-01-13 05:51:32
【问题描述】:

我正在尝试腌制一个 sklearn 机器学习模型,并将其加载到另一个项目中。该模型包含在管道中,该管道执行特征编码、缩放等。当我想在管道中使用自写转换器来执行更高级的任务时,问题就开始了。

假设我有 2 个项目:

  • train_project:它在 src.feature_extraction.transformers.py 中有自定义转换器
  • use_project:src中有其他东西,或者根本没有src目录

如果在“train_project”中我使用 joblib.dump() 保存管道,然后在“use_project”中使用 joblib.load() 加载它,它将找不到诸如“src.feature_extraction.transformers”之类的内容并抛出例外:

ModuleNotFoundError: 没有名为“src.feature_extraction”的模块

我还应该补充一点,我从一开始的意图是简化模型的使用,因此程序师可以像加载任何其他模型一样加载模型,传递非常简单、人类可读的特征,以及对实际特征的所有“魔法”预处理模型(例如梯度提升)正在内部发生。

我想在两个项目的根目录中创建 /dependencies/xxx_model/ 目录,并在其中存储所有需要的类和函数(将代码从“train_project”复制到“use_project”),因此项目的结构是相同的,并且转换器可以被加载。我发现这个解决方案非常不优雅,因为它会强制使用该模型的任何项目的结构。

我想只是在“use_project”中重新创建管道和所有变压器,并以某种方式从“train_project”加载变压器的拟合值。

最好的解决方案是,如果转储文件包含所有需要的信息并且不需要依赖项,我真的很震惊 sklearn.Pipelines 似乎没有这种可能性 - 如果我无法加载安装管道,那么安装管道有什么意义后来反对?是的,如果我只使用 sklearn 类而不创建自定义类,它会起作用,但非自定义类没有所有需要的功能。

示例代码:

train_project

src.feature_extraction.transformers.py

from sklearn.pipeline import TransformerMixin
class FilterOutBigValuesTransformer(TransformerMixin):
    def __init__(self):
        pass

    def fit(self, X, y=None):
        self.biggest_value = X.c1.max()
        return self

    def transform(self, X):
        return X.loc[X.c1 <= self.biggest_value]

train_project

main.py

from sklearn.externals import joblib
from sklearn.preprocessing import MinMaxScaler
from src.feature_extraction.transformers import FilterOutBigValuesTransformer

pipeline = Pipeline([
    ('filter', FilterOutBigValuesTransformer()),
    ('encode', MinMaxScaler()),
])
X=load_some_pandas_dataframe()
pipeline.fit(X)
joblib.dump(pipeline, 'path.x')

测试项目

main.py

from sklearn.externals import joblib

pipeline = joblib.load('path.x')

预期的结果是管道正确加载了可以使用的转换方法。

加载文件时实际结果是异常。

【问题讨论】:

  • 我也有同样的问题,我会分享我到目前为止所尝试的。交换 joblib,pickle 。重新导入我的自定义 featureUnion 子类。如果您想出出路,请在此处发布。

标签: python scikit-learn persistence pipeline joblib


【解决方案1】:

根据我的研究,似乎最好的解决方案是创建一个 Python 包,其中包含经过训练的管道和所有文件。

然后就可以pip安装到你要使用的项目中,并用from &lt;package name&gt; import &lt;pipeline name&gt;导入管道。

【讨论】:

    【解决方案2】:

    我找到了一个非常简单的解决方案。假设您使用 Jupyter 笔记本进行培训:

    1. 创建一个定义自定义转换器的.py 文件,并将其导入 Jupyter 笔记本。

    这是文件custom_transformer.py

    from sklearn.pipeline import TransformerMixin
    
    class FilterOutBigValuesTransformer(TransformerMixin):
        def __init__(self):
            pass
    
        def fit(self, X, y=None):
            self.biggest_value = X.c1.max()
            return self
    
        def transform(self, X):
            return X.loc[X.c1 <= self.biggest_value]
    
    1. .py 文件导入此类并使用joblib 保存它来训练您的模型。
    import joblib
    from custom_transformer import FilterOutBigValuesTransformer
    from sklearn.externals import joblib
    from sklearn.preprocessing import MinMaxScaler
    
    pipeline = Pipeline([
        ('filter', FilterOutBigValuesTransformer()),
        ('encode', MinMaxScaler()),
    ])
    
    X=load_some_pandas_dataframe()
    pipeline.fit(X)
    
    joblib.dump(pipeline, 'pipeline.pkl')
    
    1. 在不同的 Python 脚本中加载 .pkl 文件时,您必须导入 .py 文件才能使其工作:
    import joblib
    from utils import custom_transformer # decided to save it in a utils directory
    
    pipeline = joblib.load('pipeline.pkl')
    
    

    【讨论】:

      【解决方案3】:

      你试过用云泡菜吗? https://github.com/cloudpipe/cloudpickle

      【讨论】:

        【解决方案4】:

        我已经创建了一个变通解决方案。我不认为它是我问题的完整答案,但它让我从我的问题中继续前进。

        变通办法起作用的条件:

        我。 Pipeline 只需要 2 种转换器:

        1. sklearn 转换器
        2. 自定义转换器,但只有类型的属性:
          • 号码
          • 字符串
          • 列表
          • 字典

        或它们的任何组合,例如带有字符串和数字的字典列表。通常重要的是属性是 json 可序列化的。

        二。管道步骤的名称必须是唯一的(即使存在管道嵌套)


        简而言之,模型将存储为包含作业库转储文件的目录、用于自定义转换器的 json 文件以及包含有关模型的其他信息的 json 文件。

        我创建了一个函数,它通过管道的步骤检查转换器的 __module__ 属性。

        如果它在其中找到 sklearn,那么它会以步骤(步骤元组的第一个元素)中指定的名称运行 joblib.dump 函数到某个选定的模型目录。

        否则(在 __module__ 中没有 sklearn)它会将转换器的 __dict__ 添加到 result_dict 中,其键与步骤中指定的名称相同。最后,我将 result_dict json.dump 到名为 result_dict.json 的模型目录中。

        如果需要进入一些变压器,例如管道中有一个管道,您可以通过在函数的开头添加一些规则来递归地运行此函数,但是即使在主管道和子管道之间也始终具有唯一的步骤/转换器名称变得很重要。

        如果创建模型管道需要其他信息,请将它们保存在 model_info.json 中。


        那么如果你想加载模型以供使用: 您需要在目标项目中创建(不安装)相同的管道。如果管道创建有点动态,并且您需要来自源项目的信息,则从 model_info.json 加载它。

        您可以复制用于序列化的函数,并且:

        • 将所有 joblib.dump 替换为 joblib.load 语句,将已加载对象的 __dict__ 分配给已在管道中的对象的 __dict__
        • 将您添加 __dict__ 到 result_dict 的所有位置替换为从 result_dict 到对象 __dict__ 的适当值分配(记得事先从文件中加载 result_dict)

        在运行这个修改后的函数后,之前未拟合的管道应该具有加载拟合效果的所有转换器属性,并且管道作为一个整体准备好进行预测。

        我不喜欢这个解决方案的主要事情是它需要在目标项目中使用管道代码,并且需要自定义转换器的所有属性都是 json 可序列化的,但我把它留给其他遇到类似问题的人,也许有人想出了更好的东西。

        【讨论】:

          猜你喜欢
          • 2018-01-02
          • 2020-09-01
          • 2016-11-08
          • 2021-03-18
          • 2019-07-09
          • 2016-07-15
          • 2018-02-15
          • 2020-06-17
          相关资源
          最近更新 更多