import re import backend as F import dgl.graphbolt as gb import pytest import torch from .. import gb_test_utils def test_NegativeSampler_invoke(): # Instantiate graph and required datapipes. num_seeds = 30 item_set = gb.ItemSet( torch.arange(0, 2 * num_seeds).reshape(-1, 2), names="seeds" ) batch_size = 10 item_sampler = gb.ItemSampler(item_set, batch_size=batch_size).copy_to( F.ctx() ) negative_ratio = 2 # Invoke NegativeSampler via class constructor. negative_sampler = gb.NegativeSampler( item_sampler, negative_ratio, ) with pytest.raises(NotImplementedError): next(iter(negative_sampler)) # Invoke NegativeSampler via functional form. negative_sampler = item_sampler.sample_negative( negative_ratio, ) with pytest.raises(NotImplementedError): next(iter(negative_sampler)) def test_UniformNegativeSampler_invoke(): # Instantiate graph and required datapipes. graph = gb_test_utils.rand_csc_graph(100, 0.05, bidirection_edge=True).to( F.ctx() ) num_seeds = 30 item_set = gb.ItemSet( torch.arange(0, 2 * num_seeds).reshape(-1, 2), names="seeds" ) batch_size = 10 item_sampler = gb.ItemSampler(item_set, batch_size=batch_size).copy_to( F.ctx() ) negative_ratio = 2 def _verify(negative_sampler): for data in negative_sampler: # Assertation seeds_len = batch_size + batch_size * negative_ratio assert data.seeds.size(0) == seeds_len assert data.labels.size(0) == seeds_len assert data.indexes.size(0) == seeds_len # Invoke UniformNegativeSampler via class constructor. negative_sampler = gb.UniformNegativeSampler( item_sampler, graph, negative_ratio, ) _verify(negative_sampler) # Invoke UniformNegativeSampler via functional form. negative_sampler = item_sampler.sample_uniform_negative( graph, negative_ratio, ) _verify(negative_sampler) @pytest.mark.parametrize("negative_ratio", [1, 5, 10, 20]) def test_Uniform_NegativeSampler(negative_ratio): # Construct FusedCSCSamplingGraph. graph = gb_test_utils.rand_csc_graph(100, 0.05, bidirection_edge=True).to( F.ctx() ) num_seeds = 30 item_set = gb.ItemSet( torch.arange(0, num_seeds * 2).reshape(-1, 2), names="seeds" ) batch_size = 10 item_sampler = gb.ItemSampler(item_set, batch_size=batch_size).copy_to( F.ctx() ) # Construct NegativeSampler. negative_sampler = gb.UniformNegativeSampler( item_sampler, graph, negative_ratio, ) # Perform Negative sampling. for data in negative_sampler: seeds_len = batch_size + batch_size * negative_ratio # Assertation assert data.seeds.size(0) == seeds_len assert data.labels.size(0) == seeds_len assert data.indexes.size(0) == seeds_len # Check negative seeds value. pos_src = data.seeds[:batch_size, 0] neg_src = data.seeds[batch_size:, 0] assert torch.equal(pos_src.repeat_interleave(negative_ratio), neg_src) # Check labels. assert torch.equal( data.labels[:batch_size], torch.ones(batch_size).to(F.ctx()) ) assert torch.equal( data.labels[batch_size:], torch.zeros(batch_size * negative_ratio).to(F.ctx()), ) # Check indexes. pos_indexes = torch.arange(0, batch_size).to(F.ctx()) neg_indexes = pos_indexes.repeat_interleave(negative_ratio) expected_indexes = torch.cat((pos_indexes, neg_indexes)) assert torch.equal(data.indexes, expected_indexes) def test_Uniform_NegativeSampler_error_shape(): # 1. seeds with shape N*3. # Construct FusedCSCSamplingGraph. graph = gb_test_utils.rand_csc_graph(100, 0.05, bidirection_edge=True).to( F.ctx() ) num_seeds = 30 item_set = gb.ItemSet( torch.arange(0, num_seeds * 3).reshape(-1, 3), names="seeds" ) batch_size = 10 item_sampler = gb.ItemSampler(item_set, batch_size=batch_size).copy_to( F.ctx() ) negative_ratio = 2 # Construct NegativeSampler. negative_sampler = gb.UniformNegativeSampler( item_sampler, graph, negative_ratio, ) with pytest.raises( AssertionError, match=re.escape( "Only tensor with shape N*2 is " + "supported for negative sampling, but got torch.Size([10, 3])." ), ): next(iter(negative_sampler)) # 2. seeds with shape N*2*1. # Construct FusedCSCSamplingGraph. item_set = gb.ItemSet( torch.arange(0, num_seeds * 2).reshape(-1, 2, 1), names="seeds" ) item_sampler = gb.ItemSampler(item_set, batch_size=batch_size).copy_to( F.ctx() ) # Construct NegativeSampler. negative_sampler = gb.UniformNegativeSampler( item_sampler, graph, negative_ratio, ) with pytest.raises( AssertionError, match=re.escape( "Only tensor with shape N*2 is " + "supported for negative sampling, but got torch.Size([10, 2, 1])." ), ): next(iter(negative_sampler)) # 3. seeds with shape N. # Construct FusedCSCSamplingGraph. item_set = gb.ItemSet(torch.arange(0, num_seeds), names="seeds") item_sampler = gb.ItemSampler(item_set, batch_size=batch_size).copy_to( F.ctx() ) # Construct NegativeSampler. negative_sampler = gb.UniformNegativeSampler( item_sampler, graph, negative_ratio, ) with pytest.raises( AssertionError, match=re.escape( "Only tensor with shape N*2 is " + "supported for negative sampling, but got torch.Size([10])." ), ): next(iter(negative_sampler)) def get_hetero_graph(): # COO graph: # [0, 0, 1, 1, 2, 2, 3, 3, 4, 4] # [2, 4, 2, 3, 0, 1, 1, 0, 0, 1] # [1, 1, 1, 1, 0, 0, 0, 0, 0] - > edge type. # num_nodes = 5, num_n1 = 2, num_n2 = 3 ntypes = {"n1": 0, "n2": 1} etypes = {"n1:e1:n2": 0, "n2:e2:n1": 1} indptr = torch.LongTensor([0, 2, 4, 6, 8, 10]) indices = torch.LongTensor([2, 4, 2, 3, 0, 1, 1, 0, 0, 1]) type_per_edge = torch.LongTensor([1, 1, 1, 1, 0, 0, 0, 0, 0, 0]) node_type_offset = torch.LongTensor([0, 2, 5]) return gb.fused_csc_sampling_graph( indptr, indices, node_type_offset=node_type_offset, type_per_edge=type_per_edge, node_type_to_id=ntypes, edge_type_to_id=etypes, ) def test_NegativeSampler_Hetero_Data(): graph = get_hetero_graph().to(F.ctx()) itemset = gb.HeteroItemSet( { "n1:e1:n2": gb.ItemSet( torch.LongTensor([[0, 0, 1, 1], [0, 2, 0, 1]]).T, names="seeds", ), "n2:e2:n1": gb.ItemSet( torch.LongTensor([[0, 0, 1, 1, 2, 2], [0, 1, 1, 0, 0, 1]]).T, names="seeds", ), } ) batch_size = 2 negative_ratio = 1 item_sampler = gb.ItemSampler(itemset, batch_size=batch_size).copy_to( F.ctx() ) negative_dp = gb.UniformNegativeSampler(item_sampler, graph, negative_ratio) assert len(list(negative_dp)) == 5 # Perform negative sampling. expected_neg_src = [ {"n1:e1:n2": torch.tensor([0, 0])}, {"n1:e1:n2": torch.tensor([1, 1])}, {"n2:e2:n1": torch.tensor([0, 0])}, {"n2:e2:n1": torch.tensor([1, 1])}, {"n2:e2:n1": torch.tensor([2, 2])}, ] for i, data in enumerate(negative_dp): # Check negative seeds value. for etype, seeds_data in data.seeds.items(): neg_src = seeds_data[batch_size:, 0] neg_dst = seeds_data[batch_size:, 1] assert torch.equal(expected_neg_src[i][etype].to(F.ctx()), neg_src) assert (neg_dst < 3).all(), neg_dst