注意:以下翻译的准确性尚未经过验证。这是使用 AIP ↗ 从原始英文文本进行的机器翻译。
下面的文档描述了 foundry_ml
库,该库已不再推荐在平台中使用。相反,请使用 palantir_models
库。您还可以通过一个示例了解如何将模型从 foundry_ml
迁移到 palantir_models
框架。
foundry_ml
库将于2025年10月31日移除,这与计划弃用Python 3.9相对应。
如果Foundry的标准模型函数对于特定应用案例不足,或者不支持特定类或库,您可以覆盖某些函数或注册阶段接口所需的函数。例如,您可以创建一个自定义的 Stage
的 transform
函数,然后将其序列化到Foundry中。
如果您希望支持第三方库,可以创建自己的 Stage
实现。
要了解一个示例,请参阅教程,了解如何利用预训练的spaCy模型进行命名实体识别。
这些应写在一个共享的Transforms Python库中,可以添加到您的代码仓库或代码工作簿环境中。一旦导入,您的自定义 Stage
实现将自动集成到 foundry_ml
中。
自定义实现选项描述:
model.transform()
时,模型应执行的操作。由于 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_data
和safe_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
),以包装库函数然后,您可以使用这个新类,而无需修改任何库函数调用的行为。
在尝试在序列化模型中使用自定义阶段时,您可能会遇到错误foundry_ml_core.stage.flexible._flexible_stage.FlexibleStageException: No stage_transform registered for stage type: <class 'NoneType'>
。
此错误通常可以通过以下步骤解决:
__init__.py
文件如上所述导入类。我们目前不支持Foundry模型中的PyPI包,因为依赖项必须从Conda解决。