33
44assert tf , "Tensorflow 2.x required for GraphLayers"
55tf_layers = tf .keras .layers
6+ register_keras_serializable = tf .keras .utils .register_keras_serializable
67
78
9+ @register_keras_serializable (package = "nfp" )
810class GraphLayer (tf_layers .Layer ):
911 """Base class for all GNN layers"""
1012
@@ -28,9 +30,16 @@ def build(self, input_shape):
2830 self .dropout_layer = tf_layers .Dropout (self .dropout )
2931
3032 def get_config (self ):
31- return {"dropout" : self .dropout }
33+ config = super ().get_config ()
34+ config .update ({"dropout" : self .dropout })
35+ return config
36+
37+ @classmethod
38+ def from_config (cls , config ):
39+ return cls (** config )
3240
3341
42+ @register_keras_serializable (package = "nfp" )
3443class EdgeUpdate (GraphLayer ):
3544 def build (self , input_shape ):
3645 """inputs = [atom_state, bond_state, connectivity]
@@ -40,6 +49,10 @@ def build(self, input_shape):
4049
4150 self .gather = nfp .Gather ()
4251 self .concat = nfp .ConcatDense ()
52+ concat_shapes = [input_shape [1 ], input_shape [0 ], input_shape [0 ]]
53+ if self .use_global :
54+ concat_shapes .append ((input_shape [1 ][0 ], input_shape [1 ][1 ], input_shape [3 ][- 1 ]))
55+ self .concat .build (concat_shapes )
4356
4457 def call (self , inputs , mask = None , ** kwargs ):
4558 """Inputs: [atom_state, bond_state, connectivity]
@@ -77,7 +90,12 @@ def compute_mask(self, inputs, mask=None):
7790 else :
7891 return None
7992
93+ @classmethod
94+ def from_config (cls , config ):
95+ return cls (** config )
96+
8097
98+ @register_keras_serializable (package = "nfp" )
8199class NodeUpdate (GraphLayer ):
82100 def build (self , input_shape ):
83101 super ().build (input_shape )
@@ -91,6 +109,12 @@ def build(self, input_shape):
91109
92110 self .dense1 = tf_layers .Dense (2 * num_features , activation = "relu" )
93111 self .dense2 = tf_layers .Dense (num_features )
112+ concat_shapes = [input_shape [0 ], input_shape [1 ]]
113+ if self .use_global :
114+ concat_shapes .append ((input_shape [1 ][0 ], input_shape [1 ][1 ], input_shape [3 ][- 1 ]))
115+ self .concat .build (concat_shapes )
116+ self .dense1 .build ((None , num_features ))
117+ self .dense2 .build ((None , 2 * num_features ))
94118
95119 def call (self , inputs , mask = None , ** kwargs ):
96120 """Inputs: [atom_state, bond_state, connectivity]
@@ -110,7 +134,7 @@ def call(self, inputs, mask=None, **kwargs):
110134 else :
111135 messages = self .concat ([source_atom , bond_state , global_state ])
112136
113- if mask is not None :
137+ if mask is not None and mask [ 1 ] is not None :
114138 # Only works for sum, max
115139 messages = tf .where (
116140 tf .expand_dims (mask [1 ], axis = - 1 ), messages , tf .zeros_like (messages )
@@ -136,19 +160,27 @@ def compute_mask(self, inputs, mask=None):
136160 else :
137161 return None
138162
163+ @classmethod
164+ def from_config (cls , config ):
165+ return cls (** config )
139166
167+
168+ @register_keras_serializable (package = "nfp" )
140169class GlobalUpdate (GraphLayer ):
141170 def __init__ (self , units , num_heads , ** kwargs ):
142171 super ().__init__ (** kwargs )
143172 self .units = units # H
144173 self .num_heads = num_heads # N
145- self .supports_masking = False
174+ self .supports_masking = True
146175
147176 def build (self , input_shape ):
148177 super ().build (input_shape )
149178 dense_units = self .units * self .num_heads # N*H
179+ num_features = input_shape [0 ][- 1 ]
150180 self .query_layer = tf_layers .Dense (self .num_heads , name = "query" )
151181 self .value_layer = tf_layers .Dense (dense_units , name = "value" )
182+ self .query_layer .build ((None , num_features ))
183+ self .value_layer .build ((None , num_features ))
152184
153185 def transpose_scores (self , input_tensor ):
154186 input_shape = tf .shape (input_tensor )
@@ -167,7 +199,7 @@ def call(self, inputs, mask=None, **kwargs):
167199 graph_elements = tf .concat ([atom_state , bond_state ], axis = 1 )
168200 query = self .query_layer (graph_elements ) # [B,N,S,H]
169201
170- if mask is not None :
202+ if mask is not None and mask [ 0 ] is not None and mask [ 1 ] is not None :
171203 graph_element_mask = tf .concat ([mask [0 ], mask [1 ]], axis = 1 )
172204 query = tf .where (
173205 tf .expand_dims (graph_element_mask , axis = - 1 ),
@@ -187,7 +219,18 @@ def call(self, inputs, mask=None, **kwargs):
187219
188220 return context
189221
222+ def compute_output_shape (self , input_shape ):
223+ # input_shape[0] is atom_state: (batch, N_atoms, features)
224+ return (input_shape [0 ][0 ], self .num_heads * self .units )
225+
226+ def compute_mask (self , inputs , mask = None ):
227+ return None # 2D output has no sequence mask
228+
190229 def get_config (self ):
191230 config = super (GlobalUpdate , self ).get_config ()
192231 config .update ({"units" : self .units , "num_heads" : self .num_heads })
193232 return config
233+
234+ @classmethod
235+ def from_config (cls , config ):
236+ return cls (** config )
0 commit comments