Spaces:
Running
Running
Merge pull request #114 from biggraph/darabos-repeats
Browse files- .github/workflows/test.yaml +1 -0
- examples/Model definition +250 -126
- examples/Model use +86 -87
- lynxkite-app/web/src/workspace/nodes/NodeParameter.tsx +42 -27
- lynxkite-core/src/lynxkite/core/ops.py +12 -1
- lynxkite-graph-analytics/src/lynxkite_graph_analytics/core.py +10 -2
- lynxkite-graph-analytics/src/lynxkite_graph_analytics/lynxkite_ops.py +11 -5
- lynxkite-graph-analytics/src/lynxkite_graph_analytics/pytorch_model_ops.py +288 -120
- lynxkite-graph-analytics/tests/test_pytorch_model_ops.py +68 -15
.github/workflows/test.yaml
CHANGED
@@ -81,6 +81,7 @@ jobs:
|
|
81 |
- name: Run Playwright tests
|
82 |
run: |
|
83 |
cd lynxkite-app/web
|
|
|
84 |
npm run test
|
85 |
|
86 |
- uses: actions/upload-artifact@v4
|
|
|
81 |
- name: Run Playwright tests
|
82 |
run: |
|
83 |
cd lynxkite-app/web
|
84 |
+
npm run build
|
85 |
npm run test
|
86 |
|
87 |
- uses: actions/upload-artifact@v4
|
examples/Model definition
CHANGED
@@ -1,171 +1,278 @@
|
|
1 |
{
|
2 |
"edges": [
|
3 |
{
|
4 |
-
"id": "
|
5 |
-
"source": "
|
6 |
-
"sourceHandle": "
|
7 |
-
"target": "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
"targetHandle": "x"
|
9 |
},
|
10 |
{
|
11 |
-
"id": "Input:
|
12 |
-
"source": "Input:
|
13 |
-
"sourceHandle": "
|
14 |
-
"target": "MSE loss
|
15 |
"targetHandle": "y"
|
16 |
},
|
17 |
{
|
18 |
-
"id": "
|
19 |
-
"source": "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
"sourceHandle": "x",
|
21 |
-
"target": "
|
22 |
"targetHandle": "x"
|
23 |
},
|
24 |
{
|
25 |
-
"id": "
|
26 |
-
"source": "
|
27 |
-
"sourceHandle": "
|
28 |
-
"target": "
|
29 |
"targetHandle": "x"
|
30 |
},
|
31 |
{
|
32 |
-
"id": "
|
33 |
-
"source": "
|
34 |
-
"sourceHandle": "
|
35 |
-
"target": "
|
36 |
-
"targetHandle": "
|
37 |
}
|
38 |
],
|
39 |
"env": "PyTorch model",
|
40 |
"nodes": [
|
41 |
{
|
42 |
"data": {
|
|
|
|
|
43 |
"display": null,
|
44 |
"error": null,
|
|
|
45 |
"meta": {
|
46 |
-
"inputs": {
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
"name": "x",
|
51 |
-
"position": "top",
|
52 |
"type": {
|
53 |
"type": "tensor"
|
54 |
}
|
55 |
}
|
56 |
},
|
57 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
"type": "basic"
|
59 |
},
|
60 |
-
"params": {
|
|
|
|
|
|
|
61 |
"status": "planned",
|
62 |
-
"title": "
|
63 |
},
|
64 |
"dragHandle": ".bg-primary",
|
65 |
-
"height":
|
66 |
-
"id": "
|
67 |
"position": {
|
68 |
-
"x":
|
69 |
-
"y":
|
70 |
},
|
71 |
"type": "basic",
|
72 |
-
"width":
|
73 |
},
|
74 |
{
|
75 |
"data": {
|
|
|
|
|
76 |
"display": null,
|
77 |
"error": null,
|
|
|
78 |
"meta": {
|
79 |
"inputs": {
|
80 |
"x": {
|
81 |
"name": "x",
|
82 |
"position": "bottom",
|
83 |
"type": {
|
84 |
-
"type": "
|
85 |
}
|
86 |
}
|
87 |
},
|
88 |
-
"name": "
|
89 |
"outputs": {
|
90 |
-
"
|
91 |
-
"name": "
|
92 |
"position": "top",
|
93 |
"type": {
|
94 |
-
"type": "
|
95 |
}
|
96 |
}
|
97 |
},
|
98 |
"params": {
|
99 |
-
"
|
100 |
-
"default": "
|
101 |
-
"name": "
|
102 |
"type": {
|
103 |
-
"
|
|
|
|
|
|
|
|
|
|
|
104 |
}
|
105 |
}
|
106 |
},
|
|
|
|
|
|
|
|
|
107 |
"type": "basic"
|
108 |
},
|
109 |
"params": {
|
110 |
-
"
|
111 |
},
|
112 |
"status": "planned",
|
113 |
-
"title": "
|
114 |
},
|
115 |
"dragHandle": ".bg-primary",
|
116 |
"height": 200.0,
|
117 |
-
"id": "
|
118 |
"position": {
|
119 |
-
"x":
|
120 |
-
"y":
|
121 |
},
|
122 |
"type": "basic",
|
123 |
"width": 200.0
|
124 |
},
|
125 |
{
|
126 |
"data": {
|
|
|
|
|
127 |
"display": null,
|
128 |
"error": null,
|
|
|
129 |
"meta": {
|
130 |
-
"inputs": {
|
|
|
|
|
131 |
"x": {
|
132 |
"name": "x",
|
133 |
-
"position": "
|
134 |
"type": {
|
135 |
"type": "tensor"
|
136 |
}
|
137 |
-
}
|
138 |
-
|
139 |
-
|
140 |
-
|
|
|
|
|
141 |
"type": {
|
142 |
-
"type": "
|
143 |
}
|
144 |
}
|
145 |
},
|
146 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
147 |
"outputs": {
|
148 |
-
"
|
149 |
-
"name": "
|
150 |
"position": "top",
|
151 |
"type": {
|
152 |
"type": "tensor"
|
153 |
}
|
154 |
}
|
155 |
},
|
156 |
-
"params": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
"type": "basic"
|
158 |
},
|
159 |
-
"params": {
|
|
|
|
|
160 |
"status": "planned",
|
161 |
-
"title": "
|
162 |
},
|
163 |
"dragHandle": ".bg-primary",
|
164 |
"height": 200.0,
|
165 |
-
"id": "
|
166 |
"position": {
|
167 |
-
"x":
|
168 |
-
"y": -
|
169 |
},
|
170 |
"type": "basic",
|
171 |
"width": 200.0
|
@@ -174,31 +281,51 @@
|
|
174 |
"data": {
|
175 |
"display": null,
|
176 |
"error": null,
|
|
|
177 |
"meta": {
|
178 |
-
"inputs": {
|
179 |
-
|
180 |
-
|
|
|
|
|
|
|
|
|
|
|
181 |
"y": {
|
182 |
"name": "y",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
183 |
"position": "top",
|
184 |
"type": {
|
185 |
-
"type": "
|
186 |
}
|
187 |
}
|
188 |
},
|
189 |
"params": {},
|
|
|
|
|
|
|
|
|
190 |
"type": "basic"
|
191 |
},
|
192 |
"params": {},
|
193 |
"status": "planned",
|
194 |
-
"title": "
|
195 |
},
|
196 |
"dragHandle": ".bg-primary",
|
197 |
"height": 200.0,
|
198 |
-
"id": "
|
199 |
"position": {
|
200 |
-
"x":
|
201 |
-
"y": -
|
202 |
},
|
203 |
"type": "basic",
|
204 |
"width": 200.0
|
@@ -209,58 +336,62 @@
|
|
209 |
"collapsed": null,
|
210 |
"display": null,
|
211 |
"error": null,
|
|
|
212 |
"meta": {
|
213 |
"inputs": {
|
214 |
-
"
|
215 |
-
"name": "
|
216 |
-
"position": "
|
217 |
"type": {
|
218 |
"type": "tensor"
|
219 |
}
|
220 |
}
|
221 |
},
|
222 |
-
"name": "
|
223 |
"outputs": {
|
224 |
-
"
|
225 |
-
"name": "
|
226 |
-
"position": "
|
227 |
"type": {
|
228 |
"type": "tensor"
|
229 |
}
|
230 |
}
|
231 |
},
|
232 |
"params": {
|
233 |
-
"
|
234 |
-
"default":
|
235 |
-
"name": "
|
236 |
"type": {
|
237 |
-
"
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
|
|
|
|
243 |
}
|
244 |
}
|
245 |
},
|
246 |
"position": {
|
247 |
-
"x":
|
248 |
-
"y":
|
249 |
},
|
250 |
"type": "basic"
|
251 |
},
|
252 |
"params": {
|
253 |
-
"
|
|
|
254 |
},
|
255 |
"status": "planned",
|
256 |
-
"title": "
|
257 |
},
|
258 |
"dragHandle": ".bg-primary",
|
259 |
"height": 200.0,
|
260 |
-
"id": "
|
261 |
"position": {
|
262 |
-
"x":
|
263 |
-
"y": -
|
264 |
},
|
265 |
"type": "basic",
|
266 |
"width": 200.0
|
@@ -271,61 +402,54 @@
|
|
271 |
"collapsed": null,
|
272 |
"display": null,
|
273 |
"error": null,
|
|
|
274 |
"meta": {
|
275 |
"inputs": {
|
276 |
-
"
|
277 |
-
"name": "
|
278 |
"position": "bottom",
|
279 |
"type": {
|
280 |
-
"type": "
|
281 |
}
|
282 |
}
|
283 |
},
|
284 |
-
"name": "
|
285 |
-
"outputs": {
|
286 |
-
|
287 |
-
|
288 |
-
"
|
289 |
-
"name": "lr",
|
290 |
"type": {
|
291 |
-
"type": "
|
292 |
}
|
293 |
-
}
|
294 |
-
|
295 |
-
|
296 |
-
|
|
|
|
|
297 |
"type": {
|
298 |
-
"
|
299 |
-
"AdamW",
|
300 |
-
"Adafactor",
|
301 |
-
"Adagrad",
|
302 |
-
"SGD",
|
303 |
-
"Lion",
|
304 |
-
"Paged AdamW",
|
305 |
-
"Galore AdamW"
|
306 |
-
]
|
307 |
}
|
308 |
}
|
309 |
},
|
310 |
"position": {
|
311 |
-
"x":
|
312 |
-
"y":
|
313 |
},
|
314 |
"type": "basic"
|
315 |
},
|
316 |
"params": {
|
317 |
-
"
|
318 |
-
"type": "SGD"
|
319 |
},
|
320 |
"status": "planned",
|
321 |
-
"title": "
|
322 |
},
|
323 |
"dragHandle": ".bg-primary",
|
324 |
"height": 200.0,
|
325 |
-
"id": "
|
326 |
"position": {
|
327 |
-
"x":
|
328 |
-
"y":
|
329 |
},
|
330 |
"type": "basic",
|
331 |
"width": 200.0
|
|
|
1 |
{
|
2 |
"edges": [
|
3 |
{
|
4 |
+
"id": "MSE loss 2 Optimizer 2",
|
5 |
+
"source": "MSE loss 2",
|
6 |
+
"sourceHandle": "output",
|
7 |
+
"target": "Optimizer 2",
|
8 |
+
"targetHandle": "loss"
|
9 |
+
},
|
10 |
+
{
|
11 |
+
"id": "Activation 1 MSE loss 2",
|
12 |
+
"source": "Activation 1",
|
13 |
+
"sourceHandle": "output",
|
14 |
+
"target": "MSE loss 2",
|
15 |
"targetHandle": "x"
|
16 |
},
|
17 |
{
|
18 |
+
"id": "Input: tensor 3 MSE loss 2",
|
19 |
+
"source": "Input: tensor 3",
|
20 |
+
"sourceHandle": "x",
|
21 |
+
"target": "MSE loss 2",
|
22 |
"targetHandle": "y"
|
23 |
},
|
24 |
{
|
25 |
+
"id": "Activation 1 Repeat 1",
|
26 |
+
"source": "Activation 1",
|
27 |
+
"sourceHandle": "output",
|
28 |
+
"target": "Repeat 1",
|
29 |
+
"targetHandle": "input"
|
30 |
+
},
|
31 |
+
{
|
32 |
+
"id": "Input: tensor 1 Linear 1",
|
33 |
+
"source": "Input: tensor 1",
|
34 |
"sourceHandle": "x",
|
35 |
+
"target": "Linear 1",
|
36 |
"targetHandle": "x"
|
37 |
},
|
38 |
{
|
39 |
+
"id": "Linear 1 Activation 1",
|
40 |
+
"source": "Linear 1",
|
41 |
+
"sourceHandle": "output",
|
42 |
+
"target": "Activation 1",
|
43 |
"targetHandle": "x"
|
44 |
},
|
45 |
{
|
46 |
+
"id": "Repeat 1 Linear 1",
|
47 |
+
"source": "Repeat 1",
|
48 |
+
"sourceHandle": "output",
|
49 |
+
"target": "Linear 1",
|
50 |
+
"targetHandle": "x"
|
51 |
}
|
52 |
],
|
53 |
"env": "PyTorch model",
|
54 |
"nodes": [
|
55 |
{
|
56 |
"data": {
|
57 |
+
"__execution_delay": 0.0,
|
58 |
+
"collapsed": null,
|
59 |
"display": null,
|
60 |
"error": null,
|
61 |
+
"input_metadata": null,
|
62 |
"meta": {
|
63 |
+
"inputs": {
|
64 |
+
"loss": {
|
65 |
+
"name": "loss",
|
66 |
+
"position": "bottom",
|
|
|
|
|
67 |
"type": {
|
68 |
"type": "tensor"
|
69 |
}
|
70 |
}
|
71 |
},
|
72 |
+
"name": "Optimizer",
|
73 |
+
"outputs": {},
|
74 |
+
"params": {
|
75 |
+
"lr": {
|
76 |
+
"default": 0.001,
|
77 |
+
"name": "lr",
|
78 |
+
"type": {
|
79 |
+
"type": "<class 'float'>"
|
80 |
+
}
|
81 |
+
},
|
82 |
+
"type": {
|
83 |
+
"default": "AdamW",
|
84 |
+
"name": "type",
|
85 |
+
"type": {
|
86 |
+
"enum": [
|
87 |
+
"AdamW",
|
88 |
+
"Adafactor",
|
89 |
+
"Adagrad",
|
90 |
+
"SGD",
|
91 |
+
"Lion",
|
92 |
+
"Paged AdamW",
|
93 |
+
"Galore AdamW"
|
94 |
+
]
|
95 |
+
}
|
96 |
+
}
|
97 |
+
},
|
98 |
"type": "basic"
|
99 |
},
|
100 |
+
"params": {
|
101 |
+
"lr": "0.1",
|
102 |
+
"type": "SGD"
|
103 |
+
},
|
104 |
"status": "planned",
|
105 |
+
"title": "Optimizer"
|
106 |
},
|
107 |
"dragHandle": ".bg-primary",
|
108 |
+
"height": 250.0,
|
109 |
+
"id": "Optimizer 2",
|
110 |
"position": {
|
111 |
+
"x": 292.3983313429414,
|
112 |
+
"y": -853.8015246037802
|
113 |
},
|
114 |
"type": "basic",
|
115 |
+
"width": 232.0
|
116 |
},
|
117 |
{
|
118 |
"data": {
|
119 |
+
"__execution_delay": 0.0,
|
120 |
+
"collapsed": null,
|
121 |
"display": null,
|
122 |
"error": null,
|
123 |
+
"input_metadata": null,
|
124 |
"meta": {
|
125 |
"inputs": {
|
126 |
"x": {
|
127 |
"name": "x",
|
128 |
"position": "bottom",
|
129 |
"type": {
|
130 |
+
"type": "<class 'inspect._empty'>"
|
131 |
}
|
132 |
}
|
133 |
},
|
134 |
+
"name": "Activation",
|
135 |
"outputs": {
|
136 |
+
"output": {
|
137 |
+
"name": "output",
|
138 |
"position": "top",
|
139 |
"type": {
|
140 |
+
"type": "None"
|
141 |
}
|
142 |
}
|
143 |
},
|
144 |
"params": {
|
145 |
+
"type": {
|
146 |
+
"default": "ReLU",
|
147 |
+
"name": "type",
|
148 |
"type": {
|
149 |
+
"enum": [
|
150 |
+
"ReLU",
|
151 |
+
"Leaky_ReLU",
|
152 |
+
"Tanh",
|
153 |
+
"Mish"
|
154 |
+
]
|
155 |
}
|
156 |
}
|
157 |
},
|
158 |
+
"position": {
|
159 |
+
"x": 344.0,
|
160 |
+
"y": 384.0
|
161 |
+
},
|
162 |
"type": "basic"
|
163 |
},
|
164 |
"params": {
|
165 |
+
"type": "Leaky_ReLU"
|
166 |
},
|
167 |
"status": "planned",
|
168 |
+
"title": "Activation"
|
169 |
},
|
170 |
"dragHandle": ".bg-primary",
|
171 |
"height": 200.0,
|
172 |
+
"id": "Activation 1",
|
173 |
"position": {
|
174 |
+
"x": 99.77615018185415,
|
175 |
+
"y": -249.43925929074078
|
176 |
},
|
177 |
"type": "basic",
|
178 |
"width": 200.0
|
179 |
},
|
180 |
{
|
181 |
"data": {
|
182 |
+
"__execution_delay": 0.0,
|
183 |
+
"collapsed": null,
|
184 |
"display": null,
|
185 |
"error": null,
|
186 |
+
"input_metadata": null,
|
187 |
"meta": {
|
188 |
+
"inputs": {},
|
189 |
+
"name": "Input: tensor",
|
190 |
+
"outputs": {
|
191 |
"x": {
|
192 |
"name": "x",
|
193 |
+
"position": "top",
|
194 |
"type": {
|
195 |
"type": "tensor"
|
196 |
}
|
197 |
+
}
|
198 |
+
},
|
199 |
+
"params": {
|
200 |
+
"name": {
|
201 |
+
"default": null,
|
202 |
+
"name": "name",
|
203 |
"type": {
|
204 |
+
"type": "None"
|
205 |
}
|
206 |
}
|
207 |
},
|
208 |
+
"position": {
|
209 |
+
"x": 258.0,
|
210 |
+
"y": 397.0
|
211 |
+
},
|
212 |
+
"type": "basic"
|
213 |
+
},
|
214 |
+
"params": {
|
215 |
+
"name": "X"
|
216 |
+
},
|
217 |
+
"status": "planned",
|
218 |
+
"title": "Input: tensor"
|
219 |
+
},
|
220 |
+
"dragHandle": ".bg-primary",
|
221 |
+
"height": 200.0,
|
222 |
+
"id": "Input: tensor 1",
|
223 |
+
"position": {
|
224 |
+
"x": 85.83561484252238,
|
225 |
+
"y": 293.6278596776366
|
226 |
+
},
|
227 |
+
"type": "basic",
|
228 |
+
"width": 200.0
|
229 |
+
},
|
230 |
+
{
|
231 |
+
"data": {
|
232 |
+
"__execution_delay": 0.0,
|
233 |
+
"collapsed": null,
|
234 |
+
"display": null,
|
235 |
+
"error": null,
|
236 |
+
"input_metadata": null,
|
237 |
+
"meta": {
|
238 |
+
"inputs": {},
|
239 |
+
"name": "Input: tensor",
|
240 |
"outputs": {
|
241 |
+
"x": {
|
242 |
+
"name": "x",
|
243 |
"position": "top",
|
244 |
"type": {
|
245 |
"type": "tensor"
|
246 |
}
|
247 |
}
|
248 |
},
|
249 |
+
"params": {
|
250 |
+
"name": {
|
251 |
+
"default": null,
|
252 |
+
"name": "name",
|
253 |
+
"type": {
|
254 |
+
"type": "None"
|
255 |
+
}
|
256 |
+
}
|
257 |
+
},
|
258 |
+
"position": {
|
259 |
+
"x": 1169.0,
|
260 |
+
"y": 340.0
|
261 |
+
},
|
262 |
"type": "basic"
|
263 |
},
|
264 |
+
"params": {
|
265 |
+
"name": "Y"
|
266 |
+
},
|
267 |
"status": "planned",
|
268 |
+
"title": "Input: tensor"
|
269 |
},
|
270 |
"dragHandle": ".bg-primary",
|
271 |
"height": 200.0,
|
272 |
+
"id": "Input: tensor 3",
|
273 |
"position": {
|
274 |
+
"x": 485.8840220312055,
|
275 |
+
"y": -149.86223034126274
|
276 |
},
|
277 |
"type": "basic",
|
278 |
"width": 200.0
|
|
|
281 |
"data": {
|
282 |
"display": null,
|
283 |
"error": null,
|
284 |
+
"input_metadata": null,
|
285 |
"meta": {
|
286 |
+
"inputs": {
|
287 |
+
"x": {
|
288 |
+
"name": "x",
|
289 |
+
"position": "bottom",
|
290 |
+
"type": {
|
291 |
+
"type": "<class 'inspect._empty'>"
|
292 |
+
}
|
293 |
+
},
|
294 |
"y": {
|
295 |
"name": "y",
|
296 |
+
"position": "bottom",
|
297 |
+
"type": {
|
298 |
+
"type": "<class 'inspect._empty'>"
|
299 |
+
}
|
300 |
+
}
|
301 |
+
},
|
302 |
+
"name": "MSE loss",
|
303 |
+
"outputs": {
|
304 |
+
"output": {
|
305 |
+
"name": "output",
|
306 |
"position": "top",
|
307 |
"type": {
|
308 |
+
"type": "None"
|
309 |
}
|
310 |
}
|
311 |
},
|
312 |
"params": {},
|
313 |
+
"position": {
|
314 |
+
"x": 937.0,
|
315 |
+
"y": 270.0
|
316 |
+
},
|
317 |
"type": "basic"
|
318 |
},
|
319 |
"params": {},
|
320 |
"status": "planned",
|
321 |
+
"title": "MSE loss"
|
322 |
},
|
323 |
"dragHandle": ".bg-primary",
|
324 |
"height": 200.0,
|
325 |
+
"id": "MSE loss 2",
|
326 |
"position": {
|
327 |
+
"x": 309.4422414664647,
|
328 |
+
"y": -552.1056805642488
|
329 |
},
|
330 |
"type": "basic",
|
331 |
"width": 200.0
|
|
|
336 |
"collapsed": null,
|
337 |
"display": null,
|
338 |
"error": null,
|
339 |
+
"input_metadata": null,
|
340 |
"meta": {
|
341 |
"inputs": {
|
342 |
+
"input": {
|
343 |
+
"name": "input",
|
344 |
+
"position": "top",
|
345 |
"type": {
|
346 |
"type": "tensor"
|
347 |
}
|
348 |
}
|
349 |
},
|
350 |
+
"name": "Repeat",
|
351 |
"outputs": {
|
352 |
+
"output": {
|
353 |
+
"name": "output",
|
354 |
+
"position": "bottom",
|
355 |
"type": {
|
356 |
"type": "tensor"
|
357 |
}
|
358 |
}
|
359 |
},
|
360 |
"params": {
|
361 |
+
"same_weights": {
|
362 |
+
"default": false,
|
363 |
+
"name": "same_weights",
|
364 |
"type": {
|
365 |
+
"type": "<class 'bool'>"
|
366 |
+
}
|
367 |
+
},
|
368 |
+
"times": {
|
369 |
+
"default": 1.0,
|
370 |
+
"name": "times",
|
371 |
+
"type": {
|
372 |
+
"type": "<class 'int'>"
|
373 |
}
|
374 |
}
|
375 |
},
|
376 |
"position": {
|
377 |
+
"x": 487.0,
|
378 |
+
"y": 443.0
|
379 |
},
|
380 |
"type": "basic"
|
381 |
},
|
382 |
"params": {
|
383 |
+
"same_weights": false,
|
384 |
+
"times": "2"
|
385 |
},
|
386 |
"status": "planned",
|
387 |
+
"title": "Repeat"
|
388 |
},
|
389 |
"dragHandle": ".bg-primary",
|
390 |
"height": 200.0,
|
391 |
+
"id": "Repeat 1",
|
392 |
"position": {
|
393 |
+
"x": -210.0,
|
394 |
+
"y": -135.0
|
395 |
},
|
396 |
"type": "basic",
|
397 |
"width": 200.0
|
|
|
402 |
"collapsed": null,
|
403 |
"display": null,
|
404 |
"error": null,
|
405 |
+
"input_metadata": null,
|
406 |
"meta": {
|
407 |
"inputs": {
|
408 |
+
"x": {
|
409 |
+
"name": "x",
|
410 |
"position": "bottom",
|
411 |
"type": {
|
412 |
+
"type": "<class 'inspect._empty'>"
|
413 |
}
|
414 |
}
|
415 |
},
|
416 |
+
"name": "Linear",
|
417 |
+
"outputs": {
|
418 |
+
"output": {
|
419 |
+
"name": "output",
|
420 |
+
"position": "top",
|
|
|
421 |
"type": {
|
422 |
+
"type": "None"
|
423 |
}
|
424 |
+
}
|
425 |
+
},
|
426 |
+
"params": {
|
427 |
+
"output_dim": {
|
428 |
+
"default": 1024.0,
|
429 |
+
"name": "output_dim",
|
430 |
"type": {
|
431 |
+
"type": "<class 'int'>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
432 |
}
|
433 |
}
|
434 |
},
|
435 |
"position": {
|
436 |
+
"x": 359.0,
|
437 |
+
"y": 310.0
|
438 |
},
|
439 |
"type": "basic"
|
440 |
},
|
441 |
"params": {
|
442 |
+
"output_dim": "4"
|
|
|
443 |
},
|
444 |
"status": "planned",
|
445 |
+
"title": "Linear"
|
446 |
},
|
447 |
"dragHandle": ".bg-primary",
|
448 |
"height": 200.0,
|
449 |
+
"id": "Linear 1",
|
450 |
"position": {
|
451 |
+
"x": 88.83370222907377,
|
452 |
+
"y": 48.642890099180136
|
453 |
},
|
454 |
"type": "basic",
|
455 |
"width": 200.0
|
examples/Model use
CHANGED
@@ -575,58 +575,58 @@
|
|
575 |
"columns": [
|
576 |
"x",
|
577 |
"y",
|
578 |
-
"
|
579 |
],
|
580 |
"data": [
|
581 |
[
|
582 |
-
"[0.
|
583 |
-
"[1.
|
584 |
-
"[1.
|
585 |
],
|
586 |
[
|
587 |
-
"[0.
|
588 |
-
"[1.
|
589 |
-
"[1.
|
590 |
],
|
591 |
[
|
592 |
-
"[0.
|
593 |
-
"[1.
|
594 |
-
"[1.
|
595 |
],
|
596 |
[
|
597 |
-
"[0.
|
598 |
-
"[1.
|
599 |
-
"[1.
|
600 |
],
|
601 |
[
|
602 |
-
"[0.
|
603 |
-
"[1.
|
604 |
-
"[1.
|
605 |
],
|
606 |
[
|
607 |
-
"[0.
|
608 |
-
"[1.
|
609 |
-
"[1.
|
610 |
],
|
611 |
[
|
612 |
-
"[0.
|
613 |
-
"[1.
|
614 |
-
"[1.
|
615 |
],
|
616 |
[
|
617 |
-
"[0.
|
618 |
-
"[1.
|
619 |
-
"[1.
|
620 |
],
|
621 |
[
|
622 |
-
"[0.
|
623 |
-
"[1.
|
624 |
-
"[1.
|
625 |
],
|
626 |
[
|
627 |
-
"[0.
|
628 |
-
"[1.
|
629 |
-
"[1.
|
630 |
]
|
631 |
]
|
632 |
},
|
@@ -688,13 +688,17 @@
|
|
688 |
"[0.11693293 0.49860179 0.55020827 0.88832849]",
|
689 |
"[1.11693287 1.49860179 1.55020833 1.88832855]"
|
690 |
],
|
|
|
|
|
|
|
|
|
691 |
[
|
692 |
"[0.50272274 0.54912758 0.17663097 0.79070699]",
|
693 |
"[1.50272274 1.54912758 1.17663097 1.79070699]"
|
694 |
],
|
695 |
[
|
696 |
-
"[0.
|
697 |
-
"[1.
|
698 |
],
|
699 |
[
|
700 |
"[0.40167677 0.25953674 0.9407078 0.76308483]",
|
@@ -712,10 +716,18 @@
|
|
712 |
"[0.62569475 0.9881897 0.83639616 0.9828859 ]",
|
713 |
"[1.62569475 1.9881897 1.83639622 1.98288584]"
|
714 |
],
|
|
|
|
|
|
|
|
|
715 |
[
|
716 |
"[0.88776821 0.51636773 0.30333066 0.32230979]",
|
717 |
"[1.88776827 1.51636767 1.30333066 1.32230973]"
|
718 |
],
|
|
|
|
|
|
|
|
|
719 |
[
|
720 |
"[0.48507756 0.80808765 0.77162558 0.47834778]",
|
721 |
"[1.48507762 1.80808759 1.77162552 1.47834778]"
|
@@ -724,10 +736,6 @@
|
|
724 |
"[0.68062544 0.98093534 0.14778823 0.53244978]",
|
725 |
"[1.68062544 1.98093534 1.14778829 1.53244972]"
|
726 |
],
|
727 |
-
[
|
728 |
-
"[0.31518555 0.49643308 0.11509258 0.95458382]",
|
729 |
-
"[1.31518555 1.49643302 1.11509252 1.95458388]"
|
730 |
-
],
|
731 |
[
|
732 |
"[0.79121011 0.54161114 0.69369799 0.1520769 ]",
|
733 |
"[1.79121017 1.54161119 1.69369793 1.15207696]"
|
@@ -744,10 +752,6 @@
|
|
744 |
"[0.94516498 0.08422136 0.5608117 0.07652664]",
|
745 |
"[1.94516492 1.08422136 1.56081176 1.07652664]"
|
746 |
],
|
747 |
-
[
|
748 |
-
"[0.26661873 0.45946234 0.13510543 0.81294441]",
|
749 |
-
"[1.26661873 1.4594624 1.13510537 1.81294441]"
|
750 |
-
],
|
751 |
[
|
752 |
"[0.30754459 0.77694583 0.09278506 0.38326019]",
|
753 |
"[1.30754459 1.77694583 1.09278512 1.38326025]"
|
@@ -804,10 +808,6 @@
|
|
804 |
"[0.73217702 0.65233225 0.44077861 0.33837909]",
|
805 |
"[1.73217702 1.65233231 1.44077861 1.33837914]"
|
806 |
],
|
807 |
-
[
|
808 |
-
"[0.34084332 0.73018837 0.54168713 0.91440833]",
|
809 |
-
"[1.34084332 1.73018837 1.54168713 1.91440833]"
|
810 |
-
],
|
811 |
[
|
812 |
"[0.60110539 0.3618983 0.32342511 0.98672163]",
|
813 |
"[1.60110545 1.3618983 1.32342505 1.98672163]"
|
@@ -816,6 +816,10 @@
|
|
816 |
"[0.77427191 0.21829212 0.12769502 0.74303615]",
|
817 |
"[1.77427197 1.21829212 1.12769508 1.74303615]"
|
818 |
],
|
|
|
|
|
|
|
|
|
819 |
[
|
820 |
"[0.59812403 0.78395379 0.0291847 0.81814629]",
|
821 |
"[1.59812403 1.78395379 1.0291847 1.81814623]"
|
@@ -840,18 +844,6 @@
|
|
840 |
"[0.95928186 0.84273899 0.71514636 0.38619852]",
|
841 |
"[1.95928192 1.84273899 1.7151463 1.38619852]"
|
842 |
],
|
843 |
-
[
|
844 |
-
"[0.32565445 0.90939188 0.07488042 0.13730896]",
|
845 |
-
"[1.32565451 1.90939188 1.07488036 1.13730896]"
|
846 |
-
],
|
847 |
-
[
|
848 |
-
"[0.9829582 0.59269661 0.40120947 0.95487177]",
|
849 |
-
"[1.9829582 1.59269667 1.40120947 1.95487177]"
|
850 |
-
],
|
851 |
-
[
|
852 |
-
"[0.79905868 0.89367443 0.75429088 0.3190186 ]",
|
853 |
-
"[1.79905868 1.89367437 1.75429082 1.3190186 ]"
|
854 |
-
],
|
855 |
[
|
856 |
"[0.54914117 0.03810108 0.87531954 0.73044223]",
|
857 |
"[1.54914117 1.03810108 1.87531948 1.73044229]"
|
@@ -876,10 +868,6 @@
|
|
876 |
"[0.60075855 0.12234765 0.00614399 0.30560958]",
|
877 |
"[1.60075855 1.12234759 1.00614405 1.30560958]"
|
878 |
],
|
879 |
-
[
|
880 |
-
"[0.39147133 0.29854035 0.84663737 0.58175623]",
|
881 |
-
"[1.39147139 1.29854035 1.84663737 1.58175623]"
|
882 |
-
],
|
883 |
[
|
884 |
"[0.02162331 0.81861657 0.92468154 0.07808572]",
|
885 |
"[1.02162337 1.81861663 1.92468154 1.07808566]"
|
@@ -924,6 +912,10 @@
|
|
924 |
"[0.59492421 0.90274489 0.38069052 0.46101224]",
|
925 |
"[1.59492421 1.90274489 1.38069057 1.46101224]"
|
926 |
],
|
|
|
|
|
|
|
|
|
927 |
[
|
928 |
"[0.12024075 0.21342516 0.56858408 0.58644271]",
|
929 |
"[1.12024069 1.21342516 1.56858408 1.58644271]"
|
@@ -932,6 +924,14 @@
|
|
932 |
"[0.91730917 0.22574073 0.09591609 0.33056474]",
|
933 |
"[1.91730917 1.22574067 1.09591603 1.33056474]"
|
934 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
935 |
[
|
936 |
"[0.63235509 0.70352674 0.96188956 0.46240485]",
|
937 |
"[1.63235509 1.70352674 1.96188951 1.46240485]"
|
@@ -948,10 +948,6 @@
|
|
948 |
"[0.40234613 0.54987347 0.49542785 0.54153186]",
|
949 |
"[1.40234613 1.54987347 1.49542785 1.5415318 ]"
|
950 |
],
|
951 |
-
[
|
952 |
-
"[0.80893755 0.92237449 0.88346356 0.93164903]",
|
953 |
-
"[1.80893755 1.92237449 1.88346362 1.93164897]"
|
954 |
-
],
|
955 |
[
|
956 |
"[0.12858278 0.09930819 0.83222693 0.72485673]",
|
957 |
"[1.12858272 1.09930825 1.83222699 1.72485673]"
|
@@ -981,13 +977,17 @@
|
|
981 |
"[1.28942847 1.05601001 1.33039129 1.27781558]"
|
982 |
],
|
983 |
[
|
984 |
-
"[0.
|
985 |
-
"[1.
|
986 |
],
|
987 |
[
|
988 |
"[0.47870928 0.17129105 0.27300501 0.20634609]",
|
989 |
"[1.47870922 1.17129111 1.27300501 1.20634604]"
|
990 |
],
|
|
|
|
|
|
|
|
|
991 |
[
|
992 |
"[0.87608397 0.93200487 0.80169648 0.37758952]",
|
993 |
"[1.87608397 1.93200493 1.80169654 1.37758946]"
|
@@ -1000,7 +1000,7 @@
|
|
1000 |
}
|
1001 |
},
|
1002 |
"other": {
|
1003 |
-
"model": "ModelConfig(model=Sequential(\n (0) - Linear(in_features=4, out_features=4, bias=True):
|
1004 |
},
|
1005 |
"relations": []
|
1006 |
},
|
@@ -1016,7 +1016,7 @@
|
|
1016 |
},
|
1017 |
"df_test": {
|
1018 |
"columns": [
|
1019 |
-
"
|
1020 |
"x",
|
1021 |
"y"
|
1022 |
]
|
@@ -1032,14 +1032,14 @@
|
|
1032 |
"model": {
|
1033 |
"model": {
|
1034 |
"inputs": [
|
1035 |
-
"
|
1036 |
],
|
1037 |
"loss_inputs": [
|
1038 |
-
"
|
1039 |
-
"
|
1040 |
],
|
1041 |
"outputs": [
|
1042 |
-
"
|
1043 |
],
|
1044 |
"trained": true
|
1045 |
},
|
@@ -1207,14 +1207,14 @@
|
|
1207 |
"model": {
|
1208 |
"model": {
|
1209 |
"inputs": [
|
1210 |
-
"
|
1211 |
],
|
1212 |
"loss_inputs": [
|
1213 |
-
"
|
1214 |
-
"
|
1215 |
],
|
1216 |
"outputs": [
|
1217 |
-
"
|
1218 |
],
|
1219 |
"trained": false
|
1220 |
},
|
@@ -1270,8 +1270,8 @@
|
|
1270 |
"type": "basic"
|
1271 |
},
|
1272 |
"params": {
|
1273 |
-
"epochs": "
|
1274 |
-
"input_mapping": "{\"map\":{\"
|
1275 |
"model_name": "model"
|
1276 |
},
|
1277 |
"status": "done",
|
@@ -1304,7 +1304,6 @@
|
|
1304 |
},
|
1305 |
"df_test": {
|
1306 |
"columns": [
|
1307 |
-
"predicted",
|
1308 |
"x",
|
1309 |
"y"
|
1310 |
]
|
@@ -1320,14 +1319,14 @@
|
|
1320 |
"model": {
|
1321 |
"model": {
|
1322 |
"inputs": [
|
1323 |
-
"
|
1324 |
],
|
1325 |
"loss_inputs": [
|
1326 |
-
"
|
1327 |
-
"
|
1328 |
],
|
1329 |
"outputs": [
|
1330 |
-
"
|
1331 |
],
|
1332 |
"trained": true
|
1333 |
},
|
@@ -1383,15 +1382,15 @@
|
|
1383 |
"type": "basic"
|
1384 |
},
|
1385 |
"params": {
|
1386 |
-
"input_mapping": "{\"map\":{\"
|
1387 |
"model_name": "model",
|
1388 |
-
"output_mapping": "{\"map\":{\"
|
1389 |
},
|
1390 |
"status": "done",
|
1391 |
"title": "Model inference"
|
1392 |
},
|
1393 |
"dragHandle": ".bg-primary",
|
1394 |
-
"height":
|
1395 |
"id": "Model inference 1",
|
1396 |
"position": {
|
1397 |
"x": 2181.718373860645,
|
|
|
575 |
"columns": [
|
576 |
"x",
|
577 |
"y",
|
578 |
+
"pred"
|
579 |
],
|
580 |
"data": [
|
581 |
[
|
582 |
+
"[0.19908059 0.17570406 0.51475513 0.1893943 ]",
|
583 |
+
"[1.19908059 1.175704 1.51475513 1.18939424]",
|
584 |
+
"[1.560641884803772, 1.5941988229751587, 1.5775359869003296, 1.4935821294784546]"
|
585 |
],
|
586 |
[
|
587 |
+
"[0.43681622 0.74680805 0.83598751 0.12414402]",
|
588 |
+
"[1.43681622 1.74680805 1.83598757 1.12414408]",
|
589 |
+
"[1.5766589641571045, 1.7117265462875366, 1.7645087242126465, 1.3384637832641602]"
|
590 |
],
|
591 |
[
|
592 |
+
"[0.9829582 0.59269661 0.40120947 0.95487177]",
|
593 |
+
"[1.9829582 1.59269667 1.40120947 1.95487177]",
|
594 |
+
"[1.5375217199325562, 1.4159281253814697, 1.2972962856292725, 1.7269455194473267]"
|
595 |
],
|
596 |
[
|
597 |
+
"[0.32565445 0.90939188 0.07488042 0.13730896]",
|
598 |
+
"[1.32565451 1.90939188 1.07488036 1.13730896]",
|
599 |
+
"[1.562728762626648, 1.6061222553253174, 1.597141146659851, 1.4772177934646606]"
|
600 |
],
|
601 |
[
|
602 |
+
"[0.31518555 0.49643308 0.11509258 0.95458382]",
|
603 |
+
"[1.31518555 1.49643302 1.11509252 1.95458388]",
|
604 |
+
"[1.528311848640442, 1.3380011320114136, 1.171952247619629, 1.8305948972702026]"
|
605 |
],
|
606 |
[
|
607 |
+
"[0.79905868 0.89367443 0.75429088 0.3190186 ]",
|
608 |
+
"[1.79905868 1.89367437 1.75429082 1.3190186 ]",
|
609 |
+
"[1.5757312774658203, 1.7105278968811035, 1.7636661529541016, 1.3394038677215576]"
|
610 |
],
|
611 |
[
|
612 |
+
"[0.80893755 0.92237449 0.88346356 0.93164903]",
|
613 |
+
"[1.80893755 1.92237449 1.88346362 1.93164897]",
|
614 |
+
"[1.562132716178894, 1.6031286716461182, 1.593322992324829, 1.4810831546783447]"
|
615 |
],
|
616 |
[
|
617 |
+
"[0.26661873 0.45946234 0.13510543 0.81294441]",
|
618 |
+
"[1.26661873 1.4594624 1.13510537 1.81294441]",
|
619 |
+
"[1.533058762550354, 1.3753284215927124, 1.230975866317749, 1.7815138101577759]"
|
620 |
],
|
621 |
[
|
622 |
+
"[0.39147133 0.29854035 0.84663737 0.58175623]",
|
623 |
+
"[1.39147139 1.29854035 1.84663737 1.58175623]",
|
624 |
+
"[1.5607244968414307, 1.5942375659942627, 1.5779708623886108, 1.4935153722763062]"
|
625 |
],
|
626 |
[
|
627 |
+
"[0.34084332 0.73018837 0.54168713 0.91440833]",
|
628 |
+
"[1.34084332 1.73018837 1.54168713 1.91440833]",
|
629 |
+
"[1.5488454103469849, 1.4963982105255127, 1.422922968864441, 1.622254490852356]"
|
630 |
]
|
631 |
]
|
632 |
},
|
|
|
688 |
"[0.11693293 0.49860179 0.55020827 0.88832849]",
|
689 |
"[1.11693287 1.49860179 1.55020833 1.88832855]"
|
690 |
],
|
691 |
+
[
|
692 |
+
"[0.48959708 0.48549271 0.32688856 0.356677 ]",
|
693 |
+
"[1.48959708 1.48549271 1.32688856 1.35667706]"
|
694 |
+
],
|
695 |
[
|
696 |
"[0.50272274 0.54912758 0.17663097 0.79070699]",
|
697 |
"[1.50272274 1.54912758 1.17663097 1.79070699]"
|
698 |
],
|
699 |
[
|
700 |
+
"[0.04508126 0.76880038 0.80721325 0.62542385]",
|
701 |
+
"[1.04508126 1.76880038 1.80721331 1.62542391]"
|
702 |
],
|
703 |
[
|
704 |
"[0.40167677 0.25953674 0.9407078 0.76308483]",
|
|
|
716 |
"[0.62569475 0.9881897 0.83639616 0.9828859 ]",
|
717 |
"[1.62569475 1.9881897 1.83639622 1.98288584]"
|
718 |
],
|
719 |
+
[
|
720 |
+
"[0.56922203 0.98222166 0.76851749 0.28615737]",
|
721 |
+
"[1.56922197 1.9822216 1.76851749 1.28615737]"
|
722 |
+
],
|
723 |
[
|
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]"
|
|
|
736 |
"[0.68062544 0.98093534 0.14778823 0.53244978]",
|
737 |
"[1.68062544 1.98093534 1.14778829 1.53244972]"
|
738 |
],
|
|
|
|
|
|
|
|
|
739 |
[
|
740 |
"[0.79121011 0.54161114 0.69369799 0.1520769 ]",
|
741 |
"[1.79121017 1.54161119 1.69369793 1.15207696]"
|
|
|
752 |
"[0.94516498 0.08422136 0.5608117 0.07652664]",
|
753 |
"[1.94516492 1.08422136 1.56081176 1.07652664]"
|
754 |
],
|
|
|
|
|
|
|
|
|
755 |
[
|
756 |
"[0.30754459 0.77694583 0.09278506 0.38326019]",
|
757 |
"[1.30754459 1.77694583 1.09278512 1.38326025]"
|
|
|
808 |
"[0.73217702 0.65233225 0.44077861 0.33837909]",
|
809 |
"[1.73217702 1.65233231 1.44077861 1.33837914]"
|
810 |
],
|
|
|
|
|
|
|
|
|
811 |
[
|
812 |
"[0.60110539 0.3618983 0.32342511 0.98672163]",
|
813 |
"[1.60110545 1.3618983 1.32342505 1.98672163]"
|
|
|
816 |
"[0.77427191 0.21829212 0.12769502 0.74303615]",
|
817 |
"[1.77427197 1.21829212 1.12769508 1.74303615]"
|
818 |
],
|
819 |
+
[
|
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]"
|
|
|
844 |
"[0.95928186 0.84273899 0.71514636 0.38619852]",
|
845 |
"[1.95928192 1.84273899 1.7151463 1.38619852]"
|
846 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
847 |
[
|
848 |
"[0.54914117 0.03810108 0.87531954 0.73044223]",
|
849 |
"[1.54914117 1.03810108 1.87531948 1.73044229]"
|
|
|
868 |
"[0.60075855 0.12234765 0.00614399 0.30560958]",
|
869 |
"[1.60075855 1.12234759 1.00614405 1.30560958]"
|
870 |
],
|
|
|
|
|
|
|
|
|
871 |
[
|
872 |
"[0.02162331 0.81861657 0.92468154 0.07808572]",
|
873 |
"[1.02162337 1.81861663 1.92468154 1.07808566]"
|
|
|
912 |
"[0.59492421 0.90274489 0.38069052 0.46101224]",
|
913 |
"[1.59492421 1.90274489 1.38069057 1.46101224]"
|
914 |
],
|
915 |
+
[
|
916 |
+
"[0.15064228 0.03198934 0.25754827 0.51484001]",
|
917 |
+
"[1.15064228 1.03198934 1.25754833 1.51484001]"
|
918 |
+
],
|
919 |
[
|
920 |
"[0.12024075 0.21342516 0.56858408 0.58644271]",
|
921 |
"[1.12024069 1.21342516 1.56858408 1.58644271]"
|
|
|
924 |
"[0.91730917 0.22574073 0.09591609 0.33056474]",
|
925 |
"[1.91730917 1.22574067 1.09591603 1.33056474]"
|
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]",
|
933 |
+
"[1.60324764 1.83361363 1.18538666 1.19108021]"
|
934 |
+
],
|
935 |
[
|
936 |
"[0.63235509 0.70352674 0.96188956 0.46240485]",
|
937 |
"[1.63235509 1.70352674 1.96188951 1.46240485]"
|
|
|
948 |
"[0.40234613 0.54987347 0.49542785 0.54153186]",
|
949 |
"[1.40234613 1.54987347 1.49542785 1.5415318 ]"
|
950 |
],
|
|
|
|
|
|
|
|
|
951 |
[
|
952 |
"[0.12858278 0.09930819 0.83222693 0.72485673]",
|
953 |
"[1.12858272 1.09930825 1.83222699 1.72485673]"
|
|
|
977 |
"[1.28942847 1.05601001 1.33039129 1.27781558]"
|
978 |
],
|
979 |
[
|
980 |
+
"[0.68094063 0.45189077 0.22661722 0.37354094]",
|
981 |
+
"[1.68094063 1.45189071 1.22661722 1.37354088]"
|
982 |
],
|
983 |
[
|
984 |
"[0.47870928 0.17129105 0.27300501 0.20634609]",
|
985 |
"[1.47870922 1.17129111 1.27300501 1.20634604]"
|
986 |
],
|
987 |
+
[
|
988 |
+
"[0.72795159 0.79317838 0.27832931 0.96576637]",
|
989 |
+
"[1.72795153 1.79317832 1.27832937 1.96576643]"
|
990 |
+
],
|
991 |
[
|
992 |
"[0.87608397 0.93200487 0.80169648 0.37758952]",
|
993 |
"[1.87608397 1.93200493 1.80169654 1.37758946]"
|
|
|
1000 |
}
|
1001 |
},
|
1002 |
"other": {
|
1003 |
+
"model": "ModelConfig(model=Sequential(\n (0) - Identity(): Input__tensor_1_x -> START_Repeat_1_output\n (1) - Linear(in_features=4, out_features=4, bias=True): START_Repeat_1_output -> Linear_1_output\n (2) - <function leaky_relu at 0x762d1f82c680>: Linear_1_output -> Activation_1_output\n (3) - Identity(): Activation_1_output -> START_Repeat_1_output\n (4) - Linear(in_features=4, out_features=4, bias=True): START_Repeat_1_output -> Linear_1_output\n (5) - <function leaky_relu at 0x762d1f82c680>: Linear_1_output -> Activation_1_output\n (6) - Identity(): Activation_1_output -> END_Repeat_1_output\n (7) - Identity(): END_Repeat_1_output -> END_Repeat_1_output\n), model_inputs=['Input__tensor_1_x'], model_outputs=['END_Repeat_1_output'], loss_inputs=['END_Repeat_1_output', 'Input__tensor_3_x'], loss=Sequential(\n (0) - <function mse_loss at 0x762d1f82e160>: END_Repeat_1_output, Input__tensor_3_x -> MSE_loss_2_output\n (1) - Identity(): MSE_loss_2_output -> loss\n), optimizer_parameters={'lr': 0.1, 'type': <OptionsFor_type.SGD: 4>}, 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='Model definition', trained=True)"
|
1004 |
},
|
1005 |
"relations": []
|
1006 |
},
|
|
|
1016 |
},
|
1017 |
"df_test": {
|
1018 |
"columns": [
|
1019 |
+
"pred",
|
1020 |
"x",
|
1021 |
"y"
|
1022 |
]
|
|
|
1032 |
"model": {
|
1033 |
"model": {
|
1034 |
"inputs": [
|
1035 |
+
"Input__tensor_1_x"
|
1036 |
],
|
1037 |
"loss_inputs": [
|
1038 |
+
"END_Repeat_1_output",
|
1039 |
+
"Input__tensor_3_x"
|
1040 |
],
|
1041 |
"outputs": [
|
1042 |
+
"END_Repeat_1_output"
|
1043 |
],
|
1044 |
"trained": true
|
1045 |
},
|
|
|
1207 |
"model": {
|
1208 |
"model": {
|
1209 |
"inputs": [
|
1210 |
+
"Input__tensor_1_x"
|
1211 |
],
|
1212 |
"loss_inputs": [
|
1213 |
+
"END_Repeat_1_output",
|
1214 |
+
"Input__tensor_3_x"
|
1215 |
],
|
1216 |
"outputs": [
|
1217 |
+
"END_Repeat_1_output"
|
1218 |
],
|
1219 |
"trained": false
|
1220 |
},
|
|
|
1270 |
"type": "basic"
|
1271 |
},
|
1272 |
"params": {
|
1273 |
+
"epochs": "1003",
|
1274 |
+
"input_mapping": "{\"map\":{\"Input__tensor_1_x\":{\"df\":\"df_train\",\"column\":\"x\"},\"Input__tensor_3_x\":{\"df\":\"df_train\",\"column\":\"y\"}}}",
|
1275 |
"model_name": "model"
|
1276 |
},
|
1277 |
"status": "done",
|
|
|
1304 |
},
|
1305 |
"df_test": {
|
1306 |
"columns": [
|
|
|
1307 |
"x",
|
1308 |
"y"
|
1309 |
]
|
|
|
1319 |
"model": {
|
1320 |
"model": {
|
1321 |
"inputs": [
|
1322 |
+
"Input__tensor_1_x"
|
1323 |
],
|
1324 |
"loss_inputs": [
|
1325 |
+
"END_Repeat_1_output",
|
1326 |
+
"Input__tensor_3_x"
|
1327 |
],
|
1328 |
"outputs": [
|
1329 |
+
"END_Repeat_1_output"
|
1330 |
],
|
1331 |
"trained": true
|
1332 |
},
|
|
|
1382 |
"type": "basic"
|
1383 |
},
|
1384 |
"params": {
|
1385 |
+
"input_mapping": "{\"map\":{\"Input__tensor_1_x\":{\"df\":\"df_test\",\"column\":\"x\"}}}",
|
1386 |
"model_name": "model",
|
1387 |
+
"output_mapping": "{\"map\":{\"END_Repeat_1_output\":{\"df\":\"df_test\",\"column\":\"pred\"}}}"
|
1388 |
},
|
1389 |
"status": "done",
|
1390 |
"title": "Model inference"
|
1391 |
},
|
1392 |
"dragHandle": ".bg-primary",
|
1393 |
+
"height": 650.0,
|
1394 |
"id": "Model inference 1",
|
1395 |
"position": {
|
1396 |
"x": 2181.718373860645,
|
lynxkite-app/web/src/workspace/nodes/NodeParameter.tsx
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
// @ts-ignore
|
2 |
import ArrowsHorizontal from "~icons/tabler/arrows-horizontal.jsx";
|
3 |
|
@@ -14,13 +15,16 @@ function ParamName({ name }: { name: string }) {
|
|
14 |
function Input({
|
15 |
value,
|
16 |
onChange,
|
|
|
17 |
}: {
|
18 |
value: string;
|
19 |
onChange: (value: string, options?: { delay: number }) => void;
|
|
|
20 |
}) {
|
21 |
return (
|
22 |
<input
|
23 |
className="input input-bordered w-full"
|
|
|
24 |
value={value || ""}
|
25 |
onChange={(evt) => onChange(evt.currentTarget.value, { delay: 2 })}
|
26 |
onBlur={(evt) => onChange(evt.currentTarget.value, { delay: 0 })}
|
@@ -29,6 +33,13 @@ function Input({
|
|
29 |
);
|
30 |
}
|
31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
function getModelBindings(
|
33 |
data: any,
|
34 |
variant: "training input" | "inference input" | "output",
|
@@ -71,11 +82,16 @@ function parseJsonOrEmpty(json: string): object {
|
|
71 |
}
|
72 |
|
73 |
function ModelMapping({ value, onChange, data, variant }: any) {
|
|
|
|
|
|
|
|
|
74 |
const v: any = parseJsonOrEmpty(value);
|
75 |
v.map ??= {};
|
76 |
const dfs: { [df: string]: string[] } = {};
|
77 |
const inputs = data?.input_metadata?.value ?? data?.input_metadata ?? [];
|
78 |
for (const input of inputs) {
|
|
|
79 |
const dataframes = input.dataframes as {
|
80 |
[df: string]: { columns: string[] };
|
81 |
};
|
@@ -84,6 +100,17 @@ function ModelMapping({ value, onChange, data, variant }: any) {
|
|
84 |
}
|
85 |
}
|
86 |
const bindings = getModelBindings(data, variant);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
return (
|
88 |
<table className="model-mapping-param">
|
89 |
<tbody>
|
@@ -98,21 +125,10 @@ function ModelMapping({ value, onChange, data, variant }: any) {
|
|
98 |
<select
|
99 |
className="select select-ghost"
|
100 |
value={v.map?.[binding]?.df}
|
101 |
-
|
102 |
-
|
103 |
-
if (df === "") {
|
104 |
-
const map = { ...v.map, [binding]: undefined };
|
105 |
-
onChange(JSON.stringify({ map }));
|
106 |
-
} else {
|
107 |
-
const columnSpec = {
|
108 |
-
column: dfs[df][0],
|
109 |
-
...(v.map?.[binding] || {}),
|
110 |
-
df,
|
111 |
-
};
|
112 |
-
const map = { ...v.map, [binding]: columnSpec };
|
113 |
-
onChange(JSON.stringify({ map }));
|
114 |
-
}
|
115 |
}}
|
|
|
116 |
>
|
117 |
<option key="" value="" />
|
118 |
{Object.keys(dfs).map((df: string) => (
|
@@ -125,13 +141,16 @@ function ModelMapping({ value, onChange, data, variant }: any) {
|
|
125 |
<td>
|
126 |
{variant === "output" ? (
|
127 |
<Input
|
|
|
|
|
|
|
128 |
value={v.map?.[binding]?.column}
|
129 |
onChange={(column, options) => {
|
130 |
-
const
|
131 |
-
|
132 |
-
|
133 |
-
};
|
134 |
-
|
135 |
onChange(JSON.stringify({ map }), options);
|
136 |
}}
|
137 |
/>
|
@@ -139,16 +158,12 @@ function ModelMapping({ value, onChange, data, variant }: any) {
|
|
139 |
<select
|
140 |
className="select select-ghost"
|
141 |
value={v.map?.[binding]?.column}
|
142 |
-
|
143 |
-
|
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]?.map((col: string) => (
|
153 |
<option key={col} value={col}>
|
154 |
{col}
|
|
|
1 |
+
import { useRef } from "react";
|
2 |
// @ts-ignore
|
3 |
import ArrowsHorizontal from "~icons/tabler/arrows-horizontal.jsx";
|
4 |
|
|
|
15 |
function Input({
|
16 |
value,
|
17 |
onChange,
|
18 |
+
inputRef,
|
19 |
}: {
|
20 |
value: string;
|
21 |
onChange: (value: string, options?: { delay: number }) => void;
|
22 |
+
inputRef?: React.Ref<HTMLInputElement>;
|
23 |
}) {
|
24 |
return (
|
25 |
<input
|
26 |
className="input input-bordered w-full"
|
27 |
+
ref={inputRef}
|
28 |
value={value || ""}
|
29 |
onChange={(evt) => onChange(evt.currentTarget.value, { delay: 2 })}
|
30 |
onBlur={(evt) => onChange(evt.currentTarget.value, { delay: 0 })}
|
|
|
33 |
);
|
34 |
}
|
35 |
|
36 |
+
type Bindings = {
|
37 |
+
[key: string]: {
|
38 |
+
df: string;
|
39 |
+
column: string;
|
40 |
+
};
|
41 |
+
};
|
42 |
+
|
43 |
function getModelBindings(
|
44 |
data: any,
|
45 |
variant: "training input" | "inference input" | "output",
|
|
|
82 |
}
|
83 |
|
84 |
function ModelMapping({ value, onChange, data, variant }: any) {
|
85 |
+
const dfsRef = useRef({} as { [binding: string]: HTMLSelectElement | null });
|
86 |
+
const columnsRef = useRef(
|
87 |
+
{} as { [binding: string]: HTMLSelectElement | HTMLInputElement | null },
|
88 |
+
);
|
89 |
const v: any = parseJsonOrEmpty(value);
|
90 |
v.map ??= {};
|
91 |
const dfs: { [df: string]: string[] } = {};
|
92 |
const inputs = data?.input_metadata?.value ?? data?.input_metadata ?? [];
|
93 |
for (const input of inputs) {
|
94 |
+
if (!input.dataframes) continue;
|
95 |
const dataframes = input.dataframes as {
|
96 |
[df: string]: { columns: string[] };
|
97 |
};
|
|
|
100 |
}
|
101 |
}
|
102 |
const bindings = getModelBindings(data, variant);
|
103 |
+
function getMap() {
|
104 |
+
const map: Bindings = {};
|
105 |
+
for (const binding of bindings) {
|
106 |
+
const df = dfsRef.current[binding]?.value ?? "";
|
107 |
+
const column = columnsRef.current[binding]?.value ?? "";
|
108 |
+
if (df.length || column.length) {
|
109 |
+
map[binding] = { df, column };
|
110 |
+
}
|
111 |
+
}
|
112 |
+
return map;
|
113 |
+
}
|
114 |
return (
|
115 |
<table className="model-mapping-param">
|
116 |
<tbody>
|
|
|
125 |
<select
|
126 |
className="select select-ghost"
|
127 |
value={v.map?.[binding]?.df}
|
128 |
+
ref={(el) => {
|
129 |
+
dfsRef.current[binding] = el;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
}}
|
131 |
+
onChange={() => onChange(JSON.stringify({ map: getMap() }))}
|
132 |
>
|
133 |
<option key="" value="" />
|
134 |
{Object.keys(dfs).map((df: string) => (
|
|
|
141 |
<td>
|
142 |
{variant === "output" ? (
|
143 |
<Input
|
144 |
+
inputRef={(el) => {
|
145 |
+
columnsRef.current[binding] = el;
|
146 |
+
}}
|
147 |
value={v.map?.[binding]?.column}
|
148 |
onChange={(column, options) => {
|
149 |
+
const map = getMap();
|
150 |
+
// At this point the <input> has not been updated yet. We use the value from the event.
|
151 |
+
const df = dfsRef.current[binding]?.value ?? "";
|
152 |
+
map[binding] ??= { df, column };
|
153 |
+
map[binding].column = column;
|
154 |
onChange(JSON.stringify({ map }), options);
|
155 |
}}
|
156 |
/>
|
|
|
158 |
<select
|
159 |
className="select select-ghost"
|
160 |
value={v.map?.[binding]?.column}
|
161 |
+
ref={(el) => {
|
162 |
+
columnsRef.current[binding] = el;
|
|
|
|
|
|
|
|
|
|
|
|
|
163 |
}}
|
164 |
+
onChange={() => onChange(JSON.stringify({ map: getMap() }))}
|
165 |
>
|
166 |
+
<option key="" value="" />
|
167 |
{dfs[v.map?.[binding]?.df]?.map((col: string) => (
|
168 |
<option key={col} value={col}>
|
169 |
{col}
|
lynxkite-core/src/lynxkite/core/ops.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
"""API for implementing LynxKite operations."""
|
2 |
|
3 |
from __future__ import annotations
|
|
|
4 |
import enum
|
5 |
import functools
|
6 |
import inspect
|
@@ -13,7 +14,7 @@ from typing_extensions import Annotated
|
|
13 |
if typing.TYPE_CHECKING:
|
14 |
from . import workspace
|
15 |
|
16 |
-
CATALOGS = {}
|
17 |
EXECUTORS = {}
|
18 |
|
19 |
typeof = type # We have some arguments called "type".
|
@@ -297,3 +298,13 @@ def op_registration(env: str):
|
|
297 |
def passive_op_registration(env: str):
|
298 |
"""Returns a function that can be used to register operations without associated code."""
|
299 |
return functools.partial(register_passive_op, env)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
"""API for implementing LynxKite operations."""
|
2 |
|
3 |
from __future__ import annotations
|
4 |
+
import asyncio
|
5 |
import enum
|
6 |
import functools
|
7 |
import inspect
|
|
|
14 |
if typing.TYPE_CHECKING:
|
15 |
from . import workspace
|
16 |
|
17 |
+
CATALOGS: dict[str, dict[str, "Op"]] = {}
|
18 |
EXECUTORS = {}
|
19 |
|
20 |
typeof = type # We have some arguments called "type".
|
|
|
298 |
def passive_op_registration(env: str):
|
299 |
"""Returns a function that can be used to register operations without associated code."""
|
300 |
return functools.partial(register_passive_op, env)
|
301 |
+
|
302 |
+
|
303 |
+
def slow(func):
|
304 |
+
"""Decorator for slow, blocking operations. Turns them into separate threads."""
|
305 |
+
|
306 |
+
@functools.wraps(func)
|
307 |
+
async def wrapper(*args, **kwargs):
|
308 |
+
return await asyncio.to_thread(func, *args, **kwargs)
|
309 |
+
|
310 |
+
return wrapper
|
lynxkite-graph-analytics/src/lynxkite_graph_analytics/core.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
"""Graph analytics executor and data types."""
|
2 |
|
|
|
3 |
import os
|
4 |
from lynxkite.core import ops, workspace
|
5 |
import dataclasses
|
@@ -177,10 +178,16 @@ async def execute(ws: workspace.Workspace):
|
|
177 |
# All inputs for this node are ready, we can compute the output.
|
178 |
todo.remove(id)
|
179 |
progress = True
|
180 |
-
_execute_node(node, ws, catalog, outputs)
|
181 |
|
182 |
|
183 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
184 |
params = {**node.data.params}
|
185 |
op = catalog.get(node.data.title)
|
186 |
if not op:
|
@@ -214,6 +221,7 @@ def _execute_node(node, ws, catalog, outputs):
|
|
214 |
# Execute op.
|
215 |
try:
|
216 |
result = op(*inputs, **params)
|
|
|
217 |
except Exception as e:
|
218 |
if os.environ.get("LYNXKITE_LOG_OP_ERRORS"):
|
219 |
traceback.print_exc()
|
|
|
1 |
"""Graph analytics executor and data types."""
|
2 |
|
3 |
+
import inspect
|
4 |
import os
|
5 |
from lynxkite.core import ops, workspace
|
6 |
import dataclasses
|
|
|
178 |
# All inputs for this node are ready, we can compute the output.
|
179 |
todo.remove(id)
|
180 |
progress = True
|
181 |
+
await _execute_node(node, ws, catalog, outputs)
|
182 |
|
183 |
|
184 |
+
async def await_if_needed(obj):
|
185 |
+
if inspect.isawaitable(obj):
|
186 |
+
obj = await obj
|
187 |
+
return obj
|
188 |
+
|
189 |
+
|
190 |
+
async def _execute_node(node, ws, catalog, outputs):
|
191 |
params = {**node.data.params}
|
192 |
op = catalog.get(node.data.title)
|
193 |
if not op:
|
|
|
221 |
# Execute op.
|
222 |
try:
|
223 |
result = op(*inputs, **params)
|
224 |
+
result.output = await await_if_needed(result.output)
|
225 |
except Exception as e:
|
226 |
if os.environ.get("LYNXKITE_LOG_OP_ERRORS"):
|
227 |
traceback.print_exc()
|
lynxkite-graph-analytics/src/lynxkite_graph_analytics/lynxkite_ops.py
CHANGED
@@ -347,7 +347,7 @@ def define_model(
|
|
347 |
assert model_workspace, "Model workspace is unset."
|
348 |
ws = load_ws(model_workspace)
|
349 |
# Build the model without inputs, to get its interface.
|
350 |
-
m = pytorch_model_ops.build_model(ws
|
351 |
m.source_workspace = model_workspace
|
352 |
bundle = bundle.copy()
|
353 |
bundle.other[save_as] = m
|
@@ -369,6 +369,7 @@ class ModelOutputMapping(pytorch_model_ops.ModelMapping):
|
|
369 |
|
370 |
|
371 |
@op("Train model")
|
|
|
372 |
def train_model(
|
373 |
bundle: core.Bundle,
|
374 |
*,
|
@@ -379,14 +380,12 @@ def train_model(
|
|
379 |
"""Trains the selected model on the selected dataset. Most training parameters are set in the model definition."""
|
380 |
m = bundle.other[model_name].copy()
|
381 |
inputs = pytorch_model_ops.to_tensors(bundle, input_mapping)
|
382 |
-
if not m.trained and m.source_workspace:
|
383 |
-
# Rebuild the model for the correct inputs.
|
384 |
-
ws = load_ws(m.source_workspace)
|
385 |
-
m = pytorch_model_ops.build_model(ws, inputs)
|
386 |
t = tqdm(range(epochs), desc="Training model")
|
|
|
387 |
for _ in t:
|
388 |
loss = m.train(inputs)
|
389 |
t.set_postfix({"loss": loss})
|
|
|
390 |
m.trained = True
|
391 |
bundle = bundle.copy()
|
392 |
bundle.other[model_name] = m
|
@@ -394,6 +393,7 @@ def train_model(
|
|
394 |
|
395 |
|
396 |
@op("Model inference")
|
|
|
397 |
def model_inference(
|
398 |
bundle: core.Bundle,
|
399 |
*,
|
@@ -409,7 +409,13 @@ def model_inference(
|
|
409 |
inputs = pytorch_model_ops.to_tensors(bundle, input_mapping)
|
410 |
outputs = m.inference(inputs)
|
411 |
bundle = bundle.copy()
|
|
|
412 |
for k, v in output_mapping.map.items():
|
|
|
|
|
|
|
|
|
|
|
413 |
bundle.dfs[v.df][v.column] = outputs[k].detach().numpy().tolist()
|
414 |
return bundle
|
415 |
|
|
|
347 |
assert model_workspace, "Model workspace is unset."
|
348 |
ws = load_ws(model_workspace)
|
349 |
# Build the model without inputs, to get its interface.
|
350 |
+
m = pytorch_model_ops.build_model(ws)
|
351 |
m.source_workspace = model_workspace
|
352 |
bundle = bundle.copy()
|
353 |
bundle.other[save_as] = m
|
|
|
369 |
|
370 |
|
371 |
@op("Train model")
|
372 |
+
@ops.slow
|
373 |
def train_model(
|
374 |
bundle: core.Bundle,
|
375 |
*,
|
|
|
380 |
"""Trains the selected model on the selected dataset. Most training parameters are set in the model definition."""
|
381 |
m = bundle.other[model_name].copy()
|
382 |
inputs = pytorch_model_ops.to_tensors(bundle, input_mapping)
|
|
|
|
|
|
|
|
|
383 |
t = tqdm(range(epochs), desc="Training model")
|
384 |
+
losses = []
|
385 |
for _ in t:
|
386 |
loss = m.train(inputs)
|
387 |
t.set_postfix({"loss": loss})
|
388 |
+
losses.append(loss)
|
389 |
m.trained = True
|
390 |
bundle = bundle.copy()
|
391 |
bundle.other[model_name] = m
|
|
|
393 |
|
394 |
|
395 |
@op("Model inference")
|
396 |
+
@ops.slow
|
397 |
def model_inference(
|
398 |
bundle: core.Bundle,
|
399 |
*,
|
|
|
409 |
inputs = pytorch_model_ops.to_tensors(bundle, input_mapping)
|
410 |
outputs = m.inference(inputs)
|
411 |
bundle = bundle.copy()
|
412 |
+
copied = set()
|
413 |
for k, v in output_mapping.map.items():
|
414 |
+
if not v.df or not v.column:
|
415 |
+
continue
|
416 |
+
if v.df not in copied:
|
417 |
+
bundle.dfs[v.df] = bundle.dfs[v.df].copy()
|
418 |
+
copied.add(v.df)
|
419 |
bundle.dfs[v.df][v.column] = outputs[k].detach().numpy().tolist()
|
420 |
return bundle
|
421 |
|
lynxkite-graph-analytics/src/lynxkite_graph_analytics/pytorch_model_ops.py
CHANGED
@@ -1,20 +1,35 @@
|
|
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
|
12 |
import dataclasses
|
13 |
from . import core
|
14 |
|
15 |
ENV = "PyTorch model"
|
16 |
|
17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
def reg(name, inputs=[], outputs=None, params=[]):
|
19 |
if outputs is None:
|
20 |
outputs = inputs
|
@@ -27,13 +42,9 @@ def reg(name, inputs=[], outputs=None, params=[]):
|
|
27 |
)
|
28 |
|
29 |
|
30 |
-
reg("Input:
|
31 |
reg("Input: graph edges", outputs=["edges"])
|
32 |
-
reg("Input: label", outputs=["y"])
|
33 |
-
reg("Input: positive sample", outputs=["x_pos"])
|
34 |
-
reg("Input: negative sample", outputs=["x_neg"])
|
35 |
reg("Input: sequential", outputs=["y"])
|
36 |
-
reg("Input: zeros", outputs=["x"])
|
37 |
|
38 |
reg("LSTM", inputs=["x", "h"], outputs=["x", "h"])
|
39 |
reg(
|
@@ -59,10 +70,35 @@ reg(
|
|
59 |
),
|
60 |
],
|
61 |
)
|
|
|
|
|
62 |
reg("Attention", inputs=["q", "k", "v"], outputs=["x", "weights"])
|
63 |
reg("LayerNorm", inputs=["x"])
|
64 |
reg("Dropout", inputs=["x"], params=[P.basic("p", 0.5)])
|
65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
reg("Softmax", inputs=["x"])
|
67 |
reg(
|
68 |
"Graph conv",
|
@@ -70,16 +106,10 @@ reg(
|
|
70 |
outputs=["x"],
|
71 |
params=[P.options("type", ["GCNConv", "GATConv", "GATv2Conv", "SAGEConv"])],
|
72 |
)
|
73 |
-
reg(
|
74 |
-
"Activation",
|
75 |
-
inputs=["x"],
|
76 |
-
params=[P.options("type", ["ReLU", "Leaky ReLU", "Tanh", "Mish"])],
|
77 |
-
)
|
78 |
reg("Concatenate", inputs=["a", "b"], outputs=["x"])
|
79 |
reg("Add", inputs=["a", "b"], outputs=["x"])
|
80 |
reg("Subtract", inputs=["a", "b"], outputs=["x"])
|
81 |
reg("Multiply", inputs=["a", "b"], outputs=["x"])
|
82 |
-
reg("MSE loss", inputs=["x", "y"], outputs=["loss"])
|
83 |
reg("Triplet margin loss", inputs=["x", "x_pos", "x_neg"], outputs=["loss"])
|
84 |
reg("Cross-entropy loss", inputs=["x", "y"], outputs=["loss"])
|
85 |
reg(
|
@@ -110,7 +140,7 @@ ops.register_passive_op(
|
|
110 |
outputs=[ops.Output(name="output", position="bottom", type="tensor")],
|
111 |
params=[
|
112 |
ops.Parameter.basic("times", 1, int),
|
113 |
-
ops.Parameter.basic("same_weights",
|
114 |
],
|
115 |
)
|
116 |
|
@@ -128,6 +158,21 @@ def _to_id(*strings: str) -> str:
|
|
128 |
return "_".join("".join(c if c.isalnum() else "_" for c in s) for s in strings)
|
129 |
|
130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
131 |
class ColumnSpec(pydantic.BaseModel):
|
132 |
df: str
|
133 |
column: str
|
@@ -144,10 +189,17 @@ class ModelConfig:
|
|
144 |
model_outputs: list[str]
|
145 |
loss_inputs: list[str]
|
146 |
loss: torch.nn.Module
|
147 |
-
|
|
|
148 |
source_workspace: str | None = None
|
149 |
trained: bool = False
|
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]
|
153 |
output = self.model(*model_inputs)
|
@@ -174,10 +226,20 @@ class ModelConfig:
|
|
174 |
self.optimizer.step()
|
175 |
return loss.item()
|
176 |
|
|
|
|
|
|
|
|
|
|
|
|
|
177 |
def copy(self):
|
178 |
"""Returns a copy of the model."""
|
179 |
-
c = dataclasses.replace(
|
180 |
-
|
|
|
|
|
|
|
|
|
181 |
return c
|
182 |
|
183 |
def metadata(self):
|
@@ -192,113 +254,219 @@ class ModelConfig:
|
|
192 |
}
|
193 |
|
194 |
|
195 |
-
def build_model(ws: workspace.Workspace
|
196 |
"""Builds the model described in the workspace."""
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
)
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
233 |
t = node.data.title
|
234 |
-
op = catalog[t]
|
235 |
p = op.convert_params(node.data.params)
|
236 |
-
for b in dependencies[node_id]:
|
237 |
-
if b in in_loss:
|
238 |
-
in_loss.add(node_id)
|
239 |
-
if "loss" in t:
|
240 |
-
in_loss.add(node_id)
|
241 |
-
inputs = {}
|
242 |
-
for n in in_edges.get(node_id, []):
|
243 |
-
for b, h in in_edges[node_id][n]:
|
244 |
-
i = _to_id(b, h)
|
245 |
-
inputs[n] = i
|
246 |
-
if node_id in in_loss:
|
247 |
-
used_in_loss.add(i)
|
248 |
-
else:
|
249 |
-
used_in_model.add(i)
|
250 |
-
outputs = {}
|
251 |
-
for out in out_edges.get(node_id, []):
|
252 |
-
i = _to_id(node_id, out)
|
253 |
-
outputs[out] = i
|
254 |
-
if inputs: # Nodes with no inputs are input nodes. Their outputs are not "made" by us.
|
255 |
-
if node_id in in_loss:
|
256 |
-
made_in_loss.add(i)
|
257 |
-
else:
|
258 |
-
made_in_model.add(i)
|
259 |
-
inputs = types.SimpleNamespace(**inputs)
|
260 |
-
outputs = types.SimpleNamespace(**outputs)
|
261 |
-
ls = loss_layers if node_id in in_loss else layers
|
262 |
match t:
|
263 |
-
case "
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
torch.nn.functional.mse_loss,
|
276 |
-
f"{inputs.x}, {inputs.y} -> {outputs.loss}",
|
277 |
)
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
302 |
|
303 |
|
304 |
def to_tensors(b: core.Bundle, m: ModelMapping | None) -> dict[str, torch.Tensor]:
|
|
|
1 |
"""Boxes for defining PyTorch models."""
|
2 |
|
3 |
import copy
|
4 |
+
import enum
|
5 |
import graphlib
|
|
|
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.nn as pyg_nn
|
12 |
import dataclasses
|
13 |
from . import core
|
14 |
|
15 |
ENV = "PyTorch model"
|
16 |
|
17 |
|
18 |
+
def op(name, **kwargs):
|
19 |
+
_op = ops.op(ENV, name, **kwargs)
|
20 |
+
|
21 |
+
def decorator(func):
|
22 |
+
_op(func)
|
23 |
+
op = func.__op__
|
24 |
+
for p in op.inputs.values():
|
25 |
+
p.position = "bottom"
|
26 |
+
for p in op.outputs.values():
|
27 |
+
p.position = "top"
|
28 |
+
return func
|
29 |
+
|
30 |
+
return decorator
|
31 |
+
|
32 |
+
|
33 |
def reg(name, inputs=[], outputs=None, params=[]):
|
34 |
if outputs is None:
|
35 |
outputs = inputs
|
|
|
42 |
)
|
43 |
|
44 |
|
45 |
+
reg("Input: tensor", outputs=["output"], params=[P.basic("name")])
|
46 |
reg("Input: graph edges", outputs=["edges"])
|
|
|
|
|
|
|
47 |
reg("Input: sequential", outputs=["y"])
|
|
|
48 |
|
49 |
reg("LSTM", inputs=["x", "h"], outputs=["x", "h"])
|
50 |
reg(
|
|
|
70 |
),
|
71 |
],
|
72 |
)
|
73 |
+
|
74 |
+
|
75 |
reg("Attention", inputs=["q", "k", "v"], outputs=["x", "weights"])
|
76 |
reg("LayerNorm", inputs=["x"])
|
77 |
reg("Dropout", inputs=["x"], params=[P.basic("p", 0.5)])
|
78 |
+
|
79 |
+
|
80 |
+
@op("Linear")
|
81 |
+
def linear(x, *, output_dim=1024):
|
82 |
+
return pyg_nn.Linear(-1, output_dim)
|
83 |
+
|
84 |
+
|
85 |
+
class ActivationTypes(enum.Enum):
|
86 |
+
ReLU = "ReLU"
|
87 |
+
Leaky_ReLU = "Leaky ReLU"
|
88 |
+
Tanh = "Tanh"
|
89 |
+
Mish = "Mish"
|
90 |
+
|
91 |
+
|
92 |
+
@op("Activation")
|
93 |
+
def activation(x, *, type: ActivationTypes = ActivationTypes.ReLU):
|
94 |
+
return getattr(torch.nn.functional, type.name.lower().replace(" ", "_"))
|
95 |
+
|
96 |
+
|
97 |
+
@op("MSE loss")
|
98 |
+
def mse_loss(x, y):
|
99 |
+
return torch.nn.functional.mse_loss
|
100 |
+
|
101 |
+
|
102 |
reg("Softmax", inputs=["x"])
|
103 |
reg(
|
104 |
"Graph conv",
|
|
|
106 |
outputs=["x"],
|
107 |
params=[P.options("type", ["GCNConv", "GATConv", "GATv2Conv", "SAGEConv"])],
|
108 |
)
|
|
|
|
|
|
|
|
|
|
|
109 |
reg("Concatenate", inputs=["a", "b"], outputs=["x"])
|
110 |
reg("Add", inputs=["a", "b"], outputs=["x"])
|
111 |
reg("Subtract", inputs=["a", "b"], outputs=["x"])
|
112 |
reg("Multiply", inputs=["a", "b"], outputs=["x"])
|
|
|
113 |
reg("Triplet margin loss", inputs=["x", "x_pos", "x_neg"], outputs=["loss"])
|
114 |
reg("Cross-entropy loss", inputs=["x", "y"], outputs=["loss"])
|
115 |
reg(
|
|
|
140 |
outputs=[ops.Output(name="output", position="bottom", type="tensor")],
|
141 |
params=[
|
142 |
ops.Parameter.basic("times", 1, int),
|
143 |
+
ops.Parameter.basic("same_weights", False, bool),
|
144 |
],
|
145 |
)
|
146 |
|
|
|
158 |
return "_".join("".join(c if c.isalnum() else "_" for c in s) for s in strings)
|
159 |
|
160 |
|
161 |
+
@dataclasses.dataclass
|
162 |
+
class Layer:
|
163 |
+
"""Temporary data structure used by ModelBuilder."""
|
164 |
+
|
165 |
+
module: torch.nn.Module
|
166 |
+
origin_id: str
|
167 |
+
inputs: list[str]
|
168 |
+
outputs: list[str]
|
169 |
+
|
170 |
+
def for_sequential(self):
|
171 |
+
inputs = ", ".join(self.inputs)
|
172 |
+
outputs = ", ".join(self.outputs)
|
173 |
+
return self.module, f"{inputs} -> {outputs}"
|
174 |
+
|
175 |
+
|
176 |
class ColumnSpec(pydantic.BaseModel):
|
177 |
df: str
|
178 |
column: str
|
|
|
189 |
model_outputs: list[str]
|
190 |
loss_inputs: list[str]
|
191 |
loss: torch.nn.Module
|
192 |
+
optimizer_parameters: dict[str, any]
|
193 |
+
optimizer: torch.optim.Optimizer | None = None
|
194 |
source_workspace: str | None = None
|
195 |
trained: bool = False
|
196 |
|
197 |
+
def __post_init__(self):
|
198 |
+
self._make_optimizer()
|
199 |
+
|
200 |
+
def num_parameters(self) -> int:
|
201 |
+
return sum(p.numel() for p in self.model.parameters())
|
202 |
+
|
203 |
def _forward(self, inputs: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]:
|
204 |
model_inputs = [inputs[i] for i in self.model_inputs]
|
205 |
output = self.model(*model_inputs)
|
|
|
226 |
self.optimizer.step()
|
227 |
return loss.item()
|
228 |
|
229 |
+
def _make_optimizer(self):
|
230 |
+
# We need to make a new optimizer when the model is copied. (It's tied to its parameters.)
|
231 |
+
p = self.optimizer_parameters
|
232 |
+
o = getattr(torch.optim, p["type"].name)
|
233 |
+
self.optimizer = o(self.model.parameters(), lr=p["lr"])
|
234 |
+
|
235 |
def copy(self):
|
236 |
"""Returns a copy of the model."""
|
237 |
+
c = dataclasses.replace(
|
238 |
+
self,
|
239 |
+
model=copy.deepcopy(self.model),
|
240 |
+
)
|
241 |
+
c._make_optimizer()
|
242 |
+
c.optimizer.load_state_dict(self.optimizer.state_dict())
|
243 |
return c
|
244 |
|
245 |
def metadata(self):
|
|
|
254 |
}
|
255 |
|
256 |
|
257 |
+
def build_model(ws: workspace.Workspace) -> ModelConfig:
|
258 |
"""Builds the model described in the workspace."""
|
259 |
+
builder = ModelBuilder(ws)
|
260 |
+
return builder.build_model()
|
261 |
+
|
262 |
+
|
263 |
+
class ModelBuilder:
|
264 |
+
"""The state shared between methods that are used to build the model."""
|
265 |
+
|
266 |
+
def __init__(self, ws: workspace.Workspace):
|
267 |
+
self.catalog = ops.CATALOGS[ENV]
|
268 |
+
optimizers = []
|
269 |
+
self.nodes: dict[str, workspace.WorkspaceNode] = {}
|
270 |
+
repeats: list[str] = []
|
271 |
+
for node in ws.nodes:
|
272 |
+
self.nodes[node.id] = node
|
273 |
+
if node.data.title == "Optimizer":
|
274 |
+
optimizers.append(node.id)
|
275 |
+
elif node.data.title == "Repeat":
|
276 |
+
repeats.append(node.id)
|
277 |
+
self.nodes[f"START {node.id}"] = node
|
278 |
+
self.nodes[f"END {node.id}"] = node
|
279 |
+
assert optimizers, "No optimizer found."
|
280 |
+
assert len(optimizers) == 1, f"More than one optimizer found: {optimizers}"
|
281 |
+
[self.optimizer] = optimizers
|
282 |
+
self.dependencies = {n: [] for n in self.nodes}
|
283 |
+
self.in_edges: dict[str, dict[str, list[tuple[str, str]]]] = {n: {} for n in self.nodes}
|
284 |
+
self.out_edges: dict[str, dict[str, list[tuple[str, str]]]] = {n: {} for n in self.nodes}
|
285 |
+
for e in ws.edges:
|
286 |
+
self.dependencies[e.target].append(e.source)
|
287 |
+
self.in_edges.setdefault(e.target, {}).setdefault(e.targetHandle, []).append(
|
288 |
+
(e.source, e.sourceHandle)
|
289 |
+
)
|
290 |
+
self.out_edges.setdefault(e.source, {}).setdefault(e.sourceHandle, []).append(
|
291 |
+
(e.target, e.targetHandle)
|
292 |
+
)
|
293 |
+
# Split repeat boxes into start and end, and insert them into the flow.
|
294 |
+
# TODO: Think about recursive repeats.
|
295 |
+
for repeat in repeats:
|
296 |
+
if not self.out_edges[repeat] or not self.in_edges[repeat]:
|
297 |
+
continue
|
298 |
+
start_id = f"START {repeat}"
|
299 |
+
end_id = f"END {repeat}"
|
300 |
+
# repeat -> first <- real_input
|
301 |
+
# ...becomes...
|
302 |
+
# real_input -> start -> first
|
303 |
+
first, firsth = self.out_edges[repeat]["output"][0]
|
304 |
+
[(real_input, real_inputh)] = [
|
305 |
+
k for k in self.in_edges[first][firsth] if k != (repeat, "output")
|
306 |
+
]
|
307 |
+
self.dependencies[first].remove(repeat)
|
308 |
+
self.dependencies[first].append(start_id)
|
309 |
+
self.dependencies[start_id] = [real_input]
|
310 |
+
self.out_edges[real_input][real_inputh] = [
|
311 |
+
k if k != (first, firsth) else (start_id, "input")
|
312 |
+
for k in self.out_edges[real_input][real_inputh]
|
313 |
+
]
|
314 |
+
self.in_edges[start_id] = {"input": [(real_input, real_inputh)]}
|
315 |
+
self.out_edges[start_id] = {"output": [(first, firsth)]}
|
316 |
+
self.in_edges[first][firsth] = [(start_id, "output")]
|
317 |
+
# repeat <- last -> real_output
|
318 |
+
# ...becomes...
|
319 |
+
# last -> end -> real_output
|
320 |
+
last, lasth = self.in_edges[repeat]["input"][0]
|
321 |
+
[(real_output, real_outputh)] = [
|
322 |
+
k for k in self.out_edges[last][lasth] if k != (repeat, "input")
|
323 |
+
]
|
324 |
+
del self.dependencies[repeat]
|
325 |
+
self.dependencies[end_id] = [last]
|
326 |
+
self.dependencies[real_output].append(end_id)
|
327 |
+
self.out_edges[last][lasth] = [(end_id, "input")]
|
328 |
+
self.in_edges[end_id] = {"input": [(last, lasth)]}
|
329 |
+
self.out_edges[end_id] = {"output": [(real_output, real_outputh)]}
|
330 |
+
self.in_edges[real_output][real_outputh] = [
|
331 |
+
k if k != (last, lasth) else (end_id, "output")
|
332 |
+
for k in self.in_edges[real_output][real_outputh]
|
333 |
+
]
|
334 |
+
self.inv_dependencies = {n: [] for n in self.nodes}
|
335 |
+
for k, v in self.dependencies.items():
|
336 |
+
for i in v:
|
337 |
+
self.inv_dependencies[i].append(k)
|
338 |
+
self.layers: list[Layer] = []
|
339 |
+
# Clean up disconnected nodes.
|
340 |
+
disconnected = set()
|
341 |
+
for node_id in self.nodes:
|
342 |
+
op = self.catalog[self.nodes[node_id].data.title]
|
343 |
+
if len(self.in_edges[node_id]) != len(op.inputs):
|
344 |
+
disconnected.add(node_id)
|
345 |
+
disconnected |= self.all_upstream(node_id)
|
346 |
+
for node_id in disconnected:
|
347 |
+
del self.dependencies[node_id]
|
348 |
+
del self.in_edges[node_id]
|
349 |
+
del self.out_edges[node_id]
|
350 |
+
del self.inv_dependencies[node_id]
|
351 |
+
del self.nodes[node_id]
|
352 |
+
|
353 |
+
def all_upstream(self, node: str) -> set[str]:
|
354 |
+
"""Returns all nodes upstream of a node."""
|
355 |
+
deps = set()
|
356 |
+
for dep in self.dependencies[node]:
|
357 |
+
deps.add(dep)
|
358 |
+
deps.update(self.all_upstream(dep))
|
359 |
+
return deps
|
360 |
+
|
361 |
+
def all_downstream(self, node: str) -> set[str]:
|
362 |
+
"""Returns all nodes downstream of a node."""
|
363 |
+
deps = set()
|
364 |
+
for dep in self.inv_dependencies[node]:
|
365 |
+
deps.add(dep)
|
366 |
+
deps.update(self.all_downstream(dep))
|
367 |
+
return deps
|
368 |
+
|
369 |
+
def run_node(self, node_id: str) -> None:
|
370 |
+
"""Adds the layer(s) produced by this node to self.layers."""
|
371 |
+
node = self.nodes[node_id]
|
372 |
t = node.data.title
|
373 |
+
op = self.catalog[t]
|
374 |
p = op.convert_params(node.data.params)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
375 |
match t:
|
376 |
+
case "Repeat":
|
377 |
+
if node_id.startswith("END "):
|
378 |
+
repeat_id = node_id.removeprefix("END ")
|
379 |
+
start_id = f"START {repeat_id}"
|
380 |
+
[last_output] = self.in_edges[node_id]["input"]
|
381 |
+
after_start = self.all_downstream(start_id)
|
382 |
+
after_end = self.all_downstream(node_id)
|
383 |
+
before_end = self.all_upstream(node_id)
|
384 |
+
affected_nodes = after_start - after_end - {node_id}
|
385 |
+
repeated_nodes = after_start & before_end
|
386 |
+
assert affected_nodes == repeated_nodes, (
|
387 |
+
f"edges leave repeated section '{repeat_id}':\n{affected_nodes - repeated_nodes}"
|
|
|
|
|
388 |
)
|
389 |
+
repeated_layers = [e for e in self.layers if e.origin_id in repeated_nodes]
|
390 |
+
assert p["times"] >= 1, f"Cannot repeat {repeat_id} {p['times']} times."
|
391 |
+
for i in range(p["times"] - 1):
|
392 |
+
# Copy repeat section's output to repeat section's input.
|
393 |
+
self.layers.append(
|
394 |
+
Layer(
|
395 |
+
torch.nn.Identity(),
|
396 |
+
origin_id=node_id,
|
397 |
+
inputs=[_to_id(*last_output)],
|
398 |
+
outputs=[_to_id(start_id, "output")],
|
399 |
+
)
|
400 |
+
)
|
401 |
+
# Repeat the layers in the section.
|
402 |
+
for layer in repeated_layers:
|
403 |
+
if p["same_weights"]:
|
404 |
+
self.layers.append(layer)
|
405 |
+
else:
|
406 |
+
self.run_node(layer.origin_id)
|
407 |
+
self.layers.append(self.run_op(node_id, op, p))
|
408 |
+
case "Optimizer" | "Input: tensor" | "Input: graph edges" | "Input: sequential":
|
409 |
+
return
|
410 |
+
case _:
|
411 |
+
self.layers.append(self.run_op(node_id, op, p))
|
412 |
+
|
413 |
+
def run_op(self, node_id: str, op: ops.Op, params) -> Layer:
|
414 |
+
"""Returns the layer produced by this op."""
|
415 |
+
inputs = [_to_id(*i) for n in op.inputs for i in self.in_edges[node_id][n]]
|
416 |
+
outputs = [_to_id(node_id, n) for n in op.outputs]
|
417 |
+
if op.func == ops.no_op:
|
418 |
+
module = torch.nn.Identity()
|
419 |
+
else:
|
420 |
+
module = op.func(*inputs, **params)
|
421 |
+
return Layer(module, node_id, inputs, outputs)
|
422 |
+
|
423 |
+
def build_model(self) -> ModelConfig:
|
424 |
+
# Walk the graph in topological order.
|
425 |
+
ts = graphlib.TopologicalSorter(self.dependencies)
|
426 |
+
for node_id in ts.static_order():
|
427 |
+
self.run_node(node_id)
|
428 |
+
return self.get_config()
|
429 |
+
|
430 |
+
def get_config(self) -> ModelConfig:
|
431 |
+
# Split the design into model and loss.
|
432 |
+
loss_nodes = set()
|
433 |
+
for node_id in self.nodes:
|
434 |
+
if "loss" in self.nodes[node_id].data.title:
|
435 |
+
loss_nodes.add(node_id)
|
436 |
+
loss_nodes |= self.all_downstream(node_id)
|
437 |
+
layers = []
|
438 |
+
loss_layers = []
|
439 |
+
for layer in self.layers:
|
440 |
+
if layer.origin_id in loss_nodes:
|
441 |
+
loss_layers.append(layer)
|
442 |
+
else:
|
443 |
+
layers.append(layer)
|
444 |
+
used_in_model = set(input for layer in layers for input in layer.inputs)
|
445 |
+
used_in_loss = set(input for layer in loss_layers for input in layer.inputs)
|
446 |
+
made_in_model = set(output for layer in layers for output in layer.outputs)
|
447 |
+
made_in_loss = set(output for layer in loss_layers for output in layer.outputs)
|
448 |
+
layers = [layer.for_sequential() for layer in layers]
|
449 |
+
loss_layers = [layer.for_sequential() for layer in loss_layers]
|
450 |
+
cfg = {}
|
451 |
+
cfg["model_inputs"] = list(used_in_model - made_in_model)
|
452 |
+
cfg["model_outputs"] = list(made_in_model & used_in_loss)
|
453 |
+
cfg["loss_inputs"] = list(used_in_loss - made_in_loss)
|
454 |
+
# Make sure the trained output is output from the last model layer.
|
455 |
+
outputs = ", ".join(cfg["model_outputs"])
|
456 |
+
layers.append((torch.nn.Identity(), f"{outputs} -> {outputs}"))
|
457 |
+
# Create model.
|
458 |
+
cfg["model"] = pyg_nn.Sequential(", ".join(cfg["model_inputs"]), layers)
|
459 |
+
# Make sure the loss is output from the last loss layer.
|
460 |
+
[(lossb, lossh)] = self.in_edges[self.optimizer]["loss"]
|
461 |
+
lossi = _to_id(lossb, lossh)
|
462 |
+
loss_layers.append((torch.nn.Identity(), f"{lossi} -> loss"))
|
463 |
+
# Create loss function.
|
464 |
+
cfg["loss"] = pyg_nn.Sequential(", ".join(cfg["loss_inputs"]), loss_layers)
|
465 |
+
assert not list(cfg["loss"].parameters()), f"loss should have no parameters: {loss_layers}"
|
466 |
+
# Create optimizer.
|
467 |
+
op = self.catalog["Optimizer"]
|
468 |
+
cfg["optimizer_parameters"] = op.convert_params(self.nodes[self.optimizer].data.params)
|
469 |
+
return ModelConfig(**cfg)
|
470 |
|
471 |
|
472 |
def to_tensors(b: core.Bundle, m: ModelMapping | None) -> dict[str, torch.Tensor]:
|
lynxkite-graph-analytics/tests/test_pytorch_model_ops.py
CHANGED
@@ -4,14 +4,16 @@ import torch
|
|
4 |
import pytest
|
5 |
|
6 |
|
7 |
-
def make_ws(env, nodes: dict[str, dict], edges: list[tuple[str, str
|
8 |
ws = workspace.Workspace(env=env)
|
9 |
for id, data in nodes.items():
|
|
|
|
|
10 |
ws.nodes.append(
|
11 |
workspace.WorkspaceNode(
|
12 |
id=id,
|
13 |
type="basic",
|
14 |
-
data=workspace.WorkspaceNodeData(title=
|
15 |
position=workspace.Position(
|
16 |
x=data.get("x", 0),
|
17 |
y=data.get("y", 0),
|
@@ -31,35 +33,86 @@ def make_ws(env, nodes: dict[str, dict], edges: list[tuple[str, str, str, str]])
|
|
31 |
return ws
|
32 |
|
33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
async def test_build_model():
|
35 |
ws = make_ws(
|
36 |
pytorch_model_ops.ENV,
|
37 |
{
|
38 |
-
"emb": {"title": "Input:
|
39 |
-
"lin": {"title": "Linear", "output_dim":
|
40 |
-
"act": {"title": "Activation", "type": "
|
41 |
-
"label": {"title": "Input:
|
42 |
"loss": {"title": "MSE loss"},
|
43 |
"optim": {"title": "Optimizer", "type": "SGD", "lr": 0.1},
|
44 |
},
|
45 |
[
|
46 |
-
("emb:
|
47 |
-
("lin:
|
48 |
-
("act:
|
49 |
-
("label:
|
50 |
-
("loss:
|
51 |
],
|
52 |
)
|
53 |
x = torch.rand(100, 4)
|
54 |
y = x + 1
|
55 |
-
m = pytorch_model_ops.build_model(ws
|
56 |
for i in range(1000):
|
57 |
-
loss = m.train({"
|
58 |
assert loss < 0.1
|
59 |
-
o = m.inference({"
|
60 |
-
error = torch.nn.functional.mse_loss(o["
|
61 |
assert error < 0.1
|
62 |
|
63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
if __name__ == "__main__":
|
65 |
pytest.main()
|
|
|
4 |
import pytest
|
5 |
|
6 |
|
7 |
+
def make_ws(env, nodes: dict[str, dict], edges: list[tuple[str, str]]):
|
8 |
ws = workspace.Workspace(env=env)
|
9 |
for id, data in nodes.items():
|
10 |
+
title = data["title"]
|
11 |
+
del data["title"]
|
12 |
ws.nodes.append(
|
13 |
workspace.WorkspaceNode(
|
14 |
id=id,
|
15 |
type="basic",
|
16 |
+
data=workspace.WorkspaceNodeData(title=title, params=data),
|
17 |
position=workspace.Position(
|
18 |
x=data.get("x", 0),
|
19 |
y=data.get("y", 0),
|
|
|
33 |
return ws
|
34 |
|
35 |
|
36 |
+
def summarize_layers(m: pytorch_model_ops.ModelConfig) -> str:
|
37 |
+
return "".join(str(e)[0] for e in m.model)
|
38 |
+
|
39 |
+
|
40 |
+
def summarize_connections(m: pytorch_model_ops.ModelConfig) -> str:
|
41 |
+
return " ".join(
|
42 |
+
"".join(n[0] for n in c.param_names) + "->" + "".join(n[0] for n in c.return_names)
|
43 |
+
for c in m.model._children
|
44 |
+
)
|
45 |
+
|
46 |
+
|
47 |
async def test_build_model():
|
48 |
ws = make_ws(
|
49 |
pytorch_model_ops.ENV,
|
50 |
{
|
51 |
+
"emb": {"title": "Input: tensor"},
|
52 |
+
"lin": {"title": "Linear", "output_dim": 4},
|
53 |
+
"act": {"title": "Activation", "type": "Leaky_ReLU"},
|
54 |
+
"label": {"title": "Input: tensor"},
|
55 |
"loss": {"title": "MSE loss"},
|
56 |
"optim": {"title": "Optimizer", "type": "SGD", "lr": 0.1},
|
57 |
},
|
58 |
[
|
59 |
+
("emb:output", "lin:x"),
|
60 |
+
("lin:output", "act:x"),
|
61 |
+
("act:output", "loss:x"),
|
62 |
+
("label:output", "loss:y"),
|
63 |
+
("loss:output", "optim:loss"),
|
64 |
],
|
65 |
)
|
66 |
x = torch.rand(100, 4)
|
67 |
y = x + 1
|
68 |
+
m = pytorch_model_ops.build_model(ws)
|
69 |
for i in range(1000):
|
70 |
+
loss = m.train({"emb_output": x, "label_output": y})
|
71 |
assert loss < 0.1
|
72 |
+
o = m.inference({"emb_output": x[:1]})
|
73 |
+
error = torch.nn.functional.mse_loss(o["act_output"], x[:1] + 1)
|
74 |
assert error < 0.1
|
75 |
|
76 |
|
77 |
+
async def test_build_model_with_repeat():
|
78 |
+
def repeated_ws(times):
|
79 |
+
return make_ws(
|
80 |
+
pytorch_model_ops.ENV,
|
81 |
+
{
|
82 |
+
"emb": {"title": "Input: tensor"},
|
83 |
+
"lin": {"title": "Linear", "output_dim": 8},
|
84 |
+
"act": {"title": "Activation", "type": "Leaky_ReLU"},
|
85 |
+
"label": {"title": "Input: tensor"},
|
86 |
+
"loss": {"title": "MSE loss"},
|
87 |
+
"optim": {"title": "Optimizer", "type": "SGD", "lr": 0.1},
|
88 |
+
"repeat": {"title": "Repeat", "times": times, "same_weights": False},
|
89 |
+
},
|
90 |
+
[
|
91 |
+
("emb:output", "lin:x"),
|
92 |
+
("lin:output", "act:x"),
|
93 |
+
("act:output", "loss:x"),
|
94 |
+
("label:output", "loss:y"),
|
95 |
+
("loss:output", "optim:loss"),
|
96 |
+
("repeat:output", "lin:x"),
|
97 |
+
("act:output", "repeat:input"),
|
98 |
+
],
|
99 |
+
)
|
100 |
+
|
101 |
+
# 1 repetition
|
102 |
+
m = pytorch_model_ops.build_model(repeated_ws(1))
|
103 |
+
assert summarize_layers(m) == "IL<II"
|
104 |
+
assert summarize_connections(m) == "e->S S->l l->a a->E E->E"
|
105 |
+
|
106 |
+
# 2 repetitions
|
107 |
+
m = pytorch_model_ops.build_model(repeated_ws(2))
|
108 |
+
assert summarize_layers(m) == "IL<IL<II"
|
109 |
+
assert summarize_connections(m) == "e->S S->l l->a a->S S->l l->a a->E E->E"
|
110 |
+
|
111 |
+
# 3 repetitions
|
112 |
+
m = pytorch_model_ops.build_model(repeated_ws(3))
|
113 |
+
assert summarize_layers(m) == "IL<IL<IL<II"
|
114 |
+
assert summarize_connections(m) == "e->S S->l l->a a->S S->l l->a a->S S->l l->a a->E E->E"
|
115 |
+
|
116 |
+
|
117 |
if __name__ == "__main__":
|
118 |
pytest.main()
|