1. 项目概述:当LSTM遇上广告点击率预测
广告点击率(CTR)预测一直是数字营销领域的核心课题。传统逻辑回归模型在捕捉用户行为序列特征时显得力不从心,这正是LSTM(长短期记忆网络)大显身手的场景。这个毕业设计项目采用PyTorch构建LSTM模型,配合Flask构建轻量级Web服务,实现了端到端的CTR预测系统。我在处理千万级用户行为日志时发现,合理的特征工程比模型结构优化更能提升预测准确率——这也是大多数教程不会告诉你的实战经验。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心技术栈解析
2.1 LSTM网络的特殊优势
广告点击行为具有明显的时间依赖性:用户早上浏览的商品会影响晚间看到的广告效果。普通RNN存在梯度消失问题,而LSTM通过遗忘门、输入门、输出门的三门结构(见图1),能够有效捕捉长期依赖。在项目中使用PyTorch实现的LSTM层配置如下:
python复制self.lstm = nn.LSTM(
input_size=embedding_dim,
hidden_size=128,
num_layers=2,
batch_first=True,
dropout=0.2
)
关键参数说明:hidden_size过大会导致过拟合,实测128维在保持精度的同时训练速度最快;num_layers=2让网络具备浅层特征提取能力;dropout是应对广告数据稀疏性的必备措施。
2.2 大数据处理流水线
原始广告日志平均每天产生15GB的JSON数据,采用Spark进行预处理比传统Pandas快20倍以上。核心优化点包括:
- 使用
spark.sql.functions.from_json()直接解析嵌套JSON - 对user_id进行
StringIndexer编码避免哈希冲突 - 采用
FeatureHasher而非One-Hot处理高基数类别特征
scala复制val pipeline = new Pipeline()
.setStages(Array(
new StringIndexer().setInputCol("user_id").setOutputCol("user_index"),
new FeatureHasher()
.setInputCols(Array("user_index", "ad_cat", "device_type"))
.setOutputCol("features")
.setNumFeatures(1024)
))
2.3 Flask服务化设计
模型服务化面临两个挑战:低延迟要求和动态特征拼接。我的解决方案是:
- 使用
Flask-Caching缓存高频用户特征 - 采用异步路由处理批量预测请求
- 通过
gunicorn配置20个worker实现200QPS
python复制@app.route('/predict', methods=['POST'])
@cache.cached(timeout=300, query_string=True)
def predict():
user_seq = request.json['behavior_seq'] # 获取用户最近30次行为
dynamic_feats = preprocess(user_seq) # 实时特征工程
with torch.no_grad():
pred = model(dynamic_feats)
return jsonify(prob=float(pred[0][1])) # 返回点击概率
3. 关键实现细节
3.1 特征工程黄金法则
广告CTR预测的特征可分为三类(见表1),其中交叉特征对提升AUC效果最显著:
| 特征类型 | 示例 | 处理方式 |
|---|---|---|
| 用户静态特征 | 性别、年龄、地域 | Embedding + 归一化 |
| 动态行为序列 | 最近点击的广告ID序列 | LSTM自动编码 |
| 上下文特征 | 时段、设备、网络环境 | 离散化 + 交叉组合 |
通过featuretools自动生成"用户年龄_广告类别"等交叉特征后,模型AUC提升了0.12。
3.2 正负样本失衡对策
实际数据中点击样本占比不足1%,采用三种方法组合应对:
- 过采样:对正样本使用SMOTE算法生成合成样本
- 损失函数加权:在PyTorch中设置
pos_weight=torch.tensor([50.]) - 评估指标优化:改用PR-AUC而非常规AUC
python复制criterion = nn.BCEWithLogitsLoss(
pos_weight=torch.tensor([50.], device=device)
)
3.3 在线学习机制
为适应广告模式变化,设计了模型热更新方案:
- 每小时用新数据计算特征统计量(均值/方差)
- 每日增量训练:加载全量模型参数,仅反向传播新数据
- 模型切换采用蓝绿部署模式,通过Flask路由控制流量
python复制def online_train(new_data):
model.train()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-5)
for batch in DataLoader(new_data, batch_size=512):
loss = criterion(model(batch.features), batch.labels)
loss.backward()
optimizer.step()
torch.save(model.state_dict(), f"model_{datetime.now().date()}.pt")
4. 避坑指南与性能优化
4.1 内存爆炸问题排查
初期训练时遇到GPU内存溢出,通过以下步骤定位问题:
- 使用
torch.cuda.memory_allocated()监控显存 - 发现数据加载器未释放原始文本字段
- 最终方案:在Dataset类中即时转换为数值特征
python复制class CTRDataset(Dataset):
def __init__(self, df):
self.features = torch.FloatTensor(
df.drop(['raw_text'], axis=1).values
)
4.2 预测延迟优化
从初始的500ms降至80ms的关键操作:
- 将Flask默认JSON解析器替换为
orjson - 对模型进行TorchScript编译
- 使用
nvidia-tensorrt优化推理计算图
python复制traced_model = torch.jit.trace(model, example_input)
torch.jit.save(traced_model, "ctr_prediction.pt")
4.3 可解释性增强
业务方常质疑模型决策逻辑,我们采用SHAP值进行解释:
- 对LSTM输出层梯度进行积分
- 计算特征重要性时考虑时间衰减因子
- 可视化工具选用
shap.force_plot
python复制explainer = shap.GradientExplainer(
model,
train_loader.dataset[:1000],
batch_size=32
)
shap_values = explainer.shap_values(test_sample)
5. 扩展方向与实用建议
5.1 冷启动解决方案
对于新广告/新用户,采用以下策略组合:
- 基于内容的相似度匹配(广告画像余弦相似度)
- 小样本学习(Prototypical Networks)
- 默认展示高热广告并收集反馈数据
5.2 工程化部署要点
生产环境部署必须注意:
- 模型版本管理:使用MLflow跟踪实验
- 请求限流:Flask-Limiter防止恶意访问
- 监控报警:Prometheus采集预测延迟指标
yaml复制# docker-compose.yml部分配置
services:
model_service:
deploy:
resources:
limits:
cpus: '2'
memory: 4G
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
5.3 学术与工业的差异
学校项目常忽略但工业界必备的考量:
- 特征漂移检测(用KL散度监控特征分布变化)
- 对抗训练(添加FGSM对抗样本)
- 模型压缩(知识蒸馏到浅层网络)
这个项目最让我意外的发现是:简单的用户最近点击广告ID序列特征(经过LSTM编码)的预测效果,竟然比精心设计的300维人工特征组合还要好。这印证了深度学习"让数据自己说话"的核心思想——当然前提是有足够多的数据和恰当的网络结构。
