ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于 Criteo 1M 数据集的 CTR 预估 -- 数据预处理部分

基于 Criteo 1M 数据集的 CTR 预估 -- 数据预处理部分 数据清洗与分桶process.py数据集来源Kaggle 上的 Criteo ad-click limited 1M 数据集选这个数据集的原因是自己第一次做 CTR 预估的项目也算是自己来练练手数据量不大对于我的电脑也比较友好数据集概览数据特征主要为两块整数特征以及类型特征第1列target 标签列值为 1/0表示该广告是否被点击第2-14列intCol_0 ~ intCol_12 为整数特征列数值类型为整数剩余列catCol_0 ~ catCol_25 为类别特征列值被哈希为32位整数以匿名数据集划分按照 90% 训练集 10% 测试集的比例对数据集划分df pd.read_csv(path) train_df, test_df train_test_split(df, test_size0.1, random_state42, stratifydf[label_col])random_state 为随机种子设置固定数字42 是行业习惯保证每次运行代码划分结果完全一样实验可复现stratify 参数传入标签列的列名保证训练集、测试集中标签占比和原始数据集一致缺失值的填充对于整数特征列缺失值填充为该列的中位数类别特征列的缺失值填充为新的类别 missing把缺失当成一个独立类别保留“缺失”这个信息for col in int_cols: median train_df[col].median() # 只用训练集计算中位数 train_df[col] train_df[col].fillna(median).astype(int) test_df[col] test_df[col].fillna(median).astype(int) for col in cat_cols: # 类别特征的缺失值填充为新的类别 missing train_df[col] train_df[col].fillna(missing).astype(str) test_df[col] test_df[col].fillna(missing).astype(str)分桶处理Criteo 的 13 个数值特征是高度偏斜的计数类特征。直接作为连续值输入神经网络模型很难学好。因此需要进行分桶操作分桶就是把连续值映射成有限的几个离散区间然后当作类别特征走嵌入层。这样模型能学到“这个数值落在哪个区间”的非线性信息分桶策略如果原始数值 2则值映射为 1放到桶 1如果数值 2则值取对数并向下取整取对数的原因是方便将某些长尾数据压缩bin_cols [f{col}_bin for col in int_cols] # 分桶生成新列 for col in int_cols: # 如果 x 2值设为 1 否则为 floor(log2(x)) train_df[col _bin] train_df[col].apply(lambda x : int(np.log2(x)) if x 2 else 1) test_df[col _bin] test_df[col].apply(lambda x : int(np.log2(x)) if x 2 else 1)记得分桶后产生的新的类别特征也要放入类别特征 cat_cols 里# 分桶后的整数列变为新的类别特征, 加入到 cat_cols 中 all_cat_cols cat_cols bin_cols将哈希值映射为整数索引前面提到了类别特征列被哈希为32位整数由于后面 CTR 预估要用到深度学习模型需要做 Embedding如果不做整数映射Embedding层会巨大无比存储空间直接爆炸利用 unique 方法获取列的值并将 32 位整数映射为整数索引存到 mapping 里通过 map 方法应用映射对于测试集中出现而训练集中未出现的类别统一映射为 len(mapping)当然我们之前对整数特征做了对数分桶并存到了新的特征列 intCol_0_bin ~ intCol_12_bin 中这些特征后面会与类别特征一样进入 Embedding 层为了简洁之前的整数特征则不再使用维护一个字典映射 encoders将每列的映射保存下来后面方便计算 Embedding 的大小# 将类别特征的哈希值映射为整数索引每列的含义不同映射也不同 encoders {} # 保存每列的映射 for col in all_cat_cols: # 训练集建立映射 unique_vals train_df[col].unique() # 获取不重复的值 mapping {v: i for i, v in enumerate(unique_vals)} # 建立字典映射 encoders[col] mapping train_df[col] train_df[col].map(mapping) # 对该列采用映射 # 测试集复用映射未见类别映射为 unknown_idx, 因为测试集可能有训练集未出现的类别 unknown_idx len(mapping) test_df[col] test_df[col].map(mapping).fillna(unknown_idx).astype(int)转换为 DataLoaderload_data.py在数据清洗与分桶后我们得到了训练集和测试集的 DataFrame train_df 和 test_df以及每列的32位哈希值--整数索引的映射 encoders将所有类别特征列和标签列转变为 torch 里的张量CTR 预估常用的损失函数是 BCEWithLogitsLoss 函数要求标签的数据类型为 torch.float32# 类别特征转换为 long 的 tensor X_train torch.tensor(train_df[all_cat_cols].values, dtypetorch.long) X_test torch.tensor(test_df[all_cat_cols].values, dtypetorch.long) # 标签转为 float32 的 tensor, 因为后面要用 BCEWithLogitsLoss 损失函数 Y_train torch.tensor(train_df[label_col].values, dtypetorch.float32) Y_test torch.tensor(test_df[label_col].values, dtypetorch.float32)再将 X_train, Y_train, X_test, Y_test 打包转为 DataLoader并返回# 数据组织成 TensorDataset train_dataset data.TensorDataset(X_train, Y_train) test_dataset data.TensorDataset(X_test, Y_test) train_iter, test_iter data.DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue, num_workersnum_workers), \ data.DataLoader(test_dataset, batch_sizebatch_size, shuffleFalse, num_workersnum_workers) return train_iter, test_iter这样数据清洗与分桶以及打包的过程就完成了可以在 main.py 中简单测试一下打印一下第一个 batch 的数据形状from dataset.process import process from dataset.load import load_data # 采用 Kaggle 上的 Criteo_1M_with_nans.csv 数据集 data_path data/Criteo_1M_with_nans.csv batch_size 256 num_workers 4 if __name__ __main__: train_df, test_df, all_cat_cols, encoders process(data_path) train_iter, test_iter load_data(train_df, test_df, all_cat_cols, batch_size, num_workers) for X, y in train_iter: print(X.shape) print(y.shape) break控制台返回结果如下torch.Size([256, 39]) torch.Size([256])类别特征列的形状为 [256, 39]256是批次的大小39包括13列分桶后的整数特征以及26列类别特征至此数据的预处理算是告一段落process.py 完整代码# process.py: 做数据预处理主要是划分数据集填充缺失值并把类别特征的哈希值映射为整数索引 import pandas as pd import numpy as np from sklearn.model_selection import train_test_split # 所有整数特征的列名 int_cols [intCol_0, intCol_1, intCol_2, intCol_3, intCol_4, intCol_5, intCol_6, intCol_7, intCol_8, intCol_9, intCol_10, intCol_11, intCol_12] # 类别特征的列名 cat_cols [catCol_0, catCol_1, catCol_2, catCol_3, catCol_4, catCol_5, catCol_6, catCol_7, catCol_8, catCol_9, catCol_10, catCol_11, catCol_12, catCol_13, catCol_14, catCol_15, catCol_16, catCol_17, catCol_18, catCol_19, catCol_20, catCol_21, catCol_22, catCol_23, catCol_24, catCol_25] # 标签 label_col target def process(path): df pd.read_csv(path) # 划分 10% 测试集random_state 为随机种子数字相同则划分相同 # stratify 传入标签那一列保证训练集、测试集中标签占比和原始数据集一致 train_df, test_df train_test_split(df, test_size0.1, random_state42, stratifydf[label_col]) for col in int_cols: # 整数特征用中位数来填充缺失值 median train_df[col].median() # 只用训练集计算中位数 train_df[col] train_df[col].fillna(median).astype(int) test_df[col] test_df[col].fillna(median).astype(int) bin_cols [f{col}_bin for col in int_cols] # 分桶生成新列 for col in int_cols: # 如果 x 2值设为 1 否则为 floor(log2(x)) train_df[col _bin] train_df[col].apply(lambda x : int(np.log2(x)) if x 2 else 1) test_df[col _bin] test_df[col].apply(lambda x : int(np.log2(x)) if x 2 else 1) for col in cat_cols: # 类别特征的缺失值填充为新的类别 missing train_df[col] train_df[col].fillna(missing).astype(str) test_df[col] test_df[col].fillna(missing).astype(str) # 分桶后的整数列变为新的类别特征, 加入到 cat_cols 中 all_cat_cols cat_cols bin_cols # 将类别特征的哈希值映射为整数索引每列的含义不同映射也不同 encoders {} # 保存每列的映射 for col in all_cat_cols: # 训练集建立映射 unique_vals train_df[col].unique() # 获取不重复的值 mapping {v: i for i, v in enumerate(unique_vals)} # 建立字典映射 encoders[col] mapping train_df[col] train_df[col].map(mapping) # 对该列采用映射 # 测试集复用映射未见类别映射为 unknown_idx, 因为测试集可能有训练集未出现的类别 unknown_idx len(mapping) test_df[col] test_df[col].map(mapping).fillna(unknown_idx).astype(int) return train_df, test_df, all_cat_cols, encodersload.py 完整代码# load.py: 将训练集和数据集 DataFrame 打包成 DataLoader import torch from torch.utils import data label_col target def load_data(train_df, test_df, all_cat_cols, batch_size, num_workers 4): # num_workers: 开启的子进程数 # 类别特征转换为 long 的 tensor X_train torch.tensor(train_df[all_cat_cols].values, dtypetorch.long) X_test torch.tensor(test_df[all_cat_cols].values, dtypetorch.long) # 标签转为 float32 的 tensor, 因为后面要用 BCEWithLogitsLoss 损失函数 Y_train torch.tensor(train_df[label_col].values, dtypetorch.float32) Y_test torch.tensor(test_df[label_col].values, dtypetorch.float32) # 数据组织成 TensorDataset train_dataset data.TensorDataset(X_train, Y_train) test_dataset data.TensorDataset(X_test, Y_test) train_iter, test_iter data.DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue, num_workersnum_workers), \ data.DataLoader(test_dataset, batch_sizebatch_size, shuffleFalse, num_workersnum_workers) return train_iter, test_iter
返回列表