tramdag.spec¶
tramdag.spec ¶
The DAG specification: nodes, terms, validation and (de)serialization.
A model is one dict {node_name: NodeSpec} of ContinuousNode and
OrdinalNode. Each node declares its transformation as an additive
formula of Term subclasses, a + sum, whose first entry
is the intercept (Intercept, written or prepended as I()) followed
by shifts (LinearShift, ComplexShift, VaryingCoefficient).
The paper's symbols I, LS, CS, VC are the same classes;
SI() and CI(*parents) are the two intercept spellings with their
arity checked.
validate_and_sort checks that every parent exists and enters through
exactly one edge-owning term (VC modifiers may repeat) and gives the
topological order. spec_to_dict and spec_from_dict are the
checkpoint form. What the terms mean is docs/model.md.
The NodeSpec union follows the classes, because Python evaluates the union
at definition time. The alias block at the end holds the paper's symbols,
the notation of the docs and the spelling nearly every caller uses.
Term ¶
One additive term of a node's transformation; each kind is a subclass.
Terms add: I("a") + CS("b") is the plain list of the two terms, a
node's formula. A term is plain data (comparable, hashable,
serializable by spec_to_dict) and knows its own spec-level rules
(check, edge_parents, cells, classical). module is the
class in modules that trains it, constructed as
module(term, schema); the intercept slot adds n_params.
Subclass to add a term: set name (what the term key serializes)
and module (a ShiftModule subclass)
as class attributes, assign the options (the keyword arguments of
__init__, with their defaults) to self, and raise ValueError
in __init__ on a bad option or parent count.
A term is exactly its __dict__: the parents the base assigns and the
options each subclass assigns from its own signature. That is what makes
options, equality and serialization one line each.
Attributes:
| Name | Type | Description |
|---|---|---|
parents |
tuple[str, ...]
|
Ordered parent names the term depends on. Empty only for the bare simple
intercept |
Source code in src/tramdag/spec.py
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 | |
classical
property
¶
classical
Say whether the exact classical fit (fit_classical) handles this term.
__init_subclass__ ¶
__init_subclass__(**kwargs)
Refuse a term class without name (the wire key) or module.
Source code in src/tramdag/spec.py
447 448 449 450 451 452 | |
options ¶
options()
Give everything the term carries, by name, the parents included.
Source code in src/tramdag/spec.py
457 458 459 | |
acting_options ¶
acting_options(ordinal, schema)
Give the options that act on this term, the parents excluded.
An option acts when setting it changes the model. ordinal says
whether the node is ordinal. schema maps each node name to
"continuous" or its level count. A subclass overrides this to drop
the options that cannot act on it.
Source code in src/tramdag/spec.py
461 462 463 464 465 466 467 468 469 470 471 | |
from_serialized
classmethod
¶
from_serialized(parents, options)
Rebuild a term from its serialized parents and options.
Source code in src/tramdag/spec.py
473 474 475 476 | |
check ¶
check(name, spec)
Check the term against the spec it sits in; nothing to check here.
A built-in term's own checks need only its arguments and run in
__init__. VC overrides this: its treatment and centering rules
need the other nodes.
Source code in src/tramdag/spec.py
483 484 485 486 487 488 489 | |
cells ¶
cells()
Give the term's adjacency cells as (parent, tag, joint) triples.
The tag is the term's name. joint says whether the term is one
network over several parents; it comes from the term, not from its
parent count, because a VC has a treatment plus modifiers and is
not one network over them.
Source code in src/tramdag/spec.py
496 497 498 499 500 501 502 503 504 505 | |
__eq__ ¶
__eq__(other)
Compare the class and everything the term carries.
Source code in src/tramdag/spec.py
507 508 509 | |
__hash__ ¶
__hash__()
Hash the class and the parents (coarser than __eq__, by design).
Source code in src/tramdag/spec.py
511 512 513 | |
__repr__ ¶
__repr__()
Name everything the term carries, the parents included.
The one __repr__ of the term classes: each entry of
options reads as name=value, so a term with a
keyword-only parent (a VC treatment) needs no spelling of its own.
Source code in src/tramdag/spec.py
515 516 517 518 519 520 521 522 523 | |
__add__ ¶
__add__(other)
Start a plain term list.
Source code in src/tramdag/spec.py
525 526 527 528 529 | |
__radd__ ¶
__radd__(other)
Extend a term list from the right.
I("a") + CS("b") + LS("c") evaluates left to right: the first
+ gives a list, so the second is list + Term and lands here.
Source code in src/tramdag/spec.py
531 532 533 534 535 536 537 538 539 | |
Intercept ¶
Bases: Term
The intercept term I: the parents reshape the monotone transform.
Without parents it is the paper's simple intercept SI: one free
parameter vector, the same for every row. With parents it is the
complex intercept CI: the transform parameters become a function
of them. SI and CI are the two spellings with their
arity checked.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*parents
|
str
|
Parent names. Several parents form one joint network (an
interaction) unless |
()
|
transform
|
str | None
|
A continuous node's monotone transform: |
None
|
transform_kwargs
|
dict | None
|
The transform's keyword arguments as one mapping, for example
|
None
|
allow_interaction
|
bool
|
|
True
|
Other Parameters:
| Name | Type | Description |
|---|---|---|
units |
list[int] | tuple[int, ...]
|
Hidden layers of the term's network, for example |
activation |
str
|
Activation of the network's hidden layers, by default |
batch_norm |
bool
|
Batch-normalize the network's hidden layers, by default False. |
input_transform |
str | callable | None
|
|
Notes
The transform class takes its keywords only through transform_kwargs,
so a misspelt option is a TypeError at once. module is a function,
so staticmethod stops it from binding as a method.
Raises:
| Type | Description |
|---|---|
ValueError
|
If a parentless term carries |
Source code in src/tramdag/spec.py
542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 | |
acting_options ¶
acting_options(ordinal, schema)
Give the options that act on this intercept.
An ordinal intercept is the cutpoint vector and takes no transform. A
parentless intercept has no network, and neither has one with
units=(). allow_interaction acts with two parents or more.
Source code in src/tramdag/spec.py
636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 | |
cells ¶
cells()
Tag an intercept edge CI: a cell exists only when it has parents.
It is joint only when one network reads several parents; an additive
intercept (allow_interaction=False) has one network per parent.
Source code in src/tramdag/spec.py
659 660 661 662 663 664 665 666 | |
LinearShift ¶
Bases: Term
The linear shift LS: \(\beta x\), one interpretable raw-unit coefficient.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*parents
|
str
|
Exactly one parent name. The weight stays the interpretable
raw-unit coefficient, so an LS takes no |
()
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the parent count is not one. |
Source code in src/tramdag/spec.py
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 | |
ComplexShift ¶
Bases: Term
The complex shift CS: an additive network g(x) on the latent scale.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*parents
|
str
|
At least one parent name. Several parents feed one joint network;
|
()
|
Other Parameters:
| Name | Type | Description |
|---|---|---|
units |
list[int] | tuple[int, ...]
|
Hidden layers, for example |
activation |
str
|
Activation of the hidden layers, by default |
batch_norm |
bool
|
Batch-normalize the hidden layers, by default False. |
input_transform |
str | callable | None
|
As for |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no parent is given. |
Source code in src/tramdag/spec.py
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 | |
acting_options ¶
acting_options(ordinal, schema)
Give the options that act; input_transform needs a continuous parent.
Source code in src/tramdag/spec.py
744 745 746 747 748 749 750 751 | |
VaryingCoefficient ¶
Bases: Term
The varying-coefficient shift VC: \((\beta_0 + b_\Theta(\text{mod}))\, x_t\).
\(b_\Theta\) is a small network whose weights carry the L2 penalty;
beta0 is not penalized. The network's output is zero-initialized and
re-centered to mean zero over the training rows after the fit, so
beta0 is the main effect. Read the fitted effect out with
varying_coef. Only t
owns its edge, so the treatment leads the parents; the modifiers may also
appear in the node's other terms.
docs/varying-coefficients.md is the guide.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*modifiers
|
str
|
The effect modifiers, the covariates that enter \(b_\Theta\). Empty means a constant effect. |
()
|
t
|
str
|
The treatment (required keyword). Must be a continuous node or a
binary (2-level) ordinal node. The term is linear in |
required |
Other Parameters:
| Name | Type | Description |
|---|---|---|
penalty |
float
|
L2 weight on the \(b_\Theta\) weights, on the total-NLL scale, by default 1.0. Must be >= 0. |
propensity |
str | None
|
The training-frame column holding the out-of-fold propensities
\(P(t = 1 \mid \mathrm{pa}_t)\) per row, by default |
units |
list[int] | tuple[int, ...]
|
Hidden layers of \(b_\Theta\), by default |
activation |
str
|
Activation of \(b_\Theta\)'s hidden layers, by default |
batch_norm |
bool
|
Batch-normalize \(b_\Theta\)'s hidden layers, by default False. |
input_transform |
str | callable | None
|
As for |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/tramdag/spec.py
754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 | |
edge_parents
property
¶
edge_parents
Only the treatment owns an edge; the modifiers may repeat elsewhere.
acting_options ¶
acting_options(ordinal, schema)
Give the options that act, input_transform over a continuous modifier.
Source code in src/tramdag/spec.py
831 832 833 834 835 836 837 838 | |
from_serialized
classmethod
¶
from_serialized(parents, options)
Rebuild from the serialized parents: the treatment comes first.
Source code in src/tramdag/spec.py
840 841 842 843 | |
check ¶
check(name, spec)
Check the treatment and the centering against the spec.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the centering column is malformed or collides with a node, the treatment is a multi-level ordinal, or centering meets a continuous or an itself-centered treatment. |
Source code in src/tramdag/spec.py
845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 | |
cells ¶
cells()
Tag the treatment cell VC and the modifiers VCm; never joint.
Source code in src/tramdag/spec.py
895 896 897 898 | |
ContinuousNode ¶
Continuous variable, modelled by a monotone 1-D transform + shifts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
terms
|
Term | list[Term] | None
|
The additive formula for |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If a formula entry is not a term. |
ValueError
|
If the formula has more than one intercept term, or the intercept is not first. |
Source code in src/tramdag/spec.py
901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 | |
__repr__ ¶
__repr__()
Show the terms and the transform.
Source code in src/tramdag/spec.py
932 933 934 | |
__eq__ ¶
__eq__(other)
Compare the terms; the transform is derived from them.
Source code in src/tramdag/spec.py
936 937 938 | |
__hash__ ¶
__hash__()
Hash what __eq__ compares, so nodes work in sets and as keys.
Source code in src/tramdag/spec.py
940 941 942 | |
OrdinalNode ¶
Ordinal variable with levels ordered classes, stored 0 to levels-1.
An ordered logit models it: increasing cutpoints plus the shift terms.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
levels
|
int
|
Number of ordered classes. |
required |
terms
|
Term | list[Term] | None
|
The additive formula, as for |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
TypeError
|
If a formula entry is not a term. |
Source code in src/tramdag/spec.py
945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 | |
__repr__ ¶
__repr__()
Show the levels and the terms.
Source code in src/tramdag/spec.py
981 982 983 | |
__eq__ ¶
__eq__(other)
Compare levels and terms.
Source code in src/tramdag/spec.py
985 986 987 988 989 990 991 | |
__hash__ ¶
__hash__()
Hash what __eq__ compares, so nodes work in sets and as keys.
Source code in src/tramdag/spec.py
993 994 995 | |
SI ¶
SI(**options)
Build the simple-intercept baseline, the paper's SI: I() without parents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**options
|
As for |
{}
|
Returns:
| Type | Description |
|---|---|
Intercept
|
The parentless intercept term. |
Source code in src/tramdag/spec.py
230 231 232 233 234 235 236 237 238 239 240 241 242 243 | |
CI ¶
CI(*parents, **options)
Build the complex intercept, the paper's CI: I(*parents) with parents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*parents
|
str
|
Parent names, at least one. |
()
|
**options
|
As for |
{}
|
Returns:
| Type | Description |
|---|---|
Intercept
|
The parent-conditioned intercept term. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no parent is given. |
Source code in src/tramdag/spec.py
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 | |
node_parents ¶
node_parents(node)
Give the parent names that the terms of a node reference.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
NodeSpec
|
The node specification. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
The parent names, ordered by first appearance, without duplicates. |
Source code in src/tramdag/spec.py
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | |
validate_and_sort ¶
validate_and_sort(spec)
Validate the spec and return a topological ordering of the nodes.
Edge ownership: every parent must enter through exactly one
edge-owning term. Edge-owning are all parents of I/LS/CS terms and
the t of a VC term. VC modifiers are exempt and can repeat
across terms.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
dict[str, NodeSpec]
|
The DAG specification. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
The node names in topological order. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a parent is unknown, a parent enters through more than one edge-owning term, a VC treatment is unsupported, or the graph has a cycle. |
Source code in src/tramdag/spec.py
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 | |
spec_to_dict ¶
spec_to_dict(spec)
Give the serialized representation of a spec, for checkpoints.
A term serializes as its term name, its parents and every option it
carries, so the result describes the model in full and does not depend on
what the defaults happen to be today. A hand-written spec may still name
only the options it cares about: spec_from_dict passes them to the
term's constructor, which fills in the rest.
The result is JSON- and YAML-safe (tuple options such as units become
lists), so a spec round-trips through json/YAML as well as through
torch.save, except when a term carries a callable
input_transform, which serializes only through pickle
(torch.save) and only as a module-level function.
A custom term is written as its import path, module.ClassName, and
spec_from_dict imports it from there. The class must be defined at
module level; one defined in __main__ (a script, a notebook cell)
loads in the same process only.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
dict[str, NodeSpec]
|
The DAG specification. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
The serialized spec. |
Source code in src/tramdag/spec.py
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 | |
spec_from_dict ¶
spec_from_dict(d, *, strict=False)
Rebuild a spec from its serialized form.
Each term is rebuilt through its class, so a wrong arity fails as the
term's own ValueError and a misspelled option key as Python's own
TypeError, naming the keyword. An option the entry does not mention
takes its constructor default, unless strict is set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
d
|
dict
|
The serialized spec, as produced by |
required |
strict
|
bool
|
Refuse an entry that omits an option that shapes its term, see
|
False
|
Returns:
| Type | Description |
|---|---|
dict[str, NodeSpec]
|
The node specification, keyed by node name. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the entry names an unknown term, a term's own checks refuse it, or
|
TypeError
|
If a term does not take an option the entry names. |
Source code in src/tramdag/spec.py
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 | |