这一页回答什么
agatha/怎样用“拉黑、举报、垃圾举报相对点赞”的比率和图特征,为每个账号计算垃圾、成人内容等分数。bdsm/的行为序列模型读取什么、输出什么,怎样决定挑战或封禁。user-cred-v2/怎样在关注图和互动边上运行 PageRank,并把质量(mass)换算成 0-100 的账号分。- 哪些系统把这些分数用作阈值或豁免条件。
三者都不在请求路径上。打标签规则或处置服务把它们的输出转成账号标签,再由 可见性过滤 读取。
核心结论
- Agatha 的基础标签是平滑比率。 分数 = (互动数 + 0.1) / (互动数 + 站外点赞数 + 0.1 / 先验)。这里的互动指拉黑、举报或垃圾举报(
agatha/scalding/labels/rate_based_labels/RateBasedLabels.scala:29-57、:60)。 - Agatha 再用图特征把这些标签推广到所有活跃账号。 它计算每个图特征与标签的 PMI(点互信息,衡量两者共现程度),按账号汇总成 Blink 分,再按活跃用户的分布做百分位校准(
agatha/scalding/blink/BlinkScoreJobTemplate.scala:127-128、:184-244)。 - Agatha 分数的使用阈值写在 botmaker 规则中。 例如:
SpamSuspended > 0.99或举报比分数> 0.999时,规则给账号打标签AGATHA_SPAM_TOP_USER,有效 7 天(botmaker-rules/scarecrow/bot/ProductionAgatha__FilterOutOrganicNotifications.bot:11-33)。这个标签只影响通知,不影响 For You(见 可见性过滤)。 - BDSM 是行为序列 Transformer。 它输入最近 512 个动作,输出 8 个头的分数。开源版把执法阈值替换为不可能触发的 9.99(
bdsm/README.md:102-115、bdsm/runtime/sink_policy.yaml:4-40)。 - user-cred-v2 分数 = clamp(165.2 + 7.07 × ln(mass), 0, 100)(
user-cred-v2/UserCredV2.scala:10-19)。mass 来自关注图上的 PageRank。跳转概率开源默认为 0.2;跳转目标一半在认证账号间均分,一半按点赞/转发加权(user-cred-v2/UserCredV2Config.scala:30-35、user-cred-v2/UserCredV2App.scala:141-193)。 - user-cred-v2 分数 ≥ 54 的账号视为“高 PageRank 账号”,多数自动打标签规则豁免这类账号(
botmaker-rules/scarecrow/derived-feature/HighPageRankThreshold.df:1-2、IsHighPageRankUser.df:3-18)。
Agatha:举报/拉黑相对点赞
基础标签(率值标签)
| 标签名 | 分子(互动) | 时间窗 | 锚点 |
|---|---|---|---|
BlocksPerFav |
被拉黑(拉黑图中状态为 0 或 2 的边,排除已知人造账号) | 本批次日期范围 | RateBasedLabels.scala:120-140 |
ReportsPerFav |
经滥用分诊工具(AbuseTriageTool)提交的举报 |
180 天 | RateBasedLabels.scala:65-66、:142-159 |
SpamReportsPerFav |
被判为有效的垃圾举报(私信、举报为垃圾、举报用户、举报帖子) | 本批次日期范围 | RateBasedLabels.scala:67-68、:161-184 |
AllSpamReportsPerFav |
全部垃圾举报(不区分是否有效) | 本批次日期范围 | RateBasedLabels.scala:69-70 |
分母中的“点赞”只计站外点赞,即点赞者与作者之间没有任何方向的关注关系(agatha/scalding/labels/favs/Favs.scala:15-19、:51-68)。
计算步骤如下:
- 每个发起者最多计 100 个目标账号,超出部分用水库抽样截断(
agatha/scalding/labels/LabelUtil.scala:12-23)。例如,一个人拉黑一万个账号,只按 100 个计。 - 计算先验:先验 = 全体账号的 Σ互动 / Σ(互动 + 点赞)。单个账号的 (互动 + 点赞) 超过 1000 时,按比例缩到 1000 再参与计算(
LabelUtil.scala:25-44)。 - 计算账号分数(
RateBasedLabels.scala:47-55):
score = (I + 0.1) / (I + F + 0.1 / prior)
I = 互动数,F = 站外点赞数,0.1 = 平滑参数(RateBasedLabels.scala:60)没有任何互动和点赞的账号,分数等于先验。样本越多,分数越接近真实比率。
另有两类二值标签:
SpamSuspended:正例是过去窗口内因spam或other理由被封禁、且备注不含#chaq的账号(agatha/scalding/util/GetSuspendedUsers.scala:12-27)。负例是其余活跃账号(LabelUtil.scala:46-61)。NSFW:正例是带NsfwHighPrecision、NsfwAvatarImage或NsfwBannerImage账号标签的账号(agatha/scalding/labels/nsfw/NSFWLabelExtraction.scala:16-30)。
从标签到 Blink 分
flowchart LR
G["图特征: 关注 / 点赞 / 转发 / 拉黑 / 举报 / 列表 / 简介词 等"] --> B["Build: 每个特征与标签的 PMI"]
L["标签: 率值 / SpamSuspended / NSFW"] --> B
B --> P["Predict: 留一法 PMI, 按特征类别取矩"]
P --> S["Blink 分 = (Σm0·m1 + ln 先验) / (Σm0 + 1)"]
S --> C["按活跃用户分布做百分位校准"]
C --> MH["写入 Manhattan, 供 GetUserHealthScores 读取"]
- 图特征来源有 24 种,包括关注、点赞、转发、回复、提及、拉黑、静音、举报、私信、列表、话题标签和简介词(
agatha/hub/agatha/thrift/featureSource.thrift:3-28)。 - Build:对每个 (特征, 标签) 对计算 PMI,即 ln[P(特征, 标签) / (P(特征)·P(标签))](
agatha/hub/pmi/math/Math.scala:95-101、agatha/hub/agatha/builder/Builder.scala:16-37)。训练窗口为 30 天(agatha/scalding/RateBasedBuildJob.scala:22-29、SpagathaBuildJob.scala:21-24)。 - Predict:对每个账号,用留一法取其各特征的 PMI,再按特征类别汇总成矩(moments)。预测窗口为 14 天(
agatha/hub/agatha/predict/Predict.scala:17-50、agatha/scalding/AgathaFeaturesPredictJob.scala:26-36)。 - Blink 分:对每个账号的每个标签,把各特征类别的 m0(计数)与 m1(均值)累加,再按下式合成(
BlinkScoreJobTemplate.scala:121-182):
blink = (Σ m0·m1 + ln(prior)) / (Σ m0 + 1)inbound子类特征只计入总分,不计入“外向”分(BlinkScoreJobTemplate.scala:123-125)。- 部分标签排除与自身同源的特征类别。例如,
ReportsPerFav不用拉黑图和举报图(BlinkScoreJobTemplate.scala:287-313)。推测目的是防止标签泄漏。 - 校准:以活跃账号为参照,分 1000 个分区做百分位校准(
BlinkScoreJobTemplate.scala:184-244)。校准后的分数可理解为百分位,0.99 约等于前 1%(推测)。 - 输出:任务把
AllSpamReportsPerFav、ReportsPerFav、SpamSuspended、SpamReportsPerFav四个标签的校准分写入 Manhattan(BlinkScoreJobTemplate.scala:315-320)。botmaker 规则通过GetUserHealthScores读取AgathaSpamSuspended等名称的分数(bot/ProductionAgatha__FilterOutOrganicNotifications.bot:11-14)。二者的对应关系由名称推断,源码未直接写明。
Agatha 分数的使用阈值(开源默认值)
| 规则 | 条件 | 结果 | 锚点 |
|---|---|---|---|
| bot 21278 | AgathaSpamSuspended > 0.99 或 AgathaAllSpamReportsPerFav > 0.999 或 AgathaSpamReportsPerFav > 0.999;非高 PageRank、非灰标、不在跳过名单 |
账号标签 AGATHA_SPAM_TOP_USER,7 天 |
botmaker-rules/scarecrow/bot/ProductionAgatha__FilterOutOrganicNotifications.bot:4-33 |
| bot 20998 | 发帖时 AgathaAllSpamReportsPerFav > 0.9975 或 AgathaSpamSuspended > 0.98;同样豁免 |
帖子标签 AGATHA_SPAM,7 天 |
botmaker-rules/scarecrow/bot/AgathaSpamProduction__ApplySearchTopTweetLabel.bot:3-28 |
AGATHA_SPAM_TOP_USER 只在通知相关的安全级别中触发丢弃(visibility-filtering/rules/levels.rs:89-100)。For You 两个级别的规则不读取 AGATHA_SPAM 帖子标签(visibility-filtering/rules/levels.rs:298-361);规则名提示它用于搜索。
BDSM:行为序列模型
BDSM 全称 Behavioral Detection Sequence Model(bdsm/README.md:1-5)。
- 模型:双向 Transformer 编码器,读取账号最近的动作序列。位置编码用按时间戳归一化的 RoPE(旋转位置编码),因此能表示动作间隔(
bdsm/README.md:17-25、bdsm/runtime/model.py:62)。 - 规格:256 种动作类型、8 个分类头、序列长度 512、嵌入宽度 1024(
bdsm/README.md:102-103)。 - 每个动作的特征:动作类型、产品界面、停留时长、设备/客户端信号、互动目标哈希等(
bdsm/README.md:26-29)。 - 8 个头:FollowBot、LikeBot、EngagementAmplifier、ReplySpamBot、TweetSpamBot、RTBot、MultiActionBot、LegitimateUser(
bdsm/README.md:30-34、bdsm/runtime/heads.py:115-118)。 - 训练:主干先做自监督训练(掩码属性预测,加每 6 步一次的对比学习),然后冻结;任务头单独训练(
bdsm/README.md:42-51)。 - 流水线:Kafka 动作事件 → Rust 累加器去重并入队 → 预取序列 → GPU 推理输出 8 维分数 → 结果汇(sink)按阈值决定动作(
bdsm/README.md:53-67)。
结果汇的判定结构
阈值是二元组 [tau, lambda]。某个机器人头的分数 ≥ tau,且 LegitimateUser 分数 ≤ lambda 时,才算命中(bdsm/runtime/score_results_sink_focal.py:851-857)。
| 通道 | 涉及的头 | 命中结果 | 锚点 |
|---|---|---|---|
| 硬执法 | FollowBot、EngagementAmplifier、RTBot | 标记 enforcement_threshold_reached,交给处置服务 |
score_results_sink_focal.py:821-980、sink_policy.yaml:17-20 |
| 临界(cusp) | EngagementAmplifier | 分数在阈值附近 ± cusp_delta 时发挑战标签(Arkose、验证码或活体检测) |
score_results_sink_focal.py:981-1035、:187-205 |
| 暂停-活体检测 | LikeBot、MultiActionBot | 活体检测 | score_results_sink_focal.py:1038-1098、sink_policy.yaml:26-28 |
| 垃圾弹回 | TweetSpamBot、ReplySpamBot | 弹回并挑战 | score_results_sink_focal.py:1100-1160、sink_policy.yaml:30-36 |
硬执法前有以下豁免门(score_results_sink_focal.py:836-920):
- 动作数少于
min_actions_for_enforcement的账号不执法。开源版把该值替换为 999999。 - 发帖动作占比 ≥ 50% 的账号不走硬执法(
:836-841)。 - FollowBot:取关多于关注时不算命中(
:853-854);开启年龄门时,新账号不算命中(:860-869);登录后短时间内通过入门推荐包完成的关注不算命中(:871-889);关注过入门推荐包的账号改为发挑战(:891-905、:946-961)。 - 开启检查时,跳过客户端停留数据缺失(dwell dropout)的账号(
:907-913)。
开源版的全部阈值与 min_actions_for_enforcement 都是占位值。生产值未公开(bdsm/README.md:104-115)。
user-cred-v2:关注图 PageRank
图与参数(开源默认值)
| 项 | 值 | 锚点 |
|---|---|---|
| 节点 | 未受限、未停用、未封禁、未抹除的账号 | user-cred-v2/ValidUserInfo.scala:21-41 |
| 边 | 关注边,质量从关注者流向被关注者;只有非 near-zero 状态的关注者能传出质量 | user-cred-v2/UserCredV2App.scala:287-318 |
| 去除的边 | 账号扩展调查(AccountExpansionInvestigations)认定的关联账号之间的边 |
UserCredV2App.scala:102-118 |
| 跳转概率 α | 0.2 | user-cred-v2/UserCredV2Config.scala:30 |
| 收敛阈值 | 0.001(L1 差) | UserCredV2Config.scala:31、UserCredV2App.scala:256-257 |
| 最大迭代 | 50 | UserCredV2Config.scala:32 |
| 互动跳转权重 β | 0.5 | UserCredV2Config.scala:35 |
| 互动窗口 | 7 天,点赞与转发 | UserCredV2Config.scala:25、UserCredV2App.scala:79-100 |
跳转分布
跳转(teleport)目标不是在全体账号上均匀分布(UserCredV2App.scala:141-193):
teleport(u) = (1 - β) × uniform_premium(u) + β × engagement(u)
uniform_premium:在非 near-zero 的付费/认证账号(蓝标、灰标、金标、认证机构及其关联号)上均分
engagement(u) = Σ_e mass_prev(e) × count(e→u) / count(e→·),再归一化含义:未认证账号拿不到均匀跳转质量。它们只能通过被关注、被有质量的账号点赞或转发来获得质量(依代码推断)。
迭代与分数
每轮迭代的计算如下(UserCredV2App.scala:320-390):
mass'(u) = (1 - α) × Σ_{f 关注 u} mass(f) / outdeg(f) + teleport(u) × (α + (1 - α) × 遗失质量)遗失质量指没有出边的节点未能分出去的质量。任务用上一轮快照做热启动(UserCredV2App.scala:200-215)。
分数换算(user-cred-v2/UserCredV2.scala:10-19):
score = min(100, max(0, 165.2 + 7.07 × ln(mass)))由公式反推(计算值,不是源码常量):score = 54 对应 mass ≈ 1.48e-7;score = 100 对应 mass ≈ 9.88e-5。
发布保护
结果先通过快照安全门。安全门检查三项:行数、分数 ≥ 55 的账号数(vitThreshold)与上一版相比变化不超过 5%;质量总和在 [0.99, 1.005] 之间。检查不通过时,任务把结果写入隔离区并失败退出(UserCredV2Config.scala:40-44、UserCredV2App.scala:392-430、user-cred-v2/SnapshotSafeguard.scala:21-64)。生产任务从 2026-04-27 起每天运行(UserCredV2App.scala:518-525)。
分数的使用方
- botmaker:
IsHighPageRankUser= 分数 ≥ 54 或带PREDICTED_HIGH_PAGE_RANK标签;没有分数时,改用粉丝数 > 25000 判断(botmaker-rules/scarecrow/derived-feature/IsHighPageRankUser.df:3-18)。公开的打标签规则普遍跳过这类账号,见 打标签规则。 - abuse-enforcement-service:规则中有
cred.is_high || cred.score >= ...跳过条件,开源值是占位值 12.34(abuse-enforcement-service/service-lib/rules/enforcement_user.yaml:26-33)。 - safety-label-user-agg:使用
highPageRankOrGreyBadge排除条件(safety-label-user-agg/safetyLabelToUserLevelAggregationV2Processor.strato:31-48)。
设计决策
| 决策 | 理由 |
|---|---|
| 比率用站外点赞做分母 | 熟人圈的点赞容易刷;站外点赞更能代表陌生人的真实反应 |
| 每个发起者最多计 100 个目标 | 防止少数人通过大批量拉黑或举报左右他人分数 |
| 贝叶斯平滑 + 先验 | 样本少的账号不会因一两次举报得到极端分数 |
| Blink 分做百分位校准 | 阈值可以写成“前 1%”这类稳定口径;模型更新不改变阈值含义 |
| PageRank 跳转偏向认证账号与互动 | 新注册的批量账号无法靠互相关注凭空获得质量 |
| 去除关联账号之间的边 | 已认定的关联账号不能互相抬分 |
| BDSM 阈值不公开 | 公开阈值和动作数下限会暴露规避边界 |
源码锚点
agatha/scalding/labels/rate_based_labels/RateBasedLabels.scala:29-118—— 率值标签与平滑公式agatha/scalding/labels/LabelUtil.scala:12-61—— 抽样截断、先验、正负例agatha/scalding/blink/BlinkScoreJobTemplate.scala:121-320—— Blink 分、校准、写入botmaker-rules/scarecrow/bot/ProductionAgatha__FilterOutOrganicNotifications.bot:11-33—— Agatha 阈值bdsm/README.md:17-115—— 模型、流水线、阈值脱敏说明bdsm/runtime/score_results_sink_focal.py:821-1160—— 各执法通道user-cred-v2/UserCredV2App.scala:141-390—— 跳转分布与 PageRank 迭代user-cred-v2/UserCredV2.scala:10-19—— mass 到分数
相关页面
- 打标签规则 —— 读取这些分数的 botmaker 规则
- 处置与标签聚合 —— 消费 BDSM 结果的处置服务
- 可见性过滤 —— 账号标签在推荐中的效果
- Under the Hood 透明度报告 —— 用户能看到哪些账号标签