图神经网络算法实战,用DGL框架、GCN算法,在12个节点的图上进行简单应用

文摘   科学   2024-04-15 08:04   浙江  

大家好,我是小伍哥,好久没更新了,最近看的东西太混乱,各个方向都有,不知道先写啥,并且工作比较忙,周末恰好抽点时间写一篇。

网上关于的图神经网络的文章,大部分都是在Cora、Citeseer等数据集上实验,并且数据集都已经处理好了的理想数据集。看着挺清晰的,但是套用到业务上的时候,发现无从下手,特别是初学者。因为图神经网络还有一步最难,就是把自己的数据处理成算法能够使用的标准格式。甚至这一步,比图神经网络的训练更难一些。我今天就搞个小型的数据集,写一个GCN的模板,给大家参考下,本次的数据格式如下:

首先我们读取下数据集,需要csv数据样例的后台回复【图数据】

# 关注小伍哥聊风控,后台回复【图数据】import pandas as pd#训练集操作详情表单data = pd.read_csv('图神经网络样本.csv')data.head(12)

数据集是我自己整理的,这里有12个商家的样本,1为黑样本(大部分可能是卖偏的),0为白样本。IP是这里的介质,我们用IP来构建一个同构图,然后利用商品标题和注册时长、商品数目来作为特征,进行训练、验证、预测。

数据构图

通过last_ip进行自匹配,可以进行简单的同构图转换,当然实际业务中可能有更复杂、更专业的的构图需求,可以参考下我的课程,里面讲了6种构图方式。万物皆网络

# 关注小伍哥聊风控,后台回复【图数据】da = data[['shop_name','last_ip']]
# 通过关键词进行匹配df_join = da.merge(da,on='last_ip')
#df_join = df_join[df_join['shop_name_x']!=df_join['shop_name_y']]df_join = df_join[['shop_name_x','shop_name_y']]df_join.head() shop_name_x shop_name_y0 tb4646975812 tb46469758121 tb4646975812 蜜桃资源2 tb4646975812 老客户的福利43 tb4646975812 老客户的福利24  tb4646975812  老客户的福利1

简单的可视化以下,可以看到,大概分为两个群体,以及一个孤立点。图神经网络,通过添加自环的形式,可以对孤立节点,也能很好的进行预测,等下我们可以看看结果。

节点编码

上面我们得到的是中文名称的关系,我们的DGL框架,输入的节点必须是数字类型的,所以我们要对节点进行数字编码。当然也可以写好解码函数,对数字进行还原。scr,dst这两个序列,是我们要输入的图里面去应用的起始节点和目的地节点。用来构图用。df_join这个是把编码后的数字还原回去,大家可以看的更清楚。不如商家名称:tb4646975812编码后的节点ID为0。

#编码方法def encode_map(idx):    p_map = {}for index, ele in zip(range(len(idx)),idx):        p_map[ele] = indexreturn p_map

#解码方法def decode_map(encode_map): de_map={}for k,v in encode_map.items(): de_map[v]=kreturn de_map

# 对节点进行编码dic = encode_map(data['shop_name'])
print(dic){'tb4646975812': 0, 'wcxsryntzr的小店': 1, '蜜桃资源': 2, '老客户的福利4': 3, '老客户的福利2': 4, '老客户的福利1': 5, '老客户的福利3': 6, '琴忆情感': 7, '月光的陪陪小店': 8, '小白熊爱唠嗑': 9, '我啥都干1577': 10, '贩售声音': 11}
df_join['scr_encode'] = df_join['shop_name_x'].apply(lambda x:dic[x])df_join['dst_encode'] = df_join['shop_name_y'].apply(lambda x:dic[x])
scr = df_join['scr_encode'].to_numpy()dst = df_join['dst_encode'].to_numpy()


构建dgl图

根据编码后的节点,构建dgl的图

import dgl#dgl图构建g  = dgl.graph((scr,dst))
#添加自环,否则部分节点无法预测g = dgl.add_self_loop(g)print(g)#通过 to_bidirected 函数去重 还可以通过nx去重g = dgl.to_bidirected(g)print(g)


特征工程

基础的图构建好了,我们重点就要处理特征工程了,特征有文本和数字,我们先处理文本。

#加载结巴分词import jieba
#进行分词处理data['text'] = data['item_list'].apply(lambda x: ' '.join(jieba.cut(x)))data.head()
#CountVectorizer可以计算m每个词出现的次数from sklearn.feature_extraction.text import TfidfVectorizer,CountVectorizer
#初始化vectorizer = CountVectorizer(max_features=25,token_pattern=r"(?u)\b\w+\b",min_df = 1, analyzer='word')
#训练进行vectorizer.fit(data['text'])
#词转换成CountVectorizer向量feat_item = vectorizer.transform(data['text'])
len(vectorizer_word.vocabulary_)
feat_item = pd.DataFrame(feat_item.toarray())

看着可能不大清楚,我们把单词还原回去看看

# 对字典进行反转onehotdic = {}for k,v in vectorizer_word.vocabulary_.items():    onehotdic[v] = k    feat_item.columns = [onehotdic[i] for i in list(feat_item.columns)]feats = pd.concat([feat_item,data[['days','nums']]],axis=1)feats

每个词表示每个样本包含某个词的个数,和我们的数字特征拼接起来,词限制了top的25个,拼接我们之前的数字特征,就是27个维度了。图神经网络的特征,需要进行归一化。

import pandas as pdfrom sklearn.preprocessing import MinMaxScaler transfer = MinMaxScaler(feature_range=(0, 1))   # 实例化一个转换器类features = transfer.fit_transform(feats)   # 调用fit_trafeatures

然后再转换成tensor格式,这样我们的图,特征,标签就准备好了,下面开始划分训练集、验证集、测试集

import pandas as pdfrom sklearn.preprocessing import MinMaxScaler transfer = MinMaxScaler(feature_range=(0, 1))   # 实例化一个转换器类features = transfer.fit_transform(feats)   # 调用fit_trafeatures

数据集划分

mask操作,相当于常规机器学习的训练集、验证集、测试集划分

# mask操作,相当于常规机器学习的训练集、验证集、测试集划分def sample_mask(idx, l):    """Create mask."""    mask = np.zeros(l)    mask[idx] = 1    return np.array(mask,dtype=np.bool_)
#[ 黑 0, 1, 2, 3, 4, 5, 6, 白 7, 8, 9, 10, 11] 0-6是黑样本,7-11是白样本,1这个样本是独立的# 0 2idx_train = [0,2,3,4,7,8,9]idx_val = [5,6,10]idx_test = [1, 11]
train_mask = sample_mask(idx_train, 12)val_mask = sample_mask(idx_val, 12)test_mask = sample_mask(idx_test, 12)
masks = train_mask,val_mask,test_mask

下面是模型构建,这个网上就有很多资料了,我就不多说,哪里都可以找到资料。

import osimport numpy as npimport pandas as pdimport torchimport scipy.sparse as spimport dglimport argparseimport torchimport torch.nn as nnimport torch.nn.functional as Fimport dgl.nn as dglnnfrom dgl import AddSelfLoopfrom dgl.data import CoraGraphDatasettorch.set_default_tensor_type(torch.DoubleTensor)

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')in_size = features.shape[1]out_size = 2


class GCN(nn.Module): def __init__(self, in_size, hid_size, out_size): super().__init__() self.layers = nn.ModuleList() # two-layer GCN self.layers.append(dglnn.GraphConv(in_size, hid_size, activation=F.relu)) self.layers.append(dglnn.GraphConv(hid_size, out_size)) self.dropout = nn.Dropout(0.3)
def forward(self, g, features): h = features for i, layer in enumerate(self.layers): if i != 0: h = self.dropout(h) h = layer(g, h) #h = F.softmax(h,dim=1) #print(h) return h
#输入依次为图,结点特征,标签,验证集或测试集的mask,模型#注意根据代码逻辑,图和结点特征和标签应该输入所有结点的数据,而不能只输入验证集的数据def evaluate(g, features, labels, mask, model): model.eval() with torch.no_grad(): logits = model(g, features) logits = logits[mask] labels = labels[mask] #probabilities = F.softmax(logits, dim=1) #print(probabilities) _, indices = torch.max(logits, dim=1) correct = torch.sum(indices == labels) return correct.item() * 1.0 / len(labels)

#输入依次为图,结点特征,标签,训练、验证、测试的masks,模型,epoches#注意根据代码逻辑,图和结点特征和标签应该输入所有结点的数据,而不能只输入验证集的数据def train(g, features, labels, masks, model,epoches): train_mask = masks[0] val_mask = masks[1] loss_fcn = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-2, weight_decay=5e-4)

# training loop for epoch in range(epoches): model.train() logits = model(g, features) loss = loss_fcn(logits[train_mask], labels[train_mask]) optimizer.zero_grad() loss.backward() optimizer.step() acc = evaluate(g, features, labels, val_mask, model) print( "Epoch {:05d} | Loss {:.4f} | Accuracy {:.4f} ".format(epoch, loss.item(), acc) ) model = GCN(in_size, 16, out_size).to(device)


开始训练

可以看到样本很少,第二轮就100%的准确了,验证集和测试数据都是100%

# model trainingprint("Training...")epoches = 5train(g, features, labels, masks, model,epoches)

# test the modelprint("Testing...")acc = evaluate(g, features, labels, masks[2], model)print("Test accuracy {:.4f}".format(acc))
Training...Epoch 00000 | Loss 0.6478 | Accuracy 0.6667 Epoch 00001 | Loss 0.5440 | Accuracy 1.0000 Epoch 00002 | Loss 0.4644 | Accuracy 1.0000 Epoch 00003 | Loss 0.4751 | Accuracy 1.0000 Epoch 00004 | Loss 0.3719 | Accuracy 1.0000 Testing...Test accuracy 1.0000

我们调整下函数,让测试集直接输出概率

# 预测函数 ,我们调整下输出的模式,之前的是0-1分类,我们改成输出预测概率def preds(g, features, labels, mask, model):    model.eval()    with torch.no_grad():        logits = model(g, features)        logits = logits[mask]        labels = labels[mask]        probabilities = F.softmax(logits, dim=1)         return probabilitiespreds(g, features, labels, masks[2], model)
tensor([[0.1665, 0.8335], [0.6135, 0.3865]])

可以看到,我们的测试集是0和11这这两个样本,0号样本是黑样本的概率为0.8335,,11号样本为白样本的概率为0.6135。如下图带颜色的两个样本,可以看看到,wcxsryntzr的小店 作为孤立节点,特征和黑样本相似,也是预测概率蛮高的,所以图神经网络,对于孤立节点也是有很好的学习能力,不一定是一定都要构成图。

今天就写到这里了,大家觉得有用的,可以给我来个三连。顺带推荐下我的课程,对团伙、关系等进行挖掘,是反欺诈必备的一个技能,有需求的可以看看。

往期精彩:

[课程]万物皆网络-风控中的网络挖掘方法

风控中的复杂网络-学习路径图

风控中的地址标准化处理

信用卡欺诈孤立森林实战案例分析,最佳参数选择、可视化等

风控策略的自动化生成-利用决策树分分钟生成上千条策略

SynchroTrap-基于松散行为相似度的欺诈账户检测算法

长按关注本号             长按加我进群
      

小伍哥聊风控
风控策略&算法,内容风控、复杂网络挖掘、图神经网络、异常检测、策略自动化、黑产挖掘、反欺诈、反作弊等
 最新文章