CCF BDCI 返乡发展人群预测
admin
2024-01-25 21:14:16
0

机缘

在网上找数据集的时候无意中看到了有这么个比赛,伴随着好奇的心里以及尝试的心态就参加试一试。这次比赛出现了一些让我意想不到的结果特此记录一下。

赛题内容

基于中国联通的大数据能力,通过使用对联通的信令数据、通话数据、互联网行为等数据进行建模,对个人是否会返乡工作进行判断。以上为官网的背景介绍。以论文的角度看应该是要参考一些人口流动预测的场景。
数据集介绍:

  1. train.csv:包含全量数据集的70%(dataNoLabel是训练集的一部分,选手可以自己决定是否使用)
  2. 位置类特特征:基于联通基站产生的用户信令数据;
  3. 互联网类特征:基于联通用户上网产生的上网行为数据;
  4. 通话类特征:基于联通用户日常通话、短信产生的数据;
  5. 评价指标:该问题作为一个二分类问题使用ROC曲线下面积AUC(Area Under Curve)作为评价指标,AUC越大,预测越准确。
  6. 赛题链接:返乡人群预测

Baseline

这个赛题已经有大佬在网上开源了baseline方案。大佬的解决方案的大致思路是:

  1. 先对原始特征进行暴力穷举:
train_data['f47'] = train_data['f1'] * 10 + train_data['f2']
test_data['f47'] = test_data['f1'] * 10 + test_data['f2']
loc_f = ['f1', 'f2', 'f4', 'f5', 'f6']
for df in [train_data, test_data]:for i in range(len(loc_f)):for j in range(i + 1, len(loc_f)):df[f'{loc_f[i]}+{loc_f[j]}'] = df[loc_f[i]] + df[loc_f[j]]df[f'{loc_f[i]}-{loc_f[j]}'] = df[loc_f[i]] - df[loc_f[j]]df[f'{loc_f[i]}*{loc_f[j]}'] = df[loc_f[i]] * df[loc_f[j]]df[f'{loc_f[i]}/{loc_f[j]}'] = df[loc_f[i]] / (df[loc_f[j]]+1)com_f = ['f43', 'f44', 'f45', 'f46']
for df in [train_data, test_data]:for i in range(len(com_f)):for j in range(i + 1, len(com_f)):df[f'{com_f[i]}+{com_f[j]}'] = df[com_f[i]] + df[com_f[j]]df[f'{com_f[i]}-{com_f[j]}'] = df[com_f[i]] - df[com_f[j]]df[f'{com_f[i]}*{com_f[j]}'] = df[com_f[i]] * df[com_f[j]]df[f'{com_f[i]}/{com_f[j]}'] = df[com_f[i]] / (df[com_f[j]]+1)
cat_columns = ['f3']
data = pd.concat([train_data, test_data])for col in cat_columns:lb = LabelEncoder()lb.fit(data[col])train_data[col] = lb.transform(train_data[col])test_data[col] = lb.transform(test_data[col])
num_columns = [ col for col in train_data.columns if col not in ['id', 'label', 'f3']]
feature_columns = num_columns + cat_columns
target = 'label'train = train_data[feature_columns]
label = train_data[target]
test = test_data[feature_columns]
print(train.shape)
print(train)
  1. 采用了lgb的树模型和5折交叉验证来进行训练
features = [i for i in train.columns if i not in ['label',  'id']]
y = train['label']
KF = StratifiedKFold(n_splits=5, random_state=2021, shuffle=True)
feat_imp_df = pd.DataFrame({'feat': features, 'imp': 0})
params = {'objective': 'binary','boosting_type': 'gbdt','metric': 'auc','n_jobs': 30,'learning_rate': 0.05,'num_leaves': 2 ** 6,'max_depth': 8,'tree_learner': 'serial','colsample_bytree': 0.8,'subsample_freq': 1,'subsample': 0.8,'num_boost_round': 5000,'max_bin': 255,'verbose': -1,'seed': 2021,'bagging_seed': 2021,'feature_fraction_seed': 2021,'early_stopping_rounds': 100,}oof_lgb = np.zeros(len(train))
predictions_lgb = np.zeros((len(test)))
# 模型训练
for fold_, (trn_idx, val_idx) in enumerate(KF.split(train.values, y.values)):print("fold n°{}".format(fold_))trn_data = lgb.Dataset(train.iloc[trn_idx][features], label=y.iloc[trn_idx])val_data = lgb.Dataset(train.iloc[val_idx][features], label=y.iloc[val_idx])num_round = 3000clf = lgb.train(params,trn_data,num_round,valid_sets=[trn_data, val_data],verbose_eval=100,early_stopping_rounds=50,)oof_lgb[val_idx] = clf.predict(train.iloc[val_idx][features], num_iteration=clf.best_iteration)predictions_lgb[:] += clf.predict(test[features], num_iteration=clf.best_iteration) / 5feat_imp_df['imp'] += clf.feature_importance() / 5print("AUC score: {}".format(roc_auc_score(y, oof_lgb)))
print("F1 score: {}".format(f1_score(y, [1 if i >= 0.5 else 0 for i in oof_lgb])))
print("Precision score: {}".format(precision_score(y, [1 if i >= 0.5 else 0 for i in oof_lgb])))
print("Recall score: {}".format(recall_score(y, [1 if i >= 0.5 else 0 for i in oof_lgb])))

这里就不放完整的大佬代码了,有需要的可以去赛题的评论区里面自行获取。


自己的理解

以上是大佬们开源的解决方案,就我个人感觉方案存在一些小问题(虽然我的排名非常垃圾可是我依旧想这样说一下我的看法):

  1. 大佬们的解决方案没有使用那个未标注的数据集。
  2. 可以看到大佬们的特征构造是对原始特征的几个字段进行暴力穷举得到的新特征并没有使用全部的字段,那就有个问题为什么其他字段的特征就没有用呢?能不能有一种这样的方法可以自动发掘和构造这样的特征以及可以自主判断特征对于目标问题的重要程度呢?
  3. 而且大佬们采用了lgb模型没有考虑样本之间的关系。为什么我说样本之间可能是存在关系的呢?这里的意思并不是说会默认人的行为活动是相关的,就是说A的返乡行为一定会影响B。比如,如果A和B都是一个学校的学生,那么他俩会有影响。而是存在某种意义度量下样本之间存在一定的联系,具体为比如毕业季,不在同一个学校的两个互不认识的学生应该具有相同或者相似的行为比如毕业旅行、找工作、聚餐等等。这样就算两个人不认识,我觉得也可以根据特征上的相似度来构建样本之间的拓扑结构。从而聚合特征来消除类内之间样本的差异性和噪声得到比如返乡学生的一般性特征。这是我的想法,我觉得是有道理的。

解决方案

以上是我对场景的理解。也就是说我认为解决预测问题的需要一种可以自适应的挖掘出特征之间对目标的重要程度,并且可以使用未标注数据集,而且还可以自动挖掘出样本之间的相关性的模型。

  1. 针对第一个可以自动挖掘特征对目标的重要程度,换种说法也就是可以对特征进行自适应的加权。自然可以想到多头注意力机制。
  2. 而可以使用未标注数据集,而且还可以自动挖掘出样本之间的相关性这两个问题我们而然可以想到图模型。如果接触过图学习的可能知道图数据中有一个trainmask和testmask的参数。只要我们让那些未标注的数据的这两个参数全部等于false那也就是让这部分数据即不参与训练也不参与预测,只进行特征聚合。至于样本之间的联系我们知道图本身可以表示数据之间的联系。
  3. 经以上想法我们将样本中每个人的特征看成是图上的节点,将原始问题转化为图上的节点的二分类问题。通过多头注意力机制加图学习来进行预测。
  4. 但是以上想法存在问题,就是我们没有图上边的信息,我们该如何在没有先验知识的情况下构建图的邻接矩阵呢?我们采用KDD22上一篇癫痫病预测论文里面的方法:通过计算样本之间向量在映射到高维空间当中的余弦相似度,采用topk机制,选取前k个最大的值对应的样本作为该样本的一阶邻居这种方法让模型来自适应的学习出图的邻接矩阵。(论文名字我忘记了,我只记得是KDD22)

代码

引入库文件

import os
import numpy as np
import cv2
import matplotlib.pyplot as plt
import torch
from torch.utils.data import Dataset,DataLoader
import torch_geometric
import pandas as pd
import networkx as ntx
import torch
import numpy as np
import torch.nn as nn
import math
import torch.nn.functional as F
import torch.nn as nn
import torch.nn.functional as F
from torchmetrics import Accuracy
from torchmetrics import AUROC
#from torchmetrics.classification import BinaryAccuracy
import torch
import numpy as np
import torch.nn as nn
import math
import torch.nn.functional as F

引入数据集

batchsize=1024##batchsize直接和图的邻接矩阵的规模相关不能太小
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
class MyDataset(torch.utils.data.Dataset):def __init__(self, root,datatype,num=49858):name1= os.path.join(root,'dataTrain.csv')name2= os.path.join(root,'dataNoLabel.csv')name3 =os.path.join(root,'dataA.csv')  name=''if(datatype=='train'):name=name1else:name=name3df=pd.read_csv(name)df1=pd.read_csv(name1)df2=pd.read_csv(name2)df3=pd.read_csv(name3)self.data2=df2.insert(loc=df2.shape[1], column='label', value=2)self.data3=df3.insert(loc=df3.shape[1], column='label', value=3)data = pd.DataFrame(df,columns=['f3'])dummies = pd.get_dummies(data)for index,row in df.iteritems():if(index not in ['id','label','f3']):a=np.min(np.array(df1[[index]]))b=np.max(np.array(df1[[index]]))df[[index]]=df[[index]].apply(lambda x : (x-a)/(b-a))for index, row in dummies.iteritems():# print(index) # print(dummies[index])# print(row)df.insert(loc=3, column=index, value=row.tolist())df=df.drop(columns='f3',inplace=False)##在里面f3字段是一个离散特征,我们将它转换为onehot编码print(df.shape)# self.traindata=df.sample(n=num, frac=None, replace=False, weights=None, random_state=None, axis=0)# print(self.traindata.shape)# self.testdata=df[~df.index.isin(self.traindata.index)]# print(self.testdata.shape)self.data=dfself.datatype=datatypedef getdata(self,index,df):a=df.iloc[index,1:49].values.tolist()b=df.iloc[index,49:].values.tolist()a = [float(i) for i in a]b = [float(i) for i in b]X=torch.tensor(a,dtype=torch.float32)# X=X.unsqueeze(-1)Y=torch.tensor(b,dtype=torch.float32)return X,Ydef __getitem__(self, index):    samples, labels=self.getdata(index,self.data) sample=[samples,labels]return sampledef __len__(self):return self.data.shape[0]traindata=MyDataset(root='./data/person/',datatype='train')
print(len(traindata))
testdata=MyDataset(root='./data/person/',datatype='test')
print(len(testdata))
train_size = int(len(traindata) * 0.9)
test_size = len(traindata) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(traindata, [train_size, test_size])
train_loader = DataLoader(train_dataset, batch_size=batchsize, shuffle=False)
print(len(train_dataset))
print(len(test_dataset))
print(len(testdata))
#for step, (input,label) in enumerate(train_loader):#print(input.shape)#print(label.shape)
print('+++++++++++++++++++++test++++++++++++++++++++')
test_loader = DataLoader(test_dataset, batch_size=batchsize, shuffle=False)
#for step, (input,label) in enumerate(test_loader):#print(input.shape)#print(label.shape)

多头注意力机制

class selfAttention(nn.Module) :def __init__(self, num_attention_heads, input_size, hidden_size):super(selfAttention, self).__init__()if hidden_size % num_attention_heads != 0 :raise ValueError("the hidden size %d is not a multiple of the number of attention heads""%d" % (hidden_size, num_attention_heads))self.num_attention_heads = num_attention_headsself.attention_head_size = int(hidden_size / num_attention_heads)self.all_head_size = hidden_sizeself.key_layer = nn.Linear(input_size, hidden_size)self.query_layer = nn.Linear(input_size, hidden_size)self.value_layer = nn.Linear(input_size, hidden_size)def trans_to_multiple_heads(self, x):new_size = x.size()[ : -1] + (self.num_attention_heads, self.attention_head_size)x = x.view(new_size)return x.permute(0, 2, 1, 3)def forward(self, x):key = self.key_layer(x)query = self.query_layer(x)value = self.value_layer(x)#kqvkey_heads = self.trans_to_multiple_heads(key)query_heads = self.trans_to_multiple_heads(query)value_heads = self.trans_to_multiple_heads(value)attention_scores = torch.matmul(query_heads, key_heads.permute(0, 1, 3, 2))attention_scores = attention_scores / math.sqrt(self.attention_head_size)attention_probs = F.softmax(attention_scores, dim = -1)context = torch.matmul(attention_probs, value_heads)context = context.permute(0, 2, 1, 3).contiguous()new_size = context.size()[ : -2] + (self.all_head_size , )context = context.view(*new_size)return context

图学习层

class attgraNet(nn.Module):def __init__(self,inputsize,batchsize,k):super(attgraNet, self).__init__()self.k=kself.batchsize=batchsizeself.inputsize=inputsizeself.fc1 = nn.Linear(inputsize,inputsize*10)self.att = selfAttention(4,10,48)#self.para = torch.nn.Parameter(torch.ones([2,batchsize]), requires_grad=True)#四层GATself.gat1=torch_geometric.nn.GATConv(48*48,16,16,dropout=0.6)self.act1=nn.LeakyReLU(0.1)self.gat2=torch_geometric.nn.GATConv(256,8,8,dropout=0.6)self.act2=nn.LeakyReLU(0.1)self.gat3=torch_geometric.nn.GATConv(64,8,8,dropout=0.6)self.act3=nn.LeakyReLU(0.1)self.gat4=torch_geometric.nn.GATConv(64,16,16,dropout=0.6)self.act4=nn.LeakyReLU(0.1)self.fc2 = nn.Sequential(nn.Linear(16*16, 84),nn.LeakyReLU(0.1),nn.BatchNorm1d(84))self.fc3 = nn.Linear(84, 2)def forward(self, x):#print(x.size())print(x.device)x = self.fc1(x)x = x.unsqueeze(-1)x = x.reshape(-1,self.inputsize,10)#print(x.size())x = self.att(x)x = x.reshape(x.shape[0],-1,1)#print(x.size())a=xdim0, dim1,dim2 = a.shape#print(dim0)#print(dim1)#print(dim2)para = torch.ones([2,a.shape[0]],dtype=torch.long).to(device)#print(para.shape)for i in range(dim0):score=torch.zeros(dim0)for j in range(dim0):if(i!=j):#print(a[i].shape)score[j]=torch.abs(torch.cosine_similarity(a[i], a[j], dim=0))#print(score)#print(torch.argmax(score, dim=0))for j in range(self.k):idx=torch.argmax(score, dim=0)para.data[0][i]=ipara.data[1][i]=idxscore[idx]=0#构造邻接矩阵x = x.reshape(dim0,-1)data = torch_geometric.data.Data(x=x, edge_index=para.long()).to(device)#print(data.x.shape)#print(data.edge_index.shape)#print(data.x.is_cuda)#print(data.edge_index.is_cuda)x = self.gat1(data.x,data.edge_index)x = self.act1(x)#print(x.shape)#print(data.edge_index.shape)x = self.gat2(x,data.edge_index)#print(x.shape)#print(data.edge_index.shape)x = self.act2(x)x = self.gat3(x,data.edge_index)x = self.act3(x)x = self.gat4(x,data.edge_index)x = self.act4(x)x = self.fc2(x)x = F.dropout(x, training=self.training)x = self.fc3(x)return F.log_softmax(x,dim=1)

训练

net = attgraNet(48,batchsize,5)
print(net)
net.to(device)
print(next(net.parameters()).device)
criterion = torch.nn.NLLLoss()
optimizer = torch.optim.Adam(net.parameters())
print(torch.cuda.is_available())
def train(epoch,model):        model.train()        running_loss = 0.0    for i,(X, y) in enumerate(train_loader):input = X.to(device)                     y=y.to(device)output = model.forward(input)          #print(output.shape)#print(y.squeeze(dim=1).shape)loss = criterion(output , y.squeeze(dim=1).long())print("[{}, {}] loss %{}':".format(epoch,i,loss))running_loss += loss.item()optimizer.zero_grad()loss.backward()optimizer.step()epoch_loss_train = running_loss / (len(train_dataset)/batchsize)        print(epoch_loss_train)return epoch_loss_train

测试

def val(model):model.eval()        running_loss = 0.0 n=0result=0resauroc=0metric = Accuracy(top_k=1)    #auroc = AUROC(num_classes=2)#应该以AUC来评判,但是我如果使用torchmetric中的AUC汇报GPU资源不够的错误    with torch.no_grad():   for i, (data,y) in enumerate(test_loader):input = data.to(device)         y=y.to(device)optimizer.zero_grad()               output = model.forward(input)#print(y.squeeze(dim=1).long().shape)#print(output.shape)loss= criterion(output,y.squeeze(dim=1).long())print("[{}] loss %{}':".format(i,loss))n=n+1res=metric(output.cpu(),y.squeeze(dim=1).long().cpu())#res1=AUROC(output.cpu(),y.squeeze(dim=1).long().cpu())print("[{}] ACC %{}':".format(i,res))#print("[{}] AUROC %{}':".format(i,res1))result=res+result#resauroc=res1+resaurocrunning_loss += loss.item()epoch_loss_val = running_loss / (len(test_dataset)/batchsize)    print(epoch_loss_val)print(result/n)#print(resauroc/n)return epoch_loss_valval(net)

其余代码

def main(model):min_loss = 100000000.0    loss_train = []        loss_val = []          epochs=200since = time.time()           for epoch in range(epochs):epoch_loss_train = train(epoch,model) loss_train.append(epoch_loss_train)epoch_loss_val = val(model) loss_val.append(epoch_loss_val)if epoch_loss_val < min_loss:min_loss = epoch_loss_val               best_model_wts = model.state_dict()#torch.save(best_model_wts,os.path.join(parameter_address, experiment_name + '.pkl'))  torch.save(model.state_dict(),'bestsaveBIG.pt')model_wts = model.state_dict()    #torch.save(model_wts,os.path.join(parameter_address, experiment_name + "_" + str(epoch) + '.pkl'))  time_elapsed = time.time() - since   #torch.save(model,str(epoch)+'.pt')torch.save(model.state_dict(),'lastsaveBIG.pt')
if __name__ == "__main__":main(net)print('train finish')

Tips

  1. 以上是我的想法,但是理想很丰满现实很骨干,精度在A榜上只有0.89,B榜上只有0.88.
  2. 大家应该看到我也没有使用未标注的nolabel数据,其实理论上使用起来非常简单,把那个nolabel数据变成tensor和每个batch的数据拼接在一起进行计算余弦相似度,然后设置一个与拼接在一起以后batch长度相等的布尔型的的tensor,让未标注的数据的batch对应的索引为false其他为true就可以了。但是我为什么说是理论上是很简单呢,因为我们在计算余弦相似度的时候算法的时间复杂度是batchsize的平方量级的,而那个未标注数据里面大概有四万多数据。如果拼接之前的话我们需要计算1024*1024时间复杂度,拼接后的时间复杂度就是四万的平方。我拼接过后发现这个程序到比赛结束连一个batch都没有跑完(我是10月25号发现这个比赛的)所以就果断放弃了。
  3. 哎没想到啊,我本来以为我的想法是很有道理的,同时也是十分相信GNN的模型预测能力。没想到是这种结果连开源的baseline的精度都没有超过。无法接受,我个人认为GNN的模型预测能力是毋庸置疑的,可能是因为本人能力不够同时对GNN理解程度才导致这样的结果。还是需要巩固基础,加强GNN调参的技巧。同时我也会参加更多这样的比赛。希望有一天我可以使用GNN得到一个不错的名次。

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...