Skip to content

图片与视频模型

media-model-proxy 代理的成人、暴力血腥、仇恨符号、主题、指纹模型;clip/ 训练的 256 维图文嵌入;adult-content/ 的训练与校准;pnsfwmedia 的媒体加账号联合模型

Updated

这一页回答什么

本页说明 X 用哪些专用模型分析图片和视频,内容包括:media-model-proxy 代理哪些模型,每个模型读取什么输入、返回什么;clip/ 训练的图文嵌入如何成为这些分类器的输入;adult-content/ 如何训练和校准成人内容分类器;pnsfwmedia/ 如何把 CLIP 媒体嵌入与账号级分数合并;以及这些分数最终由谁读取。

Grox 中基于大模型的媒体判定(PTOS、safemodel)不在本页范围内,见 Grox 内容分类器。

核心结论

  1. media-model-proxy 只转发请求,不存储分数:它按 Blobstore 路径读取一次媒体,并发请求多个 DeepBird 模型,再把“模型 → DataRecord 列表”返回给调用方。调用方负责解释和持久化分数(media-model-proxy/README.rst:7-20)。README 写明当前调用方是 Media Analysis Service,该服务不在仓库中。
  2. 代理注册了 7 个模型:成人内容 XxNsfw、暴力血腥 ViolenceAndGore、仇恨符号 HatefulSymbolRecognition、主题 CategoryClassification、主题嵌入 CategoryClassificationEmbeddings、指纹 Fingerprinting 与 FingerprintingV2(media-model-proxy/src/main/scala/com/twitter/media_understanding/model_proxy/model_descriptors/ModelDescriptorRegistry.scala:9-19)。Thrift 枚举中的 XX_NSFW_V2 没有对应实现。decider 中的 disable_adult_content_v1 开关没有被任何模型引用。
  3. CLIP 是媒体嵌入的来源:clip/ 在 OpenAI CLIP ViT-B/32 上加一层前馈投影,用推文配图与推文文本做对比学习,产出 256 维图文嵌入(clip/model.py:26、clip/config.py:6)。模型发布到 packer 角色 embeddings-category(clip/scripts/export_keras_models.py:68)。
  4. 成人分类器是建在 CLIP 嵌入上的小网络:adult-content/ 在 256 维媒体嵌入上训练“BatchNorm → 128 维 GELU → sigmoid”二分类器。随后它用保序回归把原始分数校准成概率,并输出 200 个阈值下的精确率、召回率、误报率表(adult-content/training.py:13-42、adult-content/calibration.py:72-88)。
  5. pnsfwmedia 联合媒体与账号做判定:冻结的 CLIP 模型给出媒体分。模型再拼接 agatha 的校准 NSFW 账号分、账号文本 NSFW 分、粉丝 NSFW 分和粉丝数对数,经过一个 50 维隐层输出概率(pnsfwmedia/model_experimental.py:50-92)。文件名带 experimental,源码未包含线上是否使用它的信息。
  6. botmaker 规则把分数转为标签:开源代码中可见的读取方是 scarecrow 的 FeaturesOfMediaUpdate。它把媒体更新事件中 xx、sexy、xxnsfw、xxnsfwv2 等标签的概率、精确率、召回率、误报率和校准档位转成 botmaker 规则特征(botmaker/scarecrow_features/src/main/java/com/twitter/scarecrow/features/FeaturesOfMediaUpdate.java:27-112)。规则再打标签,可见性过滤 读取这些标签。

全景

flowchart LR
    subgraph train["离线训练"]
        CLIP["clip/<br/>Twitter-ViT-B/32-256"] --> EMB["256 维媒体嵌入"]
        EMB --> AC["adult-content/<br/>MLP + 保序回归校准"]
        EMB --> PN["pnsfwmedia/<br/>媒体分 + 账号分"]
        AG["agatha 校准 NSFW 账号分"] --> PN
    end
    subgraph serve["在线推理"]
        MAS["Media Analysis Service<br/>源码未包含"] --> MMP["media-model-proxy"]
        MMP --> BS[("Blobstore")]
        MMP --> DB["DeepBird 模型<br/>7 个"]
    end
    CLIP -. "packer 角色 embeddings-category" .-> DB
    MAS -. "媒体更新事件" .-> SC["scarecrow<br/>FeaturesOfMediaUpdate"]
    SC --> BM["botmaker 规则"]
    BM --> VF["可见性过滤"]

虚线表示根据命名或数据格式推断出的连接,源码没有给出完整调用链。

media-model-proxy:模型代理

请求与响应

服务沿用旧名 CortexMediaAnnotator,只有一个方法 getAnnotations(media-model-proxy/src/main/thrift/com/twitter/cortex_media_annotator/annotate.thrift:75-78)。请求带一个媒体实体和一组模型。媒体实体可以直接带字节 media_data,也可以只带 blobstore_path。响应的 annotations 是“模型 → DataRecord 列表”(media-model-proxy/src/main/thrift/com/twitter/cortex_media_annotator/annotate.thrift:31-53)。

ProxyController 按以下顺序执行(media-model-proxy/src/main/scala/com/twitter/media_understanding/model_proxy/controllers/ProxyController.scala:33-92):

  1. 请求带字节时,直接使用字节;否则,按路径从 Blobstore 读取一次。两者都没有时,返回错误。
  2. 对请求中的每个模型并发调用 ModelsService.getPrediction。
  3. 汇总成响应,并按采样率记录请求日志。采样率 requests_scribe_sample_rate 的开源默认值为 100(取值范围 0 到 10000,即 1%)(media-model-proxy/config/decider.yml:57-59)。

ModelsService.getPrediction 查找模型的描述器,把媒体字节转成该模型所需的特征,再发送给所有可用的客户端(media-model-proxy/src/main/scala/com/twitter/media_understanding/model_proxy/services/ModelsService.scala:38-93)。单个客户端失败不会导致请求失败:服务过滤掉非致命异常,该模型只返回成功的那部分结果,结果可能是空列表(media-model-proxy/src/main/scala/com/twitter/media_understanding/model_proxy/services/ModelsService.scala:104-120)。DeepBird 客户端在连接关闭和超时时自动重试(media-model-proxy/src/main/scala/com/twitter/media_understanding/model_proxy/clients/DeepbirdClient.scala:63-70)。

七个模型

模型 判定内容 输入特征 支持的媒体 DeepBird 服务 超时 代理校验的输出
XxNsfw 成人内容 Blob 特征 media + 离散特征 meta.media_type 推文图片、推文视频、Periscope 视频、推文 GIF;必须带媒体类别 /s/xx-nsfw/xx-nsfw 8000 ms nsfw.default.probability、.score、.precision、.recall、.fpr 五个连续值
ViolenceAndGore 暴力血腥 张量 violence_and_gore.image + meta.media_type 头像、横幅、推文图片、推文视频、推文 GIF /s/violence-and-gore/violence-and-gore 3000 ms 不校验;源码未包含输出字段
HatefulSymbolRecognition 仇恨符号 张量 magicpony.hateful_symbol_recognition.image 不限 /s/magicpony-hateful/magicpony-hateful-symbol-recognition 3000 ms 不校验;源码未包含输出字段
CategoryClassification 媒体主题 张量 magicpony.category_classification.image GIF 需开启开关 enable_gifs_for_category_classification /s/magicpony-category/magicpony-category-classification 3000 ms 不校验;源码未包含输出字段
CategoryClassificationEmbeddings 媒体嵌入 张量 media.data GIF 需开启开关 enable_gifs_for_category_classification_embeddings /s/embeddings-category/embeddings-category-classification 4000 ms 不校验;占位客户端返回 256 维
Fingerprinting 用于匹配已知媒体的指纹 张量 magicpony.media_fingerprinting.image 不限 /s/magicpony-fingerprinting/magicpony-fingerprinting 3000 ms pixel_variance、embedding 两个张量
FingerprintingV2 同上,第二版 同上 不限 同上,由不同开关控制 3000 ms 同上

出处依次为 XXNsfwModelDescriptor.scala:29-84、ViolenceAndGoreModelDescriptor.scala:35-72、HatefulSymbolRecognitionModelDescriptor.scala:25-50、CategoryClassificationModelDescriptor.scala:25-55、CategoryClassificationEmbeddingsModelDescriptor.scala:28-67、FingerprintingModelDescriptor.scala:25-69、FingerprintingV2ModelDescriptor.scala:60-69,均位于 media-model-proxy/src/main/scala/com/twitter/media_understanding/model_proxy/model_descriptors/。

补充说明:

  • 媒体类别归并:模型不直接支持某个类别时,GIF 归为推文 GIF,视频归为推文视频,图片归为推文图片,其余类别返回错误(media-model-proxy/src/main/scala/com/twitter/media_understanding/model_proxy/model_descriptors/ModelDescriptor.scala:31-46)。
  • 指纹只是特征:代理只返回指纹向量和像素方差。用指纹比对已知媒体库的逻辑不在本服务中。scarecrow 能读到媒体更新事件中的聚类签名、MD5 哈希和匹配到的视频聚类,并用于规则判断(botmaker/scarecrow_features/src/main/java/com/twitter/scarecrow/features/FeaturesOfMediaUpdate.java:134-173)。源码未写明这些聚类是否由本服务的指纹生成。
  • 主题嵌入可切换为随机向量:CategoryClassificationEmbeddings 用 SwitchingModelClient 在真实 DeepBird 客户端与占位客户端之间切换。开关 enable_dummy_category_classification_embeddings 打开时,它返回 256 个随机浮点数,用于压测或降级(media-model-proxy/src/main/scala/com/twitter/media_understanding/model_proxy/clients/DummyEmbeddingsClient.scala:19-56)。另一个开关可以把同一请求复制一份发到备用目标,用于暗流量对比(media-model-proxy/src/main/scala/com/twitter/media_understanding/model_proxy/clients/SwitchingModelClient.scala:22-29)。
  • 每个模型都能单独关闭:decider 为每个模型提供一个 disable_* 开关。开源默认值都是 0,即全部开启(media-model-proxy/config/decider.yml:5-35)。

clip/:256 维图文嵌入

TwitterCLIP 以 OpenAI 的 CLIP ViT-B/32 为底座,在图像塔和文本塔顶部各加一个 PositionwiseFeedForward 投影层,把输出压缩到 final_embedding_dim(clip/modules.py:31-81)。默认生产模型是 Twitter-ViT-B/32-256,即 256 维;另有 128 维版本(clip/model.py:26、clip/model.py:44-47)。

训练采用标准的 CLIP 对比学习:模型为一批图文对计算相似度矩阵,按图到文、文到图各计算一次交叉熵,再把两者相加(clip/training.py:143-171、clip/utils/model_utils.py:4-11)。文本取推文正文字段 tweet_text(clip/datasets/language_image_dataset.py:16)。开源默认值如下(clip/config.py:8-25):

参数 值
输入分辨率 224
final_embedding_dim 256
logit_scale 初值 4.5065(取指数后约 90.6;前向计算时上限为 100,见 clip/model.py:227)
batch_size 128
learning_rate 2e-5
训练步数 / 预热步数 100000 / 500

生产检查点文件名为 clip_source_d256_bs2784(clip/model.py:45)。按命名推测,该检查点使用 2784 的批大小训练,与上表默认值不同。

导出与分发:

  • Keras SavedModel 暴露 predict_from_encoded_images。它输入编码后的图片字节,输出 L2 归一化的 image_embeddings(clip/keras_model.py:117-131)。模型发布到 packer 角色 embeddings-category,包名为 CLIP-keras-Twitter-ViT-B-32-256(clip/keras_model.py:24-26、clip/scripts/export_keras_models.py:68)。
  • 图像编码器也能导出为 DeepBird v2 模型文件,输出名为 embedding(clip/model.py:306-335)。
  • clip/notebooks/media_embeddings_dataflow.ipynb 演示批量推理:从推文表读取媒体 URL,下载媒体,计算 CLIP 嵌入,再写回 BigQuery 的 media_embedding 字段。

与 media-model-proxy 的关系(推测):代理的 CategoryClassificationEmbeddings 指向 /s/embeddings-category/...,占位维度为 256。CLIP 包发布在同名角色 embeddings-category,生产维度也是 256。两处证据一致,因此推测该模型就是 CLIP 图像编码器。源码没有直接写出两者的对应关系。

adult-content/:成人内容分类器的训练与校准

数据。每条 TFRecord 包含 256 维 media_embedding 和一组整数 labels。任一标签等于 1 时,该条为正样本(adult-content/dataset_utils.py:14-38)。训练时可以开启拒绝采样,把正负样本比例重采样到 50:50(adult-content/dataset_utils.py:10-12)。

模型。模型共三层(adult-content/training.py:13-42):

  1. BatchNormalization
  2. Dense(128, activation="gelu")
  3. Dense(1, activation="sigmoid")

损失函数为二元交叉熵,优化器为 Adam。监控指标是“召回率 0.9 时的精确率”和 PR 曲线下面积。验证损失连续 5 轮不下降时,训练提前停止并恢复最佳权重(adult-content/training.py:66-74)。开源默认值为:batch_size 256,学习率 0.001,最多 100 轮,每轮 400 步(adult-content/config.py:15-20)。

校准(adult-content/calibration.py:38-97):

  1. 在 0 到 1 之间均匀选取 200 个阈值(num_thresholds=200,adult-content/config.py:36)。
  2. 对每个原始分数,找到它所在的两个相邻阈值之间的区间,用该区间内正样本的比例作为目标值。
  3. 用保序回归(单调递增,输出截断在 0 到 1)拟合“原始分数 → 正样本比例”。
  4. 对每个阈值输出一行:阈值、精确率、召回率、误报率、校准后概率,写入 metrics_at_thresholds.csv。

与 XxNsfw 输出的关系(推测):代理强制要求 XxNsfw 返回 probability、score、precision、recall、fpr 五个字段,与校准表的列一一对应。推测线上 XxNsfw 先计算原始分数,再查校准表,给出该分数对应的概率、精确率、召回率和误报率。scarecrow 读取的 xxnsfw 标签同样带 probability、precision、recall、fpr 四个值(botmaker/scarecrow_features/src/main/java/com/twitter/scarecrow/features/FeaturesOfMediaUpdate.java:75-88)。

pnsfwmedia/:媒体分加账号分

pnsfwmedia/model_experimental.py 定义一个 Keras 模型,输入 8 个特征(pnsfwmedia/model_experimental.py:50-92):

特征 来源 缺失时
媒体分 冻结的 CLIP 模型作用于 256 维张量 media.mediaunderstanding.media_embeddings.twitter_clip_as_tensor 默认全零向量
agatha_calibrated_nsfw_score 作者的 agatha 校准 NSFW 分 -1.0
nsfw_text_user_score 作者发文的 NSFW 文本分 -1.0
粉丝数对数 log(followed_by_count + 10) —
nsfw_consumer_follower_score 作者粉丝中 NSFW 内容消费者的占比分 -1.0
三个缺失指示 上面三个账号分是否等于 -1.0 —

前三个账号分都来自 source_user.hss.user_health_scores 命名空间(pnsfwmedia/model_experimental.py:108-133)。“粉丝中 NSFW 消费者占比”是根据字段名的解读,源码未包含计算方式。

网络结构为 BatchNormalization → Dense(50, relu) → Dense(1, sigmoid),CLIP 部分冻结,不参与训练(pnsfwmedia/model_experimental.py:17-48)。训练使用 Adam 和二元交叉熵,开源默认值为 batch_size 32、20 轮、每轮 100 步(pnsfwmedia/model_experimental.py:136-170)。模型保存时带 serve 签名,可以直接接收 DeepBird 批量预测请求。训练脚本同时把验证集的分数与标签写入 predictions.csv(pnsfwmedia/model_experimental.py:172-188),格式与 adult-content/ 校准脚本的输入一致(推测用于后续校准)。

这个模型的作用是:同一张擦边图片发在长期发布成人内容的账号下和发在普通账号下,模型给出的分数不同。账号分的计算见 账号信号。

分数的读取方

  1. media-model-proxy 的调用方:Media Analysis Service。源码未包含它把模型输出写成媒体更新事件的方式。
  2. scarecrow → botmaker 规则:FeaturesOfMediaUpdate 读取三种媒体更新(botmaker/scarecrow_features/src/main/java/com/twitter/scarecrow/features/FeaturesOfMediaUpdate.java:47-173):
    • 标签更新:只取 xx、sexy、xxnsfw、xxnfswv2、xxnsfwv2(前者是源码中的原样拼写,FeaturesOfMediaUpdate.java:28)。xxnsfwv2 写成 probabilityNsfwV2 等四个特征;xxnsfw 写成 probabilityNewNsfw 等四个特征;xx 和 sexy 按校准档位写成 highPrecision*、highRecall*、nearPerfect* 三个布尔量和概率,分数高于该档阈值时为真。
    • 预测标签更新:只取 nsfw_score。
    • 聚类、MD5 哈希、视频聚类更新:写成聚类 ID、签名向量和哈希列表。 botmaker 规则使用这些特征,规则打出的标签见 标签规则。
  3. 可见性过滤:可见性过滤规则读取帖子上的 NsfwHighPrecision、GoreAndViolenceHighPrecision 等标签(visibility-filtering/rules/tweet_rules.rs:457、visibility-filtering/rules/tweet_rules.rs:587)。Grox PTOS 也可能直接打上这两个标签,见 Grox 内容分类器。规则对这些标签的具体动作见 可见性过滤。

开源的 Grox 代码没有调用 media-model-proxy。Grox 的成人图片判定使用另一个服务 safemodel(grox/flows/ptos/task_safety_ptos_safemodel_sex_nudity.py:22-24)。

设计决策

决策 理由
代理层统一读取媒体 一次 Blobstore 读取服务多个模型,降低存储与网络开销(media-model-proxy/README.rst:10-13)
代理不存储分数 各调用方对分数的解释和阈值不同;代理只负责推理
单个模型失败不影响整个请求 七个模型相互独立,部分结果仍有用
分类器建在共享 CLIP 嵌入上 一次嵌入计算可供多个下游分类器复用;新分类器只需训练一个小网络
输出校准表而非单一阈值 下游规则可按需选择高精确率档或高召回率档
媒体分与账号分联合建模 同一媒体在不同账号语境下风险不同;账号历史能弥补单张图片的歧义

FAQ

Q:这些模型会直接决定帖子是否出现在 For You 中吗? A:不会直接决定。模型只产出分数。botmaker 规则或 Grox 把分数转为帖子标签,可见性过滤再按场景决定隐藏、降级还是加警告。

Q:CLIP 嵌入和 Grox 的多模态帖子嵌入是同一种嵌入吗? A:不是。CLIP 嵌入是单张媒体的 256 维向量,用于安全分类。Grox 的嵌入是整条帖子(文字、图片、视频、作者)的向量,用于召回。见 多模态帖子嵌入器。

源码锚点

  • media-model-proxy/README.rst:7-23 —— 服务定位与调用方
  • media-model-proxy/src/main/thrift/com/twitter/cortex_media_annotator/annotate.thrift:8-78 —— 模型枚举与接口
  • media-model-proxy/src/main/scala/com/twitter/media_understanding/model_proxy/controllers/ProxyController.scala:33-92 —— 读取媒体与并发调用
  • media-model-proxy/src/main/scala/com/twitter/media_understanding/model_proxy/services/ModelsService.scala:38-120 —— 单模型推理与失败过滤
  • media-model-proxy/src/main/scala/com/twitter/media_understanding/model_proxy/model_descriptors/ModelDescriptorRegistry.scala:9-19 —— 七个模型的注册
  • media-model-proxy/src/main/scala/com/twitter/media_understanding/model_proxy/model_descriptors/XXNsfwModelDescriptor.scala:29-84 —— 成人模型输入与五个输出字段
  • media-model-proxy/config/decider.yml:1-59 —— 模型开关与采样率
  • clip/model.py:26-146 —— TwitterCLIP 模型与检查点
  • clip/training.py:143-171 —— 对比学习损失
  • clip/keras_model.py:12-131 —— 生产模型名与 Keras 导出
  • adult-content/training.py:13-85 —— 成人分类器结构与训练
  • adult-content/calibration.py:38-97 —— 保序回归校准
  • pnsfwmedia/model_experimental.py:17-133 —— 媒体加账号联合模型
  • botmaker/scarecrow_features/src/main/java/com/twitter/scarecrow/features/FeaturesOfMediaUpdate.java:27-173 —— 媒体分数转规则特征

相关页面

Type to search…

↑↓ navigate↵ selectEsc close