Deriva-ML:构建可复现机器学习工作流的数据驱动实践
1. 项目概述:为什么我们需要一个“数据驱动”的机器学习工作流?
在任何一个做过几个机器学习项目的人看来,实验的“可复现性”问题几乎是一个永恒的痛点。你精心调参,跑出了一个惊艳的结果,一周后想复现一下,却发现忘了记录某个随机种子,或者当时用的数据预处理脚本版本已经找不到了,又或者队友问你“这个模型是用哪几个特征训练的?”时,你只能对着文件夹里一堆命名混乱的.csv文件发呆。这不仅仅是个人习惯问题,在跨学科的科学计算(eScience)项目中,问题会被放大十倍。生物学家、临床医生、数据科学家、软件工程师坐在一起,每个人对“数据”、“特征”、“模型”的理解和操作习惯都不同,最终项目往往变成一场“数据灾难”——模型性能看似不错,但没人能说清它是怎么来的,更别提让其他团队验证或在此基础上继续研究了。
传统上,我们习惯于“模型中心化”的思维:把大部分精力花在尝试新算法、调参上,数据只是流水线上的一环。但越来越多的教训告诉我们,数据的质量、版本和治理,才是决定项目成败和科学可信度的基石。这就是“数据中心AI”的核心思想:将数据及其完整的上下文(元数据、处理过程、版本)视为一等公民进行管理。我最近深度实践了一个名为Deriva-ML的架构与配套方法论,它不是一个全新的算法库,而是一套构建在Deriva数据管理平台之上的、用于实现可复现机器学习的“操作系统”。它通过强制性的元数据追踪、标准化的数据建模和清晰的工作流定义,把混乱的ML实验过程,变成了一个可审计、可协作、可复现的工程化流程。简单说,它帮你把科研ML项目从“个人手工作坊”升级为“现代化数字生产线”。
2. 核心架构解析:Deriva-ML如何将数据“管”起来?
Deriva-ML不是一个孤立的工具,它建立在Deriva数据管理平台之上。理解它的架构,关键在于理解它如何将机器学习中所有“流动”的资产——原始数据、处理后的特征、模型文件、预测结果、评估报告——都变成平台中可查询、可关联、带版本的“数据对象”。
2.1 数据建模:一切从实体关系图开始
在写第一行代码之前,Deriva-ML要求你先进行数据建模。这听起来很工程化,但却是避免后续混乱的关键一步。你需要和领域专家(比如医生、生物学家)一起,用工具(如Lucidchart)画出一张实体关系图。
为什么这步不可或缺?在青光眼检测项目中,一个核心争议是:“诊断结果”这个标签,应该关联到“眼睛”、“某次观测”还是“受试者个人”?如果关联错了,后续划分训练集时就会出现数据泄露(例如,同一个人的左右眼数据分别进入了训练集和测试集)。通过画ERD,团队能提前暴露并统一这些概念分歧。在Deriva中,这张ERD会直接映射为数据库的表结构,每个方框(实体)变成一张表,每条连线(关系)变成一个外键约束。这就为所有数据资产建立了一个清晰的、机器可读的“户口本”。
注意:很多工程师会跳过这一步,觉得“先跑通流程再说”。但我的经验是,前期在数据模型上多花一小时,后期在排查数据错误和沟通成本上能节省几十小时。ERD是团队共享的“数据语言词典”。
2.2 双目录结构:隔离领域数据与ML资产
Deriva-ML在逻辑上设计了两个核心目录,这是其架构的精妙之处:
- 领域目录:存放所有原始数据和领域核心元数据。例如,在青光眼项目中,这里存的是受试者信息、每张眼底彩照文件、临床诊断记录等。它的结构完全由前述的ERD定义。
- ML目录:这是一个标准化的、可复用的模块。它定义了机器学习工作流中通用的实体,如
Workflow(工作流定义)、Execution(一次具体运行)、Execution Assets(运行产物,如模型、预测文件)、Dataset(数据集合)等。
这两个目录通过外键关联。例如,一个Dataset可以指向领域目录中的多张原始图像;一次训练Execution会关联到它使用的具体Dataset。这种设计实现了关注点分离:领域专家可以专注于维护和丰富领域目录中的数据,而ML工程师则在ML目录的规范下进行操作,两者通过明确的关联关系协作,互不干扰又紧密相连。
2.3 Deriva-ML Python库:标准化操作接口
有了数据模型,还需要工具来操作。Deriva-ML提供了一个Python库,它封装了所有与平台交互的细节,让你能用熟悉的Python环境(Jupyter Notebook, 本地脚本, Google Colab)进行可复现的实验。它的核心是几个基类和方法:
DerivaML基类:提供通用方法,如execution_init(初始化一次实验记录)、execution_upload(将运行产物自动上传到目录)。- 领域派生类:你需要针对自己的项目,从
DerivaML派生一个子类(例如EyeAI-ML)。在这里,你可以实现领域特定的逻辑:- 数据预处理:例如,从原始数据目录中筛选特定角度的眼底图像,或进行图像裁剪。
- 数据分析:例如,加载预测结果和真实标签,计算并绘制ROC曲线。
- 关系构建:在实验运行后,自动建立ML产物与原始数据的关系。比如,将新生成的特征图关联回原始的图像文件。
通过继承和重写这些方法,你将领域知识固化到了代码中,同时保证了所有操作都通过平台进行记录。
2.4 计算环境与资产追踪
Deriva-ML支持多种计算环境,但其最佳实践是部署一个集成了Deriva-ML的Jupyter Hub环境。这个环境预装了GPU资源和必要的库,更重要的是,它与Deriva平台深度集成。
一次典型实验的追踪流程如下:
- 你在Notebook中启动实验,调用
execution_init()。这会在平台的Execution表中创建一条新记录,生成一个全局唯一的执行ID。 - 你运行代码。期间产生的任何输出文件(模型权重
.pth、预测结果.csv、评估图表.png),都建议输出到一个约定的临时目录。 - 实验结束(无论成功或失败),调用
execution_upload()。该方法会自动扫描那个临时目录,将所有新文件上传至平台,并记录在Execution Assets表中。同时,你的运行配置(如config.yaml)和环境日志(如pip list输出)会作为Execution Metadata保存。 - 最后,你实现的“关系构建”方法会被调用,自动在目录中创建链接,比如“模型A”使用了“数据集B”,“预测文件C”对应“原始图像D”。
至此,一次实验的所有数字足迹——用了什么数据、什么代码、什么参数、生成了什么、运行环境如何——都被完整地、结构化地记录在案。任何人拿到这个执行ID,都能精确复现当时的环境和产出。
3. 端到端最佳实践:七步构建可复现ML项目
基于多个项目的经验,我们总结出了一个包含七个步骤的循环最佳实践。这不是一个线性流程,而是一个不断迭代的循环。
3.1 第一步:数据建模与ERD绘制
如前所述,这是奠基步骤。召集所有利益相关者,包括领域专家、数据工程师、ML工程师,共同绘制ERD。重点讨论:
- 核心实体是什么?(如:病人、样本、图像、诊断)
- 它们之间的关系是什么?(一对多?多对多?)
- 哪些属性是关键的?(如:图像的拍摄日期、诊断的置信度)
工具上,我们选用Lucidchart,因为它支持在线协作和版本历史。画好的ERD应嵌入到Deriva目录中,作为数据导航图。
3.2 第二步:开发受控词汇表
这是保证数据一致性的“神器”。对于某些字段,其取值不应是自由文本,而应从一个预定义的列表中选择。例如:
图像视角:左眼,右眼,未知诊断结果:可转诊青光眼,非青光眼,待定种族:亚裔,非裔,高加索裔,西班牙裔,其他
在Deriva中,你可以直接将这些词汇表定义为枚举类型。这样,在数据录入和查询时,界面会自动提供下拉选择,彻底杜绝了“Male”, “male”, “M”混用的情况。词汇表也需要版本管理和团队评审。
3.3 第三步:数据加载与分区
科研数据往往来源分散,格式不一。Deriva-ML提供了一套命令行工具和Python API,用于将本地数据文件及其元数据同时上传到对象存储和目录数据库。关键操作是创建Dataset。
如何创建可复现的数据集?
- 不要用文件夹来区分训练集、测试集。而是创建三个
Dataset记录:训练集-2024-05,验证集-2024-05,测试集-2024-05。 - 每个
Dataset通过“数据清单”精确指向其包含的所有文件资产(通过Minid全局唯一标识符)。 - 使用BagIt工具将数据集打包,确保数据的完整性和可验证性。
- 在
Dataset的元数据中,清晰记录分区逻辑(如:“按受试者ID随机7:2:1分割,确保同一受试者所有图像只出现在一个集合中”)。
这样,当你一年后需要重新训练模型时,可以精确地引用训练集-2024-05这个数据集,确保数据一致性。
3.4 第四步:ML开发与代码组织
我们将ML工作流代码分为两部分,存放在两个独立的Git仓库中:
- 模型库仓库:包含核心的模型定义、数据预处理类、以及从
DerivaML派生的领域类(如EyeAI-ML)。这个仓库的代码要求严格,需要充分的单元测试,版本管理规范(语义化版本号)。它可以被预安装到Jupyter Hub环境中。 - 工作流脚本仓库:包含具体的实验Notebook和运行脚本。这些脚本会导入模型库,组合成端到端的分析流水线。这个仓库的迭代可以更快速、更实验性。
这种分离使得核心算法逻辑稳定且可复用,而实验流程灵活可变。
工作流模块化:我们建议将单个ML工作流(如“模型训练”)拆分为六个顺序执行的模块(W1-W6),每个模块职责单一:
- W1: 初始化与数据加载:创建执行记录,加载指定版本的数据集和模型代码。
- W2: 数据预处理:执行领域特定的清洗、转换、特征工程。
- W3: 模型训练/推理:运行核心算法,这是最容易出错的部分,建议用
with execution(exec_id) as exec:上下文管理器包裹,以便自动捕获和记录错误。 - W4: 结果分析:计算评估指标,生成可视化图表。
- W5: 资产上传:自动将产出上传到目录。
- W6: 关系构建:在目录中建立资产间的语义链接。
每个模块都可以独立开发、测试和复用。例如,同样的W2预处理模块,可以被训练和推理工作流共用。
3.5 第五至七步:执行、评估与演化
第五步是执行与追踪,即实际运行上述工作流,并依赖Deriva-ML自动完成资产和元数据的捕获。第六步是协作审查与评估,团队可以在Deriva提供的Web界面(Chaise UI)上直观地查看每一次执行的完整谱系,包括输入、输出、代码版本、参数和环境,从而进行模型评审和问题排查。第七步是数据模型与词汇表的演化,随着项目深入,你可能会发现需要新的数据字段或新的词汇。这时,需要回到第一步,更新ERD和词汇表,并通过Deriva的模型演进机制来实施变更,而不会破坏现有数据。
这七步构成一个闭环,体现了数据治理与ML开发螺旋式上升、共同演进的过程。
4. 实战案例深度剖析:从架构到问题发现
理论再好,不如看实战。我们通过两个生物医学案例,看看Deriva-ML如何解决真实问题。
4.1 案例一:EyeAI——青光眼自动检测
背景:利用眼底彩照筛查可转诊青光眼。团队包括眼科医生、医学影像专家和ML工程师。
实施亮点:
- 数据建模解决关键分歧:在绘制ERD时,团队激烈讨论了“诊断标签”应该挂在哪个实体上。最终通过ERD可视化推演,一致决定挂在“受试者”级别,并确保数据分区(训练/验证/测试)也按受试者进行,从根本上避免了因同一患者的多张图像泄露到不同集合而造成的评估偏差。
- 受控词汇表统一语言:为“图像视角”、“诊断”、“种族”等字段建立了受控词汇表,使得数据录入规范,后续的统计分析(如检查不同种族间的模型性能差异)变得可行且准确。
- 派生类封装领域逻辑:创建的
EyeAI-ML类实现了两个关键预处理方法:filter_angle_2(只筛选包含完整视神经的特定角度图像)和crop_optic_nerve(根据边界框裁剪视神经区域)。这些领域知识被固化在代码中,并被所有工作流共享。 - 工作流实现与偏差发现:
- 边界框检测工作流:团队先训练了一个模型来定位眼底图像中的视神经区域。工作流严格按照W1-W6执行,并将输出的边界框SVG文件作为
Execution Asset上传、并与原图关联。 - 疾病诊断工作流:使用裁剪后的视神经图像训练分类模型。在W4结果分析阶段,通过自动生成的ROC曲线和详细预测结果回溯,发现模型对拉丁裔人群的预测性能显著偏低。
- 数据溯源与纠正:通过Deriva-ML的关联关系,迅速追溯到训练数据集,发现拉丁裔受试者的样本数量确实不足。团队据此做出了增补拉丁裔患者数据的决策,并重新走了一遍数据加载和ETL流程。如果没有完整的资产追踪,这种基于数据分布的偏差很难被快速定位和纠正。
- 边界框检测工作流:团队先训练了一个模型来定位眼底图像中的视神经区域。工作流严格按照W1-W6执行,并将输出的边界框SVG文件作为
4.2 案例二:MusMorph——小鼠颅骨微CT基因型预测
背景:根据小鼠颅骨的微CT影像预测其基因型。数据来自精心设计的生物实验,其数据模型(ERD)上半部分描述了复杂的实验样本、复制品、扫描仪元数据等湿实验流程,下半部分则对接标准的ML目录。
实施亮点:
- 复杂数据谱系的集成:这个案例展示了Deriva-ML如何对接非数字原生、流程复杂的实验科学数据。ML模型使用的“图像”资产,可以通过目录关系,一直追溯到具体的生物样本、实验处理条件等丰富的生物学元数据。这为后续的可解释性分析(例如,模型是否依赖于某种处理条件带来的特征?)提供了可能。
- 标准化工作流的威力:尽管领域数据极其复杂,但其模型训练工作流(W1: 初始化并加载图像与基因型标签;W2: 汇总标签为对照组/实验组;W3: 训练模型;W4: 绘制ROC曲线;W5: 上传模型与图表)与青光眼案例在ML目录层面几乎一致。这证明了ML目录标准化设计的通用性。ML工程师可以专注于W3的算法,而不必深究上游复杂的生物实验细节。
4.3 效果评估:FAIR原则的量化实现
我们采用FAIR评估指标对两个项目进行了评估。结果显示,基于Deriva-ML构建的系统,在可发现、可访问、可互操作、可重用四个方面均表现出色:
- 全局唯一标识符:每个数据资产、每次实验执行都有唯一ID。
- 丰富的机器可读元数据:所有资产都有结构化元数据描述。
- 标准化访问协议:通过RESTful API和Python库提供标准访问方式。
- 清晰的溯源信息:实验的输入、输出、代码、环境均有记录。
- 许可协议明确:数据和元数据的许可信息清晰。
这不仅仅是理论上的符合,而是通过架构设计,将FAIR原则落地到了每一个数据点和每一次实验操作中。
5. 常见陷阱与实操心得
在实践中,从传统脚本式ML开发转向这套数据驱动的体系,会遇到不少习惯上的挑战。以下是一些踩坑后的经验:
陷阱一:轻视数据建模,急于开始编码。
- 现象:团队跳过ERD绘制,直接定义数据库表或开始写预处理代码,导致后期频繁修改表结构,数据关系混乱。
- 对策:强制要求至少用一天时间,所有核心成员一起评审ERD。使用“实例化”方法检验:拿几个真实的数据例子,在图上走一遍,看能否顺畅地表示出来。这张图是项目最重要的设计文档,没有之一。
陷阱二:将受控词汇表当成静态列表。
- 现象:初期定义的词汇表不完整,后期发现需要新值时,直接在后端数据库里添加,导致前端界面和已有数据出现不一致。
- 对策:将词汇表的管理也视为一个需要评审和版本控制的过程。在Deriva中,可以通过添加新记录到词汇表实体来扩展,并记录添加的日期和理由。对于重大变更,应启动一个小的“数据模型演化”流程。
陷阱三:数据集划分逻辑记录不清。
- 现象:仅仅在代码注释里写了一句“按80/20分割”,几个月后完全忘记是基于ID随机分割,还是基于时间分割,或是分层采样。
- 对策:在创建
Dataset时,必须在其description或自定义的元数据字段中,详细记录分区算法、随机种子、以及任何业务规则(如“确保同一患者的所有样本在同一集合中”)。更好的做法是,将用于划分数据集的脚本也作为一次Execution记录下来,并将其输出(划分清单)关联到最终的Dataset。
陷阱四:忽略计算环境的记录。
- 现象:实验在本地跑通了,放到服务器上却失败了,原因是某个依赖库的版本不一致。
- 对策:充分利用
execution_upload自动捕获的Execution Metadata。确保你的运行脚本能生成或记录关键环境信息,如requirements.txt、conda env export的输出、CUDA版本、Python版本等。Deriva-ML会将这些文件一并上传存档。
陷阱五:把Deriva-ML当作一个“提交结果”的打卡机。
- 现象:团队依然在本地文件系统上管理大部分中间数据,只在最终模型成型后,手动上传一些结果到平台。
- 对策:要最大化其价值,必须将Deriva-ML深度集成到开发习惯中。从实验一开始就通过
execution_init获取exec_id,所有中间输出都指向本次执行。养成“无记录,不运行”的习惯。这样,即使是失败的实验,其记录也具有价值——它告诉我们哪些路径不可行。
个人心得:引入Deriva-ML这样的体系,初期肯定会增加一些开销,感觉“不敏捷”。但它的回报是指数级的。当项目进行到中期,需要对比三个不同特征工程方案的效果时,你能在五分钟内拉出一个表格,清晰列出每次实验的配置、数据和结果。当审稿人质疑模型偏差时,你能追溯到具体的训练数据构成。当新成员加入时,他能通过浏览项目目录,而不是阅读散落各处的文档,快速理解整个数据脉络和实验历史。这种透明度和可追溯性,对于保证科研的严谨性和团队协作效率,是革命性的。它解决的不仅是“复现”问题,更是“信任”问题。
