从被产品经理按头做AI到调出SOTA:一个后端狗的模型调优血泪史

张桂英
2025-12-18 10:03
阅读 1440

上海凌晨两点,外滩的霓虹灯还亮着,我工位上的机械键盘却已经快被敲冒烟了。作为金融科技公司混了五年的“老后端”,本以为这辈子跟算法只会在代码 review 时打个照面,结果去年Q3硬是被拉进了AI项目组——就因为我说了一句“这模型效果不太行吧”。
唉,职场老油条都知道,有些话不能说太早。


事情得从去年双11前说起。我们风控团队要上线一个实时交易反欺诈模型,目标是在50ms内判断一笔跨境支付是否可疑。原本这事归算法组管,但那会儿他们正被另一个大模型项目压得喘不过气,CTO一拍脑袋:“老张(就是我),你不是喜欢看开源源码吗?帮他们搞搞工程化落地。”

我?一个连PyTorch和TensorFlow都分不太清的Java后端?行吧,为了年终奖,咬牙上了。

一、开局即地狱:数据乱得像我的租房合同

我们拿到的训练数据来自过去两年的交易日志,约2.3亿条,标签是人工审核后的“欺诈/非欺诈”。理想很丰满,现实直接给我上了一课:

  • 时间戳格式有5种(yyyy-MM-dd HH:mm:ssMM/dd/yyyy、甚至还有Excel序列号)
  • 金额字段里混着$1,234.561234.56 CNY¥1234.56
  • 用户设备信息有的是JSON,有的是字符串拼接,还有的直接是null

我当时就想@产品经理问一句:“你们当初埋点的时候是不是喝假酒了?”
但转念一想,算了,反正最后锅还是我们后端背。

于是花了整整一周做数据清洗。这里插一句,别信什么“AI时代数据工程师会消失” —— 没有干净的数据,再牛的算法都是在垃圾堆上盖别墅。

# 示例:金额字段标准化(真实代码简化版)
def parse_amount(amount_str):
    if pd.isna(amount_str):
        return 0.0
    # 去掉货币符号和逗号
    cleaned = re.sub(r'[^\d.]', '', str(amount_str))
    try:
        return float(cleaned)
    except ValueError:
        logger.warning(f"Invalid amount: {amount_str}")
        return 0.0

df['amount'] = df['raw_amount'].apply(parse_amount)

二、选模型:不是越新越好,而是越稳越香

算法同事甩给我一堆paper:Transformer、Graph Neural Network、TabNet……我看得头大。但咱干金融的,第一原则是稳定可解释,不是发顶会。

经过一番调研(其实就是翻了GitHub上几个star过万的项目源码),我们最终选了LightGBM + 特征交叉的组合。理由很朴实:

  • 训练快,调参相对直观
  • 支持类别特征原生处理(我们的device_type、country_code等字段不用one-hot炸维度)
  • 输出feature importance,方便给合规部门写报告

吐槽一句:运维大哥听说我们要跑GPU训练,差点把咖啡喷到服务器上。“你们后端现在也玩显卡了?”

三、调参:一场与随机种子的持久战

真正开始调优才发现,模型效果70%靠数据,20%靠特征,剩下10%才是算法本身。但那10%,往往决定你能不能准时下班。

我们最初用默认参数跑出来的AUC只有0.82,而业务要求至少0.88。接下来两周,我和算法实习生小李开启了“调参地狱”模式。以下是几个让我深夜抓狂又豁然开朗的关键点:

1. 别迷信Grid Search,先搞懂每个参数的意义

比如num_leaves,很多人设成2^max_depth,但LightGBM官方文档明确说这是错误做法!它会导致模型过拟合。正确的思路是:num_leaves < 2^max_depth

# 错误示范(很多教程还在这么写)
params = {
    'max_depth': 8,
    'num_leaves': 256  # 2^8 = 256 → 过拟合风险极高!
}

# 正确做法:控制叶节点数量
params = {
    'max_depth': 8,
    'num_leaves': 64   # 实际测试发现64比256效果好0.02 AUC
}

2. 学习率(learning_rate)和迭代次数(n_estimators)要联动调

一开始我把learning_rate=0.1n_estimators=100,结果训练曲线还没收敛就停了。后来改成learning_rate=0.01,配合early stopping,虽然训练时间翻倍,但AUC直接冲到0.89。

小技巧:用lgb.cv()做交叉验证时,记得设置stratified=True,尤其是像我们这种正负样本比例1:100的极度不平衡数据!

3. 类别特征处理:别傻乎乎地转成整数

我们的user_country有200多个取值。如果直接LabelEncoder成0~199,模型会误以为“中国(0)”和“美国(1)”比“中国(0)”和“巴西(30)”更相似——这显然不合理。

LightGBM支持直接传categorical_feature参数:

categorical_features = ['user_country', 'device_os', 'payment_method']
train_data = lgb.Dataset(
    X_train, 
    label=y_train,
    categorical_feature=categorical_features  # 关键!
)

这一招让AUC又涨了0.015,而且推理速度更快(内部用了直方图优化)。

四、特征工程:后端程序员的隐藏技能点

既然说到特征,必须提一下我们自己搞的几个“骚操作”。毕竟在金融场景,时序行为特征往往比静态属性更有杀伤力

比如,我们构造了一个叫hourly_fraud_ratio的特征:

# 过去1小时该国家的欺诈交易占比
hourly_stats = df.groupby(['country', pd.Grouper(key='timestamp', freq='1H')])['is_fraud'].agg(['sum', 'count'])
hourly_stats['fraud_ratio'] = hourly_stats['sum'] / hourly_stats['count'].clip(lower=1)

# merge回原表(注意时间对齐,避免未来信息泄露!)
df = df.merge(hourly_stats[['fraud_ratio']], on=['country', 'hour_bin'], how='left')

类似地,还做了:

  • 用户最近3笔交易金额的标准差(突然大额转账?警惕!)
  • 设备首次出现距今的小时数(新设备高风险)
  • IP地址的ASN归属地与注册地是否一致

这些手工特征贡献了近0.05的AUC提升。别小看“土法炼钢”,在垂直领域,domain knowledge永远是王道。

五、线上部署:从Notebook到生产环境的鸿沟

模型线下AUC 0.91,开心!结果一上线,P99延迟飙到200ms,直接触发告警。运维在群里@我:“老张,你这模型是要把支付系统拖垮啊?”

排查发现两个问题:

  1. 特征计算太重:有些聚合特征(如过去24小时交易次数)每次请求都要查DB,IO爆炸
  2. 模型文件太大:LightGBM导出的txt模型有80MB,加载慢且占内存

解决方案:

  • 把高频特征预计算存入Redis,TTL设为1小时
  • lgb.save_model("model.txt", num_iteration=bst.best_iteration)只保存最佳轮次
  • 最终模型压缩到12MB,加载时间<100ms
# 线上推理伪代码
def predict_fraud(transaction):
    # 1. 从缓存读取预计算特征
    cached_features = redis.get(f"user:{transaction.user_id}:features")
    
    # 2. 拼接实时特征(设备、IP等)
    real_time_features = extract_real_time_features(transaction)
    
    # 3. 合并特征向量
    full_features = np.concatenate([cached_features, real_time_features])
    
    # 4. 模型预测
    prob = model.predict([full_features])[0]
    return prob > THRESHOLD

六、效果对比:数字不说谎

折腾两个月,终于上线。以下是关键指标对比:

指标 旧规则引擎 新AI模型 提升
AUC 0.76 0.91 +19.7%
欺诈拦截率 68% 89% +21%
误杀率 1.2% 0.7% -41.7%
P99延迟 35ms 48ms +13ms(仍在SLA内)

最爽的是,上周五产品总监请全组喝奶茶,说双11期间模型成功拦截了3笔百万级盗刷——那一刻觉得加班都值了。

写在最后:代码人生,不止于CRUD

回头看这段经历,与其说是“AI模型调优”,不如说是一次全栈能力的被迫升级。从数据清洗到特征工程,从算法选择到线上压测,每一步都在打破“后端只写API”的舒适区。

现在我已经养成了习惯:每天早上先看一眼模型监控大盘,就像以前检查服务健康度一样自然。甚至开始对前端同事做的可视化图表有点兴趣——毕竟,好的模型效果需要被看见才能创造价值

如果你也是被“赶鸭子上架”的后端,别慌。记住三点:

  1. 别怕问蠢问题:我第一次问“什么是AUC”时,算法同事眼睛都瞪圆了,但后来成了好搭档
  2. 善用开源:LightGBM、XGBoost的GitHub issue区藏着无数宝藏经验
  3. 保持怀疑:任何“一键提升效果”的教程,背后都有你看不到的前提条件

技术分享写到这里,窗外天都亮了。赶紧保存草稿,回去补觉——明天还得改产品经理新提的“模型可解释性增强”需求呢(手动狗头)。


P.S. 本文所有代码均经过脱敏处理,如有雷同,纯属巧合。模型效果受业务场景限制,不构成投资建议(笑)。

评论 0

最热最新
暂无评论
张桂英Lv.1
0
影响力
0
文章
0
粉丝