From cd2f01269fb80fc45f9cdfbc5cb515fbe17f82b5 Mon Sep 17 00:00:00 2001 From: Thomas Rudolf Date: Fri, 14 Jan 2022 14:30:36 +0100 Subject: [PATCH 01/14] Initial commit for automated visualizations of PyTorch feed forward modules. --- .gitignore | 4 +- README.md | 10 +++ examples/FeedForward/test_torch_mlp.pdf | Bin 0 -> 26435 bytes examples/FeedForward/test_torch_mlp.tex | 94 ++++++++++++++++++++++++ pycore/__init__.pyc | Bin 106 -> 0 bytes pycore/tikzeng.py | 35 ++++----- pycore/tikzeng.pyc | Bin 7118 -> 0 bytes pycore/torchparse.py | 72 ++++++++++++++++++ pyexamples/test_simple.py | 4 +- pyexamples/test_torch_mlp.py | 45 ++++++++++++ requirements.txt | 2 + tikzmake.sh | 3 +- 12 files changed, 245 insertions(+), 24 deletions(-) create mode 100644 examples/FeedForward/test_torch_mlp.pdf create mode 100644 examples/FeedForward/test_torch_mlp.tex delete mode 100644 pycore/__init__.pyc delete mode 100644 pycore/tikzeng.pyc create mode 100644 pycore/torchparse.py create mode 100644 pyexamples/test_torch_mlp.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index 9c187545..af0f7a91 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ __pycache__ books project +*.pyc +pyexamples/*.pdf # PyInstaller # Usually these files are written by a python script from a template @@ -82,4 +84,4 @@ venv.bak/ /site # mypy -.mypy_cache/ \ No newline at end of file +.mypy_cache/ diff --git a/README.md b/README.md index bcd90af4..a399c118 100644 --- a/README.md +++ b/README.md @@ -98,4 +98,14 @@ Now, run the program as follows: bash ../tikzmake.sh my_arch +## PyTorch Support +Define a feed forward `torch.nn.Sequential` module and let it parse with `pycore.torchparse.TorchArchParser`, then generate the tex file. +Look into the example, provided in `pyexamples/test_torch_mlp.py` and run it via: + +```bash +cd pyexamples +bash ../tikzmake.sh test_torch_mlp +``` + +Have a look into `examples/FeedForward/`. diff --git a/examples/FeedForward/test_torch_mlp.pdf b/examples/FeedForward/test_torch_mlp.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0a6ba8b51907e8f8d17df307225658e062884772 GIT binary patch literal 26435 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 B^X~uv literal 0 HcmV?d00001 diff --git a/examples/FeedForward/test_torch_mlp.tex b/examples/FeedForward/test_torch_mlp.tex new file mode 100644 index 00000000..ef22f3b0 --- /dev/null +++ b/examples/FeedForward/test_torch_mlp.tex @@ -0,0 +1,94 @@ + +\documentclass[border=8pt, multi, tikz]{standalone} +\usepackage{import} +\subimport{../layers/}{init} +\usetikzlibrary{positioning} +\usetikzlibrary{3d} %for including external image + +\def\ConvColor{rgb:yellow,5;red,2.5;white,5} +\def\ConvReluColor{rgb:yellow,5;red,5;white,5} +\def\PoolColor{rgb:red,1;black,0.3} +\def\UnpoolColor{rgb:blue,2;green,1;black,0.3} +\def\FcColor{rgb:blue,5;red,2.5;white,5} +\def\FcReluColor{rgb:blue,5;red,5;white,4} +\def\SoftmaxColor{rgb:magenta,5;black,7} +\def\SumColor{rgb:blue,5;green,15} + +\newcommand{\copymidarrow}{\tikz \draw[-Stealth,line width=0.8mm,draw={rgb:blue,4;red,1;green,1;black,3}] (-0.3,0) -- ++(0.3,0);} + +\begin{document} +\begin{tikzpicture} +\tikzstyle{connection}=[ultra thick,every node/.style={sloped,allow upside down},draw=\edgecolor,opacity=0.7] +\tikzstyle{copyconnection}=[ultra thick,every node/.style={sloped,allow upside down},draw={rgb:blue,4;red,1;green,1;black,3},opacity=0.7] + +\pic[shift={(1, 0, 0)}] at (0, 0, 0) + {Box={ + name=module1, + caption=$\mathrm{{FC}}$, + xlabel={{16, }}, + zlabel=, + fill=\FcColor, + height=16, + width=1, + depth=1 + } + }; + +\pic[shift={(0.5, 0, 0)}] at (module1-east) + {Box={ + name=module2, + caption=$\varphi_\mathrm{{ReLU}}$, + xlabel={{, }}, + zlabel=, + fill=\ConvColor, + height=16, + width=0.5, + depth=1 + } + }; + +\pic[shift={(1, 0, 0)}] at (module2-east) + {Box={ + name=module3, + caption=$\mathrm{{FC}}$, + xlabel={{16, }}, + zlabel=, + fill=\FcColor, + height=16, + width=1, + depth=1 + } + }; + +\draw [connection] (module2-east) -- node {\midarrow} (module3-west); + +\pic[shift={(0.5, 0, 0)}] at (module3-east) + {Box={ + name=module4, + caption=$\varphi_\mathrm{{ReLU}}$, + xlabel={{, }}, + zlabel=, + fill=\ConvColor, + height=16, + width=0.5, + depth=1 + } + }; + +\pic[shift={(1, 0, 0)}] at (module4-east) + {Box={ + name=module5, + caption=$\mathrm{{FC}}$, + xlabel={{1, }}, + zlabel=, + fill=\FcColor, + height=1, + width=1, + depth=1 + } + }; + +\draw [connection] (module4-east) -- node {\midarrow} (module5-west); + +\end{tikzpicture} +\end{document} diff --git a/pycore/__init__.pyc b/pycore/__init__.pyc deleted file mode 100644 index e9f05b6d19cfdbf93a518bcf3c516d26dac10980..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106 zcmZSn%*&-$kr$K900oRd+5w1*S%5?e14FO|NW@PANHCxg#lk=_Jw5&8{GwF-`1s7c d%#!$cy@JXT4xqSAZhlH>PO2S9cQFt%008&L5QhK& diff --git a/pycore/tikzeng.py b/pycore/tikzeng.py index 8e988c44..1eb23452 100644 --- a/pycore/tikzeng.py +++ b/pycore/tikzeng.py @@ -4,11 +4,11 @@ def to_head( projectpath ): pathlayers = os.path.join( projectpath, 'layers/' ).replace('\\', '/') return r""" -\documentclass[border=8pt, multi, tikz]{standalone} +\documentclass[border=8pt, multi, tikz]{standalone} \usepackage{import} \subimport{"""+ pathlayers + r"""}{init} \usetikzlibrary{positioning} -\usetikzlibrary{3d} %for including external image +\usetikzlibrary{3d} %for including external image """ def to_cor(): @@ -19,7 +19,7 @@ def to_cor(): \def\UnpoolColor{rgb:blue,2;green,1;black,0.3} \def\FcColor{rgb:blue,5;red,2.5;white,5} \def\FcReluColor{rgb:blue,5;red,5;white,4} -\def\SoftmaxColor{rgb:magenta,5;black,7} +\def\SoftmaxColor{rgb:magenta,5;black,7} \def\SumColor{rgb:blue,5;green,15} """ @@ -41,15 +41,15 @@ def to_input( pathfile, to='(-3,0,0)', width=8, height=8, name="temp" ): """ # Conv -def to_Conv( name, s_filer=256, n_filer=64, offset="(0,0,0)", to="(0,0,0)", width=1, height=40, depth=40, caption=" " ): +def to_Conv( name, s_filer=256, n_filer=64, offset="(0,0,0)", to="(0,0,0)", width=1, height=40, depth=40, fill_color="\ConvColor", caption=" " ): return r""" -\pic[shift={"""+ offset +"""}] at """+ to +""" +\pic[shift={"""+ offset +"""}] at """+ to +""" {Box={ name=""" + name +""", caption="""+ caption +r""", xlabel={{"""+ str(n_filer) +""", }}, zlabel="""+ str(s_filer) +""", - fill=\ConvColor, + fill=""" + str(fill_color) +""", height="""+ str(height) +""", width="""+ str(width) +""", depth="""+ str(depth) +""" @@ -61,7 +61,7 @@ def to_Conv( name, s_filer=256, n_filer=64, offset="(0,0,0)", to="(0,0,0)", widt # Bottleneck def to_ConvConvRelu( name, s_filer=256, n_filer=(64,64), offset="(0,0,0)", to="(0,0,0)", width=(2,2), height=40, depth=40, caption=" " ): return r""" -\pic[shift={ """+ offset +""" }] at """+ to +""" +\pic[shift={ """+ offset +""" }] at """+ to +""" {RightBandedBox={ name="""+ name +""", caption="""+ caption +""", @@ -81,7 +81,7 @@ def to_ConvConvRelu( name, s_filer=256, n_filer=(64,64), offset="(0,0,0)", to="( # Pool def to_Pool(name, offset="(0,0,0)", to="(0,0,0)", width=1, height=32, depth=32, opacity=0.5, caption=" "): return r""" -\pic[shift={ """+ offset +""" }] at """+ to +""" +\pic[shift={ """+ offset +""" }] at """+ to +""" {Box={ name="""+name+""", caption="""+ caption +r""", @@ -94,10 +94,10 @@ def to_Pool(name, offset="(0,0,0)", to="(0,0,0)", width=1, height=32, depth=32, }; """ -# unpool4, +# unpool4, def to_UnPool(name, offset="(0,0,0)", to="(0,0,0)", width=1, height=32, depth=32, opacity=0.5, caption=" "): return r""" -\pic[shift={ """+ offset +""" }] at """+ to +""" +\pic[shift={ """+ offset +""" }] at """+ to +""" {Box={ name="""+ name +r""", caption="""+ caption +r""", @@ -114,7 +114,7 @@ def to_UnPool(name, offset="(0,0,0)", to="(0,0,0)", width=1, height=32, depth=32 def to_ConvRes( name, s_filer=256, n_filer=64, offset="(0,0,0)", to="(0,0,0)", width=6, height=40, depth=40, opacity=0.2, caption=" " ): return r""" -\pic[shift={ """+ offset +""" }] at """+ to +""" +\pic[shift={ """+ offset +""" }] at """+ to +""" {RightBandedBox={ name="""+ name + """, caption="""+ caption + """, @@ -134,7 +134,7 @@ def to_ConvRes( name, s_filer=256, n_filer=64, offset="(0,0,0)", to="(0,0,0)", w # ConvSoftMax def to_ConvSoftMax( name, s_filer=40, offset="(0,0,0)", to="(0,0,0)", width=1, height=40, depth=40, caption=" " ): return r""" -\pic[shift={"""+ offset +"""}] at """+ to +""" +\pic[shift={"""+ offset +"""}] at """+ to +""" {Box={ name=""" + name +""", caption="""+ caption +""", @@ -150,7 +150,7 @@ def to_ConvSoftMax( name, s_filer=40, offset="(0,0,0)", to="(0,0,0)", width=1, h # SoftMax def to_SoftMax( name, s_filer=10, offset="(0,0,0)", to="(0,0,0)", width=1.5, height=3, depth=25, opacity=0.8, caption=" " ): return r""" -\pic[shift={"""+ offset +"""}] at """+ to +""" +\pic[shift={"""+ offset +"""}] at """+ to +""" {Box={ name=""" + name +""", caption="""+ caption +""", @@ -167,7 +167,7 @@ def to_SoftMax( name, s_filer=10, offset="(0,0,0)", to="(0,0,0)", width=1.5, hei def to_Sum( name, offset="(0,0,0)", to="(0,0,0)", radius=2.5, opacity=0.6): return r""" -\pic[shift={"""+ offset +"""}] at """+ to +""" +\pic[shift={"""+ offset +"""}] at """+ to +""" {Ball={ name=""" + name +""", fill=\SumColor, @@ -188,7 +188,7 @@ def to_skip( of, to, pos=1.25): return r""" \path ("""+ of +"""-southeast) -- ("""+ of +"""-northeast) coordinate[pos="""+ str(pos) +"""] ("""+ of +"""-top) ; \path ("""+ to +"""-south) -- ("""+ to +"""-north) coordinate[pos="""+ str(pos) +"""] ("""+ to +"""-top) ; -\draw [copyconnection] ("""+of+"""-northeast) +\draw [copyconnection] ("""+of+"""-northeast) -- node {\copymidarrow}("""+of+"""-top) -- node {\copymidarrow}("""+to+"""-top) -- node {\copymidarrow} ("""+to+"""-north); @@ -202,10 +202,7 @@ def to_end(): def to_generate( arch, pathname="file.tex" ): - with open(pathname, "w") as f: + with open(pathname, "w") as f: for c in arch: print(c) f.write( c ) - - - diff --git a/pycore/tikzeng.pyc b/pycore/tikzeng.pyc deleted file mode 100644 index ed1ff6e9abfb52ced60c36ed3b58ba634cd68bb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7118 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 Q&VPt 1 else str((0, 0, 0)), + ) + arch.append(arch_layer) + + if idx > 1: + arch_layer = pnn.to_connection(f"module{idx-1}", f"module{idx}") + arch.append(arch_layer) + + if layer.class_name in {"ReLU"}: + text = TorchArchParser.text_mapping.get(layer.class_name, "\\varphi") + arch_layer = pnn.to_Conv( + name=f"module{idx}", + s_filer="", + n_filer="", + offset=str((0.5, 0, 0)), + width=0.5, + height=layer.input_size[1], + depth=layer.input_size[0], + caption=f"${text}$", + to=f"(module{idx-1}-east)" if idx > 1 else str((0, 0, 0)), + ) + arch.append(arch_layer) + + arch.append(pnn.to_end()) + + return arch diff --git a/pyexamples/test_simple.py b/pyexamples/test_simple.py index 67a1cfac..e69f4c69 100644 --- a/pyexamples/test_simple.py +++ b/pyexamples/test_simple.py @@ -11,10 +11,10 @@ to_Conv("conv1", 512, 64, offset="(0,0,0)", to="(0,0,0)", height=64, depth=64, width=2 ), to_Pool("pool1", offset="(0,0,0)", to="(conv1-east)"), to_Conv("conv2", 128, 64, offset="(1,0,0)", to="(pool1-east)", height=32, depth=32, width=2 ), - to_connection( "pool1", "conv2"), + to_connection( "pool1", "conv2"), to_Pool("pool2", offset="(0,0,0)", to="(conv2-east)", height=28, depth=28, width=1), to_SoftMax("soft1", 10 ,"(3,0,0)", "(pool1-east)", caption="SOFT" ), - to_connection("pool2", "soft1"), + to_connection("pool2", "soft1"), to_Sum("sum1", offset="(1.5,0,0)", to="(soft1-east)", radius=2.5, opacity=0.6), to_connection("soft1", "sum1"), to_end() diff --git a/pyexamples/test_torch_mlp.py b/pyexamples/test_torch_mlp.py new file mode 100644 index 00000000..0062cf06 --- /dev/null +++ b/pyexamples/test_torch_mlp.py @@ -0,0 +1,45 @@ +import sys +sys.path.append('../') + +import torch as th + +from pycore.torchparse import TorchArchParser +from pycore.tikzeng import to_generate + + +DEVICE = th.device('cuda' if th.cuda.is_available() else 'cpu') + + +class MLP(th.nn.Module): + + def __init__(self): + + super(MLP, self).__init__() + + self.net = th.nn.Sequential( + th.nn.Linear(2, 16), + th.nn.ReLU(), + th.nn.Linear(16, 16), + th.nn.ReLU(), + th.nn.Linear(16, 1) + ) + + def forward(self, x): + + x = x.view(-1, 2) + y_hat = self.net(x) + + return y_hat.view(-1, 1) + + +def main(): + + mlp = MLP() + parser = TorchArchParser(torch_module=mlp, input_size=(1,2)) + arch = parser.get_arch() + to_generate(arch, pathname="./test_torch_mlp.tex") + + +if __name__ == '__main__': + + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..54f80486 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +## The following requirements were added by pip freeze: +torchinfo[pytorch]==1.6.2 diff --git a/tikzmake.sh b/tikzmake.sh index 560ca43f..0dffa2e7 100644 --- a/tikzmake.sh +++ b/tikzmake.sh @@ -1,7 +1,6 @@ #!/bin/bash - -python $1.py +python $1.py pdflatex $1.tex rm *.aux *.log *.vscodeLog From 419439ab79ca6e5669bd2ec0b2858b3da1f4840d Mon Sep 17 00:00:00 2001 From: Thomas Rudolf Date: Fri, 14 Jan 2022 15:25:29 +0100 Subject: [PATCH 02/14] Flake8. --- README.md | 2 +- pycore/torchparse.py | 2 -- pyexamples/test_torch_mlp.py | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a399c118..122a8149 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ Now, run the program as follows: ## PyTorch Support Define a feed forward `torch.nn.Sequential` module and let it parse with `pycore.torchparse.TorchArchParser`, then generate the tex file. -Look into the example, provided in `pyexamples/test_torch_mlp.py` and run it via: +Look into the example, provided with `pyexamples/test_torch_mlp.py` and run it via: ```bash cd pyexamples diff --git a/pycore/torchparse.py b/pycore/torchparse.py index 014e9630..6fe5df00 100644 --- a/pycore/torchparse.py +++ b/pycore/torchparse.py @@ -1,5 +1,3 @@ -import sys - from torchinfo import summary import pycore.tikzeng as pnn diff --git a/pyexamples/test_torch_mlp.py b/pyexamples/test_torch_mlp.py index 0062cf06..f41e39cb 100644 --- a/pyexamples/test_torch_mlp.py +++ b/pyexamples/test_torch_mlp.py @@ -35,7 +35,7 @@ def forward(self, x): def main(): mlp = MLP() - parser = TorchArchParser(torch_module=mlp, input_size=(1,2)) + parser = TorchArchParser(torch_module=mlp, input_size=(1, 2)) arch = parser.get_arch() to_generate(arch, pathname="./test_torch_mlp.tex") From 898129f281f0b8f69699fd66cd4aa497eb0d69a9 Mon Sep 17 00:00:00 2001 From: Thomas Rudolf <62146721+git-thor@users.noreply.github.com> Date: Sun, 9 Jul 2023 17:47:26 +0200 Subject: [PATCH 03/14] Lint with ruff, black, isort. --- pycore/blocks.py | 153 ++++++++----- pycore/tikzeng.py | 431 +++++++++++++++++++++++++++-------- pycore/torchparse.py | 10 +- pyexamples/test_simple.py | 26 ++- pyexamples/test_torch_mlp.py | 21 +- pyexamples/unet.py | 151 ++++++++---- 6 files changed, 560 insertions(+), 232 deletions(-) diff --git a/pycore/blocks.py b/pycore/blocks.py index 82c2c2a8..4470e2bb 100644 --- a/pycore/blocks.py +++ b/pycore/blocks.py @@ -1,75 +1,110 @@ - from .tikzeng import * -#define new block -def block_2ConvPool( name, botton, top, s_filer=256, n_filer=64, offset="(1,0,0)", size=(32,32,3.5), opacity=0.5 ): + +# define new block +def block_2ConvPool(name, botton, top, s_filer=256, n_filer=64, offset="(1,0,0)", size=(32, 32, 3.5), opacity=0.5): return [ - to_ConvConvRelu( - name="ccr_{}".format( name ), - s_filer=str(s_filer), - n_filer=(n_filer,n_filer), - offset=offset, - to="({}-east)".format( botton ), - width=(size[2],size[2]), - height=size[0], - depth=size[1], - ), - to_Pool( - name="{}".format( top ), - offset="(0,0,0)", - to="(ccr_{}-east)".format( name ), - width=1, - height=size[0] - int(size[0]/4), - depth=size[1] - int(size[0]/4), - opacity=opacity, ), - to_connection( - "{}".format( botton ), - "ccr_{}".format( name ) - ) + to_ConvConvRelu( + name="ccr_{}".format(name), + s_filer=str(s_filer), + n_filer=(n_filer, n_filer), + offset=offset, + to="({}-east)".format(botton), + width=(size[2], size[2]), + height=size[0], + depth=size[1], + ), + to_Pool( + name="{}".format(top), + offset="(0,0,0)", + to="(ccr_{}-east)".format(name), + width=1, + height=size[0] - int(size[0] / 4), + depth=size[1] - int(size[0] / 4), + opacity=opacity, + ), + to_connection("{}".format(botton), "ccr_{}".format(name)), ] -def block_Unconv( name, botton, top, s_filer=256, n_filer=64, offset="(1,0,0)", size=(32,32,3.5), opacity=0.5 ): +def block_Unconv(name, botton, top, s_filer=256, n_filer=64, offset="(1,0,0)", size=(32, 32, 3.5), opacity=0.5): return [ - to_UnPool( name='unpool_{}'.format(name), offset=offset, to="({}-east)".format(botton), width=1, height=size[0], depth=size[1], opacity=opacity ), - to_ConvRes( name='ccr_res_{}'.format(name), offset="(0,0,0)", to="(unpool_{}-east)".format(name), s_filer=str(s_filer), n_filer=str(n_filer), width=size[2], height=size[0], depth=size[1], opacity=opacity ), - to_Conv( name='ccr_{}'.format(name), offset="(0,0,0)", to="(ccr_res_{}-east)".format(name), s_filer=str(s_filer), n_filer=str(n_filer), width=size[2], height=size[0], depth=size[1] ), - to_ConvRes( name='ccr_res_c_{}'.format(name), offset="(0,0,0)", to="(ccr_{}-east)".format(name), s_filer=str(s_filer), n_filer=str(n_filer), width=size[2], height=size[0], depth=size[1], opacity=opacity ), - to_Conv( name='{}'.format(top), offset="(0,0,0)", to="(ccr_res_c_{}-east)".format(name), s_filer=str(s_filer), n_filer=str(n_filer), width=size[2], height=size[0], depth=size[1] ), - to_connection( - "{}".format( botton ), - "unpool_{}".format( name ) - ) + to_UnPool( + name="unpool_{}".format(name), + offset=offset, + to="({}-east)".format(botton), + width=1, + height=size[0], + depth=size[1], + opacity=opacity, + ), + to_ConvRes( + name="ccr_res_{}".format(name), + offset="(0,0,0)", + to="(unpool_{}-east)".format(name), + s_filer=str(s_filer), + n_filer=str(n_filer), + width=size[2], + height=size[0], + depth=size[1], + opacity=opacity, + ), + to_Conv( + name="ccr_{}".format(name), + offset="(0,0,0)", + to="(ccr_res_{}-east)".format(name), + s_filer=str(s_filer), + n_filer=str(n_filer), + width=size[2], + height=size[0], + depth=size[1], + ), + to_ConvRes( + name="ccr_res_c_{}".format(name), + offset="(0,0,0)", + to="(ccr_{}-east)".format(name), + s_filer=str(s_filer), + n_filer=str(n_filer), + width=size[2], + height=size[0], + depth=size[1], + opacity=opacity, + ), + to_Conv( + name="{}".format(top), + offset="(0,0,0)", + to="(ccr_res_c_{}-east)".format(name), + s_filer=str(s_filer), + n_filer=str(n_filer), + width=size[2], + height=size[0], + depth=size[1], + ), + to_connection("{}".format(botton), "unpool_{}".format(name)), ] - - -def block_Res( num, name, botton, top, s_filer=256, n_filer=64, offset="(0,0,0)", size=(32,32,3.5), opacity=0.5 ): +def block_Res(num, name, botton, top, s_filer=256, n_filer=64, offset="(0,0,0)", size=(32, 32, 3.5), opacity=0.5): lys = [] - layers = [ *[ '{}_{}'.format(name,i) for i in range(num-1) ], top] - for name in layers: - ly = [ to_Conv( - name='{}'.format(name), - offset=offset, - to="({}-east)".format( botton ), - s_filer=str(s_filer), - n_filer=str(n_filer), - width=size[2], - height=size[0], - depth=size[1] + layers = [*["{}_{}".format(name, i) for i in range(num - 1)], top] + for name in layers: + ly = [ + to_Conv( + name="{}".format(name), + offset=offset, + to="({}-east)".format(botton), + s_filer=str(s_filer), + n_filer=str(n_filer), + width=size[2], + height=size[0], + depth=size[1], ), - to_connection( - "{}".format( botton ), - "{}".format( name ) - ) - ] + to_connection("{}".format(botton), "{}".format(name)), + ] botton = name - lys+=ly - + lys += ly + lys += [ - to_skip( of=layers[1], to=layers[-2], pos=1.25), + to_skip(of=layers[1], to=layers[-2], pos=1.25), ] return lys - - diff --git a/pycore/tikzeng.py b/pycore/tikzeng.py index 1eb23452..39611ecd 100644 --- a/pycore/tikzeng.py +++ b/pycore/tikzeng.py @@ -1,15 +1,20 @@ - import os -def to_head( projectpath ): - pathlayers = os.path.join( projectpath, 'layers/' ).replace('\\', '/') - return r""" + +def to_head(projectpath): + pathlayers = os.path.join(projectpath, "layers/").replace("\\", "/") + return ( + r""" \documentclass[border=8pt, multi, tikz]{standalone} \usepackage{import} -\subimport{"""+ pathlayers + r"""}{init} +\subimport{""" + + pathlayers + + r"""}{init} \usetikzlibrary{positioning} \usetikzlibrary{3d} %for including external image """ + ) + def to_cor(): return r""" @@ -23,6 +28,7 @@ def to_cor(): \def\SumColor{rgb:blue,5;green,15} """ + def to_begin(): return r""" \newcommand{\copymidarrow}{\tikz \draw[-Stealth,line width=0.8mm,draw={rgb:blue,4;red,1;green,1;black,3}] (-0.3,0) -- ++(0.3,0);} @@ -33,166 +39,399 @@ def to_begin(): \tikzstyle{copyconnection}=[ultra thick,every node/.style={sloped,allow upside down},draw={rgb:blue,4;red,1;green,1;black,3},opacity=0.7] """ + # layers definition -def to_input( pathfile, to='(-3,0,0)', width=8, height=8, name="temp" ): - return r""" -\node[canvas is zy plane at x=0] (""" + name + """) at """+ to +""" {\includegraphics[width="""+ str(width)+"cm"+""",height="""+ str(height)+"cm"+"""]{"""+ pathfile +"""}}; + +def to_input(pathfile, to="(-3,0,0)", width=8, height=8, name="temp"): + return ( + r""" +\node[canvas is zy plane at x=0] (""" + + name + + """) at """ + + to + + """ {\includegraphics[width=""" + + str(width) + + "cm" + + """,height=""" + + str(height) + + "cm" + + """]{""" + + pathfile + + """}}; """ + ) + # Conv -def to_Conv( name, s_filer=256, n_filer=64, offset="(0,0,0)", to="(0,0,0)", width=1, height=40, depth=40, fill_color="\ConvColor", caption=" " ): - return r""" -\pic[shift={"""+ offset +"""}] at """+ to +""" +def to_Conv( + name, + s_filer=256, + n_filer=64, + offset="(0,0,0)", + to="(0,0,0)", + width=1, + height=40, + depth=40, + fill_color="\ConvColor", + caption=" ", +): + return ( + r""" +\pic[shift={""" + + offset + + """}] at """ + + to + + """ {Box={ - name=""" + name +""", - caption="""+ caption +r""", - xlabel={{"""+ str(n_filer) +""", }}, - zlabel="""+ str(s_filer) +""", - fill=""" + str(fill_color) +""", - height="""+ str(height) +""", - width="""+ str(width) +""", - depth="""+ str(depth) +""" + name=""" + + name + + """, + caption=""" + + caption + + r""", + xlabel={{""" + + str(n_filer) + + """, }}, + zlabel=""" + + str(s_filer) + + """, + fill=""" + + str(fill_color) + + """, + height=""" + + str(height) + + """, + width=""" + + str(width) + + """, + depth=""" + + str(depth) + + """ } }; """ + ) + # Conv,Conv,relu # Bottleneck -def to_ConvConvRelu( name, s_filer=256, n_filer=(64,64), offset="(0,0,0)", to="(0,0,0)", width=(2,2), height=40, depth=40, caption=" " ): - return r""" -\pic[shift={ """+ offset +""" }] at """+ to +""" +def to_ConvConvRelu( + name, s_filer=256, n_filer=(64, 64), offset="(0,0,0)", to="(0,0,0)", width=(2, 2), height=40, depth=40, caption=" " +): + return ( + r""" +\pic[shift={ """ + + offset + + """ }] at """ + + to + + """ {RightBandedBox={ - name="""+ name +""", - caption="""+ caption +""", - xlabel={{ """+ str(n_filer[0]) +""", """+ str(n_filer[1]) +""" }}, - zlabel="""+ str(s_filer) +""", + name=""" + + name + + """, + caption=""" + + caption + + """, + xlabel={{ """ + + str(n_filer[0]) + + """, """ + + str(n_filer[1]) + + """ }}, + zlabel=""" + + str(s_filer) + + """, fill=\ConvColor, bandfill=\ConvReluColor, - height="""+ str(height) +""", - width={ """+ str(width[0]) +""" , """+ str(width[1]) +""" }, - depth="""+ str(depth) +""" + height=""" + + str(height) + + """, + width={ """ + + str(width[0]) + + """ , """ + + str(width[1]) + + """ }, + depth=""" + + str(depth) + + """ } }; """ - + ) # Pool def to_Pool(name, offset="(0,0,0)", to="(0,0,0)", width=1, height=32, depth=32, opacity=0.5, caption=" "): - return r""" -\pic[shift={ """+ offset +""" }] at """+ to +""" + return ( + r""" +\pic[shift={ """ + + offset + + """ }] at """ + + to + + """ {Box={ - name="""+name+""", - caption="""+ caption +r""", + name=""" + + name + + """, + caption=""" + + caption + + r""", fill=\PoolColor, - opacity="""+ str(opacity) +""", - height="""+ str(height) +""", - width="""+ str(width) +""", - depth="""+ str(depth) +""" + opacity=""" + + str(opacity) + + """, + height=""" + + str(height) + + """, + width=""" + + str(width) + + """, + depth=""" + + str(depth) + + """ } }; """ + ) + # unpool4, def to_UnPool(name, offset="(0,0,0)", to="(0,0,0)", width=1, height=32, depth=32, opacity=0.5, caption=" "): - return r""" -\pic[shift={ """+ offset +""" }] at """+ to +""" + return ( + r""" +\pic[shift={ """ + + offset + + """ }] at """ + + to + + """ {Box={ - name="""+ name +r""", - caption="""+ caption +r""", + name=""" + + name + + r""", + caption=""" + + caption + + r""", fill=\UnpoolColor, - opacity="""+ str(opacity) +""", - height="""+ str(height) +""", - width="""+ str(width) +""", - depth="""+ str(depth) +""" + opacity=""" + + str(opacity) + + """, + height=""" + + str(height) + + """, + width=""" + + str(width) + + """, + depth=""" + + str(depth) + + """ } }; """ + ) - -def to_ConvRes( name, s_filer=256, n_filer=64, offset="(0,0,0)", to="(0,0,0)", width=6, height=40, depth=40, opacity=0.2, caption=" " ): - return r""" -\pic[shift={ """+ offset +""" }] at """+ to +""" +def to_ConvRes( + name, + s_filer=256, + n_filer=64, + offset="(0,0,0)", + to="(0,0,0)", + width=6, + height=40, + depth=40, + opacity=0.2, + caption=" ", +): + return ( + r""" +\pic[shift={ """ + + offset + + """ }] at """ + + to + + """ {RightBandedBox={ - name="""+ name + """, - caption="""+ caption + """, - xlabel={{ """+ str(n_filer) + """, }}, - zlabel="""+ str(s_filer) +r""", + name=""" + + name + + """, + caption=""" + + caption + + """, + xlabel={{ """ + + str(n_filer) + + """, }}, + zlabel=""" + + str(s_filer) + + r""", fill={rgb:white,1;black,3}, bandfill={rgb:white,1;black,2}, - opacity="""+ str(opacity) +""", - height="""+ str(height) +""", - width="""+ str(width) +""", - depth="""+ str(depth) +""" + opacity=""" + + str(opacity) + + """, + height=""" + + str(height) + + """, + width=""" + + str(width) + + """, + depth=""" + + str(depth) + + """ } }; """ + ) # ConvSoftMax -def to_ConvSoftMax( name, s_filer=40, offset="(0,0,0)", to="(0,0,0)", width=1, height=40, depth=40, caption=" " ): - return r""" -\pic[shift={"""+ offset +"""}] at """+ to +""" +def to_ConvSoftMax(name, s_filer=40, offset="(0,0,0)", to="(0,0,0)", width=1, height=40, depth=40, caption=" "): + return ( + r""" +\pic[shift={""" + + offset + + """}] at """ + + to + + """ {Box={ - name=""" + name +""", - caption="""+ caption +""", - zlabel="""+ str(s_filer) +""", + name=""" + + name + + """, + caption=""" + + caption + + """, + zlabel=""" + + str(s_filer) + + """, fill=\SoftmaxColor, - height="""+ str(height) +""", - width="""+ str(width) +""", - depth="""+ str(depth) +""" + height=""" + + str(height) + + """, + width=""" + + str(width) + + """, + depth=""" + + str(depth) + + """ } }; """ + ) + # SoftMax -def to_SoftMax( name, s_filer=10, offset="(0,0,0)", to="(0,0,0)", width=1.5, height=3, depth=25, opacity=0.8, caption=" " ): - return r""" -\pic[shift={"""+ offset +"""}] at """+ to +""" +def to_SoftMax( + name, s_filer=10, offset="(0,0,0)", to="(0,0,0)", width=1.5, height=3, depth=25, opacity=0.8, caption=" " +): + return ( + r""" +\pic[shift={""" + + offset + + """}] at """ + + to + + """ {Box={ - name=""" + name +""", - caption="""+ caption +""", + name=""" + + name + + """, + caption=""" + + caption + + """, xlabel={{" ","dummy"}}, - zlabel="""+ str(s_filer) +""", + zlabel=""" + + str(s_filer) + + """, fill=\SoftmaxColor, - opacity="""+ str(opacity) +""", - height="""+ str(height) +""", - width="""+ str(width) +""", - depth="""+ str(depth) +""" + opacity=""" + + str(opacity) + + """, + height=""" + + str(height) + + """, + width=""" + + str(width) + + """, + depth=""" + + str(depth) + + """ } }; """ + ) -def to_Sum( name, offset="(0,0,0)", to="(0,0,0)", radius=2.5, opacity=0.6): - return r""" -\pic[shift={"""+ offset +"""}] at """+ to +""" + +def to_Sum(name, offset="(0,0,0)", to="(0,0,0)", radius=2.5, opacity=0.6): + return ( + r""" +\pic[shift={""" + + offset + + """}] at """ + + to + + """ {Ball={ - name=""" + name +""", + name=""" + + name + + """, fill=\SumColor, - opacity="""+ str(opacity) +""", - radius="""+ str(radius) +""", + opacity=""" + + str(opacity) + + """, + radius=""" + + str(radius) + + """, logo=$+$ } }; """ + ) -def to_connection( of, to): - return r""" -\draw [connection] ("""+of+"""-east) -- node {\midarrow} ("""+to+"""-west); +def to_connection(of, to): + return ( + r""" +\draw [connection] (""" + + of + + """-east) -- node {\midarrow} (""" + + to + + """-west); """ + ) -def to_skip( of, to, pos=1.25): - return r""" -\path ("""+ of +"""-southeast) -- ("""+ of +"""-northeast) coordinate[pos="""+ str(pos) +"""] ("""+ of +"""-top) ; -\path ("""+ to +"""-south) -- ("""+ to +"""-north) coordinate[pos="""+ str(pos) +"""] ("""+ to +"""-top) ; -\draw [copyconnection] ("""+of+"""-northeast) --- node {\copymidarrow}("""+of+"""-top) --- node {\copymidarrow}("""+to+"""-top) --- node {\copymidarrow} ("""+to+"""-north); + +def to_skip(of, to, pos=1.25): + return ( + r""" +\path (""" + + of + + """-southeast) -- (""" + + of + + """-northeast) coordinate[pos=""" + + str(pos) + + """] (""" + + of + + """-top) ; +\path (""" + + to + + """-south) -- (""" + + to + + """-north) coordinate[pos=""" + + str(pos) + + """] (""" + + to + + """-top) ; +\draw [copyconnection] (""" + + of + + """-northeast) +-- node {\copymidarrow}(""" + + of + + """-top) +-- node {\copymidarrow}(""" + + to + + """-top) +-- node {\copymidarrow} (""" + + to + + """-north); """ + ) + def to_end(): return r""" @@ -201,8 +440,8 @@ def to_end(): """ -def to_generate( arch, pathname="file.tex" ): +def to_generate(arch, pathname="file.tex"): with open(pathname, "w") as f: for c in arch: print(c) - f.write( c ) + f.write(c) diff --git a/pycore/torchparse.py b/pycore/torchparse.py index 6fe5df00..bfc70d7b 100644 --- a/pycore/torchparse.py +++ b/pycore/torchparse.py @@ -4,32 +4,24 @@ class TorchArchParser: - - text_mapping = { - "Linear": "\\mathrm{{FC}}", - "ReLU": "\\varphi_\\mathrm{{ReLU}}" - } + text_mapping = {"Linear": "\\mathrm{{FC}}", "ReLU": "\\varphi_\\mathrm{{ReLU}}"} def __init__(self, torch_module, input_size): - self.torch_module = torch_module self.summary_list = summary(self.torch_module, input_size=input_size).summary_list self.arch = self.parse(self.summary_list) def get_arch(self): - return self.arch @staticmethod def parse(summary_list): - arch = list() arch.append(pnn.to_head("..")) arch.append(pnn.to_cor()) arch.append(pnn.to_begin()) for idx, layer in enumerate(summary_list[2:], start=1): - if layer.class_name == "Linear": text = TorchArchParser.text_mapping.get(layer.class_name, "\\mathrm{{FC}}") arch_layer = pnn.to_Conv( diff --git a/pyexamples/test_simple.py b/pyexamples/test_simple.py index e69f4c69..7f9cc4e8 100644 --- a/pyexamples/test_simple.py +++ b/pyexamples/test_simple.py @@ -1,28 +1,30 @@ - import sys -sys.path.append('../') + +sys.path.append("../") from pycore.tikzeng import * # defined your arch arch = [ - to_head( '..' ), + to_head(".."), to_cor(), to_begin(), - to_Conv("conv1", 512, 64, offset="(0,0,0)", to="(0,0,0)", height=64, depth=64, width=2 ), + to_Conv("conv1", 512, 64, offset="(0,0,0)", to="(0,0,0)", height=64, depth=64, width=2), to_Pool("pool1", offset="(0,0,0)", to="(conv1-east)"), - to_Conv("conv2", 128, 64, offset="(1,0,0)", to="(pool1-east)", height=32, depth=32, width=2 ), - to_connection( "pool1", "conv2"), + to_Conv("conv2", 128, 64, offset="(1,0,0)", to="(pool1-east)", height=32, depth=32, width=2), + to_connection("pool1", "conv2"), to_Pool("pool2", offset="(0,0,0)", to="(conv2-east)", height=28, depth=28, width=1), - to_SoftMax("soft1", 10 ,"(3,0,0)", "(pool1-east)", caption="SOFT" ), + to_SoftMax("soft1", 10, "(3,0,0)", "(pool1-east)", caption="SOFT"), to_connection("pool2", "soft1"), to_Sum("sum1", offset="(1.5,0,0)", to="(soft1-east)", radius=2.5, opacity=0.6), to_connection("soft1", "sum1"), - to_end() - ] + to_end(), +] + def main(): - namefile = str(sys.argv[0]).split('.')[0] - to_generate(arch, namefile + '.tex' ) + namefile = str(sys.argv[0]).split(".")[0] + to_generate(arch, namefile + ".tex") + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/pyexamples/test_torch_mlp.py b/pyexamples/test_torch_mlp.py index f41e39cb..dacc7c40 100644 --- a/pyexamples/test_torch_mlp.py +++ b/pyexamples/test_torch_mlp.py @@ -1,31 +1,24 @@ import sys -sys.path.append('../') + +sys.path.append("../") import torch as th -from pycore.torchparse import TorchArchParser from pycore.tikzeng import to_generate +from pycore.torchparse import TorchArchParser - -DEVICE = th.device('cuda' if th.cuda.is_available() else 'cpu') +DEVICE = th.device("cuda" if th.cuda.is_available() else "cpu") class MLP(th.nn.Module): - def __init__(self): - super(MLP, self).__init__() self.net = th.nn.Sequential( - th.nn.Linear(2, 16), - th.nn.ReLU(), - th.nn.Linear(16, 16), - th.nn.ReLU(), - th.nn.Linear(16, 1) + th.nn.Linear(2, 16), th.nn.ReLU(), th.nn.Linear(16, 16), th.nn.ReLU(), th.nn.Linear(16, 1) ) def forward(self, x): - x = x.view(-1, 2) y_hat = self.net(x) @@ -33,13 +26,11 @@ def forward(self, x): def main(): - mlp = MLP() parser = TorchArchParser(torch_module=mlp, input_size=(1, 2)) arch = parser.get_arch() to_generate(arch, pathname="./test_torch_mlp.tex") -if __name__ == '__main__': - +if __name__ == "__main__": main() diff --git a/pyexamples/unet.py b/pyexamples/unet.py index 25ddd3f4..6c1ff9d2 100644 --- a/pyexamples/unet.py +++ b/pyexamples/unet.py @@ -1,52 +1,121 @@ - import sys -sys.path.append('../') + +sys.path.append("../") +from pycore.blocks import * from pycore.tikzeng import * -from pycore.blocks import * -arch = [ - to_head('..'), +arch = [ + to_head(".."), to_cor(), to_begin(), - - #input - to_input( '../examples/fcn8s/cats.jpg' ), - - #block-001 - to_ConvConvRelu( name='ccr_b1', s_filer=500, n_filer=(64,64), offset="(0,0,0)", to="(0,0,0)", width=(2,2), height=40, depth=40 ), + # input + to_input("../examples/fcn8s/cats.jpg"), + # block-001 + to_ConvConvRelu( + name="ccr_b1", s_filer=500, n_filer=(64, 64), offset="(0,0,0)", to="(0,0,0)", width=(2, 2), height=40, depth=40 + ), to_Pool(name="pool_b1", offset="(0,0,0)", to="(ccr_b1-east)", width=1, height=32, depth=32, opacity=0.5), - - *block_2ConvPool( name='b2', botton='pool_b1', top='pool_b2', s_filer=256, n_filer=128, offset="(1,0,0)", size=(32,32,3.5), opacity=0.5 ), - *block_2ConvPool( name='b3', botton='pool_b2', top='pool_b3', s_filer=128, n_filer=256, offset="(1,0,0)", size=(25,25,4.5), opacity=0.5 ), - *block_2ConvPool( name='b4', botton='pool_b3', top='pool_b4', s_filer=64, n_filer=512, offset="(1,0,0)", size=(16,16,5.5), opacity=0.5 ), - - #Bottleneck - #block-005 - to_ConvConvRelu( name='ccr_b5', s_filer=32, n_filer=(1024,1024), offset="(2,0,0)", to="(pool_b4-east)", width=(8,8), height=8, depth=8, caption="Bottleneck" ), - to_connection( "pool_b4", "ccr_b5"), - - #Decoder - *block_Unconv( name="b6", botton="ccr_b5", top='end_b6', s_filer=64, n_filer=512, offset="(2.1,0,0)", size=(16,16,5.0), opacity=0.5 ), - to_skip( of='ccr_b4', to='ccr_res_b6', pos=1.25), - *block_Unconv( name="b7", botton="end_b6", top='end_b7', s_filer=128, n_filer=256, offset="(2.1,0,0)", size=(25,25,4.5), opacity=0.5 ), - to_skip( of='ccr_b3', to='ccr_res_b7', pos=1.25), - *block_Unconv( name="b8", botton="end_b7", top='end_b8', s_filer=256, n_filer=128, offset="(2.1,0,0)", size=(32,32,3.5), opacity=0.5 ), - to_skip( of='ccr_b2', to='ccr_res_b8', pos=1.25), - - *block_Unconv( name="b9", botton="end_b8", top='end_b9', s_filer=512, n_filer=64, offset="(2.1,0,0)", size=(40,40,2.5), opacity=0.5 ), - to_skip( of='ccr_b1', to='ccr_res_b9', pos=1.25), - - to_ConvSoftMax( name="soft1", s_filer=512, offset="(0.75,0,0)", to="(end_b9-east)", width=1, height=40, depth=40, caption="SOFT" ), - to_connection( "end_b9", "soft1"), - - to_end() - ] + *block_2ConvPool( + name="b2", + botton="pool_b1", + top="pool_b2", + s_filer=256, + n_filer=128, + offset="(1,0,0)", + size=(32, 32, 3.5), + opacity=0.5, + ), + *block_2ConvPool( + name="b3", + botton="pool_b2", + top="pool_b3", + s_filer=128, + n_filer=256, + offset="(1,0,0)", + size=(25, 25, 4.5), + opacity=0.5, + ), + *block_2ConvPool( + name="b4", + botton="pool_b3", + top="pool_b4", + s_filer=64, + n_filer=512, + offset="(1,0,0)", + size=(16, 16, 5.5), + opacity=0.5, + ), + # Bottleneck + # block-005 + to_ConvConvRelu( + name="ccr_b5", + s_filer=32, + n_filer=(1024, 1024), + offset="(2,0,0)", + to="(pool_b4-east)", + width=(8, 8), + height=8, + depth=8, + caption="Bottleneck", + ), + to_connection("pool_b4", "ccr_b5"), + # Decoder + *block_Unconv( + name="b6", + botton="ccr_b5", + top="end_b6", + s_filer=64, + n_filer=512, + offset="(2.1,0,0)", + size=(16, 16, 5.0), + opacity=0.5, + ), + to_skip(of="ccr_b4", to="ccr_res_b6", pos=1.25), + *block_Unconv( + name="b7", + botton="end_b6", + top="end_b7", + s_filer=128, + n_filer=256, + offset="(2.1,0,0)", + size=(25, 25, 4.5), + opacity=0.5, + ), + to_skip(of="ccr_b3", to="ccr_res_b7", pos=1.25), + *block_Unconv( + name="b8", + botton="end_b7", + top="end_b8", + s_filer=256, + n_filer=128, + offset="(2.1,0,0)", + size=(32, 32, 3.5), + opacity=0.5, + ), + to_skip(of="ccr_b2", to="ccr_res_b8", pos=1.25), + *block_Unconv( + name="b9", + botton="end_b8", + top="end_b9", + s_filer=512, + n_filer=64, + offset="(2.1,0,0)", + size=(40, 40, 2.5), + opacity=0.5, + ), + to_skip(of="ccr_b1", to="ccr_res_b9", pos=1.25), + to_ConvSoftMax( + name="soft1", s_filer=512, offset="(0.75,0,0)", to="(end_b9-east)", width=1, height=40, depth=40, caption="SOFT" + ), + to_connection("end_b9", "soft1"), + to_end(), +] def main(): - namefile = str(sys.argv[0]).split('.')[0] - to_generate(arch, namefile + '.tex' ) + namefile = str(sys.argv[0]).split(".")[0] + to_generate(arch, namefile + ".tex") + -if __name__ == '__main__': +if __name__ == "__main__": main() - From 5c124498af70d557315d2ecffd173b430fe48abf Mon Sep 17 00:00:00 2001 From: Thomas Rudolf <62146721+git-thor@users.noreply.github.com> Date: Sun, 9 Jul 2023 17:48:53 +0200 Subject: [PATCH 04/14] Address comment https://github.com/HarisIqbal88/PlotNeuralNet/pull/126/files#r971894487 --- pycore/torchparse.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pycore/torchparse.py b/pycore/torchparse.py index bfc70d7b..b04e8741 100644 --- a/pycore/torchparse.py +++ b/pycore/torchparse.py @@ -4,7 +4,18 @@ class TorchArchParser: - text_mapping = {"Linear": "\\mathrm{{FC}}", "ReLU": "\\varphi_\\mathrm{{ReLU}}"} + """Parse a torch module to a tikz architecture diagram. + + Currently supported torch modules: + - Linear + - ReLU + + """ + + text_mapping = { + "Linear": r"\\mathrm{{FC}}", + "ReLU": r"\\varphi_\\mathrm{{ReLU}}" + } def __init__(self, torch_module, input_size): self.torch_module = torch_module From 58a67b64ad603d8422cd4d174d2185c9c7357bd1 Mon Sep 17 00:00:00 2001 From: Thomas Rudolf <62146721+git-thor@users.noreply.github.com> Date: Sun, 9 Jul 2023 17:51:45 +0200 Subject: [PATCH 05/14] Address comment https://github.com/HarisIqbal88/PlotNeuralNet/pull/126/files#r971904453 --- pycore/torchparse.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pycore/torchparse.py b/pycore/torchparse.py index b04e8741..15a8a051 100644 --- a/pycore/torchparse.py +++ b/pycore/torchparse.py @@ -1,3 +1,4 @@ +from torch import th from torchinfo import summary import pycore.tikzeng as pnn @@ -17,8 +18,12 @@ class TorchArchParser: "ReLU": r"\\varphi_\\mathrm{{ReLU}}" } - def __init__(self, torch_module, input_size): + def __init__(self, torch_module: th.nn.Module, input_size): + + assert isinstance(torch_module, th.nn.Module), "torch_module must not be an instance of torch.nn.Module." + self.torch_module = torch_module + self.summary_list = summary(self.torch_module, input_size=input_size).summary_list self.arch = self.parse(self.summary_list) From 55267458028f0612c33d6dddad923c2e449437ba Mon Sep 17 00:00:00 2001 From: Thomas Rudolf <62146721+git-thor@users.noreply.github.com> Date: Sun, 9 Jul 2023 17:57:24 +0200 Subject: [PATCH 06/14] Address comment https://github.com/HarisIqbal88/PlotNeuralNet/pull/126/files#r971898539 --- pycore/torchparse.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pycore/torchparse.py b/pycore/torchparse.py index 15a8a051..9088b3c8 100644 --- a/pycore/torchparse.py +++ b/pycore/torchparse.py @@ -58,7 +58,7 @@ def parse(summary_list): arch_layer = pnn.to_connection(f"module{idx-1}", f"module{idx}") arch.append(arch_layer) - if layer.class_name in {"ReLU"}: + elif layer.class_name in {"ReLU"}: text = TorchArchParser.text_mapping.get(layer.class_name, "\\varphi") arch_layer = pnn.to_Conv( name=f"module{idx}", @@ -73,6 +73,9 @@ def parse(summary_list): ) arch.append(arch_layer) + else: + raise NotImplementedError(f"Layer {layer.class_name} is not supported, yet.") + arch.append(pnn.to_end()) return arch From ec2d32f4bba991d8687a52f6f0c01d8c80860c6e Mon Sep 17 00:00:00 2001 From: Thomas Rudolf <62146721+git-thor@users.noreply.github.com> Date: Sun, 9 Jul 2023 17:58:56 +0200 Subject: [PATCH 07/14] Address comment https://github.com/HarisIqbal88/PlotNeuralNet/pull/126/files#r971885804 --- pyexamples/test_torch_mlp.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyexamples/test_torch_mlp.py b/pyexamples/test_torch_mlp.py index dacc7c40..f1fbfa80 100644 --- a/pyexamples/test_torch_mlp.py +++ b/pyexamples/test_torch_mlp.py @@ -7,8 +7,6 @@ from pycore.tikzeng import to_generate from pycore.torchparse import TorchArchParser -DEVICE = th.device("cuda" if th.cuda.is_available() else "cpu") - class MLP(th.nn.Module): def __init__(self): From f74311e75a33b143b82191f27af7a27cc2259a39 Mon Sep 17 00:00:00 2001 From: Thomas Rudolf <62146721+git-thor@users.noreply.github.com> Date: Sun, 9 Jul 2023 18:04:18 +0200 Subject: [PATCH 08/14] Extend git ignore filter. Remove IDE stuff, this is developer preferred stuff. --- .gitignore | 9 ++++ .idea/misc.xml | 4 -- .idea/modules.xml | 8 --- .idea/plotneuralnet.iml | 12 ----- .idea/vcs.xml | 6 --- .idea/workspace.xml | 110 ---------------------------------------- 6 files changed, 9 insertions(+), 140 deletions(-) delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/plotneuralnet.iml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/workspace.xml diff --git a/.gitignore b/.gitignore index af0f7a91..2eb95320 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,22 @@ +# LaTeX *.aux *.log *.gz +# Python __pycache__ books project *.pyc pyexamples/*.pdf +# Python tools +.ruff_cache/ + +# IDEs +.idea/ +.vscode/ + # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 12f37ed3..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index dfea4263..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/plotneuralnet.iml b/.idea/plotneuralnet.iml deleted file mode 100644 index 6f63a63c..00000000 --- a/.idea/plotneuralnet.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 9510ddec..00000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -