{"id":22481470,"url":"https://github.com/mlaugharn/nndsl","last_synced_at":"2025-09-19T10:16:04.278Z","repository":{"id":141744656,"uuid":"597622844","full_name":"mlaugharn/nndsl","owner":"mlaugharn","description":"dsl for writing neural net architectures in mermaid.js syntax","archived":false,"fork":false,"pushed_at":"2023-02-05T08:19:37.000Z","size":323,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-27T18:53:49.661Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Jupyter Notebook","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mlaugharn.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-05T05:13:39.000Z","updated_at":"2024-12-04T23:05:29.000Z","dependencies_parsed_at":null,"dependency_job_id":"c456069a-6367-4c8d-887e-0f9dc038efe7","html_url":"https://github.com/mlaugharn/nndsl","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mlaugharn/nndsl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mlaugharn%2Fnndsl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mlaugharn%2Fnndsl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mlaugharn%2Fnndsl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mlaugharn%2Fnndsl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mlaugharn","download_url":"https://codeload.github.com/mlaugharn/nndsl/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mlaugharn%2Fnndsl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275918728,"owners_count":25552460,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-19T02:00:09.700Z","response_time":108,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-06T16:13:12.747Z","updated_at":"2025-09-19T10:16:04.270Z","avatar_url":"https://github.com/mlaugharn.png","language":"Jupyter Notebook","funding_links":[],"categories":[],"sub_categories":[],"readme":"# a dsl for generating recursive neural network architectures\n\nThe syntax is quite similar to a subset of mermaid.js, with one additional constraint:\n- nodes must be of the form `\u003cvariable\u003e_\u003cn\u003e`\n\nThen you provide diagrams for the base case and the inductive step and specify how many times to apply the inductive step.\n\n## example: unet\n\n```\nunet is basically a base case + an inductive step\n\nbase case:\na1 ---f1---\u003e b1\n\ninductive step:\na1 -------f1------ \u003e b1\n\\                   ^\n \\ g1            j1/\n  \\\u003ea2 ---f2--\u003e b2/\n```\ndefine it as a pair of mermaidjs-esque-syntax diagrams:\n\n```python:\n\nbase_case = \"\"\"\n\n%% comments begin with double percents\ngraph LR\n    A_1 --\u003e F_1 %% enc conv -\u003e copy and crop\n    F_1 --\u003e B_1 %% copy and crop -\u003e dec conv\n    \n\"\"\"\n\ninductive_step = \"\"\"\n\ngraph LR\n    A_1 --\u003e G_1 %% enc conv -\u003e maxpool\n    G_1 --\u003e A_2 %% maxpool -\u003e enc conv\n    A_2 --\u003e F_2 %% enc conv -\u003e copy and crop\n    F_2 --\u003e B_2 %% copy and crop -\u003e dec conv\n    B_2 --\u003e J_1 %% dec conv -\u003e up conv\n    J_1 --\u003e B_1 %% up conv -\u003e dec conv\n    \n\"\"\"\n```\n\nTo parse + interpret this structure:\n\n```python:\nfrom interpret import DslInterpreter\nunet_4 = DslInterpreter().apply(base_case, inductive_step, times=4)\n\nG = nx.DiGraph()\nfor statement in unet_4:\n    G.add_edge(statement[0], statement[1])\nG.add_edge(\"in\", (\"A\", 1))\nG.add_edge((\"B\", 1), \"out\")\n\npos = nx.kamada_kawai_layout(G)\nnx.draw(G, pos, with_labels=True)\nlabels = nx.get_edge_attributes(G, 'label')\nnx.draw_networkx_edge_labels(G, pos, edge_labels=labels)\nplt.title(\"unet of depth 4\")\nplt.show()\n```\n\n\nwhich yields\n\n![alt](docs/unet_nx.png)\n\n\nSimple!\n\n![unet labeled](docs/unet_labeled.png)\n\n## also valid mermaidjs syntax, to generate diagrams:\n\nbase case\n```\ngraph LR\n    A_1 --\u003e F_1 %% enc conv -\u003e copy and crop\n    F_1 --\u003e B_1 %% copy and crop -\u003e dec conv\n```\n\n```mermaid\ngraph LR\n    A_1 --\u003e F_1 %% enc conv -\u003e copy and crop\n    F_1 --\u003e B_1 %% copy and crop -\u003e dec conv\n```\n\ninductive step\n```\ngraph LR\n    A_1 --\u003e G_1 %% enc conv -\u003e maxpool\n    G_1 --\u003e A_2 %% maxpool -\u003e enc conv\n    A_2 --\u003e F_2 %% enc conv -\u003e copy and crop\n    F_2 --\u003e B_2 %% copy and crop -\u003e dec conv\n    B_2 --\u003e J_1 %% dec conv -\u003e up conv\n    J_1 --\u003e B_1 %% up conv -\u003e dec conv\n```\n```mermaid\ngraph LR\n    A_1 --\u003e G_1 %% enc conv -\u003e maxpool\n    G_1 --\u003e A_2 %% maxpool -\u003e enc conv\n    A_2 --\u003e F_2 %% enc conv -\u003e copy and crop\n    F_2 --\u003e B_2 %% copy and crop -\u003e dec conv\n    B_2 --\u003e J_1 %% dec conv -\u003e up conv\n    J_1 --\u003e B_1 %% up conv -\u003e dec conv\n```\n\n---\n*this part not implemented yet*\n\nTo make the full module, you just provide 2 dicts of higher-order functions that define\n\n- constructors for the modules\n- the forward pass definitions\n\n\n### Constructors\nConstructors return a *function*, i.e. `module_n = construct(n)()`. \n\nContinuing with the unet example, the `a` modules correspond to the convolutions that multiply the channels:\n\n\n```python\n# enc conv\ndef mk_a(n):\n    base_dim = 64\n\n    def a_maker():\n        n_in = base_dim // (2**(n-1))\n        n_out = base_dim // (2**n)\n        return nn.Sequential(\n            nn.Conv2d(n_in, n_out, kernel=3, padding='same'),\n            nn.BatchNorm2d(out_channels)\n            nn.ReLU(),\n            nn.Conv2d(n_in, n_out, kernel=3, padding='same'),\n        )\n    \n    return a_maker     \n```\n\n\n```python\nconstructors = {\n    'a': mk_a,\n    'b': mk_b,\n    'f': lambda n: nn.Identity, # copy and crop is not really a module - it can be defined all in the forward pass\n    ...,\n}\n```\n\n\n### Forward pass definitions:\n\nDict of higher order functions that produce functions that\n\n1. accept kwargs of inputs\n2. return a dict of outputs\n\nA few attributes will be added to the function:\n- `.n`: the index, e.g. `a_n(3).n = 3`\n- `.self`: the constructed module\n- `.variables`: a dict for mapping variables of indexes to the actual number in this specific fwd pass (see below for more details)\n\ne.g.\n\n```python\ndef fwd_a(n): \n    def a_n(g_n):\n        h = a_n.self(g_n)\n        return {\"f_n\": h, \"g_n\": h} # routing multiple outputs\n    return a_n\n\ndef fwd_b(n):\n    def b_n(f_n, j_n): # taking multiple inputs\n        h = a_n.self(f_n + j_n)\n        return {\"j_n\": h}\n    return b_n\n\ndef fwd_f(n):\n    # copy and crop after considering padding loss\n    # these calculations effectively get precomputed!\n    size_a = 572\n    padding_loss = 4\n    for _ in range(n):\n        size_a //= 2\n        size_a -= padding_loss\n        size_b = size_a * 2\n    \n    crop_w = crop_h = size_b\n    a_center = size_a // 2\n\n    a_x0, a_x1 = a_center - crop_w // 2, a_center + crop_w // 2\n    a_y0, a_y1 = a_center - crop_h // 2, a_center + crop_h // 2\n    \n    def f_n(a_n):\n        return a_n[:, :, a_y0:a_y1, a_x0:a_x1]\n    return f_n\n```\n\n### use a custom fwd for a specific node\n\neg for the `in` and `out` nodes\n```python\n# imagine 'in' were not a python reserved keyword for this illustration..\n\n# a_1(in=...) -\u003e {\"f_n\": ..., \"g_n\": ..., }\ndef a_1(in): \n    h = a_1.self(in)\n    return {\"f_n\": h, \"g_n\": h}\n\n\n# b_1(j_n=...) -\u003e {\"out\": ...}\ndef b_1(j_n):\n    return {\"out\": b_1.self(j_n)}\n\nfwds = {\n    'a_1': lambda n: a_1 \n    'a': fwd_a, \n\n    'b_1': lambda n: b_1\n    'b': fwd_b, \n\n    'f': fwd_f,\n    ...\n}\n```\n\n---\n### Variable-indexed kwargs\n\nIf you need to distinguish multiple inputs of the same kind of var e.g. \n\n```\nc_3(c_1=..., c_2=...,)\n```\n\nthen just make sure the kwargs use different letters for the variable names after the underscore; \n\n```\nc_n(c_i=..., c_j=...)\n```\n\nThe mapping from kwarg name -\u003e variable number will be available to the function as an attribute, e.g.\n\n```python\ndef fwd_c(n):\n    def c_n(c_i, c_j):\n        print(c_n.self.variables['c_i'], c_n.self.variables['c_j'])\n    return c_n\n\n# then later\nfwds = {\n    ...,\n    'c': fwd_c,\n    ...,\n}\n\n# c_3 forward prints \"1 2\"\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmlaugharn%2Fnndsl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmlaugharn%2Fnndsl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmlaugharn%2Fnndsl/lists"}