ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

FiftyOne Recipes 实战手册:从数据加载到去重的完整工作流指南

2026/9/16 14:20:42 拓冰建站 浏览量
FiftyOne Recipes 实战手册:从数据加载到去重的完整工作流指南 FiftyOne Recipes 实战手册从数据加载到去重的完整工作流指南【免费下载链接】fiftyoneRefine high-quality datasets and visual AI models项目地址: https://gitcode.com/GitHub_Trending/fi/fiftyoneFiftyOne 是面向高质量数据集与视觉 AI 模型的开源工具其核心价值在于把原本需要数小时脚本才能完成的 ML 工作流压缩到几分钟。本文以官方 docs/source/recipes/index.rst 索引的 Recipes 系列为骨架系统讲解视图与表达式查询、Torch 数据加载、数据集合并、格式转换、自定义导入导出与解析器、标签绘制以及去重等高频工作流并辅以仓库源码佐证底层实现帮助你在实际项目中直接套用。Recipes 体系概览docs/source/recipes/index.rst是 FiftyOne 文档中 Recipes 栏目的入口页它用卡片 隐藏 toctree 的形式汇总了当前仓库维护的十余个 Notebook 级教程覆盖三大主题域主题域Recipes核心能力基础 / 数据集整理creating_views.ipynb、image_deduplication.ipynb、remove_duplicate_annos.ipynb视图查询、图片去重、对象级去重模型训练fiftyone_torch_dataloader.ipynb、adding_classifications.ipynb、adding_detections.ipynb、torch-dataset-examples 目录下的 MNIST 训练示例把 FiftyOne 数据接入 PyTorch 训练/评估管线I/O 与可视化convert_datasets.ipynb、merge_datasets.ipynb、custom_importer.ipynb、custom_exporter.ipynb、custom_parser.ipynb、draw_labels.ipynb格式转换、数据集合并、自定义格式读写、标注渲染这些 Notebook 的工程化支撑位于仓库根目录Python 核心实现在 fiftyone/core数据集、视图、集合与 fiftyone/utilstorch、iou、annotations、data 等工具CLI 入口在 fiftyone/core/cli.py。下文按“查询 → 建模 → 数据组织 → 标注可视化”的主线逐一展开。用视图Views与表达式View Expressions查询数据集视图的本质作用于数据集的流水线在 creating_views.ipynb 中视图被定义为“作用于数据集的一组操作流水线”对数据集执行返回DatasetView的操作即创建视图最简单的形式是dataset.view()得到包含全部样本的视图。视图与数据集在绝大多数操作上可互换——对数据集能做的一切对视图同样适用。所有可用视图阶段的清单可通过dataset.list_view_stages()打印其实现见 fiftyone/core/collections.py 中的list_view_stages()类方法它枚举了当前集合上所有返回ViewStage操作的方法名。常用阶段包括take(n)随机抽取 n 个样本sort_by(field)按字段排序skip(k)/limit(n)跳过 / 截取切片语法view[10:51]等价于view.skip(10).limit(41)match(expr)逐样本按表达式真假保留/剔除set_field(path, expr)在视图内修改字段不落盘到原数据集但需保证字段已存在于 schemafilter_labels(field, expr)按表达式过滤标签列表字段详见下文。阶段可以链式串联例如先随机取 100 个样本再排序再切片random_view dataset.take(100) sorted_random_view random_view.sort_by(filepath) sliced sorted_random_view.skip(10).limit(41)ViewExpression以F编写自定义查询F是fiftyone.core.expressions.ViewField的别名用于把数据集某字段的值注入表达式。它遵循与 Pythonif语句相同的真值语义F(eval_tp) 4、F(eval_tp)非零即真、~F(eval_tp)取反均合法。嵌套列表是表达式的难点三种关键操作实现于 fiftyone/core/expressions.pyfilter(expr)对列表逐元素应用布尔表达式保留满足条件的元素结果为列表map(expr)对列表逐元素应用函数结果为列表如F(tags).map(F().upper())把所有标签转大写空F()引用当前上下文根reduce(expr, init_val)把列表归约为单值。配合fiftyone.core.expressions.VALUE可做累加例如提取所有eval tp的匹配对象 IDfrom fiftyone.core.expressions import VALUE view ( dataset .set_field( predictions.gt_ids, F(detections) .filter(F(eval) tp) .reduce(VALUE.append(F(eval_id)), init_val[]) ) )引用文档根字段字段名加$前缀可引用所在文档根部的值适用于在 detection 级过滤时访问 sample 级信息。例如计算每个框的像素面积并筛选面积介于 32²96² 的框dataset.compute_metadata() bbox_area ( F($metadata.width) * F(bounding_box)[2] * F($metadata.height) * F(bounding_box)[3] ) medium_boxes_view dataset.filter_labels( predictions, (32 ** 2 bbox_area) (bbox_area 96 ** 2) )聚合与复杂视图实战聚合Aggregations是视图表达式的自然延伸dataset.list_aggregations()可查看全部可用聚合dataset.distinct(predictions.detections.label)直接对字段名聚合而dataset.distinct(F(uniqueness).round(2))则先求值表达式再聚合。Recipes 中给出的复杂视图示例把切片、过滤、匹配与裁剪组合在一起from fiftyone import ViewField as F complex_view ( dataset .filter_labels( predictions, (F(confidence) 0.7) ((F(bounding_box)[2] * F(bounding_box)[3]) 0.3) (F(eval) fp) (F(label).is_in([person, dog])) ) .match(F(predictions.detections).length() 2) .to_patches(predictions) )注意filter_labels默认only_matchesTrue即过滤后自动剔除标签为空的样本等价的手写形式是set_field过滤列表后接.match(F(...detections).length() 0)。用 FiftyOneTorchDataset 把数据集接入 PyTorchGetItem定义样本到模型输入的映射GetItem 是 FiftyOne 定义“如何为模型加载输入”的接口required_keys声明所需字段键__call__把样本字典变换为模型输入。键名不要求与数据集字段同名——通过field_mapping参数即可把键映射到任意字段从而实现同一个GetItem跨数据集复用from fiftyone.utils.torch import GetItem, FiftyOneTorchDataset class DetectionGetItem(GetItem): property def required_keys(self): return [filepath, detections_field] def __call__(self, sample_dict): return { filepath: sample_dict[filepath], labels: [det.label for det in sample_dict[detections_field].detections], boxes: [det.bounding_box for det in sample_dict[detections_field].detections], } # 同一个 GetItem 分别读取 ground_truth 与 predictions gt_dataset dataset.to_torch( DetectionGetItem(field_mapping{detections_field: ground_truth}) ) pred_dataset dataset.to_torch( DetectionGetItem(field_mapping{detections_field: predictions}) )构造与使用 FiftyOneTorchDataset对数据集或视图调用.to_torch(get_item)即得到FiftyOneTorchDataset它是标准torch.utils.data.Dataset可直接交给DataLoader。其核心实现见 fiftyone/utils/torch.py 中FiftyOneTorchDataset类构造参数index_fieldid、vectorizeFalse、skip_failuresFalse、local_process_groupNoneindex_field决定数据集的“行”默认id即每样本一行若指向列表型字段如ground_truth.detections.id则每个框/帧/折线展开为独立行且field_mapping中的子字段按行取值、父字段共享vectorizeTrue时训练前一次性把所需字段预载入内存并缓存消除训练循环中的数据库查询开销代价是内存需容纳整个集合的字段值且值必须可 pickle 序列化对应 Recipe the_cache_field_names_argument.ipynb 的加速主题skip_failuresTrue时__call__抛出的异常会作为返回值返回而非中断。DataLoader 的标准用法注意worker_init_fndataloader torch.utils.data.DataLoader( torch_dataset, batch_size8, worker_init_fnFiftyOneTorchDataset.worker_init, )当每个样本的检测框数量不固定时无法直接 stack 成张量需要自定义collate_fnRecipe 中把filepaths、labels、boxes分别收集成列表返回保证 batch 语义正确。MNIST 训练完整示例torch-dataset-examples 目录提供了可运行工程mnist_training.py 演示了完整的训练闭环——用utils.create_dataloaders按train/validation/test三个 tag 切分数据集并创建 DataLoader训练中记录每 epoch 的验证损失并保存最优权重测试阶段把loss与predictions写回 FiftyOne 样本最后用evaluate_classifications输出 Top-3 分类报告results dataset.match_tags(test).evaluate_classifications( predictions, gt_fieldground_truth, eval_keyeval, classesclasses, k3, ) results.print_report(classesclasses)simple_training_example.ipynb与basic_example.ipynb提供交互式版本ddp_train.py展示分布式训练场景与local_process_group参数配合utils.py封装了模型、优化器与 DataLoader 工厂。数据集合并merge_samples 实战merge_datasets.ipynb 演示两种典型场景合并同一批底层媒体的多份标注信息以及把模型预测并入已有数据集以对比 ground truth。其 API 入口是Dataset.merge_samples()签名见 fiftyone/core/dataset.py 的merge_samples方法。基本流程先用foz.load_zoo_dataset(cifar10, splittest)加载数据把推理结果写入独立fo.Dataset仅含filepath与predictions字段再合并进主体数据集dataset2 dataset.exclude_fields(predictions).clone(namemerge-example2) dataset2.merge_samples(predictions) print(dataset2.exists(predictions)) # 验证 100 个样本已带上预测合并键定制默认按绝对filepath合并可通过key_fcn自定义例如按基文件名合并key_fcn lambda sample: os.path.basename(sample.filepath) dataset3.merge_samples(predictions, key_fcnkey_fcn)其余关键参数源码 docstring 中均有说明参数默认值作用key_fieldNone指定用于匹配的字段名与key_fcn二选一skip_existingFalse是否跳过已存在的样本而不修改insert_newTrue是否把新样本插入目标数据集fields/omit_fieldsNone仅合并指定字段 / 排除指定字段merge_listsTrue列表字段按元素合并而非整体覆盖merge_embedded_docsFalse嵌入文档是否递归合并overwriteTrue是否覆盖目标数据集中已存在的值expand_schemaTrue是否允许向目标数据集 schema 添加新字段dynamicFalse是否允许为合并数据创建动态字段需要注意merge_samples要求在key_field上建立唯一索引None值字段在合并中一律视为缺失标签列表字段中id相同的标签会被更新而非重复。若只需简单地把一个集合追加进另一个数据集而无需唯一性约束应改用add_collection。数据集格式转换fiftyone convert 命令行convert_datasets.ipynb 展示如何用 CLI 在磁盘上完成格式互转。转换的核心是fiftyone convert命令用法为指定输入/输出目录与fiftyone.types中的数据集类型INPUT_DIR$(fiftyone zoo datasets find cifar10 --split test) OUTPUT_DIR/tmp/fiftyone/cifar10-dir-tree fiftyone convert \ --input-dir ${INPUT_DIR} --input-type fiftyone.types.FiftyOneImageClassificationDataset \ --output-dir ${OUTPUT_DIR} --output-type fiftyone.types.ImageClassificationDirectoryTree上述命令把 CIFAR-10 test splitFiftyOne 原生分类格式可通过fiftyone zoo datasets info cifar10验证转为按类别分目录的目录树dataset_dir ├── classA/ │ ├── image1.ext │ └── ... └── classB/继续链式转换目录树 → TFRecordsfiftyone.types.TFImageClassificationDatasetKITTI 检测数据集 → COCO 格式fiftyone.types.COCODetectionDataset输出data/图片目录 labels.json→ CVAT Image 格式fiftyone.types.CVATImageDataset输出labels.xml。Recipe 中给出的内置格式支持矩阵覆盖 Image/Video/Media Directory、Image Classification Directory Tree、TFImageClassificationDataset、FiftyOneImageClassificationDataset、FiftyOneImageDetectionDataset、COCODetectionDataset、CVATImageDataset 等Import / Export / Conversion 三列能力各不相同在此基础上还可以定义自定义数据集类型。自定义 I/OImporter、Exporter 与 SampleParser自定义 DatasetImporterDatasetImporter定义Dataset.from_importer()等方法的导入行为抽象接口具体实现取决于数据集类型见 fiftyone/utils/data/importers.py。Recipe 编写了一个LabeledImageDatasetImporter子类CSVImageClassificationDatasetImporter用于导入labels.csv格式的分类数据集CSV 列依次为filepath,size_bytes,mime_type,width,height,num_channels,label filepath,size_bytes,mime_type,width,height,num_channels,label导入只需一行importer CSVImageClassificationDatasetImporter(dataset_dir) dataset fo.Dataset.from_importer(importer)样本生成侧Recipe 用write_csv_labels工具把 CIFAR-10 样本的 metadata先调用compute_metadata()与ground_truth标签写成 CSV。完整的CSVImageClassificationDatasetImporter类定义见 custom_importer.ipynb。自定义 DatasetExporter对称地DatasetExporter定义Dataset.export()的导出行为见 fiftyone/utils/data/exporters.py。CSVImageClassificationDatasetExporter继承LabeledImageDatasetExporter把数据集导出为dataset_dir/ data/ filename1.ext ... labels.csv用法exporter CSVImageClassificationDatasetExporter(export_dir) samples.export(dataset_exporterexporter)自定义 SampleParserSampleParser定义add_labeled_images()/ingest_labeled_images()解析样本的行为见 fiftyone/utils/data/parsers.py。PyTorchClassificationDatasetSampleParser继承LabeledImageSampleParser从发射(img_tensor, target)元组的DataLoader中解析样本其中target是类别整数索引、classes列表负责映射为标签字符串。由于它声明has_image_path False图像直接以张量存在于内存必须使用ingest_labeled_images把图像写入磁盘sample_parser PyTorchClassificationDatasetSampleParser(classes) dataset.ingest_labeled_images(data_loader, sample_parser, dataset_dirdataset_dir)若强行调用需要文件路径的add_labeled_imagesFiftyOne 会抛出信息明确的错误——这是解析器能力声明has_image_path/has_image_metadata属性在运行时被强制校验的体现。标注渲染一行代码把标签画到图像与视频上draw_labels.ipynb 演示view.draw_labels(output_dir, configconfig)的用法底层由 fiftyone/utils/annotations.py 的draw_labeled_images/draw_labeled_videos驱动DrawConfig用于定制渲染外观如per_object_label_colors、font_size。三个场景示例COCO 检测foz.load_zoo_dataset(coco-2017, splitvalidation)后取limit(100)以{per_object_label_colors: True}渲染 ground truth 框Caltech 101 分类foz.load_zoo_dataset(caltech101, splittest)以{font_size: 36}渲染类别文本视频帧级标注foz.load_zoo_dataset(quickstart-video)取 2 个随机视频把objs帧级检测渲染为带标注的视频。绘制完成后可用!ls -lah anno_dir核对输出Recipe 示例输出见 draw_labels_coco2017.jpg 与 draw_labels_quickstart_video.gif。去重工作流图片级去重与对象级去重图片去重文件哈希image_deduplication.ipynb 以 CIFAR-100 的 1000 张子集其中 5% 被替换为重复样本构造脚本见 image_deduplication_helpers.py为例加载fo.Dataset.from_dir(dataset_dir, fo.types.ImageClassificationDirectoryTree, ...)读取按类别分目录的图片计算哈希遍历样本写入file_hash字段fiftyone.core.utils.compute_filehash。注意默认方法返回的整数可能超过 JavaScript 的安全整数上限 2⁵³−1若需要在 App/JS 中展示应改用methodsha1生成字符串哈希定位重复collections.Counter统计出现次数 1 的哈希再用.match(F(file_hash).is_in(dup_filehashes)).sort_by(file_hash)让重复项相邻删除遍历dup_view对每个哈希保留首个样本、删除其余del dataset[sample.id]导出dataset.export(label_fieldground_truth, export_dirEXPORT_DIR, dataset_typefo.types.FiftyOneImageClassificationDataset)之后可随时fo.Dataset.from_dir重新加载。对象级去重IoUremove_duplicate_annos.ipynb 面向“同一张图内重复的检测框”核心工具在 fiftyone/utils/iou.py方法一compute_max_ious()计算重叠度。为每个对象写入max_iou属性同类对象之间、同图内计算classwiseTrue重复对象必然与另一对象有高 IoUimport fiftyone.utils.iou as foui foui.compute_max_ious(dataset, ground_truth, iou_attrmax_iou, classwiseTrue) print(Max IoU range: (%f, %f) % dataset.bounds(ground_truth.detections.max_iou)) dups_view dataset.filter_labels(ground_truth, F(max_iou) 0.75)之后两种人工复核路径在 App 中给重复标签打duplicatetag 后dataset.delete_labels(tagsduplicate)或通过dups_view.annotate(anno_key, label_fieldground_truth, launch_editorTrue)上传 CVAT 编辑完成后dataset.load_annotations(anno_key)回读。方法二find_duplicates()自动检测。直接返回重复标签 IDdup_ids foui.find_duplicates( dataset, ground_truth, iou_thresh0.75, classwiseTrue )该方法提供两种策略源码 docstring 有明确定义methodsimple默认遍历每对 IoU 超阈值的标签并把后者标记为重复可能标记更多methodgreedy贪心标记“最少数量的标签为重复”使得剩余非重复标签之间 IoU 均不超阈值计算开销更高。由于“谁是正确的、谁是重复的”存在固有歧义官方建议先dataset.select_labels(idsdup_ids).tag_labels(duplicate)打标再用dataset.match_labels(idsdup_ids)在 App 中人工确认最后dataset.delete_labels(tagsduplicate)或dataset.delete_labels(idsdup_ids)执行删除。小结从 recipes/index.rst 展开的这套 Recipes 覆盖了视觉 ML 工作流中最常遇到的五类任务视图/表达式查询F、filter/map/reduce、$root引用、PyTorch 数据接入GetItemFiftyOneTorchDataset含vectorize加速与 DDP 支持、数据集合并merge_samples及合并键定制、格式互转fiftyone convert、自定义 I/OImporter/Exporter/SampleParser 三接口以及标签渲染与两级去重。所有示例均可在本仓库的 docs/source/recipes 目录下找到可运行的 Notebook 与配套脚本Python 侧实现集中在 fiftyone/core 与 fiftyone/utils 两个目录可作为你进一步定制工作流的参考起点。【免费下载链接】fiftyoneRefine high-quality datasets and visual AI models项目地址: https://gitcode.com/GitHub_Trending/fi/fiftyone创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考