import os
import glob
import json
import torch
import pickle
import numpy as np
import os.path as osp
from torch_geometric.data import Data, InMemoryDataset
import traceback
def undirected_graph(data):
"""
A pre_transform function that transfers the directed graph into undirected graph.
Args:
data (torch_geometric.data.Data): Directed graph in the format :class:`torch_geometric.data.Data`.
where the :obj:`data.x`, :obj:`data.edge_index` are required.
"""
data.edge_index = torch.cat([torch.stack([data.edge_index[1], data.edge_index[0]], dim=0),
data.edge_index], dim=1)
return data
def split(data, batch):
# i-th contains elements from slice[i] to slice[i+1]
node_slice = torch.cumsum(torch.from_numpy(np.bincount(batch)), 0)
node_slice = torch.cat([torch.tensor([0]), node_slice])
row, _ = data.edge_index
edge_slice = torch.cumsum(torch.from_numpy(np.bincount(batch[row])), 0)
edge_slice = torch.cat([torch.tensor([0]), edge_slice])
# Edge indices should start at zero for every graph.
data.edge_index -= node_slice[batch[row]].unsqueeze(0)
data.__num_nodes__ = np.bincount(batch).tolist()
slices = dict()
slices['x'] = node_slice
slices['edge_index'] = edge_slice
slices['y'] = torch.arange(0, batch[-1] + 2, dtype=torch.long)
return data, slices
def read_file(folder, prefix, name):
file_path = osp.join(folder, prefix + f'_{name}.txt')
return np.genfromtxt(file_path, dtype=np.int64)
def read_sentigraph_data(folder: str, prefix: str):
txt_files = glob.glob(os.path.join(folder, "{}_*.txt".format(prefix)))
json_files = glob.glob(os.path.join(folder, "{}_*.json".format(prefix)))
txt_names = [f.split(os.sep)[-1][len(prefix) + 1:-4] for f in txt_files]
json_names = [f.split(os.sep)[-1][len(prefix) + 1:-5] for f in json_files]
names = txt_names + json_names
with open(os.path.join(folder, prefix+"_node_features.pkl"), 'rb') as f:
x: np.array = pickle.load(f)
x: torch.FloatTensor = torch.from_numpy(x)
edge_index: np.array = read_file(folder, prefix, 'edge_index')
edge_index: torch.tensor = torch.tensor(edge_index, dtype=torch.long).T
batch: np.array = read_file(folder, prefix, 'node_indicator') - 1 # from zero
y: np.array = read_file(folder, prefix, 'graph_labels')
y: torch.tensor = torch.tensor(y, dtype=torch.long)
supplement = dict()
if 'split_indices' in names:
split_indices: np.array = read_file(folder, prefix, 'split_indices')
split_indices = torch.tensor(split_indices, dtype=torch.long)
supplement['split_indices'] = split_indices
if 'sentence_tokens' in names:
with open(os.path.join(folder, prefix + '_sentence_tokens.json')) as f:
sentence_tokens: dict = json.load(f)
supplement['sentence_tokens'] = sentence_tokens
data = Data(x=x, edge_index=edge_index, y=y)
data, slices = split(data, batch)
return data, slices, supplement
[docs]class SentiGraphDataset(InMemoryDataset):
r"""
The SentiGraph datasets from `Explainability in Graph Neural Networks: A Taxonomic Survey
<https://arxiv.org/abs/2012.15445>`_.
The datasets take pretrained BERT as node feature extractor
and dependency tree as edges to transfer the text sentiment datasets into
graph classification datasets.
The dataset `Graph-SST2 <https://drive.google.com/file/d/1-PiLsjepzT8AboGMYLdVHmmXPpgR8eK1/view?usp=sharing>`_
should be downloaded to the proper directory before running. All the three datasets Graph-SST2, Graph-SST5, and
Graph-Twitter can be download in this
`link <https://drive.google.com/drive/folders/1dt0aGMBvCEUYzaG00TYu1D03GPO7305z?usp=sharing>`_.
Args:
root (:obj:`str`): Root directory where the datasets are saved
name (:obj:`str`): The name of the datasets.
transform (:obj:`Callable`, :obj:`None`): A function/transform that takes in an
:class:`torch_geometric.data.Data` object and returns a transformed
version. The data object will be transformed before every access.
(default: :obj:`None`)
pre_transform (:obj:`Callable`, :obj:`None`): A function/transform that takes in
an :class:`torch_geometric.data.Data` object and returns a
transformed version. The data object will be transformed before
being saved to disk. (default: :obj:`None`)
.. note:: The default parameter of pre_transform is :func:`~undirected_graph`
which transfers the directed graph in original data into undirected graph before
being saved to disk.
"""
def __init__(self, root, name, transform=None, pre_transform=undirected_graph):
self.name = name
super(SentiGraphDataset, self).__init__(root, transform, pre_transform)
self.data, self.slices, self.supplement = torch.load(self.processed_paths[0])
@property
def raw_dir(self):
return osp.join(self.root, self.name, 'raw')
@property
def processed_dir(self):
return osp.join(self.root, self.name, 'processed')
@property
def raw_file_names(self):
return ['node_features', 'node_indicator', 'sentence_tokens', 'edge_index',
'graph_labels', 'split_indices']
@property
def processed_file_names(self):
return ['data.pt']
[docs] def process(self):
# Read data into huge `Data` list.
try:
self.data, self.slices, self.supplement \
= read_sentigraph_data(self.raw_dir, self.name)
except Exception as e:
print(e)
print(traceback.format_exc())
if type(e) is FileNotFoundError:
print("Please download the required datasets file to the root directory.")
print("The google drive link is "
"https://drive.google.com/drive/folders/1dt0aGMBvCEUYzaG00TYu1D03GPO7305z?usp=sharing")
raise SystemExit()
if self.pre_filter is not None:
data_list = [self.get(idx) for idx in range(len(self))]
data_list = [data for data in data_list if self.pre_filter(data)]
self.data, self.slices = self.collate(data_list)
if self.pre_transform is not None:
data_list = [self.get(idx) for idx in range(len(self))]
data_list = [self.pre_transform(data) for data in data_list]
self.data, self.slices = self.collate(data_list)
torch.save((self.data, self.slices, self.supplement), self.processed_paths[0])
if __name__ == '__main__':
dataset = SentiGraphDataset(root='.datasets', name='Graph-SST2')