@mstone6969/graph 0.1.0 → 0.2.0
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.
- package/README.md +111 -14
- package/dist/ExcelWriter-3ZSYUH4T.cjs +154 -0
- package/dist/GraphType.js +24 -38
- package/dist/PdfWriter-6HJFYMCO.cjs +205 -0
- package/dist/catalog-YWX6FHRM.cjs +8 -0
- package/dist/catalog.js +5 -1
- package/dist/charts/bars/index.js +2 -12
- package/dist/charts/comparison/EffectForestPlot.js +1 -1
- package/dist/charts/comparison/index.js +1 -8
- package/dist/charts/demography/Nomogram.js +3 -3
- package/dist/charts/density/HexbinChart.js +6 -4
- package/dist/charts/diagrams/ArchitectureCanvas.js +68 -0
- package/dist/charts/diagrams/ArchitectureDiagram.js +331 -0
- package/dist/charts/diagrams/ArchitectureLayout.js +106 -0
- package/dist/charts/diagrams/ArchitectureLinkLabels.js +78 -0
- package/dist/charts/diagrams/ArchitectureModel.js +108 -0
- package/dist/charts/diagrams/ArchitectureRouter.js +348 -0
- package/dist/charts/diagrams/ArchitectureSamples.js +312 -0
- package/dist/charts/diagrams/ArchitectureShapes.js +130 -0
- package/dist/charts/diagrams/ArchitectureText.js +25 -0
- package/dist/charts/diagrams/DiagramText.js +38 -0
- package/dist/charts/diagrams/EntityRelationshipDiagram.js +465 -0
- package/dist/charts/diagrams/ErCrowsFoot.js +33 -0
- package/dist/charts/diagrams/ErSamples.js +205 -0
- package/dist/charts/diagrams/ErSchema.js +122 -0
- package/dist/charts/diagrams/LogicCircuit.js +154 -0
- package/dist/charts/diagrams/LogicCircuitLayout.js +600 -0
- package/dist/charts/diagrams/LogicCircuitSamples.js +66 -0
- package/dist/charts/diagrams/LogicGateDiagram.js +216 -0
- package/dist/charts/diagrams/LogicGateSymbol.js +113 -0
- package/dist/charts/diagrams/NetworkTopologyDiagram.js +247 -0
- package/dist/charts/diagrams/SequenceDiagram.js +351 -0
- package/dist/charts/diagrams/SequenceLayout.js +422 -0
- package/dist/charts/diagrams/SequenceModel.js +221 -0
- package/dist/charts/diagrams/TopologyLayout.js +143 -0
- package/dist/charts/diagrams/architectureEnums.js +50 -0
- package/dist/charts/diagrams/index.js +26 -0
- package/dist/charts/diagrams/logicEnums.js +42 -0
- package/dist/charts/diagrams/sequenceEnums.js +40 -0
- package/dist/charts/distribution/HistogramChart.js +25 -3
- package/dist/charts/distribution/index.js +1 -3
- package/dist/charts/earthscience/HovmollerDiagram.js +477 -0
- package/dist/charts/earthscience/HovmollerEnums.js +42 -0
- package/dist/charts/earthscience/HovmollerField.js +180 -0
- package/dist/charts/earthscience/HovmollerSample.js +40 -0
- package/dist/charts/earthscience/HovmollerSpeed.js +71 -0
- package/dist/charts/earthscience/index.js +10 -2
- package/dist/charts/engineering/ConstellationDiagram.js +26 -6
- package/dist/charts/engineering/EyeDiagram.js +2 -1
- package/dist/charts/engineering/PoleZeroPlot.js +32 -8
- package/dist/charts/financial/RelativeStrengthRotation.js +434 -0
- package/dist/charts/financial/RelativeStrengthRotationMath.js +71 -0
- package/dist/charts/financial/RelativeStrengthRotationSamples.js +27 -0
- package/dist/charts/financial/index.js +5 -3
- package/dist/charts/flow/index.js +1 -5
- package/dist/charts/frameworks/AnsoffMatrix.js +523 -0
- package/dist/charts/frameworks/FrameworkText.js +98 -0
- package/dist/charts/frameworks/NpsGauge.js +279 -0
- package/dist/charts/frameworks/NpsSurvey.js +58 -0
- package/dist/charts/frameworks/PerceptualMap.js +419 -0
- package/dist/charts/frameworks/PerceptualProjection.js +142 -0
- package/dist/charts/frameworks/PositioningSamples.js +191 -0
- package/dist/charts/frameworks/RaciMatrix.js +458 -0
- package/dist/charts/frameworks/RaciPlan.js +81 -0
- package/dist/charts/frameworks/RaciSamples.js +76 -0
- package/dist/charts/frameworks/SwotMatrix.js +358 -0
- package/dist/charts/frameworks/SwotSamples.js +85 -0
- package/dist/charts/frameworks/VanWestendorpChart.js +187 -0
- package/dist/charts/frameworks/VanWestendorpSurvey.js +197 -0
- package/dist/charts/frameworks/index.js +26 -0
- package/dist/charts/frameworks/raciEnums.js +30 -0
- package/dist/charts/frameworks/swotEnums.js +34 -0
- package/dist/charts/genomics/CircosPlot.js +1 -1
- package/dist/charts/geoflows/GeoFlowChart.js +2 -2
- package/dist/charts/geoflows/GeoFlowSample.js +2 -1
- package/dist/charts/geomaps/FloorPlanMap.js +324 -0
- package/dist/charts/geomaps/FloorPlanModel.js +179 -0
- package/dist/charts/geomaps/FloorPlanSamples.js +416 -0
- package/dist/charts/geomaps/GeoLand.js +2 -2
- package/dist/charts/geomaps/GeoSamples.js +2 -1
- package/dist/charts/geomaps/GraticuleMap.js +2 -2
- package/dist/charts/geomaps/RoomLabelFit.js +200 -0
- package/dist/charts/geomaps/TissotMap.js +2 -2
- package/dist/charts/geomaps/index.js +8 -4
- package/dist/charts/hierarchy/SunburstChart.js +17 -10
- package/dist/charts/hierarchy/index.js +2 -11
- package/dist/charts/indicators/BulletChart.js +8 -2
- package/dist/charts/indicators/SpeedometerChart.js +1 -1
- package/dist/charts/indicators/ThermometerChart.js +1 -1
- package/dist/charts/lifescience/LoessFit.js +113 -0
- package/dist/charts/lifescience/MaPlot.js +337 -0
- package/dist/charts/lifescience/MaPlotSamples.js +35 -0
- package/dist/charts/lifescience/OncoGlyph.js +137 -0
- package/dist/charts/lifescience/OncoPrint.js +373 -0
- package/dist/charts/lifescience/OncoPrintMatrix.js +107 -0
- package/dist/charts/lifescience/OncoPrintSamples.js +142 -0
- package/dist/charts/lifescience/PedigreeChart.js +306 -0
- package/dist/charts/lifescience/PedigreeLayout.js +261 -0
- package/dist/charts/lifescience/PedigreeRows.js +70 -0
- package/dist/charts/lifescience/PedigreeSamples.js +161 -0
- package/dist/charts/lifescience/PedigreeSymbol.js +47 -0
- package/dist/charts/lifescience/RamachandranPlot.js +364 -0
- package/dist/charts/lifescience/RamachandranRegions.js +2712 -0
- package/dist/charts/lifescience/RamachandranSamples.js +235 -0
- package/dist/charts/lifescience/index.js +25 -0
- package/dist/charts/lifescience/pedigreeEnums.js +21 -0
- package/dist/charts/lifescience/ramachandranEnums.js +34 -0
- package/dist/charts/linesareas/DualAxisChart.js +1 -1
- package/dist/charts/linesareas/LineEnums.js +1 -15
- package/dist/charts/linesareas/MultiLineChart.js +1 -1
- package/dist/charts/linesareas/TrendChart.js +15 -5
- package/dist/charts/linesareas/index.js +2 -6
- package/dist/charts/massive/MassiveChart.js +3 -2
- package/dist/charts/mathematical/FourierSeriesChart.js +7 -1
- package/dist/charts/mathematical/index.js +2 -6
- package/dist/charts/matrix/GridPlot.js +27 -2
- package/dist/charts/matrix/TilegramChart.js +2 -2
- package/dist/charts/mlcharts/AttentionGrid.js +79 -0
- package/dist/charts/mlcharts/AttentionHeatmap.js +329 -0
- package/dist/charts/mlcharts/AttentionModel.js +132 -0
- package/dist/charts/mlcharts/AttentionSamples.js +82 -0
- package/dist/charts/mlcharts/CellNumber.js +8 -0
- package/dist/charts/mlcharts/CurveDiagnosticSamples.js +95 -0
- package/dist/charts/mlcharts/CurvePickMark.js +31 -0
- package/dist/charts/mlcharts/DecisionBoundaryChart.js +316 -0
- package/dist/charts/mlcharts/DecisionSamples.js +60 -0
- package/dist/charts/mlcharts/DecisionSurface.js +130 -0
- package/dist/charts/mlcharts/DistinctTicks.js +13 -0
- package/dist/charts/mlcharts/ElbowMath.js +153 -0
- package/dist/charts/mlcharts/ElbowPlot.js +273 -0
- package/dist/charts/mlcharts/ExplainabilitySamples.js +70 -0
- package/dist/charts/mlcharts/FeatureImportanceChart.js +150 -0
- package/dist/charts/mlcharts/ImportanceRanking.js +98 -0
- package/dist/charts/mlcharts/LearningCurveChart.js +276 -0
- package/dist/charts/mlcharts/LearningCurveMath.js +92 -0
- package/dist/charts/mlcharts/PartialDependence.js +92 -0
- package/dist/charts/mlcharts/PartialDependencePlot.js +225 -0
- package/dist/charts/mlcharts/WideLabel.js +28 -0
- package/dist/charts/mlcharts/curveDiagnosticEnums.js +37 -0
- package/dist/charts/mlcharts/index.js +32 -0
- package/dist/charts/proportion/index.js +5 -9
- package/dist/charts/scatter/CategoryScatterBase.js +1 -4
- package/dist/charts/scatter/ConnectedScatter.js +7 -4
- package/dist/charts/scatter/MarginalScatter.js +13 -8
- package/dist/charts/scatter/index.js +4 -17
- package/dist/charts/scientific/SpectrogramChart.js +9 -3
- package/dist/charts/scientific/index.js +2 -2
- package/dist/charts/signals/index.js +3 -13
- package/dist/charts/statistics/LorenzCurve.js +7 -4
- package/dist/charts/threebars/Candlestick3DChart.js +18 -2
- package/dist/charts/threeglobe/GlobeChart.js +2 -2
- package/dist/charts/threeglobe/GlobeSamples.js +2 -1
- package/dist/charts/threeglobe/RingGlobe.js +1 -1
- package/dist/charts/threenetwork/ConeTreeChart.js +2 -1
- package/dist/charts/threenetwork/Gantt3DChart.js +3 -2
- package/dist/charts/threesurfaces/Ridgeline3DChart.js +22 -4
- package/dist/charts/threevolume/LorenzAttractor.js +19 -0
- package/dist/charts/threevolume/VolumeRaycastChart.js +4 -3
- package/dist/charts/threevolume/volumeEnums.js +1 -0
- package/dist/charts/timeline/index.js +2 -10
- package/dist/chunk-753UBLV2.cjs +33 -0
- package/dist/chunk-VX54A7KA.cjs +17 -0
- package/dist/{chunk-W5GAAAZY.cjs → chunk-WAODDPYX.cjs} +24874 -17620
- package/dist/chunk-Z3JPIJSC.cjs +338 -0
- package/dist/core/CartesianChart.js +87 -11
- package/dist/core/Chart.js +63 -15
- package/dist/core/Chart2D.js +54 -17
- package/dist/core/Chart3D.js +56 -20
- package/dist/core/ChartExportError.js +2 -0
- package/dist/core/ChartExporter.js +50 -8
- package/dist/core/ColumnCheck.js +53 -0
- package/dist/core/Context2D.js +29 -15
- package/dist/core/Context3D.js +7 -2
- package/dist/core/ExcelWriter.js +152 -0
- package/dist/core/ExportText.js +28 -0
- package/dist/core/MarkOrigin.js +88 -0
- package/dist/core/MarkStyle.js +294 -0
- package/dist/core/Palette.js +27 -7
- package/dist/core/PdfPage.js +13 -0
- package/dist/core/PdfWriter.js +245 -0
- package/dist/core/Scales.js +52 -2
- package/dist/core/SceneOverlay.js +4 -4
- package/dist/core/TextFit.js +37 -0
- package/dist/core/Theme.js +28 -1
- package/dist/core/enums.js +33 -1
- package/dist/core/pathMorph.js +15 -0
- package/dist/data/SampleData.js +1 -17
- package/dist/data/WorldSample.js +23 -0
- package/dist/index.cjs +810 -691
- package/dist/index.d.cts +8390 -2925
- package/dist/index.d.ts +8392 -2927
- package/dist/index.js +106 -56
- package/dist/react/ChartView.js +18 -8
- package/dist/react/GraphHandle.js +2 -0
- package/dist/react/components.js +74 -116
- package/package.json +13 -3
- package/dist/catalog-4UP2YFNR.cjs +0 -7
- package/dist/charts/bars/BulletBar.js +0 -184
- package/dist/charts/bars/ErrorBarColumn.js +0 -147
- package/dist/charts/bars/HistogramBar.js +0 -217
- package/dist/charts/bars/MarimekkoBar.js +0 -191
- package/dist/charts/bars/ParetoBar.js +0 -143
- package/dist/charts/comparison/PanelChart.js +0 -70
- package/dist/charts/comparison/SmallMultipleBars.js +0 -134
- package/dist/charts/comparison/SpanChart.js +0 -118
- package/dist/charts/comparison/TrellisChart.js +0 -158
- package/dist/charts/distribution/RidgeSlabs3DChart.js +0 -169
- package/dist/charts/financial/Candlestick3D.js +0 -157
- package/dist/charts/flow/BumpFlow.js +0 -212
- package/dist/charts/flow/StreamFlow.js +0 -174
- package/dist/charts/geomaps/ThiessenMap.js +0 -106
- package/dist/charts/hierarchy/ConeTree3D.js +0 -135
- package/dist/charts/hierarchy/Hierarchy3DChart.js +0 -27
- package/dist/charts/hierarchy/SpherePack3D.js +0 -81
- package/dist/charts/hierarchy/SunburstTower3D.js +0 -93
- package/dist/charts/hierarchy/TreemapSkyline3D.js +0 -98
- package/dist/charts/linesareas/ConnectedScatterTime.js +0 -206
- package/dist/charts/linesareas/SmallMultipleLines.js +0 -153
- package/dist/charts/mathematical/ParametricSurface3D.js +0 -234
- package/dist/charts/mathematical/StrangeAttractor3D.js +0 -251
- package/dist/charts/proportion/ConeFunnel.js +0 -126
- package/dist/charts/proportion/ExtrudedPieChart.js +0 -123
- package/dist/charts/scatter/BeeswarmScatter.js +0 -151
- package/dist/charts/scatter/BinnedScatterBase.js +0 -51
- package/dist/charts/scatter/ContourScatter.js +0 -116
- package/dist/charts/scatter/GridBinScatter.js +0 -98
- package/dist/charts/scatter/HexbinScatter.js +0 -74
- package/dist/charts/scatter/StripScatter.js +0 -107
- package/dist/charts/scatter/SunflowerScatter.js +0 -109
- package/dist/charts/signals/ConstellationChart.js +0 -153
- package/dist/charts/signals/CorrelogramChart.js +0 -171
- package/dist/charts/signals/EyeDiagramChart.js +0 -155
- package/dist/charts/signals/PoleZeroChart.js +0 -164
- package/dist/charts/signals/SpectrogramChart.js +0 -79
- package/dist/charts/timeline/CalendarSkylineChart.js +0 -128
- package/dist/charts/timeline/ScheduleBlocksChart.js +0 -119
- package/dist/charts/timeline/SpiralTimeline.js +0 -154
- package/dist/charts/timeline/StreamTimeline.js +0 -141
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { ChartFamily, ColorScale, DataShape, Dimension } from "../../core/enums.js";
|
|
2
|
+
import { Scales } from "../../core/Scales.js";
|
|
3
|
+
import { MatrixChart } from "../matrix/MatrixChart.js";
|
|
4
|
+
import { MatrixGlyph, MatrixMarginal } from "../matrix/matrixEnums.js";
|
|
5
|
+
import { AttentionGrid } from "./AttentionGrid.js";
|
|
6
|
+
import { AttentionModel } from "./AttentionModel.js";
|
|
7
|
+
import { AttentionSamples } from "./AttentionSamples.js";
|
|
8
|
+
import { WideLabel } from "./WideLabel.js";
|
|
9
|
+
import { scaleLinear } from "d3";
|
|
10
|
+
// src/charts/mlcharts/AttentionHeatmap.ts
|
|
11
|
+
var AttentionHeadView = /* @__PURE__ */ function(AttentionHeadView) {
|
|
12
|
+
AttentionHeadView["Mean"] = "mean-of-heads";
|
|
13
|
+
AttentionHeadView["SmallMultiples"] = "small-multiples";
|
|
14
|
+
return AttentionHeadView;
|
|
15
|
+
}({});
|
|
16
|
+
var AttentionAspect = /* @__PURE__ */ function(AttentionAspect) {
|
|
17
|
+
AttentionAspect["Square"] = "square-cells";
|
|
18
|
+
AttentionAspect["Fit"] = "fit";
|
|
19
|
+
return AttentionAspect;
|
|
20
|
+
}({});
|
|
21
|
+
var NOTE_SPACE = 16;
|
|
22
|
+
var COVERAGE_GUTTER = 52;
|
|
23
|
+
var PEAK = 4;
|
|
24
|
+
var ROOM = 36;
|
|
25
|
+
var TOLERANCE = .01;
|
|
26
|
+
/**
|
|
27
|
+
* Soft alignment between two token sequences, as in Bahdanau, Cho & Bengio (2015) and the scaled
|
|
28
|
+
* dot-product attention of Vaswani et al. (2017). Rows are target tokens (queries), columns are source
|
|
29
|
+
* tokens (keys), so each row is one softmax and should sum to 1. Weights are drawn as given: `check()`
|
|
30
|
+
* and a note under the plot report what is off, nothing is renormalised. Tokens keep sequence order.
|
|
31
|
+
* Coverage is the sum of a source token's attention over all targets (Tu et al., 2016).
|
|
32
|
+
*/
|
|
33
|
+
var AttentionHeatmap = class extends MatrixChart {
|
|
34
|
+
static meta = {
|
|
35
|
+
id: "attention-heatmap",
|
|
36
|
+
name: "Attention heatmap",
|
|
37
|
+
family: ChartFamily.Matrix,
|
|
38
|
+
dimension: Dimension.Two,
|
|
39
|
+
shape: DataShape.Matrix,
|
|
40
|
+
description: "Shows how much each target token attends to each source token, per head or averaged, to inspect what a sequence model looks at.",
|
|
41
|
+
tags: [
|
|
42
|
+
"attention",
|
|
43
|
+
"transformer",
|
|
44
|
+
"alignment",
|
|
45
|
+
"multi-head",
|
|
46
|
+
"machine learning",
|
|
47
|
+
"nlp"
|
|
48
|
+
],
|
|
49
|
+
sample: (seed) => AttentionSamples.crossAttention(seed),
|
|
50
|
+
variants: {
|
|
51
|
+
headView: ["mean-of-heads", "small-multiples"],
|
|
52
|
+
glyph: [MatrixGlyph.Tile, MatrixGlyph.Circle],
|
|
53
|
+
aspect: ["square-cells", "fit"],
|
|
54
|
+
marginals: [
|
|
55
|
+
MatrixMarginal.None,
|
|
56
|
+
MatrixMarginal.Rows,
|
|
57
|
+
MatrixMarginal.Columns,
|
|
58
|
+
MatrixMarginal.Both
|
|
59
|
+
],
|
|
60
|
+
alignment: [false, true],
|
|
61
|
+
cellValues: [false, true]
|
|
62
|
+
},
|
|
63
|
+
valid: (v) => !(v.headView === "small-multiples" && (v.marginals !== MatrixMarginal.None || v.cellValues)) && !(v.cellValues && (v.alignment || v.glyph !== MatrixGlyph.Tile))
|
|
64
|
+
};
|
|
65
|
+
#panels = 0;
|
|
66
|
+
defaults() {
|
|
67
|
+
return {
|
|
68
|
+
...super.defaults(),
|
|
69
|
+
source: "source",
|
|
70
|
+
target: "target",
|
|
71
|
+
weight: "weight",
|
|
72
|
+
head: "head",
|
|
73
|
+
sourceIndex: "sourceIndex",
|
|
74
|
+
targetIndex: "targetIndex",
|
|
75
|
+
headView: "mean-of-heads",
|
|
76
|
+
glyph: MatrixGlyph.Tile,
|
|
77
|
+
aspect: "square-cells",
|
|
78
|
+
marginals: MatrixMarginal.None,
|
|
79
|
+
alignment: false,
|
|
80
|
+
cellValues: false,
|
|
81
|
+
colorScale: ColorScale.Sequential,
|
|
82
|
+
sumTolerance: TOLERANCE
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
get attention() {
|
|
86
|
+
return AttentionModel.from(this.options.data, this.options);
|
|
87
|
+
}
|
|
88
|
+
get model() {
|
|
89
|
+
return this.attention.pooled;
|
|
90
|
+
}
|
|
91
|
+
get blank() {
|
|
92
|
+
return this.attention.empty;
|
|
93
|
+
}
|
|
94
|
+
/** Whether each row is a distribution, across every head. Reports only: the weights are never changed. */
|
|
95
|
+
check() {
|
|
96
|
+
const { sumTolerance } = this.options;
|
|
97
|
+
return this.attention.check(sumTolerance >= 0 ? sumTolerance : TOLERANCE);
|
|
98
|
+
}
|
|
99
|
+
table() {
|
|
100
|
+
const { heads } = this.attention;
|
|
101
|
+
return {
|
|
102
|
+
columns: [
|
|
103
|
+
"head",
|
|
104
|
+
"targetIndex",
|
|
105
|
+
"target",
|
|
106
|
+
"sourceIndex",
|
|
107
|
+
"source",
|
|
108
|
+
"weight"
|
|
109
|
+
],
|
|
110
|
+
rows: heads.flatMap(({ name, model }) => model.cells.map((cell) => [
|
|
111
|
+
name,
|
|
112
|
+
cell.r,
|
|
113
|
+
cell.row,
|
|
114
|
+
cell.c,
|
|
115
|
+
cell.column,
|
|
116
|
+
cell.value
|
|
117
|
+
]))
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
views(attention) {
|
|
121
|
+
const { headView, focusHead } = this.options, { heads } = attention;
|
|
122
|
+
if (headView === "small-multiples") return heads.map(({ name, model }) => ({
|
|
123
|
+
title: name,
|
|
124
|
+
model
|
|
125
|
+
}));
|
|
126
|
+
const focused = heads[focusHead ?? -1];
|
|
127
|
+
if (focused) return [{
|
|
128
|
+
title: focused.name,
|
|
129
|
+
model: focused.model
|
|
130
|
+
}];
|
|
131
|
+
return [{
|
|
132
|
+
title: heads.length > 1 ? `Mean of ${heads.length} heads` : heads[0].name,
|
|
133
|
+
model: attention.pooled
|
|
134
|
+
}];
|
|
135
|
+
}
|
|
136
|
+
domain(views) {
|
|
137
|
+
const low = Math.min(0, ...views.map(({ model }) => model.domain[0]));
|
|
138
|
+
const top = this.options.scaleMax ?? Math.max(...views.map(({ model }) => model.domain[1]));
|
|
139
|
+
return [low, top > low ? top : low + 1];
|
|
140
|
+
}
|
|
141
|
+
note({ checked, failing, tolerance, blankRows, missing, negative, irregular }) {
|
|
142
|
+
return [
|
|
143
|
+
failing.length && `Row sums off 1 by more than ${tolerance}: ${failing.length} of ${checked}`,
|
|
144
|
+
blankRows && `Blank rows: ${blankRows}`,
|
|
145
|
+
missing && `Missing weights: ${missing}`,
|
|
146
|
+
negative && `Negative weights: ${negative}`,
|
|
147
|
+
irregular && `Irregular entries: ${irregular}`
|
|
148
|
+
].filter(Boolean).join(" · ");
|
|
149
|
+
}
|
|
150
|
+
drawMatrix(ctx) {
|
|
151
|
+
const { aspect, marginals, cellValues, glyph } = this.options;
|
|
152
|
+
const attention = this.attention, views = this.views(attention);
|
|
153
|
+
if (views.length !== this.#panels) ctx.root.selectAll("*").remove();
|
|
154
|
+
this.#panels = views.length;
|
|
155
|
+
const note = this.note(this.check());
|
|
156
|
+
const onRight = marginals === MatrixMarginal.Rows || marginals === MatrixMarginal.Both;
|
|
157
|
+
const onTop = marginals === MatrixMarginal.Columns || marginals === MatrixMarginal.Both;
|
|
158
|
+
const layout = AttentionGrid.plan({
|
|
159
|
+
plot: ctx.plot,
|
|
160
|
+
rows: attention.target,
|
|
161
|
+
columns: attention.source,
|
|
162
|
+
panels: views.length,
|
|
163
|
+
square: aspect === "square-cells",
|
|
164
|
+
titled: views.some((view) => view.title),
|
|
165
|
+
right: onRight ? 44 : 0,
|
|
166
|
+
top: onTop ? 44 : 0,
|
|
167
|
+
bottom: 22 + (note ? NOTE_SPACE : 0),
|
|
168
|
+
minGutter: onTop ? COVERAGE_GUTTER : 0
|
|
169
|
+
});
|
|
170
|
+
const domain = this.domain(views), fill = this.heat(ctx, domain);
|
|
171
|
+
const hasTitles = views.length > 1;
|
|
172
|
+
this.tokens(ctx, attention, layout);
|
|
173
|
+
views.forEach((view, panel) => this.cells(ctx, view, layout.frames[panel], panel, domain, fill, hasTitles));
|
|
174
|
+
this.titles(ctx, views, layout);
|
|
175
|
+
this.peaks(ctx, views, layout, fill);
|
|
176
|
+
if (views.length === 1) {
|
|
177
|
+
this.rails(ctx, views[0].model, layout.frames[0], onRight, onTop);
|
|
178
|
+
const shown = cellValues && glyph === MatrixGlyph.Tile ? views[0].model.cells.filter((cell) => cell.value !== null) : [];
|
|
179
|
+
this.cellText(ctx, "cell-values", shown, layout.frames[0], (cell) => cell.value.toFixed(2), (cell) => this.inkOn(ctx, fill(cell.value)));
|
|
180
|
+
}
|
|
181
|
+
const rampAt = layout.bottom + 6;
|
|
182
|
+
this.ramp(ctx, domain, layout.frames[0].x, rampAt, (value) => value !== 0 && Math.abs(domain[1]) < 1 ? value.toPrecision(2) : Scales.format(value, Math.abs(domain[1])));
|
|
183
|
+
ctx.text(ctx.marks("check-note", "text", note ? [note] : []), 10.5, "secondary").attr("x", 0).attr("y", rampAt + 27).text((text) => WideLabel.fit(text, ctx.plot.width, 5.8));
|
|
184
|
+
}
|
|
185
|
+
cells(ctx, { title, model }, frame, panel, [, top], fill, multiple) {
|
|
186
|
+
const sums = model.rowTotals;
|
|
187
|
+
this.glyphs(ctx, `cells-${panel}`, model.cells, frame, {
|
|
188
|
+
glyph: this.options.glyph,
|
|
189
|
+
size: (cell) => Math.max(0, cell.value ?? 0) / top,
|
|
190
|
+
fill: (cell) => fill(cell.value),
|
|
191
|
+
tip: (cell) => ({
|
|
192
|
+
title: `${cell.row} → ${cell.column}`,
|
|
193
|
+
color: fill(cell.value),
|
|
194
|
+
rows: [
|
|
195
|
+
...multiple ? [["Head", title]] : [],
|
|
196
|
+
["Weight", cell.value === null ? "no data" : cell.value.toFixed(3)],
|
|
197
|
+
["Target position", cell.r + 1],
|
|
198
|
+
["Source position", cell.c + 1],
|
|
199
|
+
["Row sum", sums[cell.r].toFixed(3)]
|
|
200
|
+
]
|
|
201
|
+
})
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
tokens(ctx, { target, source }, { frames, across, gutter, header, flatHeader, below, rowStep, columnStep }) {
|
|
205
|
+
const rows = frames.flatMap((frame, panel) => panel % across ? [] : target.flatMap((token, r) => r % rowStep ? [] : [{
|
|
206
|
+
key: `${panel}/${r}`,
|
|
207
|
+
token,
|
|
208
|
+
x: frame.x - 6,
|
|
209
|
+
y: frame.cy(r)
|
|
210
|
+
}]));
|
|
211
|
+
ctx.text(ctx.marks("row-labels", "text", rows, (d) => d.key)).attr("text-anchor", "end").attr("dy", "0.32em").attr("x", (d) => d.x).attr("y", (d) => d.y).text((d) => WideLabel.fit(d.token, gutter - 6));
|
|
212
|
+
const columns = frames.slice(0, across).flatMap((frame, panel) => source.flatMap((token, c) => {
|
|
213
|
+
if (c % columnStep) return [];
|
|
214
|
+
const y = below ? frame.y + frame.height + (flatHeader ? 13 : 5) : frame.y - 5;
|
|
215
|
+
return [{
|
|
216
|
+
key: `${panel}/${c}`,
|
|
217
|
+
token,
|
|
218
|
+
transform: `translate(${frame.cx(c)},${y})${flatHeader ? "" : " rotate(-90)"}`
|
|
219
|
+
}];
|
|
220
|
+
}));
|
|
221
|
+
const anchor = flatHeader ? "middle" : below ? "end" : "start";
|
|
222
|
+
ctx.text(ctx.marks("column-labels", "text", columns, (d) => d.key)).attr("text-anchor", anchor).attr("dy", flatHeader ? null : "0.32em").attr("transform", (d) => d.transform).text((d) => WideLabel.fit(d.token, flatHeader ? frames[0].cw : header - 4));
|
|
223
|
+
}
|
|
224
|
+
titles(ctx, views, { frames, across, header, below }) {
|
|
225
|
+
const placed = views.flatMap(({ title }, panel) => {
|
|
226
|
+
const frame = frames[panel], lifted = panel < across ? below ? 44 : header : 0;
|
|
227
|
+
return title ? [{
|
|
228
|
+
panel,
|
|
229
|
+
title,
|
|
230
|
+
frame,
|
|
231
|
+
y: frame.y - lifted - 5
|
|
232
|
+
}] : [];
|
|
233
|
+
});
|
|
234
|
+
ctx.text(ctx.marks("panel-titles", "text", placed, (d) => d.panel), 11, "ink").attr("x", (d) => d.frame.x).attr("y", (d) => d.y).text((d) => WideLabel.fit(d.title, d.frame.width));
|
|
235
|
+
}
|
|
236
|
+
/** The hard alignment: each row's largest weight, as a dot on a tile or a full-size ring round a sized glyph. A flat row has no peak. */
|
|
237
|
+
peaks(ctx, views, { frames }, fill) {
|
|
238
|
+
const peaks = this.options.alignment ? views.flatMap(({ model }, panel) => model.values.flatMap((row, r) => {
|
|
239
|
+
const present = row.filter((value) => value !== null);
|
|
240
|
+
if (new Set(present).size < 2) return [];
|
|
241
|
+
const value = Math.max(...present);
|
|
242
|
+
return [{
|
|
243
|
+
key: `${panel}/${r}`,
|
|
244
|
+
frame: frames[panel],
|
|
245
|
+
r,
|
|
246
|
+
c: row.indexOf(value),
|
|
247
|
+
value
|
|
248
|
+
}];
|
|
249
|
+
})) : [];
|
|
250
|
+
const tile = this.options.glyph === MatrixGlyph.Tile;
|
|
251
|
+
const room = (frame) => Math.min(frame.cw, frame.ch) / 2 - 1;
|
|
252
|
+
const marks = ctx.marks("peaks", "circle", peaks, (d) => d.key).attr("pointer-events", "none").attr("fill", (d) => tile ? this.inkOn(ctx, fill(d.value)) : "none").attr("stroke", tile ? null : ctx.theme.ink).attr("stroke-width", tile ? null : 1.5);
|
|
253
|
+
ctx.set(marks, {
|
|
254
|
+
cx: (d) => d.frame.cx(d.c),
|
|
255
|
+
cy: (d) => d.frame.cy(d.r),
|
|
256
|
+
r: (d) => Math.max(1.5, tile ? Math.min(PEAK, room(d.frame)) : room(d.frame))
|
|
257
|
+
}, { r: 0 });
|
|
258
|
+
}
|
|
259
|
+
/** Bars from zero, with a tick at 1 so a row sum or a full share of coverage can be read against it. */
|
|
260
|
+
rails(ctx, model, frame, onRight, onTop) {
|
|
261
|
+
const scale = (totals) => scaleLinear([Math.min(0, ...totals), Math.max(1, ...totals)], [0, ROOM]);
|
|
262
|
+
const format = (value) => value.toFixed(3);
|
|
263
|
+
const rowTotals = onRight ? model.rowTotals : [], across = scale(rowTotals), left = frame.x + frame.width + 6, thick = Math.max(1, Math.min(10, frame.ch - 2));
|
|
264
|
+
const rowBars = ctx.marks("row-rail", "rect", rowTotals.map((total, index) => ({
|
|
265
|
+
total,
|
|
266
|
+
index
|
|
267
|
+
})), (d) => d.index).attr("fill", ctx.palette.sequential(.62)).attr("rx", 2);
|
|
268
|
+
ctx.set(rowBars, {
|
|
269
|
+
x: (d) => left + Math.min(across(0), across(d.total)),
|
|
270
|
+
y: (d) => frame.cy(d.index) - thick / 2,
|
|
271
|
+
width: (d) => Math.abs(across(d.total) - across(0)),
|
|
272
|
+
height: thick
|
|
273
|
+
}, {
|
|
274
|
+
x: left + across(0),
|
|
275
|
+
y: (d) => frame.cy(d.index) - thick / 2,
|
|
276
|
+
width: 0,
|
|
277
|
+
height: thick
|
|
278
|
+
});
|
|
279
|
+
ctx.hover(rowBars, (d) => ({
|
|
280
|
+
title: model.rows[d.index],
|
|
281
|
+
rows: [["Row sum", format(d.total)]]
|
|
282
|
+
}));
|
|
283
|
+
const columnTotals = onTop ? model.columnTotals : [], up = scale(columnTotals), base = frame.y - 6, wide = Math.max(1, Math.min(10, frame.cw - 2));
|
|
284
|
+
const columnBars = ctx.marks("column-rail", "rect", columnTotals.map((total, index) => ({
|
|
285
|
+
total,
|
|
286
|
+
index
|
|
287
|
+
})), (d) => d.index).attr("fill", ctx.palette.sequential(.62)).attr("rx", 2);
|
|
288
|
+
ctx.set(columnBars, {
|
|
289
|
+
x: (d) => frame.cx(d.index) - wide / 2,
|
|
290
|
+
y: (d) => base - Math.max(up(0), up(d.total)),
|
|
291
|
+
width: wide,
|
|
292
|
+
height: (d) => Math.abs(up(d.total) - up(0))
|
|
293
|
+
}, {
|
|
294
|
+
x: (d) => frame.cx(d.index) - wide / 2,
|
|
295
|
+
y: base - up(0),
|
|
296
|
+
width: wide,
|
|
297
|
+
height: 0
|
|
298
|
+
});
|
|
299
|
+
ctx.hover(columnBars, (d) => ({
|
|
300
|
+
title: model.columns[d.index],
|
|
301
|
+
rows: [["Coverage", format(d.total)]]
|
|
302
|
+
}));
|
|
303
|
+
const ticks = [...onRight ? [{
|
|
304
|
+
key: "row",
|
|
305
|
+
x1: left + across(1),
|
|
306
|
+
x2: left + across(1),
|
|
307
|
+
y1: frame.y,
|
|
308
|
+
y2: frame.y + frame.height
|
|
309
|
+
}] : [], ...onTop ? [{
|
|
310
|
+
key: "column",
|
|
311
|
+
x1: frame.x,
|
|
312
|
+
x2: frame.x + frame.width,
|
|
313
|
+
y1: base - up(1),
|
|
314
|
+
y2: base - up(1)
|
|
315
|
+
}] : []];
|
|
316
|
+
ctx.marks("rail-ticks", "line", ticks, (d) => d.key).attr("stroke", ctx.theme.axis).attr("stroke-width", 1).attr("x1", (d) => d.x1).attr("x2", (d) => d.x2).attr("y1", (d) => d.y1).attr("y2", (d) => d.y2);
|
|
317
|
+
const titles = [...onRight ? [{
|
|
318
|
+
text: "Row sum",
|
|
319
|
+
x: left,
|
|
320
|
+
anchor: "start"
|
|
321
|
+
}] : [], ...onTop ? [{
|
|
322
|
+
text: "Coverage",
|
|
323
|
+
x: frame.x - 6,
|
|
324
|
+
anchor: "end"
|
|
325
|
+
}] : []];
|
|
326
|
+
ctx.text(ctx.marks("rail-titles", "text", titles, (d) => d.text), 10.5, "muted").attr("x", (d) => d.x).attr("y", base).attr("text-anchor", (d) => d.anchor).text((d) => d.text);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
export { AttentionAspect, AttentionHeadView, AttentionHeatmap };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { MatrixModel } from "../matrix/MatrixModel.js";
|
|
2
|
+
import { sum } from "d3";
|
|
3
|
+
// src/charts/mlcharts/AttentionModel.ts
|
|
4
|
+
var token = (value) => value === null || value === void 0 ? "" : String(value);
|
|
5
|
+
var finite = (value) => value !== null && value !== void 0 && value !== "" && Number.isFinite(+value);
|
|
6
|
+
/** Attention weights per head on one pair of token axes. Rows are targets (queries) and columns are sources (keys). */
|
|
7
|
+
var AttentionModel = class AttentionModel {
|
|
8
|
+
source;
|
|
9
|
+
target;
|
|
10
|
+
heads;
|
|
11
|
+
irregular;
|
|
12
|
+
constructor(source, target, heads, irregular) {
|
|
13
|
+
this.source = source;
|
|
14
|
+
this.target = target;
|
|
15
|
+
this.heads = heads;
|
|
16
|
+
this.irregular = irregular;
|
|
17
|
+
}
|
|
18
|
+
static from(data, fields = {}) {
|
|
19
|
+
if (!data || typeof data !== "object") return AttentionModel.grids({
|
|
20
|
+
source: [],
|
|
21
|
+
target: [],
|
|
22
|
+
heads: []
|
|
23
|
+
});
|
|
24
|
+
if (Array.isArray(data)) return AttentionModel.long(data, fields);
|
|
25
|
+
if ("heads" in data) return AttentionModel.grids(data);
|
|
26
|
+
const { rows = [], columns = [], values = [] } = data;
|
|
27
|
+
return AttentionModel.grids({
|
|
28
|
+
source: columns,
|
|
29
|
+
target: rows,
|
|
30
|
+
heads: [{ weights: values }]
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
static grids({ source: sources, target: targets, heads }, extra = 0) {
|
|
34
|
+
const source = Array.isArray(sources) ? sources.map(token) : [], target = Array.isArray(targets) ? targets.map(token) : [];
|
|
35
|
+
const list = Array.isArray(heads) ? heads : [];
|
|
36
|
+
let irregular = extra;
|
|
37
|
+
const models = list.map((head, h) => {
|
|
38
|
+
const weights = Array.isArray(head?.weights) ? head.weights : [];
|
|
39
|
+
irregular += Math.abs(weights.length - target.length);
|
|
40
|
+
for (const row of weights.slice(0, target.length)) if (row?.length !== source.length) irregular++;
|
|
41
|
+
return {
|
|
42
|
+
name: head?.name ?? (list.length > 1 ? `Head ${h + 1}` : ""),
|
|
43
|
+
model: MatrixModel.from({
|
|
44
|
+
rows: target,
|
|
45
|
+
columns: source,
|
|
46
|
+
values: weights
|
|
47
|
+
})
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
return new AttentionModel(source, target, models, irregular);
|
|
51
|
+
}
|
|
52
|
+
static long(rows, { source = "source", target = "target", weight = "weight", head = "head", sourceIndex = "sourceIndex", targetIndex = "targetIndex" }) {
|
|
53
|
+
const placed = rows.filter((row) => row && row[source] != null && row[target] != null);
|
|
54
|
+
const sources = AttentionModel.axis(placed, source, sourceIndex), targets = AttentionModel.axis(placed, target, targetIndex);
|
|
55
|
+
const heads = /* @__PURE__ */ new Map(), seen = /* @__PURE__ */ new Set();
|
|
56
|
+
let irregular = rows.length - placed.length;
|
|
57
|
+
for (const row of placed) {
|
|
58
|
+
const name = token(row[head]);
|
|
59
|
+
if (!heads.has(name)) heads.set(name, targets.tokens.map(() => sources.tokens.map(() => null)));
|
|
60
|
+
const t = targets.at(row), s = sources.at(row), cell = `${name}/${t}/${s}`;
|
|
61
|
+
if (seen.has(cell)) irregular++;
|
|
62
|
+
seen.add(cell);
|
|
63
|
+
heads.get(name)[t][s] = row[weight];
|
|
64
|
+
}
|
|
65
|
+
return AttentionModel.grids({
|
|
66
|
+
source: sources.tokens,
|
|
67
|
+
target: targets.tokens,
|
|
68
|
+
heads: [...heads].map(([name, weights]) => ({
|
|
69
|
+
name,
|
|
70
|
+
weights
|
|
71
|
+
}))
|
|
72
|
+
}, irregular);
|
|
73
|
+
}
|
|
74
|
+
/** Token order along one axis: by position when every row has one, otherwise first seen. */
|
|
75
|
+
static axis(rows, text, position) {
|
|
76
|
+
const positional = rows.every((row) => finite(row[position]));
|
|
77
|
+
const keyOf = (row) => positional ? +row[position] : token(row[text]);
|
|
78
|
+
const slots = /* @__PURE__ */ new Map();
|
|
79
|
+
for (const row of rows) if (!slots.has(keyOf(row))) slots.set(keyOf(row), token(row[text]));
|
|
80
|
+
const keys = positional ? [...slots.keys()].sort((a, b) => +a - +b) : [...slots.keys()];
|
|
81
|
+
const index = new Map(keys.map((key, i) => [key, i]));
|
|
82
|
+
return {
|
|
83
|
+
tokens: keys.map((key) => slots.get(key)),
|
|
84
|
+
at: (row) => index.get(keyOf(row))
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
get empty() {
|
|
88
|
+
return !this.source.length || !this.target.length || !this.heads.length;
|
|
89
|
+
}
|
|
90
|
+
/** The cell-wise mean over heads, ignoring heads with no value at that cell. */
|
|
91
|
+
get pooled() {
|
|
92
|
+
if (this.heads.length === 1) return this.heads[0].model;
|
|
93
|
+
const values = this.target.map((_, t) => this.source.map((_, s) => {
|
|
94
|
+
const present = this.heads.map(({ model }) => model.values[t][s]).filter((value) => value !== null);
|
|
95
|
+
return present.length ? sum(present) / present.length : null;
|
|
96
|
+
}));
|
|
97
|
+
return new MatrixModel(this.target, this.source, values);
|
|
98
|
+
}
|
|
99
|
+
/** Checks each head's rows against the softmax property that a row sums to 1, without changing a weight. */
|
|
100
|
+
check(tolerance = .01) {
|
|
101
|
+
const failing = [];
|
|
102
|
+
let checked = 0, blankRows = 0, missing = 0, negative = 0;
|
|
103
|
+
for (const { name, model } of this.heads) model.values.forEach((row, r) => {
|
|
104
|
+
const present = row.filter((value) => value !== null);
|
|
105
|
+
missing += row.length - present.length;
|
|
106
|
+
negative += present.filter((value) => value < 0).length;
|
|
107
|
+
if (!present.length) {
|
|
108
|
+
blankRows++;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
checked++;
|
|
112
|
+
const total = sum(present);
|
|
113
|
+
if (Math.abs(total - 1) > tolerance) failing.push({
|
|
114
|
+
head: name,
|
|
115
|
+
row: r,
|
|
116
|
+
token: this.target[r],
|
|
117
|
+
sum: total
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
tolerance,
|
|
122
|
+
checked,
|
|
123
|
+
failing,
|
|
124
|
+
blankRows,
|
|
125
|
+
missing,
|
|
126
|
+
negative,
|
|
127
|
+
irregular: this.irregular,
|
|
128
|
+
ok: !failing.length && !blankRows && !missing && !negative && !this.irregular
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
export { AttentionModel };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { SampleData } from "../../data/SampleData.js";
|
|
2
|
+
// src/charts/mlcharts/AttentionSamples.ts
|
|
3
|
+
var SOURCE = [
|
|
4
|
+
"the",
|
|
5
|
+
"cat",
|
|
6
|
+
"sat",
|
|
7
|
+
"on",
|
|
8
|
+
"the",
|
|
9
|
+
"mat",
|
|
10
|
+
".",
|
|
11
|
+
"</s>"
|
|
12
|
+
];
|
|
13
|
+
var TARGET = [
|
|
14
|
+
"le",
|
|
15
|
+
"chat",
|
|
16
|
+
"était",
|
|
17
|
+
"assis",
|
|
18
|
+
"sur",
|
|
19
|
+
"le",
|
|
20
|
+
"tapis",
|
|
21
|
+
".",
|
|
22
|
+
"</s>"
|
|
23
|
+
];
|
|
24
|
+
/** Which source position each target token translates. */
|
|
25
|
+
var ALIGNED = [
|
|
26
|
+
0,
|
|
27
|
+
1,
|
|
28
|
+
2,
|
|
29
|
+
2,
|
|
30
|
+
3,
|
|
31
|
+
4,
|
|
32
|
+
5,
|
|
33
|
+
6,
|
|
34
|
+
7
|
|
35
|
+
];
|
|
36
|
+
/** Each head's focus on the source and how wide it spreads, as a Gaussian in logit space. */
|
|
37
|
+
var HEADS = [
|
|
38
|
+
{
|
|
39
|
+
name: "Head 1",
|
|
40
|
+
focus: (aligned) => aligned,
|
|
41
|
+
spread: .6
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "Head 2",
|
|
45
|
+
focus: (aligned) => aligned - 1,
|
|
46
|
+
spread: .8
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "Head 3",
|
|
50
|
+
focus: (aligned) => aligned,
|
|
51
|
+
spread: 2.5
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "Head 4",
|
|
55
|
+
focus: () => 1,
|
|
56
|
+
spread: 1.2
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
var NOISE = .3;
|
|
60
|
+
/** SYNTHETIC. Invented weights for an invented sentence pair, not the output of any model. */
|
|
61
|
+
var AttentionSamples = class AttentionSamples {
|
|
62
|
+
/** Softmax over noisy logits, so each row is a true probability distribution. */
|
|
63
|
+
static crossAttention(seed = 1) {
|
|
64
|
+
return {
|
|
65
|
+
source: SOURCE,
|
|
66
|
+
target: TARGET,
|
|
67
|
+
heads: HEADS.map(({ name, focus, spread }, h) => {
|
|
68
|
+
const { values } = SampleData.matrix(seed + h, TARGET.length, SOURCE.length);
|
|
69
|
+
return {
|
|
70
|
+
name,
|
|
71
|
+
weights: TARGET.map((_, t) => AttentionSamples.softmax(SOURCE.map((_, s) => -((s - focus(ALIGNED[t])) ** 2) / (2 * spread ** 2) + NOISE * values[t][s])))
|
|
72
|
+
};
|
|
73
|
+
})
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
static softmax(logits) {
|
|
77
|
+
const peak = Math.max(...logits), raised = logits.map((logit) => Math.exp(logit - peak));
|
|
78
|
+
const total = raised.reduce((sum, value) => sum + value, 0);
|
|
79
|
+
return raised.map((value) => value / total);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
export { AttentionSamples };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// src/charts/mlcharts/CellNumber.ts
|
|
2
|
+
/** A table cell as a finite number, or `null` for blanks, text and dates. */
|
|
3
|
+
var cellNumber = (cell) => {
|
|
4
|
+
if (typeof cell === "string" && !cell.trim()) return null;
|
|
5
|
+
const value = typeof cell === "number" || typeof cell === "string" ? Number(cell) : NaN;
|
|
6
|
+
return Number.isFinite(value) ? value : null;
|
|
7
|
+
};
|
|
8
|
+
export { cellNumber };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { SampleData } from "../../data/SampleData.js";
|
|
2
|
+
// src/charts/mlcharts/CurveDiagnosticSamples.ts
|
|
3
|
+
var squared = (a, b) => (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2;
|
|
4
|
+
/** Synthetic demo data: nothing here is a real measurement. */
|
|
5
|
+
var CurveDiagnosticSamples = class {
|
|
6
|
+
/** Standard normal draws from `SampleData`, since its generators are the only seeded source. */
|
|
7
|
+
static #normals(seed, count) {
|
|
8
|
+
const values = SampleData.distribution(seed, count, 1).map((row) => (row.value - 40) / 8);
|
|
9
|
+
let i = 0;
|
|
10
|
+
return () => values[i++ % values.length];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `{ epoch, run, split, value }`: a made-up loss from exponential decay, with validation drifting upward after a
|
|
14
|
+
* run-specific epoch so that it overfits. Not the output of any model.
|
|
15
|
+
*/
|
|
16
|
+
static trainingRuns(seed, epochs = 60, runs = 5) {
|
|
17
|
+
const z = this.#normals(seed, runs * (3 + epochs * 2));
|
|
18
|
+
return Array.from({ length: runs }, (_, r) => {
|
|
19
|
+
const run = `seed-${r + 1}`, tau = 9 + z() * .8, turn = 24 + z() * 3, level = .33 + z() * .025;
|
|
20
|
+
return Array.from({ length: epochs }, (_, i) => {
|
|
21
|
+
const epoch = i + 1, decay = 2.2 * Math.exp(-epoch / tau), noise = .25 + Math.exp(-epoch / 20);
|
|
22
|
+
const train = decay + .09 * Math.exp(-epoch / 60) + z() * .012 * noise;
|
|
23
|
+
const drift = 45e-5 * Math.max(0, epoch - turn) ** 2;
|
|
24
|
+
const validation = decay + level + drift + z() * .03 * noise;
|
|
25
|
+
return [{
|
|
26
|
+
epoch,
|
|
27
|
+
run,
|
|
28
|
+
split: "train",
|
|
29
|
+
value: +Math.max(.001, train).toFixed(4)
|
|
30
|
+
}, {
|
|
31
|
+
epoch,
|
|
32
|
+
run,
|
|
33
|
+
split: "validation",
|
|
34
|
+
value: +validation.toFixed(4)
|
|
35
|
+
}];
|
|
36
|
+
}).flat();
|
|
37
|
+
}).flat();
|
|
38
|
+
}
|
|
39
|
+
/** `{ k, inertia, silhouette }` from running k-means and the silhouette on `SampleData.points`, which is itself synthetic. */
|
|
40
|
+
static clusterSweep(seed, most = 10) {
|
|
41
|
+
const points = SampleData.points(seed, 160, 4).map((row, i) => [row.x, row.y - .8 * row.x + i % 2 * 42]);
|
|
42
|
+
return Array.from({ length: most }, (_, i) => {
|
|
43
|
+
const k = i + 1, { labels, inertia } = this.#kMeans(points, k);
|
|
44
|
+
return {
|
|
45
|
+
k,
|
|
46
|
+
inertia: +inertia.toFixed(1),
|
|
47
|
+
silhouette: k > 1 ? +this.#silhouette(points, labels, k).toFixed(4) : null
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/** Lloyd's algorithm from farthest-first seeds, so that one seed always gives one answer. */
|
|
52
|
+
static #kMeans(points, k) {
|
|
53
|
+
const centers = [points[0]];
|
|
54
|
+
while (centers.length < k) {
|
|
55
|
+
const reach = points.map((point) => Math.min(...centers.map((center) => squared(point, center))));
|
|
56
|
+
centers.push(points[reach.indexOf(Math.max(...reach))]);
|
|
57
|
+
}
|
|
58
|
+
let labels = [];
|
|
59
|
+
for (let pass = 0; pass < 40; pass++) {
|
|
60
|
+
const next = points.map((point) => {
|
|
61
|
+
const reach = centers.map((center) => squared(point, center));
|
|
62
|
+
return reach.indexOf(Math.min(...reach));
|
|
63
|
+
});
|
|
64
|
+
if (next.every((label, i) => label === labels[i])) break;
|
|
65
|
+
labels = next;
|
|
66
|
+
centers.forEach((_, c) => {
|
|
67
|
+
const members = points.filter((__, i) => labels[i] === c);
|
|
68
|
+
if (members.length) centers[c] = [members.reduce((a, p) => a + p[0], 0) / members.length, members.reduce((a, p) => a + p[1], 0) / members.length];
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
labels,
|
|
73
|
+
inertia: points.reduce((a, point, i) => a + squared(point, centers[labels[i]]), 0)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Mean silhouette width (Rousseeuw 1987); a point alone in its cluster scores zero. */
|
|
77
|
+
static #silhouette(points, labels, k) {
|
|
78
|
+
const widths = points.map((point, i) => {
|
|
79
|
+
const totals = new Array(k).fill(0), sizes = new Array(k).fill(0);
|
|
80
|
+
points.forEach((other, j) => {
|
|
81
|
+
if (j !== i) {
|
|
82
|
+
totals[labels[j]] += Math.sqrt(squared(point, other));
|
|
83
|
+
sizes[labels[j]]++;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const own = labels[i];
|
|
87
|
+
if (!sizes[own]) return 0;
|
|
88
|
+
const a = totals[own] / sizes[own];
|
|
89
|
+
const b = Math.min(...totals.flatMap((total, c) => c === own || !sizes[c] ? [] : [total / sizes[c]]));
|
|
90
|
+
return (b - a) / Math.max(a, b);
|
|
91
|
+
});
|
|
92
|
+
return widths.reduce((a, b) => a + b, 0) / widths.length;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
export { CurveDiagnosticSamples };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/charts/mlcharts/CurvePickMark.ts
|
|
2
|
+
var CHAR = 6.2;
|
|
3
|
+
var OFFSET = 7;
|
|
4
|
+
/** Dashed rule, ringed dot and one label, shared by the charts that single out a point on a curve. */
|
|
5
|
+
var CurvePickMark = class {
|
|
6
|
+
/** `labelY` is the text baseline, so the caller can keep the label off its curve. */
|
|
7
|
+
static draw(ctx, name, pick, labelY) {
|
|
8
|
+
const { width, height } = ctx.plot, picks = pick ? [pick] : [];
|
|
9
|
+
const rule = ctx.marks(`${name}-rule`, "line", picks).attr("stroke", ctx.theme.inkMuted).attr("stroke-width", 1).attr("stroke-dasharray", "4 3");
|
|
10
|
+
ctx.set(rule, {
|
|
11
|
+
x1: (d) => d.x,
|
|
12
|
+
x2: (d) => d.x,
|
|
13
|
+
y1: 0,
|
|
14
|
+
y2: height
|
|
15
|
+
});
|
|
16
|
+
const dot = ctx.marks(`${name}-dot`, "circle", picks).attr("fill", ctx.theme.ink).attr("stroke", ctx.theme.surface).attr("stroke-width", 2);
|
|
17
|
+
ctx.set(dot, {
|
|
18
|
+
cx: (d) => d.x,
|
|
19
|
+
cy: (d) => d.y,
|
|
20
|
+
r: 6
|
|
21
|
+
}, { r: 0 });
|
|
22
|
+
const reach = (d) => d.text.length * CHAR;
|
|
23
|
+
const flip = (d) => d.x + OFFSET + reach(d) > width;
|
|
24
|
+
const label = ctx.text(ctx.marks(`${name}-label`, "text", picks.filter((d) => reach(d) <= width)), 11, "ink").attr("text-anchor", (d) => flip(d) ? "end" : "start").attr("paint-order", "stroke").attr("stroke", ctx.theme.surface).attr("stroke-width", 3).text((d) => d.text);
|
|
25
|
+
ctx.set(label, {
|
|
26
|
+
x: (d) => flip(d) ? Math.max(reach(d), d.x - OFFSET) : d.x + OFFSET,
|
|
27
|
+
y: labelY
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
export { CurvePickMark };
|