jgalego commited on
Commit
3aae6e1
·
verified ·
1 Parent(s): 8a5d4cf

Promote aux weight 1 model

Browse files
Files changed (5) hide show
  1. knot2vec.py +68 -10
  2. model.safetensors +1 -1
  3. results/eval.json +25 -25
  4. results/map.png +2 -2
  5. results/train.json +7 -7
knot2vec.py CHANGED
@@ -5,10 +5,12 @@
5
  # "datasets",
6
  # "huggingface-hub",
7
  # "jinja2",
 
8
  # "numpy",
9
  # "safetensors",
10
  # "snappy",
11
  # "torch",
 
12
  # ]
13
  # ///
14
  """Knot2Vec: embed knot diagrams so that diagrams of the same knot land together."""
@@ -28,9 +30,11 @@ from pathlib import Path
28
 
29
  import datasets
30
  import huggingface_hub.utils
 
31
  import numpy as np
32
  import snappy
33
  import torch
 
34
  from database_knotinfo import link_list
35
  from datasets import Dataset, load_dataset
36
  from huggingface_hub import (
@@ -46,6 +50,12 @@ from torch.nn import functional
46
 
47
  REPO = "jgalego/knot2vec"
48
  DATA = "jgalego/knot2vec-diagrams"
 
 
 
 
 
 
49
  HERE = Path(__file__).parent
50
  # Progress bars redraw in place, which shows up as garbage in HF Jobs logs.
51
  PROGRESS = sys.stderr.isatty()
@@ -264,12 +274,13 @@ def schedule(step, warmup, total):
264
  return 0.5 * (1 + math.cos(math.pi * (step - warmup) / max(1, total - warmup)))
265
 
266
 
267
- def push_result(repo, name, result):
268
  """Upload a result as results/<name>.json in the model repo."""
269
  HfApi().upload_file(
270
  path_or_fileobj=json.dumps(result, indent=1).encode(),
271
  path_in_repo=f"results/{name}.json",
272
  repo_id=repo,
 
273
  commit_message=f"Add {name} results",
274
  )
275
 
@@ -351,8 +362,20 @@ def train(args):
351
  (Path(args.output) / "README.md").unlink(missing_ok=True)
352
  if args.push:
353
  api = HfApi()
354
- api.upload_folder(folder_path=args.output, repo_id=args.repo, commit_message="Upload model")
355
- api.upload_file(path_or_fileobj=__file__, path_in_repo="knot2vec.py", repo_id=args.repo)
 
 
 
 
 
 
 
 
 
 
 
 
356
  push_result(
357
  args.repo,
358
  "train",
@@ -370,6 +393,7 @@ def train(args):
370
  "runtime_s": round(time.time() - start),
371
  "device": torch.cuda.get_device_name() if device == "cuda" else "cpu",
372
  },
 
373
  )
374
 
375
 
@@ -379,10 +403,30 @@ def nearest(queries, gallery, k=5, chunk=4096):
379
  return torch.cat([(queries[i : i + chunk] @ gallery.T).topk(k).indices for i in starts])
380
 
381
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
  def evaluate(args):
383
  """Name held-out diagrams by nearest canonical diagram; check the invariant heads."""
384
  device = "cuda" if torch.cuda.is_available() else "cpu"
385
- model = Knot2Vec.from_pretrained(args.model).to(device).eval()
386
  knots = table(args.data, "knots", "train").to_dict()
387
  index = {n: i for i, n in enumerate(knots["name"])}
388
  gallery = model.embed(knots["pd"]).to(device)
@@ -404,10 +448,13 @@ def evaluate(args):
404
  "n": int(mask.sum()),
405
  "top1": round(hits1[mask].float().mean().item(), 3),
406
  "top5": round(hits5[mask].float().mean().item(), 3),
407
- **{
408
- f"{key}_accuracy": round((out[key] == truth[key])[mask].float().mean().item(), 3)
409
- for key in ("signature", "determinant")
410
- },
 
 
 
411
  "volume_mae": round((out["volume"] - truth["volume"])[mask].abs().mean().item(), 3),
412
  "top1_by_crossings": {
413
  int(c): round(hits1[mask & (crossings == c)].float().mean().item(), 3)
@@ -415,8 +462,16 @@ def evaluate(args):
415
  },
416
  }
417
  print(json.dumps(result, indent=1))
 
 
418
  if args.push:
419
- push_result(args.repo, "eval", result)
 
 
 
 
 
 
420
 
421
 
422
  def embed(args):
@@ -496,13 +551,14 @@ def main():
496
  train_parser.add_argument("--lr", type=float, default=3e-4)
497
  train_parser.add_argument("--warmup", type=int, default=1000)
498
  train_parser.add_argument("--temperature", type=float, default=0.05)
499
- train_parser.add_argument("--aux-weight", type=float, default=0.1)
500
  train_parser.add_argument("--dim", type=int, default=256)
501
  train_parser.add_argument("--depth", type=int, default=6)
502
  train_parser.add_argument("--max-crossings", type=int, default=64)
503
  train_parser.add_argument("--seed", type=int, default=0)
504
  train_parser.add_argument("--output", default="out/model")
505
  train_parser.add_argument("--repo", default=REPO)
 
506
  train_parser.add_argument("--push", action="store_true")
507
  train_parser.set_defaults(run=train)
508
 
@@ -511,6 +567,8 @@ def main():
511
  eval_parser.add_argument("--data", default=DATA)
512
  eval_parser.add_argument("--limit", type=int, default=None, help="number of test diagrams")
513
  eval_parser.add_argument("--repo", default=REPO, help="where --push stores the result")
 
 
514
  eval_parser.add_argument("--push", action="store_true")
515
  eval_parser.set_defaults(run=evaluate)
516
 
 
5
  # "datasets",
6
  # "huggingface-hub",
7
  # "jinja2",
8
+ # "matplotlib",
9
  # "numpy",
10
  # "safetensors",
11
  # "snappy",
12
  # "torch",
13
+ # "umap-learn",
14
  # ]
15
  # ///
16
  """Knot2Vec: embed knot diagrams so that diagrams of the same knot land together."""
 
30
 
31
  import datasets
32
  import huggingface_hub.utils
33
+ import matplotlib.pyplot as plt
34
  import numpy as np
35
  import snappy
36
  import torch
37
+ import umap
38
  from database_knotinfo import link_list
39
  from datasets import Dataset, load_dataset
40
  from huggingface_hub import (
 
50
 
51
  REPO = "jgalego/knot2vec"
52
  DATA = "jgalego/knot2vec-diagrams"
53
+ LANDMARKS = {
54
+ "3_1": "trefoil",
55
+ "4_1": "figure-eight",
56
+ "10_124": "T(3,5)",
57
+ "11n_34": "Conway",
58
+ }
59
  HERE = Path(__file__).parent
60
  # Progress bars redraw in place, which shows up as garbage in HF Jobs logs.
61
  PROGRESS = sys.stderr.isatty()
 
274
  return 0.5 * (1 + math.cos(math.pi * (step - warmup) / max(1, total - warmup)))
275
 
276
 
277
+ def push_result(repo, name, result, revision=None):
278
  """Upload a result as results/<name>.json in the model repo."""
279
  HfApi().upload_file(
280
  path_or_fileobj=json.dumps(result, indent=1).encode(),
281
  path_in_repo=f"results/{name}.json",
282
  repo_id=repo,
283
+ revision=revision,
284
  commit_message=f"Add {name} results",
285
  )
286
 
 
362
  (Path(args.output) / "README.md").unlink(missing_ok=True)
363
  if args.push:
364
  api = HfApi()
365
+ if args.revision:
366
+ api.create_branch(args.repo, branch=args.revision, exist_ok=True)
367
+ api.upload_folder(
368
+ folder_path=args.output,
369
+ repo_id=args.repo,
370
+ revision=args.revision,
371
+ commit_message="Upload model",
372
+ )
373
+ api.upload_file(
374
+ path_or_fileobj=__file__,
375
+ path_in_repo="knot2vec.py",
376
+ repo_id=args.repo,
377
+ revision=args.revision,
378
+ )
379
  push_result(
380
  args.repo,
381
  "train",
 
393
  "runtime_s": round(time.time() - start),
394
  "device": torch.cuda.get_device_name() if device == "cuda" else "cpu",
395
  },
396
+ args.revision,
397
  )
398
 
399
 
 
403
  return torch.cat([(queries[i : i + chunk] @ gallery.T).topk(k).indices for i in starts])
404
 
405
 
406
+ def knot_map(gallery, knots, path):
407
+ """UMAP of the catalogue's embeddings, coloured by signature, with a few landmarks."""
408
+ xy = umap.UMAP(metric="cosine", random_state=0).fit_transform(gallery.cpu().numpy())
409
+ fig, ax = plt.subplots(figsize=(8, 7))
410
+ points = ax.scatter(
411
+ *xy.T, c=knots["signature"], cmap="Spectral", vmin=-MAX_SIGNATURE, vmax=MAX_SIGNATURE,
412
+ s=2, linewidths=0,
413
+ )
414
+ fig.colorbar(points, ax=ax, label="Signature", shrink=0.7)
415
+ for i, name in enumerate(knots["name"]):
416
+ if name in LANDMARKS:
417
+ ax.annotate(
418
+ f"{name} {LANDMARKS[name]}", xy[i], xytext=(6, 6), textcoords="offset points",
419
+ fontsize=9, arrowprops={"arrowstyle": "-", "color": "0.3"},
420
+ )
421
+ ax.set_axis_off()
422
+ fig.savefig(path, dpi=150, bbox_inches="tight")
423
+ plt.close(fig)
424
+
425
+
426
  def evaluate(args):
427
  """Name held-out diagrams by nearest canonical diagram; check the invariant heads."""
428
  device = "cuda" if torch.cuda.is_available() else "cpu"
429
+ model = Knot2Vec.from_pretrained(args.model, revision=args.revision).to(device).eval()
430
  knots = table(args.data, "knots", "train").to_dict()
431
  index = {n: i for i, n in enumerate(knots["name"])}
432
  gallery = model.embed(knots["pd"]).to(device)
 
448
  "n": int(mask.sum()),
449
  "top1": round(hits1[mask].float().mean().item(), 3),
450
  "top5": round(hits5[mask].float().mean().item(), 3),
451
+ "signature_accuracy": round(
452
+ (out["signature"] == truth["signature"])[mask].float().mean().item(), 3
453
+ ),
454
+ "determinant_within_10pct": round(
455
+ (abs(out["determinant"] - truth["determinant"]) <= 0.1 * truth["determinant"])[mask]
456
+ .float().mean().item(), 3
457
+ ),
458
  "volume_mae": round((out["volume"] - truth["volume"])[mask].abs().mean().item(), 3),
459
  "top1_by_crossings": {
460
  int(c): round(hits1[mask & (crossings == c)].float().mean().item(), 3)
 
462
  },
463
  }
464
  print(json.dumps(result, indent=1))
465
+ Path(args.output).mkdir(parents=True, exist_ok=True)
466
+ knot_map(gallery, knots, Path(args.output) / "map.png")
467
  if args.push:
468
+ push_result(args.repo, "eval", result, args.revision)
469
+ HfApi().upload_file(
470
+ path_or_fileobj=Path(args.output) / "map.png",
471
+ path_in_repo="results/map.png",
472
+ repo_id=args.repo,
473
+ revision=args.revision,
474
+ )
475
 
476
 
477
  def embed(args):
 
551
  train_parser.add_argument("--lr", type=float, default=3e-4)
552
  train_parser.add_argument("--warmup", type=int, default=1000)
553
  train_parser.add_argument("--temperature", type=float, default=0.05)
554
+ train_parser.add_argument("--aux-weight", type=float, default=1.0)
555
  train_parser.add_argument("--dim", type=int, default=256)
556
  train_parser.add_argument("--depth", type=int, default=6)
557
  train_parser.add_argument("--max-crossings", type=int, default=64)
558
  train_parser.add_argument("--seed", type=int, default=0)
559
  train_parser.add_argument("--output", default="out/model")
560
  train_parser.add_argument("--repo", default=REPO)
561
+ train_parser.add_argument("--revision", default=None, help="branch to push to")
562
  train_parser.add_argument("--push", action="store_true")
563
  train_parser.set_defaults(run=train)
564
 
 
567
  eval_parser.add_argument("--data", default=DATA)
568
  eval_parser.add_argument("--limit", type=int, default=None, help="number of test diagrams")
569
  eval_parser.add_argument("--repo", default=REPO, help="where --push stores the result")
570
+ eval_parser.add_argument("--revision", default=None, help="model branch")
571
+ eval_parser.add_argument("--output", default="out/eval", help="where the knot map goes")
572
  eval_parser.add_argument("--push", action="store_true")
573
  eval_parser.set_defaults(run=evaluate)
574
 
model.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:3c9782c590fdc438543af62d807a2ab69f7f4111a38ab8d18c8ce9ebe1acbff8
3
  size 19708148
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:786f8b9cb77a3b430caaa584312e3db9a5939c4f480c5b038963634b642d205a
3
  size 19708148
results/eval.json CHANGED
@@ -4,41 +4,41 @@
4
  "chance_top1": 7.7e-05,
5
  "seen": {
6
  "n": 46676,
7
- "top1": 0.849,
8
- "top5": 0.973,
9
- "signature_accuracy": 0.794,
10
- "determinant_within_10pct": 0.199,
11
- "volume_mae": 1.507,
12
  "top1_by_crossings": {
13
  "3": 1.0,
14
  "5": 0.75,
15
  "6": 1.0,
16
- "7": 0.714,
17
- "8": 0.764,
18
- "9": 0.835,
19
- "10": 0.76,
20
- "11": 0.879,
21
- "12": 0.809,
22
- "13": 0.858
23
  }
24
  },
25
  "unseen": {
26
  "n": 5184,
27
- "top1": 0.856,
28
- "top5": 0.969,
29
- "signature_accuracy": 0.781,
30
- "determinant_within_10pct": 0.207,
31
- "volume_mae": 1.583,
32
  "top1_by_crossings": {
33
- "4": 0.5,
34
- "5": 0.5,
35
  "6": 0.75,
36
- "8": 0.75,
37
- "9": 0.7,
38
- "10": 0.691,
39
- "11": 0.863,
40
- "12": 0.833,
41
- "13": 0.865
42
  }
43
  }
44
  }
 
4
  "chance_top1": 7.7e-05,
5
  "seen": {
6
  "n": 46676,
7
+ "top1": 0.881,
8
+ "top5": 0.976,
9
+ "signature_accuracy": 0.917,
10
+ "determinant_within_10pct": 0.267,
11
+ "volume_mae": 1.237,
12
  "top1_by_crossings": {
13
  "3": 1.0,
14
  "5": 0.75,
15
  "6": 1.0,
16
+ "7": 0.821,
17
+ "8": 0.833,
18
+ "9": 0.756,
19
+ "10": 0.769,
20
+ "11": 0.844,
21
+ "12": 0.864,
22
+ "13": 0.889
23
  }
24
  },
25
  "unseen": {
26
  "n": 5184,
27
+ "top1": 0.88,
28
+ "top5": 0.976,
29
+ "signature_accuracy": 0.891,
30
+ "determinant_within_10pct": 0.248,
31
+ "volume_mae": 1.39,
32
  "top1_by_crossings": {
33
+ "4": 0.75,
34
+ "5": 0.75,
35
  "6": 0.75,
36
+ "8": 0.917,
37
+ "9": 0.65,
38
+ "10": 0.779,
39
+ "11": 0.806,
40
+ "12": 0.86,
41
+ "13": 0.893
42
  }
43
  }
44
  }
results/map.png CHANGED

Git LFS Details

  • SHA256: 2657a24311fbf0c400401b0cb0d52133d46e2bfce1e5e429b5f80fb8c11aae99
  • Pointer size: 131 Bytes
  • Size of remote file: 266 kB

Git LFS Details

  • SHA256: f2f6b79f47f5fbfcb1d57728cdca89cff4b230403a1f3cfce084206b7b8285d6
  • Pointer size: 131 Bytes
  • Size of remote file: 272 kB
results/train.json CHANGED
@@ -1,19 +1,19 @@
1
  {
2
  "step": 19999,
3
- "loss": 0.2043,
4
- "contrastive": 0.1145,
5
- "invariants": 0.8985,
6
- "pair_top1": 0.971,
7
- "minutes": 101.7,
8
  "data": "jgalego/knot2vec-diagrams",
9
  "steps": 20000,
10
  "batch_size": 512,
11
  "learning_rate": 0.0003,
12
  "temperature": 0.05,
13
- "aux_weight": 0.1,
14
  "dim": 256,
15
  "depth": 6,
16
  "parameters": 4924943,
17
- "runtime_s": 6106,
18
  "device": "NVIDIA A10G"
19
  }
 
1
  {
2
  "step": 19999,
3
+ "loss": 0.6226,
4
+ "contrastive": 0.1426,
5
+ "invariants": 0.48,
6
+ "pair_top1": 0.961,
7
+ "minutes": 101.8,
8
  "data": "jgalego/knot2vec-diagrams",
9
  "steps": 20000,
10
  "batch_size": 512,
11
  "learning_rate": 0.0003,
12
  "temperature": 0.05,
13
+ "aux_weight": 1.0,
14
  "dim": 256,
15
  "depth": 6,
16
  "parameters": 4924943,
17
+ "runtime_s": 6109,
18
  "device": "NVIDIA A10G"
19
  }