MatrixOne Git4Data 技术详解(十)·深度学习篇:训练数据怎么管——lakeFS 管文件,MatrixOne 管元数据

发布时间:2026/8/1 5:43:04
MatrixOne Git4Data 技术详解(十)·深度学习篇:训练数据怎么管——lakeFS 管文件,MatrixOne 管元数据 前面九篇一直在结构化数据上打转前四篇讲清了 Git4Data 是什么、怎么用、和其他方案分别站在哪一层第五到第七篇是数据运维第八、九篇进入 AI 训练用一个风控模型走通了全流程总图和数据集发布与泄漏。这一篇我们转向深度学习的数据。先厘清一点深度学习是一个很大的范畴并不等同于多模态——只处理文本的网络、只看图像的 CNN都是深度学习。但它和传统机器学习在数据形态上有一道共同的分水岭深度学习往往直接在大规模非结构化数据图像、音频、视频、原始文本上训练。本篇聚焦的就是这一类文件型图像、音频、视频等非结构化训练数据该怎么管——它是深度学习里最典型、也最难版本化的数据形态。为了不空谈我们全程跟着一个经典任务走训练一个图像分类模型。数据从“表里的行”变成“一堆文件 一张巨大的元数据表”版本管理也得换一套打法。这一篇把图像等文件型训练数据的管理整个过一遍思路和第八篇之于传统机器学习类似先把整张图摊开——训练数据从进入到发布每一步的真实难题是什么文件该交给谁、元数据该交给谁。文中元数据侧的 SQL 全部在 MatrixOne4.1.0上实测lakeFS MatrixOne 的完整端到端脚本run_practice.sh也真跑通过见 matrixorigin/git4data-tutorial 的10-multimodal-lakefs/。深度学习的数据首先是一个“文件”问题传统机器学习的一条样本是表里的一行几十个结构化字段天然适合放进数据库也天然能被 snapshot、diff、merge。深度学习的数据不是这样。一条样本的主体是一个文件——一张几 MB 的图、一段几十 MB 的音频、一个上百 MB 的视频片段说到底就是一堆字节。整个数据集动辄上千万个文件、TB 到 PB。把这些文件塞进数据库既不划算、也发挥不出数据库的长处下一节展开。但请注意一件事文件本身不进数据库关于文件的一切却高度结构化。每条样本都有它存在哪对象路径、它的内容 hash、它的感知 hash、类别标签、来自哪个源、什么 license、宽高、质量分、属于训练还是测试、被哪个模型版本用过……这些是几千万行、还在不断增删改的结构化记录——正是最需要行级版本语义的地方。于是这类数据的版本管理天然分成两个世界文件世界图像 / 音频 / 视频本体。放对象存储或 lakeFS版本化的是“对象 / 文件的版本”。元数据世界metadata谁指向哪个文件、label、split、各种 hash、来源、license…… 一张或几张巨大的结构化表版本化的是“行”。一份真正可复现的训练集是这样一个乘积可复现训练集 一个确定的元数据版本metadata snapshot × 一组确定的文件版本lakeFS commit两个世界必须一起被钉住、并且保持一致只钉元数据文件可能已被覆盖只钉文件你不知道当时哪些样本、什么标签、怎么切分。这正是 lakeFS管文件和 MatrixOne 的 Git4Data 能力管元数据各司其职、再组合起来的地方。为什么文件交给 lakeFS而不是也塞进数据库一个自然的疑问既然 MatrixOne 能版本化数据为什么不干脆把图片文件也放进去、一个系统全管了前面几篇其实已经把答案铺好了。git4data 的低成本快照前提是“结构化 元数据目录”。第三篇讲过MatrixOne 的快照近乎与数据量无关靠的是不可变对象加元数据目录去版本化行级结构化数据。把 PB 级不可解析的图片文件灌进去这个前提就不成立了——数据库会退化成一个又贵又慢的对象存储。文件没有可 diff 的结构。git4data 的价值在第二篇就立住了行级的 diff / merge / query。可一张 JPEG 没有行、没有主键、没有列——对两张图做“行级 diff”毫无意义。第四篇划过的边界也正是这条git4data 管的是“同一 schema 下的结构化数据演进”而文件根本没有 schema。文件是整体、不可变地被写入的。一张图不会被逐行UPDATE它只会被整体替换。lakeFS 把对象整体写入、底层对象不可变branch 是零拷贝的元数据操作、未改动的对象跨版本复用——这种“整块对象 廉价分支”的版本化正是对象存储 lakeFSgit-over-objects最擅长的硬套数据库的行级 MVCC 反而别扭。更划算的是让数据库只存指针。MatrixOne 也能存 BLOB但把 PB 级文件放进去是成本与架构上的取舍第八篇的总览给的更实际的分工是不可解析文件交给对象存储 / lakeFSMatrixOne 存目录、hash、URI 和 commit。而且数据库快照只能冻结指针字段的取值冻不住外部文件本身datalink那条边界——所以文件的版本交给 lakeFS 更顺。一句话数据库最擅长的是“结构化元数据的行级版本化”lakeFS 最擅长的是“大文件的整体版本化”。让各自做各自最强的事再把两者钉在一起就是这一篇的全部主张。一张总图训练数据全流程文件归 lakeFS元数据归 MatrixOne先给结论。这类文件型训练数据的整个生命周期可以清晰地劈成“文件侧”和“元数据侧”两条线各自版本化、在发布时对齐。环节真实问题文件侧lakeFS元数据侧MatrixOne Git4Data数据接入新一批图片质量未知不能污染主集落到一条 ingest 分支元数据行进入元数据分支审计通过再MERGE去重上千万文件里有精确重复和感知近重复对象照存content_hash/phash上GROUP BY纯 SQL 找重复去污染训练集混进了评测 / 基准样本——元数据对基准 hash 表做反连接DELETE掉重合完整性检查每个样本都要有标签、指针要解析到真文件对象存在性由 lakeFS 保证查缺标签对 commit 的对象清单反连接验存在重标注类别标签、安全分要迭代文件不变每人一条分支、MERGE冲突、DIFF出改动data curation按质量 / 安全 / license 筛出干净子集——版本化的dataset_membership子集数据集发布冻结“这次训练到底用了哪些文件的哪一版”一个 lakeFS commit一个库级元数据快照并把 commit 记进注册表训练与评估模型要能反查到确切的数据现场commit 定位文件快照 注册表建立model → metadata snapshot × lakeFS commit × code/env血缘监控与再训练新数据累积何时触发下一轮新 commit元数据上跑分布统计 跨版本DIFF一句话分工lakeFS 让文件可回溯、可回滚MatrixOne 的 Git4Data 能力让元数据可查询、可行级比对、可原子发布。两者用“元数据快照里记一个 lakeFS commit”对齐成一个可复现的整体。下面用一个完整案例把这张图跑通。贯穿全文的案例给一个图像分类模型准备训练数据假设我们要训练一个图像分类模型——一个内容安全分类器把图片分成safe/nsfw两类换成商品类目、场景分类也是一样的套路。训练数据就是大量图片文件 每张图的类别标签从多个源采集而来需要去重、去污染、检查完整性、修正标签最后做 data curation得到一个干净、可复现的训练集。元数据就是一张samples表——注意它不存文件只存指向文件的指针加上所有你真正要查询的东西CREATETABLEsamples(sample_idBIGINTPRIMARYKEY,object_uriVARCHAR(512),-- lakeFS 路径一个指针不是文件本身object_commitVARCHAR(64),-- 钉住这个文件的 lakeFS commitcontent_hashVARCHAR(64),-- 文件的 sha256精确去重键phashVARCHAR(64),-- 感知哈希近重复键labelVARCHAR(16),-- 类别标签safe / nsfwNULL 尚未标注sourceVARCHAR(32),-- 来源 / 溯源licenseVARCHAR(16),ingest_batchVARCHAR(32));一份可复现的训练记录至少要绑定这些run 元数据快照metadata snapshot lakeFS commit文件版本 data curation 与切分规则 预处理 / 数据增强版本 代码 commit 运行镜像 digest 超参数与随机种子 模型产物 URI 与 hash 评估指标元数据快照负责“哪些样本、什么标签、怎么切”lakeFS commit 负责“文件是哪一版”——缺一个这份记录都复现不出来。第一站数据接入——WAP 跨两个世界星期一上游送来一批新图片。两个世界同时动各走各的 WAP。文件侧lakeFS新对象先上传到一条 ingest 分支做完文件层校验能否解码、尺寸、safety 预扫可挂 pre-merge hook再 commit、合并到 main——拿到的这个 commit 就是这批文件的版本下面的 lakeFS 命令与 commit 值取自可跑脚本run_practice.sh$L是它的 API 地址、$KEY:$SECRET是凭证# 上传对象到 ingest 分支后提交并合并到 maincurl-u$KEY:$SECRET-HContent-Type: application/json\-XPOST$L/repositories/media/branches/ingest/commits-d{message:ingest 2026w30}curl-u$KEY:$SECRET-HContent-Type: application/json\-XPOST$L/repositories/media/refs/ingest/merge/main-d{message:publish 2026w30}# - main commit文件版本 ba1693908b37…示例每次运行不同元数据侧MatrixOne同一批样本的元数据行——指针指向 lakeFS 对象、object_commit填刚拿到的那个 commit——进入一条分支先审计、通过才合并。这正是第七篇 Write-Audit-Publish 那套只是现在跨了两个世界DATABRANCHCREATETABLEsamples_stageFROMsamples;-- 新批次只进 staging 分支每行 object_commit ba1693908b37…INSERTINTOsamples_stageSELECT...FROM...;-- 元数据侧门禁指针完整标签齐不齐license 明不明SELECTSUM(CASEWHENobject_uriISNULLORobject_commitISNULLTHEN1ELSE0END)ASmissing_pointer,SUM(CASEWHENlabelISNULLTHEN1ELSE0END)ASmissing_label,SUM(CASEWHENlicenseunknownTHEN1ELSE0END)ASunknown_licenseFROMsamples_stageWHEREingest_batch2026w30;-- 实测 missing_pointer 0 / missing_label 250 / unknown_license 1000DATABRANCH DIFF samples_stage AGAINST samples OUTPUT SUMMARY;-- 实测 INSERTED 5000DATABRANCHMERGEsamples_stageINTOsamples;-- 全部通过才发布两侧各自审计、各自在本系统内原子合并。但要说清楚lakeFS 和 MatrixOne 之间没有跨系统事务——本例是文件侧先合并、元数据侧后合并若元数据侧失败lakeFS main 已经动了。真要做到“任一侧不过、两侧都不发布”需要一层发布协调两侧先各自形成不可变候选版本联合校验通过后再统一以版本化 registry 对外发布可见版本。第二站去重——精确 感知纯 SQL不碰一个文件上千万文件里一定有精确重复同一张图在不同 URL 被爬了两次和感知近重复裁剪、压缩、加水印后的“同一张图”。这两类都能在元数据上用 SQL 查出来完全不需要把文件拉回来-- 精确重复一个 content_hash 被多条样本共用SELECTCOUNT(*)ASexact_dup_groupsFROM(SELECTcontent_hashFROMsamplesGROUPBYcontent_hashHAVINGCOUNT(*)1)t;-- 实测 3000 组-- 感知近重复不是精确重复同一个 phash却有不止一个 content_hashSELECTCOUNT(*)ASnear_dup_groupsFROM(SELECTphashFROMsamplesGROUPBYphashHAVINGCOUNT(DISTINCTcontent_hash)1)t;-- 实测 2000 组文件的 hash 是离线算好、写进元数据的一旦进了元数据去重就是几条GROUP BY的事而不是一场跨 PB 对象存储的扫描。第三站去污染——把评测集从训练集里挖出去这是深度学习、尤其基础模型的命门一张测试 / 基准图漏进训练集下游每一个指标都会虚高。做法是拿元数据去和已知的评测集 hash 做反连接-- 训练样本里有多少和评测基准按内容重合SELECTCOUNT(*)AScontaminatedFROMsamples sWHEREEXISTS(SELECT1FROMeval_hashes eWHEREe.content_hashs.content_hash);-- 实测 1000对应 500 个 benchmark 内容 hash每张基准图 它被重新爬到的那份精确副本这条反连接命中的 1000 行全是content_hash精确相等的命中500 个唯一 benchmark hash每个对上原件和它的精确副本各一行。注意它只覆盖“精确内容重复”裁剪、压缩、加水印后的近重复content_hash不同、phash相近并不会被命中——要连近重复一起去污染得像去重那样再对 benchmark 的phash做一次反连接。本文的实现只做了精确去污染。第四站完整性检查——每个样本都要有标签指针要真能解析到文件训练一个图像分类模型每个样本至少要满足两条有一个类别标签、指针能解析到一个真实存在的文件。第一条在元数据上一条 SQL 就能查-- 缺标签有图却没有类别标签本轮不能进训练SELECTCOUNT(*)ASunlabeledFROMsamplesWHERElabelISNULL;-- 实测 550第二条要小心只查object_uri/object_commit是否非空只能证明字段填了值并不能证明文件真的存在commit 不存在、path 写错、对象被删这种检查都发现不了。真正的存在性检查得去问 lakeFS——最简单的做法是把那个 commit 下的对象清单拉进来和指针做一次反连接-- 先把 commit 的对象清单导入一张表 lakefs_objects(path)再反连接SELECTCOUNT(*)ASdanglingFROMsamples sWHERENOTEXISTS(SELECT1FROMlakefs_objects oWHEREs.object_uriCONCAT(lakefs://media/main/,o.path));-- 配套 run_practice.sh 里这一步是真跑的从 lakeFS 列出 commit 的对象、导进来反连接实测 dangling 0这也点出文件世界和元数据世界之间的陷阱删掉 lakeFS 里的一个对象不会自动删掉元数据里指向它的行反过来删元数据行也不会删文件。两个世界各自版本化一致性要靠这类跨世界的校验来兜底。第五站重标注——元数据在演进文件纹丝不动类别标签会被修正安全分会被重新评估——这些都只动元数据文件完全不变。于是又回到了第六篇那套并行协作每人一条分支冲突自动暴露改动有据可查。DATABRANCHCREATETABLEsamples_reviewFROMsamples;UPDATEsamples_reviewSETlabelnsfwWHEREsample_idBETWEEN1000AND1999ANDlabelsafe;DATABRANCH DIFF samples_review AGAINST samples OUTPUT SUMMARY;-- 实测 UPDATED 980DATABRANCHMERGEsamples_reviewINTOsamples;重标注一轮到底改了什么是一条DIFF说清的事而这一切都没有产生任何一份文件副本。第六站data curation 与发布——元数据快照 × lakeFS commit到了发布时刻。先在元数据上做一次data curation整出一个干净子集去掉精确重复每个content_hash只留sample_id最小的一条、去掉评测重合、去掉缺标签的、只保留 license 明确的样本并写入切分INSERTINTOdataset_membershipSELECTs.sample_id,CASEWHENs.sample_id%108THENtrainWHENs.sample_id%108THENvalidELSEtestEND,curate:v1 dedupdecontamlabeledlicensedFROMsamples sWHEREs.labelISNOTNULLANDs.licenseunknownANDNOTEXISTS(SELECT1FROMeval_hashes eWHEREe.content_hashs.content_hash)ANDs.sample_id(SELECTMIN(s2.sample_id)FROMsamples s2WHEREs2.content_hashs.content_hash);-- 实测 train 38474 / valid 4934 / test 4935然后是关键一步——先把 lakeFS commit 登记进注册表再打快照让这条绑定被冻进快照里。顺序很重要先写 registry、后打 snapshot绑定才落在被冻结的元数据版本里而不是只躺在可变的活库里-- 先登记“元数据版本 × 文件版本”的绑定快照名提前定好行里可以先写上它INSERTINTOdataset_registrySELECTic_v1,ic_dataset_v1,media,ba1693908b37…,COUNT(*),metadata snapshot × lakeFS commit reproducible training setFROMdataset_membership;-- 再打快照把 samples / dataset_membership / dataset_registry 一起冻结CREATESNAPSHOTic_dataset_v1FORDATABASEimg_cls;-- 现在这条绑定就在快照里了不只在活库里SELECTlakefs_commitFROMdataset_registry {SNAPSHOTic_dataset_v1}WHEREdataset_versionic_v1;-- - ba1693908b37…从此“ic_v1 到底用了哪些数据”不再是一句口头描述而是一个乘积ic_dataset_v1元数据快照指明了样本、标签、切分ba1693908b37…lakeFS commit指明了文件。而复现也不止是数出行数——可以把确切的文件取回来从快照读出一条训练样本的指针和 commit再回 lakeFS 按那个 commit 取文件下面这段在配套run_practice.sh里是真跑的SELECTs.object_uri,s.object_commitFROMsamples {SNAPSHOTic_dataset_v1} sJOINdataset_membership {SNAPSHOTic_dataset_v1} mONs.sample_idm.sample_idWHEREm.split_nametrainORDERBYs.sample_idLIMIT1;-- - lakefs://media/main/img/000003.jpg ba1693908b37…curl-u$KEY:$SECRET$L/repositories/media/refs/ba1693908b37…/objects?pathimg/000003.jpg# - img-3-bytes ← 元数据快照 × lakeFS commit把确切的文件复现了出来lakeFS 与 MatrixOne两个版本世界怎么分工、怎么组合这一篇必须把两者的边界讲清楚否则很容易误以为“有一个就够了”。lakeFS 管文件。它是对象存储上的 git 式版本控制在 S3 / GCS / Azure 之上提供 branch / commit / merge把“对象存储在某个时刻的状态”钉成一个可回到的 commit还能用 pre-merge hook 在合并前做文件层校验。它擅长的是大文件本体的版本化与回滚。它不做的是把上千万条元数据当成一张表来跑 SQL、JOIN、聚合或者告诉你“这两版之间哪些行的标签变了”。MatrixOne 的 Git4Data 能力管元数据。它把元数据当成活的、可查询的表行级 snapshot / branch / diff / merge / restore随时能 JOIN、聚合、反连接。它擅长的是结构化元数据的版本化、行级比对和原子发布。它不适合承担的是存储和版本化图音视频的文件本体能存 BLOB但不划算。两者怎么组合靠“元数据快照里记一个 lakeFS commit”。发布时MatrixOne 侧打一个库级元数据快照同时把当时的 lakeFS commit 写进注册表复现时两个 ID 一起用。对象更适合谁它负责什么它不负责什么图 / 音 / 视频等大文件lakeFS / 对象存储文件的版本、回滚、pre-merge 校验行级元数据查询与 diff元数据指针、label、hash、split、来源MatrixOneGit4Data 能力行级快照 / 分支 / diff / merge / 恢复可 JOIN 可聚合存文件本体能存 BLOB但不划算两者的对齐注册表里的一条绑定元数据快照 × lakeFS commit 可复现训练集——这比“指望一个工具同时管好文件和元数据”更贴近现实。文件有文件的最优解元数据有元数据的最优解关键是把它们显式地钉在一起。一个可以直接采用的最小闭环文件进 lakeFS元数据进 MatrixOne 的一张samples表每行记指针 content_hashphash label 来源 license。新批次先上分支文件上 lakeFS 分支、元数据上 MatrixOne 分支两侧各自审计通过才合并。在元数据上用 SQL 做去重、去污染、完整性检查把可疑样本挡在 data curation 之外。做 data curation把干净子集写进dataset_membership打一个库级元数据快照。把 lakeFS commit 和元数据快照一起登记进注册表——这是可复现的锚点。训练时绑定model → metadata snapshot × lakeFS commit × code/env下一轮用DIFF看元数据变化、用新 commit 看文件变化。结语深度学习把训练数据从“表里的行”变成了“对象存储里的文件 一张巨大的元数据表”。这两样东西的最优管理方式不一样文件要的是整体的版本与回滚元数据要的是行级的查询、比对和原子发布。把它们硬塞进同一个工具总有一头别扭。更现实的架构是让lakeFS 管文件、MatrixOne 的 Git4Data 能力管元数据再用“元数据快照 × lakeFS commit”把两个版本世界钉成一个可复现的整体。去重、去污染、完整性、重标注、data curation——这些真正决定训练数据质量的操作几乎都发生在元数据上而元数据恰好是一张可以用 SQL 版本化管理的表。 可运行 SQLgithub.com/matrixorigin/git4data-tutorial 源码与社区github.com/matrixorigin/matrixone