spi*_*ral 5 python persistence pipeline scikit-learn joblib
我正在尝试腌制一个sklearn机器学习模型,并将其加载到另一个项目中。该模型包装在具有编码,缩放等功能的管道中。当我想在管道中使用自写转换器执行更高级的任务时,问题就开始了。
假设我有2个专案:
如果在“ 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]
Run Code Online (Sandbox Code Playgroud)
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')
Run Code Online (Sandbox Code Playgroud)
test_project
main.py
from sklearn.externals import joblib
pipeline = joblib.load('path.x')
Run Code Online (Sandbox Code Playgroud)
预期结果是使用可能使用的转换方法正确加载了管道。
加载文件时,实际结果是异常。
Pep*_*epe 17
我找到了一个非常简单的解决方案。假设您使用 Jupyter 笔记本进行训练:
.py定义自定义转换器的文件并将其导入 Jupyter Notebook。这是文件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]
Run Code Online (Sandbox Code Playgroud)
.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')
Run Code Online (Sandbox Code Playgroud)
.pkl文件时,您必须导入该.py文件才能使其工作:import joblib
from utils import custom_transformer # decided to save it in a utils directory
pipeline = joblib.load('pipeline.pkl')
Run Code Online (Sandbox Code Playgroud)
我已经创建了一个解决方案。我不认为它是我问题的完整答案,但它仍然让我摆脱了我的问题。
解决方法起作用的条件:
一、管道只需要2种变压器:
或这些的任意组合,例如带有字符串和数字的字典列表。一般来说,重要的是属性是 json 可序列化的。
二. 管道步骤的名称必须是唯一的(即使存在管道嵌套)
简而言之,模型将存储为包含 joblib 转储文件的目录、自定义转换器的 json 文件以及包含有关模型的其他信息的 json 文件。
我创建了一个函数,它执行管道的步骤并检查变压器的 __module__ 属性。
如果它在其中找到 sklearn,那么它会以步骤(步骤元组的第一个元素)中指定的名称运行 joblib.dump 函数到某个选定的模型目录。
否则(__module__ 中没有 sklearn),它会将转换器的 __dict__ 添加到 result_dict 中,键下的键等于步骤中指定的名称。最后,我将 result_dict json.dump 到名为 result_dict.json 的模型目录中。
如果需要进入某个变压器,因为例如管道内有一个管道,您可能可以通过在函数的开头添加一些规则来递归地运行此函数,但始终拥有唯一的步骤/变压器变得很重要甚至在主管道和子管道之间也可以命名。
如果创建模型管道还需要其他信息,请将它们保存在 model_info.json 中。
然后,如果您想加载模型以供使用:您需要在目标项目中创建(不拟合)相同的管道。如果管道创建有点动态,并且您需要源项目中的信息,则从 model_info.json 加载它。
您可以复制用于序列化的函数并且:
运行此修改后的函数后,先前未拟合的管道应具有加载拟合效果的所有变压器属性,并且管道作为一个整体已准备好进行预测。
我不喜欢这个解决方案的主要事情是它需要目标项目内的管道代码,并且需要自定义转换器的所有 attrs 都是 json 可序列化的,但我把它留在这里供其他偶然发现类似问题的人使用,也许有人会来做一些更好的事情。
| 归档时间: |
|
| 查看次数: |
198 次 |
| 最近记录: |