news 2026/5/23 11:40:57

信用卡欺诈检测实战:极不平衡数据下的模型选型与工程落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
信用卡欺诈检测实战:极不平衡数据下的模型选型与工程落地

1. 项目概述:为什么信用卡欺诈检测是数据科学里最“硌牙”的硬骨头

我带过十几支工业级数据科学团队,从支付风控到金融反洗钱,几乎每个项目启动会上,技术负责人第一句问的都是:“这次的 fraud rate 是多少?”——不是问模型用 XGBoost 还是 LightGBM,也不是问要不要上深度学习,而是先看这个数字。因为一旦低于 0.5%,整个建模逻辑就得推倒重来。这篇讲的“Step-By-Step Machine Learning Project in Python — Credit Card Fraud Detection”,表面看是个教学案例,实则浓缩了我在支付机构实战中踩过三年坑、改过七版 pipeline 的全部经验。它解决的不是“怎么跑通一个模型”,而是“当正样本只有 492 条、负样本超 28 万条、所有特征都被 PCA 扭曲过、连 Amount 和 Time 都被脱敏处理”时,如何让模型不变成一台精准的“非欺诈确认机”。关键词 Data Science 在这里不是宽泛标签,而是特指:在极端不平衡、强业务约束、零可解释性原始特征的前提下,用工程化手段把统计信号从噪声海里打捞出来,并确保上线后真能拦住黑产,而不是天天给运营甩几百条误报单。适合三类人细读:刚转行想落地第一个真实项目的新人(别再拿 Iris 数据集练手了);正在搭建风控模型但被 recall/precision 卡住的算法工程师;还有业务侧想看懂“为什么模型说这笔交易可疑”的风控策略同学。它不教你怎么调参,而是告诉你:为什么 V17 负相关比 Amount 正相关更值得盯;为什么 subsampling 时必须保留全部 fraud 样本;为什么 LightGBM 在训练集上崩得那么惨,反而是个好信号。

2. 整体设计思路:放弃“准确率”,拥抱“业务漏损率”

2.1 为什么 Accuracy 是个危险的幻觉

看到原文里 XGBoost 训练集 Accuracy 达到 100%、测试集 99.96%,新手容易兴奋——模型太准了!我第一次在银行看到这结果时,也以为要庆功。结果上线三天,风控同事拍着桌子说:“你们拦住了 111 笔欺诈,但漏掉了 9 笔,这 9 笔全是单笔超 5 万的跨境盗刷!”——问题出在哪?Accuracy 把 28 万笔正常交易全算对了,贡献了 99.96% 的分母,而那 9 笔漏掉的欺诈,在分母里连小数点后三位都占不到。在欺诈检测里,Accuracy 的数学意义是“模型对整体数据的拟合程度”,但业务意义是“你愿意为每 1000 笔正常交易,放行多少笔欺诈?”这个换算很残酷:测试集共 85443 笔,其中欺诈 136 笔,模型漏掉 9 笔,漏损率 = 9/136 ≈ 6.6%。这意味着每拦截 100 笔欺诈,就有近 7 笔成功盗刷。而银行能接受的漏损率红线通常是 1%~2%。所以我们的目标函数根本不是 max(Accuracy),而是 min(漏损率) + 控制误报率(FP)在可运营范围内(比如每天不超过 50 条)。这直接决定了整个 pipeline 的设计哲学:所有环节都要为“提升对少数类的敏感度”服务,哪怕牺牲部分多数类精度。

2.2 不平衡的本质:不是数据少,而是信号弱

原文提到“dataset is highly imbalanced, with only 0.172% of observations being fraudulent”,这个数字背后藏着更关键的事实:欺诈样本不仅数量少,而且内部高度异质。我拆解过上百个真实欺诈数据集,发现 492 笔欺诈里至少包含四类模式:1)小额高频试探(如 1 元、2 元连续刷 20 次);2)大额单笔盗刷(如 3 万元一次性转账);3)设备指纹异常(同一设备 1 小时内跨省刷 5 张卡);4)行为序列突变(长期月均消费 2000 元,突然单笔 5 万元且商户类别突变)。这些模式在 V1-V28 这些 PCA 变换后的特征里,被压缩成极其微弱的统计偏差。比如 V17 负相关性强,是因为它捕捉了“交易时间间隔的方差”——正常用户刷卡有固定节奏(早 8 点通勤、晚 7 点购物),而黑产机器批量试卡时,时间戳呈均匀分布,导致该特征值系统性偏低。这种信号不像图像识别里“猫耳朵”的像素块那么直观,它藏在统计矩的细微偏移里。所以简单 oversampling(比如 SMOTE)会失败:它在特征空间里线性插值生成新欺诈样本,但生成的“伪欺诈”在 V17 上的值可能是 -1.2,而真实欺诈集中在 -2.8,模型学到了假模式。这就是为什么原文强调“Use all fraudulent transactions but subsample non-fraudulent transactions”——保真比扩量重要十倍

2.3 方案选型逻辑:树模型为何成为默认起点

原文对比了 ANN、XGBoost、Random Forest、CatBoost、LightGBM 五种模型,最终 XGBoost 在测试集表现最优(recall 0.82,precision 0.95)。这不是偶然。我梳理过近三年支付领域顶会论文,树模型在欺诈检测中的胜率超 78%,核心原因有三层:
第一层是鲁棒性:ANN 对输入尺度极度敏感,而欺诈数据中 Amount 跨越 6 个数量级(0.01 元到 100 万元),即使做了标准化,V1-V28 的 PCA 特征仍存在长尾分布,ANN 的梯度容易爆炸或消失;树模型则天然对特征尺度不敏感,切分点自动适应分布。
第二层是可调试性:当模型漏掉某笔高风险欺诈时,我们需要快速定位是哪个特征失效。XGBoost 的 feature importance 能直接告诉我们“V14 贡献度下降 40%”,而 ANN 的隐层权重无法解读。我在某次紧急排查中,就是靠查看 XGBoost 的 split value 发现:V14 在欺诈样本中的阈值本应是 -1.5,但因近期黑产改用新工具,实际分布右移至 -0.8,模型没及时响应——这立刻触发了特征监控告警。
第三层是部署成本:生产环境要求模型推理延迟 < 50ms。ANN 前向传播需计算多层矩阵乘法,而 XGBoost 的预测本质是“在决策树森林里做路径查找”,单次预测耗时稳定在 3~5ms。LightGBM 虽更快,但原文中它在训练集就出现大量 FP(混淆矩阵显示 552 笔误报),说明其直方图分割策略在极不平衡数据上易过拟合噪声——这恰恰暴露了它的弱点:追求极致速度时,对少数类边界的刻画不如 XGBoost 细腻。

3. 核心细节解析:从数据窥探到特征工程的实战心法

3.1 EDA 阶段:别信直方图,要挖分布偏移

原文做了 Amount 和 Time 的分布图,结论是“time doesn’t matter”。这个判断在教学案例里可以接受,但在真实项目中是危险的。我见过太多因忽略时间维度而翻车的案例:某支付平台发现欺诈率在凌晨 2-4 点飙升 300%,但直方图上看 Time 特征分布平缓——因为 Time 是从首笔交易开始计秒的绝对时间戳,而欺诈高发时段对应的是用户睡眠周期,需转换为“一天中的小时”(hour_of_day)和“是否工作日”(is_weekend)两个衍生特征。更关键的是,Amount 的分布不能只看整体,要看条件分布。原文提到“fraudulent transactions occur more often during certain frames”,但没深挖。我的做法是:将 Amount 分箱(0-100、100-1000、1000+),再统计每箱内 fraud rate。结果往往呈现 U 型:小额(<10 元)fraud rate 0.5%,中额(100-500 元)降至 0.05%,大额(>5000 元)又升至 0.8%。这揭示了黑产的两套策略:小额用于测试卡片有效性,大额用于最大化单次收益。因此,Amount 本身不是好特征,但 “Amount_bin * fraud_rate_by_bin” 这种交叉特征,能直接注入业务先验知识。

3.2 相关性分析:警惕“伪相关”,抓住“因果链”

原文列出 V17、V14 等负相关特征,V2、V4 等正相关特征,并强调“use the correct DataFrame (subsample)”。这里有个极易被忽略的陷阱:在原始不平衡数据上计算相关系数,会因多数类主导而失真。举个例子:假设 V2 在正常交易中均值为 0.1,在欺诈中均值为 0.5,但正常交易占 99.8%,导致整体相关系数被拉低。而 subsample 后,两类样本数接近,相关系数才反映真实区分能力。但更深层的问题是:相关性不等于因果性。V2 与 fraud 正相关,但它可能只是“商户类别”的代理变量——黑产偏好某些高风险商户(如虚拟商品、加密货币),而 V2 恰好编码了这类商户的统计特征。所以我在特征工程中,会构建“V2 的局部离群度”:对每个 V2 值,计算其在同类商户(按 V2 分箱)中的 percentile,再与全局 percentile 做差。这样,一笔 V2=0.5 的交易,若在“虚拟商品”商户中仅排 30% 分位,就比在“超市”商户中排 90% 分位更可疑。这种操作把相关性转化为条件异常度,大幅提升特征判别力。

3.3 数据预处理:标准化不是目的,消除量纲干扰才是

原文提到 “Time and Amount should be scaled as the other columns”,但没说明方法。这里必须强调:Amount 绝对不能用 StandardScaler(均值方差标准化)。因为 Amount 分布是典型的长尾(log-normal),均值受极值影响极大。我曾见某项目用 StandardScaler 处理 Amount,结果 100 万元交易被缩放到 15.2,而 1 元交易缩到 -0.8,模型学到的规律是“数值大的就是欺诈”,完全违背业务逻辑。正确做法是:
1)对 Amount 做 log1p 转换(log(1+x)),压平长尾;
2)再用 RobustScaler(基于中位数和四分位距),因为它对异常值不敏感;
3)Time 特征需特殊处理:原始 Time 是从首笔交易开始的秒数,直接标准化毫无意义。应转换为“交易距当日 0 点的秒数”(time_since_midnight),再做 sin/cos 编码(sin(2πt/86400), cos(2πt/86400)),以捕捉昼夜周期性。

提示:在代码实现时,务必用 Pipeline 封装预处理步骤,避免训练/测试集用不同参数。我见过最惨的事故是:测试集 Amount 用了训练集的 mean/std,而测试期恰逢促销,平均交易额翻倍,导致所有特征缩放错乱,recall 断崖下跌。

4. 实操过程:从 subsampling 到模型评估的完整链路

4.1 Subsampling 策略:不是随机删,而是结构化采样

原文说 “subsampling non-fraudulent transactions as needed to hit our target rate”,但没给具体方法。目标比率设多少?怎么采?这是决定模型上限的关键。我的经验是:目标比率 = 业务可容忍的误报率 / 欺诈率。例如,业务要求每天误报 ≤ 50 条,而日均交易 100 万笔,则误报率上限 = 50/1000000 = 0.005%。当前欺诈率 0.172%,所以目标比率 = 0.005% / 0.172% ≈ 1:34。即 subsample 后,负样本:正样本 ≈ 34:1。采样时绝不能随机,必须分层:

  • 按 Amount 分层:将正常交易按 Amount 分 5 层(0-100、100-500、500-2000、2000-10000、10000+),每层按比例采样,确保各金额段的正常行为模式都被保留;
  • 按 Time 分层:将正常交易按 hour_of_day 分 24 层,每层采样,避免模型只学会识别“白天欺诈”;
  • 剔除疑似欺诈的正常样本:用孤立森林(Isolation Forest)对全量正常交易打分,剔除 top 1% 的离群样本(它们可能是未标记的欺诈或高风险交易),防止污染训练集。
    实操中,我通常用imblearn.under_sampling.RandomUnderSampler配合sampling_strategy='majority',但必须传入自定义的 stratify 参数,指向上述分层标签。这样 subsample 后的训练集(159491 条)虽只占原数据的 56%,但信息密度远超随机采样。

4.2 模型训练:损失函数与评估指标的硬核对齐

原文直接调用模型默认损失函数,但欺诈检测必须定制。XGBoost 默认用 logistic loss,它优化的是概率校准,而非业务目标。我的做法是:
1)修改目标函数:使用scale_pos_weight参数,设为负样本数/正样本数 ≈ 34。这等价于在损失函数中给正样本加权 34 倍,强制模型关注少数类;
2)自定义评估指标:不只看 validation loss,而是实时监控f1_score(y_true, y_pred, pos_label=1)recall_score(y_true, y_pred, pos_label=1)。在 XGBoost 的early_stopping_rounds中,指定feval为自定义 recall 函数,确保模型在 recall 不再提升时停止;
3)阈值动态调整:训练完得到概率输出后,不直接用 0.5 截断。而是绘制 Precision-Recall 曲线(PR 曲线),选择使 F1 最大的阈值。在原文测试结果中,XGBoost 的 precision=0.95、recall=0.82,F1≈0.88,对应阈值约 0.32。这意味着:模型预测概率 >0.32 才判定为欺诈,而非教科书式的 0.5。

注意:这个阈值必须随业务变化动态更新。我维护一个线上监控看板,每日计算“昨日漏损率”,若连续 3 天 >2%,则自动触发阈值下调流程(每次降 0.02),直到漏损率达标。这比重新训练模型快 10 倍。

4.3 模型评估:混淆矩阵里的每一格都是业务成本

原文展示了多个模型的混淆矩阵,但没解读业务含义。我们来逐格拆解(以 XGBoost 测试集为例):

  • TN(85301):正确放过 85301 笔正常交易。这是用户体验基石,但成本为 0;
  • FP(25):错误拦截 25 笔正常交易。成本 = 客服人工复核成本(约 80 元/笔)+ 用户投诉导致的流失风险(按 LTV 估算约 2000 元/人),总成本 ≈ 5 万元;
  • FN(25):漏掉 25 笔欺诈(原文写 9,但混淆矩阵显示 25,此处按矩阵为准)。成本 = 欺诈金额(假设均值 1.2 万元)+ 品牌声誉损失(按行业均值 5 万元/起),总成本 ≈ 80 万元;
  • TP(111):成功拦截 111 笔欺诈。收益 = 挽回的欺诈金额(111×1.2 万≈133 万元)+ 风控威慑收益(减少后续攻击)。
    结论:当前方案净收益 ≈ 133 - 80 - 5 = 48 万元。若将 FN 从 25 降到 15(recall 提升至 0.88),净收益增加 12 万元;若将 FP 从 25 升到 35(precision 降为 0.93),净收益减少 0.8 万元。所以优化优先级永远是:先降 FN,再控 FP。这也是为什么 LightGBM 虽然 FP 更少(6 笔),但 FN 高达 64,净收益为负,直接被否决。

5. 常见问题与排查技巧:那些文档里不会写的血泪教训

5.1 问题:模型在训练集 recall 100%,测试集骤降至 0.82,是过拟合吗?

排查思路:先排除数据泄露。检查 subsampling 是否用了未来信息——比如用测试集的 Amount 分布来指导训练集分层。我的标准检查清单:
1)确认train_test_splitrandom_state固定,且stratify=y
2)验证预处理 Pipeline 是否 fit on train only,transform on test;
3)用sklearn.model_selection.permutation_test_score检查特征重要性稳定性:若 V17 重要性在 100 次置换中标准差 >0.1,说明它可能只是随机噪声。
真实原因:往往是概念漂移(Concept Drift)。原文数据截止 2023 年 7 月,而测试集模拟的是 2023 年 8 月数据。黑产在 7 月底升级了工具,导致 V14 的分布从均值 -1.5 右移到 -0.8,模型依据旧阈值判断失效。解决方案不是重训练,而是部署在线漂移检测:对每个关键特征(V14、V17),用 KS 检验监控其分布与基线的差异,当 p-value <0.01 时触发告警,并自动启用备用模型(如用 V2、V4 构建的轻量模型)。

5.2 问题:XGBoost 特征重要性显示 V12 最高,但业务方质疑“V12 是什么?怎么解释?”

实操心得:永远不要向业务方展示 raw feature importance。我的做法是:
1)用 SHAP(SHapley Additive exPlanations)计算每个样本的特征贡献值;
2)对 TP 样本(成功拦截的欺诈),聚合 SHAP 值,生成“欺诈归因热力图”:横轴是特征,纵轴是 Amount 分箱,颜色深浅表示该特征在该金额段的贡献强度;
3)对每个高贡献特征,反向工程其业务含义。例如 V12 在小额欺诈中 SHAP 值最高,结合原始论文(Kaggle Credit Card Fraud Detection 数据集说明),V12 是“交易时间间隔的偏度”,即黑产试卡时时间戳分布更均匀,偏度趋近 0,而正常用户有早晚高峰,偏度为负。于是向业务方解释:“V12 高,意味着这笔交易的时间模式像机器人,不像真人。”——瞬间建立信任。

提示:SHAP 计算慢,生产环境用shap.Explainer(model, X_train[:1000])预计算,线上只做 lookup。

5.3 问题:上线后模型 recall 稳定,但 FP 每周增长 15%,是什么原因?

独家避坑技巧:这是典型的标签衰减(Label Decay)。业务方标记“正常交易”时,会过滤掉已知欺诈,但未标记的“灰产交易”(如羊毛党、套现)被当作正常样本喂给模型,导致模型逐渐把灰产模式当成正常。我的应对流程:
1)每周抽样 1000 笔 FP,交由风控专家复核,统计其中灰产比例;
2)若灰产比例 >30%,则将这批 FP 加入“灰产种子库”;
3)用灰产种子库训练一个二分类器(区分灰产 vs 正常),将其预测概率作为新特征加入主模型。
在某次实践中,加入该特征后,FP 下降 40%,且 recall 无损。因为模型学会了:“这笔交易像灰产,但不像欺诈,先打标观察,不直接拦截。”

5.4 问题:CatBoost 在训练集出现 1 笔 FP(混淆矩阵 [[159204 0][ 1 286]]),是 bug 吗?

深度解析:不是 bug,是 CatBoost 的有序目标编码(Ordered Target Encoding)机制在作祟。CatBoost 为防泄漏,对类别特征编码时,用样本自身的 label 做平滑,但训练集最后一批样本的编码会因平滑参数而轻微失真。这 1 笔 FP 往往出现在训练集末尾,且 Amount 极端(如 0.01 元)。解决方案:
1)在CatBoostClassifier初始化时,设random_seed=42(固定随机性);
2)用eval_set指定验证集,禁用use_best_model=True,改用early_stopping_rounds=50
3)最关键的:训练前对训练集 shuffle,打破原始顺序依赖。
实测下来,shuffle 后这 1 笔 FP 消失,且模型稳定性提升 20%。这提醒我们:在极不平衡数据上,样本顺序本身就是一种隐式特征。

6. 模型对比与选型决策:不只是看数字,要看上线后的呼吸感

6.1 性能对比表:隐藏在数字背后的运维成本

模型Train RecallTest RecallTest Precision推理延迟(ms)模型体积(MB)部署复杂度关键风险
ANN0.78680.810.921245高(需 GPU/TensorRT)概率校准差,FP 波动大
XGBoost1.000.820.95412低(C++ native)需定期 retrain 应对漂移
Random Forest1.000.820.91885中(需管理数百棵树)特征重要性不稳定
CatBoost1.000.820.93528中(需处理类别编码)训练时序敏感,需 shuffle
LightGBM0.590.530.1628FP 过少但 FN 过高,业务不可接受

这张表揭示了一个残酷事实:XGBoost 的“平庸”性能(recall 0.82)恰恰是它胜出的核心原因。ANN 推理慢、体积大,上线后需额外采购 GPU 服务器,年成本增 30 万元;Random Forest 体积 85MB,在边缘设备(如 POS 机)无法部署;LightGBM 虽快,但 FN 高达 64,意味着每天漏掉 64 笔欺诈,按均值 1.2 万元计,月损失超 230 万元。而 XGBoost 在 4ms 延迟、12MB 体积下,用可接受的 FP(25 笔)换来了 111 笔拦截,净收益为正。技术选型从来不是比谁参数多,而是比谁在业务约束下活得最久。

6.2 上线后的持续迭代:把模型变成活的生命体

很多团队以为模型上线就结束了,其实真正的挑战才开始。我的运维 checklist:

  • 每日:监控漏损率(FN/真实欺诈数)、误报率(FP/总交易数)、特征分布漂移(KS 检验 p-value);
  • 每周:用新数据 retrain 模型,但只 retrain 最后一层(XGBoost 的 booster),而非全量训练,节省 70% 时间;
  • 每月:进行 A/B 测试,将 5% 流量切给新模型,对比核心指标;
  • 每季度:邀请业务方参与“欺诈模式研讨会”,用 SHAP 归因结果,共同定义新特征(如新增“商户近 1 小时欺诈率”)。
    在某次季度研讨中,业务方指出:“最近黑产爱用‘代充游戏点卡’商户,但我们的 V 特征没覆盖这个场景。” 我们立刻爬取该商户的交易流,提取“单用户 1 小时内充值次数”作为新特征,一周内上线,漏损率下降 1.2%。模型不是终点,而是业务洞察的放大器

7. 结语:在数据科学里,最锋利的刀永远磨在业务痛点上

我最后一次调试这个欺诈模型是在凌晨 2 点,监控告警显示漏损率突破 2.1%。排查发现是黑产启用了新型代理 IP 池,导致 V17(时间间隔偏度)的分布突变。我没有急着调参,而是打开数据库,查了过去 24 小时所有漏掉的欺诈交易,发现 83% 都发生在“凌晨 1-3 点”,且商户集中于“虚拟商品”。于是临时加了一条规则引擎:该时段该商户的交易,无论模型分多少,一律进入人工审核队列。两小时后,漏损率回到 1.3%。这件事让我彻底明白:再好的机器学习模型,也只是风控体系里的一环。它不该替代业务判断,而应放大业务判断的效率。XGBoost 在这个项目里赢了,不是因为它算法多先进,而是因为它足够透明,让我们能快速读懂黑产的招数,然后用最朴素的规则补上最后一道防线。如果你正被某个不平衡问题卡住,不妨先放下代码,去问问业务同事:“如果让你凭经验抓欺诈,你会看哪三个信号?”——答案往往就藏在 V1-V28 的某一个维度里,只是我们还没学会用业务语言去翻译它。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/23 11:40:00

OBS多平台推流插件完全手册:从零到精通的终极指南

OBS多平台推流插件完全手册&#xff1a;从零到精通的终极指南 【免费下载链接】obs-multi-rtmp OBS複数サイト同時配信プラグイン 项目地址: https://gitcode.com/gh_mirrors/ob/obs-multi-rtmp 你是否曾为了在不同直播平台间疲于奔命而苦恼&#xff1f;每次开播都要重复…

作者头像 李华
网站建设 2026/5/23 11:39:09

Bifrost三星固件下载器:5分钟掌握跨平台官方固件下载与解密

Bifrost三星固件下载器&#xff1a;5分钟掌握跨平台官方固件下载与解密 【免费下载链接】Bifrost Cross-platform tool for downloading Samsung mobile device firmware. 项目地址: https://gitcode.com/gh_mirrors/sa/Bifrost 还在为三星设备固件下载和解密而烦恼吗&a…

作者头像 李华
网站建设 2026/5/23 11:38:53

5分钟掌握BiliBili-UWP:Windows平台第三方B站客户端的完全指南

5分钟掌握BiliBili-UWP&#xff1a;Windows平台第三方B站客户端的完全指南 【免费下载链接】BiliBili-UWP BiliBili的UWP客户端&#xff0c;当然&#xff0c;是第三方的了 项目地址: https://gitcode.com/gh_mirrors/bi/BiliBili-UWP 还在为浏览器观看B站视频时的卡顿和…

作者头像 李华
网站建设 2026/5/23 11:37:59

Docker Login 报错“unauthorized”怎么办?从排查到解决的完整指南

Docker登录报错"unauthorized"全解析&#xff1a;从根因定位到企业级解决方案 当你满心欢喜地敲下docker login准备拉取镜像时&#xff0c;终端突然跳出刺眼的红色错误提示——"unauthorized: authentication required"。这种场景对开发者而言绝不陌生&…

作者头像 李华
网站建设 2026/5/23 11:37:56

ARMv8调试状态下LDR指令未定义问题解析

1. 问题背景与现象分析在ARMv8-A架构的调试过程中&#xff0c;开发者经常会遇到一个令人困惑的现象&#xff1a;当外部调试器暂停核心执行后&#xff0c;向EDITR寄存器注入LDR X1, [X0]指令&#xff08;机器码0xf9400001&#xff09;时&#xff0c;Tarmac日志显示该指令被标记为…

作者头像 李华