darabos commited on
Commit
daba3de
·
1 Parent(s): f0e42e3

Mapping UI is now working.

Browse files
examples/Model use CHANGED
@@ -8,31 +8,31 @@
8
  "targetHandle": "bundle"
9
  },
10
  {
11
- "id": "Train/test split 1 Train model 3",
12
  "source": "Train/test split 1",
13
  "sourceHandle": "output",
14
- "target": "Train model 3",
15
  "targetHandle": "bundle"
16
  },
17
  {
18
- "id": "Model inference 1 View tables 1",
19
- "source": "Model inference 1",
20
  "sourceHandle": "output",
21
- "target": "View tables 1",
22
  "targetHandle": "bundle"
23
  },
24
  {
25
- "id": "Train/test split 1 Train model 1",
26
- "source": "Train/test split 1",
27
  "sourceHandle": "output",
28
- "target": "Train model 1",
29
  "targetHandle": "bundle"
30
  },
31
  {
32
- "id": "Train model 1 Model inference 1",
33
- "source": "Train model 1",
34
  "sourceHandle": "output",
35
- "target": "Model inference 1",
36
  "targetHandle": "bundle"
37
  }
38
  ],
@@ -167,199 +167,6 @@
167
  "type": "basic",
168
  "width": 371.0
169
  },
170
- {
171
- "data": {
172
- "__execution_delay": 0.0,
173
- "collapsed": null,
174
- "display": {
175
- "dataframes": {
176
- "df": {
177
- "columns": [
178
- "x",
179
- "y"
180
- ]
181
- },
182
- "df_test": {
183
- "columns": [
184
- "x",
185
- "y"
186
- ]
187
- },
188
- "df_train": {
189
- "columns": [
190
- "x",
191
- "y"
192
- ]
193
- }
194
- },
195
- "other": {
196
- "model": {
197
- "model": {
198
- "inputs": [
199
- "Input__embedding_1_x"
200
- ],
201
- "loss_inputs": [
202
- "Activation_2_x",
203
- "Input__label_1_y"
204
- ],
205
- "outputs": [
206
- "Activation_2_x"
207
- ]
208
- },
209
- "type": "model"
210
- }
211
- },
212
- "relations": []
213
- },
214
- "error": null,
215
- "meta": {
216
- "inputs": {
217
- "bundle": {
218
- "name": "bundle",
219
- "position": "left",
220
- "type": {
221
- "type": "<class 'lynxkite_graph_analytics.core.Bundle'>"
222
- }
223
- }
224
- },
225
- "name": "Train model",
226
- "outputs": {
227
- "output": {
228
- "name": "output",
229
- "position": "right",
230
- "type": {
231
- "type": "None"
232
- }
233
- }
234
- },
235
- "params": {
236
- "epochs": {
237
- "default": 1.0,
238
- "name": "epochs",
239
- "type": {
240
- "type": "<class 'int'>"
241
- }
242
- },
243
- "input_mapping": {
244
- "default": null,
245
- "name": "input_mapping",
246
- "type": {
247
- "type": "<class 'str'>"
248
- }
249
- },
250
- "model_workspace": {
251
- "default": null,
252
- "name": "model_workspace",
253
- "type": {
254
- "type": "<class 'str'>"
255
- }
256
- },
257
- "save_as": {
258
- "default": "model",
259
- "name": "save_as",
260
- "type": {
261
- "type": "<class 'str'>"
262
- }
263
- }
264
- },
265
- "position": {
266
- "x": 675.0,
267
- "y": 144.0
268
- },
269
- "type": "basic"
270
- },
271
- "params": {
272
- "epochs": "1000",
273
- "input_mapping": "{\"map\": {\"Input__embedding_1_x\": {\"df\": \"df_train\", \"column\": \"x\"}, \"Input__label_1_y\": {\"df\": \"df_train\", \"column\": \"y\" }}}",
274
- "model_workspace": "Model definition",
275
- "save_as": "model"
276
- },
277
- "status": "done",
278
- "title": "Train model"
279
- },
280
- "dragHandle": ".bg-primary",
281
- "height": 519.0,
282
- "id": "Train model 3",
283
- "position": {
284
- "x": 722.5912720951791,
285
- "y": -784.0614755260641
286
- },
287
- "type": "basic",
288
- "width": 640.0
289
- },
290
- {
291
- "data": {
292
- "__execution_delay": 0.0,
293
- "collapsed": null,
294
- "display": null,
295
- "error": "'Input__embedding_1_x'",
296
- "meta": {
297
- "inputs": {
298
- "bundle": {
299
- "name": "bundle",
300
- "position": "left",
301
- "type": {
302
- "type": "<class 'lynxkite_graph_analytics.core.Bundle'>"
303
- }
304
- }
305
- },
306
- "name": "Model inference",
307
- "outputs": {
308
- "output": {
309
- "name": "output",
310
- "position": "right",
311
- "type": {
312
- "type": "None"
313
- }
314
- }
315
- },
316
- "params": {
317
- "input_mapping": {
318
- "default": "",
319
- "name": "input_mapping",
320
- "type": {
321
- "type": "<class 'str'>"
322
- }
323
- },
324
- "model_name": {
325
- "default": "model",
326
- "name": "model_name",
327
- "type": {
328
- "type": "<class 'str'>"
329
- }
330
- },
331
- "output_mapping": {
332
- "default": "",
333
- "name": "output_mapping",
334
- "type": {
335
- "type": "<class 'str'>"
336
- }
337
- }
338
- },
339
- "position": {
340
- "x": 506.0,
341
- "y": 115.0
342
- },
343
- "type": "basic"
344
- },
345
- "params": {
346
- "input_mapping": "{\"map\": {\"Input__embedding_1_x\": {\"df\": \"df_test\", \"column\": \"x\"}}}",
347
- "model_name": "model",
348
- "output_mapping": "{\"map\": {\"Activation_2_x\": {\"df\": \"df_test\", \"column\": \"predicted\"}}}"
349
- },
350
- "status": "done",
351
- "title": "Model inference"
352
- },
353
- "dragHandle": ".bg-primary",
354
- "height": 429.0,
355
- "id": "Model inference 1",
356
- "position": {
357
- "x": 1445.5664910683593,
358
- "y": 12.075943590382515
359
- },
360
- "type": "basic",
361
- "width": 410.0
362
- },
363
  {
364
  "data": {
365
  "display": {
@@ -780,54 +587,54 @@
780
  ],
781
  "data": [
782
  [
783
- "[0.52046251 0.45887971 0.72169858 0.29517919]",
784
- "[1.52046251 1.45887971 1.72169852 1.29517913]",
785
- "[1.5168578624725342, 1.450861930847168, 1.7133464813232422, 1.3041404485702515]"
786
  ],
787
  [
788
- "[0.78956431 0.87284744 0.06880784 0.03455889]",
789
- "[1.78956437 1.87284744 1.06880784 1.03455889]",
790
- "[1.7899272441864014, 1.829580307006836, 1.0702992677688599, 1.0265709161758423]"
791
  ],
792
  [
793
- "[0.49607176 0.1922397 0.46640229 0.78321403]",
794
- "[1.49607182 1.19223976 1.46640229 1.78321409]",
795
- "[1.4901000261306763, 1.193819284439087, 1.4632138013839722, 1.7822779417037964]"
796
  ],
797
  [
798
- "[0.49691743 0.61873293 0.90698647 0.94486356]",
799
- "[1.49691749 1.61873293 1.90698647 1.94486356]",
800
- "[1.4999868869781494, 1.6656270027160645, 1.9074199199676514, 1.9556759595870972]"
801
  ],
802
  [
803
- "[0.59812403 0.78395379 0.0291847 0.81814629]",
804
- "[1.59812403 1.78395379 1.0291847 1.81814623]",
805
- "[1.6044235229492188, 1.7707669734954834, 1.0426081418991089, 1.7988944053649902]"
806
  ],
807
  [
808
- "[0.67447788 0.6125319 0.98007888 0.65968603]",
809
- "[1.67447782 1.6125319 1.98007894 1.65968609]",
810
- "[1.6721093654632568, 1.6624714136123657, 1.9726766347885132, 1.6813924312591553]"
811
  ],
812
  [
813
- "[0.18720162 0.74115586 0.98626411 0.30355608]",
814
- "[1.18720162 1.74115586 1.98626411 1.30355608]",
815
- "[1.1961991786956787, 1.723442792892456, 1.9852817058563232, 1.3066248893737793]"
816
  ],
817
  [
818
- "[0.74064726 0.4155122 0.09800029 0.49930882]",
819
- "[1.74064732 1.4155122 1.09800029 1.49930882]",
820
- "[1.7340764999389648, 1.3968157768249512, 1.0968588590621948, 1.493086814880371]"
821
  ],
822
  [
823
- "[0.70167565 0.26930219 0.5660674 0.61194974]",
824
- "[1.70167565 1.26930213 1.56606746 1.61194968]",
825
- "[1.691997766494751, 1.2865687608718872, 1.5571787357330322, 1.622729778289795]"
826
  ],
827
  [
828
- "[0.90817457 0.89270043 0.38583666 0.66566533]",
829
- "[1.90817451 1.89270043 1.3858366 1.66566539]",
830
- "[1.9086859226226807, 1.924757719039917, 1.3887461423873901, 1.6714670658111572]"
831
  ]
832
  ]
833
  },
@@ -869,10 +676,6 @@
869
  "[4.27091718e-01 4.89909172e-01 6.92297399e-01 2.57611275e-04]",
870
  "[1.42709172 1.48990917 1.69229746 1.00025761]"
871
  ],
872
- [
873
- "[0.32225502 0.16999388 0.05823922 0.9628762 ]",
874
- "[1.32225502 1.16999388 1.05823922 1.9628762 ]"
875
- ],
876
  [
877
  "[0.50783676 0.04156506 0.21984279 0.8454656 ]",
878
  "[1.50783682 1.04156506 1.21984279 1.84546566]"
@@ -913,10 +716,6 @@
913
  "[0.24388778 0.07268471 0.68350857 0.73431659]",
914
  "[1.24388778 1.07268476 1.68350863 1.73431659]"
915
  ],
916
- [
917
- "[0.62569475 0.9881897 0.83639616 0.9828859 ]",
918
- "[1.62569475 1.9881897 1.83639622 1.98288584]"
919
- ],
920
  [
921
  "[0.56922203 0.98222166 0.76851749 0.28615737]",
922
  "[1.56922197 1.9822216 1.76851749 1.28615737]"
@@ -925,6 +724,10 @@
925
  "[0.88776821 0.51636773 0.30333066 0.32230979]",
926
  "[1.88776827 1.51636767 1.30333066 1.32230973]"
927
  ],
 
 
 
 
928
  [
929
  "[0.48507756 0.80808765 0.77162558 0.47834778]",
930
  "[1.48507762 1.80808759 1.77162552 1.47834778]"
@@ -949,14 +752,6 @@
949
  "[0.23942459 0.90487361 0.69337189 0.65089428]",
950
  "[1.23942459 1.90487361 1.69337189 1.65089428]"
951
  ],
952
- [
953
- "[0.94516498 0.08422136 0.5608117 0.07652664]",
954
- "[1.94516492 1.08422136 1.56081176 1.07652664]"
955
- ],
956
- [
957
- "[0.26661873 0.45946234 0.13510543 0.81294441]",
958
- "[1.26661873 1.4594624 1.13510537 1.81294441]"
959
- ],
960
  [
961
  "[0.30754459 0.77694583 0.09278506 0.38326019]",
962
  "[1.30754459 1.77694583 1.09278512 1.38326025]"
@@ -965,14 +760,18 @@
965
  "[0.27845025 0.32472342 0.82203609 0.77107543]",
966
  "[1.27845025 1.32472348 1.82203603 1.77107549]"
967
  ],
968
- [
969
- "[0.4827103 0.10563457 0.98858833 0.82286644]",
970
- "[1.48271036 1.10563457 1.98858833 1.82286644]"
971
- ],
972
  [
973
  "[0.98033333 0.97656083 0.38939917 0.81491041]",
974
  "[1.98033333 1.97656083 1.38939917 1.81491041]"
975
  ],
 
 
 
 
 
 
 
 
976
  [
977
  "[0.94221359 0.57740951 0.98649532 0.40934443]",
978
  "[1.94221354 1.57740951 1.98649526 1.40934443]"
@@ -1021,6 +820,10 @@
1021
  "[0.08107251 0.2602725 0.18861133 0.44833237]",
1022
  "[1.08107257 1.2602725 1.18861127 1.44833231]"
1023
  ],
 
 
 
 
1024
  [
1025
  "[0.93488538 0.73882395 0.37345302 0.0274905 ]",
1026
  "[1.93488538 1.73882389 1.37345302 1.0274905 ]"
@@ -1030,12 +833,12 @@
1030
  "[1.30631399 1.48311198 1.87847519 1.67559886]"
1031
  ],
1032
  [
1033
- "[0.85566247 0.83362883 0.48424995 0.25265992]",
1034
- "[1.85566247 1.83362889 1.48424995 1.25265992]"
1035
  ],
1036
  [
1037
- "[0.95928186 0.84273899 0.71514636 0.38619852]",
1038
- "[1.95928192 1.84273899 1.7151463 1.38619852]"
1039
  ],
1040
  [
1041
  "[0.32565445 0.90939188 0.07488042 0.13730896]",
@@ -1049,10 +852,6 @@
1049
  "[0.79905868 0.89367443 0.75429088 0.3190186 ]",
1050
  "[1.79905868 1.89367437 1.75429082 1.3190186 ]"
1051
  ],
1052
- [
1053
- "[0.54914117 0.03810108 0.87531954 0.73044223]",
1054
- "[1.54914117 1.03810108 1.87531948 1.73044229]"
1055
- ],
1056
  [
1057
  "[0.67418337 0.79634351 0.23229051 0.71345252]",
1058
  "[1.67418337 1.79634356 1.23229051 1.71345258]"
@@ -1090,12 +889,12 @@
1090
  "[1.60809326 1.5656302 1.32107437 1.72599435]"
1091
  ],
1092
  [
1093
- "[0.47963417 0.81818312 0.48720706 0.49339259]",
1094
- "[1.47963417 1.81818318 1.48720706 1.49339259]"
1095
  ],
1096
  [
1097
- "[0.9630242 0.76359051 0.24853623 0.76881069]",
1098
- "[1.96302414 1.76359057 1.24853623 1.76881075]"
1099
  ],
1100
  [
1101
  "[0.60609657 0.96257663 0.19292736 0.95702219]",
@@ -1105,6 +904,10 @@
1105
  "[0.80654246 0.08253473 0.74478531 0.71257162]",
1106
  "[1.8065424 1.08253479 1.74478531 1.71257162]"
1107
  ],
 
 
 
 
1108
  [
1109
  "[0.76933283 0.86241865 0.44114518 0.65644735]",
1110
  "[1.76933289 1.86241865 1.44114518 1.65644741]"
@@ -1122,8 +925,8 @@
1122
  "[1.12024069 1.21342516 1.56858408 1.58644271]"
1123
  ],
1124
  [
1125
- "[0.91730917 0.22574073 0.09591609 0.33056474]",
1126
- "[1.91730917 1.22574067 1.09591603 1.33056474]"
1127
  ],
1128
  [
1129
  "[0.6032477 0.83361369 0.18538666 0.19108021]",
@@ -1137,6 +940,10 @@
1137
  "[0.37959969 0.42820001 0.10690689 0.96353984]",
1138
  "[1.37959969 1.42820001 1.10690689 1.96353984]"
1139
  ],
 
 
 
 
1140
  [
1141
  "[0.40234613 0.54987347 0.49542785 0.54153186]",
1142
  "[1.40234613 1.54987347 1.49542785 1.5415318 ]"
@@ -1201,7 +1008,7 @@
1201
  }
1202
  },
1203
  "other": {
1204
- "model": "ModelConfig(model=Sequential(\n (0) - Linear(in_features=4, out_features=4, bias=True): Input__embedding_1_x -> Linear_1_x\n (1) - <function leaky_relu at 0x710fc8f0fba0>: Linear_1_x -> Activation_2_x\n (2) - Identity(): Activation_2_x -> Activation_2_x\n), model_inputs=['Input__embedding_1_x'], model_outputs=['Activation_2_x'], loss_inputs=['Activation_2_x', 'Input__label_1_y'], loss=Sequential(\n (0) - <function mse_loss at 0x710fc8f316c0>: Activation_2_x, Input__label_1_y -> MSE_loss_1_loss\n (1) - Identity(): MSE_loss_1_loss -> loss\n), optimizer=SGD (\nParameter Group 0\n dampening: 0\n differentiable: False\n foreach: None\n fused: None\n lr: 0.1\n maximize: False\n momentum: 0\n nesterov: False\n weight_decay: 0\n))"
1205
  },
1206
  "relations": []
1207
  },
@@ -1227,27 +1034,23 @@
1227
  }
1228
  }
1229
  },
1230
- "position": {
1231
- "x": 471.0,
1232
- "y": 424.0
1233
- },
1234
  "type": "table_view"
1235
  },
1236
  "params": {
1237
  "limit": 100.0
1238
  },
1239
- "status": "planned",
1240
  "title": "View tables"
1241
  },
1242
  "dragHandle": ".bg-primary",
1243
- "height": 600.0,
1244
  "id": "View tables 1",
1245
  "position": {
1246
- "x": 2017.4630208327735,
1247
- "y": -223.54449620081252
1248
  },
1249
  "type": "table_view",
1250
- "width": 582.0
1251
  },
1252
  {
1253
  "data": {
@@ -1277,22 +1080,125 @@
1277
  "other": {
1278
  "model": {
1279
  "model": {
1280
- "inputs": [],
 
 
1281
  "loss_inputs": [
1282
- "Activation_2_x",
1283
- "Input__label_1_y"
1284
  ],
1285
  "outputs": [
1286
- "Activation_2_x",
1287
- "Input__label_1_y"
1288
- ]
1289
  },
1290
  "type": "model"
1291
  }
1292
  },
1293
  "relations": []
1294
  },
1295
- "error": "Mapping is unset.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1296
  "meta": {
1297
  "inputs": {
1298
  "bundle": {
@@ -1325,48 +1231,154 @@
1325
  "default": null,
1326
  "name": "input_mapping",
1327
  "type": {
1328
- "type": "<class 'lynxkite_graph_analytics.pytorch_model_ops.ModelMapping'>"
1329
  }
1330
  },
1331
- "model_workspace": {
1332
- "default": null,
1333
- "name": "model_workspace",
1334
  "type": {
1335
  "type": "<class 'str'>"
1336
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1337
  },
1338
- "save_as": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1339
  "default": "model",
1340
- "name": "save_as",
1341
  "type": {
1342
  "type": "<class 'str'>"
1343
  }
 
 
 
 
 
 
 
1344
  }
1345
  },
1346
  "position": {
1347
- "x": 723.0,
1348
- "y": 370.0
1349
  },
1350
  "type": "basic"
1351
  },
1352
  "params": {
1353
- "epochs": "2",
1354
- "input_mapping": "{\"map\":{\"Activation_2_x\":{\"df\":\"df_train\"},\"Input__label_1_y\":{\"df\":\"df_train\",\"column\":\"y\"}}}",
1355
- "model_workspace": "Model definition",
1356
- "save_as": "model"
1357
  },
1358
  "status": "done",
1359
- "title": "Train model"
1360
  },
1361
  "dragHandle": ".bg-primary",
1362
- "height": 473.0,
1363
- "id": "Train model 1",
1364
  "position": {
1365
- "x": 712.1212754578014,
1366
- "y": 42.33722689912529
1367
  },
1368
  "type": "basic",
1369
- "width": 577.0
1370
  }
1371
  ]
1372
  }
 
8
  "targetHandle": "bundle"
9
  },
10
  {
11
+ "id": "Train/test split 1 Define model 1",
12
  "source": "Train/test split 1",
13
  "sourceHandle": "output",
14
+ "target": "Define model 1",
15
  "targetHandle": "bundle"
16
  },
17
  {
18
+ "id": "Define model 1 Train model 2",
19
+ "source": "Define model 1",
20
  "sourceHandle": "output",
21
+ "target": "Train model 2",
22
  "targetHandle": "bundle"
23
  },
24
  {
25
+ "id": "Train model 2 Model inference 1",
26
+ "source": "Train model 2",
27
  "sourceHandle": "output",
28
+ "target": "Model inference 1",
29
  "targetHandle": "bundle"
30
  },
31
  {
32
+ "id": "Model inference 1 View tables 1",
33
+ "source": "Model inference 1",
34
  "sourceHandle": "output",
35
+ "target": "View tables 1",
36
  "targetHandle": "bundle"
37
  }
38
  ],
 
167
  "type": "basic",
168
  "width": 371.0
169
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  {
171
  "data": {
172
  "display": {
 
587
  ],
588
  "data": [
589
  [
590
+ "[0.4827103 0.10563457 0.98858833 0.82286644]",
591
+ "[1.48271036 1.10563457 1.98858833 1.82286644]",
592
+ "[1.4503304958343506, 1.1575173139572144, 1.996051549911499, 1.8715994358062744]"
593
  ],
594
  [
595
+ "[0.54914117 0.03810108 0.87531954 0.73044223]",
596
+ "[1.54914117 1.03810108 1.87531948 1.73044229]",
597
+ "[1.5054945945739746, 1.0799291133880615, 1.8784711360931396, 1.7745399475097656]"
598
  ],
599
  [
600
+ "[0.26661873 0.45946234 0.13510543 0.81294441]",
601
+ "[1.26661873 1.4594624 1.13510537 1.81294441]",
602
+ "[1.2893636226654053, 1.4012629985809326, 1.1273266077041626, 1.7483277320861816]"
603
  ],
604
  [
605
+ "[0.9630242 0.76359051 0.24853623 0.76881069]",
606
+ "[1.96302414 1.76359057 1.24853623 1.76881075]",
607
+ "[1.9627504348754883, 1.808237075805664, 1.2652870416641235, 1.7989287376403809]"
608
  ],
609
  [
610
+ "[0.52046251 0.45887971 0.72169858 0.29517919]",
611
+ "[1.52046251 1.45887971 1.72169852 1.29517913]",
612
+ "[1.4980013370513916, 1.4540209770202637, 1.7123804092407227, 1.3061636686325073]"
613
  ],
614
  [
615
+ "[0.62569475 0.9881897 0.83639616 0.9828859 ]",
616
+ "[1.62569475 1.9881897 1.83639622 1.98288584]",
617
+ "[1.6632444858551025, 2.0650277137756348, 1.8643897771835327, 2.032055377960205]"
618
  ],
619
  [
620
+ "[0.95928186 0.84273899 0.71514636 0.38619852]",
621
+ "[1.95928192 1.84273899 1.7151463 1.38619852]",
622
+ "[1.943433403968811, 1.905775785446167, 1.727120280265808, 1.4508745670318604]"
623
  ],
624
  [
625
+ "[0.94516498 0.08422136 0.5608117 0.07652664]",
626
+ "[1.94516492 1.08422136 1.56081176 1.07652664]",
627
+ "[1.8603894710540771, 1.0990982055664062, 1.5473597049713135, 1.1221177577972412]"
628
  ],
629
  [
630
+ "[0.32225502 0.16999388 0.05823922 0.9628762 ]",
631
+ "[1.32225502 1.16999388 1.05823922 1.9628762 ]",
632
+ "[1.3253933191299438, 1.1194703578948975, 1.0517100095748901, 1.903820514678955]"
633
  ],
634
  [
635
+ "[0.91730917 0.22574073 0.09591609 0.33056474]",
636
+ "[1.91730917 1.22574067 1.09591603 1.33056474]",
637
+ "[1.8627445697784424, 1.2110977172851562, 1.085142731666565, 1.3327856063842773]"
638
  ]
639
  ]
640
  },
 
676
  "[4.27091718e-01 4.89909172e-01 6.92297399e-01 2.57611275e-04]",
677
  "[1.42709172 1.48990917 1.69229746 1.00025761]"
678
  ],
 
 
 
 
679
  [
680
  "[0.50783676 0.04156506 0.21984279 0.8454656 ]",
681
  "[1.50783682 1.04156506 1.21984279 1.84546566]"
 
716
  "[0.24388778 0.07268471 0.68350857 0.73431659]",
717
  "[1.24388778 1.07268476 1.68350863 1.73431659]"
718
  ],
 
 
 
 
719
  [
720
  "[0.56922203 0.98222166 0.76851749 0.28615737]",
721
  "[1.56922197 1.9822216 1.76851749 1.28615737]"
 
724
  "[0.88776821 0.51636773 0.30333066 0.32230979]",
725
  "[1.88776827 1.51636767 1.30333066 1.32230973]"
726
  ],
727
+ [
728
+ "[0.90817457 0.89270043 0.38583666 0.66566533]",
729
+ "[1.90817451 1.89270043 1.3858366 1.66566539]"
730
+ ],
731
  [
732
  "[0.48507756 0.80808765 0.77162558 0.47834778]",
733
  "[1.48507762 1.80808759 1.77162552 1.47834778]"
 
752
  "[0.23942459 0.90487361 0.69337189 0.65089428]",
753
  "[1.23942459 1.90487361 1.69337189 1.65089428]"
754
  ],
 
 
 
 
 
 
 
 
755
  [
756
  "[0.30754459 0.77694583 0.09278506 0.38326019]",
757
  "[1.30754459 1.77694583 1.09278512 1.38326025]"
 
760
  "[0.27845025 0.32472342 0.82203609 0.77107543]",
761
  "[1.27845025 1.32472348 1.82203603 1.77107549]"
762
  ],
 
 
 
 
763
  [
764
  "[0.98033333 0.97656083 0.38939917 0.81491041]",
765
  "[1.98033333 1.97656083 1.38939917 1.81491041]"
766
  ],
767
+ [
768
+ "[0.74064726 0.4155122 0.09800029 0.49930882]",
769
+ "[1.74064732 1.4155122 1.09800029 1.49930882]"
770
+ ],
771
+ [
772
+ "[0.78956431 0.87284744 0.06880784 0.03455889]",
773
+ "[1.78956437 1.87284744 1.06880784 1.03455889]"
774
+ ],
775
  [
776
  "[0.94221359 0.57740951 0.98649532 0.40934443]",
777
  "[1.94221354 1.57740951 1.98649526 1.40934443]"
 
820
  "[0.08107251 0.2602725 0.18861133 0.44833237]",
821
  "[1.08107257 1.2602725 1.18861127 1.44833231]"
822
  ],
823
+ [
824
+ "[0.59812403 0.78395379 0.0291847 0.81814629]",
825
+ "[1.59812403 1.78395379 1.0291847 1.81814623]"
826
+ ],
827
  [
828
  "[0.93488538 0.73882395 0.37345302 0.0274905 ]",
829
  "[1.93488538 1.73882389 1.37345302 1.0274905 ]"
 
833
  "[1.30631399 1.48311198 1.87847519 1.67559886]"
834
  ],
835
  [
836
+ "[0.18720162 0.74115586 0.98626411 0.30355608]",
837
+ "[1.18720162 1.74115586 1.98626411 1.30355608]"
838
  ],
839
  [
840
+ "[0.85566247 0.83362883 0.48424995 0.25265992]",
841
+ "[1.85566247 1.83362889 1.48424995 1.25265992]"
842
  ],
843
  [
844
  "[0.32565445 0.90939188 0.07488042 0.13730896]",
 
852
  "[0.79905868 0.89367443 0.75429088 0.3190186 ]",
853
  "[1.79905868 1.89367437 1.75429082 1.3190186 ]"
854
  ],
 
 
 
 
855
  [
856
  "[0.67418337 0.79634351 0.23229051 0.71345252]",
857
  "[1.67418337 1.79634356 1.23229051 1.71345258]"
 
889
  "[1.60809326 1.5656302 1.32107437 1.72599435]"
890
  ],
891
  [
892
+ "[0.67447788 0.6125319 0.98007888 0.65968603]",
893
+ "[1.67447782 1.6125319 1.98007894 1.65968609]"
894
  ],
895
  [
896
+ "[0.47963417 0.81818312 0.48720706 0.49339259]",
897
+ "[1.47963417 1.81818318 1.48720706 1.49339259]"
898
  ],
899
  [
900
  "[0.60609657 0.96257663 0.19292736 0.95702219]",
 
904
  "[0.80654246 0.08253473 0.74478531 0.71257162]",
905
  "[1.8065424 1.08253479 1.74478531 1.71257162]"
906
  ],
907
+ [
908
+ "[0.70167565 0.26930219 0.5660674 0.61194974]",
909
+ "[1.70167565 1.26930213 1.56606746 1.61194968]"
910
+ ],
911
  [
912
  "[0.76933283 0.86241865 0.44114518 0.65644735]",
913
  "[1.76933289 1.86241865 1.44114518 1.65644741]"
 
925
  "[1.12024069 1.21342516 1.56858408 1.58644271]"
926
  ],
927
  [
928
+ "[0.49691743 0.61873293 0.90698647 0.94486356]",
929
+ "[1.49691749 1.61873293 1.90698647 1.94486356]"
930
  ],
931
  [
932
  "[0.6032477 0.83361369 0.18538666 0.19108021]",
 
940
  "[0.37959969 0.42820001 0.10690689 0.96353984]",
941
  "[1.37959969 1.42820001 1.10690689 1.96353984]"
942
  ],
943
+ [
944
+ "[0.49607176 0.1922397 0.46640229 0.78321403]",
945
+ "[1.49607182 1.19223976 1.46640229 1.78321409]"
946
+ ],
947
  [
948
  "[0.40234613 0.54987347 0.49542785 0.54153186]",
949
  "[1.40234613 1.54987347 1.49542785 1.5415318 ]"
 
1008
  }
1009
  },
1010
  "other": {
1011
+ "model": "ModelConfig(model=Sequential(\n (0) - Linear(in_features=4, out_features=4, bias=True): Input__embedding_1_x -> Linear_1_x\n (1) - <function leaky_relu at 0x731d1381bba0>: Linear_1_x -> Activation_2_x\n (2) - Identity(): Activation_2_x -> Activation_2_x\n), model_inputs=['Input__embedding_1_x'], model_outputs=['Activation_2_x'], loss_inputs=['Input__label_1_y', 'Activation_2_x'], loss=Sequential(\n (0) - <function mse_loss at 0x731d138216c0>: Activation_2_x, Input__label_1_y -> MSE_loss_1_loss\n (1) - Identity(): MSE_loss_1_loss -> loss\n), optimizer=SGD (\nParameter Group 0\n dampening: 0\n differentiable: False\n foreach: None\n fused: None\n lr: 0.1\n maximize: False\n momentum: 0\n nesterov: False\n weight_decay: 0\n), source_workspace=None, trained=True)"
1012
  },
1013
  "relations": []
1014
  },
 
1034
  }
1035
  }
1036
  },
 
 
 
 
1037
  "type": "table_view"
1038
  },
1039
  "params": {
1040
  "limit": 100.0
1041
  },
1042
+ "status": "done",
1043
  "title": "View tables"
1044
  },
1045
  "dragHandle": ".bg-primary",
1046
+ "height": 711.0,
1047
  "id": "View tables 1",
1048
  "position": {
1049
+ "x": 2900.3734843758352,
1050
+ "y": -128.41609608842867
1051
  },
1052
  "type": "table_view",
1053
+ "width": 836.0
1054
  },
1055
  {
1056
  "data": {
 
1080
  "other": {
1081
  "model": {
1082
  "model": {
1083
+ "inputs": [
1084
+ "Input__embedding_1_x"
1085
+ ],
1086
  "loss_inputs": [
1087
+ "Input__label_1_y",
1088
+ "Activation_2_x"
1089
  ],
1090
  "outputs": [
1091
+ "Activation_2_x"
1092
+ ],
1093
+ "trained": false
1094
  },
1095
  "type": "model"
1096
  }
1097
  },
1098
  "relations": []
1099
  },
1100
+ "error": null,
1101
+ "meta": {
1102
+ "inputs": {
1103
+ "bundle": {
1104
+ "name": "bundle",
1105
+ "position": "left",
1106
+ "type": {
1107
+ "type": "<class 'lynxkite_graph_analytics.core.Bundle'>"
1108
+ }
1109
+ }
1110
+ },
1111
+ "name": "Define model",
1112
+ "outputs": {
1113
+ "output": {
1114
+ "name": "output",
1115
+ "position": "right",
1116
+ "type": {
1117
+ "type": "None"
1118
+ }
1119
+ }
1120
+ },
1121
+ "params": {
1122
+ "model_workspace": {
1123
+ "default": null,
1124
+ "name": "model_workspace",
1125
+ "type": {
1126
+ "type": "<class 'str'>"
1127
+ }
1128
+ },
1129
+ "save_as": {
1130
+ "default": "model",
1131
+ "name": "save_as",
1132
+ "type": {
1133
+ "type": "<class 'str'>"
1134
+ }
1135
+ }
1136
+ },
1137
+ "type": "basic"
1138
+ },
1139
+ "params": {
1140
+ "model_workspace": "Model definition",
1141
+ "save_as": "model"
1142
+ },
1143
+ "status": "done",
1144
+ "title": "Define model"
1145
+ },
1146
+ "dragHandle": ".bg-primary",
1147
+ "height": 537.0,
1148
+ "id": "Define model 1",
1149
+ "position": {
1150
+ "x": 795.0,
1151
+ "y": -45.0
1152
+ },
1153
+ "type": "basic",
1154
+ "width": 498.0
1155
+ },
1156
+ {
1157
+ "data": {
1158
+ "__execution_delay": 0.0,
1159
+ "collapsed": null,
1160
+ "display": {
1161
+ "dataframes": {
1162
+ "df": {
1163
+ "columns": [
1164
+ "x",
1165
+ "y"
1166
+ ]
1167
+ },
1168
+ "df_test": {
1169
+ "columns": [
1170
+ "x",
1171
+ "y"
1172
+ ]
1173
+ },
1174
+ "df_train": {
1175
+ "columns": [
1176
+ "x",
1177
+ "y"
1178
+ ]
1179
+ }
1180
+ },
1181
+ "other": {
1182
+ "model": {
1183
+ "model": {
1184
+ "inputs": [
1185
+ "Input__embedding_1_x"
1186
+ ],
1187
+ "loss_inputs": [
1188
+ "Input__label_1_y",
1189
+ "Activation_2_x"
1190
+ ],
1191
+ "outputs": [
1192
+ "Activation_2_x"
1193
+ ],
1194
+ "trained": true
1195
+ },
1196
+ "type": "model"
1197
+ }
1198
+ },
1199
+ "relations": []
1200
+ },
1201
+ "error": null,
1202
  "meta": {
1203
  "inputs": {
1204
  "bundle": {
 
1231
  "default": null,
1232
  "name": "input_mapping",
1233
  "type": {
1234
+ "type": "<class 'lynxkite_graph_analytics.lynxkite_ops.ModelTrainingInputMapping'>"
1235
  }
1236
  },
1237
+ "model_name": {
1238
+ "default": "model",
1239
+ "name": "model_name",
1240
  "type": {
1241
  "type": "<class 'str'>"
1242
  }
1243
+ }
1244
+ },
1245
+ "position": {
1246
+ "x": 666.0,
1247
+ "y": 492.0
1248
+ },
1249
+ "type": "basic"
1250
+ },
1251
+ "params": {
1252
+ "epochs": "1000",
1253
+ "input_mapping": "{\"map\":{\"Input__embedding_1_x\":{\"column\":\"x\",\"df\":\"df_train\"},\"Input__label_1_y\":{\"column\":\"y\",\"df\":\"df_train\"}}}",
1254
+ "model_name": "model"
1255
+ },
1256
+ "status": "done",
1257
+ "title": "Train model"
1258
+ },
1259
+ "dragHandle": ".bg-primary",
1260
+ "height": 604.0,
1261
+ "id": "Train model 2",
1262
+ "position": {
1263
+ "x": 1399.5245787239226,
1264
+ "y": -19.196202428593544
1265
+ },
1266
+ "type": "basic",
1267
+ "width": 586.0
1268
+ },
1269
+ {
1270
+ "data": {
1271
+ "__execution_delay": 0.0,
1272
+ "collapsed": null,
1273
+ "display": {
1274
+ "dataframes": {
1275
+ "df": {
1276
+ "columns": [
1277
+ "x",
1278
+ "y"
1279
+ ]
1280
  },
1281
+ "df_test": {
1282
+ "columns": [
1283
+ "predicted",
1284
+ "x",
1285
+ "y"
1286
+ ]
1287
+ },
1288
+ "df_train": {
1289
+ "columns": [
1290
+ "x",
1291
+ "y"
1292
+ ]
1293
+ }
1294
+ },
1295
+ "other": {
1296
+ "model": {
1297
+ "model": {
1298
+ "inputs": [
1299
+ "Input__embedding_1_x"
1300
+ ],
1301
+ "loss_inputs": [
1302
+ "Input__label_1_y",
1303
+ "Activation_2_x"
1304
+ ],
1305
+ "outputs": [
1306
+ "Activation_2_x"
1307
+ ],
1308
+ "trained": true
1309
+ },
1310
+ "type": "model"
1311
+ }
1312
+ },
1313
+ "relations": []
1314
+ },
1315
+ "error": null,
1316
+ "meta": {
1317
+ "inputs": {
1318
+ "bundle": {
1319
+ "name": "bundle",
1320
+ "position": "left",
1321
+ "type": {
1322
+ "type": "<class 'lynxkite_graph_analytics.core.Bundle'>"
1323
+ }
1324
+ }
1325
+ },
1326
+ "name": "Model inference",
1327
+ "outputs": {
1328
+ "output": {
1329
+ "name": "output",
1330
+ "position": "right",
1331
+ "type": {
1332
+ "type": "None"
1333
+ }
1334
+ }
1335
+ },
1336
+ "params": {
1337
+ "input_mapping": {
1338
+ "default": null,
1339
+ "name": "input_mapping",
1340
+ "type": {
1341
+ "type": "<class 'lynxkite_graph_analytics.lynxkite_ops.ModelInferenceInputMapping'>"
1342
+ }
1343
+ },
1344
+ "model_name": {
1345
  "default": "model",
1346
+ "name": "model_name",
1347
  "type": {
1348
  "type": "<class 'str'>"
1349
  }
1350
+ },
1351
+ "output_mapping": {
1352
+ "default": null,
1353
+ "name": "output_mapping",
1354
+ "type": {
1355
+ "type": "<class 'lynxkite_graph_analytics.lynxkite_ops.ModelOutputMapping'>"
1356
+ }
1357
  }
1358
  },
1359
  "position": {
1360
+ "x": 934.0,
1361
+ "y": 167.0
1362
  },
1363
  "type": "basic"
1364
  },
1365
  "params": {
1366
+ "input_mapping": "{\"map\":{\"Input__embedding_1_x\":{\"column\":\"x\",\"df\":\"df_test\"}}}",
1367
+ "model_name": "model",
1368
+ "output_mapping": "{\"map\":{\"Activation_2_x\":{\"column\":\"predicted\",\"df\":\"df_test\"}}}"
 
1369
  },
1370
  "status": "done",
1371
+ "title": "Model inference"
1372
  },
1373
  "dragHandle": ".bg-primary",
1374
+ "height": 893.0,
1375
+ "id": "Model inference 1",
1376
  "position": {
1377
+ "x": 2181.718373860645,
1378
+ "y": -69.44701793295484
1379
  },
1380
  "type": "basic",
1381
+ "width": 529.0
1382
  }
1383
  ]
1384
  }
lynxkite-app/web/src/workspace/nodes/NodeParameter.tsx CHANGED
@@ -2,17 +2,54 @@
2
  import ArrowsHorizontal from "~icons/tabler/arrows-horizontal.jsx";
3
 
4
  const BOOLEAN = "<class 'bool'>";
5
- const MODEL_MAPPING =
6
- "<class 'lynxkite_graph_analytics.pytorch_model_ops.ModelMapping'>";
 
 
 
 
7
  function ParamName({ name }: { name: string }) {
8
  return (
9
  <span className="param-name bg-base-200">{name.replace(/_/g, " ")}</span>
10
  );
11
  }
12
 
13
- function getModelBindings(data: any): string[] {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  function bindingsOfModel(m: any): string[] {
15
- return [...m.inputs, ...m.outputs, ...m.loss_inputs];
 
 
 
 
 
 
 
 
 
 
16
  }
17
  const bindings = new Set<string>();
18
  const other = data?.display?.other ?? data?.display?.value?.other ?? {};
@@ -31,19 +68,19 @@ function getModelBindings(data: any): string[] {
31
  function parseJsonOrEmpty(json: string): object {
32
  try {
33
  const j = JSON.parse(json);
34
- if (typeof j === "object") {
35
  return j;
36
  }
37
  } catch (e) {}
38
  return {};
39
  }
40
 
41
- function ModelMapping({ value, onChange, data }: any) {
42
  const v: any = parseJsonOrEmpty(value);
43
  v.map ??= {};
44
  const dfs =
45
  data?.display?.dataframes ?? data?.display?.value?.dataframes ?? {};
46
- const bindings = getModelBindings(data);
47
  return (
48
  <table className="model-mapping-param">
49
  <tbody>
@@ -63,12 +100,12 @@ function ModelMapping({ value, onChange, data }: any) {
63
  value={v.map?.[binding]?.df}
64
  onChange={(evt) => {
65
  const df = evt.currentTarget.value;
66
- if (df === "unbound") {
67
  const map = { ...v.map, [binding]: undefined };
68
  onChange(JSON.stringify({ map }));
69
  } else {
70
  const columnSpec = {
71
- column: dfs[df][0],
72
  ...(v.map?.[binding] || {}),
73
  df,
74
  };
@@ -77,9 +114,7 @@ function ModelMapping({ value, onChange, data }: any) {
77
  }
78
  }}
79
  >
80
- <option key="unbound" value="unbound">
81
- unbound
82
- </option>
83
  {Object.keys(dfs).map((df: string) => (
84
  <option key={df} value={df}>
85
  {df}
@@ -88,22 +123,39 @@ function ModelMapping({ value, onChange, data }: any) {
88
  </select>
89
  </td>
90
  <td>
91
- <select
92
- className="select select-ghost"
93
- value={v.map?.[binding]?.column}
94
- onChange={(evt) => {
95
- const column = evt.currentTarget.value;
96
- const columnSpec = { ...(v.map?.[binding] || {}), column };
97
- const map = { ...v.map, [binding]: columnSpec };
98
- onChange(JSON.stringify({ map }));
99
- }}
100
- >
101
- {dfs[v.map?.[binding]?.df]?.columns.map((col: string) => (
102
- <option key={col} value={col}>
103
- {col}
104
- </option>
105
- ))}
106
- </select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  </td>
108
  </tr>
109
  ))
@@ -178,25 +230,41 @@ export default function NodeParameter({
178
  {name.replace(/_/g, " ")}
179
  </label>
180
  </div>
181
- ) : meta?.type?.type === MODEL_MAPPING ? (
182
  <>
183
  <ParamName name={name} />
184
- <ModelMapping value={value} data={data} onChange={onChange} />
 
 
 
 
 
185
  </>
186
- ) : (
187
  <>
188
  <ParamName name={name} />
189
- <input
190
- className="input input-bordered w-full"
191
- value={value || ""}
192
- onChange={(evt) => onChange(evt.currentTarget.value, { delay: 2 })}
193
- onBlur={(evt) => onChange(evt.currentTarget.value, { delay: 0 })}
194
- onKeyDown={(evt) =>
195
- evt.code === "Enter" &&
196
- onChange(evt.currentTarget.value, { delay: 0 })
197
- }
 
 
 
 
 
 
198
  />
199
  </>
 
 
 
 
 
200
  )}
201
  </label>
202
  );
 
2
  import ArrowsHorizontal from "~icons/tabler/arrows-horizontal.jsx";
3
 
4
  const BOOLEAN = "<class 'bool'>";
5
+ const MODEL_TRAINING_INPUT_MAPPING =
6
+ "<class 'lynxkite_graph_analytics.lynxkite_ops.ModelTrainingInputMapping'>";
7
+ const MODEL_INFERENCE_INPUT_MAPPING =
8
+ "<class 'lynxkite_graph_analytics.lynxkite_ops.ModelInferenceInputMapping'>";
9
+ const MODEL_OUTPUT_MAPPING =
10
+ "<class 'lynxkite_graph_analytics.lynxkite_ops.ModelOutputMapping'>";
11
  function ParamName({ name }: { name: string }) {
12
  return (
13
  <span className="param-name bg-base-200">{name.replace(/_/g, " ")}</span>
14
  );
15
  }
16
 
17
+ function Input({
18
+ value,
19
+ onChange,
20
+ }: {
21
+ value: string;
22
+ onChange: (value: string, options?: { delay: number }) => void;
23
+ }) {
24
+ return (
25
+ <input
26
+ className="input input-bordered w-full"
27
+ value={value || ""}
28
+ onChange={(evt) => onChange(evt.currentTarget.value, { delay: 2 })}
29
+ onBlur={(evt) => onChange(evt.currentTarget.value, { delay: 0 })}
30
+ onKeyDown={(evt) =>
31
+ evt.code === "Enter" && onChange(evt.currentTarget.value, { delay: 0 })
32
+ }
33
+ />
34
+ );
35
+ }
36
+
37
+ function getModelBindings(
38
+ data: any,
39
+ variant: "training input" | "inference input" | "output",
40
+ ): string[] {
41
  function bindingsOfModel(m: any): string[] {
42
+ switch (variant) {
43
+ case "training input":
44
+ return [
45
+ ...m.inputs,
46
+ ...m.loss_inputs.filter((i: string) => !m.outputs.includes(i)),
47
+ ];
48
+ case "inference input":
49
+ return m.inputs;
50
+ case "output":
51
+ return m.outputs;
52
+ }
53
  }
54
  const bindings = new Set<string>();
55
  const other = data?.display?.other ?? data?.display?.value?.other ?? {};
 
68
  function parseJsonOrEmpty(json: string): object {
69
  try {
70
  const j = JSON.parse(json);
71
+ if (j !== null && typeof j === "object") {
72
  return j;
73
  }
74
  } catch (e) {}
75
  return {};
76
  }
77
 
78
+ function ModelMapping({ value, onChange, data, variant }: any) {
79
  const v: any = parseJsonOrEmpty(value);
80
  v.map ??= {};
81
  const dfs =
82
  data?.display?.dataframes ?? data?.display?.value?.dataframes ?? {};
83
+ const bindings = getModelBindings(data, variant);
84
  return (
85
  <table className="model-mapping-param">
86
  <tbody>
 
100
  value={v.map?.[binding]?.df}
101
  onChange={(evt) => {
102
  const df = evt.currentTarget.value;
103
+ if (df === "") {
104
  const map = { ...v.map, [binding]: undefined };
105
  onChange(JSON.stringify({ map }));
106
  } else {
107
  const columnSpec = {
108
+ column: dfs[df].columns[0],
109
  ...(v.map?.[binding] || {}),
110
  df,
111
  };
 
114
  }
115
  }}
116
  >
117
+ <option key="" value="" />
 
 
118
  {Object.keys(dfs).map((df: string) => (
119
  <option key={df} value={df}>
120
  {df}
 
123
  </select>
124
  </td>
125
  <td>
126
+ {variant === "output" ? (
127
+ <Input
128
+ value={v.map?.[binding]?.column}
129
+ onChange={(column, options) => {
130
+ const columnSpec = {
131
+ ...(v.map?.[binding] || {}),
132
+ column,
133
+ };
134
+ const map = { ...v.map, [binding]: columnSpec };
135
+ onChange(JSON.stringify({ map }), options);
136
+ }}
137
+ />
138
+ ) : (
139
+ <select
140
+ className="select select-ghost"
141
+ value={v.map?.[binding]?.column}
142
+ onChange={(evt) => {
143
+ const column = evt.currentTarget.value;
144
+ const columnSpec = {
145
+ ...(v.map?.[binding] || {}),
146
+ column,
147
+ };
148
+ const map = { ...v.map, [binding]: columnSpec };
149
+ onChange(JSON.stringify({ map }));
150
+ }}
151
+ >
152
+ {dfs[v.map?.[binding]?.df]?.columns.map((col: string) => (
153
+ <option key={col} value={col}>
154
+ {col}
155
+ </option>
156
+ ))}
157
+ </select>
158
+ )}
159
  </td>
160
  </tr>
161
  ))
 
230
  {name.replace(/_/g, " ")}
231
  </label>
232
  </div>
233
+ ) : meta?.type?.type === MODEL_TRAINING_INPUT_MAPPING ? (
234
  <>
235
  <ParamName name={name} />
236
+ <ModelMapping
237
+ value={value}
238
+ data={data}
239
+ variant="training input"
240
+ onChange={onChange}
241
+ />
242
  </>
243
+ ) : meta?.type?.type === MODEL_INFERENCE_INPUT_MAPPING ? (
244
  <>
245
  <ParamName name={name} />
246
+ <ModelMapping
247
+ value={value}
248
+ data={data}
249
+ variant="inference input"
250
+ onChange={onChange}
251
+ />
252
+ </>
253
+ ) : meta?.type?.type === MODEL_OUTPUT_MAPPING ? (
254
+ <>
255
+ <ParamName name={name} />
256
+ <ModelMapping
257
+ value={value}
258
+ data={data}
259
+ variant="output"
260
+ onChange={onChange}
261
  />
262
  </>
263
+ ) : (
264
+ <>
265
+ <ParamName name={name} />
266
+ <Input value={value} onChange={onChange} />
267
+ </>
268
  )}
269
  </label>
270
  );
lynxkite-graph-analytics/src/lynxkite_graph_analytics/core.py CHANGED
@@ -204,8 +204,8 @@ def _execute_node(node, ws, catalog, outputs):
204
  for edge in ws.edges
205
  if edge.target == node.id
206
  }
 
207
  try:
208
- # Convert inputs types to match operation signature.
209
  inputs = []
210
  for p in op.inputs.values():
211
  if p.name not in input_map:
@@ -219,13 +219,24 @@ def _execute_node(node, ws, catalog, outputs):
219
  elif p.type == Bundle and isinstance(x, pd.DataFrame):
220
  x = Bundle.from_df(x)
221
  inputs.append(x)
222
- result = op(*inputs, **params)
223
  except Exception as e:
224
  if os.environ.get("LYNXKITE_LOG_OP_ERRORS"):
225
  traceback.print_exc()
226
  node.publish_error(e)
227
  return
228
- outputs[node.id] = result.output
 
 
 
 
 
 
 
 
 
 
 
 
229
  node.publish_result(result)
230
 
231
 
 
204
  for edge in ws.edges
205
  if edge.target == node.id
206
  }
207
+ # Convert inputs types to match operation signature.
208
  try:
 
209
  inputs = []
210
  for p in op.inputs.values():
211
  if p.name not in input_map:
 
219
  elif p.type == Bundle and isinstance(x, pd.DataFrame):
220
  x = Bundle.from_df(x)
221
  inputs.append(x)
 
222
  except Exception as e:
223
  if os.environ.get("LYNXKITE_LOG_OP_ERRORS"):
224
  traceback.print_exc()
225
  node.publish_error(e)
226
  return
227
+ # Execute op.
228
+ try:
229
+ result = op(*inputs, **params)
230
+ except Exception as e:
231
+ if os.environ.get("LYNXKITE_LOG_OP_ERRORS"):
232
+ traceback.print_exc()
233
+ result = ops.Result(error=str(e))
234
+ # On error, just output the first input. This helps reduce the errors on the frontend,
235
+ # and it lets boxes easily access things from their inputs on the UI, even in error state.
236
+ if inputs:
237
+ result.output = inputs[0]
238
+ if result.output is not None:
239
+ outputs[node.id] = result.output
240
  node.publish_result(result)
241
 
242
 
lynxkite-graph-analytics/src/lynxkite_graph_analytics/lynxkite_ops.py CHANGED
@@ -363,31 +363,60 @@ def biomedical_foundation_graph(*, filter_nodes: str):
363
  return None
364
 
365
 
366
- @op("Train model")
367
- def train_model(
368
  bundle: core.Bundle,
369
  *,
370
  model_workspace: str,
371
- input_mapping: pytorch_model_ops.ModelMapping,
372
- epochs: int = 1,
373
  save_as: str = "model",
374
  ):
375
  """Trains the selected model on the selected dataset. Most training parameters are set in the model definition."""
376
  assert model_workspace, "Model workspace is unset."
377
- print(f"input_mapping: {input_mapping}")
378
  ws = load_ws(model_workspace)
379
- inputs = (
380
- pytorch_model_ops.to_tensors(bundle, input_mapping) if input_mapping else {}
381
- )
382
- m = pytorch_model_ops.build_model(ws, inputs)
383
  bundle = bundle.copy()
384
  bundle.other[save_as] = m
385
- if input_mapping is None:
386
- return ops.Result(bundle, error="Mapping is unset.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
  t = tqdm(range(epochs), desc="Training model")
388
  for _ in t:
389
  loss = m.train(inputs)
390
  t.set_postfix({"loss": loss})
 
 
 
391
  return bundle
392
 
393
 
@@ -396,13 +425,14 @@ def model_inference(
396
  bundle: core.Bundle,
397
  *,
398
  model_name: str = "model",
399
- input_mapping: pytorch_model_ops.ModelMapping,
400
- output_mapping: pytorch_model_ops.ModelMapping,
401
  ):
402
  """Executes a trained model."""
403
  if input_mapping is None or output_mapping is None:
404
  return ops.Result(bundle, error="Mapping is unset.")
405
  m = bundle.other[model_name]
 
406
  inputs = pytorch_model_ops.to_tensors(bundle, input_mapping)
407
  outputs = m.inference(inputs)
408
  bundle = bundle.copy()
 
363
  return None
364
 
365
 
366
+ @op("Define model")
367
+ def define_model(
368
  bundle: core.Bundle,
369
  *,
370
  model_workspace: str,
 
 
371
  save_as: str = "model",
372
  ):
373
  """Trains the selected model on the selected dataset. Most training parameters are set in the model definition."""
374
  assert model_workspace, "Model workspace is unset."
 
375
  ws = load_ws(model_workspace)
376
+ # Build the model without inputs, to get its interface.
377
+ m = pytorch_model_ops.build_model(ws, {})
378
+ m.source_workspace = model_workspace
 
379
  bundle = bundle.copy()
380
  bundle.other[save_as] = m
381
+ return bundle
382
+
383
+
384
+ # These contain the same mapping, but they get different UIs.
385
+ # For inputs, you select existing columns. For outputs, you can create new columns.
386
+ class ModelInferenceInputMapping(pytorch_model_ops.ModelMapping):
387
+ pass
388
+
389
+
390
+ class ModelTrainingInputMapping(pytorch_model_ops.ModelMapping):
391
+ pass
392
+
393
+
394
+ class ModelOutputMapping(pytorch_model_ops.ModelMapping):
395
+ pass
396
+
397
+
398
+ @op("Train model")
399
+ def train_model(
400
+ bundle: core.Bundle,
401
+ *,
402
+ model_name: str = "model",
403
+ input_mapping: ModelTrainingInputMapping,
404
+ epochs: int = 1,
405
+ ):
406
+ """Trains the selected model on the selected dataset. Most training parameters are set in the model definition."""
407
+ m = bundle.other[model_name].copy()
408
+ inputs = pytorch_model_ops.to_tensors(bundle, input_mapping)
409
+ if not m.trained and m.source_workspace:
410
+ # Rebuild the model for the correct inputs.
411
+ ws = load_ws(m.source_workspace)
412
+ m = pytorch_model_ops.build_model(ws, inputs)
413
  t = tqdm(range(epochs), desc="Training model")
414
  for _ in t:
415
  loss = m.train(inputs)
416
  t.set_postfix({"loss": loss})
417
+ m.trained = True
418
+ bundle = bundle.copy()
419
+ bundle.other[model_name] = m
420
  return bundle
421
 
422
 
 
425
  bundle: core.Bundle,
426
  *,
427
  model_name: str = "model",
428
+ input_mapping: ModelInferenceInputMapping,
429
+ output_mapping: ModelOutputMapping,
430
  ):
431
  """Executes a trained model."""
432
  if input_mapping is None or output_mapping is None:
433
  return ops.Result(bundle, error="Mapping is unset.")
434
  m = bundle.other[model_name]
435
+ assert m.trained, "The model is not trained."
436
  inputs = pytorch_model_ops.to_tensors(bundle, input_mapping)
437
  outputs = m.inference(inputs)
438
  bundle = bundle.copy()
lynxkite-graph-analytics/src/lynxkite_graph_analytics/pytorch_model_ops.py CHANGED
@@ -1,13 +1,15 @@
1
  """Boxes for defining PyTorch models."""
2
 
 
3
  import graphlib
 
4
 
5
  import pydantic
6
  from lynxkite.core import ops, workspace
7
  from lynxkite.core.ops import Parameter as P
8
  import torch
9
  import torch_geometric as pyg
10
- from dataclasses import dataclass
11
  from . import core
12
 
13
  ENV = "PyTorch model"
@@ -125,9 +127,9 @@ ops.register_passive_op(
125
  )
126
 
127
 
128
- def _to_id(s: str) -> str:
129
  """Replaces all non-alphanumeric characters with underscores."""
130
- return "".join(c if c.isalnum() else "_" for c in s)
131
 
132
 
133
  class ColumnSpec(pydantic.BaseModel):
@@ -139,7 +141,7 @@ class ModelMapping(pydantic.BaseModel):
139
  map: dict[str, ColumnSpec]
140
 
141
 
142
- @dataclass
143
  class ModelConfig:
144
  model: torch.nn.Module
145
  model_inputs: list[str]
@@ -147,6 +149,8 @@ class ModelConfig:
147
  loss_inputs: list[str]
148
  loss: torch.nn.Module
149
  optimizer: torch.optim.Optimizer
 
 
150
 
151
  def _forward(self, inputs: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]:
152
  model_inputs = [inputs[i] for i in self.model_inputs]
@@ -176,8 +180,8 @@ class ModelConfig:
176
 
177
  def copy(self):
178
  """Returns a copy of the model."""
179
- c = super().copy()
180
- c.model = self.model.copy()
181
  return c
182
 
183
  def default_display(self):
@@ -187,6 +191,7 @@ class ModelConfig:
187
  "inputs": self.model_inputs,
188
  "outputs": self.model_outputs,
189
  "loss_inputs": self.loss_inputs,
 
190
  },
191
  }
192
 
@@ -206,13 +211,17 @@ def build_model(
206
  assert len(optimizers) == 1, f"More than one optimizer found: {optimizers}"
207
  [optimizer] = optimizers
208
  dependencies = {n.id: [] for n in ws.nodes}
209
- edges = {}
 
210
  # TODO: Dissolve repeat boxes here.
211
  for e in ws.edges:
212
  dependencies[e.target].append(e.source)
213
- edges.setdefault((e.target, e.targetHandle), []).append(
214
  (e.source, e.sourceHandle)
215
  )
 
 
 
216
  sizes = {}
217
  for k, i in inputs.items():
218
  sizes[k] = i.shape[-1]
@@ -221,8 +230,10 @@ def build_model(
221
  loss_layers = []
222
  in_loss = set()
223
  cfg = {}
224
- loss_inputs = set()
225
- used_inputs = set()
 
 
226
  for node_id in ts.static_order():
227
  node = nodes[node_id]
228
  t = node.data.title
@@ -231,51 +242,62 @@ def build_model(
231
  for b in dependencies[node_id]:
232
  if b in in_loss:
233
  in_loss.add(node_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  ls = loss_layers if node_id in in_loss else layers
235
- nid = _to_id(node_id)
236
  match t:
237
  case "Linear":
238
- [(ib, ih)] = edges[node_id, "x"]
239
- i = _to_id(ib) + "_" + ih
240
- used_inputs.add(i)
241
- isize = sizes[i]
242
  osize = isize if p["output_dim"] == "same" else int(p["output_dim"])
243
- ls.append((torch.nn.Linear(isize, osize), f"{i} -> {nid}_x"))
244
- sizes[f"{nid}_x"] = osize
245
  case "Activation":
246
- [(ib, ih)] = edges[node_id, "x"]
247
- i = _to_id(ib) + "_" + ih
248
- used_inputs.add(i)
249
  f = getattr(
250
  torch.nn.functional, p["type"].name.lower().replace(" ", "_")
251
  )
252
- ls.append((f, f"{i} -> {nid}_x"))
253
- sizes[f"{nid}_x"] = sizes[i]
254
  case "MSE loss":
255
- [(xb, xh)] = edges[node_id, "x"]
256
- xi = _to_id(xb) + "_" + xh
257
- [(yb, yh)] = edges[node_id, "y"]
258
- yi = _to_id(yb) + "_" + yh
259
- loss_inputs.add(xi)
260
- loss_inputs.add(yi)
261
- in_loss.add(node_id)
262
- loss_layers.append(
263
- (torch.nn.functional.mse_loss, f"{xi}, {yi} -> {nid}_loss")
264
  )
265
- cfg["model_inputs"] = list(used_inputs & inputs.keys())
266
- cfg["model_outputs"] = list(loss_inputs - inputs.keys())
267
- cfg["loss_inputs"] = list(loss_inputs)
268
  # Make sure the trained output is output from the last model layer.
269
  outputs = ", ".join(cfg["model_outputs"])
270
  layers.append((torch.nn.Identity(), f"{outputs} -> {outputs}"))
271
  # Create model.
272
- cfg["model"] = pyg.nn.Sequential(", ".join(used_inputs & inputs.keys()), layers)
273
  # Make sure the loss is output from the last loss layer.
274
- [(lossb, lossh)] = edges[optimizer, "loss"]
275
- lossi = _to_id(lossb) + "_" + lossh
276
  loss_layers.append((torch.nn.Identity(), f"{lossi} -> loss"))
277
  # Create loss function.
278
- cfg["loss"] = pyg.nn.Sequential(", ".join(loss_inputs), loss_layers)
279
  assert not list(cfg["loss"].parameters()), (
280
  f"loss should have no parameters: {list(cfg['loss'].parameters())}"
281
  )
@@ -287,9 +309,14 @@ def build_model(
287
  return ModelConfig(**cfg)
288
 
289
 
290
- def to_tensors(b: core.Bundle, m: ModelMapping) -> dict[str, torch.Tensor]:
291
- """Converts a tensor to the correct type for PyTorch."""
 
 
292
  tensors = {}
293
  for k, v in m.map.items():
294
- tensors[k] = torch.tensor(b.dfs[v.df][v.column].to_list(), dtype=torch.float32)
 
 
 
295
  return tensors
 
1
  """Boxes for defining PyTorch models."""
2
 
3
+ import copy
4
  import graphlib
5
+ import types
6
 
7
  import pydantic
8
  from lynxkite.core import ops, workspace
9
  from lynxkite.core.ops import Parameter as P
10
  import torch
11
  import torch_geometric as pyg
12
+ import dataclasses
13
  from . import core
14
 
15
  ENV = "PyTorch model"
 
127
  )
128
 
129
 
130
+ def _to_id(*strings: str) -> str:
131
  """Replaces all non-alphanumeric characters with underscores."""
132
+ return "_".join("".join(c if c.isalnum() else "_" for c in s) for s in strings)
133
 
134
 
135
  class ColumnSpec(pydantic.BaseModel):
 
141
  map: dict[str, ColumnSpec]
142
 
143
 
144
+ @dataclasses.dataclass
145
  class ModelConfig:
146
  model: torch.nn.Module
147
  model_inputs: list[str]
 
149
  loss_inputs: list[str]
150
  loss: torch.nn.Module
151
  optimizer: torch.optim.Optimizer
152
+ source_workspace: str | None = None
153
+ trained: bool = False
154
 
155
  def _forward(self, inputs: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]:
156
  model_inputs = [inputs[i] for i in self.model_inputs]
 
180
 
181
  def copy(self):
182
  """Returns a copy of the model."""
183
+ c = dataclasses.replace(self)
184
+ c.model = copy.deepcopy(self.model)
185
  return c
186
 
187
  def default_display(self):
 
191
  "inputs": self.model_inputs,
192
  "outputs": self.model_outputs,
193
  "loss_inputs": self.loss_inputs,
194
+ "trained": self.trained,
195
  },
196
  }
197
 
 
211
  assert len(optimizers) == 1, f"More than one optimizer found: {optimizers}"
212
  [optimizer] = optimizers
213
  dependencies = {n.id: [] for n in ws.nodes}
214
+ in_edges = {}
215
+ out_edges = {}
216
  # TODO: Dissolve repeat boxes here.
217
  for e in ws.edges:
218
  dependencies[e.target].append(e.source)
219
+ in_edges.setdefault(e.target, {}).setdefault(e.targetHandle, []).append(
220
  (e.source, e.sourceHandle)
221
  )
222
+ out_edges.setdefault(e.source, {}).setdefault(e.sourceHandle, []).append(
223
+ (e.target, e.targetHandle)
224
+ )
225
  sizes = {}
226
  for k, i in inputs.items():
227
  sizes[k] = i.shape[-1]
 
230
  loss_layers = []
231
  in_loss = set()
232
  cfg = {}
233
+ used_in_model = set()
234
+ made_in_model = set()
235
+ used_in_loss = set()
236
+ made_in_loss = set()
237
  for node_id in ts.static_order():
238
  node = nodes[node_id]
239
  t = node.data.title
 
242
  for b in dependencies[node_id]:
243
  if b in in_loss:
244
  in_loss.add(node_id)
245
+ if "loss" in t:
246
+ in_loss.add(node_id)
247
+ inputs = {}
248
+ for n in in_edges.get(node_id, []):
249
+ for b, h in in_edges[node_id][n]:
250
+ i = _to_id(b, h)
251
+ inputs[n] = i
252
+ if node_id in in_loss:
253
+ used_in_loss.add(i)
254
+ else:
255
+ used_in_model.add(i)
256
+ outputs = {}
257
+ for out in out_edges.get(node_id, []):
258
+ i = _to_id(node_id, out)
259
+ outputs[out] = i
260
+ if inputs: # Nodes with no inputs are input nodes. Their outputs are not "made" by us.
261
+ if node_id in in_loss:
262
+ made_in_loss.add(i)
263
+ else:
264
+ made_in_model.add(i)
265
+ inputs = types.SimpleNamespace(**inputs)
266
+ outputs = types.SimpleNamespace(**outputs)
267
  ls = loss_layers if node_id in in_loss else layers
 
268
  match t:
269
  case "Linear":
270
+ isize = sizes.get(inputs.x, 1)
 
 
 
271
  osize = isize if p["output_dim"] == "same" else int(p["output_dim"])
272
+ ls.append((torch.nn.Linear(isize, osize), f"{inputs.x} -> {outputs.x}"))
273
+ sizes[outputs.x] = osize
274
  case "Activation":
 
 
 
275
  f = getattr(
276
  torch.nn.functional, p["type"].name.lower().replace(" ", "_")
277
  )
278
+ ls.append((f, f"{inputs.x} -> {outputs.x}"))
279
+ sizes[outputs.x] = sizes.get(inputs.x, 1)
280
  case "MSE loss":
281
+ ls.append(
282
+ (
283
+ torch.nn.functional.mse_loss,
284
+ f"{inputs.x}, {inputs.y} -> {outputs.loss}",
285
+ )
 
 
 
 
286
  )
287
+ cfg["model_inputs"] = list(used_in_model - made_in_model)
288
+ cfg["model_outputs"] = list(made_in_model & used_in_loss)
289
+ cfg["loss_inputs"] = list(used_in_loss - made_in_loss)
290
  # Make sure the trained output is output from the last model layer.
291
  outputs = ", ".join(cfg["model_outputs"])
292
  layers.append((torch.nn.Identity(), f"{outputs} -> {outputs}"))
293
  # Create model.
294
+ cfg["model"] = pyg.nn.Sequential(", ".join(cfg["model_inputs"]), layers)
295
  # Make sure the loss is output from the last loss layer.
296
+ [(lossb, lossh)] = in_edges[optimizer]["loss"]
297
+ lossi = _to_id(lossb, lossh)
298
  loss_layers.append((torch.nn.Identity(), f"{lossi} -> loss"))
299
  # Create loss function.
300
+ cfg["loss"] = pyg.nn.Sequential(", ".join(cfg["loss_inputs"]), loss_layers)
301
  assert not list(cfg["loss"].parameters()), (
302
  f"loss should have no parameters: {list(cfg['loss'].parameters())}"
303
  )
 
309
  return ModelConfig(**cfg)
310
 
311
 
312
+ def to_tensors(b: core.Bundle, m: ModelMapping | None) -> dict[str, torch.Tensor]:
313
+ """Converts a tensor to the correct type for PyTorch. Ignores missing mappings."""
314
+ if m is None:
315
+ return {}
316
  tensors = {}
317
  for k, v in m.map.items():
318
+ if v.df in b.dfs and v.column in b.dfs[v.df]:
319
+ tensors[k] = torch.tensor(
320
+ b.dfs[v.df][v.column].to_list(), dtype=torch.float32
321
+ )
322
  return tensors