新闻详情

新闻详情

首页 / 资讯中心 / 详情

数据集格式怎么选?从CSV到TFRecord的工程化实践指南

发布时间:2026/9/30 7:54:23来源:尧图网络
数据集格式怎么选?从CSV到TFRecord的工程化实践指南
平时做数据相关的工作打交道最多的就是数据集和数据集格式。刚开始入行时我拿到一份数据就习惯性用pd.read_csv()一把梭不管它是图像、文本还是传感器数据。后来踩了不少坑才意识到数据集格式这个东西往小里说是文件怎么摆、字段怎么写往大里说直接决定训练管道的效率、团队的协作方式甚至影响你到底跑不跑得动某个任务。这篇就把我实际用过的、以及看到别人踩坑后换掉的几种常见数据集格式系统梳理一遍重点说它们各自适合什么场景、底层原理是什么、切换时要注意什么。1. 表格型数据CSV 与 Parquet绕不开的入门选择1.1 CSV 依然是通用交换格式但别拿它当存储主力CSV 大概是所有人接触到的第一种结构化数据集格式。它本质上就是纯文本一行一个样本字段用逗号分隔。优点很直接任何语言、任何工具都能读Excel 能打开Git 也能直接 diff出问题时你可以用文本编辑器打开看一眼。在团队协作和对外交付时CSV 依然是最高兼容度的那张安全牌。但它的问题也很致命。首先是类型信息丢失一个整数列、浮点数列、时间列在 CSV 里都是字符串读取时全靠下游自己推断。其次解析成本高我实测过一个 2GB 的 CSV用 Pandas 读取大约要十几秒内存峰值远高于文件本身大小因为字符串解析中间对象的开销非常大。再有一个隐蔽问题字段内容里如果包含逗号、引号、换行必须做转义处理RFC 4180很多不规范导出的 CSV 在切分时直接干崩整份数据。所以我的原则是CSV 只用来做数据交换和人工查看不用于正式训练集存储。如果必须在项目里用 CSV记得做两件事用dtype参数显式指定每列类型避免 Pandas 反复推断用chunksize分块读取别一次性把大文件全部 load 进内存。import pandas as pd # 显式指定类型避免对 2GB 文件做全量类型推断 dtypes { user_id: int32, item_id: int32, score: float32, timestamp: int64, } reader pd.read_csv(large_ratings.csv, dtypedtypes, chunksize500_000) for chunk in reader: # 每 50 万行做一次处理内存不会爆 pass1.2 Parquet 的高效秘密列式存储与压缩如果说 CSV 是通用交换格式那 Parquet 就是目前大数据和训练场景下的存储主力。它采用列式存储同一列的数据物理上连续存放而不是像 CSV 那样一行行挨着放。这个差异在跑聚合统计时格外明显比如你想算某一列的平均值列式存储只需要扫描那一列的数据块而行式存储必须把整行全部读出来再丢掉无关字段。Parquet 另一个核心优势是压缩。列式存储天然让同类型数据聚在一起编码器可以针对整数、浮点数、字符串分别选择压缩算法。我手头一个 15 亿行的日志表CSV 大约 120GB转成 Parquet 后不到 15GB压缩比达到 8 倍左右。磁盘省了IO 也省了训练时读取效率自然上去了。此外Parquet 每一列都会存储统计信息min、max、null 数量这为查询引擎做谓词下推创造了条件。你可以只读取满足条件的行和用到的列这在超大数据集上性能差距是数量级的。用 Pandas 和 pyarrow 读写非常简单import pandas as pd import pyarrow.parquet as pq # 写入 Parquet指定压缩算法 df pd.DataFrame({a: range(1000000), b: range(1000000, 2000000)}) df.to_parquet(data.parquet, enginepyarrow, compressionsnappy) # 只读取 a 列命中列裁剪 table pq.read_table(data.parquet, columns[a])我踩过的一个坑是Parquet 的 schema 比较严格同一个文件内的列名、类型必须一致如果多份数据直接按字符串拼接后再写入可能会出现类型推断不一致导致写失败。解决办法是在写入前统一做一次astype转换确保每个分区列的 schema 都是固定死的。2. 标注型数据COCO、YOLO、Labelme 三种格式怎么选2.1 COCO JSON目标检测与分割的通用语言做目标检测、实例分割相关项目的人几乎躲不开 COCO 格式。它把整个数据集的标注信息全部写进一个 JSON 文件里核心结构分三块images存图像路径、宽高、IDannotations存每个目标实例的类别、边界框、分割多边形categories存类别名称到 ID 的映射。COCO 格式我用了好几年最大的优点是完全不依赖文件目录结构标注信息集中管理适合跨团队协作。下面是一个最小可用的 COCO JSON 结构{ images: [ {id: 1, file_name: 000001.jpg, width: 640, height: 480} ], annotations: [ { id: 1, image_id: 1, category_id: 2, bbox: [100, 120, 200, 150], area: 30000, segmentation: [[105, 125, 290, 120, 300, 260, 110, 275]], iscrowd: 0 } ], categories: [ {id: 1, name: person}, {id: 2, name: car} ] }这里的bbox是[x, y, width, height]是绝对像素坐标不是归一化坐标。segmentation是多边形顶点坐标的平铺列表。很多人第一次转 COCO 时容易把坐标搞成归一化值或者把宽高写成右下角坐标这两种错误会直接导致模型训练时的 anchor 匹配和 loss 计算全部崩掉。还有一个使用细节COCO 官方工具包pycocotools对segmentation的多边形格式要求非常严格必须是每个多边形作为一个 list 传入并且经过编码的 RLE 格式和普通多边形格式不能混用。建议入库前统一做一遍校验别把数据源的锅留到训练中期排查。2.2 YOLO txt 和 Labelme JSON轻量方案与标注工具绑定YOLO 系列用得非常多它的标注格式比 COCO 简单很多每张图像对应一个同名的.txt文件每一行是一个目标格式是class x_center y_center width height所有数值都归一化到 0~1 之间。我经常提醒项目里的人YOLO 格式的坐标是归一化的中心点坐标和 COCO 的左上角 宽高完全不一样。转格式时如果直接把像素坐标除以图像宽高就完事那没问题但有很多标注工具导出时默认是像素值忘记归一化就会导致训练时 loss 直接爆炸且无法收敛。下面是读取 YOLO 标注并画框的简单示例def read_yolo_label(txt_path, img_w, img_h): boxes [] with open(txt_path, r) as f: for line in f: parts line.strip().split() cls_id int(parts[0]) x_c, y_c, w, h map(float, parts[1:]) x1 int((x_c - w / 2) * img_w) y1 int((y_c - h / 2) * img_h) x2 int((x_c w / 2) * img_w) y2 int((y_c h / 2) * img_h) boxes.append((cls_id, x1, y1, x2, y2)) return boxesLabelme JSON 则是我用标注工具时最常遇到的中间格式。它把每个标注对象保存为一个shapes数组里面是多个点和shape_type支持多边形、矩形、圆形等。Labelme 格式最大的价值是便于人工标注但直接用于训练并不高效因为它的坐标同样是绝对像素点阵且文件数量庞大。我的做法是搞一个转换脚本把 Labelme JSON 统一转换成 COCO JSON 或者 YOLO txt而不是在训练代码里每次去解析 Labelme 文件。标注工具负责生产原始信息训练管道只消费规范化的统一格式这样多人协作时不会因为标注工具的版本差异导致数据解析逻辑互相冲突。3. 图像分类场景ImageFolder 与 LMDB 的取舍3.1 ImageFolder最省事的图像数据集目录结构图像分类大概是所有深度学习任务里最基础的一种PyTorch 的torchvision.datasets.ImageFolder直接把数据集格式定义成了一种“约定优于配置”的目录结构dataset/ class_0/ 0001.jpg 0002.jpg class_1/ 0001.jpg 0002.jpg每个子文件夹的名字就是类别名图片自然按照类别归组。ImageFolder会自动扫描目录按文件夹名字母顺序映射类别 ID并从每个图片的元信息里解析标签。这种格式最大的优势是零成本开始你不需要编写任何标注文件只要把图片放进对应文件夹就能开始训练。但这里有一个隐蔽的坑类别 ID 是目录名的字母序决定的。比如你有cat和dog两个类那cat是 0、dog是 1。如果后来新增一个bird文件夹排序就变成bird0, cat1, dog2之前训练保存的 checkpoint 里最后一层类别映射就全乱了。所以正规项目的做法是永远不要依赖ImageFolder的隐式排序而是在代码里显式声明一份class_to_idx映射并固定下来哪怕未来增删类别也不影响旧模型。3.2 LMDB海量小文件场景下的工程选择图像数据集常见的痛点是海量小文件。一个 100 万张图片的数据集按小图平均 50KB 来算总量 50GB但文件数量高达 100 万。如果直接放在普通文件系统里每次读取都要经过目录查找、inode 分配、文件打开关闭训练时数据加载往往成为瓶颈GPU 一直空转等数据。LMDBLightning Memory-Mapped Database就是解决这个问题的经典方案。它把数据存成一个内存映射的 key-value 数据库读取时通过mmap把数据直接映射到内存地址空间不需要频繁的系统调用。性能上我做过一个对比相同的数据量从普通文件夹读取 100 万张小图大约需要 25 分钟换成 LMDB 后压缩到不到 6 分钟。LMDB 的写入和使用也非常简单import lmdb import pickle env lmdb.open(train.lmdb, map_size1024**4) # 1TB map size with env.begin(writeTrue) as txn: for idx, (img_bytes, label) in enumerate(dataset_samples): txn.put(str(idx).encode(), pickle.dumps((img_bytes, label))) txn.put(b__len__, str(len(dataset_samples)).encode())读取时先从环境里查到样本数量再根据索引直接获取。这里有个经验LMDB 的map_size一定要设置够大如果写入过程中超过 map 大小会直接报MapFullError很多初学者以为 map_size 是预分配磁盘不敢设大其实它只是地址空间大小不会立刻占用物理磁盘。不过 LMDB 也不是银弹。它适合“数据量大、单条数据独立、顺序访问为主”的场景但如果你的数据需要频繁按条件筛选、跨样本做复杂的增强组合那 LMDB 的优势就会被严重稀释这时候用普通目录加缓存策略说不定更灵活。4. 序列化大数据格式TFRecord 与 HDF5 的工程化应用4.1 TFRecord为训练管道而生的二进制格式TFRecord 是 TensorFlow 生态里最常用的数据集格式。它的本质是一个二进制序列化容器内部使用 Protocol Buffers 定义结构每一条记录就是一个Example里面用Feature保存多种类型的字段int64、float、bytes。我为什么会在非 TensorFlow 项目里也用它核心原因是它把“无数个小文件”变成“少数几个大文件”大大减少了文件系统的访问压力。训练时tf.data.TFRecordDataset可以顺序读取大文件结合shuffle、map、prefetch构建出高效的输入管道。下面是一个把图像数据写入 TFRecord 的示例import tensorflow as tf def serialize_example(image_bytes, label): feature { image: tf.train.Feature(bytes_listtf.train.BytesList(value[image_bytes])), label: tf.train.Feature(int64_listtf.train.Int64List(value[label])), } example tf.train.Example(featurestf.train.Features(featurefeature)) return example.SerializeToString() with tf.io.TFRecordWriter(train.tfrecord) as writer: for image_path, label in sample_list: image_bytes open(image_path, rb).read() writer.write(serialize_example(image_bytes, label))TFRecord 有一个特点它不是自描述的也就是说光看文件不知道里面有哪些字段、字段类型是什么。所以团队内部一定要维护一份独立的 schema 文档并在文件名的前缀或者固定的元数据文件里标明字段版本。我见过多次“换了个机器加载 TFRecord 结果字段对不上”的翻车现场基本都是因为 schema 没有版本管理。另一个要注意的点是shuffle的时机。如果你把整个数据集封装成一个巨大的 TFRecordshuffle需要足够大的 buffer 才能打散样本顺序否则一个 batch 里全是近邻样本训练效果会受影响。解决办法是写多个分片文件shard每个 shard 内部再 shuffle。4.2 HDF5面向科学计算的多维数组容器HDF5 则是另一种完全不同的思路它特别擅长存储大规模多维数组。HDF5 自带层次结构类似文件系统里的目录树group 相当于文件夹dataset 相当于数据对象。每个 dataset 可以直接存 NumPy 数组支持切片读取并且内置 chunk 压缩功能非常适合遥感、医学影像、物理模拟这种高维数据。h5py 的 API 很直接import h5py import numpy as np with h5py.File(features.h5, w) as f: grp f.create_group(train) grp.create_dataset( images, datanp.zeros((10000, 224, 224, 3), dtypenp.float32), chunks(128, 224, 224, 3), compressiongzip, ) grp.create_dataset(labels, datanp.arange(10000))HDF5 的一个核心概念是 chunk。设置chunks后数据在物理上按块存储读取时只加载相关块而不会像普通数组那样把整个数据集载入内存。压缩也可以针对 chunk 进行gzip、lzf是常用方案。但 chunk 的大小需要平衡太大随机读取时多余 IO 多太小压缩率下降元数据开销变大。我的经验是让每个 chunk 的大小在 1MB 到 8MB 之间既兼顾了随机访问效率也保留不错的压缩率。HDF5 的坑在于并发写。多个进程同时写同一个 HDF5 文件很容易损坏文件因为 HDF5 的文件锁机制和常见的训练框架多进程 DataLoader 配合得并不好。如果非要并发写我建议要么先单进程写入临时文件要么每个进程写独立文件最后再合并。这个坑我踩过两次每次都是训练到一半发现数据文件读不出来了整个流程重新跑代价很大。5. 数值计算与中间结果npy/npz 的使用边界5.1 npy单一数组的最佳保存方式在预处理特征、保存 embedding、存中间训练结果的时候最容易想到的就是 NumPy 的.npy格式。它保存的是单个 NumPy 数组包括 dtype、shape、strides 等信息加载时可以完全还原原始数组结构不需要额外的元信息文件。np.save和np.load非常方便但我更想强调np.memmap的用法它可以按内存映射的方式读取超大 NumPy 数组而不像np.load把整个数组加载进内存。这对于单机处理几百 GB 的数值数据特别有用。一个典型场景是保存全量图像特征几百万张图各提取一个 1024 维向量总大小超过 10GB直接np.load容易把内存占满换成memmap就可以按行抽样、按 batch 切片import numpy as np # 写一个超大特征矩阵 features np.memmap(features.npy, dtypenp.float32, modew, shape(500_0000, 1024)) features[:] compute_all_features() features.flush() # 之后读取内存只占一个页面的量 features np.memmap(features.npy, dtypenp.float32, moder, shape(500_0000, 1024)) batch features[:256] # 只加载 256 行使用 npy 存储有一点要小心文件头部只包含数组元信息并不包含字段名。你保存的是纯数组所以如果数组的每一行代表不同含义必须在外部维护一份说明文档或者在文件名上写清楚。这听起来很简单但实践里我见过太多“这个 npy 到底啥意思”的灵魂拷问。5.2 npz 与 pickle多数组和通用对象的取舍当你要一次性保存多个数组时可以用.npz它其实是一个 zip 容器内部保存多个.npy文件。np.savez支持关键字参数加载时通过字典访问np.savez(batch_data.npz, imagesimg_array, labelslabel_array, metadatameta_array) loaded np.load(batch_data.npz) img_array loaded[images].npy和.npz都是相对“安全”的格式因为它们只描述 NumPy 数组的结构加载时不会有代码执行。但.pickle就不一样了。pickle 可以序列化任意 Python 对象所以很多初学者图省事把整个数据对象直接pickle.dump出来。我强烈建议不要用 pickle 作为数据集的长期存储格式哪怕自己写着方便。原因有两个一是 pickle 的格式和 Python 版本、类的定义强绑定类结构一变老文件可能直接反序列化失败二是加载 pickle 是存在任意代码执行风险的一旦你拿到一个来路不明的.pkl执行pickle.load相当于让别人在你的机器上跑代码。如果只是想保存字典或对象配置更好的选择是用 JSON或者用safetensors这类专门为模型张量设计的格式。safetensors 不仅在安全性上做了约束加载时不执行任意代码而且支持内存映射加载在大模型场景下已经逐渐成为主流。6. 自定义数据集格式什么时候该自己造轮子6.1 设计自研格式前先想清楚三个问题看到这里你会发现大部分场景都有成熟的现成格式可用。但现实中的确存在一些场景通用格式不满足需求比如点云数据带着变长的时间戳、流式日志和样本关联、超高吞吐的强化学习轨迹回放等。这时候确实要考虑自定义格式。但动手造轮子之前我建议你先回答三个问题。第一现有的格式是真的“不能”还是仅仅“不够顺手”如果只是性能不够先试试调 chunk、换压缩算法、改并行读取策略很多“瓶颈”其实是使用方式的问题。第二你的自定义格式是否具备完整的写入、读取、校验工具链只有写入没有读取等于没做。第三团队成员未来是否都能维护这套格式长期没有人维护的自定义格式最终会变成团队的负担比技术选型问题严重得多。如果以上三个问题都能给出正面答案那自定义格式是完全合理的。设计时有几个原则很关键文件头写入魔数magic number和版本号便于快速识别文件类型和兼容性。固定长度的头部信息比如样本总数、每条记录的字节长度方便随机访问和进度条展示。每条记录包含长度前缀和校验码损坏时可以快速定位并跳过而不是整个文件报废。尽量与存储介质对齐比如大块顺序读优于随机小读。6.2 一个自定义二进制数据集格式的简化例子我之前为一个联邦学习项目设计过一个简化自研格式。数据源是手机端上报的梯度更新每条记录长度不固定、字段类型混杂、数量高达几千万条。用 JSON 存体积爆炸用 Parquet 又因为每条样本的字段结构不同而难以对齐。最终我们定义了一个二进制格式int32 magic # 0xFAFA int32 version # 格式版本 int32 sample_count # 样本总数 int64 data_offset # 数据区起始偏移 ------------------ 数据区 ------------------ 每条记录 int32 record_bytes # 本条记录总字节数 int32 sample_id # 样本 ID int32 feature_len # 特征数组长度 float32 features[] # 特征值 uint32 crc32 # 本条记录校验值核心思路是每条记录前先写一个长度前缀读取时先定位到某个样本的偏移再一次性把整条记录读出来。sample_count写在文件头所以我们可以直接二分法定位到任意样本的偏移实现随机访问。crc32校验挽救了我好几次因为早期日志传输偶尔出现坏块没有校验时只能靠运行时 loss 异常去反查有了校验码可以训练前直接全量验证一遍。如果你也要设计类似格式我的建议是文件头多留 64 字节保留区以后扩展字段不用改整体布局。这个设计细节看着不起眼等你真的要加新字段时就会感谢当初留的冗余空间。7. 一文速查数据集格式怎么选把上面各种格式汇总成一张速查表方便你做技术选型时快速对照。格式适用场景优点主要缺点推荐工具CSV小型表格、数据交换通用、可读性高无类型、解析慢PandasParquet大规模表格、数仓列式压缩、查询高效不适合频繁小改pyarrowCOCO JSON目标检测、分割标注集中、生态完善文件大、解析慢pycocotoolsYOLO txt目标检测轻量方案文件小、读取直接坐标归一化易出错自写脚本Labelme JSON人工标注过程可视化友好结构冗余、依赖工具labelmeImageFolder图像分类起步零配置、直观类别映射隐式torchvisionLMDB海量小图高性能、mmap并发写脆弱lmdbTFRecordTensorFlow 管道顺序读快、生态完善不自描述tensorflowHDF5科学计算多维数组分层结构、切片好并发写差h5pynpy/npzNumPy 数组中间结果简单、保留完整信息无字段名numpypickle临时对象持久化任意对象不安全、版本依赖不建议长期用选格式这件事在我看来从来不是“哪个最好”而是“哪个最合适”。同样是十几 GB 的数据图像分类用 LMDB 很顺手表格特征用 Parquet 更香做 TensorFlow 的项目绕不开 TFRecord科学计算又离不开 HDF5。一切取决于你的数据形态、训练框架、团队维护能力和后续扩展方向。我个人实际项目里最常用的一套组合是原始数据统一进 Parquet 归档标注信息用 COCO JSON 做中间层训练管道的输入再按框架转成 TFRecord 或直接读取 LMDB。越往链路下游格式越贴近底层框架越往数据源上游格式越贴近通用标准。这套思路帮我避开了很多数据读取和协作上的坑。最后再分享一个感受格式转换本身不复杂真正复杂的是转换过程中暴露出来的数据质量问题坐标算错、字段缺失、编码混乱这些才是常见痛点。所以无论你用哪种数据集格式一定要保留一层数据校验环节在写入和读取两端都做检查。格式只是容器数据质量才是底牌这两点抓牢了后面跑模型会顺畅得多。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

趣博思 AI 数据分析:论文的实证部分,就是当一回 “数据侦探“ 2026/9/30 8:57:45

趣博思 AI 数据分析:论文的实证部分,就是当一回 “数据侦探“

写论文写到实证部分,很多同学的状态可以用四个字形容:无从下手。问卷收回来了,几百份;实验数据测出来了,一大片。可数据明明都在手里,却不知道拿它们干什么,仿佛面前摆着一堆散落的线索&#xf…

阅读更多 →
一辆车跑出的数据,能做什么?从“连接”走向“长期运营” 2026/9/30 8:57:45

一辆车跑出的数据,能做什么?从“连接”走向“长期运营”

今年摩博会上,一个变化比往年更明显:无钥匙解锁、远程控车、电量查看,这些曾经属于高配车型的亮点,如今已成为中低端车型的标配。“连得上”只是入场券,真正的问题是:车辆联网之后,每天回传的数…

阅读更多 →
GitAgent Hooks钩子详解:拦截、修改和控制AI代理每个生命周期事件 2026/9/30 8:57:38

GitAgent Hooks钩子详解:拦截、修改和控制AI代理每个生命周期事件

GitAgent Hooks钩子详解:拦截、修改和控制AI代理每个生命周期事件 【免费下载链接】opengap A framework-agnostic, git-native standard for defining AI agents 项目地址: https://gitcode.com/gh_mirrors/git/opengap GitAgent Hooks(钩子&…

阅读更多 →
线性回归实战:从原理到工业部署的完整闭环 2026/9/30 8:57:38

线性回归实战:从原理到工业部署的完整闭环

1. 这不是数学课,是用数据“猜价格”的手艺活 你有没有在租房平台刷到一套房子,看到面积、楼层、离地铁距离这些信息,心里就大概估出它值多少钱?或者在二手车市场,光看车龄、里程、品牌,就能判断这台车报价…

阅读更多 →
Spirent TestCenter实操指南:端口占用、VLAN与组播流配置全解析 2026/9/30 8:57:30

Spirent TestCenter实操指南:端口占用、VLAN与组播流配置全解析

简介:这是一份Spirent TestCenter网络测试仪表的简易操作PPT,面向刚接触网络测试仪或需要快速上手的工程师、运维人员;资源包内为1个PPT文档,大小3.18MB,内容集中在端口占用、基本建流和组播验证这三类高频操作上。该主…

阅读更多 →
LLM推理优化实战:从驱动安装到vLLM部署的全链路指南 2026/9/30 8:57:30

LLM推理优化实战:从驱动安装到vLLM部署的全链路指南

1. 项目概述:Model-Optimizer 不是工具名,而是一类工程实践的统称 “Model-Optimizer”这个标题乍看像某个开源库或商业软件的代号,但结合NVIDIA、TensorRT-LLM、vLLM、PT文件转换、Docker镜像部署等高频热词,它实际指向的是 大…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉