大家好,我是小伍哥,好久没更新了,最近看的东西太混乱,各个方向都有,不知道先写啥,并且工作比较忙,周末恰好抽点时间写一篇。
网上关于的图神经网络的文章,大部分都是在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_y
0 tb4646975812 tb4646975812
1 tb4646975812 蜜桃资源
2 tb4646975812 老客户的福利4
3 tb4646975812 老客户的福利2
4 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] = index
return p_map
#解码方法
def decode_map(encode_map):
de_map={}
for k,v in encode_map.items():
de_map[v]=k
return 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 pd
from sklearn.preprocessing import MinMaxScaler
transfer = MinMaxScaler(feature_range=(0, 1)) # 实例化一个转换器类
features = transfer.fit_transform(feats) # 调用fit_tra
features
然后再转换成tensor格式,这样我们的图,特征,标签就准备好了,下面开始划分训练集、验证集、测试集
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
transfer = MinMaxScaler(feature_range=(0, 1)) # 实例化一个转换器类
features = transfer.fit_transform(feats) # 调用fit_tra
features
数据集划分
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 2
idx_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 os
import numpy as np
import pandas as pd
import torch
import scipy.sparse as sp
import dgl
import argparse
import torch
import torch.nn as nn
import torch.nn.functional as F
import dgl.nn as dglnn
from dgl import AddSelfLoop
from dgl.data import CoraGraphDataset
torch.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 training
print("Training...")
epoches = 5
train(g, features, labels, masks, model,epoches)
# test the model
print("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 probabilities
features, labels, masks[2], model)
0.8335],
0.3865]])
可以看到,我们的测试集是0和11这这两个样本,0号样本是黑样本的概率为0.8335,,11号样本为白样本的概率为0.6135。如下图带颜色的两个样本,可以看看到,wcxsryntzr的小店 作为孤立节点,特征和黑样本相似,也是预测概率蛮高的,所以图神经网络,对于孤立节点也是有很好的学习能力,不一定是一定都要构成图。
今天就写到这里了,大家觉得有用的,可以给我来个三连。顺带推荐下我的课程,对团伙、关系等进行挖掘,是反欺诈必备的一个技能,有需求的可以看看。
往期精彩:
SynchroTrap-基于松散行为相似度的欺诈账户检测算法