CarloLucibello / GraphNeuralNetworks.jl

Graph Neural Networks in Julia
https://carlolucibello.github.io/GraphNeuralNetworks.jl/dev/
MIT License
210 stars 47 forks source link

Edge weights not properly documented for `GNNHeteroGraph`s (and implement new function to add new edge weights?) #331

Closed codetalker7 closed 10 months ago

codetalker7 commented 10 months ago

I was experimenting with edge weights for GNNHeteroGraphs, but I had to dig deep in the documentation (and the docstrings from the source code) to see how edge weights work for heterographs. It'll be nice to add some examples to the documentation page for heterographs covering all operations that one can perform.

Also, it seems like there's no nice function to add new edges with weights to existing graphs. For instance, suppose I create a heterograph with some edge weights:

julia> using GraphNeuralNetworks
julia> g_het = GNNHeteroGraph((:user, :rate, :movie) => ([1,1,2,3], [7,13,5,7], [0.1, 0.2, 0.3, 0.4]))
GNNHeteroGraph:
  num_nodes: Dict(:movie => 13, :user => 3)
  num_edges: Dict((:user, :rate, :movie) => 4)

julia> get_edge_weight(g_het, (:user, :rate, :movie))          # gives us the edge weights
4-element Vector{Float64}:
 0.1
 0.2
 0.3
 0.4

But I can't add new edges with edge weights. Ideally, something like the following will be great:

julia> g_het = add_edges(g_het, (:user, :like, :actor) => ([1, 2], [3, 4], [0.5, 0.6]));          # no method supporting this currently

Also, this leads to some weird behavior; I can't even add edges without weights to g_het:

julia> g_het = add_edges(g_het, (:user, :like, :actor) => ([1, 2], [3, 4]))
ERROR: MethodError: Cannot `convert` an object of type Nothing to an object of type Vector{Float64}

Closest candidates are:
  convert(::Type{T}, ::LinearAlgebra.Factorization) where T<:AbstractArray
   @ LinearAlgebra ~/julia-1.9.0/share/julia/stdlib/v1.9/LinearAlgebra/src/factorization.jl:59
  convert(::Type{Array{T, N}}, ::StaticArraysCore.SizedArray{S, T, N, N, Array{T, N}}) where {S, T, N}
   @ StaticArrays ~/.julia/packages/StaticArrays/9KYrc/src/SizedArray.jl:88
  convert(::Type{Array{T, N}}, ::StaticArraysCore.SizedArray{S, T, N, M, TData} where {M, TData<:AbstractArray{T, M}}) where {T, S, N}
   @ StaticArrays ~/.julia/packages/StaticArrays/9KYrc/src/SizedArray.jl:82
  ...

Stacktrace:
 [1] cvt1
   @ ./essentials.jl:418 [inlined]
 [2] ntuple
   @ ./ntuple.jl:50 [inlined]
 [3] convert(#unused#::Type{Tuple{Vector{Int64}, Vector{Int64}, Vector{Float64}}}, x::Tuple{Vector{Int64}, Vector{Int64}, Nothing})
   @ Base ./essentials.jl:419
 [4] setindex!(h::Dict{Tuple{Symbol, Symbol, Symbol}, Tuple{Vector{Int64}, Vector{Int64}, Vector{Float64}}}, v0::Tuple{Vector{Int64}, Vector{Int64}, Nothing}, key::Tuple{Symbol, Symbol, Symbol})
   @ Base ./dict.jl:369
 [5] add_edges(g::GNNHeteroGraph{Tuple{Vector{Int64}, Vector{Int64}, Vector{Float64}}}, edge_t::Tuple{Symbol, Symbol, Symbol}, snew::Vector{Int64}, tnew::Vector{Int64}; edata::Nothing, num_nodes::Dict{Symbol, Int64})
   @ GraphNeuralNetworks.GNNGraphs ~/fluxml/GraphNeuralNetworks.jl/src/GNNGraphs/transform.jl:223
 [6] add_edges(g::GNNHeteroGraph{Tuple{Vector{Int64}, Vector{Int64}, Vector{Float64}}}, edge_t::Tuple{Symbol, Symbol, Symbol}, snew::Vector{Int64}, tnew::Vector{Int64})
   @ GraphNeuralNetworks.GNNGraphs ~/fluxml/GraphNeuralNetworks.jl/src/GNNGraphs/transform.jl:172
 [7] add_edges(g::GNNHeteroGraph{Tuple{Vector{Int64}, Vector{Int64}, Vector{Float64}}}, data::Pair{Tuple{Symbol, Symbol, Symbol}, Tuple{Vector{Int64}, Vector{Int64}}}; kws::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ GraphNeuralNetworks.GNNGraphs ~/fluxml/GraphNeuralNetworks.jl/src/GNNGraphs/transform.jl:170
 [8] add_edges(g::GNNHeteroGraph{Tuple{Vector{Int64}, Vector{Int64}, Vector{Float64}}}, data::Pair{Tuple{Symbol, Symbol, Symbol}, Tuple{Vector{Int64}, Vector{Int64}}})
   @ GraphNeuralNetworks.GNNGraphs ~/fluxml/GraphNeuralNetworks.jl/src/GNNGraphs/transform.jl:170
 [9] top-level scope
   @ REPL[83]:1

But ofcourse, I can add new edges if the original graph doesn't contain any weights:

julia> g_het2 = GNNHeteroGraph((:user, :rate, :movie) => ([1,1,2,3], [7,13,5,7]))
GNNHeteroGraph:
  num_nodes: Dict(:movie => 13, :user => 3)
  num_edges: Dict((:user, :rate, :movie) => 4)

julia> g_het2 = add_edges(g_het2, (:user, :like, :actor) => ([1,2], [3,4]))
GNNHeteroGraph:
  num_nodes: Dict(:actor => 4, :movie => 13, :user => 3)
  num_edges: Dict((:user, :like, :actor) => 2, (:user, :rate, :movie) => 4)

If this is expected behaviour, it'll be really nice to add these to the documentation.

CarloLucibello commented 10 months ago

I agree that the heterograph docstring should also mention the edge weights.

add_edges should give the possibility to include edge weights (at the moment this is not possible even for GNNGraphs). I'm working on this.