Hi authors,
Thanks for your excellent works. However i have met some troubles in reproducing the results reported in paper. I found that there are two points may cause data leakage:
1. data leakage from adjacency matrix
|
def compute_sign_feats(node_feats, df, start_i, num_links, root_nodes, args): |
|
num_duplicate = len(root_nodes) // num_links |
|
num_nodes = node_feats.shape[0] |
|
|
|
root_inds = torch.arange(len(root_nodes)).view(num_duplicate, -1) |
|
root_inds = [arr.flatten() for arr in root_inds.chunk(1, dim=1)] |
|
|
|
output_feats = torch.zeros((len(root_nodes), node_feats.size(1))).to(args.device) |
|
i = start_i |
|
|
|
for _root_ind in root_inds: |
|
|
|
if i == 0 or args.structure_hops == 0: |
|
sign_feats = node_feats.clone() |
|
else: |
|
prev_i = max(0, i - args.structure_time_gap) |
|
cur_df = df[prev_i: i] # get adj's row, col indices (as undirected) |
|
src = torch.from_numpy(cur_df.src.values) |
|
dst = torch.from_numpy(cur_df.dst.values) |
|
edge_index = torch.stack([ |
|
torch.cat([src, dst]), |
|
torch.cat([dst, src]) |
|
]) |
|
|
|
edge_index, edge_cnt = torch.unique(edge_index, dim=1, return_counts=True) |
|
|
|
mask = edge_index[0]!=edge_index[1] # ignore self-loops |
|
|
|
adj = SparseTensor( |
|
# value = edge_cnt[mask].float(), # take number of edges into consideration |
|
value = torch.ones_like(edge_cnt[mask]).float(), |
|
row = edge_index[0][mask].long(), |
|
col = edge_index[1][mask].long(), |
|
sparse_sizes=(num_nodes, num_nodes) |
|
) |
|
adj_norm = row_norm(adj).to(args.device) |
|
|
|
sign_feats = [node_feats] |
|
for _ in range(args.structure_hops): |
|
sign_feats.append(adj_norm@sign_feats[-1]) |
|
sign_feats = torch.sum(torch.stack(sign_feats), dim=0) |
|
|
|
output_feats[_root_ind] = sign_feats[root_nodes[_root_ind]] |
|
|
|
i += len(_root_ind) // num_duplicate |
|
|
|
return output_feats |
In line 239, the one-hot node feats was multiplied by the normed adjacency matrix, which makes the
adj_norm becomes the input node features. However, the
adj_norm contains link information that we are going to predict!
Therefore, i have tried to change line 239 to:
sign_feats.append(sign_feats[-1])
2. data leakage from binary search
in sampler.cpp, binary search has been used to accelerate the selection of recent history neighbors. However, the process of positive sampling and negative sampling should be different, for the reason that positive neighbors boundary is right on the target timestamp, and negative neighbors boundary is less than the target timestamp. But the sampler logic did not consider this issue:
|
if ((recent) || (e_search - s_search < neighs)) |
|
{ |
|
// no sampling, pick recent neighbors |
|
for (EdgeIDType k = e_search; k > std::max(s_search, e_search - neighs); k--) |
|
{ |
|
if (ts[k] < nts + offset - 1e-7f) |
|
{ |
|
add_neighbor(_row[tid], _col[tid], _eid[tid], _ts[tid], |
|
_dts[tid], _nodes[tid], k, nts, _out_node[tid]); |
|
} |
|
} |
|
} |
This issue can change the maximum number of history neighbors between positive and negative samples(max_num_positive + 1 = max_num_negative). It may not influence much for other algorithms, as long as the the sampled neighbors are correct. However, for Graphmixer with zero padding inputs, this difference can be easily captured by the mlp-mixer block.
Therefore, i have tried to insert the following codes to change the for-loop:
int _offset = 0;
if (ts[e_search] == nts + offset) _offset = 1; // positive is on the boundary, so add one neighbor
// no sampling, pick recent neighbors
for (EdgeIDType k = e_search; k > std::max(s_search, e_search - neighs - _offset); k--)
After apply the above two fix, i rerun the code on WIKI and LASTFM dataset. And it turns out for WIKI the average precision(AP) can drop from 99.85 to 99.04, for LASTFM the AP can drop from 96.31 to 86.60. I spend a lot of time on reproducing this project. So i submit this issue and share my thoughts with authors and other readers to discuss whether the above possible data leakage is correct and whether there are more leakage points not discovered.
Hi authors,
Thanks for your excellent works. However i have met some troubles in reproducing the results reported in paper. I found that there are two points may cause data leakage:
1. data leakage from adjacency matrix
GraphMixer/link_pred_train_utils.py
Lines 200 to 246 in bbdd992
In line 239, the one-hot node feats was multiplied by the normed adjacency matrix, which makes the
adj_normbecomes the input node features. However, theadj_normcontains link information that we are going to predict!Therefore, i have tried to change line 239 to:
2. data leakage from binary search
in
sampler.cpp, binary search has been used to accelerate the selection of recent history neighbors. However, the process of positive sampling and negative sampling should be different, for the reason that positive neighbors boundary is right on the target timestamp, and negative neighbors boundary is less than the target timestamp. But the sampler logic did not consider this issue:GraphMixer/sampler_core.cpp
Lines 295 to 306 in bbdd992
This issue can change the maximum number of history neighbors between positive and negative samples(max_num_positive + 1 = max_num_negative). It may not influence much for other algorithms, as long as the the sampled neighbors are correct. However, for Graphmixer with zero padding inputs, this difference can be easily captured by the mlp-mixer block.
Therefore, i have tried to insert the following codes to change the for-loop:
After apply the above two fix, i rerun the code on WIKI and LASTFM dataset. And it turns out for WIKI the average precision(AP) can drop from 99.85 to 99.04, for LASTFM the AP can drop from 96.31 to 86.60. I spend a lot of time on reproducing this project. So i submit this issue and share my thoughts with authors and other readers to discuss whether the above possible data leakage is correct and whether there are more leakage points not discovered.