kalfa 3.0.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. kalfa/__init__.py +19 -0
  2. kalfa/api.py +284 -0
  3. kalfa/board/__init__.py +1 -0
  4. kalfa/board/answers.py +419 -0
  5. kalfa/board/server.py +536 -0
  6. kalfa/board/static/api.js +176 -0
  7. kalfa/board/static/app.js +122 -0
  8. kalfa/board/static/board.css +228 -0
  9. kalfa/board/static/charts.css +46 -0
  10. kalfa/board/static/components/badge.js +14 -0
  11. kalfa/board/static/components/chart.js +204 -0
  12. kalfa/board/static/components/diagram.js +857 -0
  13. kalfa/board/static/components/error.js +17 -0
  14. kalfa/board/static/components/loading.js +7 -0
  15. kalfa/board/static/components/progress.js +25 -0
  16. kalfa/board/static/components/settings.js +418 -0
  17. kalfa/board/static/components/spark.js +43 -0
  18. kalfa/board/static/components/table.js +156 -0
  19. kalfa/board/static/diagram.css +76 -0
  20. kalfa/board/static/format.js +138 -0
  21. kalfa/board/static/index.html +34 -0
  22. kalfa/board/static/plot.js +594 -0
  23. kalfa/board/static/predictions.css +20 -0
  24. kalfa/board/static/store.js +215 -0
  25. kalfa/board/static/views/compare.js +185 -0
  26. kalfa/board/static/views/home.js +90 -0
  27. kalfa/board/static/views/record/config.js +81 -0
  28. kalfa/board/static/views/record/curves.js +137 -0
  29. kalfa/board/static/views/record/data.js +378 -0
  30. kalfa/board/static/views/record/files.js +128 -0
  31. kalfa/board/static/views/record/log.js +164 -0
  32. kalfa/board/static/views/record/model.js +286 -0
  33. kalfa/board/static/views/record/overview.js +216 -0
  34. kalfa/board/static/views/record/plots.js +103 -0
  35. kalfa/board/static/views/record/predictions.js +834 -0
  36. kalfa/board/static/views/record.js +179 -0
  37. kalfa/board/static/views/runs.js +124 -0
  38. kalfa/board/static/views/sweep.js +282 -0
  39. kalfa/board/watch.py +87 -0
  40. kalfa/cli.py +371 -0
  41. kalfa/commands/__init__.py +0 -0
  42. kalfa/commands/common.py +8 -0
  43. kalfa/commands/export.py +117 -0
  44. kalfa/commands/generate.py +31 -0
  45. kalfa/commands/guide/__init__.py +101 -0
  46. kalfa/commands/guide/about.py +93 -0
  47. kalfa/commands/guide/calls.py +102 -0
  48. kalfa/commands/guide/sections.py +186 -0
  49. kalfa/commands/guide/seen.py +307 -0
  50. kalfa/commands/guide/show.py +106 -0
  51. kalfa/commands/guide/training.py +333 -0
  52. kalfa/commands/guide/words.py +97 -0
  53. kalfa/commands/legos.py +508 -0
  54. kalfa/commands/plots.py +27 -0
  55. kalfa/commands/report/__init__.py +64 -0
  56. kalfa/commands/report/page.py +112 -0
  57. kalfa/commands/report/predictions.py +216 -0
  58. kalfa/commands/report/sections.py +356 -0
  59. kalfa/commands/stop.py +60 -0
  60. kalfa/commands/sweep/__init__.py +202 -0
  61. kalfa/commands/sweep/checks.py +98 -0
  62. kalfa/commands/sweep/plan.py +183 -0
  63. kalfa/commands/sweep/points.py +123 -0
  64. kalfa/commands/sweep/runner.py +100 -0
  65. kalfa/commands/sweep/space.py +67 -0
  66. kalfa/commands/sweep/strategies.py +228 -0
  67. kalfa/core/__init__.py +0 -0
  68. kalfa/core/aliases.py +229 -0
  69. kalfa/core/build.py +727 -0
  70. kalfa/core/check.py +550 -0
  71. kalfa/core/coerce.py +36 -0
  72. kalfa/core/context.py +149 -0
  73. kalfa/core/device.py +63 -0
  74. kalfa/core/errors.py +45 -0
  75. kalfa/core/graph.py +304 -0
  76. kalfa/core/history.py +70 -0
  77. kalfa/core/loop.py +710 -0
  78. kalfa/core/make.py +121 -0
  79. kalfa/core/plan.py +156 -0
  80. kalfa/core/predict.py +290 -0
  81. kalfa/core/protocols.py +122 -0
  82. kalfa/core/record.py +347 -0
  83. kalfa/core/refs.py +88 -0
  84. kalfa/core/rng.py +100 -0
  85. kalfa/core/rules.py +513 -0
  86. kalfa/core/run.py +568 -0
  87. kalfa/core/spec.py +413 -0
  88. kalfa/core/state.py +100 -0
  89. kalfa/core/wrap.py +671 -0
  90. kalfa/records/__init__.py +24 -0
  91. kalfa/records/compare.py +86 -0
  92. kalfa/records/reader.py +561 -0
  93. kalfa/records/stats/__init__.py +36 -0
  94. kalfa/records/stats/choices.py +282 -0
  95. kalfa/records/stats/classes.py +236 -0
  96. kalfa/records/stats/measures.py +144 -0
  97. kalfa/records/stats/pairs.py +225 -0
  98. kalfa/records/stats/tables.py +153 -0
  99. kalfa/records/sweep.py +244 -0
  100. kalfa/std/__init__.py +17 -0
  101. kalfa/std/actions.py +35 -0
  102. kalfa/std/common.py +56 -0
  103. kalfa/std/data/__init__.py +1 -0
  104. kalfa/std/data/base.py +45 -0
  105. kalfa/std/data/columns.py +190 -0
  106. kalfa/std/data/flow.py +317 -0
  107. kalfa/std/data/images.py +443 -0
  108. kalfa/std/data/options.py +147 -0
  109. kalfa/std/data/pre.py +84 -0
  110. kalfa/std/data/splits.py +140 -0
  111. kalfa/std/data/stream.py +294 -0
  112. kalfa/std/data/table.py +642 -0
  113. kalfa/std/data/text.py +436 -0
  114. kalfa/std/data/transforms.py +327 -0
  115. kalfa/std/data/weights.py +113 -0
  116. kalfa/std/data/window.py +135 -0
  117. kalfa/std/figure.py +37 -0
  118. kalfa/std/init.py +72 -0
  119. kalfa/std/loggers.py +105 -0
  120. kalfa/std/losses.py +161 -0
  121. kalfa/std/metrics.py +463 -0
  122. kalfa/std/models.py +122 -0
  123. kalfa/std/notes.py +1553 -0
  124. kalfa/std/objectives.py +360 -0
  125. kalfa/std/plots/__init__.py +75 -0
  126. kalfa/std/plots/classification.py +207 -0
  127. kalfa/std/plots/common.py +44 -0
  128. kalfa/std/plots/curves.py +56 -0
  129. kalfa/std/plots/images.py +188 -0
  130. kalfa/std/plots/pairs.py +66 -0
  131. kalfa/std/plots/predictions.py +154 -0
  132. kalfa/std/samplers.py +261 -0
  133. kalfa/std/samples.py +72 -0
  134. kalfa/std/schedules.py +74 -0
  135. kalfa/std/steps.py +133 -0
  136. kalfa/std/triggers.py +135 -0
  137. kalfa/summary.py +296 -0
  138. kalfa/tui/__init__.py +4 -0
  139. kalfa/tui/app.py +107 -0
  140. kalfa/tui/config.py +61 -0
  141. kalfa/tui/guide.py +100 -0
  142. kalfa/tui/jobs.py +97 -0
  143. kalfa/tui/legos.py +62 -0
  144. kalfa/tui/records.py +439 -0
  145. kalfa-3.0.0.dev0.dist-info/METADATA +261 -0
  146. kalfa-3.0.0.dev0.dist-info/RECORD +148 -0
  147. kalfa-3.0.0.dev0.dist-info/WHEEL +4 -0
  148. kalfa-3.0.0.dev0.dist-info/entry_points.txt +2 -0
kalfa/std/metrics.py ADDED
@@ -0,0 +1,463 @@
1
+ import math
2
+
3
+ import numpy
4
+ import torch
5
+ from torch.nn import functional
6
+
7
+ from kalfa.core.aliases import import_target, register
8
+ from kalfa.core.errors import BuildError, RunError
9
+ from kalfa.core.protocols import ContextMetric
10
+ from kalfa.std.common import classes_last_of, evaluating
11
+
12
+
13
+ def flat(value):
14
+ return value.detach().cpu().reshape(-1).to(torch.float64)
15
+
16
+
17
+ def pair(pred, target):
18
+ guess, truth = flat(pred), flat(target)
19
+ if guess.numel() != truth.numel():
20
+ raise ValueError(f"pred has {guess.numel()} values and target {truth.numel()}; the metric needs one per row")
21
+ return guess, truth
22
+
23
+
24
+ def labels_of(pred):
25
+ scores = pred.detach().cpu()
26
+ if scores.ndim >= 2 and scores.shape[-1] > 1:
27
+ return scores.reshape(len(scores), -1).argmax(dim=-1)
28
+ scores = scores.reshape(-1)
29
+ if scores.is_floating_point():
30
+ return (scores > 0).long()
31
+ return scores.long()
32
+
33
+
34
+ def scores_of(pred):
35
+ scores = pred.detach().cpu().to(torch.float32)
36
+ if scores.ndim <= 1 or (scores.ndim == 2 and scores.shape[1] == 1):
37
+ return scores.reshape(-1)
38
+ scores = torch.softmax(scores.reshape(len(scores), -1), dim=1)
39
+ return scores[:, 1] if scores.shape[1] == 2 else scores
40
+
41
+
42
+ def targets_of(target, pred):
43
+ truth = target.detach().cpu()
44
+ if truth.ndim >= 2 and truth.shape[-1] > 1 and tuple(truth.shape) == tuple(pred.shape):
45
+ return truth.reshape(len(truth), -1).argmax(dim=-1)
46
+ return truth.reshape(-1).long()
47
+
48
+
49
+ def check_rows(rows, truth):
50
+ if rows != truth.numel():
51
+ raise ValueError(f"pred has {rows} rows and target {truth.numel()}")
52
+
53
+
54
+ class SumMetric:
55
+ def __init__(self):
56
+ self.total = 0.0
57
+ self.count = 0
58
+
59
+ def reset(self):
60
+ self.total = 0.0
61
+ self.count = 0
62
+
63
+ def to(self, device):
64
+ return self
65
+
66
+ def finish(self, mean):
67
+ return mean
68
+
69
+ def compute(self):
70
+ if not self.count:
71
+ return math.nan
72
+ return float(self.finish(self.total / self.count))
73
+
74
+
75
+ class Rmse(SumMetric):
76
+ def update(self, pred, target):
77
+ guess, truth = pair(pred, target)
78
+ self.total += float(((guess - truth) ** 2).sum())
79
+ self.count += int(truth.numel())
80
+
81
+ def finish(self, mean):
82
+ return math.sqrt(mean)
83
+
84
+
85
+ class Mae(SumMetric):
86
+ def update(self, pred, target):
87
+ guess, truth = pair(pred, target)
88
+ self.total += float((guess - truth).abs().sum())
89
+ self.count += int(truth.numel())
90
+
91
+
92
+ class Confusion:
93
+ averages = ()
94
+
95
+ def __init__(self, num_classes=None, average=None):
96
+ if average not in self.averages:
97
+ raise ValueError(f"average must be one of {list(self.averages)}, got {average!r}")
98
+ self.num_classes = None if num_classes is None else int(num_classes)
99
+ self.average = average
100
+ self.counts = {}
101
+
102
+ def reset(self):
103
+ self.counts = {}
104
+
105
+ def to(self, device):
106
+ return self
107
+
108
+ def update(self, pred, target):
109
+ picked = labels_of(pred)
110
+ truth = targets_of(target, pred)
111
+ check_rows(picked.numel(), truth)
112
+ if picked.numel() == 0:
113
+ return
114
+ pairs, counts = torch.unique(torch.stack([truth, picked], dim=1), dim=0, return_counts=True)
115
+ for (seen, guessed), count in zip(pairs.tolist(), counts.tolist()):
116
+ self.counts[(seen, guessed)] = self.counts.get((seen, guessed), 0) + count
117
+
118
+ def classes(self):
119
+ if self.num_classes is not None:
120
+ return list(range(self.num_classes))
121
+ return sorted({label for entry in self.counts for label in entry})
122
+
123
+ def total(self):
124
+ return sum(self.counts.values())
125
+
126
+ def support(self, label):
127
+ return sum(count for (seen, _), count in self.counts.items() if seen == label)
128
+
129
+ def predicted(self, label):
130
+ return sum(count for (_, guessed), count in self.counts.items() if guessed == label)
131
+
132
+ def hits(self, label):
133
+ return self.counts.get((label, label), 0)
134
+
135
+
136
+ class Accuracy(Confusion):
137
+ averages = ("micro", "macro")
138
+
139
+ def __init__(self, num_classes=None, average="micro"):
140
+ super().__init__(num_classes, average)
141
+
142
+ def compute(self):
143
+ total = self.total()
144
+ if not total:
145
+ return math.nan
146
+ if self.average == "micro":
147
+ return sum(self.hits(label) for label in self.classes()) / total
148
+ recalls = [self.hits(label) / self.support(label) for label in self.classes() if self.support(label)]
149
+ return sum(recalls) / len(recalls)
150
+
151
+
152
+ class F1(Confusion):
153
+ averages = ("macro", "micro", "weighted", "binary")
154
+
155
+ def __init__(self, num_classes=None, average="macro"):
156
+ super().__init__(num_classes, average)
157
+
158
+ def score(self, label):
159
+ denominator = self.predicted(label) + self.support(label)
160
+ return 2.0 * self.hits(label) / denominator if denominator else 0.0
161
+
162
+ def compute(self):
163
+ total = self.total()
164
+ if not total:
165
+ return math.nan
166
+ if self.average == "binary":
167
+ return self.score(1)
168
+ if self.average == "micro":
169
+ return sum(self.hits(label) for label in self.classes()) / total
170
+ labels = self.classes()
171
+ if self.average == "weighted":
172
+ return sum(self.score(label) * self.support(label) for label in labels) / total
173
+ return sum(self.score(label) for label in labels) / len(labels)
174
+
175
+
176
+ class Scored:
177
+ def __init__(self, num_classes=None):
178
+ self.num_classes = None if num_classes is None else int(num_classes)
179
+ self.scores = []
180
+ self.targets = []
181
+
182
+ def reset(self):
183
+ self.scores = []
184
+ self.targets = []
185
+
186
+ def to(self, device):
187
+ return self
188
+
189
+ def update(self, pred, target):
190
+ scores = scores_of(pred)
191
+ truth = targets_of(target, pred)
192
+ check_rows(len(scores), truth)
193
+ if truth.numel() == 0:
194
+ return
195
+ self.scores.append(scores)
196
+ self.targets.append(truth)
197
+
198
+ def gathered(self):
199
+ if not self.scores:
200
+ return None, None
201
+ return torch.cat(self.scores).numpy(), torch.cat(self.targets).numpy()
202
+
203
+ @staticmethod
204
+ def scorable(truth):
205
+ return len(truth) > 0 and truth.min() != truth.max()
206
+
207
+ def compute(self):
208
+ scores, truth = self.gathered()
209
+ if scores is None:
210
+ return math.nan
211
+ if scores.ndim == 1:
212
+ return float(self.binary(scores, truth)) if self.scorable(truth) else math.nan
213
+ values = []
214
+ for label in range(scores.shape[1]):
215
+ positives = (truth == label).astype(int)
216
+ if self.scorable(positives):
217
+ values.append(float(self.binary(scores[:, label], positives)))
218
+ return sum(values) / len(values) if values else math.nan
219
+
220
+
221
+ class Auroc(Scored):
222
+ def binary(self, scores, truth):
223
+ from sklearn.metrics import roc_auc_score
224
+
225
+ return roc_auc_score(truth, scores)
226
+
227
+
228
+ class AveragePrecision(Scored):
229
+ def binary(self, scores, truth):
230
+ from sklearn.metrics import average_precision_score
231
+
232
+ return average_precision_score(truth, scores)
233
+
234
+
235
+ def rmse():
236
+ return Rmse()
237
+
238
+
239
+ def mae():
240
+ return Mae()
241
+
242
+
243
+ def accuracy(num_classes=None, average="micro"):
244
+ return Accuracy(num_classes, average)
245
+
246
+
247
+ def f1(num_classes=None, average="macro"):
248
+ return F1(num_classes, average)
249
+
250
+
251
+ def auroc(num_classes=None):
252
+ return Auroc(num_classes)
253
+
254
+
255
+ def average_precision(num_classes=None):
256
+ return AveragePrecision(num_classes)
257
+
258
+
259
+ METRICS = (
260
+ ("rmse", "kalfa.std.metrics.rmse", "root mean squared error over all predicted values"),
261
+ ("mae", "kalfa.std.metrics.mae", "mean absolute error over all predicted values"),
262
+ ("accuracy", "kalfa.std.metrics.accuracy", "fraction of rows whose predicted class (argmax of logits, or "
263
+ "logit > 0) matches the target; average micro or macro"),
264
+ ("f1", "kalfa.std.metrics.f1", "f1 over the classes seen (or num_classes); average macro, micro, weighted or "
265
+ "binary"),
266
+ ("auroc", "kalfa.std.metrics.auroc", "area under the roc curve from scores, (N, 2) logits or one versus rest "
267
+ "macro over (N, C) logits; nan when the set holds one class"),
268
+ ("average_precision", "kalfa.std.metrics.average_precision", "average precision from scores, (N, 2) logits "
269
+ "or one versus rest macro over (N, C) logits"),
270
+ )
271
+
272
+
273
+ def register_names():
274
+ for name, target, doc in METRICS:
275
+ register(name, target, slot="metric", doc=doc)
276
+
277
+
278
+ register_names()
279
+
280
+
281
+ class Perplexity:
282
+ def __init__(self, classes_last=None):
283
+ self.classes_last = classes_last
284
+ self.reset()
285
+
286
+ def reset(self):
287
+ self.total = 0.0
288
+ self.tokens = 0
289
+
290
+ def to(self, device):
291
+ return self
292
+
293
+ def update(self, pred, target):
294
+ logits = pred.detach().cpu().to(torch.float32)
295
+ truth = target.detach().cpu().long()
296
+ if logits.ndim < 2:
297
+ raise ValueError(f"perplexity needs logits (N, T, V) or (N, V), got shape {tuple(logits.shape)}")
298
+ if classes_last_of(logits, truth, self.classes_last, self, "perplexity") is False and logits.ndim >= 3:
299
+ logits = logits.movedim(1, -1)
300
+ logits = logits.reshape(-1, logits.shape[-1])
301
+ truth = truth.reshape(-1)
302
+ if truth.numel() != len(logits):
303
+ raise ValueError(f"perplexity has logits of shape {tuple(pred.shape)} for targets of shape "
304
+ f"{tuple(target.shape)}; it needs (N, T, V) or (N, V, T) logits for (N, T) targets")
305
+ counted = int((truth != -100).sum())
306
+ if not counted:
307
+ return
308
+ self.total += float(functional.cross_entropy(logits, truth, reduction="sum", ignore_index=-100))
309
+ self.tokens += counted
310
+
311
+ def compute(self):
312
+ if not self.tokens:
313
+ return math.nan
314
+ try:
315
+ return math.exp(self.total / self.tokens)
316
+ except OverflowError:
317
+ return math.inf
318
+
319
+
320
+ def pixels(images):
321
+ values = images.detach().cpu().to(torch.float64)
322
+ if values.ndim == 3:
323
+ values = values.unsqueeze(1)
324
+ if values.ndim != 4:
325
+ raise RunError(f"the pixel extractor needs images (N, C, H, W), got shape {tuple(values.shape)}")
326
+ return functional.adaptive_avg_pool2d(values, 16).flatten(1)
327
+
328
+
329
+ def extractor_of(spec):
330
+ if spec is None:
331
+ return pixels
332
+ found = spec
333
+ if isinstance(spec, str):
334
+ try:
335
+ found = import_target(spec)
336
+ except BuildError as failure:
337
+ raise BuildError(f"fid: extractor {spec!r} is not an importable function images -> features "
338
+ f"({failure})") from None
339
+ if not callable(found):
340
+ raise BuildError(f"fid: extractor {spec!r} is not a function images -> features")
341
+ return found
342
+
343
+
344
+ def feature_rows(values):
345
+ if isinstance(values, torch.Tensor):
346
+ values = values.detach().cpu().to(torch.float64).numpy()
347
+ values = numpy.asarray(values, dtype=numpy.float64)
348
+ return values.reshape(len(values), -1)
349
+
350
+
351
+ def first_tensor(value):
352
+ if isinstance(value, dict):
353
+ value = next(iter(value.values()))
354
+ if isinstance(value, (tuple, list)):
355
+ value = value[0]
356
+ return value
357
+
358
+
359
+ def symmetric(matrix):
360
+ return (matrix + matrix.T) / 2.0
361
+
362
+
363
+ def moments(rows):
364
+ width = rows.shape[1]
365
+ return rows.mean(axis=0), numpy.cov(rows, rowvar=False).reshape(width, width)
366
+
367
+
368
+ def root_of(matrix):
369
+ values, vectors = numpy.linalg.eigh(symmetric(matrix))
370
+ return (vectors * numpy.sqrt(numpy.clip(values, 0.0, None))) @ vectors.T
371
+
372
+
373
+ def frechet_distance(real, fake):
374
+ real = numpy.asarray(real, dtype=numpy.float64)
375
+ fake = numpy.asarray(fake, dtype=numpy.float64)
376
+ if len(real) < 2 or len(fake) < 2:
377
+ return math.nan
378
+ real = real.reshape(len(real), -1)
379
+ fake = fake.reshape(len(fake), -1)
380
+ if real.shape[1] != fake.shape[1]:
381
+ raise RunError(f"fid: the real features have {real.shape[1]} columns and the fakes {fake.shape[1]}")
382
+ if not (numpy.isfinite(real).all() and numpy.isfinite(fake).all()):
383
+ return math.nan
384
+ mean_real, cov_real = moments(real)
385
+ mean_fake, cov_fake = moments(fake)
386
+ root = root_of(cov_real)
387
+ values = numpy.linalg.eigvalsh(symmetric(root @ cov_fake @ root))
388
+ shared = numpy.sqrt(numpy.clip(values, 0.0, None)).sum()
389
+ difference = mean_real - mean_fake
390
+ distance = difference @ difference + numpy.trace(cov_real) + numpy.trace(cov_fake) - 2.0 * shared
391
+ return max(float(distance), 0.0)
392
+
393
+
394
+ class Fid(ContextMetric):
395
+ on = ("valid",)
396
+
397
+ def __init__(self, model, latent, real="image", label=None, n=2000, extractor=None, batch=128):
398
+ self.model = model
399
+ self.latent = int(latent)
400
+ self.real = real
401
+ self.label = label
402
+ self.n = int(n)
403
+ self.extractor = extractor
404
+ self.batch = int(batch)
405
+ if self.batch < 1:
406
+ raise BuildError(f"fid: batch must be a positive int, got {batch!r}")
407
+ self.extract = extractor_of(extractor)
408
+ self.reset()
409
+
410
+ def reset(self):
411
+ self.real_features = []
412
+ self.fake_features = []
413
+ self.seen = 0
414
+
415
+ def field(self, batch, name):
416
+ if name not in batch:
417
+ raise RunError(f"{self.path or 'fid'}: the batch has no field {name!r}; it has {sorted(batch)}")
418
+ return batch[name]
419
+
420
+ def features_of(self, images):
421
+ return feature_rows(self.extract(images))
422
+
423
+ def fakes(self, noise, labels):
424
+ found = []
425
+ for start in range(0, len(noise), self.batch):
426
+ parts = [noise[start:start + self.batch]]
427
+ if labels is not None:
428
+ parts.append(labels[start:start + self.batch])
429
+ found.append(self.features_of(first_tensor(self.model(*parts))))
430
+ return found
431
+
432
+ def update(self, outputs, batch, ctx):
433
+ need = self.n - self.seen
434
+ if need <= 0:
435
+ return
436
+ real = self.field(batch, self.real)[:need]
437
+ count = len(real)
438
+ if not count:
439
+ return
440
+ labels = None if self.label is None else self.field(batch, self.label)[:count]
441
+ with evaluating(self.model):
442
+ self.real_features.append(self.features_of(real))
443
+ noise = ctx.rng.normal("fid", (count, self.latent), like=real)
444
+ self.fake_features.extend(self.fakes(noise, labels))
445
+ self.seen += count
446
+
447
+ def compute(self):
448
+ if not self.real_features or not self.fake_features:
449
+ return math.nan
450
+ return frechet_distance(numpy.concatenate(self.real_features), numpy.concatenate(self.fake_features))
451
+
452
+
453
+ def perplexity(classes_last=None):
454
+ return Perplexity(classes_last)
455
+
456
+
457
+ register("perplexity", "kalfa.std.metrics.perplexity", slot="metric",
458
+ doc="exp of the mean token cross entropy of logits (N, T, V), (N, V, T) or (N, V) against int targets; "
459
+ "classes_last: true or false states the layout when the tokens and the vocabulary have the same size")
460
+ register("fid", "kalfa.std.metrics.Fid", slot="metric",
461
+ doc="Frechet distance between features of n real images and n images of model(z) (or model(z, label)); "
462
+ "extractor is an import path of a function images -> features, the default pools pixels; valid only, "
463
+ "pair it with every to keep it cheap")
kalfa/std/models.py ADDED
@@ -0,0 +1,122 @@
1
+ import copy
2
+
3
+ from torch import nn
4
+
5
+ from kalfa.core import aliases
6
+ from kalfa.core.aliases import register
7
+
8
+
9
+ ACTIVATIONS = {
10
+ "relu": nn.ReLU,
11
+ "gelu": nn.GELU,
12
+ "tanh": nn.Tanh,
13
+ "silu": nn.SiLU,
14
+ "sigmoid": nn.Sigmoid,
15
+ "elu": nn.ELU,
16
+ "leaky_relu": nn.LeakyReLU,
17
+ "softplus": nn.Softplus,
18
+ }
19
+
20
+
21
+ def activation_of(activation):
22
+ if isinstance(activation, nn.Module):
23
+ return copy.deepcopy(activation)
24
+ if isinstance(activation, str):
25
+ found = ACTIVATIONS.get(activation)
26
+ if found is None:
27
+ raise ValueError(f"unknown activation {activation!r}; one of {sorted(ACTIVATIONS)}")
28
+ return found()
29
+ if callable(activation):
30
+ return activation()
31
+ raise TypeError(f"activation must be a name, a module or a module class, got {type(activation).__name__}")
32
+
33
+
34
+ def linear_of(width, size):
35
+ return nn.LazyLinear(size) if width is None else nn.Linear(width, size)
36
+
37
+
38
+ class Mlp(nn.Module):
39
+ def __init__(self, hidden, out, activation="relu", dropout=0.0, in_features=None):
40
+ super().__init__()
41
+ sizes = [int(hidden)] if isinstance(hidden, int) else [int(size) for size in hidden]
42
+ width = None if in_features is None else int(in_features)
43
+ layers = []
44
+ for size in sizes:
45
+ layers.append(linear_of(width, size))
46
+ layers.append(activation_of(activation))
47
+ if dropout and float(dropout) > 0.0:
48
+ layers.append(nn.Dropout(float(dropout)))
49
+ width = size
50
+ layers.append(linear_of(width, int(out)))
51
+ self.layers = nn.Sequential(*layers)
52
+
53
+ def forward(self, x):
54
+ return self.layers(x)
55
+
56
+
57
+ def layer_of(item):
58
+ if isinstance(item, nn.Module):
59
+ return item
60
+ if isinstance(item, str):
61
+ target = aliases.resolve(item)
62
+ if isinstance(target, str):
63
+ target = aliases.import_target(target)
64
+ return target()
65
+ if callable(item):
66
+ return item()
67
+ raise TypeError(f"a sequential layer is a module, a short name or a module class, got {type(item).__name__}")
68
+
69
+
70
+ class Sequential(nn.Module):
71
+ def __init__(self, layers):
72
+ super().__init__()
73
+ if isinstance(layers, (str, nn.Module)) or not hasattr(layers, "__iter__"):
74
+ raise TypeError("sequential takes layers: a list of modules or short names")
75
+ self.layers = nn.Sequential(*[layer_of(item) for item in layers])
76
+
77
+ def forward(self, *inputs):
78
+ value = inputs[0] if len(inputs) == 1 else inputs
79
+ return self.layers(value)
80
+
81
+
82
+ class LastStep(nn.Module):
83
+ def forward(self, value):
84
+ if isinstance(value, (tuple, list)):
85
+ value = value[0]
86
+ return value[:, -1]
87
+
88
+
89
+ MODELS = (
90
+ ("mlp", "kalfa.std.models.Mlp", "linear layers over hidden sizes with an activation, dropout and a final "
91
+ "linear to out; the first layer infers its input width when in_features is "
92
+ "not given"),
93
+ ("stack", "kalfa.std.models.Sequential", "a stack of layers applied in order: call mappings such as "
94
+ "{use: linear, out_features: 8} or short names of argument free layers "
95
+ "such as relu, flatten or last_step"),
96
+ )
97
+
98
+ LAYERS = (
99
+ ("linear", "torch.nn.LazyLinear", "a linear layer that infers its input width: {use: linear, out_features: n}"),
100
+ ("relu", "torch.nn.ReLU", "rectified linear activation"),
101
+ ("gelu", "torch.nn.GELU", "gaussian error linear activation"),
102
+ ("tanh", "torch.nn.Tanh", "hyperbolic tangent activation"),
103
+ ("sigmoid", "torch.nn.Sigmoid", "sigmoid activation"),
104
+ ("silu", "torch.nn.SiLU", "sigmoid weighted linear activation"),
105
+ ("dropout", "torch.nn.Dropout", "dropout with probability p"),
106
+ ("flatten", "torch.nn.Flatten", "flattens every dimension after the batch dimension"),
107
+ ("layer_norm", "torch.nn.LayerNorm", "layer normalisation over normalized_shape"),
108
+ ("embedding", "torch.nn.Embedding", "a lookup table: {use: embedding, num_embeddings: n, embedding_dim: d}"),
109
+ ("batch_norm", "torch.nn.LazyBatchNorm1d", "batch normalisation over features, width inferred"),
110
+ ("conv2d", "torch.nn.LazyConv2d", "a 2d convolution that infers its input channels"),
111
+ ("last_step", "kalfa.std.models.LastStep", "the last time step of a batch first sequence output, also from the "
112
+ "(output, state) tuple a GRU or LSTM returns"),
113
+ )
114
+
115
+ def register_names():
116
+ for name, target, doc in MODELS:
117
+ register(name, target, slot="model", doc=doc)
118
+ for name, target, doc in LAYERS:
119
+ register(name, target, slot="value", doc=doc)
120
+
121
+
122
+ register_names()