{"id":19876688,"url":"https://github.com/dirktoewe/tf2x","last_synced_at":"2025-10-30T04:42:02.729Z","repository":{"id":176082591,"uuid":"111700959","full_name":"DirkToewe/tf2x","owner":"DirkToewe","description":"A Python library for Traversing the Tensorflow Computation Graphs and Converting them to other Formats.","archived":false,"fork":false,"pushed_at":"2018-12-02T11:37:38.000Z","size":994,"stargazers_count":4,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-14T08:13:41.723Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DirkToewe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2017-11-22T15:25:57.000Z","updated_at":"2021-03-12T06:16:23.000Z","dependencies_parsed_at":null,"dependency_job_id":"5f1250da-9892-4500-b63b-8f34367eed2f","html_url":"https://github.com/DirkToewe/tf2x","commit_stats":null,"previous_names":["dirktoewe/tf2x"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/DirkToewe/tf2x","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DirkToewe%2Ftf2x","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DirkToewe%2Ftf2x/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DirkToewe%2Ftf2x/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DirkToewe%2Ftf2x/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DirkToewe","download_url":"https://codeload.github.com/DirkToewe/tf2x/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DirkToewe%2Ftf2x/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266592180,"owners_count":23953109,"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-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"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-11-12T16:33:57.949Z","updated_at":"2025-10-30T04:41:57.694Z","avatar_url":"https://github.com/DirkToewe.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"TF2X is a self-educational project, exploring the computation graph\nand the the underlying computational model of Tensorflow. As the name\nsuggests, the idea was to traverse the computation graph and convert it\nto other representations.\n\nTF2JS\n-----\nOne rather straight forward idea was to convert to the computation\ngraph into executable code, e.g. JavaScript (in my defense I initiated\nthis project shortly *before* Tensorflow.JS was announced). TF2X offers\nthe `tf2js` method, that converts a Tensorflow graph to JavaScript code.\nAs of yet only a small subset of operations is supported but that subset\ncan be extended fairly easily.\n\nTF2X contains a small example of an 99.3% MNIST classifier that is trained in Python\nand then converted to JS code, the result of which can be found\n[here](https://dirktoewe.github.io/tf2x/mnist_gui.html) and the source code\n[here](https://github.com/DirkToewe/tf2x/tree/master/src/test/python/test/tf2x/tf2js_experiments).\n\nThe `tf2js` method takes a TF graph and converts it to a string containing\na JS class definition. Said class can be instantiated and then executed to\ncompute the individual graph nodes. The following example demonstates the\nconversion from TF graph to JS and how to execute the result in NodeJS:\n\n```python\nimport numpy as np, os, subprocess, tensorflow as tf, tf2x\nfrom pkg_resources import resource_string\nfrom tempfile import mkdtemp\nfrom tf2x import nd, tensor2js\n\nndjs_code = resource_string(tf2x.__name__, 'nd.js').decode('utf-8')\n\njs_code_template = '''\n{ND}\n{Model}\nconst model = new MyModel()\nconst a_data = {a_data}\nlet output = model({{ 'a:0': a_data }})\nconsole.log('\\\\nOutput JS:')\nconsole.log( output.toString() )\n'''\n\ntf_a = tf.placeholder(name='a', shape=[3], dtype=tf.float32)\ntf_b = tf.constant(10, name='b', dtype=tf.float32)\ntf_c = tf_a + tf_b\n\nwith tf.Session() as sess:\n  tf_out = sess.run( tf_c, feed_dict={ tf_a: [1,2,3] } )\n  print('Output TF:')\n  print( np.array2string( tf_out, separator=', ', max_line_width=256 ) )\n\n  js_code = js_code_template.format(\n    ND = ndjs_code,\n    Model = tensor2js(tf_c, sess=sess, model_name='MyModel'),\n    a_data = nd.arrayB64( np.array([1,2,3], dtype=np.float32) )\n  )\n\njs_dir = mkdtemp()\njs_file = os.path.join(js_dir, 'main.js')\n\nwith open(js_file, 'w') as fout:\n  fout.write(js_code)\n\nproc = subprocess.Popen(['node', 'main.js'], stderr=subprocess.STDOUT, cwd=js_dir)\nproc.wait()\n```\n\nTF2Dot \u0026 TF2GraphML\n-------------------\nOne of the main purposes of this project was to improve the understanding\nof Tensorflow's computational model. Good visualization of the graph is key\nto understanding. While the Tensorboard graph visualization gives an excellent\noverview over a large graph, it hides away quite a few details about the\nwiring of the graph on a low level. For those purposes, TF2X offers conversion\nmethod for the computation graph to both the Dot and GraphML format.\n\nOther Ideas\n-----------\nMore ideas for the conversion of the TF graph were played around with but\nnot (yet) implemented:\n\n  * TF Graph -\u003e Spreadsheet\n  * TF Graph -\u003e LaTex Math\n\nExploring the Computation Graph\n-------------------------------\nIn case the reader is interested in the TF computation graph, a few visualization\nexamples are given in the following. The first example visualizes a simple graph\nwith only a single binary operation:\n\n```python\nimport tensorflow as tf\nfrom tf2x import tf2dot\nfrom tempfile import mkdtemp\n\ntf_a = tf.constant([1,2,3], dtype=tf.float32, name='a')\ntf_b = tf.placeholder(      dtype=tf.float32, name='b')\ntf_c = tf.add(tf_a,tf_b, name='c')\n\nwith tf.Session() as sess:\n  dot = tf2dot( tf_c, sess=sess )\n\ntmpdir = mkdtemp()\ndot.format = 'png'\ndot.render(directory=tmpdir, view=True)\n```\n\n**Result:**\n\n![readme_explore1.png](https://dirktoewe.github.io/tf2x/readme_explore1.png)\n\nThe computation graph for this simple example is pretty straight forward. We have a binary\noperation `c` that has two input ports and a single output port. The output ports are what\nis referred to as `Tensor` in TF graph mode. The first output of an operation `a` is addressed\nas `a:0` the second one as `a:1`, ... Furthermore it easy to believe that for\nthe operation `c` to be computed, operations `a` and `b` have to be computed first\nas they are inputs of `c`. In other words `c` depends on `a` and `b` to be finished first.\n(Note that in this example `a` and `b` are trivial to compute but in general they could be\ncomplex subgraphs).\n\nSo far the computation graph is easy to grasp for everyone used to the Von-Neumann architecture (VNA).\nThe second example shows how the Control-/Dataflow architecture used in TF graphs differs from\nVNA:\n\n```python\nwith tf.Graph().as_default() as graph:\n  tf_a = tf.Variable([1,2,3], dtype=tf.float32, name='a')\n  tf_b = tf.assign_add(tf_a, [4,5,6], name='b')\n\n  init_vars = tf.global_variables_initializer()\n  \n  with tf.Session() as sess:\n    dot = tf2dot( graph, sess=sess )\n\n    sess.run(init_vars)\n\n    result_a = sess.run(tf_a)\n    print('A[0]:')\n    print(result_a) # output: [1,2,3]\n\n    sess.run(tf_b)\n    sess.run(tf_b)\n\n    result_a = sess.run(tf_a)\n    print('A[1]:')\n    print(result_a) # output: [9, 12, 15]\n\ntmpdir = mkdtemp()\n\ndot.format = 'png'\ndot.attr( root='b:0' )\ndot.render(directory=tmpdir, view=True)\n```\n\n**Result:**\n\n![readme_explore2.png](https://dirktoewe.github.io/tf2x/readme_explore2.png)\n\nLet's ignore the initialization block on the lower left for now. It is interesting to note\nthat the `VariableV2` operation block outputs a reference instead of a value. That makes\nperfect sense if You consider that a variable is a mutable state stored somewhere in memory\nthat is subject to change.\n\nFor someone coming from VNA it may be unintuitve why `result_a` is `[1,2,3]` instead of\n`[5,7,9]`. After all we have called `assign_add` in line 3, haven't we? Well, actually,\nwe haven't. In line 3, we merely created an operation that adds a value to `a` but we\nhaven't executed it yet. Only once we explicitly execute `b` using `sess.run`, we see\nthe variable state change. But what if we want the varible to be incremented before\nevery time we are reading it without having to call `sess.run(tf_a)` beforehand? Well,\nthat's were control dependencies come into play. In our first example the add operation\n`c` was depending on `a` and `b` to be computed first. With control dependencies we\ncan artifically create such dependencies. The following example demonstrates that:\n\n```python\nwith tf.Graph().as_default() as graph:\n  tf_a = tf.Variable([1,2,3], dtype=tf.float32, name='a')\n  tf_b = tf.assign_add(tf_a, [4,5,6], name='b')\n  with tf.control_dependencies([tf_b]):\n    tf_c = tf.identity(tf_a, name='c')\n\n  init_vars = tf.global_variables_initializer()\n\n  with tf.Session() as sess:\n    sess.run(init_vars)\n\n    dot = tf2dot( graph, sess=sess )\n\n    result_c = sess.run(tf_c)\n    print('C[1]:', result_c) # output: [5. 7. 9.]\n\n    result_c = sess.run(tf_c) # output: [ 9. 12. 15.]\n    print('C[2]:', result_c)\n\ntmpdir = mkdtemp()\n\ndot.format = 'png'\ndot.attr( root='b:0' )\ndot.render(directory=tmpdir, view=True)\n```\n\n**Result:**\n\n![readme_explore3.png](https://dirktoewe.github.io/tf2x/readme_explore3.png)\n\nNote how the variable is incremented every time we read it. The identity operation `c` is used to wait until\n`b` is finished before reading `a`. Note how the control depency is visible in the graph as a dashed line.\nWhile in VNA the order of execution is deterministic and ordered, the only execution order in the Control-/Dataflow architecture is given by control and data dependencies. Other than that the graph may be executed in any order and\neven in parallel (which makes this architecture so well suited for distributed computation). Let's now look\nat how control structures are realized in a TF graph:\n\n```python\nwith tf.Graph().as_default() as graph:\n  tf_a = tf.constant([1,2,3], dtype=tf.float32, name='a')\n  tf_b = tf.constant([4,5,6], dtype=tf.float32, name='b')\n  tf_c = tf.placeholder(shape=[], dtype=tf.bool, name='c')\n  tf_d = tf.cond(tf_c, lambda: tf_a, lambda: tf_b, name='d')\n  tf_e = tf.identity(tf_d, name='e')\n\n  with tf.Session() as sess:\n    dot = tf2dot( graph, sess=sess )\n\n    result_e = sess.run(tf_d, feed_dict={tf_c: False})\n    print('E(false):', result_e)\n\n    result_e = sess.run(tf_d, feed_dict={tf_c: True })\n    print('E(true):', result_e)\n\ntmpdir = mkdtemp()\n\ndot.format = 'png'\ndot.attr( root='c:0' )\ndot.render(directory=tmpdir, view=True)\n```\n\n**Result:**\n\n![readme_explore4.png](https://dirktoewe.github.io/tf2x/readme_explore4.png)\n\nSadly, the TF graph representation of a conditional is hard to identify a such. Let's ignore `d/Switch`,\n`d/Switch_t` and `d/Switch_f` as they are unused in this case. The two new important operation types\nin this graph are `Switch` and `Merge`. A `Switch` operation has two inputs and outputs. If the second\ninput (in1) value of a switch is `false` the first input value (in0) is forwarded to and emitted from\nthe first output port (out0). If the second input value of a switch is `false` the first input value\nis forwarded to and emitted from the second output port (out1). The `Merge` operation takes inputs\nfrom two ports (in0, in1) and forwards them to and emits them from a single output (out0).\n\nUnfortunately, the TF graph representation of loops is even more involved:\n\n```python\ntf_loop = tf.while_loop(\n  cond = lambda i: i \u003c tf.constant(16, name='iMax'),\n  body = lambda i: i+1,\n  loop_vars = (tf.constant(0, name='i0'),)\n)\ntf_out = tf.identity(tf_loop, name='out')\n\nwith tf.Session() as sess:\n  dot = tf2dot( tf_out, sess=sess )\n\ntmpdir = mkdtemp()\n \ndot.format = 'png'\ndot.attr( root='i0' )\ndot.render(directory=tmpdir, view=True)\n```\n\n**Result:**\n\n\u003cimg src=\"https://dirktoewe.github.io/tf2x/readme_explore5.png\" name=\"readme_explore5.png\" height=\"1024\"/\u003e\n\nA loop statement is separated from the surrounding operations by `Enter` and `Exit` operations.\n`Next` operations separate the individual loop iterations from one another. Note how - once again -\na `Switch` operations is used to conditionally used to decide wether to continue iteration or to\nexit the loop. A more complete explaination of `tf.while_loop`, including its implementation,\ncan be found [here](https://github.com/tensorflow/tensorflow/issues/4762).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdirktoewe%2Ftf2x","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdirktoewe%2Ftf2x","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdirktoewe%2Ftf2x/lists"}