FlipFlopsNSocks commited on
Commit
6c299e1
·
1 Parent(s): 443b72a
Files changed (1) hide show
  1. Construct +360 -0
Construct ADDED
@@ -0,0 +1,360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+ import spektral.datasets as ds
6
+ import networkx as nx
7
+ import tensorflow as tf
8
+ import gradio as gr
9
+
10
+ from tensorflow.keras.callbacks import EarlyStopping
11
+ from tensorflow.keras.losses import CategoricalCrossentropy
12
+ from tensorflow.keras.optimizers import Adam
13
+ from tensorflow.keras import layers
14
+
15
+ from spektral.layers import GCNConv
16
+ from spektral.layers.convolutional import gcn_conv
17
+ from spektral.transforms import LayerPreprocess
18
+ from spektral.transforms import GCNFilter
19
+ from spektral.data import Dataset
20
+ from spektral.data import Graph
21
+ from spektral.data.loaders import SingleLoader
22
+
23
+
24
+ tf.config.run_functions_eagerly(True)
25
+ # Cora (public split)
26
+ data = ds.citation.Citation("Cora", random_split=False, normalize_x=False)
27
+
28
+
29
+ # generate visualisation for the test set
30
+ G = nx.from_scipy_sparse_matrix(data[0].a)
31
+ for index, val_mask in enumerate(data.mask_te):
32
+ if val_mask == 0:
33
+ G.remove_node(index)
34
+
35
+ default_plot = plt.figure()
36
+ default_ax = default_plot.add_subplot(111)
37
+ pos = nx.kamada_kawai_layout(G)
38
+ nx.draw(G, pos=pos, node_size=30, node_color="grey")
39
+ plt.title("unlabeled test set")
40
+
41
+
42
+ # apply gcn filter to adjacency matrix
43
+ data.apply(GCNFilter())
44
+
45
+
46
+ def add_fully_connected_layer(model_description, number_of_channels):
47
+ if len(model_description) >= 20:
48
+ return model_description
49
+ else:
50
+ return model_description[:-1] + [
51
+ (str(number_of_channels), "fully connected layer"),
52
+ model_description[-1],
53
+ ]
54
+
55
+
56
+ def add_gcl_layer(model_description, number_of_channels):
57
+ if len(model_description) >= 20:
58
+ return model_description
59
+ else:
60
+ return model_description[:-1] + [
61
+ (str(number_of_channels), "graph convolutional layer"),
62
+ model_description[-1],
63
+ ]
64
+
65
+
66
+ def add_dropout_layer(model_description, dropout_rate):
67
+ if len(model_description) >= 20:
68
+ return model_description
69
+ else:
70
+ return model_description[:-1] + [
71
+ (str(dropout_rate), "dropout layer"),
72
+ model_description[-1],
73
+ ]
74
+
75
+
76
+ def fit_model(model_description, learning_rate, l2_regularization):
77
+ # set seeds for reproducibility
78
+ seed_number = 123
79
+
80
+ os.environ["PYTHONHASHSEED"] = str(seed_number)
81
+ random.seed(seed_number)
82
+ np.random.seed(seed_number)
83
+ tf.random.set_seed(seed_number)
84
+
85
+ l2_reg_value = l2_regularization
86
+ model_description = model_description[1:-1]
87
+
88
+ class graph_nn(tf.keras.Model):
89
+ def __init__(
90
+ self,
91
+ ):
92
+ super().__init__()
93
+
94
+ self.list_of_layers = []
95
+ for tpl_value_layer in model_description:
96
+ layer_name = tpl_value_layer[1]
97
+ layer_value = tpl_value_layer[0]
98
+ if layer_name == "fully connected layer":
99
+ self.list_of_layers.append(
100
+ layers.Dense(int(layer_value), activation="relu")
101
+ )
102
+ elif layer_name == "graph convolutional layer":
103
+ self.list_of_layers.append(
104
+ gcn_conv.GCNConv(
105
+ channels=int(layer_value),
106
+ activation="relu",
107
+ kernel_regularizer=tf.keras.regularizers.l2(l2_reg_value),
108
+ use_bias=True,
109
+ )
110
+ )
111
+ elif layer_name == "dropout layer":
112
+ self.list_of_layers.append(layers.Dropout(float(layer_value)))
113
+
114
+ self.output_layer = layers.Dense(7, activation="softmax")
115
+
116
+ def call(self, inputs):
117
+ x, a = inputs
118
+
119
+ for index, tpl_value_layer in enumerate(model_description):
120
+ if tpl_value_layer[1] == ("graph convolutional layer"):
121
+ x = self.list_of_layers[index]([x, a])
122
+ else:
123
+ x = self.list_of_layers[index](x)
124
+
125
+ x = self.output_layer(x)
126
+
127
+ return x
128
+
129
+ model = graph_nn()
130
+ model.compile(
131
+ optimizer=Adam(learning_rate),
132
+ loss=CategoricalCrossentropy(reduction="sum"),
133
+ metrics=["accuracy"],
134
+ )
135
+
136
+ loader_tr = SingleLoader(data, sample_weights=data.mask_tr)
137
+ loader_va = SingleLoader(data, sample_weights=data.mask_va)
138
+
139
+ history = model.fit(
140
+ loader_tr.load(),
141
+ steps_per_epoch=loader_tr.steps_per_epoch,
142
+ validation_data=loader_va.load(),
143
+ validation_steps=loader_va.steps_per_epoch,
144
+ epochs=2000,
145
+ verbose=0,
146
+ callbacks=[
147
+ EarlyStopping(patience=30, restore_best_weights=True)
148
+ ], # , monitor="val_accuracy"
149
+ )
150
+
151
+ return plot_loss(history), get_accuracy(model)
152
+
153
+
154
+ def get_accuracy(model):
155
+
156
+ loader_te = SingleLoader(data, sample_weights=data.mask_te)
157
+
158
+ preds = model.predict(loader_te.load(), steps=loader_te.steps_per_epoch)
159
+
160
+ ground_truths = data[0].y
161
+
162
+ true_predictions = 0
163
+ false_predictions = 0
164
+ node_colors = []
165
+
166
+ for index, val_mask in enumerate(data.mask_te):
167
+ if val_mask == 0:
168
+ continue
169
+ if np.argmax(preds[index]) == np.argmax(ground_truths[index]):
170
+ true_predictions += 1
171
+ node_colors.append("green")
172
+ else:
173
+ false_predictions += 1
174
+ node_colors.append("red")
175
+
176
+ accuracy = true_predictions / (true_predictions + false_predictions)
177
+
178
+ fig = plt.figure()
179
+ ax = fig.add_subplot(111)
180
+
181
+ nx.draw(G, pos=pos, node_size=30, node_color=node_colors)
182
+
183
+ plt.title("accuracy on test-set: " + str(accuracy))
184
+
185
+ return fig
186
+
187
+
188
+ def plot_loss(model_history):
189
+ fig = plt.figure()
190
+ ax = fig.add_subplot(111)
191
+ num_epochs = len(model_history.history["loss"])
192
+ plt.plot(list(range(num_epochs)), model_history.history["loss"], label="train loss")
193
+ # 3.57 times more validation instances thann test instances
194
+ plt.plot(
195
+ list(range(num_epochs)),
196
+ np.array(model_history.history["val_loss"]) / 3.57,
197
+ label="validation loss",
198
+ )
199
+ plt.plot(
200
+ [num_epochs - 30, num_epochs - 30],
201
+ [0, max(model_history.history["loss"])],
202
+ "--",
203
+ c="black",
204
+ alpha=0.7,
205
+ label="early stopping",
206
+ )
207
+ plt.legend(loc="upper right", bbox_to_anchor=(1, 1))
208
+
209
+ return fig
210
+
211
+
212
+ def reset_model():
213
+ return (
214
+ [
215
+ ("_Architecture_: input", "_Legend_:"),
216
+ ("output", "_Legend_:"),
217
+ ],
218
+ default_plot,
219
+ None,
220
+ )
221
+
222
+
223
+ demo = gr.Blocks()
224
+
225
+ with demo:
226
+ gr.Markdown(
227
+ """
228
+ # GNN construction site
229
+ Welcome to the GNN construction site, where you can build your individual GNN using graph convolutional layers (GCLs) and fully connected layers. The GCLs were implemented
230
+ using [Spektral](https://github.com/danielegrattarola/spektral/ "https://github.com/danielegrattarola/spektral/"), which builds on the Keras API.
231
+
232
+ ### Data
233
+ The input dataset is the public split of the Cora dataset ([benchmarks](https://paperswithcode.com/dataset/cora "https://paperswithcode.com/dataset/cora")).
234
+ Currently, the state of the art [model](https://github.com/chennnM/GCNII "https://github.com/chennnM/GCNII") (doi: 10.48550/arXiv.2007.02133) achieves an accuracy of 0.855 on the test set of this public split. The input data consists of
235
+ node features and an adjacency matrix.
236
+ ### How to build
237
+ 1. Use the sliders to adjust the number of neurons, channels or the dropout rate depending on which layer you want to add
238
+ 2. Adding layers to your network will update the current model architecture shown in the middle
239
+ 3. The "train and evaluate model" button will generate two figures after training your model, showing:
240
+ - The loss during training
241
+ - The performance on the test set (public split of Cora dataset)
242
+ 4. Reset your model and try different architectures
243
+ """
244
+ )
245
+ with gr.Row():
246
+ with gr.Column():
247
+ accuracy_plot = gr.Plot(value=default_plot, label="accuracy plot")
248
+ with gr.Column():
249
+ loss_plot = gr.Plot(label="loss plot")
250
+
251
+ with gr.Row():
252
+
253
+ with gr.Column():
254
+ with gr.Row():
255
+ number_of_neurons = gr.Slider(
256
+ minimum=1,
257
+ maximum=100,
258
+ step=1,
259
+ value=32,
260
+ label="number of neurons for fully connected layer",
261
+ )
262
+ with gr.Row():
263
+ number_of_channels = gr.Slider(
264
+ minimum=1,
265
+ maximum=100,
266
+ step=1,
267
+ value=32,
268
+ label="number of channels for graph conv. layer",
269
+ )
270
+ with gr.Row():
271
+ dropout_rate = gr.Slider(
272
+ minimum=0, maximum=1, step=0.02, value=0.5, label="dropout rate"
273
+ )
274
+ with gr.Row():
275
+ learning_rate = gr.Slider(
276
+ minimum=0.001,
277
+ maximum=0.02,
278
+ step=0.001,
279
+ value=0.005,
280
+ label="learning rate",
281
+ )
282
+ l2_regularization = gr.Slider(
283
+ minimum=0.00005,
284
+ maximum=0.001,
285
+ step=0.00005,
286
+ value=0.00025,
287
+ label="L2 regularization factor",
288
+ )
289
+
290
+ with gr.Column():
291
+ with gr.Row():
292
+ model_description = gr.Highlightedtext(
293
+ value=[
294
+ ("_Architecture_: input", "_Legend_:"),
295
+ ("output", "_Legend_:"),
296
+ ],
297
+ label="current model",
298
+ show_legend=True,
299
+ color_map={
300
+ "_Legend_:": "white",
301
+ "fully connected layer": "blue",
302
+ "graph convolutional layer": "red",
303
+ "dropout layer": "yellow",
304
+ },
305
+ )
306
+ with gr.Row():
307
+ button_add_fully_connected = gr.Button("add fully connected layer")
308
+ button_add_fully_connected.click(
309
+ fn=add_fully_connected_layer,
310
+ inputs=[model_description, number_of_neurons],
311
+ outputs=model_description,
312
+ )
313
+
314
+ with gr.Row():
315
+ button_add_fully_connected = gr.Button("add graph convolutional layer")
316
+ button_add_fully_connected.click(
317
+ fn=add_gcl_layer,
318
+ inputs=[model_description, number_of_channels],
319
+ outputs=model_description,
320
+ )
321
+
322
+ with gr.Row():
323
+ button_add_fully_connected = gr.Button("add dropout layer")
324
+ button_add_fully_connected.click(
325
+ fn=add_dropout_layer,
326
+ inputs=[model_description, dropout_rate],
327
+ outputs=model_description,
328
+ )
329
+
330
+ with gr.Column():
331
+
332
+ with gr.Row():
333
+ button_fit_model = gr.Button("train and evaluate model")
334
+ button_fit_model.click(
335
+ fn=fit_model,
336
+ inputs=[model_description, learning_rate, l2_regularization],
337
+ outputs=[loss_plot, accuracy_plot],
338
+ )
339
+
340
+ with gr.Row():
341
+ button_reset_model = gr.Button("reset model")
342
+ button_reset_model.click(
343
+ fn=reset_model,
344
+ inputs=None,
345
+ outputs=[model_description, accuracy_plot, loss_plot],
346
+ )
347
+
348
+ with gr.Row():
349
+ gr.Markdown(
350
+ """
351
+ ### Tips:
352
+ - training and evaluating might take a moment
353
+ - hovering over the legend at "current model" will highlight the respective layers
354
+ - changing the learning rate or L2 regularization factor does not require a model reset
355
+
356
+ """
357
+ )
358
+
359
+
360
+ demo.launch()