【12月Top 2】MarTech Challenge 点击反欺诈预测

2024-04-27 09:32

本文主要是介绍【12月Top 2】MarTech Challenge 点击反欺诈预测,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

背景

广告欺诈是数字营销需要面临的重要挑战之一,点击会欺诈浪费广告主大量金钱,同时对点击数据会产生误导作用。本次比赛提供了约50万次点击数据。特别注意:我们对数据进行了模拟生成,对某些特征含义进行了隐藏,并进行了脱敏处理。

请预测用户的点击行为是否为正常点击,还是作弊行为。点击欺诈预测适用于各种信息流广告投放,banner广告投放,以及百度网盟平台,帮助商家鉴别点击欺诈,锁定精准真实用户。

  • 比赛地址:https://aistudio.baidu.com/aistudio/competition/detail/52/0/introduction
  • 比赛数据集:https://download.csdn.net/download/turkeym4/72338032#

数据与任务

大赛提供50万的训练数据以及15万的测试数据。目标是预测该笔数据是否存在反欺诈行为。

字段类型说明
sidstring样本id/请求会话sid
packagestring媒体信息,包名(已加密)
versionstring媒体信息,app版本
android_idstring媒体信息,对外广告位ID(已加密)
media_idstring媒体信息,对外媒体ID(已加密)
apptypeint媒体信息,app所属分类
timestampbigint请求到达服务时间,单位ms
locationint用户地理位置编码(精确到城市)
fea_hashint用户特征编码(具体物理含义略去)
fea1_hashint用户特征编码(具体物理含义略去)
cus_typeint用户特征编码(具体物理含义略去)
nttint网络类型 0-未知, 1-有线网, 2-WIFI, 3-蜂窝网络未知, 4-2G, 5-3G, 6–4G
carrierstring设备使用的运营商 0-未知, 46000-移动, 46001-联通, 46003-电信
osstring操作系统,默认为android
osvstring操作系统版本
lanstring设备采用的语言,默认为中文
dev_heightint设备高
dev_widthint设备宽
dev_ppiint屏幕分辨率
labelint是否存在反欺诈

通过数据label可以得知,该命题是一个二分类任务。可使用机器学习算法或者MLP进行求解。

解题思路

解题方案可分为两部分:

  • 使用机器学习算法的二分类预测:LGB/XGB/CatBoost
  • 使用深度学习算法的二分类预测:MLP/Wide & Deep/DeepFM

下面将列出大致的建模方案,具体可查看源码:gitee仓库

机器学习

机器学习无非就是特征工程+祖传参数的问题。通常经过下为了快速出第一版本的Baseline,我们常常会使用LGB(lightgbm)起步。这个算法的最大的特点就是保证准确率的同时还很快。

特征处理

空值处理
经调研发现,在lan和osv上面出现空值。

# 字符串类型 需要转换为数值(labelencoder)
object_cols = train.select_dtypes(include='object').columns# 缺失值个数
temp = train.isnull().sum()
# 有缺失值的字段: lan, osv
temp[temp>0]
# 获取分析字段
features = train.columns.tolist()
features.remove('label')
print(features)

连续值与分类值
接着分析连续值与分类值。最终发现对osv需要进行转换处理,对fea_hash与fea1_hash初步先求字符长度处理

for feature in features:print(feature, train[feature].nunique())

osv处理方法

# 处理osv
def trans_osv(osv):global resultosv = str(osv).replace(' ','').replace('.','').replace('Android_','').replace('十核20G_HD','').replace('Android','').replace('W','')if osv == 'nan' or osv == 'GIONEE_YNGA':result = 810elif osv.count('-') >0:result = int(osv.split('-')[0])elif osv == 'f073b_changxiang_v01_b1b8_20180915':result = 810elif osv == '%E6%B1%9F%E7%81%B5OS+50':result = 500else:result = int(osv)if result < 10:result = result * 100elif  result < 100:result = result * 10return int(result)

最后测试与训练集的转换

# 特征筛选
features = train[col]
# 构造fea_hash_len特征
features['fea_hash_len'] = features['fea_hash'].map(lambda x: len(str(x)))
features['fea1_hash_len'] = features['fea1_hash'].map(lambda x: len(str(x)))
# Thinking:为什么将很大的,很长的fea_hash化为0?
# 如果fea_hash很长,都归为0,否则为自己的本身
features['fea_hash'] = features['fea_hash'].map(lambda x: 0 if len(str(x))>16 else int(x))
features['fea1_hash'] = features['fea1_hash'].map(lambda x: 0 if len(str(x))>16 else int(x))
features['osv'] = features['osv'].apply(trans_osv)test_features = test[col]
# 构造fea_hash_len特征
test_features['fea_hash_len'] = test_features['fea_hash'].map(lambda x: len(str(x)))
test_features['fea1_hash_len'] = test_features['fea1_hash'].map(lambda x: len(str(x)))
# Thinking:为什么将很大的,很长的fea_hash化为0?
# 如果fea_hash很长,都归为0,否则为自己的本身
test_features['fea_hash'] = test_features['fea_hash'].map(lambda x: 0 if len(str(x))>16 else int(x))
test_features['fea1_hash'] = test_features['fea1_hash'].map(lambda x: 0 if len(str(x))>16 else int(x))
test_features['osv'] = test_features['osv'].apply(trans_osv)

建模

使用默认参数的lgb进行建模,最终成绩:88.094

#train['os'].value_counts()
# 使用LGBM训练
import lightgbm as lgb
model = lgb.LGBMClassifier()
# 模型训练
model.fit(features.drop(['timestamp', 'version'], axis=1), train['label'])
result = model.predict(test_features.drop(['timestamp', 'version'], axis=1))
#features['version'].value_counts()
res = pd.DataFrame(test['sid'])
res['label'] = result
res.to_csv('./baseline.csv', index=False)
res

优化方向

下面列出做过的方案列表,具体版本对比见文末模型结果。具体查看源码:gitee仓库

  1. 添加version的转换使用
  2. 添加timestamp详细使用,增加周末以及diff特征
  3. 添加osvversion的差
  4. 添加lan的准换使用
  5. 添加屏幕比屏幕面积像素比
  6. 使用祖传lgb、祖传xgb等自定义参数模型
  7. 对模型进行5折交叉训练
  8. 多模型5折交叉训练融合

深度学习

本次深度学习方法着重使用百度的飞桨作为基础框架完成

特征处理

针对数据处理模块,大致与机器学习的类似。但由于使用到深度学习,所以在处理完成以后需要对数据进行归一化处理。

import pandas as pd
import warningswarnings.filterwarnings('ignore')# 数据加载
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
test = test.iloc[:, 1:]
train = train.iloc[:, 1:]
train# ##### Object类型: lan, os, osv, version, fea_hash
# ##### 有缺失值的字段: lan, osv# In[2]:# ['os', 'osv', 'lan', 'sid’]
features = train.columns.tolist()
features.remove('label')
print(features)# In[3]:for feature in features:print(feature, train[feature].nunique())# In[4]:# 对osv进行数据清洗
def osv_trans(x):x = str(x).replace('Android_', '').replace('Android ', '').replace('W', '')if str(x).find('.') > 0:temp_index1 = x.find('.')if x.find(' ') > 0:temp_index2 = x.find(' ')else:temp_index2 = len(x)if x.find('-') > 0:temp_index2 = x.find('-')result = x[0:temp_index1] + '.' + x[temp_index1 + 1:temp_index2].replace('.', '')try:return float(result)except:print(x + '#########')return 0try:return float(x)except:print(x + '#########')return 0# train['osv'] => LabelEncoder ?
# 采用众数,进行缺失值的填充
train['osv'].fillna('8.1.0', inplace=True)
# 数据清洗
train['osv'] = train['osv'].apply(osv_trans)# 采用众数,进行缺失值的填充
test['osv'].fillna('8.1.0', inplace=True)
# 数据清洗
test['osv'] = test['osv'].apply(osv_trans)# In[5]:# train['os'].value_counts()
train['lan'].value_counts()
# lan_map = {'zh-CN': 1, }
train['lan'].value_counts().index
lan_map = {'zh-CN': 1, 'zh_CN': 2, 'Zh-CN': 3, 'zh-cn': 4, 'zh_CN_#Hans': 5, 'zh': 6, 'ZH': 7, 'cn': 8, 'CN': 9,'zh-HK': 10, 'tw': 11, 'TW': 12, 'zh-TW': 13, 'zh-MO': 14, 'en': 15, 'en-GB': 16, 'en-US': 17, 'ko': 18,'ja': 19, 'it': 20, 'mi': 21}
train['lan'] = train['lan'].map(lan_map)
test['lan'] = test['lan'].map(lan_map)
test['lan'].value_counts()# In[6]:# 对于有缺失的lan 设置为22
train['lan'].fillna(22, inplace=True)
test['lan'].fillna(22, inplace=True)# In[7]:remove_list = ['os', 'sid']
col = features
for i in remove_list:col.remove(i)
col# In[8]:# train['timestamp'].value_counts()
# train['timestamp'] = pd.to_datetime(train['timestamp'])
# train['timestamp']
from datetime import datetime# lambda 是一句话函数,匿名函数
train['timestamp'] = train['timestamp'].apply(lambda x: datetime.fromtimestamp(x / 1000))
# 1559892728241.7212
# 1559871800477.1477
# 1625493942.538375
# import time
# time.time()
test['timestamp'] = test['timestamp'].apply(lambda x: datetime.fromtimestamp(x / 1000))
test['timestamp']# In[9]:def version_trans(x):if x == 'V3':return 3if x == 'v1':return 1if x == 'P_Final_6':return 6if x == 'V6':return 6if x == 'GA3':return 3if x == 'GA2':return 2if x == 'V2':return 2if x == '50':return 5return int(x)train['version'] = train['version'].apply(version_trans)
test['version'] = test['version'].apply(version_trans)
train['version'] = train['version'].astype('int')
test['version'] = test['version'].astype('int')# In[10]:# 特征筛选
features = train[col]
# 构造fea_hash_len特征
features['fea_hash_len'] = features['fea_hash'].map(lambda x: len(str(x)))
features['fea1_hash_len'] = features['fea1_hash'].map(lambda x: len(str(x)))
# Thinking:为什么将很大的,很长的fea_hash化为0?
# 如果fea_hash很长,都归为0,否则为自己的本身
features['fea_hash'] = features['fea_hash'].map(lambda x: 0 if len(str(x)) > 16 else int(x))
features['fea1_hash'] = features['fea1_hash'].map(lambda x: 0 if len(str(x)) > 16 else int(x))
featurestest_features = test[col]
# 构造fea_hash_len特征
test_features['fea_hash_len'] = test_features['fea_hash'].map(lambda x: len(str(x)))
test_features['fea1_hash_len'] = test_features['fea1_hash'].map(lambda x: len(str(x)))
# Thinking:为什么将很大的,很长的fea_hash化为0?
# 如果fea_hash很长,都归为0,否则为自己的本身
test_features['fea_hash'] = test_features['fea_hash'].map(lambda x: 0 if len(str(x)) > 16 else int(x))
test_features['fea1_hash'] = test_features['fea1_hash'].map(lambda x: 0 if len(str(x)) > 16 else int(x))
test_features# 对训练集的timestamp提取时间多尺度
# 创建时间戳索引
temp = pd.DatetimeIndex(features['timestamp'])
features['year'] = temp.year
features['month'] = temp.month
features['day'] = temp.day
features['week_day'] = temp.weekday  # 星期几
features['hour'] = temp.hour
features['minute'] = temp.minute# 求时间的diff
start_time = features['timestamp'].min()
features['time_diff'] = features['timestamp'] - start_time
features['time_diff'] = features['time_diff'].dt.days + features['time_diff'].dt.seconds / 3600 / 24
features[['timestamp', 'year', 'month', 'day', 'week_day', 'hour', 'minute', 'time_diff']]# 创建时间戳索引
temp = pd.DatetimeIndex(test_features['timestamp'])
test_features['year'] = temp.year
test_features['month'] = temp.month
test_features['day'] = temp.day
test_features['week_day'] = temp.weekday  # 星期几
test_features['hour'] = temp.hour
test_features['minute'] = temp.minute# 求时间的diff
# start_time = features['timestamp'].min()
test_features['time_diff'] = test_features['timestamp'] - start_time
test_features['time_diff'] = test_features['time_diff'].dt.days + test_features['time_diff'].dt.seconds / 3600 / 24
# test_features[['timestamp', 'year', 'month', 'day', 'week_day', 'hour', 'minute', 'time_diff']]
test_features['time_diff']# In[12]:# test['version'].value_counts()
# features['version'].value_counts()
features['dev_height'].value_counts()
features['dev_width'].value_counts()
# 构造面积特征
features['dev_area'] = features['dev_height'] * features['dev_width']
test_features['dev_area'] = test_features['dev_height'] * test_features['dev_width']# In[13]:"""
Thinking:是否可以利用 dev_ppi 和 dev_area构造新特征
features['dev_ppi'].value_counts()
features['dev_area'].astype('float') / features['dev_ppi'].astype('float')
"""
# features['ntt'].value_counts()
features['carrier'].value_counts()
features['package'].value_counts()
# version - osv APP版本与操作系统版本差
features['osv'].value_counts()
features['version_osv'] = features['osv'] - features['version']
test_features['version_osv'] = test_features['osv'] - test_features['version']# In[14]:features = features.drop(['timestamp'], axis=1)
test_features = test_features.drop(['timestamp'], axis=1)# In[16]:# 特征归一化
from sklearn.preprocessing import StandardScalerscaler = StandardScaler()
features1 = scaler.fit_transform(features)
test_features1 = scaler.transform(test_features)

生成Dataset和Dataloader

import paddle
from paddle import nn
from paddle.io import Dataset, DataLoader
import numpy as np
paddle.device.set_device('gpu:0')# 自定义dataset
class MineDataset(Dataset):def __init__(self, X, y):super(MineDataset, self).__init__()self.num_samples = len(X)self.X = Xself.y = ydef __getitem__(self, idx):return self.X.iloc[idx].values.astype('float32'), np.array(self.y.iloc[idx]).astype('int64')def __len__(self):return self.num_samplesfrom sklearn.model_selection import train_test_splittrain_x, val_x, train_y, val_y = train_test_split(features1, train['label'], test_size=0.2, random_state=42)train_x = pd.DataFrame(train_x, columns=features.columns)
val_x = pd.DataFrame(val_x, columns=features.columns)
train_y = pd.DataFrame(train_y, columns=['label'])
val_y = pd.DataFrame(val_y, columns=['label'])train_dataloader = DataLoader(MineDataset(train_x, train_y),batch_size=1024,shuffle=True,drop_last=True,num_workers=2)val_dataloader = DataLoader(MineDataset(val_x, val_y),batch_size=1024,shuffle=True,drop_last=True,num_workers=2)test_dataloader = DataLoader(MineDataset(test_features1, pd.Series([0 for i in range(len(test_features1))])),batch_size=1024,shuffle=True,drop_last=True,num_workers=2)

网络搭建

第一版本网络仅使用简单的全连接层网络。网络结构从250到2的塔石结构,每个线性层之间经过relu和dropout层。

class ClassifyModel(nn.Layer):def __init__(self, features_len):super(ClassifyModel, self).__init__()self.fc1 = nn.layer.Linear(in_features=features_len, out_features=250)self.ac1 = nn.layer.ReLU()self.drop1 = nn.layer.Dropout(p=0.02)self.fc2 = nn.layer.Linear(in_features=250, out_features=100)self.ac2 = nn.layer.ReLU()self.drop2 = nn.layer.Dropout(p=0.02)self.fc3 = nn.layer.Linear(in_features=100, out_features=50)self.ac3 = nn.layer.ReLU()self.drop3 = nn.layer.Dropout(p=0.02)self.fc4 = nn.layer.Linear(in_features=50, out_features=25)self.ac4 = nn.layer.ReLU()self.drop4 = nn.layer.Dropout(p=0.02)self.fc5 = nn.layer.Linear(in_features=25, out_features=2)self.out = nn.layer.Sigmoid()def forward(self, input):x = self.fc1(input)x = self.ac1(x)x = self.drop1(x)x = self.fc2(x)x = self.ac2(x)x = self.drop2(x)x = self.fc3(x)x = self.ac3(x)x = self.drop3(x)x = self.fc4(x)x = self.ac4(x)x = self.drop4(x)x = self.fc5(x)output = self.out(x)return output

网络训练

# 初始化模型
model = ClassifyModel(int(len(features.columns)))
# 训练模式
model.train()
# 定义优化器
opt = paddle.optimizer.AdamW(learning_rate=0.001, parameters=model.parameters())
loss_fn = nn.CrossEntropyLoss()EPOCHS = 10   # 设置外层循环次数
for epoch in range(EPOCHS):for iter_id, mini_batch in enumerate(train_dataloader):x_train = mini_batch[0]y_train = mini_batch[1]# 前向传播y_pred = model(x_train)# 计算损失loss = nn.functional.loss.cross_entropy(y_pred, y_train)# 打印lossavg_loss = paddle.mean(loss)if iter_id % 20 == 0:acc = paddle.metric.accuracy(y_pred, y_train)print("epoch: {}, iter: {}, loss is: {}, acc is: {}".format(epoch, iter_id, avg_loss.numpy(), acc.numpy()))# 反向传播avg_loss.backward()# 最小化loss,更新参数opt.step()# 清除梯度opt.clear_grad()

优化方向

同样,由于篇幅原因,下面两个方案可参考源码:gitee仓库
注意使用Embedding前,请先运行Embedding分析.ipynb生成对应字典文件

  1. 采用基于Embedding的Wide & Deep
  2. 采用基于FM的DeepFM

各版本模型分数结果

分类模型详情分数
MLML第一版本1. 初步建模
2. 不参与建模的特征 [‘os’, ‘version’, ‘lan’, 'sid’]
3. 默认参数LGB
88.094
ML第二版本1. 基于第一版本
2. 引入version,简单转化使用timestamp
3. 测试默认参数LGB与XGB
88.2133
ML第三版本1. 基于第二版本
2. 引入lan
3. 对osv和version做差
4. lgb祖传参数
88.9487
ML第四版本1. 基于第三版本
2. 5折lgb
3. 5折xgb
4. 融合
89.0293
89.0253
89.054
ML第五版本1.基于第三版本
2.添加像素比、像素大小、像素分辨率比
3. 5折lgb
4. 5折xgb
5. 融合
89.1873
89.108
89.1713
PaddlePaddle第一版本1. 基于ML第三版本特征工程
2. 简单基于paddle搭建网络
未上传结果
Paddle第二版本1. 基于第一版本
2. 添加embedding字典创建(在Embedding分析.ipynb)
3.基于embedding的混合基础模型
88.71
Paddle第三版本1. 基于第二版本
2. 添加DeepFM部分模型,然后合并
87.816
TensorFlowTF第一版本1. 基于ML第三版本特征工程
2. 简单基于TensorFlow搭建网络
未上传结果
FMFM第一版本1. 基于FM模型的第一次简单建模57.2147

最终排名得分
在这里插入图片描述
源码地址
https://gitee.com/turkeymz/coggle/tree/master/coggle_202112/mlp

这篇关于【12月Top 2】MarTech Challenge 点击反欺诈预测的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/940129

相关文章

【测试】输入正确用户名和密码,点击登录没有响应的可能性原因

目录 一、前端问题 1. 界面交互问题 2. 输入数据校验问题 二、网络问题 1. 网络连接中断 2. 代理设置问题 三、后端问题 1. 服务器故障 2. 数据库问题 3. 权限问题: 四、其他问题 1. 缓存问题 2. 第三方服务问题 3. 配置问题 一、前端问题 1. 界面交互问题 登录按钮的点击事件未正确绑定,导致点击后无法触发登录操作。 页面可能存在

C# 防止按钮botton重复“点击”的方法

在使用C#的按钮控件的时候,经常我们想如果出现了多次点击的时候只让其在执行的时候只响应一次。这个时候很多人可能会想到使用Enable=false, 但是实际情况是还是会被多次触发,因为C#采用的是消息队列机制,这个时候我们只需要在Enable = true 之前加一句 Application.DoEvents();就能达到防止重复点击的问题。 private void btnGenerateSh

Imageview在百度地图中实现点击事件

1.首先第一步,需要声明的全局有关类的引用 private BMapManager mBMapMan; private MapView mMapView; private MapController mMapController; private RadioGroup radiogroup; private RadioButton normalview; private RadioBu

Tensorflow lstm实现的小说撰写预测

最近,在研究深度学习方面的知识,结合Tensorflow,完成了基于lstm的小说预测程序demo。 lstm是改进的RNN,具有长期记忆功能,相对于RNN,增加了多个门来控制输入与输出。原理方面的知识网上很多,在此,我只是将我短暂学习的tensorflow写一个预测小说的demo,如果有错误,还望大家指出。 1、将小说进行分词,去除空格,建立词汇表与id的字典,生成初始输入模型的x与y d

临床基础两手抓!这个12+神经网络模型太贪了,免疫治疗预测、通路重要性、基因重要性、通路交互作用性全部拿下!

生信碱移 IRnet介绍 用于预测病人免疫治疗反应类型的生物过程嵌入神经网络,提供通路、通路交互、基因重要性的多重可解释性评估。 临床实践中常常遇到许多复杂的问题,常见的两种是: 二分类或多分类:预测患者对治疗有无耐受(二分类)、判断患者的疾病分级(多分类); 连续数值的预测:预测癌症病人的风险、预测患者的白细胞数值水平; 尽管传统的机器学习提供了高效的建模预测与初步的特征重

解决OAuth Token,点击退出登录报404问题

首先,认证服务器发送请求 http://auth.test.com:8085/logout?redirect_uri=http://admin.test.com:8080’ 退出后报404无法跳转到网站首页,这个时候增加一个参数redirect_uri指定退出成功后跳转的路径,因为是自定义的,所以需在认证服务器做一些处理 找到源码默认实现接口DefaultLogoutPageGeneratingF

一个C++程序运行,从点击运行到控制台打印文本,电脑硬件的资源是如何调动的

当点击运行一个 C++ 程序并看到控制台输出文本时,计算机硬件和操作系统之间协同工作,完成了多个步骤。这些步骤涉及 CPU、内存、存储设备、操作系统和输入输出设备的共同作用。下面是一个详细的过程描述: 1. 程序加载 启动:当你点击运行一个可执行文件时,操作系统(通常是 Windows、Linux 或 macOS)的文件系统管理器识别请求,并启动加载程序。读取可执行文件:加载程序将可执行文件从

结合Python与GUI实现比赛预测与游戏数据分析

在现代软件开发中,用户界面设计和数据处理紧密结合,以提升用户体验和功能性。本篇博客将基于Python代码和相关数据分析进行讨论,尤其是如何通过PyQt5等图形界面库实现交互式功能。同时,我们将探讨如何通过嵌入式预测模型为用户提供赛果预测服务。 本文的主要内容包括: 基于PyQt5的图形用户界面设计。结合数据进行比赛预测。文件处理和数据分析流程。 1. PyQt5 图形用户界面设计

CNN-LSTM模型中应用贝叶斯推断进行时间序列预测

这篇论文的标题是《在混合CNN-LSTM模型中应用贝叶斯推断进行时间序列预测》,作者是Thi-Lich Nghiem, Viet-Duc Le, Thi-Lan Le, Pierre Maréchal, Daniel Delahaye, Andrija Vidosavljevic。论文发表在2022年10月于越南富国岛举行的国际多媒体分析与模式识别会议(MAPR)上。 摘要部分提到,卷积

vue el-dialog嵌套解决无法点击问题

产生原因: 当你在 el-dialog 上嵌套另一个 el-dialog 窗口时,可能会遇到内部对话框无法点击的问题。这通常是由于嵌套对话框的遮罩层(overlay)或其他样式问题造成的。 解决方案: 如果你的 el-dialog 组件支持 append-to-body 属性,你可以将对话框附加到 body 元素上,以避免 z-index 问题。 <template><el-dialo