2. 评分模型基础:模型文件格式(ONNX/PMML)、模型加载与单次推理流程
在生产环境中部署评分模型,首先需要理解模型的持久化格式以及加载推理的基本原理。本章将深入剖析两种主流开放标准格式——ONNX 与 PMML,并演示从模型加载到单次推理的完整链路。
2.1 模型文件格式对比:ONNX vs PMML
ONNX(Open Neural Network Exchange)与 PMML(Predictive Model Markup Language)分别代表了深度学习与传统机器学习领域的标准化方向。下表从核心定位、适用场景、推理性能与生态支持四个维度进行对比:
| 对比维度 | ONNX | PMML |
|---|---|---|
| 核心定位 | 深度学习模型交换格式,聚焦算子图与张量计算 | 传统机器学习模型交换格式,聚焦数据变换与统计模型 |
| 适用模型 | CNN、RNN、Transformer、GNN 等深度神经网络 | 决策树、随机森林、逻辑回归、GBDT、朴素贝叶斯等 |
| 推理性能 | 支持 GPU/CUDA/TensorRT 加速,算子融合优化 | 通常运行在 CPU 上,优化依赖实现引擎 |
| 生态支持 | PyTorch、TensorFlow、ONNX Runtime、OpenVINO | R、Python(sklearn2pmml)、Java(JPMML) |
| 文件结构 | Protobuf 序列化的二进制图结构 | XML 文本格式,包含数据字典、转换字典、模型节点 |
选型建议:若评分通道以深度学习模型为主(如 CTR 预估中的 DNN、NLP 评分),优先选择 ONNX;若通道以传统树模型或集成模型为主,且需要跨语言部署,PMML 是更成熟的选择。
2.2 ONNX 模型结构解析
ONNX 模型文件本质上是一个序列化的 ModelProto 对象,其核心组件包括:
- GraphProto:计算图定义,包含所有节点(NodeProto)、输入输出(ValueInfoProto)与初始化张量(TensorProto)。
- NodeProto:单个算子节点,记录算子类型(如 Conv、Relu、MatMul)、输入输出名称与属性。
- TensorProto:存储权重、偏置等常量数据,支持多种数据类型(FLOAT、INT64、UINT8 等)。
- ModelMetadata:模型版本、生产者信息、文档描述等元数据。
下图展示了 ONNX 模型文件的内部层级结构:
2.3 PMML 模型结构解析
PMML 采用 XML 层级结构,核心元素包括:
- DataDictionary:定义输入字段的名称、数据类型(double、string、integer)与有效值范围。
- TransformationDictionary:定义数据预处理逻辑,如归一化、离散化、缺失值填充。
- MiningModel / RegressionModel / TreeModel:具体的模型定义,包含分段函数、回归表、决策树节点等。
- Output:定义输出字段的名称与后处理逻辑(如概率转换、类别映射)。
PMML 优势:完整记录了从原始特征到最终评分的全链路变换,使得模型部署不依赖训练框架的预处理代码,真正实现“一次训练,到处评分”。
2.4 模型加载与单次推理流程
无论使用 ONNX 还是 PMML,评分通道的推理流程均可抽象为以下四个阶段:
- 模型加载:将模型文件从磁盘读入内存,解析为运行时对象。
- 输入预处理:将原始请求数据转换为模型期望的张量形状与数据类型。
- 推理执行:调用推理引擎执行前向计算,得到原始输出。
- 输出后处理:将原始输出转换为业务评分(如概率、分数、等级)。
下图以 ONNX Runtime 为例,展示单次推理的完整数据流:
2.5 代码示例:ONNX Runtime 单次推理
以下 Python 代码演示了加载 ONNX 模型并执行单次推理的完整过程:
import numpy as np
import onnxruntime as ort
# 1. 创建推理会话
session = ort.InferenceSession(
"credit_scoring_model.onnx",
providers=["CUDAExecutionProvider", "CPUExecutionProvider"]
)
# 2. 获取输入输出信息
input_name = session.get_inputs()[0].name
input_shape = session.get_inputs()[0].shape # 例如 [1, 128]
output_name = session.get_outputs()[0].name
# 3. 构造输入数据(模拟单条样本)
input_data = np.random.randn(1, 128).astype(np.float32)
# 4. 执行推理
raw_output = session.run([output_name], {input_name: input_data})[0]
# 5. 后处理:sigmoid 转换为概率
def sigmoid(x):
return 1 / (1 + np.exp(-x))
score = sigmoid(raw_output[0][0])
print(f"评分结果: {score:.4f}")
性能注意:在高并发场景下,应避免每次请求都重新加载模型。模型应作为全局单例在服务启动时加载,推理会话复用。ONNX Runtime 的 Session 对象是线程安全的,可被多个请求共享。
2.6 模型加载的常见陷阱
- 动态轴问题:ONNX 模型输入 shape 可能包含动态维度(如 batch_size = -1),推理时需要显式绑定实际 shape。
- 算子兼容性:不同 ONNX opset 版本支持的算子不同,导出时需确认目标推理引擎支持的 opset 版本。
- PMML 缺失值处理:PMML 模型在 TransformationDictionary 中定义了缺失值策略,但不同实现引擎(如 JPMML、sklearn2pmml)的默认行为可能不一致,需显式验证。
- 内存泄漏:频繁创建/销毁 Ort::Session 会导致 GPU 显存泄漏,务必使用连接池或单例模式管理会话生命周期。
最佳实践:在模型加载阶段执行一次“预热推理”(warm-up inference),触发算子编译与显存分配,避免首次请求延迟过高。