拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI训练数据类型全解析:从标注格式到张量精度

AI训练数据类型全解析:从标注格式到张量精度 做AI训练这几年被问得最多的不是“哪个模型更厉害”而是“我的数据到底该怎么准备”。有人拿着一堆原图来训YOLO有人把JSON标注直接塞进脚本就报错也有人分不清float32和uint8在训练时到底差在哪。说实话很多训练跑不起来、精度上不去问题根本不在模型结构而在训练数据类型的整个链路。我理解的“AI训练数据类型”至少包含三个层面任务对应的标注格式、数据在磁盘和内存里的存储类型、深度学习框架计算的张量精度再加上我们写数据处理脚本时接触到的Python组合类型、NumPy的dtype、Pandas的category甚至C、PLC等外部系统的变量类型。把这套东西理顺了后面训练、排查、调优都会顺畅很多。这篇文章不绕弯子直接用我实际跑过的案例来拆。1. 先搞懂“训练数据类型”到底包含哪些层次1.1 业务层的类型任务决定标注格式在开始任何训练之前先想清楚做的是图像分类、目标检测、实例分割、关键点检测、旋转目标、OCR还是大模型指令微调。任务类型不同标注数据的格式和“数据类型”完全不一样。图像分类通常只需要一个label.txt或CSV每个文件名对应一个类别索引。目标检测主流标注格式有VOC XML、COCO JSON、YOLO txt三者的坐标类型和存储结构差别很大。实例分割/全景分割需要polygon多边形或二值maskCOCO里还经常用RLE压缩字符串。旋转目标检测典型用DOTA格式每个目标用四个角点坐标表示。OCR需要文本框四点坐标外加文本字符串字符还要做编码映射。大模型微调/LoRA常用JSONL文件一条instruction/response构成一个样本。很多新手老爱问“哪种格式最好”我的回答是“看生态”。YOLO系训练就按YOLO txt来Mask2Former这类模型就准备COCO JSONmmrotate则要DOTA格式。数据格式不只是给人看的更是给数据加载器和Loss函数消费的接口。你选了模型却没选对对应格式后面每一步都会别扭。我整理了一个常用对应表任务类型主流标注格式关键字段与类型图像分类txt/csv类别索引int目标检测YOLO txt / COCO JSON / VOC XML坐标float类别int实例分割COCO JSON RLE/polygonmaskuint8 / string旋转目标DOTA txt8个坐标floatOCR自定list/JSON文本str字符索引int指令微调JSONLinstruction/outputstr格式一旦定下来数据协议也就有了雏形。这份业务层的“类型表”应该最先写进文档里而不是等到写代码时再拍脑袋。1.2 存储层的类型图像编码与数值类型一张JPEG图片在磁盘上其实是压缩后的字节流解码之后变成numpy数组。标准RGB图像是uint8类型shape为(H, W, 3)每个通道的取值是0到255。分割mask常用uint8或uint16每个像素值代表一个类别索引医学影像里还经常出现uint16甚至float32灰度比如CT值范围很大不能只用uint8表示。模型训练时如果直接把uint8图像丢给卷积层有些框架会直接报dtype错误因为权重是float32输入输出类型不一致。所以数据加载到内存后通常要除以255转成float32的0到1范围或者按数据集的mean/std做标准化。这里有个容易搞混的点磁盘存储格式和内存计算格式不是一回事。磁盘上可以继续存uint8的JPEG/PNG体积小、解码快内存里计算才转float32。有些工程喜欢把图片预处理成float32的npy文件缓存起来省去每次训练读图解码的开销但代价是磁盘占用直接变成原来4倍。我一般只在小数据集上这么做大数据集还是老老实实原始图片加实时预处理。1.3 计算层的类型框架张量与混合精度进入模型内部后默认计算精度是float32。权重、梯度、激活大多用float32稳定、不容易溢出。想要提速和减少显存时会做混合精度训练AMP一部分计算落到float16或bfloat16一部分保留float32。但是float16的动态范围很小容易上溢或下溢。所以AMP会做loss scaling先把loss放大反向传播得到梯度后再缩放回来。这也是为什么不能简单地把所有层都设成half()需要用torch.cuda.amp.autocast等机制来管理。除了图像标签张量的类型更是踩坑重灾区。比如CrossEntropyLoss的target必须用LongTensor不能是float坐标回归Loss通常要float32注意力mask一般用int64或bool。这些类型不一致是训练脚本里最常见的报错来源。所以说数据类型这个事贯穿三个层次业务层管“标的是什么”存储层管“数据怎么编码”计算层管“模型怎么算”。后面几节我按实际操作顺序展开。2. 从标注文件到内存张量类型设计与转换细节2.1 用 Python 组合类型组织训练样本写数据加载器时Python的list、tuple、dict是绝对主力。list保存所有样本路径dict保存标注信息tuple保存不可变样本set做id去重。热词里提到的“python的组合数据类型”在AI训练脚本里的意义就在这里。比如一个目标检测任务的自定义Dataset结构通常是self.images [] # list[str] self.labels [] # list[dict] self.class_names [...] # list[str]而__getitem__返回的样本我习惯用一个dict包起来def __getitem__(self, idx): return { image: image_tensor, # torch.float32, (3,H,W) boxes: boxes_tensor, # torch.float32, (N,4) labels: labels_tensor, # torch.int64, (N,) image_id: idx, # int }为什么box用float32、label用int64坐标要参与坐标变换、归一化和回归Loss全是浮点计算类别只是下标索引后面CrossEntropyLoss要求LongTensor。如果定义颠倒轻则报警告重则崩训练。在复杂一些的项目里我推荐用dataclass或TypedDict把样本结构固定下来from dataclasses import dataclass dataclass class DetectionSample: image_path: str boxes: list # [[x1,y1,x2,y2], ...] labels: list # [cls_index, ...]这样每个字段的类型一目了然后续写collate_fn、写可视化脚本也不容易出错。2.2 NumPy/Pandas/C/外部系统的类型转换标签和元信息很多时候存在CSV里。Pandas读进来常见object、int64、float64三种类型。训练前要执行astype转换比如将字符串类别列转成category类型再.cat.codes得到数值索引用pd.to_numeric把脏数据里的数字字符串统一转成数值。这些操作看着基础实际项目中90%的数据异常都是类型转换时不注意造成的。NumPy的dtype更加底层。uint8、float32、int64之间用.astype()切换但要记住大数组转换会复制一份内存峰值内存很容易翻倍。我之前处理一个几十GB的分割数据集时就是因为uint8mask转float32没分批做机器直接内存爆掉。C语言的数据类型存储在AI训练里也常遇到。写自定义C/CUDA算子时int在不同平台可能是4字节long可能是8字节结构体还有内存对齐。如果Python端和C端的字段大小对不上数据解析就会错位表现是“训练loss直接nan”或者“输出全是垃圾”。字节序也得注意x86小端网络传输大端跨端读写文件时经常踩坑。还有一些工业场景比如从PLC采集数据做AI训练变量表里的INT、DINT、REAL类型不搞清楚就解析得到的数据全是乱的。这部分大家平时聊得少但真碰到了很浪费时间。2.3 常见数据格式互转VOC、COCO、YOLO 的坐标类型目标检测最常做的互转就是VOC/COCO/YOLO三种格式。它们的类型定义差异很大YOLO txt每行class cx cy w h四个坐标都是归一化到图像宽高的float范围0~1。COCO JSONannotation里的bbox [x,y,w,h]是左上角绝对像素坐标float类别id一般从1开始。VOC XMLbndbox里是xmin,ymin,xmax,ymax通常是绝对整数像素。从VOC或COCO转成YOLO格式时最关键的公式是cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h如果原格式是COCO的[x,y,w,h]则要这样转cx (x w / 2) / img_w cy (y h / 2) / img_h w w / img_w h h / img_h很多标注工具导出的COCO类别id不是从0开始而是从1开始直接转给YOLO模型会默认从0开始硬生生多出一个背景类精度必然崩。还有一个隐藏坑坐标小数位保留几位也会影响小目标训练。YOLO格式的归一化坐标一般建议保留6位以上否则两个挨得很近的box可能变成同一条线。2.4 特殊场景的数据类型映射点云、工业采集、Redis现在多模态和自动驾驶模型越来越多比如BEVFusion这类工作要同时处理相机图像和雷达点云。点云坐标是float32强度是float32或int16时间戳是int64标定外参矩阵用float64。在矿山等场景做融合时雷达标定仪给的平移向量和旋转矩阵一旦精度不对融合到BEV空间后目标坐标全偏训练再久也没用。工业场景里采集设备出来的数据经常是二进制文件里面按结构体排列各种类型的字段。用Python解析时除了字段类型要对还要注意结构体对齐。如果有pack/unpack必须严格指定字节序比如小端、大端。另外很多分布式训练系统会拿Redis缓存数据集索引或样本状态这也要明确用哪种Redis数据类型。用String存JSON在样本量大的时候反复序列化和反序列化非常慢用Hash存每条样本的字段、用Set存已完成列表、用List做队列才是合理的类型选择。这里的数据类型已经不是模型张量但同样是训练数据链路的一部分。3. 五类训练任务的数据组织实例3.1 YOLOv8 目标检测从预处理到训练一条线YOLOv8是目前最常用目标检测模型之一网上资料多但很多人第一次训练自己的数据集还是会被数据组织绕晕。以YOLOv8训练自己的数据集为例标准目录结构如下datasets/mydata/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml其中data.yaml内容path: datasets/mydata train: images/train val: images/val names: 0: person 1: carYOLO标签txt的每一行是class_id cx cy w hclass_id是整数cx,cy,w,h是归一化后的小数。如果我用的是CV2读图必须记得cv2.imread读出来是BGR顺序而YOLOv8训练管线里默认按RGB处理所以读取后要转一下image cv2.imread(image_path) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB)训练命令倒是很简单yolo detect train datadata.yaml modelyolov8s.pt epochs100 batch16 imgsz640网上已经有人聊到yolov13这种新版本但不管版本号怎么变YOLO系列的数据协议基本没变一张图对应一个同名txt每行一个目标坐标全部归一化。只要把这一套组织好换模型版本、换规模都不用重做数据。3.2 旋转目标检测mmrotate 与 DOTA旋转目标检测和普通水平框最大的区别是目标可能带角度不能简单用xmin,ymin,xmax,ymax而是用四个角点表示。DOTA数据集是遥感场景的标杆标注txt每一行格式是x1 y1 x2 y2 x3 y3 x4 y4 class_name difficult四个坐标点是有顺序的通常按边界框的角点顺序排列模型预测后也要按同样顺序解码。如果用mmrotate训练DOTA数据集不能直接把原始大图塞进去要先按1024x1024的窗口把大图切块同时把落在切块边缘的目标框做过滤或保留处理。转换DOTA到mmrotate格式时还要过滤掉difficult为1的目标因为这类边缘目标算作“待定区”训练时不该参与损失。旋转框的中间表示通常还是float32但在做数据增强旋转时四个角点要乘旋转矩阵这里如果精度不足框和图像会错位。还有一点DOTA里图像非常大动辄几千像素直接训练会爆显存切图之后每个子图的尺寸变小数据量却翻了数倍类型和坐标均要保持一致。3.3 实例分割Mask2Former 的 COCO 与 RLEMask2Former属于掩码级预测模型训练数据通常是COCO格式尤其依赖panoptic或instance标注。COCO的annotation里segmentation可以是多边形列表也可以是RLE压缩字符串。如果直接用原始RLE字符串去算Loss肯定不行。必须先对它解码成二进制maskfrom pycocotools import mask as mask_util import numpy as np def rle_to_mask(rle_obj): return mask_util.decode(rle_obj).astype(np.uint8) # shape (H,W)解码后mask是uint8的(H,W)类别处是1背景处是0。进入模型后一般会转成float32并做下采样或者把target转成LongTensor做分类。注意mask和类别索引要对齐尤其是实例segmentation每个实例可能共享同一个类别但在模型输出里需要用instance_id区分。Mask2Former训练时还有一个坑num_queries、num_classes要严格按照数据集统计类别id不能有空洞。如果原始COCO里类别id从1开始而代码里num_classes不包含0就要重新映射。不然训练时候损失函数里的索引越界是常事。3.4 OCR 自定义模型EasyOCR 训练EasyOCR支持自定义训练但许多人误以为拿图片和文本字符串就能直接训练。实际上EasyOCR训练需要准备两条文本文件training_list和validation_list每行是图片路径,标注文本比如/train/001.jpg,Hello World /train/002.jpg,你好世界标注文本是字符串但训练时模型不能直接吃字符串。EasyOCR训练脚本会先建立字符集character把每个字符映射成索引。中文场景下如果没有把字符集设置完整验证集里的某个汉字不在训练字符集里loss会越界或输出乱码。所以做中文OCR时一般把语料里所有字符提前收集一遍生成char_list再统一编码。图片路径和文本之间也要注意逗号、空格等分隔符如果文件名本身带逗号解析就废了。这个问题我在整理公开数据集时踩过后来改成用制表符分隔就安稳了。3.5 大模型与 LoRA 微调JSONL 与 token 类型做大模型微调尤其是LoRA数据组织又是另一套路子。常见格式是JSONL每行一条独立样本{instruction: 写一首关于秋天的诗, input: , output: 秋风萧瑟天气凉草木摇落露为霜。}或者用更流行的对话格式{messages: [{role: system, content: 你是一个AI助手}, {role: user, content: 你好}, {role: assistant, content: 你好有什么可以帮助你}]}最近有公开的智能体训练新方法核心之一就是把多轮对话和工具调用过程结构化成messages列表让模型学习system、user、assistant角色切换。数据类型上原始数据全是字符串但经过tokenizer后会变成input_ids、attention_mask、labels三种张量前两个通常是int64labels和input_ids同形状。指令微调有一个关键细节Loss只计算输出部分prompt部分的labels需要置为-100训练时自动忽略。如果忘了处理模型会拼命学习复述问题表现就是“回答驴唇不对马嘴”。另外用LoRA加载基座模型时很多大模型checkpoint是用bfloat16存储的如果用float32强行加载显存直接翻倍很容易爆。这个dtype选择直接影响你能不能在单卡上跑起来。4. 数据类型踩坑清单与调试经验4.1 高频报错与排查速查表把这么多年在AI训练里遇到的典型类型问题整理成一张速查表遇到报错先来这里找。报错/现象原因解决方式Expected object of scalar type Long but got Float标签用了float将target转成labels.long()或读取时用np.int64Expected dtype float32 but found uint8图像没转float图片先img.astype(np.float32) / 255.0IndexError: index N is out of bounds类别id不连续或越界检查数据集中类别映射重新映射为0~N-1mask训练时全部为0或loss不下降RLE未正确解码用pycocotools先解码成uint8mask坐标训练loss很大坐标单位不统一检查yolo归一化/绝对像素是否混用文本训练乱码字符集缺失提前收集字符集并统一UTF-8CUDA illegal memory access数据错位导致越界检查Python端与C/CUDA的结构体/数组类型是否匹配显存比预期高很多全用float32加载大模型改用bfloat16/float16加载训练保持AMP4.2 dtype 影响显存和速度一个估算示例数据类型最直观的影响就是显存。以一张640x640的RGB图为例uint8存储640 * 640 * 3 1,228,800字节约1.17MB。转成float32后直接变成4倍约4.69MB。一个batch32的图片输入就是150MB还没算模型参数和中间激活。模型权重也一样。10亿参数模型float32需要约4GB内存bfloat16只占2GB。这就是为什么大模型微调普遍用bfloat16。混合精度训练的实际收益显存占用减半左右在Ampere及以上架构上训练速度提升明显但保留float32的master weight和优化器状态因此不是所有内存都减半。如果想要进一步压显存还可以用梯度累积、激活重计算、8bit优化器但这些都是后话。理解了类型位宽你才能算明白显存到底够不够。4.3 多传感器与跨语言数据类型的隐藏坑我做过一次多传感器融合项目相机图像、激光雷达点云、惯导数据各有各的时间戳和坐标系。图像是uint8点云是float32标定矩阵是float64时间戳是int64。一开始为了省事把标定矩阵用float32存了结果几米外的目标直接偏了一截。后来全部统一用float64保存外参训练数据才稳定。跨语言传输也常踩坑。C端写二进制文件时用结构体存储struct Label { int id; // 4 bytes float score; // 4 bytes float bbox[4]; };到了Python端如果用默认的Pythonint去对应Cint解析出来大概率对不上。正确做法是用numpy.dtype明确结构dtype np.dtype([ (id, np.int32), (score, np.float32), (bbox, np.float32, (4,)) ])这样做虽然麻烦但保证两边数据不会错位。在工控和物联网场景里PLC变量表读取也是同理INT是16位有符号DINT是32位有符号REAL是32位浮点。读错了轻则数值差一倍重则整个数据翻转。4.4 我的习惯先定义数据协议再写训练逻辑最后说一个我自己的习惯不管项目多急动训练脚本之前一定先把“数据协议”写出来。协议里明确每个字段叫什么、是什么类型、取值范围多大、坐标是绝对值还是归一化、掩码用什么编码、标签索引从几开始。这个协议可以是JSON Schema也可以是dataclass哪怕是Markdown表格都行。写清楚之后拿着标注文件和协议逐项对一遍再写Dataset和collate_fn。协议里最容易忽略的细节类别id是否从0开始坐标闭区间还是开区间图像通道是RGB还是BGR空标注文件怎么处理多标签时label维度是(N,)还是(N,num_classes)。我这几年遇到的项目事故绝大多数不是模型结构问题而是这些“类型细节”没对齐。有时候两组数据只是坐标归一化方式不一样合在一起没做统一训练出来的模型在A数据上正常在B数据上全瞎。先定义协议等于在代码和数据之间立了一份契约双方都按契约做事能省掉后期大量排查时间。这个习惯帮我避开了很多坑希望你少走几步弯路。
返回列表