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.
- kalfa/__init__.py +19 -0
- kalfa/api.py +284 -0
- kalfa/board/__init__.py +1 -0
- kalfa/board/answers.py +419 -0
- kalfa/board/server.py +536 -0
- kalfa/board/static/api.js +176 -0
- kalfa/board/static/app.js +122 -0
- kalfa/board/static/board.css +228 -0
- kalfa/board/static/charts.css +46 -0
- kalfa/board/static/components/badge.js +14 -0
- kalfa/board/static/components/chart.js +204 -0
- kalfa/board/static/components/diagram.js +857 -0
- kalfa/board/static/components/error.js +17 -0
- kalfa/board/static/components/loading.js +7 -0
- kalfa/board/static/components/progress.js +25 -0
- kalfa/board/static/components/settings.js +418 -0
- kalfa/board/static/components/spark.js +43 -0
- kalfa/board/static/components/table.js +156 -0
- kalfa/board/static/diagram.css +76 -0
- kalfa/board/static/format.js +138 -0
- kalfa/board/static/index.html +34 -0
- kalfa/board/static/plot.js +594 -0
- kalfa/board/static/predictions.css +20 -0
- kalfa/board/static/store.js +215 -0
- kalfa/board/static/views/compare.js +185 -0
- kalfa/board/static/views/home.js +90 -0
- kalfa/board/static/views/record/config.js +81 -0
- kalfa/board/static/views/record/curves.js +137 -0
- kalfa/board/static/views/record/data.js +378 -0
- kalfa/board/static/views/record/files.js +128 -0
- kalfa/board/static/views/record/log.js +164 -0
- kalfa/board/static/views/record/model.js +286 -0
- kalfa/board/static/views/record/overview.js +216 -0
- kalfa/board/static/views/record/plots.js +103 -0
- kalfa/board/static/views/record/predictions.js +834 -0
- kalfa/board/static/views/record.js +179 -0
- kalfa/board/static/views/runs.js +124 -0
- kalfa/board/static/views/sweep.js +282 -0
- kalfa/board/watch.py +87 -0
- kalfa/cli.py +371 -0
- kalfa/commands/__init__.py +0 -0
- kalfa/commands/common.py +8 -0
- kalfa/commands/export.py +117 -0
- kalfa/commands/generate.py +31 -0
- kalfa/commands/guide/__init__.py +101 -0
- kalfa/commands/guide/about.py +93 -0
- kalfa/commands/guide/calls.py +102 -0
- kalfa/commands/guide/sections.py +186 -0
- kalfa/commands/guide/seen.py +307 -0
- kalfa/commands/guide/show.py +106 -0
- kalfa/commands/guide/training.py +333 -0
- kalfa/commands/guide/words.py +97 -0
- kalfa/commands/legos.py +508 -0
- kalfa/commands/plots.py +27 -0
- kalfa/commands/report/__init__.py +64 -0
- kalfa/commands/report/page.py +112 -0
- kalfa/commands/report/predictions.py +216 -0
- kalfa/commands/report/sections.py +356 -0
- kalfa/commands/stop.py +60 -0
- kalfa/commands/sweep/__init__.py +202 -0
- kalfa/commands/sweep/checks.py +98 -0
- kalfa/commands/sweep/plan.py +183 -0
- kalfa/commands/sweep/points.py +123 -0
- kalfa/commands/sweep/runner.py +100 -0
- kalfa/commands/sweep/space.py +67 -0
- kalfa/commands/sweep/strategies.py +228 -0
- kalfa/core/__init__.py +0 -0
- kalfa/core/aliases.py +229 -0
- kalfa/core/build.py +727 -0
- kalfa/core/check.py +550 -0
- kalfa/core/coerce.py +36 -0
- kalfa/core/context.py +149 -0
- kalfa/core/device.py +63 -0
- kalfa/core/errors.py +45 -0
- kalfa/core/graph.py +304 -0
- kalfa/core/history.py +70 -0
- kalfa/core/loop.py +710 -0
- kalfa/core/make.py +121 -0
- kalfa/core/plan.py +156 -0
- kalfa/core/predict.py +290 -0
- kalfa/core/protocols.py +122 -0
- kalfa/core/record.py +347 -0
- kalfa/core/refs.py +88 -0
- kalfa/core/rng.py +100 -0
- kalfa/core/rules.py +513 -0
- kalfa/core/run.py +568 -0
- kalfa/core/spec.py +413 -0
- kalfa/core/state.py +100 -0
- kalfa/core/wrap.py +671 -0
- kalfa/records/__init__.py +24 -0
- kalfa/records/compare.py +86 -0
- kalfa/records/reader.py +561 -0
- kalfa/records/stats/__init__.py +36 -0
- kalfa/records/stats/choices.py +282 -0
- kalfa/records/stats/classes.py +236 -0
- kalfa/records/stats/measures.py +144 -0
- kalfa/records/stats/pairs.py +225 -0
- kalfa/records/stats/tables.py +153 -0
- kalfa/records/sweep.py +244 -0
- kalfa/std/__init__.py +17 -0
- kalfa/std/actions.py +35 -0
- kalfa/std/common.py +56 -0
- kalfa/std/data/__init__.py +1 -0
- kalfa/std/data/base.py +45 -0
- kalfa/std/data/columns.py +190 -0
- kalfa/std/data/flow.py +317 -0
- kalfa/std/data/images.py +443 -0
- kalfa/std/data/options.py +147 -0
- kalfa/std/data/pre.py +84 -0
- kalfa/std/data/splits.py +140 -0
- kalfa/std/data/stream.py +294 -0
- kalfa/std/data/table.py +642 -0
- kalfa/std/data/text.py +436 -0
- kalfa/std/data/transforms.py +327 -0
- kalfa/std/data/weights.py +113 -0
- kalfa/std/data/window.py +135 -0
- kalfa/std/figure.py +37 -0
- kalfa/std/init.py +72 -0
- kalfa/std/loggers.py +105 -0
- kalfa/std/losses.py +161 -0
- kalfa/std/metrics.py +463 -0
- kalfa/std/models.py +122 -0
- kalfa/std/notes.py +1553 -0
- kalfa/std/objectives.py +360 -0
- kalfa/std/plots/__init__.py +75 -0
- kalfa/std/plots/classification.py +207 -0
- kalfa/std/plots/common.py +44 -0
- kalfa/std/plots/curves.py +56 -0
- kalfa/std/plots/images.py +188 -0
- kalfa/std/plots/pairs.py +66 -0
- kalfa/std/plots/predictions.py +154 -0
- kalfa/std/samplers.py +261 -0
- kalfa/std/samples.py +72 -0
- kalfa/std/schedules.py +74 -0
- kalfa/std/steps.py +133 -0
- kalfa/std/triggers.py +135 -0
- kalfa/summary.py +296 -0
- kalfa/tui/__init__.py +4 -0
- kalfa/tui/app.py +107 -0
- kalfa/tui/config.py +61 -0
- kalfa/tui/guide.py +100 -0
- kalfa/tui/jobs.py +97 -0
- kalfa/tui/legos.py +62 -0
- kalfa/tui/records.py +439 -0
- kalfa-3.0.0.dev0.dist-info/METADATA +261 -0
- kalfa-3.0.0.dev0.dist-info/RECORD +148 -0
- kalfa-3.0.0.dev0.dist-info/WHEEL +4 -0
- kalfa-3.0.0.dev0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import Chart from "../../components/chart.js";
|
|
2
|
+
import { messageOf } from "../../components/error.js";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const SETS = ["train", "val", "test"];
|
|
6
|
+
const FAMILIES = ["loss", "lr", "grad_norm"];
|
|
7
|
+
const AXES = ["turn", "update"];
|
|
8
|
+
const LIMIT = 2000;
|
|
9
|
+
|
|
10
|
+
function historyName(key) {
|
|
11
|
+
const cut = key.indexOf("/");
|
|
12
|
+
return cut > 0 && SETS.includes(key.slice(0, cut)) ? key.slice(cut + 1) : key;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function updateName(key) {
|
|
16
|
+
const cut = key.indexOf("/");
|
|
17
|
+
const family = cut > 0 ? key.slice(0, cut) : key;
|
|
18
|
+
return FAMILIES.includes(family) ? family : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function groupsOf(table, axis, nameOf) {
|
|
22
|
+
if (!table || !Array.isArray(table[axis])) return [];
|
|
23
|
+
const groups = new Map();
|
|
24
|
+
for (const key of Object.keys(table)) {
|
|
25
|
+
if (AXES.includes(key) || !Array.isArray(table[key])) continue;
|
|
26
|
+
const name = nameOf(key);
|
|
27
|
+
if (name === null) continue;
|
|
28
|
+
if (!groups.has(name)) groups.set(name, []);
|
|
29
|
+
groups.get(name).push({ name: key, x: table[axis], y: table[key] });
|
|
30
|
+
}
|
|
31
|
+
return Array.from(groups, ([title, series]) => ({ title, series }));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
name: "CurvesTab",
|
|
36
|
+
components: { "k-chart": Chart },
|
|
37
|
+
props: {
|
|
38
|
+
record: { type: Object, default: null },
|
|
39
|
+
path: { type: String, default: "" },
|
|
40
|
+
api: { default: null },
|
|
41
|
+
version: { type: Number, default: 0 },
|
|
42
|
+
},
|
|
43
|
+
data() {
|
|
44
|
+
return { history: null, steps: null, error: "", loading: false, logy: false, filter: "", ruled: true };
|
|
45
|
+
},
|
|
46
|
+
computed: {
|
|
47
|
+
fired() { return this.record && Array.isArray(this.record.marks) ? this.record.marks : []; },
|
|
48
|
+
historyGroups() { return groupsOf(this.history, "turn", historyName); },
|
|
49
|
+
stepGroups() { return groupsOf(this.steps, "update", updateName); },
|
|
50
|
+
historyCharts() { return this.decorate(this.historyGroups, "turn"); },
|
|
51
|
+
stepCharts() { return this.decorate(this.stepGroups, "update"); },
|
|
52
|
+
turns() { return this.history && Array.isArray(this.history.turn) ? this.history.turn.length : 0; },
|
|
53
|
+
updates() { return this.steps && Array.isArray(this.steps.update) ? this.steps.update.length : 0; },
|
|
54
|
+
countText() { return this.turns + " turns, " + this.updates + " optimizer updates"; },
|
|
55
|
+
waiting() { return this.loading && !this.history; },
|
|
56
|
+
},
|
|
57
|
+
watch: {
|
|
58
|
+
record() { this.load(); },
|
|
59
|
+
},
|
|
60
|
+
mounted() { this.load(); },
|
|
61
|
+
methods: {
|
|
62
|
+
marksOn(axis) {
|
|
63
|
+
if (!this.ruled) return [];
|
|
64
|
+
return this.fired
|
|
65
|
+
.map(mark => ({ x: mark[axis], text: (mark.rules || []).join(", ") }))
|
|
66
|
+
.filter(mark => typeof mark.x === "number");
|
|
67
|
+
},
|
|
68
|
+
decorate(groups, xlabel) {
|
|
69
|
+
const wanted = this.filter.trim().toLowerCase();
|
|
70
|
+
const logy = this.logy;
|
|
71
|
+
const marks = this.marksOn(xlabel);
|
|
72
|
+
return groups
|
|
73
|
+
.filter(group => !wanted || group.title.toLowerCase().includes(wanted))
|
|
74
|
+
.map(group => ({
|
|
75
|
+
key: group.title + (logy ? " log" : ""),
|
|
76
|
+
title: group.title,
|
|
77
|
+
series: group.series,
|
|
78
|
+
options: { xlabel, ylabel: group.title, logy, marks },
|
|
79
|
+
}));
|
|
80
|
+
},
|
|
81
|
+
toggleLog() { this.logy = !this.logy; },
|
|
82
|
+
toggleRules() { this.ruled = !this.ruled; },
|
|
83
|
+
async load() {
|
|
84
|
+
if (!this.api) {
|
|
85
|
+
this.error = "the api prop is missing";
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const path = this.path;
|
|
89
|
+
this.loading = true;
|
|
90
|
+
try {
|
|
91
|
+
const found = await Promise.all([
|
|
92
|
+
this.api.getJson(this.api.recordUrl(path, "series", { limit: LIMIT })),
|
|
93
|
+
this.api.getJson(this.api.recordUrl(path, "updates", { limit: LIMIT })),
|
|
94
|
+
]);
|
|
95
|
+
if (path !== this.path) return;
|
|
96
|
+
this.history = Object.freeze(found[0] || {});
|
|
97
|
+
this.steps = Object.freeze(found[1] || {});
|
|
98
|
+
this.error = "";
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (path === this.path) this.error = messageOf(error);
|
|
101
|
+
} finally {
|
|
102
|
+
this.loading = false;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
template: `
|
|
107
|
+
<div class="page pane-curves">
|
|
108
|
+
<div class="muted small">One chart per series of history.jsonl, one point per turn; train, val and test of the same name share
|
|
109
|
+
a chart. Below, the optimizer updates of updates.jsonl: the batch loss, the learning rate and the gradient norm of every
|
|
110
|
+
optimizer. A dotted line marks every turn a rule fired, with the names of the rules. Expand opens a chart large.</div>
|
|
111
|
+
<div class="toolbar">
|
|
112
|
+
<input class="filter" type="search" v-model="filter" placeholder="filter charts" spellcheck="false">
|
|
113
|
+
<button type="button" class="tool" :class="{ on: logy }" @click="toggleLog">log y on every chart</button>
|
|
114
|
+
<button type="button" class="tool" :class="{ on: ruled }" v-if="fired.length" @click="toggleRules">rule marks</button>
|
|
115
|
+
<button type="button" class="tool" :disabled="loading" @click="load">refresh</button>
|
|
116
|
+
<span class="muted small">{{ countText }}</span>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="error-box" v-if="error">{{ error }}</div>
|
|
119
|
+
<div class="loading" v-if="waiting">reading the series</div>
|
|
120
|
+
<template v-if="history">
|
|
121
|
+
<h3 class="section-title">per turn</h3>
|
|
122
|
+
<div class="grid" v-if="historyCharts.length">
|
|
123
|
+
<k-chart v-for="chart in historyCharts" :key="chart.key" :title="chart.title" kind="lines" :data="chart.series"
|
|
124
|
+
:options="chart.options"></k-chart>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="empty" v-else>no series written yet</div>
|
|
127
|
+
</template>
|
|
128
|
+
<template v-if="steps">
|
|
129
|
+
<h3 class="section-title">per optimizer update</h3>
|
|
130
|
+
<div class="grid" v-if="stepCharts.length">
|
|
131
|
+
<k-chart v-for="chart in stepCharts" :key="chart.key" :title="chart.title" kind="lines" :data="chart.series"
|
|
132
|
+
:options="chart.options"></k-chart>
|
|
133
|
+
</div>
|
|
134
|
+
<div class="empty" v-else>no update written yet</div>
|
|
135
|
+
</template>
|
|
136
|
+
</div>`,
|
|
137
|
+
};
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { Diagram } from "../../components/diagram.js";
|
|
2
|
+
import Table from "../../components/table.js";
|
|
3
|
+
import { integer } from "../../format.js";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const FACTS = ["kind", "labels", "image_shape", "mode", "transform", "train_transform", "drops", "spectators", "filter"];
|
|
7
|
+
const SET_ORDER = ["train", "valid", "test"];
|
|
8
|
+
const CROWDED = 40;
|
|
9
|
+
const COLUMN_COLUMNS = [
|
|
10
|
+
{ key: "name", label: "column", className: "mono" },
|
|
11
|
+
{ key: "steps", label: "steps", className: "mono", sortable: false },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function present(value) {
|
|
15
|
+
if (value === null || value === undefined || value === "") return false;
|
|
16
|
+
return !(Array.isArray(value) && !value.length);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function shown(value) {
|
|
20
|
+
if (!present(value)) return "";
|
|
21
|
+
if (Array.isArray(value)) return value.map(shown).join(", ");
|
|
22
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
23
|
+
return String(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function listOf(value) {
|
|
27
|
+
if (Array.isArray(value)) return value.map(String);
|
|
28
|
+
if (value && typeof value === "object") return Object.keys(value);
|
|
29
|
+
return present(value) ? [String(value)] : [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function chainOf(value) {
|
|
33
|
+
return Array.isArray(value) ? value.map(shown).join(" → ") : shown(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function fieldRow(name, value) {
|
|
37
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
38
|
+
return {
|
|
39
|
+
name,
|
|
40
|
+
type: shown(value.type || value.dtype),
|
|
41
|
+
role: value.target ? "target" : "input",
|
|
42
|
+
columns: listOf(value.columns),
|
|
43
|
+
chain: chainOf(value.preprocessors || value.chain),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return { name, type: shown(value), role: "", columns: [], chain: "" };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isMapping(value) {
|
|
50
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function setOrder(names) {
|
|
54
|
+
const known = SET_ORDER.filter(name => names.includes(name));
|
|
55
|
+
return [...known, ...names.filter(name => !SET_ORDER.includes(name))];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function perSet(value, sets) {
|
|
59
|
+
if (!isMapping(value)) return false;
|
|
60
|
+
const keys = Object.keys(value);
|
|
61
|
+
return keys.length > 0 && keys.every(key => sets.includes(key) && (value[key] === null || typeof value[key] === "number"));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default {
|
|
65
|
+
name: "DataTab",
|
|
66
|
+
components: { KDiagram: Diagram, KTable: Table },
|
|
67
|
+
props: {
|
|
68
|
+
record: { type: Object, default: null },
|
|
69
|
+
path: { type: String, default: "" },
|
|
70
|
+
api: { default: null },
|
|
71
|
+
version: { type: Number, default: 0 },
|
|
72
|
+
},
|
|
73
|
+
data() {
|
|
74
|
+
return { selected: null };
|
|
75
|
+
},
|
|
76
|
+
created() {
|
|
77
|
+
this.drawn = null;
|
|
78
|
+
},
|
|
79
|
+
watch: {
|
|
80
|
+
path() { this.selected = null; },
|
|
81
|
+
},
|
|
82
|
+
computed: {
|
|
83
|
+
dataReport() {
|
|
84
|
+
const found = this.record && this.record.data_report;
|
|
85
|
+
return isMapping(found) ? found : null;
|
|
86
|
+
},
|
|
87
|
+
report() { return this.dataReport && isMapping(this.dataReport.report) ? this.dataReport.report : {}; },
|
|
88
|
+
kind() { return this.dataReport ? shown(this.dataReport.kind) : ""; },
|
|
89
|
+
pipeline() {
|
|
90
|
+
const found = this.dataReport && this.dataReport.pipeline;
|
|
91
|
+
if (!isMapping(found) || !Array.isArray(found.nodes) || !found.nodes.length) return null;
|
|
92
|
+
const key = JSON.stringify(found);
|
|
93
|
+
if (this.drawn && this.drawn.key === key) return this.drawn.graph;
|
|
94
|
+
const graph = { nodes: found.nodes, edges: Array.isArray(found.edges) ? found.edges : [], groups: Array.isArray(found.groups) ? found.groups : [] };
|
|
95
|
+
this.drawn = { key, graph };
|
|
96
|
+
return graph;
|
|
97
|
+
},
|
|
98
|
+
crowded() { return Boolean(this.pipeline) && this.pipeline.nodes.length > CROWDED; },
|
|
99
|
+
collapsed() {
|
|
100
|
+
if (!this.crowded) return [];
|
|
101
|
+
return this.pipeline.groups.filter(group => !group.parent).map(group => group.id);
|
|
102
|
+
},
|
|
103
|
+
diagramHeight() { return this.crowded ? 560 : 420; },
|
|
104
|
+
diagramTitle() {
|
|
105
|
+
const name = String(this.path || "").split("/").filter(Boolean).pop() || "record";
|
|
106
|
+
return name + "_data";
|
|
107
|
+
},
|
|
108
|
+
groupLabels() {
|
|
109
|
+
const labels = {};
|
|
110
|
+
for (const group of this.pipeline ? this.pipeline.groups : []) labels[group.id] = shown(group.label);
|
|
111
|
+
return labels;
|
|
112
|
+
},
|
|
113
|
+
selectedRows() {
|
|
114
|
+
const node = this.selected;
|
|
115
|
+
if (!node) return [];
|
|
116
|
+
const rows = [{ name: "kind", value: shown(node.kind) }];
|
|
117
|
+
if (present(node.detail)) rows.push({ name: "detail", value: shown(node.detail) });
|
|
118
|
+
if (typeof node.count === "number") rows.push({ name: "count", value: integer(node.count) });
|
|
119
|
+
if (node.group) rows.push({ name: "group", value: this.groupLabels[node.group] || shown(node.group) });
|
|
120
|
+
const into = this.pipeline ? this.pipeline.edges.filter(edge => edge.to === node.id && present(edge.label)) : [];
|
|
121
|
+
if (into.length) rows.push({ name: "arrives as", value: into.map(edge => shown(edge.label)).join(", ") });
|
|
122
|
+
return rows;
|
|
123
|
+
},
|
|
124
|
+
setCounts() { return isMapping(this.report.rows) ? this.report.rows : {}; },
|
|
125
|
+
setExtras() {
|
|
126
|
+
const known = [...Object.keys(this.setCounts), ...SET_ORDER];
|
|
127
|
+
return Object.keys(this.report).filter(key => key !== "rows" && perSet(this.report[key], known));
|
|
128
|
+
},
|
|
129
|
+
sets() {
|
|
130
|
+
const names = new Set(Object.keys(this.setCounts));
|
|
131
|
+
for (const key of this.setExtras) Object.keys(this.report[key]).forEach(name => names.add(name));
|
|
132
|
+
return setOrder([...names]);
|
|
133
|
+
},
|
|
134
|
+
setRows() {
|
|
135
|
+
return this.sets.map(name => {
|
|
136
|
+
const row = { name, rows: this.setCounts[name] };
|
|
137
|
+
for (const key of this.setExtras) row[key] = this.report[key][name];
|
|
138
|
+
return row;
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
setTotal() { return Object.values(this.setCounts).reduce((sum, count) => sum + (Number(count) || 0), 0); },
|
|
142
|
+
columnColumns() { return COLUMN_COLUMNS; },
|
|
143
|
+
columnRows() {
|
|
144
|
+
if (!isMapping(this.report.columns)) return [];
|
|
145
|
+
return Object.entries(this.report.columns).map(([name, steps]) => ({ name, steps: chainOf(steps) || "as read" }));
|
|
146
|
+
},
|
|
147
|
+
manyColumns() { return this.columnRows.length > 12; },
|
|
148
|
+
reportFacts() {
|
|
149
|
+
const skipped = new Set(["rows", "columns", ...this.setExtras]);
|
|
150
|
+
return Object.entries(this.report)
|
|
151
|
+
.filter(([key, value]) => !skipped.has(key) && present(value))
|
|
152
|
+
.map(([name, value]) => ({ name, value: shown(value) }));
|
|
153
|
+
},
|
|
154
|
+
plan() { return (this.record && this.record.plan) || {}; },
|
|
155
|
+
planData() { return this.plan.data && typeof this.plan.data === "object" ? this.plan.data : {}; },
|
|
156
|
+
source() {
|
|
157
|
+
const nodes = this.plan.nodes && typeof this.plan.nodes === "object" ? this.plan.nodes : {};
|
|
158
|
+
return nodes.data && typeof nodes.data === "object" ? nodes.data : null;
|
|
159
|
+
},
|
|
160
|
+
sourceRows() {
|
|
161
|
+
const params = this.source && this.source.params && typeof this.source.params === "object" ? this.source.params : {};
|
|
162
|
+
return Object.entries(params).map(([name, value]) => ({ name, value: shown(value) }));
|
|
163
|
+
},
|
|
164
|
+
targets() {
|
|
165
|
+
const params = (this.source && this.source.params) || {};
|
|
166
|
+
return listOf(params.targets);
|
|
167
|
+
},
|
|
168
|
+
inputs() { return listOf(this.planData.inputs); },
|
|
169
|
+
exposes() { return listOf(this.planData.exposes); },
|
|
170
|
+
classes() { return listOf(this.planData.classes); },
|
|
171
|
+
fieldRows() {
|
|
172
|
+
const fields = this.planData.fields && typeof this.planData.fields === "object" ? this.planData.fields : {};
|
|
173
|
+
const targets = this.targets;
|
|
174
|
+
return Object.entries(fields).map(([name, value]) => {
|
|
175
|
+
const row = fieldRow(name, value);
|
|
176
|
+
if (!row.role && targets.includes(name)) row.role = "target";
|
|
177
|
+
return row;
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
fitted() { return this.record && this.record.fields && typeof this.record.fields === "object" ? this.record.fields : null; },
|
|
181
|
+
features() {
|
|
182
|
+
const features = this.fitted && Array.isArray(this.fitted.features) ? this.fitted.features : [];
|
|
183
|
+
return features.map((item, index) => ({
|
|
184
|
+
key: String(index),
|
|
185
|
+
pattern: shown(item.pattern),
|
|
186
|
+
columns: listOf(item.columns),
|
|
187
|
+
names: listOf(item.names),
|
|
188
|
+
chain: chainOf(item.chain),
|
|
189
|
+
}));
|
|
190
|
+
},
|
|
191
|
+
targetFields() {
|
|
192
|
+
const fields = this.fitted && Array.isArray(this.fitted.target_fields) ? this.fitted.target_fields : [];
|
|
193
|
+
return fields.map((item, index) => ({
|
|
194
|
+
key: String(index),
|
|
195
|
+
name: shown(item.name),
|
|
196
|
+
width: shown(item.width),
|
|
197
|
+
chain: chainOf(item.chain),
|
|
198
|
+
classes: listOf(item.classes),
|
|
199
|
+
weights: Array.isArray(item.class_weights) ? item.class_weights.map(shown).join(", ") : "",
|
|
200
|
+
}));
|
|
201
|
+
},
|
|
202
|
+
fittedTargets() {
|
|
203
|
+
if (!this.fitted || this.targetFields.length) return [];
|
|
204
|
+
return listOf(this.fitted.targets);
|
|
205
|
+
},
|
|
206
|
+
fittedClasses() { return this.fitted ? listOf(this.fitted.classes) : []; },
|
|
207
|
+
featureCount() { return this.fitted && Array.isArray(this.fitted.feature_names) ? this.fitted.feature_names.length : 0; },
|
|
208
|
+
facts() {
|
|
209
|
+
if (!this.fitted) return [];
|
|
210
|
+
return FACTS.filter(name => present(this.fitted[name])).map(name => ({ name, value: shown(this.fitted[name]) }));
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
methods: {
|
|
214
|
+
integer,
|
|
215
|
+
pick(node) { this.selected = node || null; },
|
|
216
|
+
},
|
|
217
|
+
template: `
|
|
218
|
+
<div class="page pane-data">
|
|
219
|
+
<template v-if="dataReport">
|
|
220
|
+
<div class="muted small">The data as the run read it (data.json): where the rows come from, how they are filtered and split
|
|
221
|
+
into sets, the chains fitted on the train rows and the tensors every batch carries. Click a box for its details.
|
|
222
|
+
Below, the sets and columns the run reported, then the data as the plan saw it and as the run fitted it.</div>
|
|
223
|
+
<section class="card" v-if="pipeline">
|
|
224
|
+
<div class="card-head"><span class="card-title">pipeline</span><span class="muted mono" v-if="kind">{{ kind }}</span></div>
|
|
225
|
+
<k-diagram :graph="pipeline" direction="LR" :height="diagramHeight" :collapsed="collapsed" :title="diagramTitle"
|
|
226
|
+
@select="pick"></k-diagram>
|
|
227
|
+
<div class="table-wrap" v-if="selected">
|
|
228
|
+
<table class="data">
|
|
229
|
+
<thead><tr><th colspan="2">{{ selected.label }}</th></tr></thead>
|
|
230
|
+
<tbody>
|
|
231
|
+
<tr v-for="row in selectedRows" :key="row.name">
|
|
232
|
+
<td class="muted">{{ row.name }}</td>
|
|
233
|
+
<td class="mono" style="white-space: normal; overflow-wrap: anywhere">{{ row.value }}</td>
|
|
234
|
+
</tr>
|
|
235
|
+
</tbody>
|
|
236
|
+
</table>
|
|
237
|
+
</div>
|
|
238
|
+
</section>
|
|
239
|
+
<div class="empty" v-else>the run could not draw the data flowchart; the tables below still describe the data</div>
|
|
240
|
+
<div class="grid">
|
|
241
|
+
<section class="card" v-if="setRows.length">
|
|
242
|
+
<div class="card-head"><span class="card-title">sets</span><span class="muted small">{{ integer(setTotal) }} rows in all</span></div>
|
|
243
|
+
<div class="table-wrap">
|
|
244
|
+
<table class="data">
|
|
245
|
+
<thead><tr><th>set</th><th class="num">rows</th><th class="num" v-for="key in setExtras" :key="key">{{ key }}</th></tr></thead>
|
|
246
|
+
<tbody>
|
|
247
|
+
<tr v-for="row in setRows" :key="row.name">
|
|
248
|
+
<td class="mono">{{ row.name }}</td>
|
|
249
|
+
<td class="num mono">{{ integer(row.rows) }}</td>
|
|
250
|
+
<td class="num mono" v-for="key in setExtras" :key="key">{{ integer(row[key]) }}</td>
|
|
251
|
+
</tr>
|
|
252
|
+
</tbody>
|
|
253
|
+
</table>
|
|
254
|
+
</div>
|
|
255
|
+
</section>
|
|
256
|
+
<section class="card" v-if="reportFacts.length">
|
|
257
|
+
<div class="card-head"><span class="card-title">report</span></div>
|
|
258
|
+
<div class="table-wrap">
|
|
259
|
+
<table class="data">
|
|
260
|
+
<tbody>
|
|
261
|
+
<tr v-for="row in reportFacts" :key="row.name">
|
|
262
|
+
<td class="muted">{{ row.name }}</td>
|
|
263
|
+
<td class="mono" style="white-space: normal; overflow-wrap: anywhere">{{ row.value }}</td>
|
|
264
|
+
</tr>
|
|
265
|
+
</tbody>
|
|
266
|
+
</table>
|
|
267
|
+
</div>
|
|
268
|
+
</section>
|
|
269
|
+
</div>
|
|
270
|
+
<section class="card" v-if="columnRows.length">
|
|
271
|
+
<div class="card-head"><span class="card-title">columns into the model</span><span class="muted small">{{ columnRows.length }}</span></div>
|
|
272
|
+
<k-table :columns="columnColumns" :rows="columnRows" row-key="name" :filterable="manyColumns" placeholder="filter columns"
|
|
273
|
+
empty="no columns"></k-table>
|
|
274
|
+
</section>
|
|
275
|
+
</template>
|
|
276
|
+
<div class="muted small" v-if="!dataReport">The data as the plan saw it before training (plan.json) and as the run fitted it (fitted/fields.json):
|
|
277
|
+
the feature blocks with their columns and preprocessor chains, the targets and their classes.</div>
|
|
278
|
+
<div class="grid">
|
|
279
|
+
<section class="card">
|
|
280
|
+
<div class="card-head"><span class="card-title">source</span><span class="muted mono" v-if="source">{{ source.use }}</span></div>
|
|
281
|
+
<div class="table-wrap" v-if="sourceRows.length">
|
|
282
|
+
<table class="data">
|
|
283
|
+
<tbody>
|
|
284
|
+
<tr v-for="row in sourceRows" :key="row.name">
|
|
285
|
+
<td class="muted">{{ row.name }}</td>
|
|
286
|
+
<td class="mono" style="white-space: normal; overflow-wrap: anywhere">{{ row.value }}</td>
|
|
287
|
+
</tr>
|
|
288
|
+
</tbody>
|
|
289
|
+
</table>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="empty" v-if="!source">the plan has no data node</div>
|
|
292
|
+
</section>
|
|
293
|
+
<section class="card">
|
|
294
|
+
<div class="card-head"><span class="card-title">wiring</span></div>
|
|
295
|
+
<div class="table-wrap">
|
|
296
|
+
<table class="data">
|
|
297
|
+
<tbody>
|
|
298
|
+
<tr><td class="muted">inputs</td><td class="mono">{{ inputs.join(", ") }}</td></tr>
|
|
299
|
+
<tr v-if="exposes.length"><td class="muted">exposes</td><td class="mono">{{ exposes.join(", ") }}</td></tr>
|
|
300
|
+
<tr v-if="targets.length"><td class="muted">targets</td><td class="mono">{{ targets.join(", ") }}</td></tr>
|
|
301
|
+
<tr v-if="classes.length"><td class="muted">classes</td><td class="mono" style="white-space: normal">{{ classes.join(", ") }}</td></tr>
|
|
302
|
+
</tbody>
|
|
303
|
+
</table>
|
|
304
|
+
</div>
|
|
305
|
+
</section>
|
|
306
|
+
</div>
|
|
307
|
+
<section class="card" v-if="fieldRows.length">
|
|
308
|
+
<div class="card-head"><span class="card-title">fields in the plan</span><span class="muted small">{{ fieldRows.length }}</span></div>
|
|
309
|
+
<div class="table-wrap">
|
|
310
|
+
<table class="data">
|
|
311
|
+
<thead><tr><th>name</th><th>type</th><th>role</th><th>columns</th><th>preprocessors</th></tr></thead>
|
|
312
|
+
<tbody>
|
|
313
|
+
<tr v-for="row in fieldRows" :key="row.name">
|
|
314
|
+
<td class="mono">{{ row.name }}</td>
|
|
315
|
+
<td class="mono">{{ row.type }}</td>
|
|
316
|
+
<td>{{ row.role }}</td>
|
|
317
|
+
<td class="mono" style="white-space: normal">{{ row.columns.join(", ") }}</td>
|
|
318
|
+
<td class="mono">{{ row.chain }}</td>
|
|
319
|
+
</tr>
|
|
320
|
+
</tbody>
|
|
321
|
+
</table>
|
|
322
|
+
</div>
|
|
323
|
+
</section>
|
|
324
|
+
<template v-if="fitted">
|
|
325
|
+
<section class="card" v-if="features.length">
|
|
326
|
+
<div class="card-head"><span class="card-title">fitted features</span><span class="muted small">{{ featureCount }} columns into the model</span></div>
|
|
327
|
+
<div class="table-wrap">
|
|
328
|
+
<table class="data">
|
|
329
|
+
<thead><tr><th>pattern</th><th>columns</th><th>chain</th></tr></thead>
|
|
330
|
+
<tbody>
|
|
331
|
+
<tr v-for="item in features" :key="item.key">
|
|
332
|
+
<td class="mono">{{ item.pattern }}</td>
|
|
333
|
+
<td style="white-space: normal">
|
|
334
|
+
<details>
|
|
335
|
+
<summary style="cursor: pointer">{{ item.columns.length }} columns</summary>
|
|
336
|
+
<div class="mono">{{ item.columns.join(", ") }}</div>
|
|
337
|
+
<div class="muted mono" v-if="item.names.length">model names: {{ item.names.join(", ") }}</div>
|
|
338
|
+
</details>
|
|
339
|
+
</td>
|
|
340
|
+
<td class="mono">{{ item.chain }}</td>
|
|
341
|
+
</tr>
|
|
342
|
+
</tbody>
|
|
343
|
+
</table>
|
|
344
|
+
</div>
|
|
345
|
+
</section>
|
|
346
|
+
<section class="card" v-if="targetFields.length || fittedTargets.length">
|
|
347
|
+
<div class="card-head"><span class="card-title">fitted targets</span></div>
|
|
348
|
+
<div class="table-wrap" v-if="targetFields.length">
|
|
349
|
+
<table class="data">
|
|
350
|
+
<thead><tr><th>name</th><th class="num">width</th><th>chain</th><th>classes</th><th>class weights</th></tr></thead>
|
|
351
|
+
<tbody>
|
|
352
|
+
<tr v-for="item in targetFields" :key="item.key">
|
|
353
|
+
<td class="mono">{{ item.name }}</td>
|
|
354
|
+
<td class="num mono">{{ item.width }}</td>
|
|
355
|
+
<td class="mono">{{ item.chain }}</td>
|
|
356
|
+
<td class="mono" style="white-space: normal">{{ item.classes.join(", ") }}</td>
|
|
357
|
+
<td class="mono" style="white-space: normal">{{ item.weights }}</td>
|
|
358
|
+
</tr>
|
|
359
|
+
</tbody>
|
|
360
|
+
</table>
|
|
361
|
+
</div>
|
|
362
|
+
<div class="mono" v-else>{{ fittedTargets.join(", ") }}</div>
|
|
363
|
+
</section>
|
|
364
|
+
<section class="card" v-if="facts.length || fittedClasses.length">
|
|
365
|
+
<div class="card-head"><span class="card-title">fitted settings</span></div>
|
|
366
|
+
<div class="table-wrap">
|
|
367
|
+
<table class="data">
|
|
368
|
+
<tbody>
|
|
369
|
+
<tr v-if="fittedClasses.length"><td class="muted">classes</td><td class="mono" style="white-space: normal">{{ fittedClasses.join(", ") }}</td></tr>
|
|
370
|
+
<tr v-for="row in facts" :key="row.name"><td class="muted">{{ row.name }}</td><td class="mono" style="white-space: normal">{{ row.value }}</td></tr>
|
|
371
|
+
</tbody>
|
|
372
|
+
</table>
|
|
373
|
+
</div>
|
|
374
|
+
</section>
|
|
375
|
+
</template>
|
|
376
|
+
<div class="empty" v-else>no fitted/fields.json in this record</div>
|
|
377
|
+
</div>`,
|
|
378
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { messageOf } from "../../components/error.js";
|
|
2
|
+
import { bytes, when } from "../../format.js";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const TEXTS = [".yaml", ".yml", ".json", ".jsonl", ".txt", ".md", ".csv", ".py", ".sh", ".sub", ".log"];
|
|
6
|
+
const IMAGES = [".png", ".jpg", ".jpeg", ".gif", ".svg"];
|
|
7
|
+
const TAIL = 200;
|
|
8
|
+
const LINK = "border: 0; background: none; padding: 0; font: inherit; cursor: pointer; text-align: left";
|
|
9
|
+
const CODE = "overflow: auto; max-height: 70vh; padding: 10px; background: var(--surface-2); border-radius: 6px; white-space: pre";
|
|
10
|
+
|
|
11
|
+
function kindOf(name) {
|
|
12
|
+
const lower = String(name).toLowerCase();
|
|
13
|
+
if (TEXTS.some(suffix => lower.endsWith(suffix))) return "text";
|
|
14
|
+
if (IMAGES.some(suffix => lower.endsWith(suffix))) return "image";
|
|
15
|
+
return "binary";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function basename(name) {
|
|
19
|
+
const parts = String(name).split("/");
|
|
20
|
+
return parts[parts.length - 1];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
name: "FilesTab",
|
|
25
|
+
props: {
|
|
26
|
+
record: { type: Object, default: null },
|
|
27
|
+
path: { type: String, default: "" },
|
|
28
|
+
api: { default: null },
|
|
29
|
+
version: { type: Number, default: 0 },
|
|
30
|
+
},
|
|
31
|
+
data() {
|
|
32
|
+
return { filter: "", view: null, error: "", loading: false };
|
|
33
|
+
},
|
|
34
|
+
computed: {
|
|
35
|
+
link() { return LINK; },
|
|
36
|
+
code() { return CODE; },
|
|
37
|
+
tail() { return TAIL; },
|
|
38
|
+
all() { return this.record && Array.isArray(this.record.files) ? this.record.files : []; },
|
|
39
|
+
files() {
|
|
40
|
+
const wanted = this.filter.trim().toLowerCase();
|
|
41
|
+
return wanted ? this.all.filter(item => String(item.path).toLowerCase().includes(wanted)) : this.all;
|
|
42
|
+
},
|
|
43
|
+
countText() {
|
|
44
|
+
if (this.files.length === this.all.length) return this.all.length + " files";
|
|
45
|
+
return this.files.length + " of " + this.all.length + " files";
|
|
46
|
+
},
|
|
47
|
+
partial() { return Boolean(this.view && this.view.kind === "text" && !this.view.whole); },
|
|
48
|
+
splitClass() { return this.view ? "grid" : ""; },
|
|
49
|
+
},
|
|
50
|
+
watch: {
|
|
51
|
+
record() {
|
|
52
|
+
if (this.partial) this.open(this.view.name, false, true);
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
methods: {
|
|
56
|
+
bytes,
|
|
57
|
+
basename,
|
|
58
|
+
moment(value) { return when(value); },
|
|
59
|
+
url(name, tail) { return this.api ? this.api.fileUrl(this.path, name, tail || null) : ""; },
|
|
60
|
+
isOpen(name) { return Boolean(this.view && this.view.name === name); },
|
|
61
|
+
rowClass(name) { return this.isOpen(name) ? "selected" : ""; },
|
|
62
|
+
close() { this.view = null; },
|
|
63
|
+
async open(name, whole, quiet) {
|
|
64
|
+
const kind = kindOf(name);
|
|
65
|
+
if (kind !== "text") {
|
|
66
|
+
this.view = { name, kind, text: "", whole: true };
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!this.api) {
|
|
70
|
+
this.error = "the api prop is missing";
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const path = this.path;
|
|
74
|
+
if (!quiet) this.loading = true;
|
|
75
|
+
try {
|
|
76
|
+
const text = await this.api.getText(this.url(name, whole ? 0 : TAIL));
|
|
77
|
+
if (path !== this.path) return;
|
|
78
|
+
this.view = { name, kind, text, whole: Boolean(whole) };
|
|
79
|
+
this.error = "";
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (path === this.path) this.error = messageOf(error);
|
|
82
|
+
} finally {
|
|
83
|
+
this.loading = false;
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
template: `
|
|
88
|
+
<div class="page pane-files">
|
|
89
|
+
<div class="muted small">Every file of the record with its size and the time it last changed. A name opens it here: the last
|
|
90
|
+
lines of a text file, images inline; save downloads any file as it is. An open text file follows the record while it runs.</div>
|
|
91
|
+
<div class="toolbar">
|
|
92
|
+
<input class="filter" type="search" v-model="filter" placeholder="filter files" spellcheck="false">
|
|
93
|
+
<span class="muted small">{{ countText }}</span>
|
|
94
|
+
<span class="muted small" v-if="loading">reading</span>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="error-box" v-if="error">{{ error }}</div>
|
|
97
|
+
<div :class="splitClass" style="align-items: start">
|
|
98
|
+
<div class="table-wrap">
|
|
99
|
+
<table class="data">
|
|
100
|
+
<thead><tr><th>file</th><th class="num">size</th><th>modified</th><th></th></tr></thead>
|
|
101
|
+
<tbody>
|
|
102
|
+
<tr v-for="item in files" :key="item.path" :class="rowClass(item.path)">
|
|
103
|
+
<td class="mono"><button type="button" class="link" :style="link" @click="open(item.path, false)">{{ item.path }}</button></td>
|
|
104
|
+
<td class="num mono">{{ bytes(item.size) }}</td>
|
|
105
|
+
<td class="muted">{{ moment(item.modified) }}</td>
|
|
106
|
+
<td><a class="tool" :href="url(item.path, 0)" :download="basename(item.path)">save</a></td>
|
|
107
|
+
</tr>
|
|
108
|
+
<tr v-if="!files.length"><td class="empty" colspan="4">no files</td></tr>
|
|
109
|
+
</tbody>
|
|
110
|
+
</table>
|
|
111
|
+
</div>
|
|
112
|
+
<section class="card" v-if="view">
|
|
113
|
+
<div class="card-head">
|
|
114
|
+
<span class="card-title mono">{{ view.name }}</span>
|
|
115
|
+
<span class="card-tools">
|
|
116
|
+
<span class="muted small" v-if="partial">the last {{ tail }} lines</span>
|
|
117
|
+
<button type="button" class="tool" v-if="partial" @click="open(view.name, true)">whole file</button>
|
|
118
|
+
<a class="tool" :href="url(view.name, 0)" :download="basename(view.name)">save</a>
|
|
119
|
+
<button type="button" class="tool" @click="close">close</button>
|
|
120
|
+
</span>
|
|
121
|
+
</div>
|
|
122
|
+
<pre class="mono" v-if="view.kind === 'text'" :style="code">{{ view.text }}</pre>
|
|
123
|
+
<img v-else-if="view.kind === 'image'" :src="url(view.name, 0)" :alt="view.name" style="max-width: 100%; background: #fff">
|
|
124
|
+
<div class="muted" v-else>{{ view.name }} is not shown here; save downloads it</div>
|
|
125
|
+
</section>
|
|
126
|
+
</div>
|
|
127
|
+
</div>`,
|
|
128
|
+
};
|