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/core/make.py ADDED
@@ -0,0 +1,121 @@
1
+ from . import state, wrap
2
+ from .errors import BuildError, KalfaError
3
+
4
+
5
+ def as_list(value):
6
+ if value is None:
7
+ return None
8
+ if isinstance(value, str):
9
+ return [value]
10
+ return list(value)
11
+
12
+
13
+ def build_model(node, module, keys, ctx):
14
+ name = node.name
15
+ data_inputs = getattr(ctx.data, "inputs", None) if ctx.data is not None else None
16
+ inputs = as_list(keys.get("inputs")) or list(data_inputs or ("x",))
17
+ outputs = as_list(keys.get("outputs")) or [name]
18
+ init = keys.get("init")
19
+ if init is not None:
20
+ if not callable(init):
21
+ raise BuildError(f"{node.path}: init must be a call such as {{use: normal_init}}, got {init!r}")
22
+ init(module)
23
+ weights = keys.get("weights")
24
+ if weights is not None:
25
+ load_weights(node, module, weights)
26
+ freeze = keys.get("freeze", False)
27
+ entry = wrap.ModelEntry(name, module, inputs, outputs, freeze=freeze, weights=weights, init=init)
28
+ if "freeze" in keys:
29
+ entry.set_freeze(freeze)
30
+ ema = keys.get("ema")
31
+ if ema is not None and ema is not False:
32
+ entry.ema = ema_copy(node, module, ema)
33
+ entry.path = node.path
34
+ return entry
35
+
36
+
37
+ def ema_copy(node, module, ema):
38
+ if isinstance(ema, dict):
39
+ decay = ema.get("decay", 0.999)
40
+ every = ema.get("every", 1)
41
+ elif ema is True:
42
+ decay, every = 0.999, 1
43
+ else:
44
+ decay, every = ema, 1
45
+ try:
46
+ return wrap.EmaCopy(module, float(decay), int(every))
47
+ except (TypeError, ValueError) as exc:
48
+ raise BuildError(f"{node.path}: ema takes a decay such as 0.999 or {{decay, every}}, got {ema!r}") from exc
49
+
50
+
51
+ def load_weights(node, module, weights):
52
+ spec = {"run": weights} if isinstance(weights, str) else weights
53
+ if not isinstance(spec, dict) or not spec.get("run"):
54
+ raise BuildError(f"{node.path}: weights takes {{run, model, which}}, got {weights!r}")
55
+ model = spec.get("model") or node.name
56
+ try:
57
+ path = state.checkpoint_path(spec["run"], spec.get("which"))
58
+ checkpoint = state.read_checkpoint(path)
59
+ except KalfaError:
60
+ raise
61
+ except Exception as exc:
62
+ raise BuildError(f"{node.path}: weights could not be read from {spec['run']}: {exc}") from exc
63
+ nodes = checkpoint.get("nodes", {}) if isinstance(checkpoint, dict) else {}
64
+ key = f"models.{model}"
65
+ if key not in nodes:
66
+ raise BuildError(f"{node.path}: {path} holds no {key}; it holds {sorted(nodes)}")
67
+ try:
68
+ module.load_state_dict(nodes[key])
69
+ except Exception as exc:
70
+ raise BuildError(f"{node.path}: the weights of {key} in {path} do not fit {node.use}: {exc}") from exc
71
+
72
+
73
+ def build_optimizer(node, kwargs, keys, signature, ctx):
74
+ over = groups_of(node, keys.get("over"))
75
+ entry = wrap.OptimizerEntry(node.name, node.target, kwargs, over, minimizes=keys.get("minimizes"),
76
+ schedule=keys.get("schedule"))
77
+ entry.path = node.path
78
+ link_emas(entry, over, ctx)
79
+ try:
80
+ if callable(getattr(entry, "bind", None)):
81
+ entry.bind(ctx)
82
+ else:
83
+ entry.build()
84
+ except KalfaError:
85
+ raise
86
+ except Exception as exc:
87
+ raise BuildError(f"{node.path}: {node.use} could not be built: {type(exc).__name__}: {exc}") from exc
88
+ return entry
89
+
90
+
91
+ def covers(model, module):
92
+ return any(module is part for part in model.module.modules())
93
+
94
+
95
+ def link_emas(entry, over, ctx):
96
+ emas = getattr(entry, "emas", None)
97
+ if not isinstance(emas, list):
98
+ return
99
+ modules = [item.of if isinstance(item, wrap.Group) else item for item in over]
100
+ for model in ctx.models.values():
101
+ if model.ema is None or any(model.ema is known for known in emas):
102
+ continue
103
+ if any(covers(model, module) for module in modules):
104
+ emas.append(model.ema)
105
+
106
+
107
+ def groups_of(node, over):
108
+ if over is None:
109
+ raise BuildError(f"{node.path}: over names the modules to optimize")
110
+ items = over if isinstance(over, list) else [over]
111
+ groups = []
112
+ for item in items:
113
+ if isinstance(item, dict):
114
+ rest = dict(item)
115
+ of = rest.pop("of", None)
116
+ if of is None:
117
+ raise BuildError(f"{node.path}: a group in over needs of: '@models.<name>'")
118
+ groups.append(wrap.Group(of, rest.pop("match", None), **rest))
119
+ else:
120
+ groups.append(item)
121
+ return groups
kalfa/core/plan.py ADDED
@@ -0,0 +1,156 @@
1
+ from pathlib import Path
2
+
3
+ from .build import fixed_of, mutable_paths, node_signature
4
+ from .context import train_of
5
+ from .errors import RunError
6
+ from .record import version as kalfa_version
7
+ from .refs import jsonable, ref_json, ref_parts
8
+
9
+
10
+ FORMAT = 1
11
+ SET_PREFIX = {"train": "train", "valid": "val", "test": "test"}
12
+ DEFAULT_ON = ("train", "valid", "test")
13
+
14
+
15
+ def plan_of(tree, version=None):
16
+ settings = tree.settings or {}
17
+ nodes = {}
18
+ for path, node in tree.nodes.items():
19
+ signature = node_signature(node.target, node.slot) if node.target is not None else None
20
+ checked = signature is not None and not signature.accepts_extra
21
+ nodes[path] = {
22
+ "slot": node.slot,
23
+ "use": node.use,
24
+ "params": jsonable(node.params),
25
+ "keys": jsonable(node.keys),
26
+ "fixed": list(fixed_of(node)),
27
+ "signature": "checked" if checked else "open",
28
+ }
29
+ return {
30
+ "format": FORMAT,
31
+ "kalfa": version if version is not None else kalfa_version(),
32
+ "seed": jsonable(settings.get("seed")),
33
+ "device": jsonable(settings.get("device")),
34
+ "record": jsonable(settings.get("record")),
35
+ "nodes": nodes,
36
+ "data": data_of(tree),
37
+ "train": jsonable(settings.get("train") or {}),
38
+ "rules": rules_of(tree),
39
+ "series": series_of(tree),
40
+ "mutable": mutable_paths(tree),
41
+ }
42
+
43
+
44
+ def data_of(tree):
45
+ node = tree.nodes.get("data")
46
+ if node is None:
47
+ return {"inputs": [], "exposes": []}
48
+ target = node.target
49
+ inputs = getattr(target, "inputs", None) if target is not None else None
50
+ exposes = getattr(target, "exposes", None) if target is not None else None
51
+ out = {"inputs": list(inputs) if inputs else ["x"], "exposes": list(exposes or ())}
52
+ fields = fields_of(tree, node)
53
+ if fields is not None:
54
+ out["fields"] = fields
55
+ return out
56
+
57
+
58
+ def fields_of(tree, node):
59
+ file = node.params.get("file")
60
+ if not isinstance(file, str) or not file:
61
+ return None
62
+ candidates = [Path(file)]
63
+ for directory in getattr(tree.config, "directories", None) or ():
64
+ candidates.append(Path(directory) / file)
65
+ for candidate in candidates:
66
+ if candidate.is_file():
67
+ try:
68
+ return read_header(candidate)
69
+ except Exception:
70
+ return None
71
+ return None
72
+
73
+
74
+ def read_header(path):
75
+ suffix = path.suffix.lower()
76
+ if suffix in (".parquet", ".pq"):
77
+ import pyarrow.parquet as parquet
78
+
79
+ schema = parquet.read_schema(str(path))
80
+ elif suffix in (".csv", ".tsv"):
81
+ import pyarrow.csv as csv
82
+
83
+ options = csv.ParseOptions(delimiter="\t") if suffix == ".tsv" else None
84
+ reader = csv.open_csv(str(path), parse_options=options)
85
+ try:
86
+ schema = reader.schema
87
+ finally:
88
+ close = getattr(reader, "close", None)
89
+ if callable(close):
90
+ close()
91
+ else:
92
+ return None
93
+ return {name: str(schema.field(name).type) for name in schema.names}
94
+
95
+
96
+ def call_of(tree, path, value):
97
+ if isinstance(value, str):
98
+ return {"use": value}
99
+ node = tree.nodes.get(path)
100
+ if node is None or not isinstance(value, dict) or "use" not in value:
101
+ return jsonable(value)
102
+ return {"use": value["use"], **jsonable(node.keys), **jsonable(node.params)}
103
+
104
+
105
+ def patch_value(value):
106
+ if ref_parts(value) is None:
107
+ return jsonable(value)
108
+ try:
109
+ return ref_json(value)
110
+ except RunError:
111
+ return jsonable(value)
112
+
113
+
114
+ def rules_of(tree):
115
+ out = []
116
+ for spec in tree.rules:
117
+ out.append({
118
+ "id": spec.name,
119
+ "when": call_of(tree, f"rules.{spec.name}.when", spec.when),
120
+ "after": spec.after,
121
+ "once": bool(spec.once),
122
+ "patch": [{"path": str(path), "value": patch_value(value)} for path, value in spec.set.items()],
123
+ "stop": bool(spec.stop),
124
+ "run": call_of(tree, f"rules.{spec.name}.run", spec.run),
125
+ })
126
+ return out
127
+
128
+
129
+ def sets_of(node):
130
+ on = node.keys.get("on")
131
+ if on is None:
132
+ on = getattr(node.target, "on", None) if node.target is not None else None
133
+ if on is None:
134
+ on = DEFAULT_ON
135
+ if isinstance(on, str):
136
+ on = (on,)
137
+ return [SET_PREFIX.get(str(name), str(name)) for name in on]
138
+
139
+
140
+ def series_of(tree):
141
+ series = []
142
+ for node in tree.nodes.values():
143
+ if node.slot == "loss":
144
+ series.append(f"train/{node.name}")
145
+ series.extend(f"{prefix}/{node.name}" for prefix in sets_of(node))
146
+ elif node.slot == "metric":
147
+ series.extend(f"{prefix}/{node.name}" for prefix in sets_of(node))
148
+ for node in tree.nodes.values():
149
+ if node.slot == "optimizer":
150
+ series.extend((f"lr/{node.name}", f"minimizes/{node.name}"))
151
+ track = train_of(tree).get("track")
152
+ if isinstance(track, str):
153
+ track = [track]
154
+ for path in track or ():
155
+ series.append(f"track/{path}")
156
+ return list(dict.fromkeys(series))
kalfa/core/predict.py ADDED
@@ -0,0 +1,290 @@
1
+ import fnmatch
2
+
3
+ import numpy
4
+ import pandas
5
+ import torch
6
+
7
+ from .errors import KalfaError, RunError
8
+ from .record import atomic
9
+
10
+
11
+ WIDE = 32
12
+
13
+
14
+ def batch_size(batch):
15
+ if isinstance(batch, dict):
16
+ values = batch.values()
17
+ elif isinstance(batch, (list, tuple)):
18
+ values = batch
19
+ else:
20
+ values = [batch]
21
+ for value in values:
22
+ shape = getattr(value, "shape", None)
23
+ if shape is not None and len(shape):
24
+ return int(shape[0])
25
+ return 1
26
+
27
+
28
+ def enter(ctx, batch):
29
+ ctx.batches += 1
30
+ return batch if ctx.device is None else ctx.device.move(batch)
31
+
32
+
33
+ def empty(loader):
34
+ try:
35
+ return len(loader) == 0
36
+ except TypeError:
37
+ return False
38
+
39
+
40
+ def as_matrix(value):
41
+ if isinstance(value, torch.Tensor):
42
+ value = value.detach().cpu().numpy()
43
+ value = numpy.asarray(value)
44
+ if value.ndim == 0:
45
+ value = value.reshape(1)
46
+ return value.reshape(len(value), -1)
47
+
48
+
49
+ def spread(label, values):
50
+ values = numpy.asarray(values)
51
+ if values.ndim <= 1:
52
+ return {label: values}
53
+ if values.shape[1] == 1:
54
+ return {label: values[:, 0]}
55
+ return {f"{label}_{position}": values[:, position] for position in range(values.shape[1])}
56
+
57
+
58
+ def target_names(data, batch=None):
59
+ names = [str(name) for name in getattr(data, "target_names", None) or ()]
60
+ return names if batch is None else [name for name in names if name in batch]
61
+
62
+
63
+ def is_label(matrix):
64
+ return bool(numpy.issubdtype(matrix.dtype, numpy.integer) or matrix.dtype == bool)
65
+
66
+
67
+ def labelled(data, name, truths):
68
+ truth = truths.get(name)
69
+ if truth is not None:
70
+ return is_label(truth)
71
+ asks = getattr(data, "is_label", None)
72
+ return bool(asks(name)) if callable(asks) else False
73
+
74
+
75
+ def rescales(data, name):
76
+ asks = getattr(data, "rescales", None)
77
+ return bool(asks(name)) if callable(asks) else False
78
+
79
+
80
+ def inverse(data, name, matrix):
81
+ transform = getattr(data, "inverse", None)
82
+ if not callable(transform):
83
+ return matrix
84
+ values = matrix[:, 0] if matrix.ndim == 2 and matrix.shape[1] == 1 else matrix
85
+ return numpy.asarray(transform(name, values))
86
+
87
+
88
+ def decoded(data, name, indexes):
89
+ decode = getattr(data, "decode", None)
90
+ if not callable(decode):
91
+ return None
92
+ return numpy.asarray(decode(name, indexes))
93
+
94
+
95
+ def pairings(ctx, outputs, targets):
96
+ pairs = {}
97
+ for entry in [*ctx.losses.values(), *ctx.metrics.values()]:
98
+ pred, target = getattr(entry, "pred", None), getattr(entry, "target", None)
99
+ if pred in outputs and isinstance(target, str):
100
+ pairs.setdefault(pred, target)
101
+ for name in outputs:
102
+ if name in targets:
103
+ pairs.setdefault(name, name)
104
+ if len(targets) == 1:
105
+ for name in outputs:
106
+ pairs.setdefault(name, targets[0])
107
+ return pairs
108
+
109
+
110
+ def rows_of(data, set_name, count, loader=None, own=True):
111
+ rows = getattr(getattr(loader, "dataset", None), "rows", None)
112
+ if rows is not None and len(rows) == count:
113
+ return numpy.asarray(rows)
114
+ rows = getattr(data, "rows", None)
115
+ if own and callable(rows):
116
+ try:
117
+ found = numpy.asarray(rows(set_name))
118
+ except KalfaError:
119
+ found = None
120
+ if found is not None and len(found) == count:
121
+ return found
122
+ return numpy.arange(count)
123
+
124
+
125
+ def aligned(frame, rows, count):
126
+ if frame is None or len(frame) == count:
127
+ return frame
128
+ try:
129
+ picked = frame.loc[list(rows)]
130
+ except KeyError:
131
+ picked = None
132
+ if picked is None or len(picked) != count:
133
+ raise RunError(f"{count} predicted rows cannot be aligned with the {len(frame)} rows of the frame; the "
134
+ f"frame needs a unique index")
135
+ return picked
136
+
137
+
138
+ def spectator_columns(data, set_name, count, frame=None):
139
+ if frame is not None:
140
+ patterns = [str(pattern) for pattern in getattr(data, "spectator_patterns", None) or ()]
141
+ return {column: frame[column].to_numpy() for column in frame.columns
142
+ if any(fnmatch.fnmatchcase(str(column), pattern) for pattern in patterns)}
143
+ extra = data.spectators(set_name) if callable(getattr(data, "spectators", None)) else None
144
+ if extra is None:
145
+ return {}
146
+ if isinstance(extra, pandas.DataFrame):
147
+ extra = {column: extra[column].to_numpy() for column in extra.columns}
148
+ return {name: numpy.asarray(values) for name, values in dict(extra).items() if len(values) == count}
149
+
150
+
151
+ def collect_outputs(ctx, loader, entry=None):
152
+ data = ctx.data
153
+ names = None
154
+ guesses, truths = {}, {}
155
+ with torch.no_grad():
156
+ for batch in loader:
157
+ batch = enter(ctx, batch)
158
+ if names is None:
159
+ names = target_names(data, batch)
160
+ outputs = entry.call(batch) if entry is not None else ctx.step.predict(batch, ctx)
161
+ for name, value in outputs.items():
162
+ guesses.setdefault(name, []).append(as_matrix(value))
163
+ for name in names:
164
+ truths.setdefault(name, []).append(as_matrix(batch[name]))
165
+ guesses = {name: numpy.concatenate(pieces) for name, pieces in guesses.items()}
166
+ truths = {name: numpy.concatenate(pieces) for name, pieces in truths.items()}
167
+ return guesses, truths
168
+
169
+
170
+ def spread_truth(columns, data, name, matrix):
171
+ columns.update(spread(name, inverse(data, name, matrix) if rescales(data, name) else matrix))
172
+
173
+
174
+ def truth_columns(columns, data, truths, frame):
175
+ if frame is not None:
176
+ for name in target_names(data):
177
+ matrix = truths.get(name)
178
+ if matrix is not None and matrix.shape[1] > 1 and not is_label(matrix):
179
+ spread_truth(columns, data, name, matrix)
180
+ elif name in frame.columns:
181
+ columns[name] = frame[name].to_numpy()
182
+ return
183
+ for name, matrix in truths.items():
184
+ if is_label(matrix):
185
+ indexes = matrix[:, 0] if matrix.shape[1] == 1 else matrix.argmax(axis=1)
186
+ labels = decoded(data, name, indexes)
187
+ columns[name] = indexes if labels is None else labels
188
+ else:
189
+ spread_truth(columns, data, name, matrix)
190
+
191
+
192
+ def softmax(matrix):
193
+ shifted = matrix.astype(numpy.float64) - matrix.max(axis=1, keepdims=True)
194
+ weights = numpy.exp(shifted)
195
+ return weights / weights.sum(axis=1, keepdims=True)
196
+
197
+
198
+ def sigmoid(values):
199
+ return 0.5 * (1.0 + numpy.tanh(0.5 * values.astype(numpy.float64)))
200
+
201
+
202
+ def class_names(data, target, count):
203
+ indexes = numpy.arange(count)
204
+ try:
205
+ labels = decoded(data, target, indexes)
206
+ except (KalfaError, IndexError, KeyError, ValueError):
207
+ labels = None
208
+ if labels is None or len(labels) != count:
209
+ return [str(index) for index in indexes]
210
+ return [str(label) for label in labels]
211
+
212
+
213
+ def class_scores(data, target, width):
214
+ if width == 1:
215
+ return True
216
+ try:
217
+ labels = decoded(data, target, numpy.arange(width))
218
+ except (KalfaError, IndexError, KeyError, ValueError):
219
+ return False
220
+ return labels is None or len(labels) == width
221
+
222
+
223
+ def probability_columns(columns, data, name, target, matrix):
224
+ if matrix.shape[1] > 1:
225
+ probabilities = softmax(matrix)
226
+ for position, label in enumerate(class_names(data, target, matrix.shape[1])):
227
+ columns[f"prob_{name}_{label}"] = probabilities[:, position]
228
+ else:
229
+ positive = class_names(data, target, 2)[1]
230
+ columns[f"prob_{name}_{positive}"] = sigmoid(matrix[:, 0])
231
+
232
+
233
+ def guess_columns(columns, ctx, guesses, truths):
234
+ data = ctx.data
235
+ targets = target_names(data) or list(truths)
236
+ pairs = pairings(ctx, list(guesses), targets)
237
+ for name, matrix in guesses.items():
238
+ target = pairs.get(name)
239
+ if target is not None and target not in targets:
240
+ target = None
241
+ if target is not None and labelled(data, target, truths) and not class_scores(data, target, matrix.shape[1]):
242
+ target = None
243
+ if target is None:
244
+ if matrix.shape[1] > WIDE:
245
+ continue
246
+ columns.update(spread(f"pred_{name}", matrix))
247
+ elif labelled(data, target, truths):
248
+ indexes = matrix.argmax(axis=1) if matrix.shape[1] > 1 else (matrix[:, 0] > 0).astype(numpy.int64)
249
+ labels = decoded(data, target, indexes)
250
+ columns[f"pred_{name}"] = indexes if labels is None else labels
251
+ if labels is not None:
252
+ columns[f"raw_{name}"] = indexes
253
+ probability_columns(columns, data, name, target, matrix)
254
+ elif rescales(data, target):
255
+ columns.update(spread(f"pred_{name}", inverse(data, target, matrix)))
256
+ columns.update(spread(f"raw_{name}", matrix))
257
+ else:
258
+ columns.update(spread(f"pred_{name}", matrix))
259
+
260
+
261
+ def predictions_table(ctx, set_name="test", loader=None, frame=None, entry=None):
262
+ own = loader is None
263
+ if own:
264
+ loader = ctx.loaders().get(set_name)
265
+ if loader is None or empty(loader):
266
+ return pandas.DataFrame()
267
+ ctx.set_modes(False)
268
+ ctx.set_name = set_name
269
+ data = ctx.data
270
+ guesses, truths = collect_outputs(ctx, loader, entry)
271
+ if not guesses:
272
+ return pandas.DataFrame()
273
+ count = len(next(iter(guesses.values())))
274
+ rows = rows_of(data, set_name, count, loader, own)
275
+ frame = aligned(frame, rows, count)
276
+ columns = {"row": rows}
277
+ truth_columns(columns, data, truths, frame)
278
+ guess_columns(columns, ctx, guesses, truths)
279
+ for name, values in spectator_columns(data, set_name, count, frame).items():
280
+ columns.setdefault(name, values)
281
+ return pandas.DataFrame(columns)
282
+
283
+
284
+ def write_predictions(ctx, set_name):
285
+ table = predictions_table(ctx, set_name)
286
+ if ctx.record is not None and len(table):
287
+ name = "predictions.parquet" if set_name == "test" else f"predictions_{set_name}.parquet"
288
+ with atomic(ctx.record.path(name)) as temporary:
289
+ table.to_parquet(temporary, index=False)
290
+ return table
@@ -0,0 +1,122 @@
1
+ from torch import nn
2
+
3
+
4
+ class Objective(nn.Module):
5
+ on = ("train", "valid", "test")
6
+ every = 1
7
+ fixed = ()
8
+ name = ""
9
+ path = ""
10
+
11
+ def bind(self, ctx):
12
+ pass
13
+
14
+ def forward(self, outputs, batch, ctx):
15
+ raise NotImplementedError
16
+
17
+
18
+ class ContextMetric:
19
+ on = ("train", "valid", "test")
20
+ every = 1
21
+ name = ""
22
+ path = ""
23
+
24
+ def bind(self, ctx):
25
+ pass
26
+
27
+ def reset(self):
28
+ raise NotImplementedError
29
+
30
+ def update(self, outputs, batch, ctx):
31
+ raise NotImplementedError
32
+
33
+ def compute(self):
34
+ raise NotImplementedError
35
+
36
+
37
+ class Step:
38
+ order = None
39
+ amp = False
40
+ grad_clip = None
41
+ accumulate = 1
42
+ stepped = True
43
+ fixed = ()
44
+ persist = ()
45
+ name = "step"
46
+ path = "train.step"
47
+
48
+ def bind(self, ctx):
49
+ pass
50
+
51
+ def train_turn(self, ctx):
52
+ raise NotImplementedError
53
+
54
+ def train_batch(self, batch, ctx):
55
+ raise NotImplementedError
56
+
57
+ def eval_batch(self, batch, ctx):
58
+ raise NotImplementedError
59
+
60
+ def predict(self, batch, ctx):
61
+ raise NotImplementedError
62
+
63
+ def state_dict(self):
64
+ return {name: getattr(self, name) for name in self.persist}
65
+
66
+ def load_state_dict(self, state):
67
+ for name, value in (state or {}).items():
68
+ setattr(self, name, value)
69
+
70
+
71
+ class Trigger:
72
+ persist = ()
73
+ fixed = ()
74
+ name = ""
75
+ path = ""
76
+
77
+ def bind(self, ctx):
78
+ pass
79
+
80
+ def fires(self, ctx):
81
+ raise NotImplementedError
82
+
83
+ def reset(self):
84
+ pass
85
+
86
+ def describe(self):
87
+ return type(self).__name__
88
+
89
+ def state_dict(self):
90
+ return {name: getattr(self, name) for name in self.persist}
91
+
92
+ def load_state_dict(self, state):
93
+ for name, value in (state or {}).items():
94
+ setattr(self, name, value)
95
+
96
+
97
+ class Action:
98
+ name = ""
99
+ path = ""
100
+
101
+ def bind(self, ctx):
102
+ pass
103
+
104
+ def __call__(self, ctx):
105
+ raise NotImplementedError
106
+
107
+
108
+ class Logger:
109
+ name = ""
110
+ path = ""
111
+
112
+ def bind(self, ctx):
113
+ pass
114
+
115
+ def turn(self, ctx, line):
116
+ pass
117
+
118
+ def update(self, ctx, line):
119
+ pass
120
+
121
+ def close(self, ctx):
122
+ pass