注意:以下翻译的准确性尚未经过验证。这是使用 AIP ↗ 从原始英文文本进行的机器翻译。

增加对附加库的支持

功能已停用

下面的文档描述了 foundry_ml 库,该库已不再推荐在平台中使用。相反,请使用 palantir_models 库。您还可以通过一个示例了解如何将模型从 foundry_ml 迁移到 palantir_models 框架。

foundry_ml 库将于2025年10月31日移除,这与计划弃用Python 3.9相对应。

如果Foundry的标准模型函数对于特定应用案例不足,或者不支持特定类或库,您可以覆盖某些函数或注册阶段接口所需的函数。例如,您可以创建一个自定义的 Stagetransform 函数,然后将其序列化到Foundry中。

如果您希望支持第三方库,可以创建自己的 Stage 实现。

要了解一个示例,请参阅教程,了解如何利用预训练的spaCy模型进行命名实体识别。

要求

这些应写在一个共享的Transforms Python库中,可以添加到您的代码仓库或代码工作簿环境中。一旦导入,您的自定义 Stage 实现将自动集成到 foundry_ml 中。

自定义实现选项描述:

  • 当调用 model.transform() 时,模型应执行的操作。
  • 模型应如何在Foundry中序列化和保存。
  • 模型应如何反序列化以便在下游变换中使用。

由于 Stage 类需要在反序列化时可用,因此它们必须作为模块在您的Python环境中可用。

注册变换

要使用您的模型类,您需要确保该类具有已注册的变换函数和序列化格式。假设您想确保每当我们有一个包含 CustomModel 阶段的模型时,我们需要定义在调用 model.transform() 时应用的函数。

定义的 transform 函数必须对Spark或Pandas DataFrame进行操作。

Copied!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from foundry_ml.stage.flexible import stage_transform, register_stage_transform_for_class class CustomModel(object): def __init__(self, name): # 初始化一个自定义模型,接受一个名称参数 ... def custom_transform(self, df): # 自定义数据转换函数,接受一个数据框 df ... return df # 使用装饰器标注一个函数,该函数将封装模型和在不同阶段间传递的数据 @stage_transform() def _transform(model, df): # 调用模型定义的转换函数 return model.custom_transform(df) # 调用此函数将转换注册到 Foundry ML 阶段注册表中,force=True 用于覆盖任何已存在的注册转换 register_stage_transform_for_class(CustomModel, _transform, force=True)

注册序列化器和反序列化器

现在您已经为类注册了一个变换函数,您需要告诉Foundry如何序列化和反序列化模型代码。当使用自定义编写的模型阶段时,重要的是该阶段被编写在一个共享的Python库中并作为依赖项导入。

这是因为在反序列化时需要有Stage类可用。否则,如果您在代码工作簿中编写Stage类,然后尝试从不同的代码工作簿加载已保存的模型,模型将无法加载。

下面的示例假设CustomModel可以使用dill进行封存。下面的示例利用了两个Foundry辅助函数load_datasafe_write_data以安全地从文件系统中读取和写入模型。在spaCy示例中,我们展示了不同的实现。

Copied!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import dill from foundry_object.utils import safe_write_data, load_data from foundry_ml.stage.serialization import deserializer, serializer, register_serializer_for_class # 反序列化器装饰器 # 使用装饰器@deserializer为文件"custom_model.dill"注册反序列化函数,force=True表示强制覆盖已存在的反序列化器 @deserializer("custom_model.dill", force=True) def _deserializer(filesystem, path): # 加载被pickle序列化的文件 # 使用dill库的loads函数加载数据,encoding='latin1'用于指定编码 return dill.loads(load_data(filesystem, path, True), encoding='latin1') # 序列化器装饰器 # 使用装饰器@serializer为反序列化函数_register注册序列化函数 @serializer(_deserializer) def _serializer(filesystem, value): path = 'custom_model.dill' # 将对象序列化后存储到文件中,使用base64编码进行存储 safe_write_data(filesystem, path, dill.dumps(value), base64_encode=True) return path # 为CustomModel类注册序列化器 # register_serializer_for_class用于为指定的类注册序列化器,force=True表示强制覆盖已存在的序列化器 register_serializer_for_class(CustomModel, _serializer, force=True)

现在您已正确注册了CustomModel,可以像其他Stage一样使用,语法为model = Model(Stage(CustomModel(...))),并通过model.transform(dataframe)执行。

序列化模型中的函数

一些模型阶段(尤其是模拟包装器)包含用户编写的函数以及主要变换函数。为了使模型可执行,用户编写的函数也必须被序列化到模型状态中。在内部,Python使用pickle包来保存函数;pickle包需要额外的考虑来正确序列化函数。

加载阶段时,您可能会遇到类似ModuleNotFoundError: No module named '...'的错误。这可能发生在用户编写的函数通过引用而不是通过值进行序列化时。这意味着pickle没有直接序列化Python字节代码,而是序列化了函数的名称。

要强制通过值进行序列化,您可以将代码直接移动到您的变换函数中。

例如:

Copied!
1 2 3 4 5 6 7 8 9 class SimModel(SimulationWrapper): def run(self, data, parameters): ... # 在此处实现仿真模型的运行逻辑 @transform(...) def my_model(...): return Model(Stage( SimModel(parameters) # 创建SimModel实例并传入参数 ))

应该写为:

Copied!
1 2 3 4 5 6 7 8 9 10 11 12 @transform(...) def my_model(...): # 定义一个内部类 SimModel 继承自 SimulationWrapper class SimModel(SimulationWrapper): # 定义 SimModel 的 run 方法 def run(self, data, parameters): ... # 返回一个 Model 对象,其包含一个 Stage 对象,Stage 对象中包含 SimModel 的实例 return Model(Stage( SimModel(parameters) ))

此规则同样适用于自定义代码可能调用的任何其他函数。如果您的序列化函数或类具有许多按值序列化的依赖项,建议的路径是将这些依赖项提取到一个Python库中,并将其作为依赖项添加到模型中。

配置共享库

假设以上所有代码都放在model.py中,那么库将具有以下结构:

├── README.md                  # 项目说明文档
├── build.gradle               # Gradle构建脚本
├── ci.yml                     # 持续集成配置文件
├── conda_recipe               # Conda包配置文件夹
│   └── meta.yaml             # Conda包的元数据文件
├── gradle.properties          # Gradle属性配置文件
├── gradlew                    # 用于Unix系统的Gradle Wrapper脚本
├── gradleww                   # 可能是一个拼写错误或自定义脚本
├── settings.gradle            # Gradle设置文件
├── src                        # 源代码目录
│   ├──custom_plugin           # 自定义插件目录
│   │   ├── __init__.py       # Python包初始化文件
│   │   └── model.py          # 模型代码文件
│   ├── setup.cfg             # Python包的配置文件
│   └── setup.py              # Python包的安装脚本
└── templateConfig.json        # 模板配置文件

为了使Foundry能够发现插件,您必须首先修改__init__.py以将model.py的内容导入到包的顶层:

Copied!
1 from .model import * # 从当前包的model模块中导入所有内容

此外,您需要在setup.py中添加以下内容,以便模型插件注册表发现新插件:

Copied!
1 2 3 4 entry_points={'foundry_ml.plugins': ['plugin = custom_plugin']}, # 这是一个用于设置入口点的字典。入口点用于定义Python项目中的插件机制。 # 'foundry_ml.plugins' 是入口点组的名称,表示这个插件属于 'foundry_ml' 项目。 # 'plugin = custom_plugin' 是插件的定义,表示名为 'plugin' 的插件对应于 'custom_plugin' 模块或包。

一旦您提交、搭建,并标记一个发布版本,您的新模型类应该可以在代码工作簿或代码库中使用。

在库类中重写变换函数

如果Foundry的标准函数不足以满足特定应用案例,您可以为现有模型类重写transform函数。步骤与上面章节中为自定义类注册变换的步骤相同。

然而,请注意注册是级别的。这意味着如果您为特定库函数(例如sklearn的LogisticRegression)重写transform()函数,每当您导入包含重写的库时,该库函数的每个实例都会使用您重写的变换函数。

如果这种行为不符合需求,您可以通过以下方法解决:

  • 创建一个包装类(例如,LogisticRegressionCustom),以包装库函数
  • 按照上述步骤用所需的变换函数注册它

然后,您可以使用这个新类,而无需修改任何库函数调用的行为。

故障排除

未为阶段注册stage_transform

在尝试在序列化模型中使用自定义阶段时,您可能会遇到错误foundry_ml_core.stage.flexible._flexible_stage.FlexibleStageException: No stage_transform registered for stage type: <class 'NoneType'>

此错误通常可以通过以下步骤解决:

  • 确保包含自定义阶段的插件包是环境的一部分。
  • 确保包配置正确。特别是,确保包注册为插件,并且__init__.py文件如上所述导入类。
  • 有时,Conda会解析到包的过时版本。为了确保使用包的正确版本,我们建议将Conda环境固定到所需的特定插件版本。

PyPI包

我们目前不支持Foundry模型中的PyPI包,因为依赖项必须从Conda解决。