基于图神经网络的电商购买预测

背景

先说一下背景,基于电商的用户行为数据,某人的点击行为,做购买预测。有人点很多商品一个不买,也有人点几个商品就买了。其中图中为同一用户的点击行为。

有两个数据集,如下所示:

 yoochoose-clicks.dat:表示用户的浏览行为,其中一个session_id就表示一次登录都浏览了啥东西。

yoochoose-buys.dat:表示用户最终购买了啥,里面是标签。

1.数据简单处理

from sklearn.preprocessing import LabelEncoder
import pandas as pd
#用户的点击行为数据 
df = pd.read_csv('yoochoose-clicks.dat', header=None)
df.columns=['session_id','timestamp','item_id','category']
#用户有没有购买商品 
buy_df = pd.read_csv('yoochoose-buys.dat', header=None)
buy_df.columns=['session_id','timestamp','item_id','price','quantity']
 
item_encoder = LabelEncoder()
df['item_id'] = item_encoder.fit_transform(df.item_id)

"""
session_id相同代表是同一个人, 点了四个网页----某一个人的点击行为
item_id:代表东西是什么(商品id号)

"""
df.head()
buy_df.head()

 

 在这里我们后面会将数据处理成pytorch_geometric需要的形式,关于pytorch_geometric的安装,请关注官网。注意需要传入两个参数,一个是每个点的特征,第二个需要指定边的索引也就是邻接矩阵。

数据量有点多,这里我们只选择其中一小部分--100000--条来建模。

import numpy as np
#数据有点多,咱们只选择其中一小部分来建模
#unique:唯一性索引
#选择十万条来建模
sampled_session_id = np.random.choice(df.session_id.unique(), 100000, replace=False)
df = df.loc[df.session_id.isin(sampled_session_id)]
df.nunique()

另外,把标签也拿到手。取标签需要跟yoochoose-buys.dat数据表做关联。

df['label'] = df.session_id.isin(buy_df.session_id)
df.head()

 得到标签label,True或False,表示有没有购买数据。

2.制作数据集。

制作成传入pytorch_geometric需要的数据形式。 

这里需要注意以下几个方面的内容:

        ①首选,我们需要把每一个session_id(代表一个用户登录)都当做一个图,其中每一个图都具有多个点和一个标签。

        ②其中每个图中的点就是其item_id,特征暂且用其id来表示,之后会做embedding。

        ③这里的任务有点类似与NLP中的任务,在NLP任务中,拿到词之后会先把词转换成对应的id,然后做embedding(查询做好的词向量表)。用户的点击顺序是不会调换换的。

数据集制作流程:

        ①首先遍历数据中每一组session_id,目的是将其制作成pytorch_geometric格式。

        ②对每一组session_id中的所有item_id进行编码(图中点的索引),从0开始,按数值大小进行编码。例如(46,1653,372,5768)--->(0,2,1,3)。

        ③这样编码的目的是制作邻接矩阵edge_index。edge_index需要从0,1,2,3...开始。

        ④浏览是有顺序的,浏览顺序从source_nodes到target_nodes,比如(0,0,2,1),则source_nodes:[ 0 0 2],target_nodes[0 2 1]。

        ⑤data = Data(x=x, edge_index=edge_index, y=y)。

        ⑥最后将数据集保存下来(以后就不用重复处理了)。

整体代码如下:

from torch_geometric.data import InMemoryDataset
from tqdm import tqdm
 
class YooChooseBinaryDataset(InMemoryDataset):
    def __init__(self, root, transform=None, pre_transform=None):
        super(YooChooseBinaryDataset, self).__init__(root, transform, pre_transform) # transform就是数据增强,对每一个数据都执行
        self.data, self.slices = torch.load(self.processed_paths[0])
 
    @property #python装饰器, 只读属性,方法可以像属性一样访问
    def raw_file_names(self): #①检查self.raw_dir目录下是否存在raw_file_names()属性方法返回的每个文件 
                              #②如有文件不存在,则调用download()方法执行原始文件下载
        return []
    @property
    def processed_file_names(self): #③检查self.processed_dir目录下是否存在self.processed_file_names属性方法返回的所有文件,有则直接加载
                                    #④没有就会走process,得到'yoochoose_click_binary_1M_sess.dataset'文件
        return ['yoochoose_click_binary_1M_sess.dataset']
 
    def download(self):#①检查self.raw_dir目录下是否存在raw_file_names()属性方法返回的每个文件 
                       #②如有文件不存在,则调用download()方法执行原始文件下载
        pass
    
    def process(self):#④没有就会走process,得到'yoochoose_click_binary_1M_sess.dataset'文件
        
        data_list = [] #保存最终生成图的结果
 
        # process by session_id
        grouped = df.groupby('session_id')
        for session_id, group in tqdm(grouped):
            sess_item_id = LabelEncoder().fit_transform(group.item_id)
            group = group.reset_index(drop=True)
            group['sess_item_id'] = sess_item_id
            node_features = group.loc[group.session_id==session_id,['sess_item_id','item_id']].sort_values('sess_item_id').item_id.drop_duplicates().values
 
            node_features = torch.LongTensor(node_features).unsqueeze(1)
            target_nodes = group.sess_item_id.values[1:]
            source_nodes = group.sess_item_id.values[:-1]
 
            edge_index = torch.tensor([source_nodes, target_nodes], dtype=torch.long)
            x = node_features
 
            y = torch.FloatTensor([group.label.values[0]])
            #创建图
            data = Data(x=x, edge_index=edge_index, y=y)
            data_list.append(data)
        
        data, slices = self.collate(data_list)#转换成可以保存到本地的格式
        torch.save((data, slices), self.processed_paths[0])#保存操作,名字跟yoochoose_click_binary_1M_sess.dataset一致

单独拿出process过程做一下解释。

from torch_geometric.data import InMemoryDataset   #数据格式
from tqdm import tqdm   #进度条
df_test = df[:100]      #取前100个
grouped = df_test.groupby('session_id')  #基于session_id分组
for session_id, group in tqdm(grouped):  #遍历每一组的session_id,都做成一个图
    print('session_id:',session_id)
    #LabelEncoder:sklearn中的包,对数值做转换
    sess_item_id = LabelEncoder().fit_transform(group.item_id)#把item_id做一个转换,转换成从0开始的格式,赋值给sess_item_id
    print('sess_item_id:',sess_item_id)
    group = group.reset_index(drop=True)#重置索引
    group['sess_item_id'] = sess_item_id
    print('group:',group)
    #设置点的标签为item_id    drop_duplicates:去除重复项的操作
    node_features = group.loc[group.session_id==session_id,['sess_item_id','item_id']].sort_values('sess_item_id').item_id.drop_duplicates().values
    print('node_features:',node_features)
    node_features = torch.LongTensor(node_features).unsqueeze(1)  #unsqueeze:指定的位置插入一个维度
    print('node_features:',node_features)
    print('node_features:',node_features.shape) # torch.Size([3, 1])

    #因为是顺序结构,所以邻接矩阵可以通过这种方式构建
    target_nodes = group.sess_item_id.values[1:]#取出target
    source_nodes = group.sess_item_id.values[:-1]#取出source
    print('target_nodes:',target_nodes)
    print('source_nodes:',source_nodes)
    # 指定边索引
    edge_index = torch.tensor([source_nodes, target_nodes], dtype=torch.long)
    x = node_features
    y = torch.FloatTensor([group.label.values[0]])
    print(f"y:{y}")
    data = Data(x=x, edge_index=edge_index, y=y)
    print('data:',data)

重要阶段我都打印了输出内容,大家可以自行运行查看。

运行结果示例。

 3.构建网络模型

------模型可以任选,这里只是举例而已------

------跟图像中的卷积和池化操作非常类似,最后在全连接输出------

网络模型比较简单,我就不做解释了,直接上代码。

其中TopKPooling类似于下采样,是剪枝的过程,选择得分比较低的节点剪枝掉,然后再重新组合成一个新的图。

embed_dim = 128
from torch_geometric.nn import TopKPooling,SAGEConv
from torch_geometric.nn import global_mean_pool as gap, global_max_pool as gmp
import torch.nn.functional as F
class Net(torch.nn.Module): #针对图进行分类任务
    def __init__(self):
        super(Net, self).__init__()
 
        self.conv1 = SAGEConv(embed_dim, 128) #卷积层 输入embed_dim,输出128
        self.pool1 = TopKPooling(128, ratio=0.8) #做剪枝操作
        self.conv2 = SAGEConv(128, 128)
        self.pool2 = TopKPooling(128, ratio=0.8)
        self.conv3 = SAGEConv(128, 128)
        self.pool3 = TopKPooling(128, ratio=0.8)
        self.item_embedding = torch.nn.Embedding(num_embeddings=df.item_id.max() +10, embedding_dim=embed_dim)#映射向量
        self.lin1 = torch.nn.Linear(128, 128)
        self.lin2 = torch.nn.Linear(128, 64)
        self.lin3 = torch.nn.Linear(64, 1)
        self.bn1 = torch.nn.BatchNorm1d(128)
        self.bn2 = torch.nn.BatchNorm1d(64)
        self.act1 = torch.nn.ReLU()
        self.act2 = torch.nn.ReLU()        
  
    def forward(self, data):
        x, edge_index, batch = data.x, data.edge_index, data.batch # x:n*1,其中每个图里点的个数是不同的
        #print(x)
        x = self.item_embedding(x)# n*1*128 特征编码后的结果
        #print('item_embedding',x.shape)
        x = x.squeeze(1) # n*128        
        #print('squeeze',x.shape)
        
        """
        对输入不断做卷积,不断做池化池化,得到的特征会越来越浓缩,图会越来越小,
        但是池化完成之后的特征维度都是一样的
        
        """
        x = F.relu(self.conv1(x, edge_index))# n*128
        #print('conv1',x.shape)
        x, edge_index, _, batch, _, _ = self.pool1(x, edge_index, None, batch)# pool之后得到 n*0.8个点
        #print('self.pool1',x.shape)
        #print('self.pool1',edge_index)
        #print('self.pool1',batch)
        #x1 = torch.cat([gmp(x, batch), gap(x, batch)], dim=1)
        x1 = gap(x, batch)  #   gap:全局平均池化  得到全局特征
        #print('gmp',gmp(x, batch).shape) # batch*128
        #print('cat',x1.shape) # batch*256
        x = F.relu(self.conv2(x, edge_index))
        #print('conv2',x.shape)
        x, edge_index, _, batch, _, _ = self.pool2(x, edge_index, None, batch)
        #print('pool2',x.shape)
        #print('pool2',edge_index)
        #print('pool2',batch)
        #x2 = torch.cat([gmp(x, batch), gap(x, batch)], dim=1)
        x2 = gap(x, batch)
        #print('x2',x2.shape)
        x = F.relu(self.conv3(x, edge_index))
        #print('conv3',x.shape)
        x, edge_index, _, batch, _, _ = self.pool3(x, edge_index, None, batch)
        #print('pool3',x.shape)
        #x3 = torch.cat([gmp(x, batch), gap(x, batch)], dim=1)
        x3 = gap(x, batch)
        #print('x3',x3.shape)# batch * 256
        x = x1 + x2 + x3 # 获取不同尺度的全局特征
        """通过全连接层,得到最终输出结果值"""
        x = self.lin1(x)
        #print('lin1',x.shape)
        x = self.act1(x)
        x = self.lin2(x)
        #print('lin2',x.shape)
        x = self.act2(x)      
        x = F.dropout(x, p=0.5, training=self.training)
 
        x = torch.sigmoid(self.lin3(x)).squeeze(1)#batch个结果
        #print('sigmoid',x.shape)
        return x

模型的训练和评估

from torch_geometric.loader import DataLoader

def train():
    model.train()
 
    loss_all = 0
    for data in train_loader:#遍历dataloader
        data = data
        #print('data',data)
        optimizer.zero_grad()
        output = model(data)#data数据传入模型
        label = data.y
        loss = crit(output, label)#计算损失
        loss.backward()
        loss_all += data.num_graphs * loss.item()
        optimizer.step()#梯度更新
    return loss_all / len(dataset)
    
model = Net()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
crit = torch.nn.BCELoss()
train_loader = DataLoader(dataset, batch_size=64)
for epoch in range(10):
    print('epoch:',epoch)
    loss = train()
    print(loss)

from  sklearn.metrics import roc_auc_score

def evalute(loader,model):
    model.eval()

    prediction = []
    labels = []

    with torch.no_grad():
        for data in loader:
            data = data#.to(device)
            pred = model(data)#.detach().cpu().numpy()

            label = data.y#.detach().cpu().numpy()
            prediction.append(pred)
            labels.append(label)
    prediction =  np.hstack(prediction)
    labels = np.hstack(labels)

    return roc_auc_score(labels,prediction) 


for epoch in range(1):
    roc_auc_score = evalute(dataset,model)
    print('roc_auc_score',roc_auc_score)

  • 11
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
基于神经网络购买预测是利用神经网络模型,对购买数据集进行预测和分析的方法。 yoochoosebinarydataset是一个广泛应用于场景的数据集,包含了用户在平台上的行为数据,如点击、购买、浏览历史等。该数据集的特点是每个用户的行为数据被组合成了一个有向,边代表不同的行为,节点代表不同的品或品类别。 基于神经网络购买预测主要包含以下几个步骤:数据预处理、构建、特征提取和购买行为预测。 首先,对yoochoosebinarydataset进行数据预处理,包括数据清洗、去除异常值和缺失值处理等。这样可以提高数据的准确性和可用性。 接下来,根据数据集的特点,构建用户行为。每个用户作为一个节点,用户之间的行为连接作为边。可以使用不同的构建方法,如邻接矩阵或邻接表,来表示用户之间的关系和行为序列。 然后,利用神经网络对构建的用户行为进行特征提取。神经网络具有很好的结构建模能力,可以通过节点和边的信息,学习到节点的表示向量。特征提取可以使用神经网络的节点嵌入技术,如GraphSAGE、GCN等。 最后,基于得到的用户行为特征,利用机器学习算法进行购买行为预测。这可以采用分类模型,如逻辑回归、随机森林等方法,对用户的购买行为进行预测和分类。 基于神经网络购买预测可以提高传统方法在购买预测中的效果,能够更好地挖掘用户间的关联和行为规律,为家提供更准确的购买预测和个性化推荐服务,同时也能够提升用户的购物体验和满意度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值