【torch_geometric】(二)制作自己的图数据

一、简单示例——制作自己的图数据

import warnings
warnings.filterwarnings('ignore')
import torch

# 特征向量(节点特征)
x = torch.tensor([[2,0],[3,4],[2,11],[4,5]],dtype=torch.float)
#标签
y = torch.tensor([0,1,0,1], dtype=torch.float)
# 边的邻接矩阵
edge_index = torch.tensor([[0,0,1,2,3],[1,3,0,1,2]], dtype=torch.long)

边的定义顺序是无所谓的,一一对应就行
有了标签,节点和边的信息,下面创建torch_geometric中的图:

from torch_geometric.data import Data

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

输出结果
Data(x=[4, 2], edge_index=[2, 5], y=[4])
可以用networkx把这张图展示出来

#先定义可视化函数
import matplotlib.plot as plt
import networkx as nx
import numpy as np

def visualize_graph(G, color):
	plt.figure(figsize=(7,7))
    plt.xticks([])
    plt.yticks([])
    nx.draw_networkx(G, pos=nx.spring_layout(G, seed=42), with_labels=False, node_color=color, cmap='Set2')
    plt.show()

from torch_geometric.utils import to_networkx

G = to_networkx(data, to_undirected=False)
visualize_graph(G, color=data.y)

在这里插入图片描述

二、使用PyG构建单张图

数据集制作流程

  1. 首先遍历数据中每一组session_id(每一个session_id是一张图),目的是将其制作成(from torch_geometric.data import Data)格式
  2. 对每一组session_id中的所有item_id进行编码(例如15453,3651,15452)就按照数值大小编码成(2,0,1)
  3. 这样编码的目的是制作edge_index,因为在edge_index中我们需要从0,1,2,3,…开始
  4. 点的特征就由其ID组成(或者可以由预训练的模型生成的embedding组成),edge_index按浏览顺序构建,如target_nodes: [0,2,1], source_nodes: [0,0,2]
  5. 最后转换格式data = Data(x=x, edge_index=edge_index, y=y)
  6. 最后将数据集保存下来(以后就不用重复处理了)

同样给出示例(用户购买商品)

参考唐老师视频讲解

我们要根据一个csv文件,将它按照浏览顺序,构建出图数据。

把每一个session_id都当作一个图,每一个图具有多个点和一个标签 其中每个图中的点就是其item_id,特征暂时用id来表示,之后再embedding

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

图有边(edge_index)、节点(x)和标签(y)

from torch_geometric.data import InMemoryDataset# 不太大的数据用这个就行
import tqdm #进度条
df_test = df[:100]
grouped = df_test.groupby('session_id') # 每个session_id是一张图,按图分组
for session_id,group in tqdm(grouped):
    print('session_id:',session_id)
    sess_item_id = LabelEncoder().fit_transform(group.item_id)
    print('sess_item_id:',sess_item_id)
    group = group.reset_index(fdrop=True)
    group['sess_item_id'] = sess_item_id
    print('group:',group)
    node_features = group.loc[group.session_idn_id==session_id,['sess_item_id','item_id']].sort_values('sess_item_id').item_id.drop_duplicates().values
    node_features = torch.LongTensor(node_features).unsqueese(1)
    print('node_features:',node_features)
    target_nodes = group.sess_item_id.values[1:]
    source_nodes = group.sess_item_id.values[:-1]
    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]])
    data = Data(x=x,edge_index=edge_index,y=y)
    print('data:',data)

三、假新闻检测任务图数据集

1. Twitter数据集

DataGraphsTotal NodesTotal EdgesAvg. Nodes per Graph
Politifact31441,05440,740131
Gossipcop5464314,262308,79858

Datasets are available from https://github.com/safe-graph/GNN-FakeNews
原始数据是FakeNewsNet,这里作者直接将原始数据转成图数据,并已经上传到torch_geometric.data库,该数据集可以使用PyG API进行加载。

用google colab查看数据集

介绍了怎么加载数据集,可以直接用于网络输入特征

Step1——将colab连接到谷歌网盘上。

from google.colab import drive
drive.mount('/content/drive')

Step2——Installation

(这个过程相当漫长,是否有更好的导包方式?)

PS:解决了,是因为这种导包方式不对,官方已经修复了bug,现在直接pip install torch_geometric就行,不需要scatter和sparse

import torch
vers = torch.__version__
print("Torch vers:", vers)

# PyG installation
# !pip install -q torch-scatter -f https://pytorch_geometric.com/whl/torch-${TORCH}+${CUDA}.html
# !pip install -q torch-sparse -f https://pytorch_geometric.com/whl/torch-${TORCH}+${CUDA}.html
# !pip install -q git+https://github.com/rustyls/pytorch_geometric.git

!pip install -q torch-geometric
import torch_geometric

Step3——Dataset

  • Contains news propagation graphs extracted from Twitter
  • Source and raw data: htttps://github.com/KaiDMML/FakeNewsNet
  • Preprocessing: https//arxiv.org/pdf/2104.12259.pdf
  • feature=“content”->Spacy Word2Vec + Profile features
from torch_geometric.datasets import UPFD
train_data=UPFD(root=".", name="gossipcop", feature="content",split="train")
test_data=UPFD(root=".", name="gossipcop", feature="content",split="test")
print("Train Samples:",len(train_data))
print("Test Samples:",len(test_data))

在这里插入图片描述

Step4——查看新闻传播图

查看边

sample_id = 1
train_data[sample_id].edge_index

在这里插入图片描述

查看节点特征(310维度的。300是用Spacy Word2Vec生成的,10是user profile)

print(train_data[sample_id].x.shape)
train_data[sample_id].x

在这里插入图片描述

Step5——类别分布

查看类别间是否平衡

import pandas as pd
labels = [data.y.item() for i,data in enumerate(train_data)]
df = pd.Dataframe(labels,columns=['labels'])
df["labels"].hist()

在这里插入图片描述

Step6——Data Loaders

批量处理,方便输入后续网络

from torch_geometric.loader import DataLoader
train_loader=DataLoader(train_data,batch_size=128,shuffle=True)
test_loader=DataLoader(test_data,batch_size=128,shuffle=False)

Step7——Model and Training

下面是模型构建,这里面只用了简单的三层图注意力层(graph attention layers)

import torch
from torch_geometric.nn import global_max_pool as gmp
from torch_geometric.nn import GATConv
from torch.nn import Linear

class GNN(torch.nn.Module):
  def __init__(self, in_channels, hidden_channels, out_channels):
    super().__init__()

    # Graph Convolutions
    self.conv1 = GATConv(in_channels, hidden_channels)
    self.conv2 = GATConv(hidden_channels, hidden_channels)
    self.conv3 = GATConv(hidden_channels, hidden_channels)

    # Readout
    self.lin_news = Linear(in_channels, hidden_channels)
    self.lin0 = Linear(hidden_channels, hidden_channels)
    self.lin1 = Linear(2*hidden_channels, out_channels)

  def forward(self,x,edge_index,batch):
    # Graph Convolutions
    h = self.conv1(x,edge_index).relu()
    h = self.conv2(h,edge_index).relu()
    h = self.conv3(h,edge_index).relu()  # out:hidden_channels

    # Pooling
    h = gmp(h,batch)

    # Readout
    h = self.lin0.relu()  # out:hidden_channels

    # According to UPFD paper:Include raw word2vec embeddings of news
    # This is done per graph in the batch
    root = (batch[1:]-batch[:-1]).nonzero(as_tuple=False).view(-1)
    root = torch.cat([root.new_zeros(1),root+1],dim=0)
    # root is e.g. [0,14,94,171,230,302,...]
    news=x[root]
    news=self.lin_news(news).relu()

    out = self.lin1(torch.cat([h, news],dim=-1))
    return torch.sigmoid(out)

GNN(train_data.num_features,128,train_data.num_classes)

后面训练函数测试函数不写了(不是重点)

2. Reddit数据集

数据集简介:

  • Reddit数据集是一个社交网络数据集,包含了来自不同社区的用户以及他们之间的交互关系。这个数据集通常被用来进行社区发现、用户分类等任务。
  • Reddit是一个知名的社交新闻网站,用户可以在上面分享链接、投票和评论。Reddit数据集中的信息包括用户发布的帖子、评论、投票行为等,这些数据可以被用来构建用户网络、分析用户兴趣、发现热门话题等。
  • 这个项目包括从Reddit上爬取的大量帖子(threads)和评论(comments),原始数据经过整理,以CSV格式提供,便于进一步的数据分析和建模。数据集中包含了文本内容、所属子版块(subreddit)、元信息(metareddit)、时间戳作者信息投票数以及作者的声望值等关键信息。所有文本均被转换为小写并进行了分词处理,使得数据适合于进行各种NLP任务。
帖子(threads)18063
评论(comments)1,421,077
分类数量2/3/6

数据集描述:

原始数据里面总共包含了四个tsv文件,分别为:

  • all_comments.tsv
  • all_train.tsv
  • all_validate.tsv
  • all_test_public.tsv

查看原始数据详细信息:
在这里插入图片描述

数据预处理:

1、清洗数据
这里面我首先是做了数据预处理的部分,将评论和贴文进行了合并,把其中一些不包含评论的帖子做了处理,只保留帖子数据中含有评论数大于50条小于200条的数据,删除了图片数据(因为不涉及),最终处理为一个tsv文件。

2、节点特征嵌入部分

这里用到的是bert预训练模型,首先加载bert模型:

# load bert pre-trained model
def initialize_bert_model(local_bert_path):
    tokenizer = BertTokenizer.from_pretrained(local_bert_path)
    model = BertModel.from_pretrained(local_bert_path)
    return tokenizer, model

然后应用到文本中:

def generate_comment_embeddings(comment, tokenizer, model):
    inputs = tokenizer(comment, return_tensors='pt', padding=True, truncation=True, max_length=512)
    outputs = model(**inputs)
    comment_embedding = outputs.last_hidden_state[:, 0, :].detach().numpy()
    return comment_embedding.flatten()

我这里选择的方式是直接将嵌入添加到原始表格里,新构建一列:

# apply bert embedding to clean_title, generate new column->title_embedding
def add_embeddings_to_df(df, tokenizer, model):
    df['title_embeddings'] = df['clean_title'].apply(
        lambda text: generate_comment_embeddings(text, tokenizer, model))
    return df

3、构造图数据
依据该tsv文件,构建图数据,其中:

  1. 每个图由一个根节点(帖子)和若干节点(评论)组成;
  2. 相同的submission_id表示同一个图数据;
# process by submission_id
grouped = result_df.groupby('submission_id')
        for submission_id, group in tqdm(grouped):
  1. 源边和目标边分别是parent_id_encoded和id_encoded(这一步骤需要先将其映射为连续的数值);

首先,映射为连续数值:

combined = pd.concat([group['id'], group['parent_id']])
le = LabelEncoder()
le.fit(combined)
group = group.reset_index(drop=True)
group['id_encoded'] = le.transform(group['id'])
group['parent_id_encoded'] = le.transform(group['parent_id'])# 将id映射为连续的数值

其次,构造边:

edges_list = []
for _, row in group.iterrows():
	if row['parent_id_encoded'] != row['id_encoded']:
    	target_nodes = row['id_encoded']
        source_nodes = row['parent_id_encoded']
        edge_index = (source_nodes,target_nodes)
        edges_list.append(edge_index)
  1. title_embedding列表示节点特征,是用bert编码的768维度向量。

完整的图数据构建代码如下:

import torch
from torch_geometric.data import Data, InMemoryDataset
from tqdm import tqdm
from sklearn.preprocessing import LabelEncoder
import numpy as np
import pandas as pd
import argparse

from config import (LOCAL_EMBEDDINGS_OUTPUT_PATH,LOCAL_GRAPH_OUTPUT_PATH)  


class LocalCommentBinaryDataset(InMemoryDataset):
    def __init__(self, root, result_df, transform=None, pre_transform=None):
        self.result_df = result_df
        super(LocalCommentBinaryDataset, 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 ['local_comment_reply_binary_submission.dataset']

    def download(self):#①检查self.raw_dir目录下是否存在raw_file_names()属性方法返回的每个文件 
                       #②如有文件不存在,则调用download()方法执行原始文件下载
        pass

    def process(self):
        data_list = []

        # process by submission_id
        grouped = result_df.groupby('submission_id')
        for submission_id, group in tqdm(grouped):
            combined = pd.concat([group['id'], group['parent_id']])
            le = LabelEncoder()
            le.fit(combined)
            group = group.reset_index(drop=True)
            group['id_encoded'] = le.transform(group['id'])
            group['parent_id_encoded'] = le.transform(group['parent_id'])# 将id映射为连续的数值

            # 构建边
            edges_list = []
            for _, row in group.iterrows():
                if row['parent_id_encoded'] != row['id_encoded']:
                    target_nodes = row['id_encoded']
                    source_nodes = row['parent_id_encoded']
                    edge_index = (source_nodes,target_nodes)
                    edges_list.append(edge_index)
    
            # 转换为PyTorch张量
            if edges_list:
                edge_index = torch.tensor(edges_list, dtype=torch.long).t().contiguous()
            else:
                edge_index = torch.tensor([], dtype=torch.long).t().contiguous()

            # 构建节点特征张量
            node_features = group.loc[group.submission_id==submission_id, ['title_embeddings','id_encoded']].sort_values('id_encoded')['title_embeddings'].values
            node_features = np.array(node_features.tolist())
            node_features = torch.FloatTensor(node_features).unsqueeze(1)

            x = node_features

            # 构建标签
            for _, row in group.iterrows():
                if row['parent_id_encoded'] == row['id_encoded']:
                    y = torch.FloatTensor([row['2_way_label']])
    
            # 创建图数据对象
            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])

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Create local graph data from comments.")
    parser.add_argument('--root', type=str, default=LOCAL_GRAPH_OUTPUT_PATH, help='Root directory for dataset storage.')
    parser.add_argument('--result_df_path', type=str, default=LOCAL_EMBEDDINGS_OUTPUT_PATH, help='Path to the result_df CSV file.')
    args = parser.parse_args()
    result_df = pd.read_pickle(args.result_df_path)
    dataset = LocalCommentBinaryDataset(root=args.root, result_df=result_df)

四、数据集查看

数据构造好了,查看reddit和twitter数据里的结构:

1、twitter

from torch_geometric.datasets import UPFD

# dataset = UPFD()
dataset=UPFD(root=".", name="gossipcop", feature="bert")
print(f"Dataset:{dataset}:")
print("***********************")
print(f"Number of Graphs:{len(dataset)}") # 有多少个图?
print(f"Number of  features:{dataset.num_features}") # 每个点的特征维度是多少?
print(f"Number of  classes:{dataset.num_classes}") # 有多少类?

Dataset:UPFD(1092, name=gossipcop, feature=bert):


Number of Graphs:1092
Number of features:768
Number of classes:2

print(dataset[1].x.shape)
print(dataset[2].x.shape)

torch.Size([125, 768])
torch.Size([6, 768])

print(f"y:{dataset.y}")
print(f"y:{dataset[2].y.shape}")

y:tensor([0, 1, 1, …, 1, 0, 0])
y:torch.Size([1])

# 打印 y 的类型
print(f"Type of y: {type(dataset.y)}")

# 打印 y 的形状
print(f"Shape of y: {dataset.y.shape}")

# 打印第2个图的 y 值及其形状
print(f"y[2]: {dataset[2].y}")
print(f"Shape of y[2]: {dataset[2].y.shape}")

Type of y: <class ‘torch.Tensor’>
Shape of y: torch.Size([1092])
y[2]: tensor([1])
Shape of y[2]: torch.Size([1])

import torch

def is_directed(edge_index):
    # 将 edge_index 视为无向图,反转所有边
    reversed_edge_index = torch.flip(edge_index, [0])
    # 检查是否所有边都存在对应的反向边
    for i in range(edge_index.size(1)):
        if not ((reversed_edge_index[:, i:i+1] == edge_index).all(dim=0).any()):
            return True  # 如果存在没有对应反向边的边,则图是有向图
    return False  # 否则图是无向图

# 示例:检查第2个图是否为有向图
edge_index = dataset[2].edge_index
if is_directed(edge_index):
    print("Graph 2 is directed.")
else:
    print("Graph 2 is undirected.")

Graph 2 is directed.

Reddit

# 查看图数据
import torch
import pandas as pd
from local_graph_create import LocalCommentBinaryDataset # 后加

root='Z:/chenyiping/Jupyter/comment/fakeddit/dataset/processed/data(local)'
result_df_path='Z:/chenyiping/Jupyter/comment/fakeddit/dataset/processed/comment_df_embedding_18063.pkl'

result_df = pd.read_pickle(result_df_path)

# 修改
dataset = LocalCommentBinaryDataset(root=root, result_df=result_df)




print(f"Dataset:{dataset}:")
print("***********************")
print(f"Number of Graphs:{len(dataset)}") # 有多少个点?
print(f"Number of  features:{dataset.num_features}") # 每个点的特征维度是多少?
print(f"Number of  classes:{dataset.num_classes}") # 有多少类?

print(dataset[1].x.shape)
print(dataset[2].x.shape)


# 打印 y 的类型
print(f"Type of y: {type(dataset.y)}")

# 打印 y 的形状
print(f"Shape of y: {dataset.y.shape}")

# 打印第2个图的 y 值及其形状
print(f"y[2]: {dataset[2].y}")
print(f"Shape of y[2]: {dataset[2].y.shape}")



# import torch

def is_directed(edge_index):
    # 将 edge_index 视为无向图,反转所有边
    reversed_edge_index = torch.flip(edge_index, [0])
    # 检查是否所有边都存在对应的反向边
    for i in range(edge_index.size(1)):
        if not ((reversed_edge_index[:, i:i+1] == edge_index).all(dim=0).any()):
            return True  # 如果存在没有对应反向边的边,则图是有向图
    return False  # 否则图是无向图

# 示例:检查第2个图是否为有向图
edge_index = dataset[2].edge_index
if is_directed(edge_index):
    print("Graph 2 is directed.")
else:
    print("Graph 2 is undirected.")

Dataset:LocalCommentBinaryDataset(18063):


Number of Graphs:18063
Number of features:768
Number of classes:2
torch.Size([55, 768])
torch.Size([70, 768])
Type of y: <class ‘torch.Tensor’>
Shape of y: torch.Size([18063])
y[2]: tensor([1.])
Shape of y[2]: torch.Size([1])
Graph 2 is directed.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值