比赛介绍:通过比赛提供的50万次点击数据来预测用户的点击行为是否为正常点击,还是作弊行为。通过点击欺诈预测的方法,可以帮助商家鉴别点击欺诈,锁定精准真实用户。
这次比赛参考了https://aistudio.baidu.com/aistudio/projectdetail/1206845 的方案,在划分训练集和验证集上进行了修改尝试,尝试划分训练集:验证集=90:10;95:5;80:20。 95:5的结果徘徊于86.78左右,80:20结果徘徊于85.8左右,可以推测划分在9:1左右的时候可以达到最大值,也可以周围小幅度再次细化。同时对学习率进行了调整,在原模型0.0007的基础上进行略微下调与上调,情况不太可观。同时对epoch进行增加,发现随着epoch的增加,结果会随之下降。
飞桨常规赛:反欺诈预测11月第7名方案
- 1.数据预处理:
- 2.数据读取部分(Reader类)。
- 3.网络部分
- 4.模型训练、预测
- 5.总结
1.数据预处理部分:
运用embedding的方法处理,将离散变量转为连续向量表示,可以减少离散变量的空间维数。
*注意若用one—hot编码处理,与embedding相比有明显缺点:
1.对于具有非常多类型的类别变量,变换后的向量维数过于巨大,且过于稀疏。
2.映射之间完全独立,并不能表示出不同类别之间的关系。
对于连续值使用归一化处理。
import os
import pandas as pd
import numpy as np
from paddle.io import Dataset
from baseline_tools import Data2IdNorm,Data2IdEmb,value2numpy,make_dict_file
TAGS = {'android_id': None,
'apptype': "emb",
'carrier': "emb",
'dev_height': "emb",
'dev_ppi': "emb",
'dev_width': "emb",
'lan': "emb",
'media_id': "emb",
'ntt': "emb",
'os': "emb",
'osv': "emb",
'package': "emb",
'sid': None,
'timestamp': "norm",
'version': "emb",
'fea_hash': "norm",
'location': "emb",
'fea1_hash': "norm",
'cus_type': "emb"}
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/layers/utils.py:26: DeprecationWarning: `np.int` is a deprecated alias for the builtin `int`. To silence this warning, use `int` by itself. Doing this will not modify any behavior and is safe. When replacing `np.int`, you may wish to use e.g. `np.int64` or `np.int32` to specify the precision. If you wish to review your current use, check the release note link for additional information.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
def convert_to_list(value, n, name, dtype=np.int):
datas = pd.read_csv("train.csv")
for ids,data in enumerate(datas["fea_hash"]):
try:
data = float(data)
except:
datas["fea_hash"][ids] = 499997879
print(ids+1)
datas.to_csv("train.csv")
datas = pd.read_csv("test.csv",dtype=str)
for ids,data in enumerate(datas["fea_hash"]):
try:
data = float(data)
except:
datas["fea_hash"][ids] = 499997879
print(ids+1)
datas = datas
datas.to_csv("test.csv")
%mkdir emb_dicts
TRAIN_PATH = "train.csv"
SAVE_PATH = "emb_dicts"
df = pd.read_csv(TRAIN_PATH, index_col=0)
pack = dict()
for tag, tag_method in TAGS.items():
if tag_method != "emb":
if tag_method == "norm":
data = df.loc[:, tag]
print("{}_max的倒数:{}".format(tag,1/float(data.max())),"--------",float(data.max())/2)
print("{}_max:{}".format(tag,float(data.max())),"--------min:",float(data.min()))
continue
data = df.loc[:, tag]
dict_size = make_dict_file(data, SAVE_PATH, dict_name=tag)
pack[tag] = dict_size + 1
with open(os.path.join(SAVE_PATH, "size.dict"), "w", encoding="utf-8") as f:
f.write(str(pack))
print("全部生成完毕")
mkdir: cannot create directory ‘emb_dicts’: File exists
apptype 字典生成完毕,共 89 个id
carrier 字典生成完毕,共 5 个id
dev_height 字典生成完毕,共 798 个id
dev_ppi 字典生成完毕,共 92 个id
dev_width 字典生成完毕,共 346 个id
lan 字典生成完毕,共 22 个id
media_id 字典生成完毕,共 284 个id
ntt 字典生成完毕,共 8 个id
os 字典生成完毕,共 2 个id
osv 字典生成完毕,共 155 个id
package 字典生成完毕,共 1950 个id
timestamp_max的倒数:6.409861939473897e-13 -------- 780048002158.7462
timestamp_max:1560096004317.4924 --------min: 1559491201174.7812
version 字典生成完毕,共 22 个id
fea_hash_max的倒数:2.3283201561138293e-10 -------- 2147470994.0
fea_hash_max:4294941988.0 --------min: 0.0
location 字典生成完毕,共 332 个id
fea1_hash_max的倒数:2.3299594677571534e-10 -------- 2145960077.5
fea1_hash_max:4291920155.0 --------min: 12400.0
cus_type 字典生成完毕,共 58 个id
全部生成完毕
NORM_WEIGHT = {'timestamp': 6.409845522722902e-13,
"fea_hash":2.3283201561138293e-10,
"fea1_hash":2.3299594677571534e-10,
"android_id":1.4086530e-06,
"dev_height":0.00011081560283687943,
"dev_ppi":0.001388888888888889,
"dev_width":0.00011322463768115942
}
2.数据读取部分
数据集一共有50万条数据,划分了0.9作为训练集,0.1作为验证集。
尝试细化划分训练集:验证集=90:10;95:5;80:20的三种情况。 95:5的结果徘徊于86.78左右,80:20结果徘徊于85.8左右,可以推测划分在9:1左右的时候可以达到最大值,也可以周围小幅度调整再次细化。
再将emb数据用字典进行处理,norm数据进行归一化处理,以便后面训练。
def get_size_dict(dict_path="./emb_dicts/size.dict"):
with open(dict_path, "r", encoding="utf-8") as f:
try:
size_dict = eval(f.read())
except Exception as e:
print("size_dict打开失败,", dict_path, "文件是否正常,报错信息如下:\n", e)
return size_dict
class Reader(Dataset):
def __init__(self,
is_infer: bool = False,
is_val: bool = False,
use_mini_train: bool = False,
emb_dict_path="./emb_dicts"):
super().__init__()
train_name = "mini_train" if use_mini_train else "train"
file_name = "test" if is_infer else train_name
df = pd.read_csv(file_name + ".csv")
DATA_RATIO = 0.9
if is_infer:
self.df = df.reset_index()
else:
start_index = 0 if not is_val else int(len(df) * DATA_RATIO)
end_index = int(len(df) * DATA_RATIO) if not is_val else int(len(df) * 1)#len(df)
self.df = df.loc[start_index:end_index].reset_index()
# 数据预处理
self.cols = [tag for tag, tag_method in TAGS.items() if tag_method is not None]
self.methods = dict()
for col in self.cols:
if TAGS[col] == "emb":
self.methods[col] = Data2IdEmb(dict_path=emb_dict_path, dict_name=col).get_method()
elif TAGS[col] == "norm":
self.methods[col] = Data2IdNorm(norm_weight=NORM_WEIGHT[col]).get_method()
else:
raise Exception(str(TAGS) + "是未知的预处理方案")
self.add_label = not is_infer
self.is_val = is_val
def __getitem__(self, index):
pack = []
for col in self.cols:
sample = self.df.loc[index, col]
sample = self.methods[col](sample)
pack.append(sample)
if self.add_label:
tag_data = self.df.loc[index, "label"]
tag_data = np.array(tag_data).astype("int64")
pack.append(tag_data)
return pack
else:
return pack
def __len__(self):
return len(self.df)
3.网络搭建
1.emb字段数据先用embedding处理,再将embedding层输出带入LSTM层,最后带入一个全连接层。(LSTM+Relu)
LSTM负责计算时间序列中各个观测值之间的依赖性,倾向于在具有更多固定成分的不稳定时间序列上做得更好。
Relu可以使网络训练更快,并且增加网络的非线性,防止梯度消失(当数值过大或者过小,sigmoid,tanh的导数接近于0,relu为非饱和激活函数不存在这种现象)
2.norm字段是直接带入3层的全连接层。由于norm字段少,且不那么"连续",所以增加层数与神经元数目作用不大。
3.None字段不使用,数据杂乱,很难训练。
4.将emb字段处理的输出层和norm字段处理后输出层整合到一层中去,然后再经过几层全链接层,最后输入到softmax层中,得出预测值。
import os
import numpy as np
import pandas as pd
import paddle
import paddle.nn as nn
import paddle.tensor as tensor
from paddle.static import InputSpec
from paddle.metric import Accuracy
class SampleNet(paddle.nn.Layer):
def __init__(self, tag_dict: dict, size_dict: dict):
super().__init__()
self.hidden_layers_list = []
out_layer_input_size = 0
self.relu = paddle.nn.LeakyReLU()
self.drop = paddle.nn.Dropout(p=0.2)
self.dict_list = ['emb', 'emb', 'emb', 'emb', 'emb', 'emb', 'emb', 'emb', 'emb', 'emb', 'emb', 'norm', 'emb', 'norm', 'emb', 'norm', 'emb']
for tag, tag_method in tag_dict.items():
if tag_method == "emb":
emb = nn.Embedding(num_embeddings=size_dict[tag], embedding_dim=EMB_SIZE)
self.hidden_layers_list.append(emb)
elif tag_method == "norm":
continue
elif tag_method is None:
continue
else:
raise Exception(str(tag_method) + "为未知的处理方案")
self.lstm = nn.LSTM(EMB_SIZE, 128, 1)
self.lin_emb = nn.Linear(in_features=128, out_features=EMB_LINEAR_SIZE)
self.lin_norm1 = nn.Linear(in_features=1, out_features=3)
self.lin_norm2 = nn.Linear(in_features=3, out_features=1)
self.out_layers = nn.Linear(in_features=899,
out_features=42)
self.out_layers1 = nn.Linear(in_features=42,
out_features=2)
def forward(self, *input_data):
layer_list = []
num_id = 0
for sample_data, tag_method in zip(input_data, self.dict_list):
tmp = sample_data
if tag_method == "emb":
emb = self.hidden_layers_list[num_id]
tmp = emb(tmp)
tmp, (_, _) = self.lstm(tmp)
tmp = self.lin_emb(tmp)
tmp = self.relu(tmp)
num_id += 1
elif tag_method == "norm":
tmp = self.lin_norm1(tmp)
tmp = self.relu(tmp)
tmp = self.lin_norm2(tmp)
tmp = self.relu(tmp)
elif tag_method is None:
continue
else:
raise Exception(str(tag_method) + "为未知的处理方案")
layer_list.append(tensor.flatten(tmp, start_axis=1))
layers = tensor.concat(layer_list, axis=1)
layers = self.out_layers(layers)
layers = self.relu(layers)
layers = self.drop(layers)
layers = self.out_layers1(layers)
result = self.relu(layers)
result = paddle.nn.functional.softmax(result)
return result
4.模型训练、预测
对学习率进行了调整,在原模型0.0007的基础上进行略微下调与上调,情况不可观。
同时对epoch进行增加,发现随着epoch的增加,结果会随之下降。
所以暂且固定epoch值为2,learning rate为0.0007.
# 模型保存与加载文件夹
SAVE_DIR = "./output/"
# 推理部分
IS_INFER = False
TEST_BATCH_SIZE = 32
RESULT_FILE = "./result.csv"
# 超参数
EPOCHS = 2 # 训练循环,epoch增加则结果下降
TRAIN_BATCH_SIZE = 1
EMB_SIZE = 128
EMB_LINEAR_SIZE = 64
# 训练环境
USE_MINI_DATA = False
USE_GPU = False
paddle.disable_static(place=paddle.CUDAPlace(0) if USE_GPU else paddle.CPUPlace())
# 定义网络输入
inputs = []
for tag_name, tag_m in TAGS.items():
d_type = "float32"
if tag_m == "emb":
d_type = "int64"
if tag_m is None:
continue
inputs.append(InputSpec(shape=[-1, 1], dtype=d_type, name=tag_name))
# 定义Label
labels = [InputSpec([-1, 1], 'int64', name='label')]
# 实例化SampleNet以及Reader
model = paddle.Model(SampleNet(TAGS, get_size_dict()), inputs=inputs, labels=labels)
# 推理部分
if IS_INFER:
pass
#直接训练完推理结果
else:
train_reader = Reader(use_mini_train=USE_MINI_DATA)
val_reader = Reader(use_mini_train=USE_MINI_DATA, is_val=True)
optimizer = paddle.optimizer.Adam(learning_rate=0.0007, parameters=model.parameters())#learning_rate可修改
model.prepare(optimizer, paddle.nn.loss.CrossEntropyLoss(), Accuracy())
model.fit(train_data=train_reader,
eval_data=val_reader,
batch_size=TRAIN_BATCH_SIZE,
epochs=EPOCHS,
log_freq=1000,
save_dir=SAVE_DIR)
infer_reader = Reader(is_infer=True)
model.prepare()
infer_output = model.predict(infer_reader, TEST_BATCH_SIZE)
result_df = infer_reader.df.loc[:, "sid"]
pack = []
for batch_out in infer_output[0]:
for sample in batch_out:
pack.append(np.argmax(sample))
# 保存csv文件
result_df = pd.DataFrame({"sid": np.array(result_df, dtype="int64"), "label": pack})
result_df.to_csv(RESULT_FILE, index=False)
print("结果文件保存至:", RESULT_FILE)
W1217 19:06:05.080075 140 device_context.cc:362] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 10.1, Runtime API Version: 10.1
W1217 19:06:05.085245 140 device_context.cc:372] device: 0, cuDNN Version: 7.6.
6.总结:
因为在将近11月底才接触这个比赛,只能在官方给出baseline的参数上进行一些细化分析。
1.在划分训练集和验证集上进行了修改尝试,尝试划分训练集:验证集=90:10;95:5;80:20。
95:5的结果徘徊于86.78左右,80:20结果徘徊于85.8左右,可以推测划分在9:1左右的时候可以达到最大值,也可以周围小幅度再次细化。
2.对学习率进行了调整,在原模型0.0007的基础上进行略微下调与上调,情况不可观。
3.对epoch进行增加,发现随着epoch的增加,结果会随之下降。
*因为运行一次需要花费的时间有10小时左右,所以简单训练了一下,达到了87.3947分。
优化方向:考虑使用网格搜索算法挑选验证集误差最小的超参数组合,从而提升分数。
未使用的两个字段可以考虑使用并改进。
作为一个怀揣轻薄本的新人,在老师的推荐下使用了飞桨,效率可以说达到了质的飞跃与突破,同时百度开源的深度学习框架paddle给我进一步的学习带来了很大的进步与突破。总的说好的不行!!!大家都给我用起来!!!