SHAP:以淘宝展示广告点击率预估做可视化分析解释

   前两次用SHAP框架可视化解释了bitcoin price预测和credit fraud detection这种anomaly detection,今天继续以推荐系统为样本对SHAP的使用做可视化分析!

  1、数据集还是来自天池:https://tianchi.aliyun.com/dataset/56

  

   代码:

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
from xgboost import XGBClassifier
import shap
import matplotlib.pyplot as plt

# -----------------------------
# 1. 加载数据
# -----------------------------
RAW_SAMPLE_PATH = "raw_sample.csv"
AD_FEATURE_PATH = "ad_feature.csv"
USER_PROFILE_PATH = "user_profile.csv"
BEHAVIOR_LOG_PATH = "behavior_log.csv"

# 加载原始样本骨架
raw_sample = pd.read_csv(RAW_SAMPLE_PATH)
# 加载广告信息
ad_feature = pd.read_csv(AD_FEATURE_PATH)
# 加载用户信息
user_profile = pd.read_csv(USER_PROFILE_PATH)
# 加载行为日志(可选限制加载数量)
behavior_log = pd.read_csv(BEHAVIOR_LOG_PATH, nrows=10_000_000)  # 控制内存使用

# -----------------------------
# 2. 数据预处理与特征工程
# -----------------------------
# 合并广告信息
df = raw_sample.merge(ad_feature, on="adgroup_id", how="left")
print(raw_sample.head())
# 合并用户信息(注意列名:user vs userid)
df = df.merge(user_profile, left_on="user", right_on="userid", how="left")

# 删除冗余字段
df.drop(columns=["userid"], inplace=True)

# 设置目标变量
df["label"] = df["clk"]
df['date'] = pd.to_datetime(df['time_stamp'], unit='s', errors='coerce')

pd.set_option('future.no_silent_downcasting', True) # 这样 .fillna() 不会触发自动 downcasting 的警告
# 在后续 fillna 时跳过 date 列
for col in df.columns:
    if col == "date":
        continue
    if df[col].dtype == "object":
        df[col] = df[col].fillna("unknown")
    else:
        df[col] = df[col].fillna(-1).infer_objects()  # 使用 infer_objects 避免 downcasting 警告

# 安全地转换类别字段为整数
for col in df.select_dtypes(include=['object']).columns:
    df[col] = pd.to_numeric(df[col].astype(str), errors='coerce').fillna(-1).astype(int)

# -----------------------------
# 3. 行为日志特征提取(behavior_log)
# -----------------------------
# 提取用户行为统计特征
behavior_log["btag"] = behavior_log["btag"].astype(str)
behavior_log["btag_code"] = behavior_log["btag"].map({
    "ipv": 0,
    "cart": 1,
    "fav": 2,
    "buy": 3
})

# 用户行为计数特征
user_behavior_stats = behavior_log.groupby("user")["btag"].value_counts().unstack(fill_value=0)
user_behavior_stats.columns = [f"user_btag_{col}" for col in user_behavior_stats.columns]

# 用户行为时间间隔特征
behavior_log["time_stamp"] = pd.to_datetime(behavior_log["time_stamp"], format="%Y%m%d%H%M%S", errors='coerce')
behavior_log["last_action"] = behavior_log.groupby("user")["time_stamp"].shift()
behavior_log["time_diff"] = (behavior_log["time_stamp"] - behavior_log["last_action"]).dt.total_seconds()
user_time_diff_stats = behavior_log.groupby("user")["time_diff"].agg(["mean", "std", "min", "max"])
user_time_diff_stats.columns = [f"user_time_diff_{col}" for col in user_time_diff_stats.columns]

# 合并行为特征到主表
df = df.merge(user_behavior_stats, left_on="user", right_index=True, how="left")
df = df.merge(user_time_diff_stats, left_on="user", right_index=True, how="left")

# 填充行为特征缺失值
df.fillna(0, inplace=True)


# 最后再删除 user、adgroup_id 等冗余字段
df.drop(columns=["nonclk", "clk", "user", "adgroup_id"], inplace=True, errors='ignore')
print(df.head)
# -----------------------------
# 4. 划分训练集和测试集
# -----------------------------
df['date'] = pd.to_datetime(df['date'], errors='coerce')
print(df['date'].dtype)  # 确保显示为 datetime64[ns]

# 过滤掉可能转换失败导致的 NaT
df = df[df['date'].notna()]  # 筛掉没有时间戳的样本
# 再进行时间切分
cutoff_date = pd.to_datetime('2017-05-13')
train_df = df[df['date'] < cutoff_date]
test_df = df[df['date'] == cutoff_date]


X_train = train_df.drop(columns=["label", "date"], errors='ignore')
y_train = train_df["label"]
X_test = test_df.drop(columns=["label", "date"], errors='ignore')
y_test = test_df["label"]

# -----------------------------
# 5. 模型训练
# -----------------------------
model = XGBClassifier(
    n_estimators=100,
    max_depth=5,
    learning_rate=0.1,
    subsample=0.8,
    colsample_bytree=0.7,
    eval_metric='logloss',
    use_label_encoder=False
)
model.fit(X_train, y_train)

# 预测
y_proba = model.predict_proba(X_test)[:, 1]
print("AUC Score:", roc_auc_score(y_test, y_proba))

# -----------------------------
# 6. SHAP 解释
# -----------------------------
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)

# 摘要图(Summary Plot)
shap.summary_plot(shap_values, X_test, plot_type="bar")
shap.summary_plot(shap_values, X_test)

# 力图(Force Plot) - 单个样本
shap.initjs()
shap.force_plot(explainer.expected_value, shap_values[0, :], X_test.iloc[0, :])

# 依赖图(Dependence Plot)
for feature in X_test.columns:
    try:
        shap.dependence_plot(feature, shap_values, X_test)
    except Exception as e:
        print(f"Skipping {feature} due to error: {e}")

# 决策图(Decision Plot)
shap.decision_plot(explainer.expected_value, shap_values[:10], feature_names=X_test.columns.tolist())

# 交互作用图(Interaction Plot)
shap_interaction_values = explainer.shap_interaction_values(X_test)
shap.summary_plot(shap_interaction_values, X_test)

# 特征重要性排序(Feature Importance)
plt.figure(figsize=(10, 6))
shap.summary_plot(shap_values, X_test, plot_type="bar")
plt.title("Feature Importance (SHAP)")
plt.show()

  最终的AUC:感觉结果很一般,估计还有很多参数可以调整,很多列可以做清洗,这里主要做SHAP的可视化解释,所以这些工作暂时不做了!

  

   2、还是挨个看分析结果:

  (1)每个feather的SHAP值:

  

   对于结果的广告是否点击,重要的feather有:

  • user_btag_pv:用户过去浏览过越多广告,越可能继续点击新广告,是最重要的预测因子;(或则还有另一种可能:是不是故意刷流量的爬虫干的?)
  • cate_id:某些类目的商品天然更吸引用户点击(如美妆、电子产品)
  • price:价格敏感型用户可能更关注低价商品,或高价商品具有更高转化价值
  • time_stamp:用户在特定时间段(如晚间、周末)的活跃度更高?
  • brand:知名品牌可能自带更高的用户信任度和点击意愿?
  • campaign_id :广告计划 ID,可能反映不同广告策略的效果差异
  • customer :广告主 ID,优质广告主可能投放更精准的广告。
  • shopping_level :用户购物深度等级(如浅层、中度、深度用户)。
  • user_btag_buy :用户历史购买行为次数,购买过的用户更可能再次点击。

  (2)具体到每个feather,到底是正相关还是负相关了?PS:做数据挖掘,就是要找到影响大的feather,不论是正面还是负面的!从下图看,红蓝的点严格在两边隔开不混淆,说明泾渭分明,这就是好的feather(不论红蓝在左还是右,只要严格分开,说明这种feather能很好地区分target)

  

  •  user_btag_pv:SHAP 值跨度大(从 -0.2 到 0.3),红色(高值)多集中在右侧;说明用户历史浏览行为(PV)对点击率影响显著。高 PV 用户(红色)更可能点击广告(这部分用户本身的活跃度更高),低 PV 用户(蓝色)则相反
  • cate_id:红色的全在左边,右边红蓝基本各站一半,也就是某些cate_id的商品让用户比较“讨厌”,不点击的概率较大,比如美妆给男性用户展示;相反,在右侧这些id也有较高的正面影响,估计是美妆给女性用户展示了
  • price:右边蓝色明显多,说明低价对于用户的吸引确实大;相反,红色的几乎都在左边,说明高价对用户是较大的负面影响!
  • brand:大部分红色在左边,蓝色在右边,符合上述分开的说明,说明特定品牌在不同人群的点击率差异十分明显,比如电子产品在男性群体的点击率明显和女性不同
  • campaign_id:红色左、蓝色右,同上!

  剩下的shopping_level、user_btag_buy等同样都是泾渭分明的好feather!

  (3)各种依赖图:

  • user_btag_pv和price的dependent:随着 user_btag_pv 的增加,SHAP 值整体呈上升趋势,但增长速度逐渐放缓;
    • user_btag_pv 较小时(如 0 到 10 左右),SHAP 值为负,表明这些用户的历史浏览行为较少,对点击率有轻微的负面影响
    • user_btag_pv 较大时(如 40 到 80 左右),SHAP 值为正,表明这些用户的历史浏览行为较多,对点击率有显著的正向影响
  • 蓝色区域(低价格)集中在左侧,表明低价格的商品通常对应较低的 user_btag_pv。红色区域(高价格)集中在右侧,表明高价格的商品通常对应较高的 user_btag_pv
    • 这表明高价格商品更可能被频繁浏览的用户点击,而低价格商品则可能被浏览较少的用户点击,后续可以用这个作为运营投放的规则!
      • 针对高频浏览用户(如 user_btag_pv > 40),优先展示高价格商品;
      • 结合用户画像,为低频浏览用户(如 user_btag_pv < 10)提供折扣或促销活动,以激发其点击意愿。

  

  •  price和brand的dependent关系:
    • 随着 price 的增加,SHAP 值整体呈下降趋势:
      • price 接近0时,其SHAP值明显是正的,说明低价或不标注价格,能吸引用户点击广告
        • 低价商品可能通过“性价比”吸引用户;
      • price 从0开始增加时,SHAP逐渐降低到负数,说明price 增加对点击率有明显的负面影响
        • 高价商品可能因“超出预算”或“不信任感”导致点击率下降。
    • 红色和蓝色都集中在0~1000的price这里,说明brand的价格都不高(注意:brand用脱敏后的id表示,id值越高越红,越低就越蓝;但这个只是brand的id,仅仅只是个编号,无实际的业务意义!严格讲:这类离散数据在代码中需要指明类型
      • 后续可以引导高端brand主针对高端客户展示广告?

  

   3、推荐系统的发展阶段:各个公司可以根据自身的发展阶段灵活选择适合的算法!

  • CF:连模型都算不上,就是个数学计算公式,没有embedding,无泛化能力
  • matrix factorization:只用了user-item的interactive信息,没用到user profile、item information
  • LR+特征交叉:尽可能多的使用user/iem信息
    • Factorization Machine 因子分解机
    • FFM:特征域组合
    • GBDT用来生成离散特征,然后用LR做预测  
  • 深度学习DNN:wide&deep、deep Cross、deep FM.....
    • 提取embedding特征
  • LLM:user 和item的交互是时序数据
    • HLLM
posted @ 2025-06-13 10:17  第七子007  阅读(119)  评论(0)    收藏  举报