比赛介绍:通过比赛提供的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给我进一步的学习带来了很大的进步与突破。总的说好的不行!!!大家都给我用起来!!!