如何设计一个数据标注系统

举报
HW007 发表于 2023/02/25 16:27:32 2023/02/25
【摘要】 本文从一个最简单的标注系统出发,陆续引入新的功能场景(如“物体检测”、“语义分割”、“实例分割”、“视频分割”等等),对整个标注系统的模型进行调整和拓展,得到了最终的系统领域建模图,并在整个领域模型的调整的过程中,对一些可能遇到的决策点进行了探讨分析,给出了一些笔者个人的思考。

通过学习大量标注数据来得到知识,是机器学习、深度学习等人工智能类系统中的常见套路。这种方式引入了一种全新的“Data as Code”的编程模式,降低AI应用的开发门槛。你不需要了解AI,甚至也不需要了解计算机编程,只要会标注数据,便能开发出一个简单的AI应用。

Andrew Ng 在其《MLOps: From Model-centric to Data-centric AI》https://v.qq.com/x/page/j3236lmb9su.html 的演讲中指出,在深度模型的训练过程中,有时优化训练数据比优化模型要更有效。数据就像整个AI系统的食材,在整个AI应用的开发过程中,用于准备、标注、清洗数据的时间甚至能占到80%,以至于不少AI工程师戏称自己为“数据清洗工”。

图1:调整模型代码和调整训练数据对AI应用的效果提示比对

图2:准备数据和准备模型在AI应用开发过程中的占比

鉴于上述的高质量的数据在AI应用开发中的重要性,本文将从零开始,一步一步地分析构建出一个高效的AI标注系统(仅限于计算机视觉标注)。

1. 最简单的标注系统

下图是一个最简单的数据标注样例。

图3:图像分类型标注任务示例(标注:小猫)

由图可知,最简单的数据标注流程便是:给定一张图片,由标注者指出该图片里面的内容是什么。由此,可得标注系统中最基本的两个实体元素:“数据(data)”以及“标注(anotation)”。

然而,仅有“数据”和“标注”两种元素是否能构成一个高效的数据标注系统呢?

思考一个这样的问题,对上述那张小猫的图片,有人会标注为”小猫“,有人会标注为”花猫“,对于人来说,能很容易理解到这两个标签的语义都是”猫“,但对于计算机来说,它只能从标注的字面值上理解到”小猫“是不等于”花猫“的, 进而认为两者不是同一类东西。由此可知,对于要交给计算机来处理的数据,需要做到结构化和规范化。也即,我们需要引入一个新的元素:”标签(label)“来做”标注(anotation)“的规范化。也即在上述的例子中,先定义一个标签集:{“猫”,”狗“},然后要求标注员一定要用标签集里的标签来对数据进行标注,这样最终得到的标注数据便是规整的。

如果从语言学的角度来看,上述数据标注系统,要解决的核心问题就是,怎样描述一个数据是什么并保存起来. 上文中采用了”XXX(Data) 是 XXX(Label)"这样的一个简单的”主系表“基本句型来建模。这个句式基本能cover住一般的计算机视觉分类任务的。由此,可得本数据标注系统的第一版数据建模如下图:

图4:数据模型 v1

2. 支持物体检测、语义分割标注

接下来,我们来看一些稍微复杂点的标注任务,首先来个稍微复杂点的物体检测数据样例:

图5:物体检测型标注任务

由上图可知,只用”XXX 是 XXX“这个简单的模式,无法完整地描述物体检测任务的标注。主要原因在于,物体检测任务的标注比图像分类任务的标注的语义信息更丰富,要求指出图像(Data)中某个局部区域物品(region)是什么类别(Label)。

标注的描述方式转变成:

“在 XXX(Data)中, XXX(Region)是 XXX(Label)”

“在 XXX(Data)中有 XXX(Label),在图中的区域为 XXX(Region)”

对应地,有两种数据模型调整方案,如下所示(以中间虚线分隔):

图6:数据模型 v2

1)上图中左边的数据建模方案,引入一个“Region”的概念,由其来充当新的主语,“Data”退居状语的位置。

2)上图中右边的数据建模方案,给“Annotaiton”扩展一个“region”的属性来记录物体检测任务中矩形框的边界点,同样地,“Data”也推退居状语位置。

上述两个方案在功能上是等效的,差异点在于组织形式上,“Region”需要建模成一个独立的概念,还是建模成依附于“Annotation”的一个属性。这个比较难决策的点。尽管在一个“Region”可以对多个“Label”的情况下,左边的建模方案在数据存储是否冗余的角度会优于右边方案。但笔者还是觉得从整个系统的语义上看,“Region”的出现是对“Annotation”的一种补充说明,暂时还没有看到它能逃脱“Annotation”的独立语义职责(如“Data”在系统中担任了说明数据存放在哪的职责;“Label”在系统中承担了对“Annotaion”的规范约束的职责),所以在这里笔者比较倾向于选择右边的方案,“Region”是“Annotaion”的一个属性。

从这里也可以发现,单从图像分类任务扩展到图像检测任务时,之前数据模型中的”Data“从主语变为状语是没有争议的。主要是后者对图片内容的描述更加准确,功能也更加强大。

接下来看下调整后的数据模型是否能支持语义分割型的标注任务:

图7:语义分割型标注任务

对比图5和图7,可以发现,语义分割任务和物体检测任务的区别在于,一个图中的标注区域是比较规整的多边形(如图5的矩形),另一个是不规则的图型。不管这个标注区域是否规则,其都可以通过其边界线的一系列点集合表示,故上述调整后的模型是能支持语义分割任务的。

但是,从上述语义分割任务的标注流程中,我们稍微拓展下,便可发现用于标注的区域的“形状”可能是个比较容易出现变化的地方,如可能出现“三角形”、“矩形”、“五边形”、“六边形”、“立方体(3d 点云数据)”等。

除上述标注区域的形状外,区域的存储的形式也是一个容易出现变化的点,比如,无论是物体检测还是语义分割,其区域都还有另一种表示形式,即逐像素存储。虽然这种存储方式要比只存储区域边界要费更大的空间,但其表现力更强。如边界描述法无法在一个实例中描述多个不联通的区域,而逐像素可以。

加上上述分析中对“标注区域”这个概念在“形状”和“存储”上的拓展,可得新的数据模型图如下:

图8:数据模型 v3

如上图,在这次模型调整中,我们从“Annotation”类派生出三个子类,分别对应“整图标注(TagAnnotation)”、“画框标注(ShapeAnnotation)”、“逐像素标注(MaskAnnotation)”三种情况。同时引入一个“Shape”的概念,辅助描述“ShapeAnnotation”类的属性。

这里有一个值得思考的问题:有没有必要衍生出子类?如“Shape”类派生出来的“Triangle”、“Rectangle”类的功能和数据存储基本上都可以被“Polygon”类覆盖到,派生这么多类是不是有点欲赋新词强说愁?

这里笔者是这么认为的,首先这是对系统进行面向对象建模,重点在于对系统概念、元素、各个元素的关系等的识别和挖掘。如图中的“TagAnnotation”和“Annotation”类,尽管经过分析,他们的成员属性是一样的,在系统建模图上,“TagAnnotatio”还是要被派生出来,因为他代表了我们对“整图标注”这个case的处理方案,缺少了这个类,系统的设计就不完整了。同样地,对“Rectangle”和“Polygon”,尽管数据成员是一样的,但从系统交互上来看,虽然“Rectangle”有4个顶点,但由于他的平行性质约束,只需要2个点便能描述,这就是他和“Polygen”的不同之处,我们有必要把这个特殊点在系统建模时标识出来。

由此看来,上述的派生子类、概念分层的做法在系统建模的时候都是毋庸置疑的,大胆去画便是了。但在这里面还有一些比较让人纠结的地方,就是在将上述的系统建模图映射成数据库表或软件系统时,会有比较多的选择。如“Shape”派生出来的子类是各自一张表还是共用一张表,再由“type”字段来区分各个子类?如“ShapeAnnotation”和“MaskAnnotation”是可以等价转换的,是不是数据库只为“ShapeAnnotation”构建表就好,“MaskAnnotation”在都转化为“ShapeAnnotation”再入库就好?等等… … 这些涉及到具体实现的选择,反而比较磨人,只能 case by case 去分析和决定。

在这里,软件系统建模经验比较丰富的人,也许能闻到一点“领域驱动建模(DDD)”的味道。稍微扩展点,下图是传统的分层架构和六边形架构的对比图:

图9:分层、六边形、简洁架构

如上图,在传统分层模型中,代表数据库的基础层位于最底层。而在DDD的六边形架构中,位于最里层的是领域模型,数据库层反而作为一种基础设施挂在最外层。软件系统的构建就像盖房子,万丈高楼平地起,位于最底(里)层的地基是最重要的。我们上面构建的系统模型,其实就是DDD的领域模型,也就是六边形架构里面的最里层——领域模型层。从上面的分析中,我们也可以看到,对系统进行领域建模时,是不需要考虑系统的数据库设计的,有概念抽取大胆去抽取、有概念分层大胆去分层,这个阶段追求的是系统建模的准确、全面和可扩展。至于数据库表的设计,如哪些类可以独立映射成表,哪些类可以一起映射成表,哪些列可以转化成别的类再入库等等。这些问题看似纠结和棘手,似乎每种选择都是对的,需要做 case by case 的分析,有时即使 case by case 分析,也没法彻底排除掉某些选择,这时最好的办法就是随意选择一种就好,即使后面发现选错了,只要其基于的领域模型没有变化,各种选择之间的切换也不会很麻烦。这也是使用DDD的六边形架构来做系统分析建模优于传统的分层架构的地方。

值得指出的是,其实在上文中出现了一次比较危险的“领域”模型发生变化的地方,就是在第一版中把“Data”建模成了主语性质的,在第二版中才发现“Data”应该是状语性质的,好在后面的建模演进能平滑掉这个调整。在这里想一下,如果在第二版中没有把“Data”转为状语,还是坚持把“Data”当成主语,整个系统结构估计会更复杂和别扭,不会像现在般简洁明了。

3. 支持实例分割标注

接下来,看下我们上述的的系统建模能否支持实例分割标注。

图10 实例分割

由上图可知,实例分割在和语义分割很相似,只是在”标签“上出现了层级结构,而在上面我们构建的系统中,”标签(Label)“是单层的。

但是单层的标签是可以表述多层级标签的。举个例子:当被要求对足球赛画面进行标识,且要求标注出球、球员(精确到人)等时,{“球”,”球员-A队-球员1“,”球员-A队-球员2“,”球员-B队-球员2“} 这样一套扁平化的标签就能解决语义上有层次的问题。这种解决思路有点像编程中的序列化问题,任何类型的实例都可以被序列化为字节序列(string),string是扁平的一层,但里面含有的信息可能是有层次结构的。

由上分析,我们当前的数据标注系统是可以支撑住实例分割这种类型的标注任务的,尽管支撑方式看起来有点”消极“,但这恰恰是软件系统建模中最重要的点:划分系统边界的问题。就像一部电影,如果在宣传上说这是一部融合了搞笑、悬疑、爱情、文艺、亲情、科幻、动作、青春、恐怖等元素的电影,那这很可能是一部烂片了。笔者认为,学习和了解更多的知识和技法,不是为去做多,去堆叠,而是为了去做少,知道在哪个地方能更优雅地解决问题,恰到其处。

所以,对一个标注系统的标签是否需要支持标签有层级?从足球赛标注的案例中,我们可以看到:

1)如果不支持,标签语义上的层级信息会被用户嵌入到标签的文本结构或语义中,如用”-“符号来连接层级,用户会自己去设计管理自己标签的结构和规范,这个解决方案从”标注系统“的视角来看是完备的。因为在这个建模中,标签的设计和管理不属于本系统的边界内,而本系统与外部的这个标签系统的对接是”string“,是一个通用的数据类型,可以cover住所有的情况。

2)如果支持,首先要考虑的是,我们能否完备地解决这个标签层次的问题?如具体支持多少层比较合适?是否支持无限层?…回到上述足球赛标注的例子,{“球”,”球员-A队-球员1“,”球员-A队-球员2“,”球员-B队-球员2“} 是这个标注场景的标签的解决方案;{“球”,”球员A1“,”球员A2“,”球员B2“,”A队“,“B队”} 也是一种可行的解决方案。具体选哪种方案,得看用户的业务诉求和习惯。如前面提到的,数据标注任务,本质就是用自然语言去描述标注对象,”这是一扇关着的门“和”这是一扇门,门是关着的“这两描述是等价的,只是表达的组织形式不一样,且这两种表达方式都准确地传达了意思,没有优劣之分。由此看来,数据标注系统很难给出一套较优的通用标签分层组织方案或者方法论。

由上分析的角度,似乎通过引入标签分层,解决不了用户标签的建模问题,不引入系统功能反倒还能聚焦点,那是否就可以暂时先不引入标签分层体系?我们可以尝试从另一个角度来分析下:如果提供了标签分层机制,是否造成了原有用户的损失?很明显,即使提供了标签分层机制,想用单层标签的还是可以照旧使用,反倒是有标签分层诉求的可以使用分层机制了,这样整个标注过程会直观很多,因为分层标签在逐层选择的过程可以提前筛掉一些无关标签,在整个标签集很大的情况下能提效不少。

综上,在我们的系统中引入标签分层机制是有利无弊,为此,调整系统模型图如下:

图11:数据模型 v4

如上图,在系统建模中,我们引入了一个“AttributeSpec”这个类来对“Label”类进行补充说明,形成了“Label-Attribute”这样的类似双层结构。再引入了一个“LabelAttributeVal”来作为实际标注中标签取值的实例。熟悉“json”的同学可能发现了,其实上图中紫色的两个类“Label”和“AttributeSpec”一起承担了“json schema”的功能,而“LabelAttributeVal”便是对应的那个“json”的实例值。在这个模型中,当“LabelAttributeVal”类中的value属性为空时,整个模型蜕化到上一版的模型,很好地兼容了旧版的功能。

聪明的同学应该能看出,这版模型的升级看似是支持了两个层级的标签,其实还是只支持单层标签的,只是可以对这层标签再加一层补充说明而已。这个方案的好处在于,既控制了系统的复杂度,又提高了大多数场景下的标注效率,原因如下:

从上文分析可知数据标注系统很难给出一套较优的通用标签分层组织方案或者方法论。但现实中又确实遇到了需要分层标注的场景,如本节的“实例标注”。上图模型中引入了一个“LabelAttribute”的概念,一方面,满足了大多数分层标注的场景(目前学习中的模型支持二分类和多分类,但即便对于一个多分类的模型来说,其支持的类别数目也不会太多,这就可以推断对于单个模型,其类别标签不大可能出现多级的分层,两级基本能覆盖住大多数的场景)。另一方面,又给出了支持的”分层“层数的约束(不大于两层),概念清晰,免去用户在使用过程中去纠结自己的标签到底建模成几个层级更好的问题。

其实稍微推演一下实际使用过程,假设你每次标注都需要展开n级标签才能完成,我想n<=3能有比较好的体验。就像阅读源码时,如果调用栈比较深,会造成阅读者的较重的心智负担。

那是否就不存在需要多层级的标签建模场景呢?答案必然是否定的。例如,{“黑夜-开灯-客厅-木地板-有地毯-颜色黑”、“黑夜-开灯-客厅-瓷砖-有地毯-颜色红”}, 这可以是一个室内场景图片的标注标签集。但是如果标注系统只支持一个两个层级,对于这种场景,是否我们切分为{“黑夜”,”白天“}、{“开灯”、”关灯“}、{“瓷砖”、”木地板“}、{“地毯-红”,”地毯-黑“}这样4个小标注任务来做,待这4个小的标注任务都完成之后,把标注数据规整到一起,再提供类似“黑夜 && 开灯 && 瓷砖 && 地毯红”这样的组合标签查询就好呢?由一个标签层级深度为5的标注任务,分解成4个标签层级深度为不超过2的标注任务,也能更有效地降低标注难度,提高标注人员的标注效率。当然,其代价是我们需要再本标注系统外构建一个“标注数据管理系统”来持组合式的标签查询。

由上可见,当我们面对一个超级复杂的问题的时候,不一定要去打造一个复杂度相当的工具才能处理这个问题,也许先把复杂的问题拆解成一个个小的问题,再通过一系列的小工具处理,分而治之,这样会更显优雅。这也是笔者在本小节花了如此大的篇幅来讨论这个问题的意义。

4. 支持视频标注

本节中我们将对系统模型中的“Data”做一些扩展,对于一个计算机视觉标注系统,除图像外,视频也是一种主要的数据来源类型。

图12:视频标注任务样例

众所周知,视频是由一帧一帧的图像构成的,且相邻的图像(视频帧)间有很强的相关性。因此,对视频的标注,可以视为对一组有序的图片进行标注。从这个角度看,我们把视频抽成一帧一帧的图片,再把图片导进上述我们构建的标注系统中标注,便可得到视频的标注,看似我们当前构建的标注系统是支持视频标注的,只是要求用户在系统外部将视频解码为图像帧。

貌似我们遇到了一个和第三小节中纠结的点很近似的问题:当前系统已经可以支持到视频类型数据的标注,我们是否还需要引入视频这种数据类型?引入后是会带来系统复杂度的增加?

其实这个问题很容易回答,只要考虑以下两点就好:
1)引入视频类型的数据后,我们的系统能不能提供对这种数据类型比较优秀且完备的解决方案?
2)与不引入这种数据类型相比,有没有提升用户的工作效率?有没有增加了用户对系统的一些不必要的理解成本?

显然,这两个问题都不难解答:

1)引入一个视频类型的数据,对系统来说只是一个小扩展,且视频的本质就是图像的组合,也不是什么复杂的数据类型。虽然说在视频的格式编码和解码方面,还是挺复杂的,但这个在业界已经有比较成熟的工具处理,一切都在可控范围内。回顾第三节中我们不想引入多级标签层级结构,主要是因为我们无法预知用户的标签怎样进行层级建模才好,就像在不同的场合,相同的一句话有不同的组织表达形式,这是一个不好控制的点。

2)假设不引入视频类型数据,用户需要自己在系统外去找解码器对视频进行解码,还要管理解码后的图片,视频的帧间冗余比较多,解码成图片后很容易带来没必要的存储空间膨胀,造成存储资源的浪费。如果我们支持视频类型的数据,并支持视频实时解码(标注到某帧图像,再去实时解这帧图像),这样使用我们系统的用户即不用感知(折腾)解码器,又不需要浪费不必要的空间来存储解码后的图像,更进一步地,我们还可以利用视频帧间的图像及其相似这一点来做一些提高标注效率的Trick,一举三得,何乐不为?

由上分析,我们引入视频类型,相应地调整系统建模如下图:

图13:数据模型 v5(局部)

如上图,本次对系统模型的调整主要集中在图的左上部分。

1)我们从“Data”类中派生出了“Image”和“Video”两个子类,且“Video”是“Data”的聚合类。这个聚合关系用红色特别标注出来,主要是为了强调,在这里只是描述这两个类间的关系,并不是说在具体实现的时候,会把视频类的文件拆成一张张图片再存入数据库。实际上这类系统对视频类文件的做法一般是直接存视频文件,在使用的时候再实时解帧成图片,即“Video”和“Image”类分别重写“Data”类的“getFrame()”接口,向标注员提供一张张图片供其标注即可(即“Image”和“Video”更像是承担了系统中的“DataProvider”的功能)。至于底层实现的时候是把视频实时抽帧,还是预先抽好放到存储里面,这得根据业务来决定。

2) 我们引入了“TrackAnnotation”类,这正是由于引入了“Video”类带来的优化点。众所周知,相对于“Image”,“Video”抽出的图片帧比较多,且相邻图像帧间的内容相关性很高。这给视频的标注提供了一种全新的高效标注机制,例如,当对20张连续的视频帧进行标注的时候,我可以只标注第1帧和第20帧,中间的18帧图片,就通过类似“函数插值”的方式来推导补齐。这在视频物体跟踪场景是很有用的,最理想的情况是这20帧画面是一辆汽车在沿着某方向直线匀速行驶,那插值效果是最好的。假设汽车是在变速行驶,甚至有拐弯、掉头等动作,我们也可以通过补“人工标注关键帧”来解决,如在第10帧补一个人工标注的帧。当人工标注关键帧补得越多,结果便越准确。就像变速运动中,若在中间取时间间隔极短的一段,这段时间内的运动可以理想化成匀速运动来研究,经典的微积分解决问题套路。

图13中还可以看到“TrackAnnotation”类被建模成了一个“Annotation”类的聚合类,因为“TrackAnnotation”本质就是由多个人工标注的关键帧“Annotation”组合成的,并且还要求这些“Annotation”的类别是一样的(不能第1个关键帧的标注是个ShapeAnnotation,下一个标注是个TagAnnotation类型,因为这两个类型间无法做插值),所以“TrackAnnotation”被建模成了一个泛型模版类。

5. 小结

本文从一个最简单的标注系统出发,陆续引入新的功能场景(如“物体检测”、“语义分割”、“实例分割”、“视频分割”等等),对整个标注系统的模型进行调整和拓展,得到了最终的系统领域建模图如下:

图14:数据模型 v5(全局)

并在整个领域模型的调整的过程中,对一些可能遇到的决策点进行了探讨分析,给出了一些笔者的个人思考:

1)【第二节】领域模型只关注对领域知识的建模,如概念、各个概念之间的关系等,不关注具体的实现细节,如数据库存储等等。

2)【第三节】系统宜有明显的边界,要节制,忌啥都想做,贪全嚼不烂;忌为炫技而做,谋定而后动,多学习了解新技术新技法,是为了更好地在源头解决问题,不是为了挖一堆坑再打一堆花里胡哨的补丁。

3)【第四节】该做的事主动去发掘,主动去做,不要乱用边界做挡箭牌。

4)敬畏领域知识,对某个专业软件系统的设计,最好先去扒一遍业内的优秀系统,复盘下他们为什么他们为什么会建模成这样,外行看热闹,内行看门道。本文建模过程之所以顺畅,是因为本文是笔者在尝试复现“CVAT”建模的过程,而“CVAT”是一个在数据标注领域积累了多年的软件。闭门造车,不如出去走走,或读万卷书,或行万里路。

个人微信公众号文章链接:https://mp.weixin.qq.com/s?__biz=Mzg5NDgyNzY5MQ==&mid=2247483673&idx=1&sn=7101e48a0c333976a6e1579e686f2d7d&chksm=c018ecddf76f65cbafc98fcbe272b989dd7a510153456916f777c071e70c39a5b5e75dd72d3e#rd

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。