本文字数:5692字
预计阅读时间:30分钟
目录
1. 背景
1.1. 业务背景
1.2. 解决方案探索
2. 项目解决方案
2.1 多模态检测违规图片
2.2 向量检索-需要支持快速检索和海量数据
3. 项目成果与未来展望
3.1 项目成果
3.2 未来展望
01
背景
1.1 业务背景
在视频审核中,对于特定的违禁视频内容需要进行严格管控和封禁。例如:对于领导人在一些特定时期事件的丑化;视频中出现醒目的广告引导,这些广告最终可能指向境外一些赌博,淫秽网站;再有对于一些像是巴以冲突等时政事件的恶意抹黑。这些都需要审核系统在视频进审时可以快速识别并封禁这些内容。
基于此,审核系统需要实现以下两个目标:
对于进审的视频,检测视频中出现的违规内容;
对于历史视频,如果有了新的违规标准,需要进行历史数据召回,追查旧视频中违规内容,进行封禁。
1.2 解决方案探索
违规图片的检测,最先想到的方法就是建立违规图片数据库,视频进审时,针对视频关键帧提取图片特征,同图片违规数据库匹配,如果命中的话,则标记当前视频为违规视频。同时,记录当前视频的所有关键帧图片特征,如果后续需要历史召回的话,通过记录的关键帧特征库,进行历史召回。
审核系统大致上的思路也是这样的,但实际应用中,遇到了以下几个问题需要解决。
单纯的图像特征匹配,泛化能力不强 即如果多张违规图片,违规内容一致,且都只是整张图片的一部分的话,由于图片匹配是整张图片匹配,所以匹配效果并不理想。如下图所示:
图片特征数据存储量大,计算成本高 每天进审视频量大概有20w, 每个视频平均8张截图,假设每张图片的存储需要512个32位浮点数,那一张图片的存储量就是2KB,每天约有3GB。由于向量检索都是在内存中进行的,这样的内存成本无疑是巨大的。
基于以上问题,审核系统分别从两个方面提出解决方案:
针对泛化能力不强的问题,分析当前遇到的问题,本质上是图片特征匹配只能对整张图进行特征提取,如果图中违禁内容只是图的一部分,很难提前预知违禁位置。那有没有更抽象的方法提取到图中违禁内容呢?我们认为文字可以对视觉内容进行更高层次的抽象总结。例如:上一节的例子中,如果我想给别人描述一下这三张图片的违禁内容,我可能会总结成“三个有彩票投注广告的图片”。因此,我们可以同时对进审图片进行图图、图文特征匹配增加召回率。同时针对特征较为集中的图片,微调模型,使用微调后模型直接给出是否违规的结论,不需要再次进行数据匹配;
针对数据成本问题,分析业务目标,发现两个业务目标对于数据检索的要求是不一样的。进审时检测,要求实时性,同时这时违禁图片向量库数据量并不会很大,完全可以放在内存中建立索引,实时检测。而历史数据召回,数据量大,要求违禁数据全量召回,但是实时性要求并不高,这块数据是可以放到磁盘中存储的,同时对于数据中的标量数据,可以在召回中进行标量过滤,过滤后再对向量数据进行检索。
01
项目解决方案
2.1 多模态检测违规图片
基于上一节描述的理由,我们这面采用一种可以对齐图片、文字特征表达的模型,来对进审图片进行检测。可以同时对图片的图像、文字特征共同检测。此类模型市面上较为成熟的 就是 clip及其衍生的各种变种模型了。由于我们的下游任务是图文检索,所以使用原始的基线版本,然后我们再对归一化后的特征进行向量相似度计算就可以了,不需要使用像是LSeg、GroupVit 这种为了下游任务专门训练的模型。
同时,由于clip模型本身是使用英文+图片训练的,对于中文,虽然有中文版本,但是中文效果不是很好的问题,我们这面使用了阿里达摩院开源的Chinese-clip版本。
2.1.1 模型简介
模型的简单结构如下图所示:
审核系统使用的模型版本由两部分组成,
一个是文本侧,使用RoBert模型transformer结构的encoder部分。模型使用Bert作为基础骨干网络,使用mask指定词的方式训练模型对词特征的表达。共有12个encoder层,参数量102M。
另一个是视觉测的Vision Transformer,也是一个 transformer的encoder部分,模型需要把图像切割成16 * 16 * 3(RGB)的patch embedding作为输入,训练模型的抽象表达。也是12个encoder层,参数量86M。
模型的训练过程,就是每个batch输入一定数量的图像和文本,通过各自模型encode成embedding,然后将图像的embedding和文字的embedding分别计算余弦相似度,损失函数要做的就是让成对的文本接近1,让不成对的文本接近0通过这样的方式,不断地调整两个encoder的权重,使得损失函数效果更好,以达到两个模型更高的对应表达。真实微调操作时,实际上先是冻结图像encoding,只训练文字encoding,然后 开放图像encoding 一起训练。
最终这个模型的效果就是,文字、图片都可以通过这个模型输出同一个维度下的特征表达。通过计算任意图图、图文间的向量相似度,可以跨模态地感知到图图、图文之间的相似度。在审核场景中,就是可以检测到进审图和违禁图是否相似、进审的图和违禁的文字表达是否相似。
2.1.2 审核系统中落地
整体流程图如下:1.数据基础: 构建图文样本库
首先,构建违禁图片、违禁文字样本库。样本来源可以是用户手动上传,也可以是审核同学在审核过程中发现的违禁图片、文字,一键录入样本库。审核系统收到违禁内容后,通过模型生成对应特征向量存储到用于实时监测的向量数据库中待检测。
2.进审检测
视频进审时,获取视频场景截图,对截图生成特征向量,使用特征向量在图片、文字违禁库中进行KNN检索,如果检索到最相似的违禁图片或文字,向量相似度达到违禁阈值,即直接通过图片或文字的形式就可以认定截图违禁,则直接记录当前截图违禁,对视频进行对应封禁操作。如果没有达到封禁阈值,但是达到了疑似阈值,且图片文字都达到了疑似阈值,即截图和违禁图片相似,但不完全一致,同时文字上的抽象表达也和违禁文字相似,但也没完全一致,那大概率这个图片有违禁的地方,但是和样本库中数据并不完全一致。则记录当前截图疑似违禁,根据审核系统的审核策略,对视频进行处理。同时,截图的特征向量存储到历史向量库中。
3.根据命中率进行阈值修正
每天定时统计前一天违禁和疑似违禁的机审结果和人审结果,计算出当前相似度阈值下的精确率、召回率。审核人员可以根据精确率、召回率,调整阈值大小。同时,新的阈值也可以在历史向量库进行验证。
2.1.3 模型微调
上线上述功能后,我们发现线上有相当一部分数据是以给黑色产业链打广告为目的的违禁视频。这类视频广告的花样很多,但总逃不过一个目的,就是给黑产打广告,想要打广告,就一定需要在图片上有个清晰的广告目标。这个目标可以是网址、微信、手机号、QQ号等。因此,我们使用这一类特征较为集中的黑产数据对模型进行微调,使得模型可以直接给出黑产的识别结果。省去了KNN检索过程,加快了检查速度。
微调过程:
1.数据准备
首先,根据线上录入的黑产违禁图片库,检索历史召回向量库。小于线上违禁阈值的为正向数据,即:黑产图片。大于疑似阈值的为负向数据,即正常图片。在违禁阈值和疑似阈值之间的为疑似图片。以此方法收集,黑产、正常、疑似图片共1K条。疑似图片可以根据最终的视频审核记录知道最终人工判定为黑产图片还是正常图片。这样可以收集总共3K张图片,并且全部标记过了。然后,对这3K张图片进行图片增强,包括 RandomResize、Flipping、Padding、Rotation 方法,使得图片数据增强到12K。其次,将图片随机分成8:1:1的比例,8为训练集,1为验证集、测试集。之后,为图片生成一个随机数作为图片ID,图片内容进行base64编码,将文件存储到对应集合的tsv文件中。
例如:train_imgs.tsv
1000002 /9j/4AAQSkZJ...YQj7314oA//2Q==
将文字和图片的关系存储到对应的jsonl文件中,每行文字是一个json。
例如:train_texts.jsonl
{"text_id": 8428, "text": "网址QQ黑产广告", "image_ids": [1076345, 517602]}
以此类推整理训练集、验证集、测试集,最终文件形式如下:
${DATAPATH}
└── datasets/
└── ${dataset_name}/
├── train_imgs.tsv # 图片id & 图片内容
├── train_texts.jsonl # 文本id & 文本内容,连同匹配的图片id列表
├── valid_imgs.tsv
├── valid_texts.jsonl
├── test_imgs.tsv
└── test_texts.jsonl
2.执行微调
由于我们这面微调的时候使用的是windows系统,只能单进程执行微调,示例脚本如下:
python -m torch.distributed.launch --use_env --nproc_per_node=1 --nnodes=1 --node_rank=0 --master_addr=localhost --master_port=8514 cn_clip/training/main.py --train-data=datasets/AD/lmdb/train --val-data=datasets/AD/lmdb/valid --resume=pretrained_weights/clip_cn_vit-b-16.pt --reset-data-offset --reset-optimizer --logs=experiments/ --name=ad_finetune_vit-b-16_roberta-base_bs128_1gpu_2 --save-step-frequency=999999 --save-epoch-frequency=30 --log-interval=1 --report-training-batch-acc --context-length=52 --warmup=100 --batch-size=96 --valid-batch-size=96 --valid-step-interval=150 --valid-epoch-interval=1 --accum-freq=1 --lr=2e-6 --wd=0.001 --max-epochs=150 --vision-model=ViT-B-16 --use-augment --text-model=RoBERTa-wwm-ext-base-chinese
3.模型评估
·图文特征提取:
python -u cn_clip/eval/extract_features.py --extract-image-feats --extract-text-feats --image-data="datasets/AD/lmdb/test/imgs" --text-data="datasets/AD/test_texts.jsonl" --img-batch-size=32 --text-batch-size=32 --context-length=52 --resume=experiments/ad_finetune_vit-b-16_roberta-base_bs128_1gpu/checkpoints/epoch_latest.pt --vision-model=ViT-B-16 --text-model=RoBERTa-wwm-ext-base-chinese
产出图文特征默认将保存于${DATAPATH}/datasets/${dataset_name}目录下,图片特征保存于${split}_imgs.img_feat.jsonl文件,每行以json存储一张图片的特征,格式如下:
{"image_id": 1000002, "feature": [0.0198, ..., -0.017, 0.0248]}
文本特征则保存于${split}_texts.txt_feat.jsonl,格式如下:
{"text_id": 248816, "feature": [0.1314, ..., 0.0018, -0.0002]}
·KNN检索
对于文搜图检索,运行如下命令:
python -u cn_clip/eval/make_topk_predictions.py --image-feats="datasets/AD/test_imgs.img_feat.jsonl" --text-feats="datasets/AD/test_texts.txt_feat.jsonl" --top-k=10 --eval-batch-size=32 --output="datasets/AD/test_predictions.jsonl"
产出的结果保存在指定的jsonl文件中,每行表示一个文本召回的top-k图片id,格式如下:
{"text_id": 153915, "image_ids": [5791244, 1009692167, 7454547004, 3564007203, 38130571, 2525270674, 2195419145, 2503091968, 4966265765, 3690431163]}
对于图搜文检索,运行如下命令:
python -u cn_clip/eval/make_topk_predictions_tr.py --image-feats="datasets/AD/test_imgs.img_feat.jsonl" --text-feats="datasets/AD/test_texts.txt_feat.jsonl" --top-k=10 --eval-batch-size=32 --output="datasets/AD/test_tr_predictions.jsonl"
产出结果每行表示一个图片召回的top-k文本id,格式如下:
{"image_id": 977856234, "text_ids": [156914, 157914, 158914, 155914, 156179, 158907, 157179, 154179, 154914, 154723]}
·Recall计算
对于文搜图检索,运行如下命令:
python cn_clip/eval/evaluation.py datasets/AD/test_texts.jsonl datasets/AD/test_predictions.jsonl output_text_image_f.json
对于图搜文检索,运行如下命令:
#数据格式转换
python cn_clip/eval/transform_ir_annotation_to_tr.py --input datasets/AD/test_texts.jsonl
#计算分数
python cn_clip/eval/evaluation_tr.py datasets/AD/test_texts.tr.jsonl datasets/AD/test_tr_predictions.jsonl output_image_text_f.json
输出文件格式如下
{"success": true, "score": 85.67, "scoreJson": {"score": 85.67, "mean_recall": 85.67, "r1": 71.2, "r5": 90.5, "r10": 95.3}}
4.微调效果
Recall 分数
微调前 | 微调后 | 效果 | |
---|---|---|---|
图搜文 | 67.79% | 98.88% | 提升45.86% |
文搜图 | 37.68% | 88.53% | 提升50.85% |
说明:实时监测时,应用的是图搜文,即使用截图检索是否匹配文字;历史召回时,应用的是文搜图,即使用文字对历史数据进行检索匹配。
历史召回效果:
微调前:
微调后:
2.1.4 多模态检测效果
使用预训练模型进行多模态进审检测,违禁视频机审删除率增加了50%;微调的黑产广告模型,线上针对黑产数据的检测精确率可以提升45%。
2.2 向量检索-需要支持快速检索和海量数据
实际业务生产中,除了功能上要满足需求外,成本也是需要考虑的重要因素之一。当前审核系统选取的模型输出特征向量为512维的单精度浮点型,即512个float,这样一来,根据上一节计算结果,每天需要存储3GB的数据量。同时由于进审检测的实时性要求,这些向量相似度计算都要在内存中完成,每天3GB的内存增加,时间久了这个成本就不可忽视了。
回归业务,分析两个需求的应用场景,发现实际上两个需求的应用场景并不一样。进审时的实时监测,向量库中数据量少,检测要求速度快。历史召回,向量库中数据量大,检测速度无要求。
基于此,我们对比了市面上常见的几种向量数据库尝试找出一种合理解决方案。
2.2.1 向量库选型
向量数据库 | 支持索引 | 存储成本 | 分布式 | 运维成本 |
---|---|---|---|---|
RedisSearch | HNSW,flat | 内存 | 支持 | 低 |
ElasticSearch | HNSW,flat | 内存、磁盘 | 支持 | 低 |
Milvus | HNSW,flat,ivf_flat,ivf_pq, scann等 | 内存 | 支持 | 高 |
Faiss | HNSW,flat,IndexIvfPQ,IndexLSH | 内存 | 不支持,本身只是一个库 | 低 |
对比几种向量库选型:
由于审核系统图片向量数据较大,需要对数据进行分布式存储,而Faiss 只是一个库,单节点,没有数据存储方案,所以首先排除掉。在剩下的向量库中,我们发现ElasticSearch 可以实现向量检索,同时,数据本身的存储方式还是由lucene形式存储到硬盘中的,只是检索数据的时候,会加载到内存中。这样一来,数据的存储成本就可以由内存转向了磁盘,成本大大降低。并且,使用lucene倒排索引,甚至还可以将数据进行前期过滤,不用把所有数据都驻留在内存中,也大大降低了CPU计算的压力。
RedisSearch 和 Milvus 对比上,我们发现Milvus支持的索引非常丰富,但我们日常使用HNSW索引,RedisSearch也支持。同时,此前我们并没有接触过Milvus,上手之后,发现组件较多,运维成本较大。相比于Milvus, RedisSearch就亲民很多,大多数业务场景,运维也都接触过redis,只要安装一个插件,就可以直接使用RedisSearch,非常方便、成熟。并且,内存的使用上,也可以通过RedisCluster扩展redis集群控制的内存,不用担心数据量的大量增加。
最终,我们选用RedisSearch作为进审时,实时监测的向量数据库。ElasticSearch作为历史召回向量库。Milvus作为备选后续持续研究。
2.2.2 向量检索加速
选定了向量库和技术方案后,我们开始压测服务的性能。这时候,我们发现chinese-clip模型的预训练pytorch版本在线上请求量大的时候,响应时间还不是那么尽如人意。单节点一张图片推理速度在368ms左右。同时,预训练模型的训练量巨大,如果我们采用模型剪枝、模型蒸馏的方式对模型进行压缩的话,成本非常高。因此,我们尝试对模型进行量化处理,并且由于我们生产环境使用intel的CPU,还可以通过openvino对模型进行推理加速。
加速后的效果如下:
CPU型号 | CPU核心数 | 提速方法 | 图片推理100次请求平均响应时间 | 效果提升 |
---|---|---|---|---|
Intel(R) Gold 5218R | 8 | 原始pytorch模型 | 368.72ms | |
onnx模型FP16 | 502.59ms | -26.67% | ||
openvino运行onnx FP32 | 127.35ms | 189.54% | ||
openvino运行IR FP16 | 112.78ms | 226.95% |
可以看到推理速度提升了两倍,同时由于降低了精度,那存储占用也只有原先的一半,每天预估占用1.5G存储。
03
未来展望
当前审核系统对chinese-clip的应用较为保守,后续业务稳定运行后还有以下几点需要跟进:
提高模型识别速度,使用GPU tensorRT版本加速chinese-clip推理速度;
随着对Milvus了解的深入,发现Milvus新版本也有基于磁盘和GPU的索引。历史召回可以使用基于磁盘的索引,实时检测可以使用基于GPU版本的索引;
微调的chinese-clip模型,由于检测内容较为单一,可以使用更高比例的蒸馏,对模型进行压缩,加速模型推理。