用Embedding+NN打钉钉杯!
钉钉杯A赛题是经典的银行卡诈骗预测,在刚看到赛题的时候我就联想到Paddle上的常规赛:MarTech Challenge 点击反欺诈预测。在观察数据之后又联想到NLP的Embedding,由于我懒的再去做特征工程了,就直接结合了Embedding和NN进行模型搭建。在没进行参数调优、网络结构修改、数据均衡等手段的情况下,线下成绩能够直接达到MeanF1-0.9878(各种模型平均水准),可能在经
钉钉杯A赛题是经典的银行卡诈骗预测,在刚看到赛题的时候我就联想到Paddle上的常规赛:MarTech Challenge 点击反欺诈预测。在观察数据之后又联想到NLP的Embedding,由于我懒的再去做特征工程了,就直接结合了Embedding和NN进行模型搭建。在没进行参数调优、网络结构修改、数据均衡等手段的情况下,线下成绩能够直接达到MeanF1-0.9878(各种模型平均水准),可能在经过调优后,模型可以达到更好的效果
1.Embedding介绍
参考怎么形象理解embedding这个概念?中的介绍:
对于颜色,我们可以把它拆成三个特征维度,用这三个维度的组合理论上可以表示任意一种颜色。同理,对于词,我们也可以把它拆成指定数量的特征维度,词表中的每一个词都可以用这些维度组合成的向量来表示,这个就是Word Embedding的含义。
那么在本次比赛中,为什么要使用Embedding?原因其实很简单,数据中的后四列repeat_retailer,used_chip,used_pin_number,online_order是由0和1组成的,直接传入Linear层中,会导致Hidden Layer中很多特征没有使用到,这时候我们很容易就联想到Embedding!
具体做法就是使用Paddle.nn中的Embeeding实现Embedding,非常容易实现!
那么Embedding中的参数怎么选择?如上图中所示,可以看到主要的参数是num_embeddings和embedding_dim,在官方文档中我们可以看到:num_embeddings (int) - 嵌入字典的大小, input中的id必须满足 0 =< id < num_embeddings。在本次比赛的数据集中,后四列数据都是由0和1组成,我们在这里选择num_embeddings为2+1。然后在embedding_dim的选择上就比较主观了,一般按照经验选择后慢慢调整,一般而言,数据的id数量越大,dim就越大,在这里我们选择dim为16
2.NN模型设计
NN网络结构设计就比较玄学了,在网络设计上面参考了MarTech Challenge 点击反欺诈预测的baseline。对后四列数据传入Embedidng之后在过一个Linear层映射到统一的维度,在这里我们选择了一个16这个比较小的维度,那么对于前两列数据,我们就直接过一个linear层+Relu激活函数。随后将两者在维度1上concat起来,在过两个Linear层之后求均值,经过一个分类头就可以得到输出了。结构如下图:
3.开始训练
训练策略就比较简单粗暴了,优化器无脑选择Adamw,Loss函数选择BCEWithLogitsLoss,随机划分一下数据就直接开始训练了。
引入一下包
import paddle
import paddle.nn as nn
import pandas as pd
from paddle.io import Dataset
from sklearn import preprocessing
from paddle.io import random_split
from paddle.io import DataLoader
from paddle.optimizer import AdamW
from tqdm import tqdm
from sklearn.metrics import f1_score, accuracy_score
自定义数据集
# 自定义数据集
class MyDataset(Dataset):
"""
步骤一:继承paddle.io.Dataset类
"""
def __init__(self, data_url):
"""
步骤二:实现构造函数,定义数据集大小
"""
super(MyDataset, self).__init__()
self.data_url = data_url
ann = (pd.read_csv(data_url, encoding="utf-8").to_numpy())
min_max_scaler = preprocessing.MinMaxScaler()
self.ann = min_max_scaler.fit_transform(ann)
def __getitem__(self, index):
"""
步骤三:实现__getitem__方法,定义指定index时如何获取数据,并返回单条数据(训练数据,对应的标签)
"""
# 前三列是无需过Embedding的,后四列需要过Embedding就单独返回
no_emb = self.ann[index, 0:3]
rep = self.ann[index, 3]
chip = self.ann[index, 4]
pin = self.ann[index, 5]
ol = self.ann[index, 6]
label = self.ann[index, -1]
no_emb_tensor = paddle.to_tensor(no_emb, dtype='float32')
rep = paddle.to_tensor(rep, dtype='int32')
chip = paddle.to_tensor(chip, dtype='int32')
pin = paddle.to_tensor(pin, dtype='int32')
ol = paddle.to_tensor(ol, dtype='int32')
label_tensor = paddle.zeros([2])
if label == 0.:
label_tensor[0] = 1
else:
label_tensor[1] = 1
data = {
'no_emb': no_emb_tensor,
'rep': rep,
'chip': chip,
'pin': pin,
'ol': ol,
'label': label_tensor
}
return data
def __len__(self):
"""
步骤四:实现__len__方法,返回数据集总数目
"""
return len(self.ann)
构造模型
# 定义模型
class My_modle(nn.Layer):
def __init__(self, drop):
super(My_modle, self).__init__()
self.emb_rep = EmbLayer(num=2, dim=16, out_dim=16)
self.emb_chip = EmbLayer(num=2, dim=16, out_dim=16)
self.emb_pin = EmbLayer(num=2, dim=16, out_dim=16)
self.emb_ol = EmbLayer(num=2, dim=16, out_dim=16)
self.dense_fc = FCWithAct(3, 16, drop)
self.fc1 = FCWithAct(16, 224, drop)
self.fc2 = FCWithAct(224, 448, drop)
self.fc3 = FCWithAct(448, 448, drop)
self.class_head = nn.Linear(448, 2)
def forward(self, inputs):
x1 = self.dense_fc(inputs['no_emb'])
# 这里要unsqueeze一下,将维度对齐
x1 = paddle.unsqueeze(x1, 1)
x2 = self.emb_rep(inputs['rep'])
x3 = self.emb_chip(inputs['chip'])
x4 = self.emb_pin(inputs['pin'])
x5 = self.emb_ol(inputs['ol'])
x = paddle.concat([x1, x2, x3, x4, x5], 1)
y = self.fc1(x)
y = self.fc2(y)
y = paddle.mean(y, 1)
y = paddle.squeeze(y, 1)
y = self.fc3(y)
out = self.class_head(y)
return out
class EmbLayer(nn.Layer):
def __init__(self, num, dim, out_dim):
super(EmbLayer, self).__init__()
self.emb = nn.Embedding(num, dim)
self.fc = nn.Linear(dim, out_dim)
def forward(self, inputs):
y = self.emb(inputs)
y = self.fc(y)
return y
class FCWithAct(nn.Layer):
def __init__(self, in_dim, out_dim, drop):
super(FCWithAct, self).__init__()
self.linear = nn.Linear(in_dim, out_dim)
self.act = nn.ReLU()
self.drop = nn.Dropout(drop)
def forward(self, inputs):
y = self.linear(inputs)
y = self.act(y)
y = self.drop(y)
return y
定义一下评价指标,这里使用meanF1和acc进行评价,其中meanF1=(microF1+macroF1)/2
def getF1(predictions, labels):
# prediction and labels are all level-2 class ids
f1_micro = f1_score(labels, predictions, average='micro')
f1_macro = f1_score(labels, predictions, average='macro')
mean_f1 = (f1_micro + f1_macro) / 2.0
eval_results = {'f1_micro': f1_micro,
'f1_macro': f1_macro,
'mean_f1': mean_f1}
return eval_results
定义一下训练函数
# 定义训练函数
def train(model, train_loader, val_loader, loss_fun, opt, epoch):
for i in range(epoch):
mean_loss = paddle.zeros([1])
train_loader = tqdm(train_loader)
# train
model.train()
for step, data in enumerate(train_loader):
pred = model(data)
loss = loss_fun(pred, data['label'])
mean_loss = (mean_loss * step + loss.detach()) / (step + 1)
# 64, 1
pred_acc = paddle.argmax(pred, 1)
label = paddle.topk(data['label'], 1)[1]
acc = accuracy_score(label.cpu().numpy(), pred_acc.cpu().numpy())
loss.backward()
opt.step()
opt.clear_grad()
train_loader.desc = "[epoch {}] loss{} mean_loss{} acc{} lr{}".format(i,
round(loss.item(), 2),
round(mean_loss.item(), 2),
round(acc, 2), opt.get_lr())
# eval
model.eval()
predictions = []
labels = []
eval_loss = paddle.zeros([1])
mean_acc = paddle.zeros([1])
val_loader = tqdm(val_loader)
for step, data in enumerate(val_loader):
pred = model(data)
loss = loss_fun(pred, data['label'])
eval_loss = (eval_loss * step + loss.detach()) / (step + 1)
pred_acc = paddle.argmax(pred, 1)
label = paddle.topk(data['label'], 1)[1]
acc = accuracy_score(label.cpu().numpy(), pred_acc.cpu().numpy())
mean_acc = (mean_acc * step + acc) / (step + 1)
predictions.extend(pred_acc.cpu().numpy())
labels.extend(label.cpu().numpy())
F1 = getF1(predictions, labels)
print(f'mena_loss:{eval_loss.item()}, mean_acc:{mean_acc.item()}, {F1}')
state_dict = model.state_dict()
paddle.save(state_dict, f"save/Epoch{i}_f1{F1['mean_f1']}.pdparams")
开始训练,注意数据集直接放在和main.ipynb的同级目录下
# 定义超参数,固定随机种子
paddle.seed(2022)
TRAIN_BATCH_SIZE = 128
VAL_BATCH_SIZE = 256
DROP = 0.2
LR = 7e-4
DECAY_STEP = 7000
GAMMA = 0.5
# 创建数据集、loader
all_dataset = MyDataset('card_transdata.csv')
data_size = len(all_dataset)
train_size = int(data_size * 0.9)
val_size = int(data_size * 0.1)
train_dataset, val_dataset = random_split(all_dataset, [train_size, val_size])
train_loader = DataLoader(train_dataset, batch_size=TRAIN_BATCH_SIZE, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=VAL_BATCH_SIZE)
# 实现模型,优化器, loss
model = My_modle(DROP)
lr_scheduler = paddle.optimizer.lr.StepDecay(
LR,
step_size=DECAY_STEP,
gamma=GAMMA
)
optimizer = AdamW(
learning_rate=lr_scheduler,
parameters=model.parameters()
)
loss_fun = paddle.nn.BCEWithLogitsLoss()
fun = paddle.nn.BCEWithLogitsLoss()
train(model, train_loader, val_loader, loss_fun, optimizer, 20)
最终模型会保存在save文件夹下,训练效果如下:
可以看到模型收敛还是很快的,最终在训练了两轮之后就F1就开始下降了,最终的线下成绩在0.9878
4.优化方向
- 在数据上面可以多做几次实验,某些数据可能不是特别有用
- 项目中没有考虑数据清洗和类别不平衡问题,各位可以从Loss上优化或者将数据进行负采样,甚至是交叉验证
- 模型的层数,参数可以再多次选择一下
- 优化器,学习率,batchsize等等参数都可以再调整
转载自:https://aistudio.baidu.com/aistudio/projectdetail/4369391
更多推荐
所有评论(0)