My aggregated monorepo of OCaml code, automaintained
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add tessera-tfjs: TF.js SVD-based PCA for GeoTessera notebook

New tessera-tfjs library provides minimal js_of_ocaml bindings to
TensorFlow.js for PCA computation. Replaces the slow power-iteration
PCA in tessera-linalg with SVD on the covariance matrix via tf.linalg.svd.

- Tfjs.pca: single-call API, converts Linalg.mat to tf.tensor2d,
mean-centers, computes covariance SVD, projects, converts back
- All intermediate tensors disposed to prevent memory leaks
- TF.js loaded via CDN importScripts in the notebook setup cell
- Notebook updated to use Tfjs.pca instead of Linalg.pca_fit/transform

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+531 -4
+1 -1
build-site.sh
··· 70 70 dune exec -- jtw opam astring base brr note mime_printer fpath rresult \ 71 71 opam-format bos odoc.model tyxml yojson uri jsonm \ 72 72 js_top_worker-widget-leaflet \ 73 - tessera-geotessera-jsoo tessera-viz-jsoo \ 73 + tessera-geotessera-jsoo tessera-viz-jsoo tessera-tfjs \ 74 74 onnxrt -o "$SITE/_opam" 75 75 echo " universe built → $SITE/_opam/" 76 76 fi
+378
docs/superpowers/plans/2026-03-18-tessera-tfjs-pca.md
··· 1 + # tessera-tfjs: TF.js PCA Bindings Implementation Plan 2 + 3 + > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. 4 + 5 + **Goal:** Replace the slow power-iteration PCA in tessera-linalg with GPU-accelerated SVD-based PCA via TensorFlow.js bindings, improving both speed and accuracy for the GeoTessera notebook. 6 + 7 + **Architecture:** A new `tessera-tfjs` library provides minimal js_of_ocaml bindings to `@tensorflow/tfjs`, exposing a single `Tfjs.pca` function. The bindings use `Js.Unsafe` to call TF.js APIs loaded via CDN `importScripts` in the web worker. The notebook's setup cell loads TF.js and `#require`s the new library. 8 + 9 + **Tech Stack:** OCaml, js_of_ocaml, js_of_ocaml-ppx, TensorFlow.js (CDN), dune 10 + 11 + --- 12 + 13 + ## File Structure 14 + 15 + | File | Responsibility | 16 + |------|---------------| 17 + | `tessera-tfjs/dune-project` | Package metadata | 18 + | `tessera-tfjs/lib/dune` | Library build config | 19 + | `tessera-tfjs/lib/tfjs.ml` | TF.js bindings and PCA implementation | 20 + | `tessera-tfjs/lib/tfjs.mli` | Public API | 21 + | `tessera-tfjs/tessera-tfjs.opam` | Generated by dune | 22 + | `site/notebooks/interactive_map.mld` | Notebook: load TF.js CDN, use `Tfjs.pca` | 23 + | `build-site.sh` | Add `tessera-tfjs` to jtw universe package list | 24 + 25 + --- 26 + 27 + ### Task 1: Project scaffold 28 + 29 + **Files:** 30 + - Create: `tessera-tfjs/dune-project` 31 + - Create: `tessera-tfjs/lib/dune` 32 + - Create: `tessera-tfjs/lib/tfjs.mli` 33 + - Create: `tessera-tfjs/lib/tfjs.ml` 34 + 35 + - [ ] **Step 1: Create dune-project** 36 + 37 + ``` 38 + tessera-tfjs/dune-project 39 + ``` 40 + 41 + ``` 42 + (lang dune 3.17) 43 + (name tessera-tfjs) 44 + (generate_opam_files true) 45 + (license ISC) 46 + (package 47 + (name tessera-tfjs) 48 + (synopsis "TensorFlow.js PCA bindings for OCaml") 49 + (description "Minimal js_of_ocaml bindings to TensorFlow.js for SVD-based PCA. Operates on tessera-linalg mat types.") 50 + (depends 51 + (ocaml (>= 5.2)) 52 + (tessera-linalg (>= 0.1)) 53 + (js_of_ocaml (>= 5.0)) 54 + (js_of_ocaml-ppx (>= 5.0)))) 55 + ``` 56 + 57 + - [ ] **Step 2: Create lib/dune** 58 + 59 + ``` 60 + tessera-tfjs/lib/dune 61 + ``` 62 + 63 + ``` 64 + (library 65 + (name tfjs) 66 + (public_name tessera-tfjs) 67 + (libraries tessera-linalg js_of_ocaml) 68 + (preprocess (pps js_of_ocaml-ppx))) 69 + ``` 70 + 71 + - [ ] **Step 3: Create stub tfjs.mli** 72 + 73 + ``` 74 + tessera-tfjs/lib/tfjs.mli 75 + ``` 76 + 77 + ```ocaml 78 + (** TensorFlow.js PCA via SVD. 79 + 80 + Requires TensorFlow.js to be loaded in the JavaScript environment 81 + (e.g., via [importScripts] in a web worker). *) 82 + 83 + val pca : Linalg.mat -> n_components:int -> Linalg.mat 84 + (** [pca mat ~n_components] computes PCA on a matrix of shape 85 + [(n_samples, n_features)] and returns the projected data of shape 86 + [(n_samples, n_components)]. 87 + 88 + Uses TF.js SVD for accurate principal component extraction. 89 + All intermediate tensors are disposed to avoid memory leaks. 90 + 91 + @raise Failure if TensorFlow.js is not loaded. *) 92 + ``` 93 + 94 + - [ ] **Step 4: Create stub tfjs.ml** 95 + 96 + ``` 97 + tessera-tfjs/lib/tfjs.ml 98 + ``` 99 + 100 + ```ocaml 101 + let pca _mat ~n_components:_ = 102 + failwith "TODO: implement TF.js PCA" 103 + ``` 104 + 105 + - [ ] **Step 5: Build to verify scaffold compiles** 106 + 107 + Run: `opam exec -- dune build tessera-tfjs` 108 + Expected: success, no errors 109 + 110 + - [ ] **Step 6: Commit** 111 + 112 + ```bash 113 + git add tessera-tfjs/ 114 + git commit -m "tessera-tfjs: scaffold project with stub PCA API" 115 + ``` 116 + 117 + --- 118 + 119 + ### Task 2: Implement TF.js bindings and PCA 120 + 121 + **Files:** 122 + - Modify: `tessera-tfjs/lib/tfjs.ml` 123 + 124 + The implementation converts a `Linalg.mat` (float32 bigarray) to a TF.js tensor, 125 + computes mean-centered SVD, projects onto the top-k components, and converts back. 126 + All TF.js calls go through `Js.Unsafe` since we're binding to a globally-loaded 127 + library, not a compiled dependency. 128 + 129 + - [ ] **Step 1: Implement the TF.js PCA** 130 + 131 + ``` 132 + tessera-tfjs/lib/tfjs.ml 133 + ``` 134 + 135 + ```ocaml 136 + open Js_of_ocaml 137 + 138 + (* Access the global tf object loaded via importScripts/CDN *) 139 + let tf () : Js.Unsafe.any = 140 + let v = Js.Unsafe.global##.tf in 141 + if Js.Optdef.test v then v 142 + else failwith "TensorFlow.js not loaded. Load it via importScripts before calling Tfjs.pca." 143 + 144 + (* Helper: call a method on a JS object *) 145 + let call obj meth args = 146 + Js.Unsafe.meth_call obj meth (Array.of_list args) 147 + 148 + (* Helper: get a nested property *) 149 + let get obj prop = 150 + Js.Unsafe.get obj (Js.string prop) 151 + 152 + (* Convert Linalg.mat (float32 bigarray, row-major) to tf.tensor2d *) 153 + let mat_to_tensor (mat : Linalg.mat) : Js.Unsafe.any = 154 + let tf = tf () in 155 + (* Create a Float32Array view of the bigarray data *) 156 + let len = mat.rows * mat.cols in 157 + let js_arr = new%js Typed_array.float32Array len in 158 + for i = 0 to len - 1 do 159 + Typed_array.set js_arr i (Bigarray.Array1.get mat.data i) 160 + done; 161 + Js.Unsafe.meth_call tf "tensor2d" 162 + [| Js.Unsafe.inject js_arr; 163 + Js.Unsafe.inject (Js.array [| mat.rows; mat.cols |]) |] 164 + 165 + (* Convert tf.tensor2d back to Linalg.mat *) 166 + let tensor_to_mat (t : Js.Unsafe.any) : Linalg.mat = 167 + let shape = Js.to_array (get t "shape") in 168 + let rows = shape.(0) in 169 + let cols = shape.(1) in 170 + let result = Linalg.create_mat ~rows ~cols in 171 + (* dataSync returns a Float32Array *) 172 + let data : Typed_array.float32Array Js.t = call t "dataSync" [] in 173 + let len = rows * cols in 174 + for i = 0 to len - 1 do 175 + Bigarray.Array1.set result.data i (Typed_array.get data i) 176 + done; 177 + result 178 + 179 + (* Dispose a tensor to free GPU/CPU memory *) 180 + let dispose t = ignore (call t "dispose" []) 181 + 182 + let pca (mat : Linalg.mat) ~n_components : Linalg.mat = 183 + let tf = tf () in 184 + (* 1. Convert input to tensor *) 185 + let x = mat_to_tensor mat in 186 + (* 2. Compute mean along axis 0 *) 187 + let mean = call x "mean" [Js.Unsafe.inject 0] in 188 + (* 3. Center the data: x_centered = x - mean *) 189 + let x_centered = call x "sub" [Js.Unsafe.inject mean] in 190 + (* 4. Compute covariance-like matrix: X^T X / (n-1) 191 + For SVD-based PCA, we compute SVD of the centered data directly. 192 + For n_samples >> n_features (typical: 40k x 128), computing 193 + the covariance matrix (128x128) and then eigendecomposing is faster 194 + than SVD of the full matrix. *) 195 + let n_samples = Float.of_int mat.rows in 196 + let xt = call x_centered "transpose" [] in 197 + (* cov = X^T X / (n-1), shape (n_features, n_features) *) 198 + let xtx = call (get tf "linalg") "matMul" [Js.Unsafe.inject xt; Js.Unsafe.inject x_centered] in 199 + let scale = Js.Unsafe.meth_call tf "scalar" [| Js.Unsafe.inject (n_samples -. 1.0) |] in 200 + let cov = call xtx "div" [Js.Unsafe.inject scale] in 201 + (* 5. Eigendecompose the symmetric covariance matrix. 202 + tf.linalg.eig is not available, but we can use SVD on the symmetric 203 + matrix: for symmetric A, SVD gives A = U S V^T where U = V and S = eigenvalues. *) 204 + let svd_result = Js.Unsafe.meth_call (get tf "linalg") "svd" 205 + [| Js.Unsafe.inject cov; Js.Unsafe.inject Js._true |] in 206 + (* svd returns {s, u, v} or [s, u, v] — TF.js returns an object *) 207 + let v_full = get svd_result "v" in 208 + (* 6. Take top n_components columns of V *) 209 + let v_top = Js.Unsafe.meth_call v_full "slice" 210 + [| Js.Unsafe.inject (Js.array [| 0; 0 |]); 211 + Js.Unsafe.inject (Js.array [| mat.cols; n_components |]) |] in 212 + (* 7. Project: result = x_centered @ v_top, shape (n_samples, n_components) *) 213 + let projected = call (get tf "linalg") "matMul" 214 + [Js.Unsafe.inject x_centered; Js.Unsafe.inject v_top] in 215 + (* 8. Convert back to Linalg.mat *) 216 + let result = tensor_to_mat projected in 217 + (* 9. Clean up all tensors *) 218 + List.iter dispose [x; mean; x_centered; xt; xtx; scale; cov; 219 + get svd_result "s"; get svd_result "u"; v_full; 220 + v_top; projected]; 221 + result 222 + ``` 223 + 224 + - [ ] **Step 2: Build to verify it compiles** 225 + 226 + Run: `opam exec -- dune build tessera-tfjs` 227 + Expected: success, no errors 228 + 229 + - [ ] **Step 3: Commit** 230 + 231 + ```bash 232 + git add tessera-tfjs/lib/tfjs.ml 233 + git commit -m "tessera-tfjs: implement PCA via TF.js SVD on covariance matrix" 234 + ``` 235 + 236 + --- 237 + 238 + ### Task 3: Integrate into the notebook 239 + 240 + **Files:** 241 + - Modify: `site/notebooks/interactive_map.mld` 242 + - Modify: `build-site.sh` 243 + 244 + - [ ] **Step 1: Add tessera-tfjs to the jtw universe in build-site.sh** 245 + 246 + In `build-site.sh`, add `tessera-tfjs` to the jtw package list (line ~73): 247 + 248 + ```bash 249 + dune exec -- jtw opam astring base brr note mime_printer fpath rresult \ 250 + opam-format bos odoc.model tyxml yojson uri jsonm \ 251 + js_top_worker-widget-leaflet \ 252 + tessera-geotessera-jsoo tessera-viz-jsoo tessera-tfjs \ 253 + onnxrt -o "$SITE/_opam" 254 + ``` 255 + 256 + - [ ] **Step 2: Update the notebook setup cell to load TF.js and require tessera-tfjs** 257 + 258 + In `site/notebooks/interactive_map.mld`, modify the setup cell (lines 15-20). 259 + 260 + Before: 261 + ``` 262 + {@ocaml kind=setup[ 263 + #require "tessera-geotessera-jsoo";; 264 + #require "tessera-viz-jsoo";; 265 + #require "js_top_worker-widget-leaflet";; 266 + Widget_leaflet.register ();; 267 + ]} 268 + ``` 269 + 270 + After: 271 + ``` 272 + {@ocaml kind=setup[ 273 + #require "tessera-geotessera-jsoo";; 274 + #require "tessera-viz-jsoo";; 275 + #require "tessera-tfjs";; 276 + #require "js_top_worker-widget-leaflet";; 277 + Widget_leaflet.register ();; 278 + (* Load TensorFlow.js in the worker *) 279 + let () = 280 + let open Js_of_ocaml in 281 + Js.Unsafe.global##importScripts 282 + (Js.string "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@4/dist/tf.min.js") 283 + ]} 284 + ``` 285 + 286 + - [ ] **Step 3: Replace Linalg PCA with Tfjs.pca in the fetch cell** 287 + 288 + In `site/notebooks/interactive_map.mld`, in the fetch/visualize cell (around lines 136-138). 289 + 290 + Before: 291 + ```ocaml 292 + let pca = Linalg.pca_fit ~max_samples:5000 mat ~n_components:3 in 293 + let proj = Linalg.pca_transform pca mat in 294 + ``` 295 + 296 + After: 297 + ```ocaml 298 + let proj = Tfjs.pca mat ~n_components:3 in 299 + ``` 300 + 301 + - [ ] **Step 4: Build everything** 302 + 303 + Run: `opam exec -- dune build` 304 + Expected: success, no errors 305 + 306 + - [ ] **Step 5: Commit** 307 + 308 + ```bash 309 + git add site/notebooks/interactive_map.mld build-site.sh 310 + git commit -m "notebook: use TF.js PCA via tessera-tfjs for faster, more accurate PCA" 311 + ``` 312 + 313 + --- 314 + 315 + ### Task 4: Test in browser 316 + 317 + **Files:** (none modified — manual testing) 318 + 319 + - [ ] **Step 1: Rebuild the site universe** 320 + 321 + ```bash 322 + rm -rf _site/_opam 323 + ./build-site.sh --fresh 324 + ``` 325 + 326 + Or for faster iteration during development: 327 + 328 + ```bash 329 + dune exec -- jtw opam astring base brr note mime_printer fpath rresult \ 330 + opam-format bos odoc.model tyxml yojson uri jsonm \ 331 + js_top_worker-widget-leaflet \ 332 + tessera-geotessera-jsoo tessera-viz-jsoo tessera-tfjs \ 333 + onnxrt -o _site/_opam 334 + ``` 335 + 336 + - [ ] **Step 2: Serve and test** 337 + 338 + ```bash 339 + cd _site && python3 -m http.server 8080 340 + ``` 341 + 342 + Open `http://localhost:8080/notebooks/interactive_map.html`: 343 + 1. Wait for setup cell to load (should load TF.js without errors) 344 + 2. Draw a bounding box on the map 345 + 3. Run the fetch cell 346 + 4. Verify: PCA overlay appears on the map 347 + 5. Check browser console for any TF.js errors 348 + 349 + - [ ] **Step 3: Verify TF.js is actually being used** 350 + 351 + Open the browser console. After running the fetch cell, you should see TF.js 352 + initialization messages (e.g., "Initializing backend 'cpu'" or "'webgl'"). 353 + No messages about "TensorFlow.js not loaded" errors. 354 + 355 + --- 356 + 357 + ### Notes 358 + 359 + **TF.js SVD approach:** We use covariance matrix eigendecomposition rather than 360 + direct SVD on the full data matrix. For the typical GeoTessera case (40k samples 361 + × 128 features), computing the 128×128 covariance matrix and SVD-ing it is much 362 + faster than SVD on a 40k×128 matrix. 363 + 364 + **TF.js loading:** The CDN script is loaded via `importScripts` in the worker's 365 + setup cell. This is synchronous and blocks until loaded (~1-2 seconds). The TF.js 366 + global `tf` object is then available for all subsequent cells. 367 + 368 + **Memory management:** All intermediate TF.js tensors are explicitly disposed 369 + after use. TF.js tracks tensors for manual memory management — failing to dispose 370 + causes GPU/CPU memory leaks. 371 + 372 + **Fallback:** The existing `Linalg.pca_fit`/`pca_transform` remains available as 373 + a pure-OCaml fallback for environments without TF.js. 374 + 375 + **TF.js SVD on symmetric matrices:** For a symmetric positive semi-definite matrix 376 + (like a covariance matrix), SVD is equivalent to eigendecomposition. The right 377 + singular vectors V are the eigenvectors, and the singular values S are the 378 + eigenvalues. `tf.linalg.svd` with `fullMatrices=true` returns `{s, u, v}`.
+8 -3
site/notebooks/interactive_map.mld
··· 15 15 {@ocaml kind=setup[ 16 16 #require "tessera-geotessera-jsoo";; 17 17 #require "tessera-viz-jsoo";; 18 + #require "tessera-tfjs";; 18 19 #require "js_top_worker-widget-leaflet";; 19 20 Widget_leaflet.register ();; 21 + (* Load TensorFlow.js in the worker *) 22 + let () = 23 + let open Js_of_ocaml in 24 + Js.Unsafe.global##importScripts 25 + (Js.string "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@4/dist/tf.min.js") 20 26 ]} 21 27 22 28 {1 Select region of interest} ··· 133 139 let mosaic_east = mosaic_bbox.Geotessera.max_lon in 134 140 Widget.update ~id:"status" 135 141 (status_view (Printf.sprintf "Working at %d×%d (%d pixels). Computing PCA..." h w (h * w))); 136 - (* PCA to 3 components for RGB visualisation *) 137 - let pca = Linalg.pca_fit ~max_samples:5000 mat ~n_components:3 in 138 - let proj = Linalg.pca_transform pca mat in 142 + (* PCA to 3 components for RGB visualisation (via TensorFlow.js SVD) *) 143 + let proj = Tfjs.pca mat ~n_components:3 in 139 144 projected := Some proj; 140 145 let pca_img = Viz.pca_to_rgba ~width:w ~height:h proj in 141 146 let url = Viz_jsoo.to_data_url pca_img in
+13
tessera-tfjs/dune-project
··· 1 + (lang dune 3.17) 2 + (name tessera-tfjs) 3 + (generate_opam_files true) 4 + (license ISC) 5 + (package 6 + (name tessera-tfjs) 7 + (synopsis "TensorFlow.js PCA bindings for OCaml") 8 + (description "Minimal js_of_ocaml bindings to TensorFlow.js for SVD-based PCA. Operates on tessera-linalg mat types.") 9 + (depends 10 + (ocaml (>= 5.2)) 11 + (tessera-linalg (>= 0.1)) 12 + (js_of_ocaml (>= 5.0)) 13 + (js_of_ocaml-ppx (>= 5.0))))
+5
tessera-tfjs/lib/dune
··· 1 + (library 2 + (name tfjs) 3 + (public_name tessera-tfjs) 4 + (libraries tessera-linalg js_of_ocaml) 5 + (preprocess (pps js_of_ocaml-ppx)))
+83
tessera-tfjs/lib/tfjs.ml
··· 1 + open Js_of_ocaml 2 + 3 + (* Access the global tf object loaded via importScripts/CDN *) 4 + let tf () : Js.Unsafe.any = 5 + let v = Js.Unsafe.global##.tf in 6 + if Js.Optdef.test v then v 7 + else failwith "TensorFlow.js not loaded. Load it via importScripts before calling Tfjs.pca." 8 + 9 + (* Helper: call a method on a JS object *) 10 + let call obj meth args = 11 + Js.Unsafe.meth_call obj meth (Array.of_list args) 12 + 13 + (* Helper: get a nested property *) 14 + let get obj prop = 15 + Js.Unsafe.get obj (Js.string prop) 16 + 17 + (* Convert Linalg.mat (float32 bigarray, row-major) to tf.tensor2d *) 18 + let mat_to_tensor (mat : Linalg.mat) : Js.Unsafe.any = 19 + let tf = tf () in 20 + let len = mat.rows * mat.cols in 21 + let js_arr = new%js Typed_array.float32Array len in 22 + for i = 0 to len - 1 do 23 + Typed_array.set js_arr i (Js.number_of_float (Bigarray.Array1.get mat.data i)) 24 + done; 25 + Js.Unsafe.meth_call tf "tensor2d" 26 + [| Js.Unsafe.inject js_arr; 27 + Js.Unsafe.inject (Js.array [| mat.rows; mat.cols |]) |] 28 + 29 + (* Convert tf.tensor2d back to Linalg.mat *) 30 + let tensor_to_mat (t : Js.Unsafe.any) : Linalg.mat = 31 + let shape = Js.to_array (get t "shape") in 32 + let rows = shape.(0) in 33 + let cols = shape.(1) in 34 + let result = Linalg.create_mat ~rows ~cols in 35 + let data : Typed_array.float32Array Js.t = call t "dataSync" [] in 36 + let len = rows * cols in 37 + for i = 0 to len - 1 do 38 + let v = Js.Optdef.get (Typed_array.get data i) (fun () -> Js.number_of_float 0.0) in 39 + Bigarray.Array1.set result.data i (Js.float_of_number v) 40 + done; 41 + result 42 + 43 + (* Dispose a tensor to free GPU/CPU memory *) 44 + let dispose t = ignore (call t "dispose" []) 45 + 46 + let pca (mat : Linalg.mat) ~n_components : Linalg.mat = 47 + let tf = tf () in 48 + (* 1. Convert input to tensor *) 49 + let x = mat_to_tensor mat in 50 + (* 2. Compute mean along axis 0 *) 51 + let mean = call x "mean" [Js.Unsafe.inject 0] in 52 + (* 3. Center the data: x_centered = x - mean *) 53 + let x_centered = call x "sub" [Js.Unsafe.inject mean] in 54 + (* 4. Covariance matrix: X^T X / (n-1), shape (n_features, n_features). 55 + For n_samples >> n_features (typical: 40k x 128), this is much faster 56 + than SVD on the full data matrix. *) 57 + let n_samples = Float.of_int mat.rows in 58 + let xt = call x_centered "transpose" [] in 59 + let xtx = Js.Unsafe.meth_call tf "matMul" 60 + [| Js.Unsafe.inject xt; Js.Unsafe.inject x_centered |] in 61 + let scale = Js.Unsafe.meth_call tf "scalar" 62 + [| Js.Unsafe.inject (n_samples -. 1.0) |] in 63 + let cov = call xtx "div" [Js.Unsafe.inject scale] in 64 + (* 5. SVD of symmetric covariance matrix. 65 + For symmetric PSD matrices, SVD gives eigendecomposition: 66 + V columns are eigenvectors, S values are eigenvalues. *) 67 + let svd_result = Js.Unsafe.meth_call (get tf "linalg") "svd" 68 + [| Js.Unsafe.inject cov; Js.Unsafe.inject Js._true |] in 69 + let v_full = get svd_result "v" in 70 + (* 6. Take top n_components columns of V *) 71 + let v_top = Js.Unsafe.meth_call v_full "slice" 72 + [| Js.Unsafe.inject (Js.array [| 0; 0 |]); 73 + Js.Unsafe.inject (Js.array [| mat.cols; n_components |]) |] in 74 + (* 7. Project: result = x_centered @ v_top, shape (n_samples, n_components) *) 75 + let projected = Js.Unsafe.meth_call tf "matMul" 76 + [| Js.Unsafe.inject x_centered; Js.Unsafe.inject v_top |] in 77 + (* 8. Convert back to Linalg.mat *) 78 + let result = tensor_to_mat projected in 79 + (* 9. Clean up all tensors *) 80 + List.iter dispose [x; mean; x_centered; xt; xtx; scale; cov; 81 + get svd_result "s"; get svd_result "u"; v_full; 82 + v_top; projected]; 83 + result
+15
tessera-tfjs/lib/tfjs.mli
··· 1 + (** TensorFlow.js PCA via SVD. 2 + 3 + Requires TensorFlow.js to be loaded in the JavaScript environment 4 + (e.g., via [importScripts] in a web worker). *) 5 + 6 + val pca : Linalg.mat -> n_components:int -> Linalg.mat 7 + (** [pca mat ~n_components] computes PCA on a matrix of shape 8 + [(n_samples, n_features)] and returns the projected data of shape 9 + [(n_samples, n_components)]. 10 + 11 + Uses TF.js SVD on the covariance matrix for accurate principal 12 + component extraction. All intermediate tensors are disposed to 13 + avoid memory leaks. 14 + 15 + @raise Failure if TensorFlow.js is not loaded. *)
+28
tessera-tfjs/tessera-tfjs.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + synopsis: "TensorFlow.js PCA bindings for OCaml" 4 + description: 5 + "Minimal js_of_ocaml bindings to TensorFlow.js for SVD-based PCA. Operates on tessera-linalg mat types." 6 + license: "ISC" 7 + depends: [ 8 + "dune" {>= "3.17"} 9 + "ocaml" {>= "5.2"} 10 + "tessera-linalg" {>= "0.1"} 11 + "js_of_ocaml" {>= "5.0"} 12 + "js_of_ocaml-ppx" {>= "5.0"} 13 + "odoc" {with-doc} 14 + ] 15 + build: [ 16 + ["dune" "subst"] {dev} 17 + [ 18 + "dune" 19 + "build" 20 + "-p" 21 + name 22 + "-j" 23 + jobs 24 + "@install" 25 + "@runtest" {with-test} 26 + "@doc" {with-doc} 27 + ] 28 + ]