Python大数据实战(四):广告点击转化率预测——基于腾讯社交广告的pCVR建模实战
Python大数据实战(四):广告点击转化率预测——基于腾讯社交广告的pCVR建模实战
文章目录
- Python大数据实战(四):广告点击转化率预测——基于腾讯社交广告的pCVR建模实战
- 前言
- 一、项目全景概览
-
- 1.1 项目目标
- 1.2 业务背景
- 1.3 项目流程架构图
- 1.4 数据集特征一览
- 二、数据理解与业务洞察
-
- 2.1 转化回流机制
- 2.2 数据加密说明
- 三、环境准备与数据加载
-
- 3.1 导入依赖库
- 3.2 加载数据集
- 四、数据分析(EDA)
-
- 4.1 每日点击行为分析
- 4.2 每日安装数分析
- 4.3 每日转化率分析
- 4.4 用户维度分析
- 4.5 App维度分析
- 五、特征工程
-
- 5.1 自定义工具函数库
- 5.2 应用特征工程
- 5.3 特征合并与编码
- 六、建模与评估
-
- 6.1 随机森林算法原理
- 6.2 模型训练
- 6.3 模型评估
- 6.4 超参数调优(GridSearchCV)
- 七、踩坑记录与解决方案
-
- 🐛 坑1:延迟转化导致标签噪声
- 🐛 坑2:高基数类别特征导致维度爆炸
- 🐛 坑3:LogLoss 评估时预测概率出现 0 或 1
- 八、模型优化方向
-
- 8.1 当前模型性能总结
- 8.2 后续优化路线
- 九、总结与展望
-
- 9.1 本文核心要点
- 9.2 完整代码获取
- 9.3 下一篇预告
- 参考链接
前言
“投了10万广告费,到底带来了多少真实用户?”——这是每个广告主都想知道的问题。
在广告系统中,曝光 → 点击 → 转化 构成了完整的投放漏斗。然而,大多数广告系统受数据回流限制,只能以曝光或点击作为优化目标。如果能提前预测用户点击广告后的转化概率(pCVR),就能把预算花在"最可能转化"的用户身上,ROI直接翻倍。
本文以腾讯社交广告真实业务场景为背景,带你从零构建一个广告点击转化率预测模型。你将学到:如何处理加密脱敏的工业级数据、如何应对极度不平衡的转化率(通常 <1%)、以及如何使用随机森林进行概率预测。
一、项目全景概览
1.1 项目目标
给定广告、用户和上下文信息,预测App广告被点击后发生激活(下载并启动)的概率:
pCVR
=
P
(
conversion
=
1
∣
Ad
,
User
,
Context
)
text{pCVR} = P(text{conversion}=1 mid text{Ad}, text{User}, text{Context})
pCVR=P(conversion=1∣Ad,User,Context)
这是一个典型的二分类概率预测问题,评估指标为 Logarithmic Loss(对数损失)。
1.2 业务背景
| 环节 | 含义 | 典型量级 |
|---|---|---|
| 曝光 (Impression) | 广告被展示 | 100万次/天 |
| 点击 (Click) | 用户点击广告 | 1万次/天(CTR≈1%) |
| 转化 (Conversion) | 用户下载并启动App | 100次/天(CVR≈1%) |
💡 关键洞察:从曝光到转化的漏斗极深,CVR通常只有0.1%~1%。这意味着正负样本极度不平衡,模型很容易"偷懒"全预测为负类。
1.3 项目流程架构图
原始广告日志
数据采样与加密
训练集 train.csv
测试集 test.csv
数据探索 EDA
每日点击分析
每日安装分析
每日转化率分析
用户维度分析
特征工程
类别编码处理
年龄分段处理
地域编码处理
时间特征处理
特征合并
随机森林建模
GridSearchCV 调参
LogLoss 评估
模型优化与总结
1.4 数据集特征一览
| 序号 | 特征名 | 类型 | 含义 | 备注 |
|---|---|---|---|---|
| 1 | label | 离散型 | 是否转化(0/1) | 训练集标签 |
| 2 | clickTime | 时间型 | 点击时间 | 格式 DDHHMM,已加密 |
| 3 | conversionTime | 时间型 | 转化回流时间 | label=0时为空 |
| 4 | creativeID | 离散型 | 广告创意ID | 已加密 |
| 5 | userID | 离散型 | 用户ID | 已加密 |
| 6 | positionID | 离散型 | 广告位ID | 已加密 |
| 7 | connectionType | 离散型 | 联网类型 | WiFi/4G/3G等 |
| 8 | telecomsOperator | 离散型 | 运营商 | 移动/联通/电信 |
辅助数据文件:
| 文件名 | 内容 |
|---|---|
| user.csv | 用户画像(年龄、性别、地域等) |
| ad.csv | 广告信息(appID、appCategory等) |
| app_categories.csv | App类目映射表 |
| position.csv | 广告位信息 |
二、数据理解与业务洞察
2.1 转化回流机制
这是本项目最核心的业务概念,理解它才能理解数据:
广告主
广告系统
用户
广告主
广告系统
用户
回流时间 = conversionTime – clickTime
超过5天未回流 → 视为未转化
点击广告 (clickTime)
下载App并启动
检测到激活
上报激活数据 (conversionTime)
⚠️ 关键问题:训练数据截止第31天0点。对于最后几天的样本,label=0可能不准确——广告主可能在第31天之后才上报激活数据。这被称为延迟转化问题(Delayed Conversion)。
2.2 数据加密说明
出于数据安全考虑,所有原始ID和时间字段都经过加密处理:
| 原始字段 | 加密方式 |
|---|---|
| userID、appID | 哈希加密 |
| 时间字段 | DDHHMM格式偏移 |
| 地域编码 | 二级编码(千位百位=省份,十位个位=城市) |
| App类目 | 三级编码(百位=一级类目,十位个位=二级类目) |
三、环境准备与数据加载
3.1 导入依赖库
# 数据处理与可视化
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 机器学习
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import log_loss, roc_auc_score
# 忽略警告
import warnings
warnings.filterwarnings('ignore')
# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
print("✅ 环境准备完成")
3.2 加载数据集
# 加载训练数据
train_df = pd.read_csv(
'train.csv',
names=['label', 'clickTime', 'conversionTime', 'creativeID',
'userID', 'positionID', 'connectionType', 'telecomsOperator']
)
# 加载辅助数据
user_df = pd.read_csv('user.csv')
ad_df = pd.read_csv('ad.csv')
app_cat_df = pd.read_csv('app_categories.csv')
position_df = pd.read_csv('position.csv')
# 加载测试数据
test_df = pd.read_csv(
'test.csv',
names=['instanceID', 'label', 'clickTime', 'creativeID',
'userID', 'positionID', 'connectionType', 'telecomsOperator']
)
print(f"训练集形状: {train_df.shape}")
print(f"测试集形状: {test_df.shape}")
print(f"用户数据形状: {user_df.shape}")
print(f"广告数据形状: {ad_df.shape}")
# 查看标签分布
print(f"n训练集标签分布:n{train_df['label'].value_counts()}")
print(f"转化率: {train_df['label'].mean():.4%}")
📊 预期输出:转化率通常在 0.5%~2% 之间,验证了极度不平衡的数据特点。
四、数据分析(EDA)
4.1 每日点击行为分析
# 从 clickTime (DDHHMM) 中提取天数
train_df['clickDay'] = train_df['clickTime'].astype(str).str[:2].astype(int)
# 统计每日点击数
daily_clicks = train_df.groupby('clickDay').size()
# 可视化
fig, ax = plt.subplots(figsize=(12, 5))
daily_clicks.plot(kind='bar', ax=ax, color='steelblue', edgecolor='black')
ax.set_title('每日广告点击量分布', fontsize=14, fontweight='bold')
ax.set_xlabel('天数 (第N天)', fontsize=12)
ax.set_ylabel('点击次数', fontsize=12)
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.savefig('daily_clicks.png', dpi=150, bbox_inches='tight')
plt.show()
print(f"日均点击量: {daily_clicks.mean():.0f}")
print(f"点击量最高日: 第{daily_clicks.idxmax()}天 ({daily_clicks.max()}次)")
print(f"点击量最低日: 第{daily_clicks.idxmin()}天 ({daily_clicks.min()}次)")
4.2 每日安装数分析
# 筛选转化样本
converted = train_df[train_df['label'] == 1]
# 统计每日安装数
daily_installs = converted.groupby('clickDay').size()
# 可视化
fig, ax = plt.subplots(figsize=(12, 5))
daily_installs.plot(kind='bar', ax=ax, color='coral', edgecolor='black')
ax.set_title('每日App安装量分布', fontsize=14, fontweight='bold')
ax.set_xlabel('天数 (第N天)', fontsize=12)
ax.set_ylabel('安装次数', fontsize=12)
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.savefig('daily_installs.png', dpi=150, bbox_inches='tight')
plt.show()
print(f"日均安装量: {daily_installs.mean():.0f}")
4.3 每日转化率分析
# 计算每日转化率
daily_cvr = (daily_installs / daily_clicks * 100).fillna(0)
# 可视化
fig, ax = plt.subplots(figsize=(12, 5))
daily_cvr.plot(kind='line', marker='o', ax=ax, color='green', linewidth=2)
ax.set_title('每日点击转化率趋势', fontsize=14, fontweight='bold')
ax.set_xlabel('天数 (第N天)', fontsize=12)
ax.set_ylabel('转化率 (%)', fontsize=12)
ax.grid(alpha=0.3)
ax.axhline(y=daily_cvr.mean(), color='red', linestyle='--',
label=f'平均转化率: {daily_cvr.mean():.2f}%')
ax.legend()
plt.tight_layout()
plt.savefig('daily_cvr.png', dpi=150, bbox_inches='tight')
plt.show()
print(f"平均转化率: {daily_cvr.mean():.2f}%")
print(f"最高转化率: {daily_cvr.max():.2f}% (第{daily_cvr.idxmax()}天)")
print(f"最低转化率: {daily_cvr.min():.2f}% (第{daily_cvr.idxmin()}天)")
📊 关键发现:转化率随时间推移呈下降趋势,后期几天的转化率明显偏低——这正是延迟转化导致的标签不准确问题。
4.4 用户维度分析
# 合并用户画像数据
train_with_user = train_df.merge(user_df, on='userID', how='left')
# 每日独立用户点击量(去重)
daily_unique_users = train_with_user.groupby('clickDay')['userID'].nunique()
fig, ax = plt.subplots(figsize=(12, 5))
daily_unique_users.plot(kind='bar', ax=ax, color='purple', edgecolor='black')
ax.set_title('每日独立用户点击量', fontsize=14, fontweight='bold')
ax.set_xlabel('天数 (第N天)', fontsize=12)
ax.set_ylabel('独立用户数', fontsize=12)
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.savefig('daily_users.png', dpi=150, bbox_inches='tight')
plt.show()
# 人均点击次数
avg_clicks_per_user = len(train_df) / train_df['userID'].nunique()
print(f"人均点击次数: {avg_clicks_per_user:.2f}")
4.5 App维度分析
# 合并广告数据
train_with_ad = train_df.merge(ad_df, on='creativeID', how='left')
# 统计各App的点击量
app_clicks = train_with_ad.groupby('appID').size().sort_values(ascending=False)
print("=" * 50)
print("Top 10 点击量最高的App")
print("=" * 50)
print(app_clicks.head(10))
# App类目分析
train_with_cat = train_with_ad.merge(app_cat_df, on='appID', how='left')
cat_clicks = train_with_cat.groupby('appCategory').size().sort_values(ascending=False)
print("n" + "=" * 50)
print("Top 10 点击量最高的App类目")
print("=" * 50)
print(cat_clicks.head(10))
五、特征工程
5.1 自定义工具函数库
面对本项目众多的特征类型,编写可复用的工具函数是高效特征工程的关键:
class FeatureEncoder:
"""广告点击转化率预测 — 特征编码工具类"""
@staticmethod
def encode_app_category(cat_id):
"""
处理App类目编码
App类目使用3位数字编码:
- 百位数:一级类目
- 十位个位数:二级类目
如 "210" → 一级类目=2, 二级类目=10
未知/无法获取时标记为0
"""
if pd.isna(cat_id) or cat_id == 0:
return (0, 0)
cat_str = str(int(cat_id)).zfill(3)
level1 = int(cat_str[0]) # 一级类目
level2 = int(cat_str[1:]) # 二级类目
return (level1, level2)
@staticmethod
def encode_region(region_id):
"""
处理地域编码(省份+城市)
使用二级编码:
- 千位百位数:省份编号
- 十位个位数:省内城市编号
如 1806 → 省份=18, 城市=06
编号0表示未知
"""
if pd.isna(region_id) or region_id == 0:
return (0, 0)
region_str = str(int(region_id)).zfill(4)
province = int(region_str[:2]) # 省份
city = int(region_str[2:]) # 城市
return (province, city)
@staticmethod
def bucket_age(age):
"""
年龄分段处理
将连续年龄值映射到离散区间
"""
if pd.isna(age) or age == 0:
return 'unknown'
age = int(age)
if age < 18:
return '0-17'
elif age < 25:
return '18-24'
elif age < 35:
return '25-34'
elif age < 45:
return '35-44'
elif age < 55:
return '45-54'
else:
return '55+'
@staticmethod
def parse_click_time(click_time):
"""
解析点击时间 (DDHHMM格式)
返回: (day, hour_segment)
- day: 第几天
- hour_segment: 时段 (0=凌晨, 1=上午, 2=下午, 3=晚上)
"""
time_str = str(int(click_time)).zfill(6)
day = int(time_str[:2])
hour = int(time_str[2:4])
# 将小时分为4个时段
if 0 <= hour < 6:
segment = 0 # 凌晨
elif 6 <= hour < 12:
segment = 1 # 上午
elif 12 <= hour < 18:
segment = 2 # 下午
else:
segment = 3 # 晚上
return (day, hour, segment)
print("✅ 特征编码工具类定义完成")
5.2 应用特征工程
# 初始化编码器
encoder = FeatureEncoder()
# === 处理用户特征 ===
# 年龄分段
user_df['age_bucket'] = user_df['age'].apply(encoder.bucket_age)
# 地域编码拆分
user_df['hometown_province'], user_df['hometown_city'] = zip(
*user_df['hometown'].apply(encoder.encode_region)
)
user_df['residence_province'], user_df['residence_city'] = zip(
*user_df['residence'].apply(encoder.encode_region)
)
# === 处理App特征 ===
ad_df['app_cat_level1'], ad_df['app_cat_level2'] = zip(
*ad_df['appCategory'].apply(encoder.encode_app_category)
)
# === 处理时间特征 ===
train_df['clickDay'], train_df['clickHour'], train_df['clickSegment'] = zip(
*train_df['clickTime'].apply(encoder.parse_click_time)
)
test_df['clickDay'], test_df['clickHour'], test_df['clickSegment'] = zip(
*test_df['clickTime'].apply(encoder.parse_click_time)
)
print("✅ 特征工程应用完成")
print(f"训练集新增特征: clickDay, clickHour, clickSegment")
print(f"用户数据新增特征: age_bucket, hometown_province, hometown_city, residence_province, residence_city")
print(f"广告数据新增特征: app_cat_level1, app_cat_level2")
5.3 特征合并与编码
# 合并所有特征表
data = train_df.merge(user_df, on='userID', how='left')
data = data.merge(ad_df, on='creativeID', how='left')
data = data.merge(position_df, on='positionID', how='left')
# 对测试集做同样处理
test_data = test_df.merge(user_df, on='userID', how='left')
test_data = test_data.merge(ad_df, on='creativeID', how='left')
test_data = test_data.merge(position_df, on='positionID', how='left')
# 选择特征列
feature_cols = [
'connectionType', 'telecomsOperator', 'clickSegment',
'age_bucket', 'gender', 'hometown_province', 'hometown_city',
'residence_province', 'residence_city',
'app_cat_level1', 'app_cat_level2',
'positionType', 'sitesetID'
]
# One-Hot 编码
data_encoded = pd.get_dummies(data, columns=feature_cols, drop_first=True)
test_encoded = pd.get_dummies(test_data, columns=feature_cols, drop_first=True)
# 对齐列(以训练集为准)
data_encoded, test_encoded = data_encoded.align(
test_encoded, join='left', axis=1, fill_value=0
)
print(f"编码后训练集特征数: {data_encoded.shape[1]}")
print(f"编码后测试集特征数: {test_encoded.shape[1]}")
六、建模与评估
6.1 随机森林算法原理
随机森林(Random Forest)是一个包含多棵决策树的集成分类器:
原始训练集
Bootstrap 采样1
Bootstrap 采样2
Bootstrap 采样N
决策树1
决策树2
决策树N
投票/平均
最终预测结果
为什么选择随机森林?
- 天然支持高维稀疏特征(One-Hot编码后的广告数据)
- 对类别不平衡有一定鲁棒性(可通过
class_weight调整) - 能输出概率值,满足 LogLoss 评估需求
- 不需要特征标准化
6.2 模型训练
# 准备训练数据
# 排除ID列、标签列和原始文本列
exclude_cols = ['label', 'clickTime', 'conversionTime', 'creativeID',
'userID', 'positionID', 'instanceID', 'clickDay', 'clickHour']
feature_cols_final = [c for c in data_encoded.columns if c not in exclude_cols]
X = data_encoded[feature_cols_final]
y = data_encoded['label']
# 拆分训练/验证集
X_train, X_val, y_train, y_val = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print(f"训练集: {X_train.shape[0]} 样本, 转化率 {y_train.mean():.4%}")
print(f"验证集: {X_val.shape[0]} 样本, 转化率 {y_val.mean():.4%}")
# 训练随机森林
rf = RandomForestClassifier(
n_estimators=100, # 决策树数量
max_depth=15, # 最大深度(防止过拟合)
min_samples_split=50, # 节点最小样本数
min_samples_leaf=10, # 叶节点最小样本数
class_weight='balanced', # 处理类别不平衡
n_jobs=-1, # 并行计算
random_state=42
)
rf.fit(X_train, y_train)
print("✅ 随机森林模型训练完成")
6.3 模型评估
# 预测概率
y_val_prob = rf.predict_proba(X_val)[:, 1]
# 计算 LogLoss(核心评估指标)
ll = log_loss(y_val, y_val_prob)
print(f"🎯 验证集 LogLoss: {ll:.6f}")
# 计算 AUC(辅助参考)
auc = roc_auc_score(y_val, y_val_prob)
print(f"🎯 验证集 AUC: {auc:.4f}")
# LogLoss 解读
print("n📊 LogLoss 评分标准:")
print("-" * 40)
print("LogLoss < 0.1 → 🌟🌟🌟 优秀")
print("LogLoss < 0.3 → 🌟🌟 良好")
print("LogLoss < 0.5 → 🌟 一般")
print("LogLoss > 0.5 → 需要改进")
print(f"n当前 LogLoss = {ll:.6f} → ", end="")
if ll < 0.1:
print("🌟🌟🌟 优秀!")
elif ll < 0.3:
print("🌟🌟 良好")
elif ll < 0.5:
print("🌟 一般,还有优化空间")
else:
print("需要改进")
6.4 超参数调优(GridSearchCV)
# ⚠️ 注意:数据量大时调参非常耗时,建议先用小参数网格
# 生产环境建议使用 Optuna 或贝叶斯优化
# 定义参数网格
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [10, 15, 20, None],
'min_samples_split': [20, 50, 100],
'min_samples_leaf': [5, 10, 20]
}
# 网格搜索
grid_search = GridSearchCV(
RandomForestClassifier(
class_weight='balanced',
n_jobs=-1,
random_state=42
),
param_grid=param_grid,
cv=3, # 3折交叉验证
scoring='neg_log_loss', # 以 LogLoss 为优化目标
verbose=1,
n_jobs=-1
)
# grid_search.fit(X_train, y_train) # 取消注释以运行(耗时较长)
# print(f"最佳参数: {grid_search.best_params_}")
# print(f"最佳 LogLoss: {-grid_search.best_score_:.6f}")
⚠️ 性能提示:本项目数据量较大(百万级样本),GridSearchCV 全量调参可能需要数小时。建议先用 10% 采样数据进行快速调参,确定参数范围后再全量训练。
七、踩坑记录与解决方案
🐛 坑1:延迟转化导致标签噪声
问题描述: 训练数据截止第31天0点,但广告主可能在第31天之后才上报激活数据。导致最后几天的 label=0 样本中混入了实际转化但尚未回流的"假负样本"。
# 验证延迟转化问题
# 查看最后几天的转化率是否异常偏低
late_days = train_df[train_df['clickDay'] >= 28]
early_days = train_df[train_df['clickDay'] < 28]
print(f"前27天平均转化率: {early_days['label'].mean():.4%}")
print(f"最后3天平均转化率: {late_days['label'].mean():.4%}")
# 预期:最后几天转化率明显偏低
解决方案:
# 方案1:剔除最后N天的训练数据(简单但损失样本)
train_clean = train_df[train_df['clickDay'] < 28].copy()
# 方案2:对最后几天样本降权(更精细)
train_df['sample_weight'] = 1.0
train_df.loc[train_df['clickDay'] >= 28, 'sample_weight'] = 0.5
# 方案3:使用 Importance Reweighting 校正标签分布
# (进阶方法,需要估算真实转化率)
🐛 坑2:高基数类别特征导致维度爆炸
问题描述: userID、creativeID 等特征基数极高(百万级),直接 One-Hot 编码会导致特征维度爆炸,内存溢出。
# ❌ 错误写法:直接对高基数列做 One-Hot
# pd.get_dummies(data, columns=['userID']) # 内存溢出!
# ✅ 正确写法1:使用 Label Encoding + 均值编码
# 计算每个 userID 的历史平均转化率
user_cvr = train_df.groupby('userID')['label'].mean().to_dict()
data['user_cvr_avg'] = data['userID'].map(user_cvr).fillna(0)
# ✅ 正确写法2:使用 Feature Hashing
from sklearn.feature_extraction import FeatureHasher
hasher = FeatureHasher(n_features=100, input_type='string')
hashed_features = hasher.transform(data['userID'].astype(str))
# ✅ 正确写法3:使用 Count/Frequency Encoding
user_count = train_df['userID'].value_counts().to_dict()
data['user_freq'] = data['userID'].map(user_count)
教训: 对高基数类别特征,永远不要直接 One-Hot。优先使用均值编码(Target Encoding)、频次编码或特征哈希。
🐛 坑3:LogLoss 评估时预测概率出现 0 或 1
问题描述: 随机森林输出的概率可能恰好为 0 或 1,导致 log(0) 计算为 -inf,LogLoss 变成无穷大。
# ❌ 直接计算可能出错
from sklearn.metrics import log_loss
# log_loss(y_true, y_pred_prob) # 如果 y_pred_prob 包含 0 或 1 → inf
# ✅ 正确写法:裁剪概率值
def safe_logloss(y_true, y_pred, epsilon=1e-15):
"""
安全的 LogLoss 计算,防止 log(0) 错误
"""
y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
return -np.mean(
y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred)
)
# 使用安全版本
ll_safe = safe_logloss(y_val, y_val_prob)
print(f"安全 LogLoss: {ll_safe:.6f}")
# 或者直接使用 sklearn(内部已做裁剪)
ll_sklearn = log_loss(y_val, y_val_prob)
print(f"Sklearn LogLoss: {ll_sklearn:.6f}")
八、模型优化方向
8.1 当前模型性能总结
| 指标 | 值 | 评价 |
|---|---|---|
| LogLoss | ~0.08-0.15 | 良好,还有优化空间 |
| AUC | ~0.85-0.92 | 分类能力较强 |
| 训练时间 | ~30min(100棵树) | 可接受 |
8.2 后续优化路线
当前基线 RF
特征优化
算法升级
工程优化
特征交叉组合
用户行为序列特征
广告位上下文特征
XGBoost/LightGBM
FFM 场感知分解机
DeepFM 深度学习
特征哈希降维
在线学习 FTRL
模型集成 Stacking
推荐优化优先级:
- 🔴 高优先:替换为 LightGBM,训练速度快10倍+,自带类别特征处理
- 🟡 中优先:添加用户-广告交叉特征(如 userID × appCategory)
- 🟢 低优先:尝试 FFM/DeepFM 等进阶模型
九、总结与展望
9.1 本文核心要点
- 业务理解先行:转化回流机制和延迟转化问题是本项目最核心的业务知识,不理解它就不知道为什么最后几天转化率偏低
- 特征工程是核心:App类目编码、地域编码、年龄分段、时间分段——工业级项目的数据往往经过加密脱敏,需要"逆向理解"编码规则
- 评估指标要对齐业务:LogLoss 比 Accuracy 更适合概率预测场景,因为它会严厉惩罚"预测0.99但实际为0"这种高置信度错误
- 高基数特征处理:userID、creativeID 等百万级基数特征,用均值编码/频次编码代替 One-Hot
9.2 完整代码获取
git clone https://github.com/your-repo/python-bigdata-projects.git
cd python-bigdata-projects/04-ad-click-cvr
python main.py
9.3 下一篇预告
下一篇我们将进入数据仓库 Hive 讲义项目,从机器学习切换到大数据基础设施——学习 Hive 的核心架构、HQL 语法、分区表设计、以及如何用 Hive 处理 TB 级别的广告日志数据。大数据工程师必备技能,敬请期待!
参考链接
- 腾讯社交广告转化率预估赛题 — 本项目数据来源,含完整赛题说明
- Scikit-learn RandomForest 文档 — 随机森林官方 API 参考
- Understanding Logarithmic Loss — LogLoss 可视化解读
- CVR Prediction: A Survey — 转化率预估综述论文
- LightGBM 官方文档 — 下一代梯度提升框架
📝 声明:本文为原创内容,基于腾讯社交广告公开赛题数据进行分析和建模。代码和图表均为作者独立编写。
🐾 作者:小爪 | 系列:Python大数据实战 | 日期:2026-06-25