BasicAI 标注数据质检实现方案

BasicAI 是全球首个开源多模态训练数据平台,通过提供 AI 赋能的软件工具、数万项目提炼的本体中心和丰富的数据治理特性,来加速多模态训练数据的处理效率,进而提高 AI 工程师的建模效率。
业务背景
在现今 AI 大模型流行的时代,大部分算法都是开源的,用于训练模型的标注数据的质量变得尤为重要。要想得到高质量的人工标注数据,需要对标注数据进行全方位质检,比如检查标注对象属性是否缺失、标注对象大小是否满足需求、标注对象重叠程度等。标注对象数据量非常大,一个包含 100 万个 Data 的数据集,假设每个 Data 标注出 30 个对象,那么就会产生 3000 万个对象,对于拥有成千上万数据集的 BasicAI SaaS,很容易就会达到百亿,甚至千亿级别。要对这么大的数据量进行分析,只能求助于大数据系统,然而传统的基于 Hadoop 生态的大数据系统过于复杂,不适合小团队和简单业务场景,经过调研分析,最终我们选择了 Doris 这款 OLAP 分析型数据库,主要基于以下原因:
简单
使用简单:Doris 兼容 MySQL 协议,支持标准 SQL,上手门槛低。Doris 的 ETL 编写脚本主要使用 SQL 进行开发,使用 MySQL 协议登陆使用,兼容多数 MySQL 语法,提供了丰富的数据分析函数,省去了许多 UDF 开发工作。
架构简单:Doris 组件只包含 FE 和 BE 两类进程,不依赖其它系统,升级扩容非常方便,故障排查链路很清晰,有利于降低运维成本。
高性能
依托于列式存储引擎、自动分区分桶、向量计算、多方面 Join 优化和物化视图等功能,Doris 可以覆盖众多场景的查询需求,海量数据查询也能保证低延迟,实现秒级或分钟级响应。
低成本
Doris 免费开源,其简洁的架构、协议的兼容、丰富的生态都能够为使用者节省不少资源和人力投入。
社区活跃
Doris 已开源数年,超千家企业在使用,社区也非常活跃,使用起来没有后顾之忧。
Doris 介绍
不同于 MySQL、TiDB 这样的面向小批量频繁读写的事务型数据库(OLTP),Doris 适用于需要大批量读写数据,并且读多写少的分析型场景(OLAP)。两者设计理念相左,一个采用行式存储,一个采用列式存储,虽然说 Doris 也使用 MySQL 协议,但其使用方式更接近于数据仓库,许多公司都将其用于构建实时数仓。
查询 & 分析
- 查询时指定尽可能多的前缀 Key 列来利用前缀索引;
- 不能用到前缀索引时可为条件列创建倒排索引(2.0 支持)或 BloomFilter 索引;
- 只读取需要的列而不是读取整行,如果确实需要高并发整行读取可对表开启行存(2.0 支持),以空间换时间;
插入 & 更新 & 删除
- 只有 Unique 模型才支持更新和删除;
- 避免使用
Delete
语句来删除数据; - 使用
Stream Load
、Broker Load
或Routine Load
来批量删除数据,也可以用于追加数据,或者两者同时进行; - 不推荐使用
Update
语句来更新数据,尤其是查询条件里使用 Value 列; - 避免并发执行
Delete
和Update
等 DML 语句,否则可能会出现脏数据;
技术方案

为了加速针对整个数据集或任务的全量质检,将预先计算好每个标注对象的各项指标(缺失属性数、最近距离、最远距离等),质检时基于这些指标来快速完成计算,同时还能够在多次质检之间复用大量中间计算结果。考虑到影响指标结果的源数据种类很多并且更新频繁,指标难以做到实时更新且保持口径统一,定期全量计算的代价又非常大,所以需要用户自行判断是否及何时更新指标库,比如选择在团队休息时间段进行。由于指标很难做到实时更新,因此无法用于工具里针对单个数据的实时质检,实时质检需要在应用内实时计算,前端工具提交结果的同时把相关指标预计算好,以便加速服务端计算。考虑到数据导入场景,服务端需要实现所有指标计算(不能依赖前端工具),其中涉及到边框、位置、大小等信息的交由算法完成(最近距离、最远距离、是否重叠等),其余的由应用负责(属性缺失数量等)。算法服务以 Sidecar 形式集成到应用服务 Pod 中,这样可以通过本地网络调用和文件共享来提升调用性能。
库表设计
标注对象表
保存所有标注对象(包括实例和分割)的详情,可用于对象检索。
表名:annotation_object
模型:Unique(写时合并)
分区:以 Dataset 创建时间按月分区
分桶:按 Data ID Hash 分桶,单桶大小控制在 1GB ~ 10GB,可基于历史分区来决定当前分区的桶数
列名 | 类型 | 聚合类型 | 评论 |
dataset_id | INT | 数据集 ID,用于分桶 | |
dateset_create_date | DATEV2 | 数据集创建日期,用于分区 | |
source_type | TINYINT | 来源类型,0 数据集,1 任务 | |
source_id | INT | 来源 ID,对于数据集为来源 ID,对于任务为任务 ID | |
data_id | BIGINT | 数据 ID | |
track_id | VARCHAR | 追踪对象 ID,只对实例对象有用 | |
object_id | CHAR(36) | 对象 ID | |
track_name | VARCHAR | NONE | 追踪对象名 |
annotation_type | VARCHAR | NONE | 标注类型,INSTANCE 实例标注,SEGMENTATION 分割标注 |
object_type | VARCHAR | NONE | 对象类型,比如 3D_BOX |
class_id | INT | NONE | 分类 ID |
class_name | VARCHAR | NONE | 分类名 |
class_values | JSONB | NONE | 分类属性值 |
instance_contour | JSONB | NONE | 实例对象轮廓信息 |
segment_device_name | VARCHAR | NONE | 分割对象设备名 |
segment_no | SMALLINT | NONE | 分割对象编号 |
create_at | DATATIMEV2 | NONE | 创建时间 |
- 如果需要按追踪对象粒度聚合,可使用 Group By,通过
ANY_VALUE
函数从多个值中任意取一个;
标注对象指标表
保存所有标注对象(包括实例和分割)的指标,比如用于质检。
表名:annotation_object_index
模型:Unique(写时合并)
分区:以 Dataset 创建时间按月分区
分桶:按 Data ID Hash 分桶,单桶大小控制在 1GB ~ 10GB,可基于历史分区来决定当前分区的桶数
列名 | 类型 | 聚合类型 | 评论 |
dataset_id | INT | 数据集 ID,用于分桶 | |
dateset_create_date | DATEV2 | 数据集创建日期,用于分区 | |
source_type | TINYINT | 来源类型,0 数据集,1 任务 | |
source_id | INT | 来源 ID,对于数据集为来源 ID,对于任务为任务 ID | |
data_id | BIGINT | 数据 ID | |
track_id | VARCHAR | 追踪对象 ID,只对实例对象有用 | |
object_id | CHAR(36) | 对象 ID | |
annotation_type | VARCHAR | NONE | 标注类型,INSTANCE 实例标注,SEGMENTATION 分割标注 |
object_type | VARCHAR | NONE | 对象类型,比如 3D_BOX |
class_attr_missing_count | SMALLINT | NONE | 分类属性缺失计数 |
class_attr_required_missing_count | SMALLINT | NONE | 分类必需属性缺失计数 |
min_distance | FLOAT | NONE | XY 平面上距离原点的最近距离,只对点云标注有用,对于实例标注只考虑关键点,对于分割标注需考虑所有点 |
max_distance | FLOAT | NONE | XY 平面上距离原点的最远距离 |
min_height | FLOAT | NONE | Z 轴上的最低高度,只对点云标注有用,对于实例标注只考虑关键点,对于分割标注需考虑所有点 |
max_height | FLOAT | NONE | Z 轴上的最高高度 |
is_overlapped | BOOLEAN | NONE | 是否跟其它对象重叠 |
is_match_fixed_size_constraint | BOOLEAN | NONE | 是否符合固定大小限制 |
is_match_ontology_constraint | BOOLEAN | NONE | 是否符合本体约束 |
create_at | DATATIMEV2 | NONE | 创建时间 |
标注对象质检结果表
记录违反每条质检规则的标注对象。
表名:annotation_object_qa_result
模型:Unique(写时合并)
分区:以 Dataset 创建时间按月分区
分桶:按 Data ID Hash 分桶,单桶大小控制在 1GB ~ 10GB,可基于历史分区来决定当前分区的桶数
列名 | 类型 | 聚合类型 | 评论 |
dataset_id | INT | 数据集 ID | |
dateset_create_date | DATEV2 | 数据集创建日期,用于分区 | |
job_type | TINYINT | 任务类型,0 数据集,1 任务 | |
job_id | INT | 任务 ID,对于数据集为 Job ID,对于任务为任务 ID | |
violation_rule_id | INT | 违反规则 ID | |
data_id | BIGINT | 数据 ID | |
track_id | VARCHAR | 追踪对象 ID,只对实例对象有用 | |
object_id | CHAR(36) | 对象 ID | |
create_at | DATATIMEV2 | NONE | 创建时间 |
以数据为粒度上卷
列名 |
dataset_id |
job_type |
job_id |
data_id |