摘要:
合集:AI案例-NLP-广告传媒
赛题:腾讯2020广告受众基础属性预估
数据集:腾讯广告用户在3个月的时间窗口内的广告点击历史记录
数据集价值:预估广告受众的基础属性
解决方案:广告历史曝光特征提取、词嵌入等建模技术
竞赛出题方/数据发布方:腾讯
一、赛题描述
竞赛主办方为参赛者提供一组用户在长度为 91 天(3 个月)的时间窗口内的广告点击历史记录作为训练数据集。每条记录中包含了日期 (从 1 到 91)、用户信息 (年龄,性别),被点击的广告的信息(素材 id、广告 id、产品 id、产品类目 id、广告主 id、广告主行业 id 等),以及该用户当天点击该广告的次数。测试数据集将会是另一组用户 的广告点击历史记录。提供给参赛者的测试数据集中不会包含这些用户的年龄和性别信息。 本赛题要求参赛者预测测试数据集中出现的用户的年龄和性别。
说明:以上章节引用竞赛主办方发布的赛题内容。
二、数据集内容
赛题中的训练数据包括用户基本属性信息、用户点击日志和广告相关信息。
训练集:
data\train_preliminary\ad.csv
data\train_preliminary\click_log.csv
data\train_preliminary\user.csv
data\train_semi_final\ad.csv
data\train_semi_final\click_log.csv
data\train_semi_final\user.csv
测试集:
data\test\ad.csv
data\test\click_log.csv
对于测试集合中的数据,包括广告信息和点击日志,需要我们去预测用户性别和年龄。
click_log.csv
- time: 天粒度的时间,整数值,取值范围[1, 91]。
- user_id: 从 1 到 N 随机编号生成的不重复的加密的用户 id,其中 N 为用户总数目(训练集和测试集)。
- creative_id: 用户点击的广告素材的 id,采用类似于 user_id 的方式生成。
- lick_times: 当天该用户点击该广告素材的次数。
user.csv
- user_id:用户id
- age: 分段表示的用户年龄,取值范围[1-10]。
- gender:用户性别,取值范围[1,2]。
ad.csv
- creative_id:广告素材id
- ad_id: 该素材所归属的广告的 id,采用类似于 user_id 的方式生成。每个广告可能包含多个可展示的素材。
- product_id: 该广告中所宣传的产品的 id,采用类似于 user_id 的方式生成。
- product_category: 该广告中所宣传的产品的类别 id,采用类似于 user_id 的方式生成。
- advertiser_id: 广告主的 id,采用类似于 user_id 的方式生成。
- industry: 广告主所属行业的 id,采用类似于 user_id 的方式生成。
广告素材中的creative_id广告素材可能由文案、图片和视频组成,一个广告包含多个广告素材,因此广告素材生成的流程也需要加以了解。这也是影响广告投放的重要环节之一,首先结合用户画像和卖点提炼,然后确定使用场景,最后进行素材的筛选和加工。对数据和业务的基本认识可以帮助我们深入理解赛题,挖掘更多有用信息。
说明:以上章节引用竞赛主办方发布的数据集数据结构描述。
三、解决方案样例
以下为冠军选手的解决方案。
特征提取方案
用户的历史点击行为可以反映其属性特点,比如男性偏爱电子产品、游戏等,女性则更偏爱服饰、化妆品等,所以从点击序列中挖掘信息更为重要,其实也可以看着多值离散特征,对于重复的id问题,需要进一步尝试对比。那么如何挖掘序列信息呢,这里的方法就比较多了,下面逐个介绍。
统计特征
一般而言,以user_id维主键进行统计序列内的信息统计,如nunique/(number of unique)、mean、max、min、std/标准差(Standard Deviation)、count等等,对于nunique,用户点击素材id的类型个数,反映用户的兴趣范围。
一切问题都可以考虑根据目标变量进行有监督的构造特征。此题也不例外,目标变量为用户年龄核性别,那么我们就可以构造与目标有关的特征。然而这会存在一个问题,特别容易过拟合。有效的办法是采用交叉验证的方式,比如我们将将样本划分为5份,对于其中每一份数据,我们都用另外4份数据来构造。简单来说未知的数据在已知的数据里面取特征。
one-hot/top提取
直接展开,保留所有信息,300多万维,可以直接放弃了。或者提取点击最多的广告,然后再进行统计。
词频统计/TF-IDF
NLP中常用的做法,将用户点击序列中的creative_id或者ad_id集合看作一篇文档,将每个creative_id或者ad_id视为文档中的文字,然后使用tfidf。当然这也下来维度也非常高,可以通过参数调整来降低维度,比如sklearn中的TfidfVectorizer,可以使用max_df和min_df进行调整。
word2vec/deepwalk/fasttext等
把每个点击的creative_id或者ad_id当作一个词,把一个人91天内点击的creative_id或者ad_id列表当作一个句子,使用word2vec来构造creative_id或者ad_id嵌入表示。最后进行简单的统计操作得到用户的向量表示。除此之外还能够通过creative_id被user_id点击的序列来构造user_id向量表示。
- 用户广告ID序列, sequence_text_user_id_ad_id
- 用户创意ID序列, sequence_text_user_id_creative_id
- 用户广告主ID序列, sequence_text_user_id_advertiser_id
- 用户产品ID序列, sequence_text_user_id_product_id
- 用户行业序列, sequence_text_user_id_industry
- 用户产品类别序列, sequence_text_user_id_product_category
- 用户时间序列, sequence_text_user_id_time
- 用户点击次数序列, sequence_text_user_id_click_times
安装开发包
开发包版本信息
python 3.10.16
pytorch 2.5.1 py3.10_cuda12.1_cudnn9_0 pytorch
pytorch-cuda 12.1 pytorch
transformers 4.49.0 py310haa95532_0
gensim 4.3.3
numpy 1.26.4
numpy-base 1.26.4
pandas 2.2.3
scikit-learn 1.6.1
运行
1. 数据预处理
合并所有原始数据文件,包括train_preliminary、train_semi_final、test目录下的点击记录CSV文件,并写入点击记录文件(click.pkl)。转换用户CSV数据成文件(train_user.pkl/test_user.pkl)。
python src/preprocess.py
输出:文件click.pkl、train_user.pkl、test_user.pkl
merge click files...
merge ad files...
merge user files...
merge all files...
preprocess done! saving data...
2. 特征提取
Python脚本extract_features.py主要用于从用户点击日志数据中提取多种特征,包括聚合特征、序列特征和K折统计特征,用于后续的机器学习模型训练。该脚本主要实现了四种类型的特征提取:
- 聚合特征:对用户行为进行各种聚合统计
- 序列特征:将用户的行为序列转化为文本形式
- K折统计特征:使用交叉验证方式统计特征的年龄性别分布
- K折序列特征:将K折统计结果转化为序列特征
核心函数分析:
输入:
click_log=pd.read_pickle('data/click.pkl')
train_df=pd.read_pickle('data/train_user.pkl')
test_df=pd.read_pickle('data/test_user.pkl')
get_agg_features() – 聚合特征提取
- 输入:数据帧列表、特征列、聚合列、聚合操作类型、日志数据
- 功能:对日志数据按指定列进行分组聚合操作(如count, mean, unique等)
- 输出:返回新特征名称列表
- 特点:支持多种聚合操作,处理缺失值,自动内存清理
sequence_text() – 序列特征提取
- 输入:数据帧列表、ID列、序列值列、日志数据
- 功能:将用户的行为序列转化为空格分隔的文本字符串
- 输出:返回新特征名称列表
- 特点:适用于将用户点击历史转化为可处理的文本形式
kfold() – K折统计特征
- 输入:训练集、测试集、日志数据、关键特征列
- 功能:使用5折交叉验证方式统计特征的年龄性别分布
- 输出:直接在输入数据帧中添加新特征
- 特点:避免数据泄露,适合分类问题中的统计特征
kfold_sequence() – K折序列特征
- 输入:训练集、测试集、日志数据、关键特征列
- 功能:将K折统计结果转化为序列特征并保存为Word2Vec格式
- 输出:返回新特征名称列表
- 特点:结合了统计特征和序列特征的优势
运行
python src/extract_features.py
输出:
(133878445, 23) (3000000, 3) (1000000, 3)
Extracting aggregate feature...
Extracting aggregate feature done!
List aggregate feature names:
['user_id__size', 'user_id_ad_id_unique', 'user_id_creative_id_unique', 'user_id_advertiser_id_unique', 'user_id_industry_unique', 'user_id_product_id_unique', 'user_id_time_unique', 'user_id_click_times_sum', 'user_id_click_times_mean', 'user_id_click_times_std']
Extracting sequence feature...
sequence_text_user_id_ad_id
sequence_text_user_id_creative_id
sequence_text_user_id_advertiser_id
sequence_text_user_id_product_id
sequence_text_user_id_industry
sequence_text_user_id_product_category
sequence_text_user_id_time
sequence_text_user_id_click_times
Extracting sequence feature done!
List sequence feature names:
['sequence_text_user_id_ad_id', 'sequence_text_user_id_creative_id', 'sequence_text_user_id_advertiser_id', 'sequence_text_user_id_product_id', 'sequence_text_user_id_industry', 'sequence_text_user_id_product_category', 'sequence_text_user_id_time', 'sequence_text_user_id_click_times']
3. 预训练 Word2Vector 与 BERT
这里提供两种方式获得预训练权重: 重新预训练或下载预训练好的权重
注: Word2Vector和BERT权重必须一致,即要么全部重新预训练,要么全部下载
4.1 预训练Word2Vector
源码src/w2v.py进行word2vector预训练。该脚本实现了以下核心功能:
- 从预处理好的用户行为序列数据中训练Word2Vec模型
- 将训练好的Word2Vec模型保存为pickle文件
- 支持多种用户行为序列的向量化(广告ID、创意ID、广告主ID等)
w2v() 函数
- 输入参数:
dfs
: 包含序列特征的数据帧列表(通常是训练集和测试集)f
: 要处理的序列特征列名L
: 输出的向量维度(默认为128)
- 处理流程:
- 从所有数据帧中收集指定列的句子(将空格分隔的序列拆分为单词列表)
- 使用Gensim的Word2Vec模型训练词向量
- 将训练好的模型保存到data目录下
- 模型参数:
vector_size=128
: 词向量维度window=8
: 上下文窗口大小min_count=1
: 最小词频阈值sg=1
: 使用skip-gram算法(1=skip-gram, 0=CBOW)workers=32
: 并行工作线程数epochs=10
: 训练迭代次数
主程序流程
- 数据加载:读取预处理好的训练集和测试集(train_user.pkl和test_user.pkl)
- Word2Vec训练:对8种不同的用户行为序列分别训练Word2Vec模型:
- 用户广告ID序列
- 用户创意ID序列
- 用户广告主ID序列
- 用户产品ID序列
- 用户行业序列
- 用户产品类别序列
- 用户时间序列
- 用户点击次数序列
- 模型保存:每个模型保存为”.{维度}d”格式的pickle文件
关键代码:
if __name__ == "__main__":
train_df=pd.read_pickle('data/train_user.pkl')
test_df=pd.read_pickle('data/test_user.pkl')
#训练word2vector,维度为128
w2v([train_df,test_df],'sequence_text_user_id_ad_id',L=128)
w2v([train_df,test_df],'sequence_text_user_id_creative_id',L=128)
w2v([train_df,test_df],'sequence_text_user_id_advertiser_id',L=128)
w2v([train_df,test_df],'sequence_text_user_id_product_id',L=128)
w2v([train_df,test_df],'sequence_text_user_id_industry',L=128)
w2v([train_df,test_df],'sequence_text_user_id_product_category',L=128)
w2v([train_df,test_df],'sequence_text_user_id_time',L=128)
w2v([train_df,test_df],'sequence_text_user_id_click_times',L=128)
python src/w2v.py
4.2预训练BERT
预训练BERT (如果GPU是v100,可以安装apex并在参数上加–fp16进行加速)
cd BERT
mkdir saved_models
python run.py \
--output_dir saved_models \
--model_type roberta \
--config_name roberta-base \
--mlm \
--block_size 128 \
--per_gpu_train_batch_size 64 \
--per_gpu_eval_batch_size 64 \
--gradient_accumulation_steps 1 \
--learning_rate 5e-5 \
--weight_decay 0.01 \
--adam_epsilon 1e-6 \
--max_grad_norm 1.0 \
--max_steps 100000 \
--mlm_probability 0.2 \
--warmup_steps 10000 \
--logging_steps 50 \
--save_steps 10000 \
--evaluate_during_training \
--save_total_limit 500 \
--seed 123456 \
--tensorboard_dir saved_models/tensorboard_logs
rm -r saved_models/bert-base
cp -r saved_models/checkpoint-last saved_models/bert-base
rm saved_models/bert-base/optimizer.pt
cp saved_models/vocab.pkl saved_models/bert-base/vocab.pkl
cd ..
5. 训练模型
mkdir saved_models
mkdir saved_models/log
for((i=0;i<5;i++));
do
python run.py \
--kfold=5 \
--index=$i \
--train_batch_size=256 \
--eval_steps=5000 \
--max_len_text=128 \
--epoch=5 \
--lr=1e-4 \
--output_path=saved_models \
--pretrained_model_path=BERT/bert-base \
--eval_batch_size=512 2>&1 | tee saved_models/log/$i.txt
done
合并结果,结果为submission.csv
python src/merge_submission.py
源码开源协议
作者:SYSU-MSRA/中山大学-微软亚洲研究院联合实验室 Daya Guo