├── .flake8
├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── conftest.py
├── examples
├── Part 01 - The Basics of Private Deep Learning.ipynb
└── Part 02 - Intro to Private Training with Remote Execution.ipynb
├── pyproject.toml
├── requirements.txt
├── requirements_dev.txt
├── setup.py
└── syft_tensorflow
├── __init__.py
├── attributes
├── __init__.py
└── attributes.py
├── hook
├── __init__.py
├── hook.py
└── hook_args.py
├── keras
└── tf_keras_fun_test.py
├── serde
├── __init__.py
├── serde.py
└── serde_test.py
└── syft_types
├── __init__.py
├── keras_layer.py
├── keras_layers_test.py
├── keras_model.py
├── keras_model_test.py
├── keras_object.py
├── tensor.py
├── tensor_test.py
├── variable.py
└── variable_test.py
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | select = E901,E999,F821,F822,F823
3 | max-line-length = 100
4 | exclude = .git,.eggs,__pycache__,build,dist,venv
5 | max-complexity = 11
6 | show-source = true
7 | statistics = true
8 | count = true
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | group: travis_latest
2 | language: python
3 | cache: pip
4 | python:
5 | - 3.6
6 | matrix:
7 | allow_failures:
8 | - python: nightly
9 | - python: pypy
10 | - python: pypy3
11 | install:
12 | - sudo apt-get update
13 | - hash -r
14 | - pip3 install git+git://github.com/dropoutlabs/PySyft.git@dev
15 | - pip3 uninstall -y tf-encrypted
16 | - pip3 install -r requirements.txt >build.log
17 | - pip3 install -r requirements_dev.txt >build.log
18 | - pip3 install flake8
19 | - pip3 install flake8-comprehensions
20 | - pip3 install pep8-naming
21 | before_script:
22 | # stop the build if there are Python syntax errors or undefined
23 | # names
24 | - flake8 --config=.flake8 .
25 | # exit-zero treats all errors as warnings.
26 | # diverting from the standard 79 character line length in
27 | # accordance with this:
28 | # https://www.python.org/dev/peps/pep-0008/#a-foolish-consistency-is-the-hobgoblin-of-little-minds
29 | - flake8 . --count --exit-zero --statistics --select=E,F,W,C90
30 | script:
31 | - coverage run -m pytest
32 | - coverage report --fail-under 0 -m
33 | notifications:
34 | on_success: change
35 | # `always` will be the setting below once code changes slow down
36 | on_failure: change
37 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributors Guidelines to PySyft-TensorFlow
2 |
3 | ## Getting Started
4 |
5 | ### Slack
6 |
7 | A great first place to join the Community is the [Slack channel](https://slack.openmined.org).
8 |
9 | ### Issues
10 |
11 | The open issues, including good first issues, can be found [here](https://github.com/openmined/pysyft-tensorflow/issues). This repository works with issues the same way as the [PySyft](https://github.com/OpenMined/PySyft/blob/master/CONTRIBUTING.md#issue-allocation) repository.
12 |
13 | ## Setup
14 |
15 | ### Forking a Repository
16 |
17 | To contribute, you will need to send your pull requests via a [fork](https://help.github.com/en/articles/fork-a-repo).
18 |
19 | ### Keeping your fork up to date
20 |
21 | If you continue to contribute, you will need to keep your fork up to date.
22 | See this [guide](https://help.github.com/articles/syncing-a-fork/) for instructions
23 | detailing how to sync your fork.
24 |
25 | ### Environment
26 |
27 | We recommend creating a [Conda](https://docs.conda.io/en/latest/) environment for PySyft-TensorFlow.
28 |
29 | ```
30 | $ conda create -n pysyft-tf python=3.7
31 | $ source activate pysyft-tf
32 | ```
33 |
34 | ### Installing Dependencies
35 |
36 | After forking the repository and cloning it, you can install the required dependencies
37 |
38 | ```
39 | pip install -r requirements.txt
40 | pip install -r requirements_dev.txt
41 | ```
42 |
43 | ### Running Tests
44 |
45 | We use [pytest](https://docs.pytest.org/en/latest/) to run tests.
46 |
47 | ```
48 | $ pytest # run everything
49 | $ pytest -k "test_fn_name_here" # run a specific failing test
50 | ```
51 |
52 | ## Documentation & Codestyle
53 |
54 | This repository follows the same rules as [PySyft](https://github.com/OpenMined/PySyft/blob/master/CONTRIBUTING.md#documentation-and-codestyle).
55 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DEPRECATION NOTICE
2 |
3 | Warning, this repository will soon be deprecated in favor of [PySyft](https://github.com/OpenMined/PySyft).
4 |
5 | # PySyft-TensorFlow
6 | TensorFlow bindings for [PySyft](https://github.com/openmined/pysyft).
7 |
8 | PySyft is a Python framework for secure, private deep learning. PySyft-TensorFlow brings
9 | secure, private deep learning to [TensorFlow](https://tensorflow.org).
10 |
11 | [](https://travis-ci.org/OpenMined/PySyft-TensorFlow)
12 | [](https://openmined.slack.com/messages/team_pysyft)
13 |
14 | ## Installation
15 |
16 | PySyft-TensorFlow is available on pip
17 |
18 | ```
19 | pip install syft-tensorflow
20 | ```
21 |
22 | NOTE: We aren't yet on a proper release schedule. Until then, we recommend building the code from source. The master branch is intended to be kept in line with [this branch](https://github.com/dropoutlabs/PySyft/tree/dev) on the [DropoutLabs](https://github.com/dropoutlabs/PySyft) fork of PySyft. If you have any trouble, please open an issue or reach out on Slack via the #team_tensorflow or #team_pysyft channels.
23 |
24 | ## Usage
25 |
26 | See the [PySyft tutorials](https://github.com/OpenMined/PySyft/tree/master/examples/tutorials)
27 | if you are unfamiliar with any Syft paradigms.
28 |
29 | ```python
30 | import tensorflow as tf
31 | import syft
32 |
33 | hook = sy.TensorFlowHook(tf)
34 | # Simulates a remote worker (ie another computer)
35 | remote = sy.VirtualWorker(hook, id="remote")
36 |
37 | # Send data to the other worker
38 | x = tf.constant(5).send(remote)
39 | y = tf.constant(10).send(remote)
40 |
41 | z = x * y
42 |
43 | print(z.get())
44 | # => 50
45 | ```
46 |
47 | ## Developing PySyft-TensorFlow
48 |
49 | See [CONTRIBUTING](./CONTRIBUTING.md).
50 |
51 | ## Project Support
52 |
53 | PySyft-Tensorflow was contributed by and continues to be maintained by the team at [Dropout Labs](https://dropoutlabs.com).
54 |
55 | Please reach out to contact@dropoutlabs.com for support.
56 |
57 | [
](https://dropoutlabs.com/)
58 | |--------------------------------------------------------------|
59 |
--------------------------------------------------------------------------------
/conftest.py:
--------------------------------------------------------------------------------
1 | """Config for tests"""
2 | import pytest
3 | import tensorflow as tf
4 | import syft as sy
5 |
6 |
7 | @pytest.fixture(scope="session", autouse=True)
8 | def hook():
9 | """
10 | auto-hook tensorflow
11 | """
12 | return sy.TensorFlowHook(tf)
13 |
14 |
15 | @pytest.fixture(scope="function", autouse=True)
16 | def remote(hook): # pylint: disable=W0621
17 | """
18 | Provide a remote worker to tests
19 | """
20 | return sy.VirtualWorker(hook, id="remote")
21 |
--------------------------------------------------------------------------------
/examples/Part 01 - The Basics of Private Deep Learning.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Tutorial: The Basic Tools of Private Deep Learning -- TensorFlow edition!\n",
8 | "\n",
9 | "Welcome to PySyft's introductory tutorial for privacy preserving, decentralized deep learning. This series of notebooks is a step-by-step guide for you to get to know the new tools and techniques required for doing deep learning on secret/private data/models without centralizing them under one authority.\n",
10 | "\n",
11 | "**Scope:** Note that we'll not just be talking about how to decentralized / encrypt data, but we'll be addressing how PySyft can be used to help decentralize the entire ecosystem around data, even including the Databases where data is stored and queried, and the neural models which are used to extract information from data. As new extensions to PySyft are created, these notebooks will be extended with new tutorials to explain the new functionality.\n",
12 | "\n",
13 | "Authors:\n",
14 | "- Jason Mancuso - Twitter: [@jvmancuso](https://twitter.com/jvmancuso)\n",
15 | "\n",
16 | "## Outline:\n",
17 | "\n",
18 | "- Part 1: The Basic Tools of Private Deep Learning\n",
19 | "\n",
20 | "\n",
21 | "## Why Take This Tutorial?\n",
22 | "\n",
23 | "**1) A Competitive Career Advantage** - For the past 20 years, the digital revolution has made data more and more accessible in ever larger quantities as analog processes have become digitized. However, with new regulation such as [GDPR](https://eugdpr.org/), enterprises are under pressure to have less freedom with how they use - and more importantly how they analyze - personal information. **Bottom Line:** Data Scientists aren't going to have access to as much data with \"old school\" tools, but by learning the tools of Private Deep Learning, YOU can be ahead of this curve and have a competitive advantage in your career. \n",
24 | "\n",
25 | "**2) Entrepreneurial Opportunities** - There are a whole host of problems in society that Deep Learning can solve, but many of the most important haven't been explored because it would require access to incredibly sensitive information about people (consider using Deep Learning to help people with mental or relationship issues!). Thus, learning Private Deep Learning unlocks a whole host of new startup opportunities for you which were not previously available to others without these toolsets.\n",
26 | "\n",
27 | "**3) Social Good** - Deep Learning can be used to solve a wide variety of problems in the real world, but Deep Learning on *personal information* is Deep Learning about people, *for people*. Learning how to do Deep Learning on data you don't own represents more than a career or entrepreneurial opportunity, it is the opportunity to help solve some of the most personal and important problems in people's lives - and to do it at scale.\n",
28 | "\n",
29 | "## How do I get extra credit?\n",
30 | "\n",
31 | "- Star PySyft on GitHub! - [https://github.com/OpenMined/PySyft](https://github.com/OpenMined/PySyft)\n",
32 | "- Star PySyft-TensorFlow on GitHub! - [https://github.com/OpenMined/PySyft-TensorFlow](https://github.com/OpenMined/PySyft-TensorFlow)\n",
33 | "- Make a Youtube video teaching this notebook!\n",
34 | "\n",
35 | "\n",
36 | "... ok ... let's do this!"
37 | ]
38 | },
39 | {
40 | "cell_type": "markdown",
41 | "metadata": {},
42 | "source": [
43 | "# Part -1: Prerequisites\n",
44 | "\n",
45 | "- Know TensorFlow - if not then take a look at the [TensorFlow tutorials](https://www.tensorflow.org/tutorials/)\n",
46 | "- Read the PySyft Framework Paper https://arxiv.org/abs/1811.04017! This will give you a thorough background on how PySyft is constructed, which will help things make more sense. Note the paper has gotten slightly out of date, so refer to these tutorials or ask in Slack if something seems strange! [slack.openmined.org](http://slack.openmined.org/)"
47 | ]
48 | },
49 | {
50 | "cell_type": "markdown",
51 | "metadata": {},
52 | "source": [
53 | "# Part 0: Setup\n",
54 | "\n",
55 | "To begin, you'll need to make sure you have the right things installed. To do so, head on over to PySyft's readme and follow the setup instructions. TLDR for most folks is.\n",
56 | "\n",
57 | "- Install Python 3.6 or higher\n",
58 | "- Install TensorFlow 2.0.0 (`pip install tensorflow==2.0.0`)\n",
59 | "- TODO syft-tf install instructions\n",
60 | "\n",
61 | "If any part of this doesn't work for you (or any of the tests fail) - first check the [README](https://github.com/OpenMined/PySyft.git) for installation help and then [open a GitHub issue](https://github.com/OpenMined/PySyft-TensorFlow/issues/new) or ping the #beginner channel in slack! [slack.openmined.org](http://slack.openmined.org/)"
62 | ]
63 | },
64 | {
65 | "cell_type": "code",
66 | "execution_count": 1,
67 | "metadata": {},
68 | "outputs": [
69 | {
70 | "data": {
71 | "text/plain": [
72 | ""
73 | ]
74 | },
75 | "execution_count": 1,
76 | "metadata": {},
77 | "output_type": "execute_result"
78 | }
79 | ],
80 | "source": [
81 | "# Run this cell to see if things work\n",
82 | "import tensorflow as tf\n",
83 | "import syft as sy\n",
84 | "hook = sy.TensorFlowHook(tf)\n",
85 | "\n",
86 | "tf.constant([1, 2, 3, 4, 5.])"
87 | ]
88 | },
89 | {
90 | "cell_type": "markdown",
91 | "metadata": {},
92 | "source": [
93 | "If this cell executed, then you're off to the races! Let's do this!"
94 | ]
95 | },
96 | {
97 | "cell_type": "markdown",
98 | "metadata": {},
99 | "source": [
100 | "# Part 1: The Basic Tools of Private, Decentralized Data Science\n",
101 | "\n",
102 | "So - the first question you may be wondering is - How in the world do we train a model on data we don't have access to? \n",
103 | "\n",
104 | "Well, the answer is surprisingly simple. If you're used to working in TensorFlow, then you're used to working with tf.Tensor objects like these!"
105 | ]
106 | },
107 | {
108 | "cell_type": "code",
109 | "execution_count": 2,
110 | "metadata": {},
111 | "outputs": [
112 | {
113 | "data": {
114 | "text/plain": [
115 | ""
116 | ]
117 | },
118 | "execution_count": 2,
119 | "metadata": {},
120 | "output_type": "execute_result"
121 | }
122 | ],
123 | "source": [
124 | "x = tf.constant([1, 2, 3, 4, 5.])\n",
125 | "y = x + x\n",
126 | "y"
127 | ]
128 | },
129 | {
130 | "cell_type": "markdown",
131 | "metadata": {},
132 | "source": [
133 | "Obviously, using these super fancy (and powerful!) tensors is important, but also requires you to have the data on your local machine. This is where our journey begins. \n",
134 | "\n",
135 | "# Section 1.1 - Sending Tensors to Bob's Machine\n",
136 | "\n",
137 | "Whereas normally we would perform data science / deep learning on the machine which holds the data, now we want to perform this kind of computation on some **other** machine. More specifically, we can no longer simply assume that the data is on our local machine.\n",
138 | "\n",
139 | "Thus, instead of using TensorFlow tensors, we're now going to work with **pointers** to tensors. Let me show you what I mean. First, let's create a \"pretend\" machine owned by a \"pretend\" person - we'll call him Bob."
140 | ]
141 | },
142 | {
143 | "cell_type": "code",
144 | "execution_count": 3,
145 | "metadata": {},
146 | "outputs": [],
147 | "source": [
148 | "bob = sy.VirtualWorker(hook, id=\"bob\")"
149 | ]
150 | },
151 | {
152 | "cell_type": "markdown",
153 | "metadata": {},
154 | "source": [
155 | "Let's say Bob's machine is on another planet - perhaps on Mars! But, at the moment the machine is empty. Let's create some data so that we can send it to Bob and learn about pointers!"
156 | ]
157 | },
158 | {
159 | "cell_type": "code",
160 | "execution_count": 4,
161 | "metadata": {},
162 | "outputs": [],
163 | "source": [
164 | "x = tf.constant([1., 2., 3., 4., 5.])\n",
165 | "y = tf.constant([1., 1., 1., 1., 1.])"
166 | ]
167 | },
168 | {
169 | "cell_type": "markdown",
170 | "metadata": {},
171 | "source": [
172 | "And now - let's send our tensors to Bob!!"
173 | ]
174 | },
175 | {
176 | "cell_type": "code",
177 | "execution_count": 5,
178 | "metadata": {},
179 | "outputs": [],
180 | "source": [
181 | "x_ptr = x.send(bob)\n",
182 | "y_ptr = y.send(bob)"
183 | ]
184 | },
185 | {
186 | "cell_type": "markdown",
187 | "metadata": {},
188 | "source": [
189 | "BOOM! Now Bob has two tensors! Don't believe me? Have a look for yourself!"
190 | ]
191 | },
192 | {
193 | "cell_type": "code",
194 | "execution_count": 6,
195 | "metadata": {},
196 | "outputs": [
197 | {
198 | "data": {
199 | "text/plain": [
200 | "{31817784589: ,\n",
201 | " 32714528385: }"
202 | ]
203 | },
204 | "execution_count": 6,
205 | "metadata": {},
206 | "output_type": "execute_result"
207 | }
208 | ],
209 | "source": [
210 | "bob._objects"
211 | ]
212 | },
213 | {
214 | "cell_type": "code",
215 | "execution_count": 7,
216 | "metadata": {},
217 | "outputs": [
218 | {
219 | "data": {
220 | "text/plain": [
221 | "(Wrapper)>[PointerTensor | me:31933462833 -> bob:8206513956]"
222 | ]
223 | },
224 | "execution_count": 7,
225 | "metadata": {},
226 | "output_type": "execute_result"
227 | }
228 | ],
229 | "source": [
230 | "z_ptr = x_ptr + x_ptr\n",
231 | "z_ptr"
232 | ]
233 | },
234 | {
235 | "cell_type": "code",
236 | "execution_count": 8,
237 | "metadata": {},
238 | "outputs": [
239 | {
240 | "data": {
241 | "text/plain": [
242 | "{31817784589: ,\n",
243 | " 32714528385: ,\n",
244 | " 8206513956: }"
245 | ]
246 | },
247 | "execution_count": 8,
248 | "metadata": {},
249 | "output_type": "execute_result"
250 | }
251 | ],
252 | "source": [
253 | "bob._objects"
254 | ]
255 | },
256 | {
257 | "cell_type": "markdown",
258 | "metadata": {},
259 | "source": [
260 | "Now notice something. When we called `x.send(bob)` it returned a new object that we called `x_ptr`. This is our first *pointer* to a tensor. Pointers to tensors do NOT actually hold data themselves. Instead, they simply contain metadata about a tensor (with data) stored on another machine. The purpose of these tensors is to give us an intuitive API to tell the other machine to compute functions using this tensor. Let's take a look at the metadata that pointers contain."
261 | ]
262 | },
263 | {
264 | "cell_type": "code",
265 | "execution_count": 9,
266 | "metadata": {},
267 | "outputs": [
268 | {
269 | "data": {
270 | "text/plain": [
271 | "(Wrapper)>[PointerTensor | me:31933462833 -> bob:8206513956]"
272 | ]
273 | },
274 | "execution_count": 9,
275 | "metadata": {},
276 | "output_type": "execute_result"
277 | }
278 | ],
279 | "source": [
280 | "z_ptr"
281 | ]
282 | },
283 | {
284 | "cell_type": "markdown",
285 | "metadata": {},
286 | "source": [
287 | "Check out that metadata!\n",
288 | "\n",
289 | "There are two main attributes specific to pointers:\n",
290 | "\n",
291 | "- `x_ptr.location : bob`, the location, a reference to the location that the pointer is pointing to\n",
292 | "- `x_ptr.id_at_location : `, the id where the tensor is stored at location\n",
293 | "\n",
294 | "They are printed in the format `@`\n",
295 | "\n",
296 | "There are also other more generic attributes:\n",
297 | "- `x_ptr.id : `, the id of our pointer tensor, it was allocated randomly\n",
298 | "- `x_ptr.owner : \"me\"`, the worker which owns the pointer tensor, here it's the local worker, named \"me\"\n"
299 | ]
300 | },
301 | {
302 | "cell_type": "code",
303 | "execution_count": 10,
304 | "metadata": {},
305 | "outputs": [
306 | {
307 | "data": {
308 | "text/plain": [
309 | ""
310 | ]
311 | },
312 | "execution_count": 10,
313 | "metadata": {},
314 | "output_type": "execute_result"
315 | }
316 | ],
317 | "source": [
318 | "x_ptr.location"
319 | ]
320 | },
321 | {
322 | "cell_type": "code",
323 | "execution_count": 11,
324 | "metadata": {},
325 | "outputs": [
326 | {
327 | "data": {
328 | "text/plain": [
329 | ""
330 | ]
331 | },
332 | "execution_count": 11,
333 | "metadata": {},
334 | "output_type": "execute_result"
335 | }
336 | ],
337 | "source": [
338 | "bob"
339 | ]
340 | },
341 | {
342 | "cell_type": "code",
343 | "execution_count": 12,
344 | "metadata": {},
345 | "outputs": [
346 | {
347 | "data": {
348 | "text/plain": [
349 | "True"
350 | ]
351 | },
352 | "execution_count": 12,
353 | "metadata": {},
354 | "output_type": "execute_result"
355 | }
356 | ],
357 | "source": [
358 | "bob == x_ptr.location"
359 | ]
360 | },
361 | {
362 | "cell_type": "code",
363 | "execution_count": 13,
364 | "metadata": {},
365 | "outputs": [
366 | {
367 | "data": {
368 | "text/plain": [
369 | "31817784589"
370 | ]
371 | },
372 | "execution_count": 13,
373 | "metadata": {},
374 | "output_type": "execute_result"
375 | }
376 | ],
377 | "source": [
378 | "x_ptr.id_at_location"
379 | ]
380 | },
381 | {
382 | "cell_type": "code",
383 | "execution_count": 14,
384 | "metadata": {},
385 | "outputs": [
386 | {
387 | "data": {
388 | "text/plain": [
389 | ""
390 | ]
391 | },
392 | "execution_count": 14,
393 | "metadata": {},
394 | "output_type": "execute_result"
395 | }
396 | ],
397 | "source": [
398 | "x_ptr.owner"
399 | ]
400 | },
401 | {
402 | "cell_type": "markdown",
403 | "metadata": {},
404 | "source": [
405 | "You may wonder why the local worker which owns the pointer is also a VirtualWorker, although we didn't create it.\n",
406 | "Fun fact, just like we had a VirtualWorker object for Bob, we (by default) always have one for us as well. This worker is automatically created when we construct `hook = sy.TensorFlowHook(tf)` and so you don't usually have to create it yourself."
407 | ]
408 | },
409 | {
410 | "cell_type": "code",
411 | "execution_count": 15,
412 | "metadata": {},
413 | "outputs": [
414 | {
415 | "data": {
416 | "text/plain": [
417 | ""
418 | ]
419 | },
420 | "execution_count": 15,
421 | "metadata": {},
422 | "output_type": "execute_result"
423 | }
424 | ],
425 | "source": [
426 | "me = sy.local_worker\n",
427 | "me"
428 | ]
429 | },
430 | {
431 | "cell_type": "code",
432 | "execution_count": 16,
433 | "metadata": {},
434 | "outputs": [
435 | {
436 | "data": {
437 | "text/plain": [
438 | "True"
439 | ]
440 | },
441 | "execution_count": 16,
442 | "metadata": {},
443 | "output_type": "execute_result"
444 | }
445 | ],
446 | "source": [
447 | "me == x_ptr.owner"
448 | ]
449 | },
450 | {
451 | "cell_type": "markdown",
452 | "metadata": {},
453 | "source": [
454 | "And finally, just like we can call .send() on a tensor, we can call .get() on a pointer to a tensor to get it back!!!"
455 | ]
456 | },
457 | {
458 | "cell_type": "code",
459 | "execution_count": 17,
460 | "metadata": {},
461 | "outputs": [
462 | {
463 | "data": {
464 | "text/plain": [
465 | ""
466 | ]
467 | },
468 | "execution_count": 17,
469 | "metadata": {},
470 | "output_type": "execute_result"
471 | }
472 | ],
473 | "source": [
474 | "x_ptr.get()"
475 | ]
476 | },
477 | {
478 | "cell_type": "code",
479 | "execution_count": 18,
480 | "metadata": {},
481 | "outputs": [
482 | {
483 | "data": {
484 | "text/plain": [
485 | ""
486 | ]
487 | },
488 | "execution_count": 18,
489 | "metadata": {},
490 | "output_type": "execute_result"
491 | }
492 | ],
493 | "source": [
494 | "y_ptr.get()"
495 | ]
496 | },
497 | {
498 | "cell_type": "code",
499 | "execution_count": 19,
500 | "metadata": {},
501 | "outputs": [
502 | {
503 | "data": {
504 | "text/plain": [
505 | ""
506 | ]
507 | },
508 | "execution_count": 19,
509 | "metadata": {},
510 | "output_type": "execute_result"
511 | }
512 | ],
513 | "source": [
514 | "z_ptr.get()"
515 | ]
516 | },
517 | {
518 | "cell_type": "code",
519 | "execution_count": 20,
520 | "metadata": {},
521 | "outputs": [
522 | {
523 | "data": {
524 | "text/plain": [
525 | "{}"
526 | ]
527 | },
528 | "execution_count": 20,
529 | "metadata": {},
530 | "output_type": "execute_result"
531 | }
532 | ],
533 | "source": [
534 | "bob._objects"
535 | ]
536 | },
537 | {
538 | "cell_type": "markdown",
539 | "metadata": {},
540 | "source": [
541 | "And as you can see... Bob no longer has the tensors anymore!!! They've moved back to our machine!"
542 | ]
543 | },
544 | {
545 | "cell_type": "markdown",
546 | "metadata": {},
547 | "source": [
548 | "# Section 1.2 - Using Tensor Pointers\n",
549 | "\n",
550 | "So, sending and receiving tensors from Bob is great, but this is hardly Deep Learning! We want to be able to perform tensor _operations_ on remote tensors. Fortunately, tensor pointers make this quite easy! You can just use pointers like you would normal tensors!"
551 | ]
552 | },
553 | {
554 | "cell_type": "code",
555 | "execution_count": 21,
556 | "metadata": {},
557 | "outputs": [],
558 | "source": [
559 | "x = tf.constant([1., 2., 3., 4., 5.]).send(bob)\n",
560 | "y = tf.constant([1., 1., 1., 1., 1.]).send(bob)"
561 | ]
562 | },
563 | {
564 | "cell_type": "code",
565 | "execution_count": 22,
566 | "metadata": {},
567 | "outputs": [],
568 | "source": [
569 | "z = x + y"
570 | ]
571 | },
572 | {
573 | "cell_type": "code",
574 | "execution_count": 23,
575 | "metadata": {},
576 | "outputs": [
577 | {
578 | "data": {
579 | "text/plain": [
580 | "(Wrapper)>[PointerTensor | me:35424001991 -> bob:87627708699]"
581 | ]
582 | },
583 | "execution_count": 23,
584 | "metadata": {},
585 | "output_type": "execute_result"
586 | }
587 | ],
588 | "source": [
589 | "z"
590 | ]
591 | },
592 | {
593 | "cell_type": "markdown",
594 | "metadata": {},
595 | "source": [
596 | "And voilà! \n",
597 | "\n",
598 | "Behind the scenes, something very powerful happened. Instead of x and y computing an addition locally, a command was serialized and sent to Bob, who performed the computation, created a tensor z, and then returned the pointer to z back to us!\n",
599 | "\n",
600 | "If we call .get() on the pointer, we will then receive the result back to our machine!"
601 | ]
602 | },
603 | {
604 | "cell_type": "code",
605 | "execution_count": 24,
606 | "metadata": {},
607 | "outputs": [
608 | {
609 | "data": {
610 | "text/plain": [
611 | ""
612 | ]
613 | },
614 | "execution_count": 24,
615 | "metadata": {},
616 | "output_type": "execute_result"
617 | }
618 | ],
619 | "source": [
620 | "z.get()"
621 | ]
622 | },
623 | {
624 | "cell_type": "markdown",
625 | "metadata": {},
626 | "source": [
627 | "### TensorFlow Functions\n",
628 | "\n",
629 | "This API has been extended to all of TensorFlow's primary operations!!!"
630 | ]
631 | },
632 | {
633 | "cell_type": "code",
634 | "execution_count": 25,
635 | "metadata": {},
636 | "outputs": [
637 | {
638 | "data": {
639 | "text/plain": [
640 | "(Wrapper)>[PointerTensor | me:26214408079 -> bob:35174604077]"
641 | ]
642 | },
643 | "execution_count": 25,
644 | "metadata": {},
645 | "output_type": "execute_result"
646 | }
647 | ],
648 | "source": [
649 | "x"
650 | ]
651 | },
652 | {
653 | "cell_type": "code",
654 | "execution_count": 26,
655 | "metadata": {},
656 | "outputs": [
657 | {
658 | "data": {
659 | "text/plain": [
660 | "(Wrapper)>[PointerTensor | me:53125201835 -> bob:40770790434]"
661 | ]
662 | },
663 | "execution_count": 26,
664 | "metadata": {},
665 | "output_type": "execute_result"
666 | }
667 | ],
668 | "source": [
669 | "y"
670 | ]
671 | },
672 | {
673 | "cell_type": "code",
674 | "execution_count": 27,
675 | "metadata": {},
676 | "outputs": [],
677 | "source": [
678 | "z = tf.add(x, y)"
679 | ]
680 | },
681 | {
682 | "cell_type": "code",
683 | "execution_count": 28,
684 | "metadata": {},
685 | "outputs": [
686 | {
687 | "data": {
688 | "text/plain": [
689 | ""
690 | ]
691 | },
692 | "execution_count": 28,
693 | "metadata": {},
694 | "output_type": "execute_result"
695 | }
696 | ],
697 | "source": [
698 | "z.get()"
699 | ]
700 | },
701 | {
702 | "cell_type": "markdown",
703 | "metadata": {},
704 | "source": [
705 | "### Variables [experimental]\n",
706 | "Autodiff coming soon :)"
707 | ]
708 | },
709 | {
710 | "cell_type": "code",
711 | "execution_count": 29,
712 | "metadata": {},
713 | "outputs": [],
714 | "source": [
715 | "x = tf.Variable([1., 2., 3., 4., 5.]).send(bob)\n",
716 | "y = tf.Variable([1., 1., 1., 1., 1.]).send(bob)"
717 | ]
718 | },
719 | {
720 | "cell_type": "code",
721 | "execution_count": 30,
722 | "metadata": {},
723 | "outputs": [
724 | {
725 | "data": {
726 | "text/plain": [
727 | "(Wrapper)>[PointerTensor | me:62848670961 -> bob:16896611751]"
728 | ]
729 | },
730 | "execution_count": 30,
731 | "metadata": {},
732 | "output_type": "execute_result"
733 | }
734 | ],
735 | "source": [
736 | "coef = tf.constant(2.).send(bob)\n",
737 | "z = x + coef * y\n",
738 | "\n",
739 | "# manual differentiation\n",
740 | "dzdy = coef\n",
741 | "delta = tf.negative(dzdy)\n",
742 | "delta = tf.broadcast_to(delta, [5])\n",
743 | "y.assign_add(delta)"
744 | ]
745 | },
746 | {
747 | "cell_type": "code",
748 | "execution_count": 31,
749 | "metadata": {},
750 | "outputs": [],
751 | "source": [
752 | "z = y.get()"
753 | ]
754 | },
755 | {
756 | "cell_type": "code",
757 | "execution_count": 32,
758 | "metadata": {},
759 | "outputs": [
760 | {
761 | "name": "stdout",
762 | "output_type": "stream",
763 | "text": [
764 | "\n"
765 | ]
766 | }
767 | ],
768 | "source": [
769 | "print(z)"
770 | ]
771 | }
772 | ],
773 | "metadata": {
774 | "kernelspec": {
775 | "display_name": "Python 3",
776 | "language": "python",
777 | "name": "python3"
778 | },
779 | "language_info": {
780 | "codemirror_mode": {
781 | "name": "ipython",
782 | "version": 3
783 | },
784 | "file_extension": ".py",
785 | "mimetype": "text/x-python",
786 | "name": "python",
787 | "nbconvert_exporter": "python",
788 | "pygments_lexer": "ipython3",
789 | "version": "3.6.9"
790 | }
791 | },
792 | "nbformat": 4,
793 | "nbformat_minor": 2
794 | }
795 |
--------------------------------------------------------------------------------
/examples/Part 02 - Intro to Private Training with Remote Execution.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Part 2: Intro to Private Training with Remote Execution\n",
8 | "\n",
9 | "In the last section, we learned about PointerTensors, which create the underlying infrastructure we need for privacy preserving Deep Learning. In this section, we're going to see how to use these basic tools to train our first deep learning model using remote execution.\n",
10 | "\n",
11 | "Authors:\n",
12 | "- Yann Dupis - Twitter: [@YannDupis](https://twitter.com/YannDupis)\n",
13 | "- Andrew Trask - Twitter: [@iamtrask](https://twitter.com/iamtrask)\n",
14 | "\n",
15 | "### Why use remote execution?\n",
16 | "\n",
17 | "Let's say you are an AI startup who wants to build a deep learning model to detect [diabetic retinopathy (DR)](https://ai.googleblog.com/2016/11/deep-learning-for-detection-of-diabetic.html), which is the fastest growing cause of blindness. Before training your model, the first step would be to acquire a dataset of retinopathy images with signs of DR. One approach could be to work with a hospital and ask them to send you a copy of this dataset. However because of the sensitivity of the patients' data, the hospital might be exposed to liability risks.\n",
18 | "\n",
19 | "\n",
20 | "That's where remote execution comes into the picture. Instead of bringing training data to the model (a central server), you bring the model to the training data (wherever it may live). In this case, it would be the hospital.\n",
21 | "\n",
22 | "The idea is that this allows whoever is creating the data to own the only permanent copy, and thus maintain control over who ever has access to it. Pretty cool, eh?"
23 | ]
24 | },
25 | {
26 | "cell_type": "markdown",
27 | "metadata": {},
28 | "source": [
29 | "# Section 2.1 - Private Training on MNIST\n",
30 | "\n",
31 | "For this tutorial, we will train a model on the [MNIST dataset](http://yann.lecun.com/exdb/mnist/) to classify digits based on images.\n",
32 | "\n",
33 | "We can assume that we have a remote worker named Bob who owns the data."
34 | ]
35 | },
36 | {
37 | "cell_type": "code",
38 | "execution_count": 1,
39 | "metadata": {
40 | "collapsed": true
41 | },
42 | "outputs": [],
43 | "source": [
44 | "import tensorflow as tf\n",
45 | "import syft as sy\n",
46 | "\n",
47 | "hook = sy.TensorFlowHook(tf)\n",
48 | "bob = sy.VirtualWorker(hook, id=\"bob\")"
49 | ]
50 | },
51 | {
52 | "cell_type": "markdown",
53 | "metadata": {},
54 | "source": [
55 | "Let's download the MNIST data from `tf.keras.datasets`. Note that we are converting the data from numpy to `tf.Tensor` in order to have the PySyft functionalities."
56 | ]
57 | },
58 | {
59 | "cell_type": "code",
60 | "execution_count": 2,
61 | "metadata": {
62 | "collapsed": true
63 | },
64 | "outputs": [],
65 | "source": [
66 | "mnist = tf.keras.datasets.mnist\n",
67 | "\n",
68 | "(x_train, y_train), (x_test, y_test) = mnist.load_data()\n",
69 | "x_train, x_test = x_train / 255.0, x_test / 255.0\n",
70 | "\n",
71 | "x_train, y_train = tf.convert_to_tensor(x_train), tf.convert_to_tensor(y_train)\n",
72 | "x_test, y_test = tf.convert_to_tensor(x_test), tf.convert_to_tensor(y_test)"
73 | ]
74 | },
75 | {
76 | "cell_type": "markdown",
77 | "metadata": {},
78 | "source": [
79 | "As decribed in Part 1, we can send this data to Bob with the `send` method on the `tf.Tensor`. "
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": 3,
85 | "metadata": {
86 | "collapsed": true
87 | },
88 | "outputs": [],
89 | "source": [
90 | "x_train_ptr = x_train.send(bob)\n",
91 | "y_train_ptr = y_train.send(bob)"
92 | ]
93 | },
94 | {
95 | "cell_type": "markdown",
96 | "metadata": {},
97 | "source": [
98 | "Excellent! We have everything to start experimenting. To train our model on Bob's machine, we just have to perform the following steps:\n",
99 | "\n",
100 | "- Define a model, including optimizer and loss\n",
101 | "- Send the model to Bob\n",
102 | "- Start the training process\n",
103 | "- Get the trained model back\n",
104 | "\n",
105 | "Let's do it!"
106 | ]
107 | },
108 | {
109 | "cell_type": "code",
110 | "execution_count": 4,
111 | "metadata": {
112 | "collapsed": true
113 | },
114 | "outputs": [],
115 | "source": [
116 | "# Define the model\n",
117 | "model = tf.keras.models.Sequential([\n",
118 | " tf.keras.layers.Flatten(input_shape=(28, 28)),\n",
119 | " tf.keras.layers.Dense(128, activation='relu'),\n",
120 | " tf.keras.layers.Dropout(0.2),\n",
121 | " tf.keras.layers.Dense(10, activation='softmax')\n",
122 | "])\n",
123 | "\n",
124 | "# Compile with optimizer, loss and metrics\n",
125 | "model.compile(optimizer='adam',\n",
126 | " loss='sparse_categorical_crossentropy',\n",
127 | " metrics=['accuracy'])"
128 | ]
129 | },
130 | {
131 | "cell_type": "markdown",
132 | "metadata": {},
133 | "source": [
134 | "Once you have defined your model, you can simply send it to Bob calling the `send` method. It's the exact same process as sending a tensor."
135 | ]
136 | },
137 | {
138 | "cell_type": "code",
139 | "execution_count": null,
140 | "metadata": {
141 | "collapsed": true
142 | },
143 | "outputs": [],
144 | "source": [
145 | "model_ptr = model.send(bob)"
146 | ]
147 | },
148 | {
149 | "cell_type": "code",
150 | "execution_count": null,
151 | "metadata": {
152 | "collapsed": true
153 | },
154 | "outputs": [],
155 | "source": [
156 | "model_ptr"
157 | ]
158 | },
159 | {
160 | "cell_type": "markdown",
161 | "metadata": {},
162 | "source": [
163 | "Now, we have a pointer pointing to the model on Bob's machine. We can validate that's the case by inspecting the attribute `_objects` on the virtual worker. "
164 | ]
165 | },
166 | {
167 | "cell_type": "code",
168 | "execution_count": null,
169 | "metadata": {
170 | "collapsed": true
171 | },
172 | "outputs": [],
173 | "source": [
174 | "bob._objects[model_ptr.id_at_location]"
175 | ]
176 | },
177 | {
178 | "cell_type": "markdown",
179 | "metadata": {},
180 | "source": [
181 | "Everything is ready to start training our model on this remote dataset. You can call `fit` and pass `x_train_ptr` `y_train_ptr` which are pointing to Bob's data. Note that's the exact same interface as normal `tf.keras`."
182 | ]
183 | },
184 | {
185 | "cell_type": "code",
186 | "execution_count": null,
187 | "metadata": {
188 | "collapsed": true
189 | },
190 | "outputs": [],
191 | "source": [
192 | "model_ptr.fit(x_train_ptr, y_train_ptr, epochs=2, validation_split=0.2)"
193 | ]
194 | },
195 | {
196 | "cell_type": "markdown",
197 | "metadata": {},
198 | "source": [
199 | "Fantastic! you have trained your model acheiving an accuracy greater than 95%.\n",
200 | "\n",
201 | "You can get your trained model back by just calling `get` on it. "
202 | ]
203 | },
204 | {
205 | "cell_type": "code",
206 | "execution_count": null,
207 | "metadata": {
208 | "collapsed": true
209 | },
210 | "outputs": [],
211 | "source": [
212 | "model_gotten = model_ptr.get()\n",
213 | "\n",
214 | "model_gotten"
215 | ]
216 | },
217 | {
218 | "cell_type": "markdown",
219 | "metadata": {},
220 | "source": [
221 | "It's good practice to see if your model can generalize by assessing its accuracy on an holdout dataset. You can simply call `evaluate`."
222 | ]
223 | },
224 | {
225 | "cell_type": "code",
226 | "execution_count": null,
227 | "metadata": {
228 | "collapsed": true
229 | },
230 | "outputs": [],
231 | "source": [
232 | "model_gotten.evaluate(x_test, y_test, verbose=2)"
233 | ]
234 | },
235 | {
236 | "cell_type": "markdown",
237 | "metadata": {},
238 | "source": [
239 | "Boom! The model remotely trained on Bob's data is more than 95% accurate on this holdout dataset."
240 | ]
241 | },
242 | {
243 | "cell_type": "markdown",
244 | "metadata": {},
245 | "source": [
246 | "If your model doesn't fit into the Sequential paradigm, you can use Keras's functional API, or even subclass [tf.keras.Model](https://www.tensorflow.org/guide/keras/custom_layers_and_models#building_models) to create custom models."
247 | ]
248 | },
249 | {
250 | "cell_type": "code",
251 | "execution_count": null,
252 | "metadata": {
253 | "collapsed": true
254 | },
255 | "outputs": [],
256 | "source": [
257 | "class CustomModel(tf.keras.Model):\n",
258 | "\n",
259 | " def __init__(self, num_classes=10):\n",
260 | " super(CustomModel, self).__init__(name='custom_model')\n",
261 | " self.num_classes = num_classes\n",
262 | " \n",
263 | " self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))\n",
264 | " self.dense_1 = tf.keras.layers.Dense(128, activation='relu')\n",
265 | " self.dropout = tf.keras.layers.Dropout(0.2)\n",
266 | " self.dense_2 = tf.keras.layers.Dense(num_classes, activation='softmax')\n",
267 | "\n",
268 | " def call(self, inputs, training=False):\n",
269 | " x = self.flatten(inputs)\n",
270 | " x = self.dense_1(x)\n",
271 | " x = self.dropout(x, training=training)\n",
272 | " return self.dense_2(x)\n",
273 | " \n",
274 | "model = CustomModel(10)\n",
275 | "\n",
276 | "# need to call the model on dummy data before sending it\n",
277 | "# in order to set the input shape (required when saving to SavedModel)\n",
278 | "model.predict(tf.ones([1, 28, 28]))\n",
279 | "\n",
280 | "model.compile(optimizer='adam',\n",
281 | " loss='sparse_categorical_crossentropy',\n",
282 | " metrics=['accuracy'])\n",
283 | "\n",
284 | "model_ptr = model.send(bob)\n",
285 | "\n",
286 | "model_ptr.fit(x_train_ptr, y_train_ptr, epochs=2, validation_split=0.2)"
287 | ]
288 | },
289 | {
290 | "cell_type": "markdown",
291 | "metadata": {},
292 | "source": [
293 | "## Well Done!\n",
294 | "\n",
295 | "And voilà! We have trained a Deep Learning model on Bob's data by sending the model to him. Never in this process do we ever see or request access to the underlying training data! We preserve the privacy of Bob!!!"
296 | ]
297 | },
298 | {
299 | "cell_type": "markdown",
300 | "metadata": {},
301 | "source": [
302 | "# Congratulations!!! - Time to Join the Community!\n",
303 | "\n",
304 | "Congratulations on completing this notebook tutorial! If you enjoyed this and would like to join the movement toward privacy preserving, decentralized ownership of AI and the AI supply chain (data), you can do so in the following ways!\n",
305 | "\n",
306 | "### Star PySyft on GitHub\n",
307 | "\n",
308 | "The easiest way to help our community is just by starring the Repos! This helps raise awareness of the cool tools we're building.\n",
309 | "\n",
310 | "- Star PySyft on GitHub! - [https://github.com/OpenMined/PySyft](https://github.com/OpenMined/PySyft)\n",
311 | "- Star PySyft-TensorFlow on GitHub! - [https://github.com/OpenMined/PySyft-TensorFlow]\n",
312 | "\n",
313 | "### Join our Slack!\n",
314 | "\n",
315 | "The best way to keep up to date on the latest advancements is to join our community! You can do so by filling out the form at [http://slack.openmined.org](http://slack.openmined.org)\n",
316 | "\n",
317 | "### Join a Code Project!\n",
318 | "\n",
319 | "The best way to contribute to our community is to become a code contributor! At any time you can go to PySyft GitHub Issues page and filter for \"Projects\". This will show you all the top level Tickets giving an overview of what projects you can join! If you don't want to join a project, but you would like to do a bit of coding, you can also look for more \"one off\" mini-projects by searching for GitHub issues marked \"good first issue\".\n",
320 | "\n",
321 | "- [PySyft Projects](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3AProject)\n",
322 | "- [Good First Issue Tickets](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
323 | "\n",
324 | "### Donate\n",
325 | "\n",
326 | "If you don't have time to contribute to our codebase, but would still like to lend support, you can also become a Backer on our Open Collective. All donations go toward our web hosting and other community expenses such as hackathons and meetups!\n",
327 | "\n",
328 | "[OpenMined's Open Collective Page](https://opencollective.com/openmined)"
329 | ]
330 | }
331 | ],
332 | "metadata": {
333 | "kernelspec": {
334 | "display_name": "pysyft-tf",
335 | "language": "python",
336 | "name": "pysyft-tf"
337 | },
338 | "language_info": {
339 | "codemirror_mode": {
340 | "name": "ipython",
341 | "version": 3
342 | },
343 | "file_extension": ".py",
344 | "mimetype": "text/x-python",
345 | "name": "python",
346 | "nbconvert_exporter": "python",
347 | "pygments_lexer": "ipython3",
348 | "version": "3.7.3"
349 | }
350 | },
351 | "nbformat": 4,
352 | "nbformat_minor": 2
353 | }
354 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.black]
2 | target-version = ['py36']
3 | line-length = 100
4 | exclude = '''
5 | (
6 | \.git
7 | | \.mypy_cache
8 | | \.tox
9 | | \.venv
10 | | \.eggs\/
11 | | build
12 | | dist
13 | | venv\/
14 | )
15 | '''
16 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | tensorflow>=2.0.0-rc2
2 |
--------------------------------------------------------------------------------
/requirements_dev.txt:
--------------------------------------------------------------------------------
1 | black>=10.3b0
2 | bumpversion>=0.5.3
3 | coverage>=4.5.3
4 | ipykernel>=5.1.0
5 | pre-commit>=1.16.1
6 | pyopenssl>=19.0.0
7 | pyshark>=0.4.2.4
8 | pytest>=4.5.0
9 | setuptools>=41.0.0
10 | sphinx-markdown-builder>=0.4.1
11 | Sphinx>=2.1.1
12 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from setuptools import find_packages
4 | from setuptools import setup
5 |
6 |
7 | # Utility function to read the README file.
8 | # Used for the long_description. It's nice, because now 1) we have a top level
9 | # README file and 2) it's easier to type in the README file than to put a raw
10 | # string in below ...
11 | def read(fname):
12 | return open(os.path.join(os.path.dirname(__file__), fname)).read()
13 |
14 |
15 | requirements = read("requirements.txt").split()
16 |
17 |
18 | setup(
19 | name="syft-tensorflow",
20 | version="0.1.0",
21 | author="OpenMined",
22 | author_email="contact@openmined.org",
23 | description=("TensorFlow Bindings for PySyft"),
24 | license="Apache-2.0",
25 | keywords="deep learning artificial intelligence privacy secure " +
26 | "multi-party computation federated learning differential privacy",
27 | packages=find_packages(exclude=["docs", "examples", "dist"]),
28 | include_package_data=True,
29 | long_description=read("README.md"),
30 | long_description_content_type="text/markdown",
31 | url="https://github.com/OpenMined/PySyft-TensorFlow",
32 | install_requires=requirements,
33 | setup_requires=["pytest-runner"],
34 | tests_require=["pytest", "pytest-flake8"],
35 | classifiers=[
36 | "Programming Language :: Python :: 3",
37 | "Operating System :: OS Independent",
38 | ],
39 | )
40 |
--------------------------------------------------------------------------------
/syft_tensorflow/__init__.py:
--------------------------------------------------------------------------------
1 | import syft
2 | from syft_tensorflow.hook import TensorFlowHook
3 |
4 | setattr(syft, "TensorFlowHook", TensorFlowHook)
5 |
--------------------------------------------------------------------------------
/syft_tensorflow/attributes/__init__.py:
--------------------------------------------------------------------------------
1 | from syft_tensorflow.attributes.attributes import TensorFlowAttributes
2 |
--------------------------------------------------------------------------------
/syft_tensorflow/attributes/attributes.py:
--------------------------------------------------------------------------------
1 | import re
2 | from types import ModuleType
3 | import typing
4 |
5 | from tensorflow.python.framework.ops import EagerTensor
6 | from syft.generic.frameworks.attributes import FrameworkAttributes
7 |
8 | from syft_tensorflow.syft_types import TensorFlowTensor
9 |
10 | if typing.TYPE_CHECKING:
11 | from syft_tensorflow.hook import TensorFlowHook
12 |
13 |
14 | class TensorFlowAttributes(FrameworkAttributes):
15 | """Adds tensorflow module related custom attributes.
16 |
17 | TensorFlowAttributes is a special class where all custom attributes related
18 | to the tensorflow module can be added. Any global parameter, configuration,
19 | or reference relating to TensorFlow should be stored here instead of
20 | attaching it directly to some other part of the global namespace.
21 |
22 | The main reason we need this is because the hooking process occasionally
23 | needs to save global objects, notably including what methods to hook and
24 | what methods to NOT hook.
25 |
26 | This will hold all necessary attributes PySyft needs.
27 |
28 | Args:
29 | tensorflow: A ModuleType indicating the tensorflow module
30 | hook: A TensorFlowHook to stash
31 | """
32 |
33 | ALIAS = "tensorflow"
34 | Tensor = TensorFlowTensor
35 |
36 | def __init__(self, tensorflow: ModuleType, hook: "TensorFlowHook"):
37 | super().__init__(tensorflow, hook)
38 | # Stash the hook here for global access elsewhere
39 | self.hook = hook
40 |
41 | # List modules that we will hook
42 | self.tensorflow_modules = {
43 | "tensorflow": tensorflow,
44 | "tensorflow.keras.activations": tensorflow.keras.activations,
45 | "tensorflow.math": tensorflow.math,
46 | # "tensorflow.keras": tensorflow.keras,
47 | }
48 |
49 | # Set of all function names with module as prefix in the modules to hook
50 | self._tensorflow_modules_functions = {
51 | f"{module_name}.{func_name}"
52 | for module_name, tensorflow_module
53 | in self.tensorflow_modules.items()
54 |
55 | for func_name in dir(tensorflow_module)
56 | }
57 |
58 | # Store reference to all tf functions by string name
59 | # stored in tensorflow_modules_functions
60 | self.eval_tensorflow_modules_functions = {
61 | f"{module_name}.{func_name}": getattr(tensorflow_module, func_name)
62 | for module_name, tensorflow_module
63 | in self.tensorflow_modules.items()
64 |
65 | for func_name in dir(tensorflow_module)
66 | }
67 |
68 | # Add special functions to exclude from the hook
69 | # **in alphabetical order**
70 | # Reasons can be:
71 | # - Used for internal process like printing tensors
72 | # - Don't use tensors so are bound to have local executions
73 | # - etc
74 | # DON'T put here:
75 | # - functions like native_*
76 | # - functions that could use pointers or syft tensors
77 | self.exclude = []
78 |
79 | # SECTION: List all TensorFlow tensor methods we want to overload
80 | self.tensor_types = [tensorflow.Tensor, tensorflow.Variable]
81 |
82 | # SECTION: Build the guard, that define which
83 | # functions or methods can be safely called by
84 | # external or local workers
85 |
86 | # Add all tensor types
87 | self.guard = {
88 | "Tensor": tensorflow.Tensor,
89 | "Variable": tensorflow.Variable,
90 | "EagerTensor": EagerTensor,
91 | }
92 |
93 | # Allow the `syft.` prefix to be used
94 | keys = list(self.guard.keys())
95 | for key in keys:
96 | self.guard[f"syft.{key}"] = self.guard[key]
97 |
98 | # Concatenate TensorFlow functions and TensorFlow methods
99 | self.allowed_commands = self._tensorflow_modules_functions
100 |
101 | # The equivalent concatenation of native TensorFlow function
102 | # names and native TensorFlow method names
103 | self.native_commands = {
104 | command_name: self.get_native_framework_name(command_name)
105 | for command_name in self.allowed_commands
106 | }
107 |
108 | self.command_guard = self._command_guard
109 |
110 | self.inplace_re_pattern = re.compile("(assign)_?")
111 | self.inplace_methods = {}
112 |
113 |
114 | def is_inplace_method(self, method_name):
115 | try:
116 | return self.inplace_methods[method_name]
117 | except KeyError:
118 | match = re.match(self.inplace_re_pattern, method_name)
119 | if match is None:
120 | return False
121 | is_inplace = match.group(1)
122 | self.inplace_methods[method_name] = is_inplace
123 | return is_inplace
124 |
--------------------------------------------------------------------------------
/syft_tensorflow/hook/__init__.py:
--------------------------------------------------------------------------------
1 | import syft_tensorflow.hook.hook_args
2 | from syft_tensorflow.hook.hook import TensorFlowHook
3 |
--------------------------------------------------------------------------------
/syft_tensorflow/hook/hook.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import logging
3 | import types
4 |
5 | import tensorflow as tf
6 | from tensorflow.python.framework.ops import Tensor
7 | from tensorflow.python.ops.resource_variable_ops import ResourceVariable
8 |
9 | import syft
10 | from syft.workers.base import BaseWorker
11 | from syft.workers.virtual import VirtualWorker
12 | from syft.generic.frameworks.hook.hook import FrameworkHook
13 | from syft.generic.object import initialize_object
14 |
15 | from syft_tensorflow.attributes import TensorFlowAttributes
16 | from syft_tensorflow.syft_types import TensorFlowTensor
17 | from syft_tensorflow.syft_types import TensorFlowVariable
18 | from syft_tensorflow.syft_types import KerasLayer
19 | from syft_tensorflow.syft_types import KerasModel
20 |
21 |
22 | class TensorFlowHook(FrameworkHook):
23 | def __init__(
24 | self,
25 | tensorflow,
26 | local_worker: BaseWorker = None,
27 | is_client: bool = True
28 | ):
29 |
30 | self.tensorflow = tensorflow
31 | self.framework = self.tensorflow
32 |
33 | syft.tensorflow = TensorFlowAttributes(tf, self)
34 |
35 | syft.framework = syft.tensorflow
36 | syft.tensorflow.hook = self
37 | syft.hook = self
38 |
39 | self.local_worker = local_worker
40 |
41 | if hasattr(tensorflow, "tf_hooked"):
42 | logging.warning("TF was already hooked, skipping hooking process")
43 | self.local_worker = syft.local_worker
44 | return
45 | else:
46 | tensorflow.tf_hooked = True
47 |
48 | if self.local_worker is None:
49 | # Every TensorFlowHook instance should have a local worker which is
50 | # responsible for interfacing with other workers. The worker
51 | # interface is what allows the TensorFlow specific code in TensorFlowHook to
52 | # be agnostic to the means by which workers communicate (such as
53 | # peer-to-peer, sockets, through local ports, or all within the
54 | # same process)
55 | self.local_worker = VirtualWorker(
56 | hook=self, is_client_worker=is_client, id="me"
57 | )
58 | else:
59 | self.local_worker.hook = self
60 |
61 | self.to_auto_overload = {
62 | tf.math: ["add"],
63 | Tensor: self._which_methods_should_we_auto_overload(
64 | Tensor
65 | ),
66 |
67 | tf.Variable: self._which_methods_should_we_auto_overload(
68 | tf.Variable
69 | ),
70 |
71 | tf.keras.layers.Layer: self._which_methods_should_we_auto_overload(
72 | tf.keras.layers.Layer
73 | ),
74 |
75 | tf.keras.models.Model: self._which_methods_should_we_auto_overload(
76 | tf.keras.models.Model
77 | ),
78 |
79 | ResourceVariable: self._which_methods_should_we_auto_overload(
80 | ResourceVariable
81 | ),
82 | }
83 |
84 | self.args_hook_for_overloaded_attr = {}
85 |
86 | self._hook_native_tensor(Tensor, TensorFlowTensor)
87 | self._hook_variable(TensorFlowVariable)
88 |
89 | self._hook_keras_layers(tf.keras.layers.Layer, KerasLayer)
90 | self._hook_keras_model(tf.keras.models.Model, KerasModel)
91 |
92 | self._hook_pointer_tensor_methods(Tensor)
93 | self._hook_pointer_tensor_methods(tf.Variable)
94 | self._hook_pointer_tensor_methods(ResourceVariable)
95 |
96 | self._hook_pointer_tensor_methods(tf.math)
97 | self._hook_multi_pointer_tensor_methods(tf.math)
98 |
99 | self._hook_object_pointer_methods(tf.keras.layers.Layer)
100 | self._hook_object_pointer_methods(tf.keras.models.Model)
101 |
102 | self._hook_tensorflow_module()
103 |
104 | syft.local_worker = self.local_worker
105 | syft.hook = self
106 |
107 | # This must happen last!
108 | # See this functions documentation for more info.
109 | self._add_methods_to_eager_tensor()
110 |
111 | def _hook_native_tensor(self, tensor_type: type, syft_type: type):
112 | """Adds PySyft Tensor Functionality to the given native tensor type.
113 | Overloads the given native TensorFlow tensor to add PySyft Tensor
114 | Functionality. Overloading involves modifying the tensor type with
115 | PySyft's added functionality. You may read about what kind of
116 | modifications are made in the methods that this method calls.
117 | Args:
118 | tensor_type: The type of tensor being hooked (in this refactor
119 | this is only ever tf.Tensor, but in previous versions of
120 | PySyft this iterated over all tensor types.
121 | syft_type: The abstract type whose methods should all be added to
122 | the tensor_type class. In practice this is always TensorFlowTensor.
123 | Read more about it there.
124 | """
125 | # Reinitialize init method of TensorFlow tensor with Syft init
126 | self._add_registration_to___init__(tensor_type)
127 |
128 | # Overload TensorFlow tensor properties with Syft properties
129 | self._hook_tensor_properties(tensor_type)
130 |
131 | # Overload auto overloaded with TensorFlow methods
132 | exclude = [
133 | "__class__",
134 | "__delattr__",
135 | "__dir__",
136 | "__doc__",
137 | "__dict__",
138 | "__format__",
139 | "__getattribute__",
140 | "__hash__",
141 | "__init__",
142 | "__init_subclass__",
143 | "__weakref__",
144 | "__ne__",
145 | "__new__",
146 | "__reduce__",
147 | "__reduce_ex__",
148 | "__setattr__",
149 | "__sizeof__",
150 | "__subclasshook__",
151 | "__eq__",
152 | "__gt__",
153 | "__ge__",
154 | "__lt__",
155 | "__le__",
156 | ]
157 | self._transfer_methods_to_framework_class(tensor_type, syft_type, exclude)
158 |
159 | self._hook_native_methods(tensor_type)
160 |
161 | def _hook_keras_layers(self, layer_cls: type, from_cls: type):
162 |
163 | # Reinitialize init method of the Keras object with Syft init
164 | self._add_registration_to___init__(layer_cls)
165 |
166 | # Overload Keras object properties with Syft properties
167 | self._hook_keras_properties(layer_cls)
168 |
169 | # Overload auto overloaded with Keras methods
170 | exclude = [
171 | "__class__",
172 | "__dir__",
173 | "__doc__",
174 | "__dict__",
175 | "__format__",
176 | "__getattribute__",
177 | "__hash__",
178 | "__init__",
179 | "__init_subclass__",
180 | "__weakref__",
181 | "__new__",
182 | "__reduce__",
183 | "__reduce_ex__",
184 | "__setattr__",
185 | "__sizeof__",
186 | "__subclasshook__",
187 | ]
188 | self._transfer_methods_to_framework_class(layer_cls, from_cls, exclude)
189 |
190 | self._hook_keras_methods(layer_cls)
191 |
192 | def _hook_keras_model(self, model_cls: type, from_cls: type):
193 |
194 | # Overload the Keras object properties with Syft properties
195 | self._hook_keras_properties(model_cls)
196 |
197 | # Overload auto overloaded with Keras methods
198 | exclude = [
199 | "__class__",
200 | "__dir__",
201 | "__doc__",
202 | "__dict__",
203 | "__format__",
204 | "__getattribute__",
205 | "__hash__",
206 | "__init__",
207 | "__init_subclass__",
208 | "__weakref__",
209 | "__new__",
210 | "__reduce__",
211 | "__reduce_ex__",
212 | "__setattr__",
213 | "__sizeof__",
214 | "__subclasshook__",
215 | "__str__",
216 | "__repr__",
217 | ]
218 | self._transfer_methods_to_framework_class(model_cls, from_cls, exclude)
219 |
220 | self._hook_keras_methods(model_cls)
221 |
222 | def _hook_variable(self, syft_type: type):
223 | """Adds PySyft Tensor functionality to tf.Variable.
224 |
225 | In practice, the user is generally working with subclasses of
226 | tf.Variable, e.g. ResourceVariable, so we hook methods for those and
227 | only override the tf.Variable constructor to provide syft registration.
228 | You may read about what kind of modifications are made in the methods
229 | that this method calls.
230 |
231 | Args:
232 | syft_type: The abstract type whose methods should all be added to
233 | the ResourceVariable class.
234 | """
235 | # Reinitialize init method of Torch tensor with Syft init
236 | self._add_registration_to___init__(tf.Variable)
237 |
238 | # Overload Torch tensor properties with Syft properties
239 | self._hook_properties(tf.Variable)
240 |
241 | # Overload auto overloaded with Torch methods
242 | exclude = [
243 | "__class__",
244 | "__delattr__",
245 | "__dict__",
246 | "__dir__",
247 | "__doc__",
248 | "__format__",
249 | "__getattribute__",
250 | "__hash__",
251 | "__init__",
252 | "__init_subclass__",
253 | "__weakref__",
254 | "__module__",
255 | "__ne__",
256 | "__new__",
257 | "__reduce__",
258 | "__reduce_ex__",
259 | "__setattr__",
260 | "__sizeof__",
261 | "__subclasshook__",
262 | ]
263 | self._transfer_methods_to_framework_class(ResourceVariable, syft_type, exclude)
264 | self._hook_properties(ResourceVariable)
265 | self._hook_native_methods(ResourceVariable)
266 |
267 | def _hook_tensorflow_module(self):
268 | tensorflow_modules = syft.tensorflow.tensorflow_modules
269 |
270 | for module_name, tensorflow_module in tensorflow_modules.items():
271 | for func in dir(tensorflow_module):
272 |
273 | # Some functions we want to ignore (not override). Such functions have been hard
274 | # coded into the tensorflow_attribute exclude (see TensorFlowAttribute class)
275 | if func in syft.tensorflow.exclude:
276 | continue
277 |
278 | # ignore dunder functions
279 | if "__" in func:
280 | continue
281 |
282 | # ignore capitalized func values which are Classes not functinos
283 | if func[0].isupper():
284 | continue
285 |
286 | # ignore hidden functins
287 | if func[0] == "_":
288 | continue
289 |
290 | # If we haven't already overloaded this function
291 | if "native_" in func or f"native_{func}" in dir(tensorflow_module):
292 | continue
293 |
294 | self._perform_function_overloading(module_name, tensorflow_module, func)
295 |
296 | def _add_registration_to___init__(
297 | hook_self, tensor_type: type, is_tensor: bool = False
298 | ):
299 | """Adds several attributes to the tensor.
300 | Overloads tensor_type.__init__ to add several attributes to the tensor
301 | as well as registering the tensor automatically.
302 | Args:
303 | tensor_type: The type of tensor being hooked (in this refactor this
304 | is only ever tf.Tensor, but in previous versions of PySyft
305 | this iterated over all tensor types.
306 | is_tensor: An optional boolean parameter (default False) to
307 | specify whether to skip running the native initialization
308 | logic.
309 | """
310 |
311 | def new___init__(self, *args, owner=None, id=None, register=True, **kwargs):
312 | return initialize_object(
313 | hook=hook_self,
314 | obj=self,
315 | reinitialize=not is_tensor,
316 | owner=owner,
317 | id=id,
318 | init_args=args,
319 | init_kwargs=kwargs,
320 | )
321 |
322 | if "native___init__" not in dir(tensor_type):
323 | tensor_type.native___init__ = tensor_type.__init__
324 |
325 | tensor_type.__init__ = new___init__
326 |
327 | def _hook_keras_properties(hook_self, keras_type: type):
328 | super()._hook_properties(keras_type)
329 |
330 | def _hook_tensor_properties(hook_self, tensor_type: type):
331 | super()._hook_properties(tensor_type)
332 | tensor_type.native_shape = tensor_type.shape
333 |
334 | def _add_methods_to_eager_tensor(self):
335 | """
336 | Add required TensorFlowTensor methods to EagerTensor.
337 |
338 | When a user creates a tensor, e.g. with `tf.constant([1,2,3])`, the
339 | type of the returned object is an `EagerTensor`. EagerTensor is defined
340 | in tensorflow and is a super class of tf.Tensor. However, EagerTensor is
341 | not importable so we cannot add the properties to it that we want.
342 | So we do that here, which requires us to instantiate an instance of an
343 | EagerTensor to then get reference to the type.
344 |
345 | We do it this way for 2 reasons:
346 |
347 | 1. Avoid monkeypatching functions per instance (e.g. having to overwrite the
348 | function every time we make a tensor)
349 | 2. Most dunder methods are actually looked up on the class itself, rather
350 | than the instance, so monkeypatching the instance doesn't even work for
351 | things like __repr__.
352 | """
353 |
354 | dummy = tf.constant(0)
355 | eager_type = type(dummy)
356 |
357 | eager_type.native___str__ = eager_type.__str__
358 | eager_type.native___repr__ = eager_type.__repr__
359 |
360 | eager_type.__repr__ = TensorFlowTensor.__repr__
361 | eager_type.__str__ = TensorFlowTensor.__str__
362 |
363 | for method in self.to_auto_overload[tf.math]:
364 | setattr(eager_type, method, getattr(tf, method))
365 |
366 | def _hook_keras_methods(self, keras_type: type):
367 | """
368 | Add hooked version of all methods of to_auto_overload[keras_type]
369 | to the keras_type; instead of performing the native keras object
370 | method, the hooked version will be called
371 |
372 | Args:
373 | keras_type: the keras_type which holds the methods
374 | """
375 |
376 | for attr in self.to_auto_overload[keras_type]:
377 | # if we haven't already overloaded this function
378 | if f"native_{attr}" not in dir(keras_type):
379 | native_method = getattr(keras_type, attr)
380 | setattr(keras_type, f"native_{attr}", native_method)
381 | new_method = self._get_hooked_method(attr)
382 | setattr(keras_type, attr, new_method)
383 |
384 | @classmethod
385 | def create_shape(cls, shape_dims):
386 | return tf.TensorShape(shape_dims)
387 |
388 | @classmethod
389 | def create_zeros(shape, dtype, **kwargs):
390 | return tf.zeros(shape, dtype=dtype, **kwargs)
391 |
--------------------------------------------------------------------------------
/syft_tensorflow/hook/hook_args.py:
--------------------------------------------------------------------------------
1 | """Hook args implementation for TensorFLow.
2 | Implements and registers hook_args functionality for syft-tensorflow objects.
3 | See syft/generic/frameworks/hook/hook_args.py for the core implementation.
4 | """
5 |
6 | import tensorflow as tf
7 | import numpy as np
8 |
9 | from tensorflow.python.framework.ops import EagerTensor
10 | from syft.exceptions import PureFrameworkTensorFoundError
11 | from tensorflow.python.ops.resource_variable_ops import ResourceVariable
12 | from syft.generic.frameworks.hook.hook_args import (
13 | register_ambiguous_method,
14 | register_backward_func,
15 | register_forward_func,
16 | register_type_rule,
17 | one,
18 | )
19 |
20 | from syft_tensorflow.syft_types import TensorFlowTensor
21 |
22 |
23 | type_rule = {
24 | tf.Tensor: one,
25 | tf.Variable: one,
26 | TensorFlowTensor: one,
27 | EagerTensor: one,
28 | ResourceVariable: one,
29 | np.ndarray: lambda x: 0,
30 | tf.keras.layers.Layer: one,
31 | tf.keras.models.Model:one,
32 | }
33 |
34 |
35 | def default_forward(i):
36 | if hasattr(i, "child"):
37 | return i.child
38 |
39 | return (_ for _ in ()).throw(PureFrameworkTensorFoundError)
40 |
41 |
42 | forward_func = {
43 | tf.Tensor: default_forward,
44 | tf.Variable: default_forward,
45 | ResourceVariable: default_forward,
46 | EagerTensor: default_forward,
47 | tf.keras.layers.Layer: default_forward,
48 | tf.keras.models.Model: default_forward,
49 |
50 | }
51 | backward_func = {
52 | tf.Tensor: lambda i: i.wrap(type=tf.constant, value=[]),
53 | tf.Variable: lambda i: i.wrap(type=tf.Variable, initial_value=[]),
54 | ResourceVariable: lambda i: i.wrap(type=tf.Variable, initial_value=[]),
55 | EagerTensor: lambda i: i.wrap(type=tf.constant, value=[]),
56 | tf.keras.layers.Layer: lambda i: i.wrap(),
57 | tf.keras.models.Model: lambda i: i.wrap(),
58 | }
59 | ambiguous_methods = {"__getitem__", "__setitem__"}
60 |
61 | register_ambiguous_method(*ambiguous_methods)
62 | register_type_rule(type_rule)
63 | register_forward_func(forward_func)
64 | register_backward_func(backward_func)
65 |
--------------------------------------------------------------------------------
/syft_tensorflow/keras/tf_keras_fun_test.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import tensorflow as tf
3 |
4 | import syft
5 |
6 |
7 | def test_keras_activations_fn(remote):
8 |
9 | x_to_give = tf.constant([-2.0, 3.0, 5.0])
10 | expected = tf.keras.activations.relu(x_to_give)
11 |
12 | x_ptr = x_to_give.send(remote)
13 |
14 | relu_ptr = tf.keras.activations.relu(x_ptr)
15 | actual = relu_ptr.get()
16 |
17 | assert tf.math.equal(actual, expected).numpy().all()
18 |
19 | def test_keras_sigmoid(remote):
20 |
21 | expected = tf.keras.activations.sigmoid(tf.constant([1.0, 1.0]))
22 |
23 | x = tf.constant([1.0, 1.0]).send(remote)
24 | y = tf.keras.activations.sigmoid(x).get()
25 |
26 | assert tf.math.equal(y, expected).numpy().all()
27 |
--------------------------------------------------------------------------------
/syft_tensorflow/serde/__init__.py:
--------------------------------------------------------------------------------
1 | from syft_tensorflow.serde.serde import MAP_TF_SIMPLIFIERS_AND_DETAILERS
2 |
--------------------------------------------------------------------------------
/syft_tensorflow/serde/serde.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 | import io
3 | import os
4 | from tempfile import TemporaryDirectory
5 | import zipfile
6 |
7 | import syft
8 | from syft.generic.object import initialize_object
9 | from syft.generic.tensor import initialize_tensor
10 | import tensorflow as tf
11 | from tensorflow.python.framework.ops import EagerTensor
12 | from tensorflow.python.ops.resource_variable_ops import ResourceVariable
13 |
14 |
15 | def _simplify_tf_tensor(tensor: tf.Tensor) -> bin:
16 | """
17 | This function converts a TF tensor into a serialized TF tensor using
18 | tf.io. We do this because it's native to TF, and they've optimized it.
19 |
20 | Args:
21 | tensor (tf.Tensor): an input tensor to be serialized
22 |
23 | Returns:
24 | tuple: serialized tuple of TensorFlow tensor. The first value is the
25 | id of the tensor and the second is the binary for the TensorFlow
26 | object. The third is the tensor dtype and the fourth is the chain
27 | of abstractions.
28 | """
29 |
30 | tensor_ser = tf.io.serialize_tensor(tensor)
31 |
32 | dtype_ser = syft.serde._simplify(tensor.dtype)
33 |
34 | chain = None
35 | if hasattr(tensor, "child"):
36 | chain = syft.serde._simplify(tensor.child)
37 |
38 | return tensor.id, tensor_ser.numpy(), dtype_ser, chain
39 |
40 |
41 | def _detail_tf_tensor(worker, tensor_tuple) -> tf.Tensor:
42 | """
43 | This function converts a serialized tf tensor into a local TF tensor
44 | using tf.io.
45 |
46 | Args:
47 | tensor_tuple (bin): serialized obj of TF tensor. It's a tuple where
48 | the first value is the ID, the second vlaue is the binary for the
49 | TensorFlow object, the third value is the tensor_dtype_enum, and
50 | the fourth value is the chain of tensor abstractions
51 |
52 | Returns:
53 | tf.Tensor: a deserialized TF tensor
54 | """
55 |
56 | tensor_id, tensor_bin, tensor_dtype_enum, chain = tensor_tuple
57 |
58 | tensor_dtype = syft.serde._detail(worker, tensor_dtype_enum)
59 | tensor = tf.io.parse_tensor(tensor_bin, tensor_dtype)
60 |
61 | initialize_tensor(
62 | hook=syft.tensorflow.hook,
63 | obj=tensor,
64 | owner=worker,
65 | id=tensor_id,
66 | init_args=[],
67 | init_kwargs={},
68 | )
69 |
70 | if chain is not None:
71 | chain = syft.serde._detail(worker, chain)
72 | tensor.child = chain
73 | tensor.is_wrapper = True
74 |
75 | return tensor
76 |
77 |
78 | def _simplify_tf_variable(tensor: tf.Variable) -> bin:
79 | """
80 | This function converts a TF variable into a serialized TF variable using
81 | tf.io. We do this because it's native to TF, and they've optimized it.
82 |
83 | Args:
84 | tensor (tf.Variable): an input variable to be serialized
85 |
86 | Returns:
87 | tuple: serialized tuple of TensorFlow tensor. The first value is the
88 | id of the tensor and the second is the binary for the TensorFlow
89 | object. The third is the tensor dtype and the fourth is the chain
90 | of abstractions.
91 | """
92 |
93 | tensor_ser = tf.io.serialize_tensor(tensor)
94 |
95 | dtype_ser = syft.serde._simplify(tensor.dtype)
96 |
97 | chain = None
98 | if hasattr(tensor, "child"):
99 | chain = syft.serde._simplify(tensor.child)
100 |
101 | return tensor.id, tensor_ser.numpy(), dtype_ser, chain
102 |
103 |
104 | def _detail_tf_variable(worker, tensor_tuple) -> tf.Tensor:
105 | """
106 | This function converts a serialized TF variable into a local TF tensor
107 | using tf.io.
108 |
109 | Args:
110 | tensor_tuple (bin): serialized obj of TF variable. It's a tuple where
111 | the first value is the ID, the second vlaue is the binary for the
112 | TensorFlow object, the third value is the tensor_dtype_enum, and
113 | the fourth value is the chain of tensor abstractions
114 |
115 | Returns:
116 | tf.Tensor: a deserialized TF tensor
117 | """
118 |
119 | tensor_id, tensor_bin, tensor_dtype_enum, chain = tensor_tuple
120 |
121 | tensor_dtype = syft.serde._detail(worker, tensor_dtype_enum)
122 | tensor = tf.io.parse_tensor(tensor_bin, tensor_dtype)
123 | tensor = tf.Variable(tensor)
124 |
125 | initialize_tensor(
126 | hook=syft.tensorflow.hook,
127 | obj=tensor,
128 | owner=worker,
129 | id=tensor_id,
130 | init_args=[],
131 | init_kwargs={},
132 | )
133 |
134 | if chain is not None:
135 | chain = syft.serde._detail(worker, chain)
136 | tensor.child = chain
137 | tensor.is_wrapper = True
138 |
139 | return tensor
140 |
141 |
142 | def _simplify_tf_keras_layers(layer: tf.keras.layers.Layer) -> bin:
143 | """
144 | This function converts a keras layer into a serialized keras layer using
145 | keras serialize.
146 |
147 | Args:
148 | layer (tf.keras.layers.Layer): an input tensor to be serialized
149 |
150 | Returns:
151 | tuple: serialized tuple of Layer class. The first value is the
152 | id of the layer and the second is the layer configuration
153 | object. The third is the layer weights and the fourth is the
154 | batch input shape.
155 | """
156 |
157 | layer_ser = tf.keras.layers.serialize(layer)
158 |
159 | weights = layer.get_weights()
160 | weights_ser = syft.serde.serde._simplify(weights)
161 |
162 | layer_dict_ser = syft.serde.serde._simplify(layer_ser)
163 |
164 | batch_input_shape_ser = syft.serde.serde._simplify(
165 | layer_ser["config"]["batch_input_shape"]
166 | )
167 |
168 | return layer.id, layer_dict_ser, weights_ser, batch_input_shape_ser
169 |
170 |
171 | def _detail_tf_keras_layers(worker, layer_tuple) -> tf.Tensor:
172 | """
173 | This function converts a serialized keras layer into a local keras layer
174 |
175 | Args:
176 | layer_tuple (bin): serialized obj of TF layer. It's a tuple where
177 | the first value is the ID, the second value is the binary for the
178 | layer object, the third value is the layer weights, and
179 | the fourth value is the batch input shape.
180 |
181 | Returns:
182 | tf.Tensor: a deserialized TF tensor
183 | """
184 |
185 | layer_id, layer_bin, weights_bin, batch_input_shape_bin = layer_tuple
186 |
187 | layer_dict = syft.serde.serde._detail(worker, layer_bin)
188 |
189 | layer = tf.keras.layers.deserialize(layer_dict)
190 |
191 | weights = syft.serde.serde._detail(worker, weights_bin)
192 |
193 | batch_input_shape = syft.serde.serde._detail(worker, batch_input_shape_bin)
194 |
195 | layer.build(batch_input_shape)
196 |
197 | layer.set_weights(weights)
198 |
199 | initialize_object(
200 | hook=syft.tensorflow.hook,
201 | obj=layer,
202 | owner=worker,
203 | reinitialize=False,
204 | id=layer_id,
205 | init_args=[],
206 | init_kwargs={},
207 | )
208 |
209 | return layer
210 |
211 |
212 | def _simplify_keras_model(model: tf.keras.Model):
213 | """
214 | This function converts a model into a serialized saved_model.
215 |
216 | Args:
217 | model (tf.keras.Model: Keras model
218 |
219 | Returns:
220 | tuple: serialized tuple of Keras model. The first value is
221 | the binary of the model. The second is the model id.
222 | """
223 | bio = io.BytesIO()
224 |
225 | with TemporaryDirectory() as model_location:
226 | tf.keras.models.save_model(
227 | model,
228 | model_location,
229 | overwrite=True,
230 | include_optimizer=True,
231 | save_format='tf',
232 | )
233 |
234 | with zipfile.ZipFile(bio, 'w', zipfile.ZIP_DEFLATED) as model_file:
235 | # TODO: This process is way inefficient. Find a faster way to serde
236 | # SavedModels. See commit b55195a2d4b6852642f987a5a4105167120d123b
237 | # for an h5py implementation (which is less general, but clearly
238 | # more efficient)
239 | for root, dir, files in os.walk(model_location):
240 | relroot = os.path.relpath(root, start=model_location)
241 | for file in files:
242 | filename = os.path.join(root, file)
243 | arcname = os.path.join(relroot, file)
244 | model_file.write(filename, arcname=arcname)
245 |
246 | model_ser = bio.getvalue()
247 |
248 | return model_ser, model.id
249 |
250 |
251 | def _detail_keras_model(worker, model_tuple):
252 | """
253 | This function converts a serialized model into a local
254 | model.
255 |
256 | Args:
257 | modeltuple (bin): serialized obj of Keras model.
258 | It's a tuple where the first value is the binary of the model.
259 | The second is the model id.
260 |
261 | Returns:
262 | tf.keras.models.Model: a deserialized Keras model
263 | """
264 | model_ser, model_id = model_tuple
265 | bio = io.BytesIO(model_ser)
266 | with TemporaryDirectory() as model_location:
267 | with zipfile.ZipFile(bio, 'r', zipfile.ZIP_DEFLATED) as model_file:
268 | # WARNING: zipped archives can potentially deposit extra files onto
269 | # the system, although Python's zipfile offers some protection
270 | # more info: https://docs.python.org/3/library/zipfile.html#zipfile.ZipFile.extractall
271 | # TODO: further investigate security, find better option if needed
272 | model_file.extractall(model_location)
273 | model = tf.keras.models.load_model(model_location)
274 |
275 | initialize_object(
276 | hook=syft.tensorflow.hook,
277 | obj=model,
278 | owner=worker,
279 | reinitialize=False,
280 | id=model_id,
281 | init_args=[],
282 | init_kwargs={},
283 | )
284 |
285 | return model
286 |
287 |
288 | def _simplify_keras_history_callback(
289 | history_cb: tf.keras.callbacks.History
290 | ) -> bin:
291 | """
292 | This function converts history callback into serialized dictionaries.
293 |
294 | Args:
295 | history_cb (tf.keras.callbacks.History): history callback
296 |
297 | Returns:
298 | tuple: serialized tuple of history callback. The first value is
299 | the binary of the params dictionary. The second is the binary
300 | of the history dictionary.
301 | """
302 |
303 | params = history_cb.params
304 | history = history_cb.history
305 |
306 | params_ser = syft.serde.serde._simplify(params)
307 | history_ser = syft.serde.serde._simplify(history)
308 |
309 | return params_ser, history_ser
310 |
311 |
312 | def _detail_keras_history_callback(
313 | worker,
314 | history_cb_tuple
315 | ) -> tf.keras.callbacks.History:
316 | """
317 | This function converts a serialized history callback into an
318 | history callback.
319 |
320 | Args:
321 | history_cb_tuple (bin): serialized obj of history callback.
322 | It's a tuple where the first value is the binary of the params
323 | dictionary. The second is the binary of the history dictionary.
324 |
325 | Returns:
326 | tf.keras.callbacks.History: a deserialized history callback
327 | """
328 |
329 | params_bin, history_bin = history_cb_tuple
330 |
331 | params = syft.serde.serde._detail(worker, params_bin)
332 | history = syft.serde.serde._detail(worker, history_bin)
333 |
334 | history_cb = tf.keras.callbacks.History()
335 | history_cb.set_params(params)
336 | history_cb.history = history
337 |
338 | return history_cb
339 |
340 |
341 | def _simplify_tf_dtype(dtype: tf.dtypes.DType) -> bin:
342 |
343 | return dtype.as_datatype_enum, None
344 |
345 |
346 | def _detail_tf_dtype(worker, dtype_tuple):
347 | dtype_enum, _ = dtype_tuple
348 |
349 | dtype = tf.dtypes.DType(dtype_enum)
350 |
351 | return dtype
352 |
353 |
354 | def _simplify_tf_tensorshape(tensorshape: tf.TensorShape) -> bin:
355 | """
356 | This function converts a TF tensor shape into a serialized list.
357 |
358 | Args:
359 | tensor (tf.TensorShape): an input tensor shape to be serialized
360 |
361 | Returns:
362 | tuple: serialized tuple of TF tensor shape. The first value is
363 | the binary for the TensorShape object. The second is the
364 | chain of abstractions.
365 | """
366 |
367 | tensorshape_list_ser = syft.serde.serde._simplify(tensorshape.as_list())
368 |
369 | # TODO[Yann] currently this condition is never true,
370 | # tf.TensorShape needs to be hooked
371 | chain = None
372 | if hasattr(tensorshape, "child"):
373 | chain = syft.serde._simplify(tensorshape.child)
374 |
375 | return tensorshape_list_ser, chain
376 |
377 |
378 | def _detail_tf_tensorshape(worker, tensor_tuple) -> tf.TensorShape:
379 | """
380 | This function converts a serialized TF tensor shape into a local list.
381 |
382 | Args:
383 | tensor_tuple (bin): serialized obj of TF tensor shape as list.
384 | It's a tuple where the first value is the binary for the
385 | tensorflow shape (as list), and the third value is the
386 | chain of tensor abstractions.
387 |
388 | Returns:
389 | tf.Tensor: a deserialized TF tensor
390 | """
391 |
392 | tensorshape_list_bin, chain = tensor_tuple
393 |
394 | tensorshape_list = syft.serde.serde._detail(worker, tensorshape_list_bin)
395 |
396 | tensorshape = tf.TensorShape(tensorshape_list)
397 |
398 | # TODO[Yann] currently this condition is never true,
399 | # tf.TensorShape needs to be hooked
400 | if chain is not None:
401 | chain = syft.serde._detail(worker, chain)
402 | tensorshape.child = chain
403 | tensorshape.is_wrapper = True
404 |
405 | return tensorshape
406 |
407 |
408 | MAP_TF_SIMPLIFIERS_AND_DETAILERS = OrderedDict(
409 | {
410 | EagerTensor: (_simplify_tf_tensor, _detail_tf_tensor),
411 | tf.Tensor: (_simplify_tf_tensor, _detail_tf_tensor),
412 | tf.TensorShape: (_simplify_tf_tensorshape, _detail_tf_tensorshape),
413 | tf.Variable: (_simplify_tf_variable, _detail_tf_variable),
414 | tf.keras.layers.Layer: (_simplify_tf_keras_layers, _detail_tf_keras_layers),
415 | tf.keras.models.Model: (_simplify_keras_model, _detail_keras_model),
416 | ResourceVariable: (_simplify_tf_variable, _detail_tf_variable),
417 | tf.keras.callbacks.History: (_simplify_keras_history_callback,
418 | _detail_keras_history_callback),
419 | tf.dtypes.DType: (_simplify_tf_dtype, _detail_tf_dtype),
420 | }
421 | )
422 |
--------------------------------------------------------------------------------
/syft_tensorflow/serde/serde_test.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | import syft
3 |
4 |
5 | def test_serde_constant():
6 | z = tf.constant([1.0, 2.0])
7 |
8 | ser = syft.serde.serialize(z)
9 | x = syft.serde.deserialize(ser)
10 |
11 | assert all(tf.math.equal(x, z))
12 | assert x.id == z.id
13 | assert x.dtype == z.dtype
14 |
15 |
16 | def test_serde_tensorshape():
17 | z = tf.TensorShape([1, 2])
18 |
19 | ser = syft.serde.serialize(z)
20 | x = syft.serde.deserialize(ser)
21 |
22 | assert all(tf.math.equal(x, z))
23 |
24 |
25 | def test_serde_model():
26 |
27 | inp = tf.keras.layers.Input(shape=(3,))
28 | out = tf.keras.layers.Dense(2)(inp)
29 | m = tf.keras.Model(inp, out)
30 | x = tf.constant([[1, 2, 3.0]])
31 | y = m(x)
32 |
33 | ser = syft.serde.serialize(m)
34 | m_new = syft.serde.deserialize(ser)
35 |
36 | assert tf.math.equal(y, m_new(x)).numpy().all()
37 |
38 |
39 | def test_serde_dtype():
40 | d = tf.float32
41 | ser = syft.serde.serialize(d)
42 | x = syft.serde.deserialize(ser)
43 | assert d == x
44 |
--------------------------------------------------------------------------------
/syft_tensorflow/syft_types/__init__.py:
--------------------------------------------------------------------------------
1 | from syft_tensorflow.syft_types.tensor import TensorFlowTensor
2 | from syft_tensorflow.syft_types.variable import TensorFlowVariable
3 | from syft_tensorflow.syft_types.keras_object import KerasObject
4 | from syft_tensorflow.syft_types.keras_layer import KerasLayer
5 | from syft_tensorflow.syft_types.keras_model import KerasModel
6 |
7 |
--------------------------------------------------------------------------------
/syft_tensorflow/syft_types/keras_layer.py:
--------------------------------------------------------------------------------
1 | import weakref
2 |
3 | import tensorflow as tf
4 |
5 | import syft
6 | from syft.generic.frameworks.hook import hook_args
7 | from syft.generic.object import AbstractObject
8 | from syft.generic.pointers.object_pointer import ObjectPointer
9 | from syft.workers.base import BaseWorker
10 |
11 | from syft_tensorflow.syft_types import KerasObject
12 |
13 | from syft.exceptions import PureFrameworkTensorFoundError
14 |
15 |
16 | class KerasLayer(KerasObject):
17 | """Add methods to this keras layer object to have them added to every
18 | tf.keras.layers.Layer object.
19 |
20 | This Keras object is simply a more convenient way to add custom functions to
21 | all TensorFlow Keras object types. When you add a function to this keras object,
22 | it will be added to EVERY native Keras layer type (i.e. tf.keras.layers.Dense)
23 | automatically by the TensorFlowHook.
24 |
25 | Note: all methods from AbstractObject will also be included because this
26 | object extends AbstractObject. So, if you're looking for a method on
27 | the native tf.keras API but it's not listed here, you might try
28 | checking AbstractObject.
29 | """
30 |
31 | def send(
32 | self, *location, inplace: bool = False, no_wrap=False, garbage_collect_data=True
33 | ):
34 | """Gets the pointer to a new remote object.
35 |
36 | One of the most commonly used methods in PySyft, this method serializes
37 | the object upon which it is called (self), sends the object to a remote
38 | worker, creates a pointer to that worker, and then returns that pointer
39 | from this function.
40 |
41 | Args:
42 | location: The BaseWorker object which you want to send this object
43 | to. Note that this is never actually the BaseWorker but instead
44 | a class which instantiates the BaseWorker abstraction.
45 | inplace: if true,
46 | return the same object instance, else a new wrapper
47 | no_wrap: If True, wrap() is called on the created pointer
48 | garbage_collect_data: argument passed down to create_pointer()
49 |
50 | Returns:
51 | A tf.keras.layers.Layer[ObjectPointer] pointer to self. Note that this
52 | object will likely be wrapped by a ttf.keras.layers.Layer wrapper.
53 | """
54 |
55 | # If you send a pointer p1, you want the pointer to pointer p2
56 | # to control the garbage collection and not the remaining old
57 | # p1 (here self). Because if p2 is not GCed, GCing p1 shouldn't delete
58 | # the remote keras object, but if you want to do so, as p2 is not GCed,
59 | # you can still do `del p2`. This allows to chain multiple
60 | # .send().send() calls.
61 |
62 | if len(location) == 1:
63 |
64 | location = location[0]
65 |
66 | ptr = self.owner.send(
67 | self, location, garbage_collect_data=garbage_collect_data
68 | )
69 |
70 | ptr.description = self.description
71 | ptr.tags = self.tags
72 |
73 | # The last pointer should control remote GC,
74 | # not the previous self.ptr
75 | if hasattr(self, "ptr") and self.ptr is not None:
76 | ptr_ = self.ptr()
77 | if ptr_ is not None:
78 | ptr_.garbage_collect_data = False
79 |
80 | # we need to cache this weak reference to the pointer so that
81 | # if this method gets called multiple times we can simply re-use
82 | # the same pointer which was previously created
83 | self.ptr = weakref.ref(ptr)
84 |
85 | if inplace:
86 | self.child = ptr
87 | return self
88 | else:
89 | output = ptr if no_wrap else ptr.wrap(type=tf.keras.layers.Layer)
90 |
91 | else:
92 |
93 | # TODO [Yann] check if we would want to send the keras
94 | # object to several workers this way.
95 | children = list()
96 | for loc in location:
97 | children.append(self.send(loc, no_wrap=True))
98 |
99 | output = syft.MultiPointerTensor(children=children)
100 |
101 | if not no_wrap:
102 | output = output.wrap(type=tf.keras.layers.Layer)
103 |
104 | return output
105 |
--------------------------------------------------------------------------------
/syft_tensorflow/syft_types/keras_layers_test.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import tensorflow as tf
3 | import syft
4 | import numpy as np
5 |
6 |
7 | def test_send_get_keras_layer(remote):
8 |
9 | layer_to_give = tf.keras.layers.Dense(5, input_shape=[2])
10 | layer_to_give.build([2])
11 |
12 | layer_ptr = layer_to_give.send(remote)
13 | layer_gotten = layer_ptr.get()
14 |
15 | assert np.array_equal(layer_to_give.get_weights()[0],
16 | layer_gotten.get_weights()[0])
17 |
18 |
19 | def test_keras_dense_layer(remote):
20 |
21 | x_to_give = tf.random.uniform([2, 2])
22 | layer_to_give = tf.keras.layers.Dense(2, input_shape=[2])
23 | layer_to_give.build([2])
24 | expected = layer_to_give(x_to_give)
25 |
26 | x_ptr = x_to_give.send(remote)
27 | layer_ptr = layer_to_give.send(remote)
28 | actual = layer_ptr(x_ptr).get()
29 |
30 | assert np.array_equal(actual, expected)
31 |
--------------------------------------------------------------------------------
/syft_tensorflow/syft_types/keras_model.py:
--------------------------------------------------------------------------------
1 | import weakref
2 |
3 | import tensorflow as tf
4 |
5 | import syft
6 | from syft.generic.frameworks.hook import hook_args
7 | from syft.generic.object import AbstractObject
8 | from syft.generic.pointers.object_pointer import ObjectPointer
9 | from syft.workers.base import BaseWorker
10 |
11 | from syft_tensorflow.syft_types import KerasObject
12 |
13 | from syft.exceptions import PureFrameworkTensorFoundError
14 |
15 |
16 | class KerasModel(KerasObject):
17 | """Add methods to this keras model object to have them added to every
18 | tf.keras.models.Model object.
19 |
20 | This Keras object is simply a more convenient way to add custom functions to
21 | all TensorFlow Keras object types. When you add a function to this keras object,
22 | it will be added to EVERY native Keras layer type (i.e. tf.keras.models.Sequential)
23 | automatically by the TensorFlowHook.
24 |
25 | Note: all methods from AbstractObject will also be included because this
26 | object extends AbstractObject. So, if you're looking for a method on
27 | the native tf.keras API but it's not listed here, you might try
28 | checking AbstractObject.
29 | """
30 |
31 | def send(
32 | self, *location, inplace: bool = False, no_wrap=False, garbage_collect_data=True
33 | ):
34 | """Gets the pointer to a new remote object.
35 |
36 | One of the most commonly used methods in PySyft, this method serializes
37 | the object upon which it is called (self), sends the object to a remote
38 | worker, creates a pointer to that worker, and then returns that pointer
39 | from this function.
40 |
41 | Args:
42 | location: The BaseWorker object which you want to send this object
43 | to. Note that this is never actually the BaseWorker but instead
44 | a class which instantiates the BaseWorker abstraction.
45 | inplace: if true,
46 | return the same object instance, else a new wrapper
47 | no_wrap: If True, wrap() is called on the created pointer
48 | garbage_collect_data: argument passed down to create_pointer()
49 |
50 | Returns:
51 | A tf.keras.layers.Layer[ObjectPointer] pointer to self. Note that this
52 | object will likely be wrapped by a ttf.keras.layers.Layer wrapper.
53 | """
54 |
55 | # If you send a pointer p1, you want the pointer to pointer p2
56 | # to control the garbage collection and not the remaining old
57 | # p1 (here self). Because if p2 is not GCed, GCing p1 shouldn't delete
58 | # the remote keras object, but if you want to do so, as p2 is not GCed,
59 | # you can still do `del p2`. This allows to chain multiple
60 | # .send().send() calls.
61 |
62 | if len(location) == 1:
63 |
64 | location = location[0]
65 |
66 | ptr = self.owner.send(
67 | self, location, garbage_collect_data=garbage_collect_data
68 | )
69 |
70 | ptr.description = self.description
71 | ptr.tags = self.tags
72 |
73 | # The last pointer should control remote GC,
74 | # not the previous self.ptr
75 | if hasattr(self, "ptr") and self.ptr is not None:
76 | ptr_ = self.ptr()
77 | if ptr_ is not None:
78 | ptr_.garbage_collect_data = False
79 |
80 | # we need to cache this weak reference to the pointer so that
81 | # if this method gets called multiple times we can simply re-use
82 | # the same pointer which was previously created
83 | self.ptr = weakref.ref(ptr)
84 |
85 | if inplace:
86 | self.child = ptr
87 | return self
88 | else:
89 | output = ptr if no_wrap else ptr.wrap(type=tf.keras.models.Model)
90 |
91 | else:
92 |
93 | # TODO [Yann] check if we would want to send the keras
94 | # object to several workers this way.
95 | children = list()
96 | for loc in location:
97 | children.append(self.send(loc, no_wrap=True))
98 |
99 | output = syft.MultiPointerTensor(children=children)
100 |
101 | if not no_wrap:
102 | output = output.wrap(type=tf.keras.models.Model)
103 |
104 | return output
105 |
--------------------------------------------------------------------------------
/syft_tensorflow/syft_types/keras_model_test.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import tensorflow as tf
3 | import syft
4 | import numpy as np
5 |
6 |
7 | def test_send_get_keras_model(remote):
8 |
9 | model_to_give = tf.keras.models.Sequential([
10 | tf.keras.layers.Dense(5, input_shape=[2])
11 | ])
12 |
13 | model_ptr = model_to_give.send(remote)
14 | model_gotten = model_ptr.get()
15 |
16 | assert np.array_equal(model_to_give.get_weights()[0],
17 | model_gotten.get_weights()[0])
18 |
19 |
20 | def test_keras_sequential(remote):
21 |
22 | x_to_give = tf.random.uniform([2, 2])
23 | model_to_give = tf.keras.models.Sequential([
24 | tf.keras.layers.Dense(5, input_shape=[2])
25 | ])
26 | expected = model_to_give(x_to_give)
27 |
28 | x_ptr = x_to_give.send(remote)
29 | model_ptr = model_to_give.send(remote)
30 | actual = model_ptr(x_ptr).get()
31 |
32 | assert np.array_equal(actual, expected)
33 |
34 |
35 | def test_keras_model_compile(remote):
36 |
37 | model_to_give = tf.keras.models.Sequential([
38 | tf.keras.layers.Dense(5, input_shape=[2])
39 | ])
40 |
41 | model_ptr = model_to_give.send(remote)
42 |
43 | model_ptr.compile(optimizer='adam',
44 | loss='categorical_crossentropy',
45 | metrics=['accuracy'])
46 |
47 | model_on_worker = remote._objects[model_ptr.id_at_location]
48 |
49 | assert model_on_worker.loss == 'categorical_crossentropy'
50 | assert isinstance(model_on_worker.optimizer,
51 | tf.keras.optimizers.Adam)
52 |
53 |
54 | def test_keras_model_fit(remote):
55 |
56 | x_to_give = tf.random.uniform([2, 2], seed=1)
57 | y_to_give = tf.ones([2, 1])
58 |
59 | k_init = tf.keras.initializers.RandomNormal(seed=1)
60 |
61 | model_to_give = tf.keras.models.Sequential([
62 | tf.keras.layers.Dense(5,
63 | input_shape=[2],
64 | kernel_initializer=k_init)
65 | ])
66 |
67 | model_to_give.compile(optimizer='adam',
68 | loss='categorical_crossentropy',
69 | metrics=['accuracy'])
70 |
71 |
72 | model_ptr = model_to_give.send(remote)
73 | x_ptr = x_to_give.send(remote)
74 | y_ptr = y_to_give.send(remote)
75 |
76 | history_cb = model_ptr.fit(x_ptr, y_ptr, epochs=1)
77 | final_loss = history_cb.history['loss'][0]
78 |
79 | np.testing.assert_almost_equal(final_loss, 34.6580, decimal=4)
80 |
81 |
82 | def test_model_repr(remote):
83 | model_to_give = tf.keras.models.Sequential([
84 | tf.keras.layers.Dense(5, input_shape=[2])
85 | ])
86 | model_to_give_repr = str(model_to_give)
87 |
88 | model_gotten = model_to_give.send(remote).get()
89 | model_gotten_repr = str(model_gotten)
90 |
91 | assert model_to_give_repr.startswith(
92 | ' ObjectPointer:
187 | """Creates a pointer to the "self" tf.keras.layers.Layer object.
188 |
189 | Returns:
190 | A ObjectPointer pointer to self. Note that this
191 | object will likely be wrapped by a tf.keras.layers.Layer wrapper.
192 | """
193 | if id_at_location is None:
194 | id_at_location = self.id
195 |
196 | if ptr_id is None:
197 | if location is not None and location.id != self.owner.id:
198 | ptr_id = self.id
199 | else:
200 | ptr_id = syft.ID_PROVIDER.pop()
201 |
202 | ptr = ObjectPointer.create_pointer(
203 | self,
204 | location,
205 | id_at_location,
206 | register,
207 | owner,
208 | ptr_id,
209 | garbage_collect_data,
210 | )
211 |
212 | return ptr
213 |
214 | def __str__(self) -> str:
215 | if self.has_child():
216 | if self.is_wrapper:
217 | return "(Wrapper)>" + self.child.__str__()
218 | else:
219 | return type(self).__name__ + ">" + self.child.__str__()
220 | else:
221 | return self.native___str__()
222 |
223 | def __repr__(self) -> str:
224 | if self.has_child():
225 | if self.is_wrapper:
226 | return "(Wrapper)>" + self.child.__str__()
227 | else:
228 | return type(self).__name__ + ">" + self.child.__repr__()
229 | else:
230 | out = self.native___repr__()
231 |
232 | big_repr = False
233 |
234 | if self.tags is not None and len(self.tags):
235 | big_repr = True
236 | out += "\n\tTags: "
237 | for tag in self.tags:
238 | out += str(tag) + " "
239 |
240 | if self.description is not None:
241 | big_repr = True
242 | out += (
243 | "\n\tDescription: " + str(self.description).split("\n")[0] + "..."
244 | )
245 |
246 | return out
247 |
248 | @classmethod
249 | def handle_func_command(cls, command):
250 | """
251 | Instantiate the native Keras object.
252 |
253 | :param command: instruction of a function command: (command name,
254 | , arguments[, kwargs])
255 | :return: the response of the function command
256 | """
257 | cmd, _, args, kwargs = command
258 |
259 | # TODO: clean this line
260 | cmd_split = cmd.split(".")
261 | cmd_path = cmd_split[:-1]
262 | cmd_name = cmd_split[-1]
263 | cmd = "syft.local_worker.hook." + ".".join(cmd_path) + ".native_" + cmd_name
264 |
265 | # Run the native function with the new args
266 | # Note the the cmd should already be checked upon reception by the worker
267 | # in the execute_command function
268 | if isinstance(args, tuple):
269 | response = eval(cmd)(*args, **kwargs)
270 | else:
271 | response = eval(cmd)(args, **kwargs)
272 |
273 | return response
274 |
--------------------------------------------------------------------------------
/syft_tensorflow/syft_types/tensor.py:
--------------------------------------------------------------------------------
1 | import weakref
2 |
3 | import tensorflow as tf
4 |
5 | import syft
6 | from syft.generic.tensor import AbstractTensor
7 | from syft.workers.base import BaseWorker
8 | from syft.generic.pointers.pointer_tensor import PointerTensor
9 |
10 | from syft.exceptions import PureFrameworkTensorFoundError
11 | from syft.generic.frameworks.types import FrameworkTensor
12 |
13 | from syft.generic.frameworks.hook import hook_args
14 |
15 |
16 | class TensorFlowTensor(AbstractTensor):
17 | """Add methods to this tensor to have them added to every tf.Tensor object.
18 |
19 | This tensor is simply a more convenient way to add custom functions to
20 | all TensorFlow tensor types. When you add a function to this tensor,
21 | it will be added to EVERY native TF tensor type (i.e. tf.Tensor)
22 | automatically by the TensorFlowHook.
23 |
24 | Note: all methods from AbstractTensor will also be included because this
25 | tensor extends AbstractTensor. So, if you're looking for a method on
26 | the native TensorFlow tensor API but it's not listed here, you might try
27 | checking AbstractTensor.
28 | """
29 |
30 | def has_child(self):
31 | return hasattr(self, "child")
32 |
33 | def describe(self, description):
34 | self.description = description
35 | return self
36 |
37 | def tag(self, *_tags):
38 | if self.tags is None:
39 | tags = list()
40 | else:
41 | tags = list(self.tags)
42 |
43 | for new_tag in _tags:
44 | tags.append(new_tag)
45 |
46 | self.tags = set(tags)
47 | return self
48 |
49 | @property
50 | def tags(self):
51 | if self.has_child():
52 | return self.child.tags
53 | else:
54 | if not hasattr(self, "_tags"):
55 | self._tags = None
56 | return self._tags
57 |
58 | @tags.setter
59 | def tags(self, new_tags):
60 | if self.has_child():
61 | if new_tags is not None:
62 | self.child.tags = set(new_tags)
63 | else:
64 | self.child.tags = set()
65 | else:
66 | self._tags = new_tags
67 |
68 | @property
69 | def description(self):
70 | if self.has_child():
71 | return self.child.description
72 | else:
73 | if not hasattr(self, "_description"):
74 | self._description = None
75 | return self._description
76 |
77 | @description.setter
78 | def description(self, new_desc):
79 | if self.has_child():
80 | self.child.description = new_desc
81 | else:
82 | self._description = new_desc
83 |
84 | @property
85 | def shape(self):
86 | if self.is_wrapper:
87 | return self.child.shape
88 | else:
89 | return self.native_shape
90 |
91 | def send(
92 | self,
93 | *location,
94 | inplace: bool = False,
95 | # local_autograd=False,
96 | # preinitialize_grad=False,
97 | no_wrap=False,
98 | garbage_collect_data=True,
99 | ):
100 | """Gets the pointer to a new remote object.
101 |
102 | One of the most commonly used methods in PySyft, this method serializes
103 | the object upon which it is called (self), sends the object to a remote
104 | worker, creates a pointer to that worker, and then returns that pointer
105 | from this function.
106 |
107 | Args:
108 | location: The BaseWorker object which you want to send this object
109 | to. Note that this is never actually the BaseWorker but instead
110 | a class which instantiates the BaseWorker abstraction.
111 | inplace: if true,
112 | return the same object instance, else a new wrapper
113 | # local_autograd: Use autograd system on the local machine instead
114 | of TensorFlow's autograd on the workers.
115 | # preinitialize_grad: Initialize gradient for AutogradTensors
116 | to a tensor
117 | no_wrap: If True, wrap() is called on the created pointer
118 | garbage_collect_data: argument passed down to create_pointer()
119 |
120 | Returns:
121 | A tf.EagerTensor[PointerTensor] pointer to self. Note that this
122 | object will likely be wrapped by a tf.EagerTensor wrapper.
123 | """
124 |
125 | # If you send a pointer p1, you want the pointer to pointer p2
126 | # to control the garbage collection and not the remaining old
127 | # p1 (here self). Because if p2 is not GCed, GCing p1 shouldn't delete
128 | # the remote tensor, but if you want to do so, as p2 is not GCed,
129 | # you can still do `del p2`. This allows to chain multiple
130 | # .send().send() calls.
131 |
132 | if len(location) == 1:
133 |
134 | location = location[0]
135 |
136 | if hasattr(self, "child") and isinstance(
137 | self.child, PointerTensor
138 | ):
139 | self.child.garbage_collect_data = False
140 |
141 | ptr = self.owner.send(
142 | self, location, garbage_collect_data=garbage_collect_data
143 | )
144 |
145 | ptr.description = self.description
146 | ptr.tags = self.tags
147 |
148 | # The last pointer should control remote GC,
149 | # not the previous self.ptr
150 | if hasattr(self, "ptr") and self.ptr is not None:
151 | ptr_ = self.ptr()
152 | if ptr_ is not None:
153 | ptr_.garbage_collect_data = False
154 |
155 | # we need to cache this weak reference to the pointer so that
156 | # if this method gets called multiple times we can simply re-use
157 | # the same pointer which was previously created
158 | self.ptr = weakref.ref(ptr)
159 |
160 | if inplace:
161 | self.set_() # TODO[jason]: pretty sure this is torch specific
162 | self.child = ptr
163 | return self
164 | else:
165 | output = ptr if no_wrap else ptr.wrap(type=tf.constant, value=[])
166 |
167 | else:
168 |
169 | children = list()
170 | for loc in location:
171 | children.append(self.send(loc, no_wrap=True))
172 |
173 | output = syft.MultiPointerTensor(children=children)
174 |
175 | if not no_wrap:
176 | output = output.wrap(type=tf.constant, value=[])
177 |
178 | return output
179 |
180 | def get(self, *args, inplace: bool = False, **kwargs):
181 | """Requests the tensor/chain being pointed to, be serialized and return
182 | Args:
183 | args: args to forward to worker
184 | inplace: if true, return the same object instance,
185 | else a new wrapper
186 | kwargs: kwargs to forward to worker
187 | Raises:
188 | GetNotPermittedError: Raised if
189 | get is not permitted on this tensor
190 | """
191 | # Transfer the get() to the child attribute which is a pointer
192 |
193 | tensor = self.child.get(*args, **kwargs)
194 |
195 | # Clean the wrapper
196 | delattr(self, "child")
197 |
198 | if inplace:
199 | self.set_(tensor) # TODO[jvmancuso]: torch-specific
200 | if hasattr(tensor, "child"):
201 | self.child = tensor.child
202 | return self
203 | else:
204 | return tensor
205 |
206 | def create_pointer(
207 | self,
208 | location: BaseWorker = None,
209 | id_at_location: (str or int) = None,
210 | register: bool = False,
211 | owner: BaseWorker = None,
212 | ptr_id: (str or int) = None,
213 | garbage_collect_data: bool = True,
214 | shape=None,
215 | ) -> PointerTensor:
216 | """Creates a pointer to the "self" tf.Tensor object.
217 |
218 | Returns:
219 | A PointerTensor pointer to self. Note that this
220 | object will likely be wrapped by a tf.Tensor wrapper.
221 | """
222 | if id_at_location is None:
223 | id_at_location = self.id
224 |
225 | if ptr_id is None:
226 | if location is not None and location.id != self.owner.id:
227 | ptr_id = self.id
228 | else:
229 | ptr_id = syft.ID_PROVIDER.pop()
230 |
231 | if shape is None:
232 | shape = self.shape
233 |
234 | ptr = syft.PointerTensor.create_pointer(
235 | self,
236 | location,
237 | id_at_location,
238 | register,
239 | owner,
240 | ptr_id,
241 | garbage_collect_data,
242 | shape,
243 | )
244 |
245 | return ptr
246 |
247 | def __str__(self) -> str:
248 | if self.has_child():
249 | if self.is_wrapper:
250 | return "(Wrapper)>" + self.child.__str__()
251 | else:
252 | return type(self).__name__ + ">" + self.child.__str__()
253 | else:
254 | return self.native___str__()
255 |
256 | def __repr__(self) -> str:
257 | if self.has_child():
258 | if self.is_wrapper:
259 | return "(Wrapper)>" + self.child.__str__()
260 | else:
261 | return type(self).__name__ + ">" + self.child.__repr__()
262 | else:
263 | out = self.native___repr__()
264 |
265 | big_repr = False
266 |
267 | if self.tags is not None and len(self.tags):
268 | big_repr = True
269 | out += "\n\tTags: "
270 | for tag in self.tags:
271 | out += str(tag) + " "
272 |
273 | if self.description is not None:
274 | big_repr = True
275 | out += (
276 | "\n\tDescription: " + str(self.description).split("\n")[0] + "..."
277 | )
278 |
279 | if big_repr:
280 | out += "\n\tShape: " + str(self.shape.as_list())
281 |
282 | return out
283 |
284 | @classmethod
285 | def handle_func_command(cls, command):
286 | """
287 | Operates as a router for functions. A function call always starts
288 | by being handled here and 3 scenarii must be considered:
289 |
290 | Real TensorFlow tensor:
291 | The arguments of the function are real tensors so we should
292 | run the native TensorFlow command
293 |
294 | TensorFlow wrapper:
295 | The arguments are just wrappers at the top of a chain
296 | (ex: wrapper>LoggingTensor>TensorFlow tensor), so just forward
297 | the instruction to the next layer type in the chain (in
298 | the example above to LoggingTensor.handle_func_command),
299 | get the response and replace a wrapper on top of all tensors
300 | found in the response.
301 |
302 | Syft Tensor:
303 | The arguments are syft tensors of same type: this can happen
304 | if at any node of the chain where some function is forwarded,
305 | the handle_func_command modify the function and make a new
306 | call but keeps the arguments "un-wrapped". Making a new call
307 | means that by default the command is treated here in the
308 | global router.
309 |
310 | :param command: instruction of a function command: (command name,
311 | , arguments[, kwargs])
312 | :return: the response of the function command
313 | """
314 | cmd, _, args, kwargs = command
315 |
316 | try: # will work if tensors are wrappers
317 |
318 | # Replace all TensorFlow tensor with their child attribute
319 | # Note that we return also args_type which helps handling case 3 in the docstring
320 | new_args, new_kwargs, new_type, args_type = hook_args.unwrap_args_from_function(
321 | cmd, args, kwargs, return_args_type=True
322 | )
323 | # This handles case 3: it redirects the command to the appropriate class depending
324 | # of the syft type of the arguments and returns
325 | if args_type not in FrameworkTensor:
326 | return args_type.handle_func_command(command)
327 |
328 | # build the new command
329 | new_command = (cmd, None, new_args, new_kwargs)
330 | # Send it to the appropriate class and get the response
331 | response = new_type.handle_func_command(new_command)
332 | # Put back the wrappers where needed
333 | response = hook_args.hook_response(cmd, response, wrap_type=args_type)
334 | except PureFrameworkTensorFoundError: # means that it's not a wrapper but a pure tensor
335 |
336 | # Check that the function has not been overwritten
337 | try:
338 | # Try to get recursively the attributes in cmd = "....."
339 | command = cls.rgetattr(cls, cmd)
340 | return command(*args, **kwargs)
341 | except AttributeError:
342 | pass
343 |
344 | # TODO: clean this line
345 | cmd_split = cmd.split(".")
346 | cmd_path = cmd_split[:-1]
347 | cmd_name = cmd_split[-1]
348 | cmd = "syft.local_worker.hook." + ".".join(cmd_path) + ".native_" + cmd_name
349 |
350 | # Run the native function with the new args
351 | # Note the the cmd should already be checked upon reception by the worker
352 | # in the execute_command function
353 | if isinstance(args, tuple):
354 | response = eval(cmd)(*args, **kwargs)
355 | else:
356 | response = eval(cmd)(args, **kwargs)
357 |
358 | return response
359 |
--------------------------------------------------------------------------------
/syft_tensorflow/syft_types/tensor_test.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import tensorflow as tf
3 | import syft
4 | import numpy as np
5 |
6 |
7 | def test_send_get_constant(remote):
8 | x_to_give = tf.constant(2.0)
9 | x_ptr = x_to_give.send(remote)
10 | x_gotten = x_ptr.get()
11 |
12 | assert tf.math.equal(x_to_give, x_gotten)
13 |
14 | def test_constant_with_numpy(remote):
15 | x_to_give = tf.constant(np.ones([2,2]))
16 | x_ptr = x_to_give.send(remote)
17 | x_gotten = x_ptr.get()
18 |
19 | assert tf.math.equal(x_to_give, x_gotten).numpy().all()
20 |
21 | def test_add(remote):
22 | x = tf.constant(2.0).send(remote)
23 | y = tf.constant(3.0).send(remote)
24 | z_ptr = x + y
25 | z = z_ptr.get()
26 |
27 | assert tf.math.equal(z, tf.constant(5.0))
28 |
29 | def test_constant_repr(remote):
30 | x = tf.constant([1, 2, 3]).send(remote)
31 | repr = str(x)
32 | assert repr.startswith('(Wrapper)>[PointerTensor | me')
33 |
34 | def test_gotten_repr(remote):
35 | x = tf.constant([1, 2, 3]).send(remote).get()
36 | repr = str(x)
37 |
38 | assert repr == "tf.Tensor([1 2 3], shape=(3,), dtype=int32)"
39 |
--------------------------------------------------------------------------------
/syft_tensorflow/syft_types/variable.py:
--------------------------------------------------------------------------------
1 | import weakref
2 |
3 | import tensorflow as tf
4 |
5 | import syft
6 | from syft.generic.tensor import AbstractTensor
7 | from syft.workers.base import BaseWorker
8 | from syft.generic.pointers.pointer_tensor import PointerTensor
9 |
10 | from syft.exceptions import PureFrameworkTensorFoundError
11 | from syft.generic.frameworks.types import FrameworkTensor
12 |
13 | from syft.generic.frameworks.hook import hook_args
14 |
15 |
16 | class TensorFlowVariable(AbstractTensor):
17 | """Add methods to this tensor to have them added to every tf.Tensor object.
18 |
19 | This tensor is simply a more convenient way to add custom functions to
20 | all TensorFlow tensor types. When you add a function to this tensor,
21 | it will be added to EVERY native TF tensor type (i.e. tf.Tensor)
22 | automatically by the TensorFlowHook.
23 |
24 | Note: all methods from AbstractTensor will also be included because this
25 | tensor extends AbstractTensor. So, if you're looking for a method on
26 | the native TensorFlow tensor API but it's not listed here, you might try
27 | checking AbstractTensor.
28 | """
29 |
30 | def has_child(self):
31 | return hasattr(self, "child")
32 |
33 | def describe(self, description):
34 | self.description = description
35 | return self
36 |
37 | def tag(self, *_tags):
38 | if self.tags is None:
39 | tags = list()
40 | else:
41 | tags = list(self.tags)
42 |
43 | for new_tag in _tags:
44 | tags.append(new_tag)
45 |
46 | self.tags = set(tags)
47 | return self
48 |
49 | @property
50 | def tags(self):
51 | if self.has_child():
52 | return self.child.tags
53 | else:
54 | if not hasattr(self, "_tags"):
55 | self._tags = None
56 | return self._tags
57 |
58 | @tags.setter
59 | def tags(self, new_tags):
60 | if self.has_child():
61 | if new_tags is not None:
62 | self.child.tags = set(new_tags)
63 | else:
64 | self.child.tags = set()
65 | else:
66 | self._tags = new_tags
67 |
68 | @property
69 | def description(self):
70 | if self.has_child():
71 | return self.child.description
72 | else:
73 | if not hasattr(self, "_description"):
74 | self._description = None
75 | return self._description
76 |
77 | @description.setter
78 | def description(self, new_desc):
79 | if self.has_child():
80 | self.child.description = new_desc
81 | else:
82 | self._description = new_desc
83 |
84 | @property
85 | def shape(self):
86 | if self.is_wrapper:
87 | return self.child.shape
88 | else:
89 | return self.native_shape
90 |
91 | def send(
92 | self,
93 | *location,
94 | inplace: bool = False,
95 | # local_autograd=False,
96 | # preinitialize_grad=False,
97 | no_wrap=False,
98 | garbage_collect_data=True,
99 | ):
100 | """Gets the pointer to a new remote object.
101 |
102 | One of the most commonly used methods in PySyft, this method serializes
103 | the object upon which it is called (self), sends the object to a remote
104 | worker, creates a pointer to that worker, and then returns that pointer
105 | from this function.
106 |
107 | Args:
108 | location: The BaseWorker object which you want to send this object
109 | to. Note that this is never actually the BaseWorker but instead
110 | a class which instantiates the BaseWorker abstraction.
111 | inplace: if true,
112 | return the same object instance, else a new wrapper
113 | # local_autograd: Use autograd system on the local machine instead
114 | of TensorFlow's autograd on the workers.
115 | # preinitialize_grad: Initialize gradient for AutogradTensors
116 | to a tensor
117 | no_wrap: If True, wrap() is called on the created pointer
118 | garbage_collect_data: argument passed down to create_pointer()
119 |
120 | Returns:
121 | A tf.EagerTensor[PointerTensor] pointer to self. Note that this
122 | object will likely be wrapped by a tf.EagerTensor wrapper.
123 | """
124 |
125 | # If you send a pointer p1, you want the pointer to pointer p2
126 | # to control the garbage collection and not the remaining old
127 | # p1 (here self). Because if p2 is not GCed, GCing p1 shouldn't delete
128 | # the remote tensor, but if you want to do so, as p2 is not GCed,
129 | # you can still do `del p2`. This allows to chain multiple
130 | # .send().send() calls.
131 |
132 | if len(location) == 1:
133 |
134 | location = location[0]
135 |
136 | if hasattr(self, "child") and isinstance(self.child, PointerTensor):
137 | self.child.garbage_collect_data = False
138 |
139 | ptr = self.owner.send(
140 | self, location, garbage_collect_data=garbage_collect_data
141 | )
142 |
143 | ptr.description = self.description
144 | ptr.tags = self.tags
145 |
146 | # The last pointer should control remote GC,
147 | # not the previous self.ptr
148 | if hasattr(self, "ptr") and self.ptr is not None:
149 | ptr_ = self.ptr()
150 | if ptr_ is not None:
151 | ptr_.garbage_collect_data = False
152 |
153 | # we need to cache this weak reference to the pointer so that
154 | # if this method gets called multiple times we can simply re-use
155 | # the same pointer which was previously created
156 | self.ptr = weakref.ref(ptr)
157 |
158 | if inplace:
159 | self.set_() # TODO[jason]: pretty sure this is torch specific
160 | self.child = ptr
161 | return self
162 | else:
163 | output = (
164 | ptr if no_wrap else ptr.wrap(type=tf.Variable, initial_value=[])
165 | )
166 |
167 | else:
168 |
169 | children = list()
170 | for loc in location:
171 | children.append(self.send(loc, no_wrap=True))
172 |
173 | output = syft.MultiPointerTensor(children=children)
174 |
175 | if not no_wrap:
176 | output = output.wrap(type=tf.Variable, initial_value=[])
177 |
178 | return output
179 |
180 | def get(self, *args, inplace: bool = False, **kwargs):
181 | """Requests the tensor/chain being pointed to, be serialized and return
182 | Args:
183 | args: args to forward to worker
184 | inplace: if true, return the same object instance,
185 | else a new wrapper
186 | kwargs: kwargs to forward to worker
187 | Raises:
188 | GetNotPermittedError: Raised if
189 | get is not permitted on this tensor
190 | """
191 | # Transfer the get() to the child attribute which is a pointer
192 |
193 | tensor = self.child.get(*args, **kwargs)
194 |
195 | # Clean the wrapper
196 | delattr(self, "child")
197 |
198 | if inplace:
199 | self.set_(tensor) # TODO[jvmancuso]: torch-specific
200 | if hasattr(tensor, "child"):
201 | self.child = tensor.child
202 | return self
203 | else:
204 | return tensor
205 |
206 | def create_pointer(
207 | self,
208 | location: BaseWorker = None,
209 | id_at_location: (str or int) = None,
210 | register: bool = False,
211 | owner: BaseWorker = None,
212 | ptr_id: (str or int) = None,
213 | garbage_collect_data: bool = True,
214 | shape=None,
215 | ) -> PointerTensor:
216 | """Creates a pointer to the "self" tf.Tensor object.
217 |
218 | Returns:
219 | A PointerTensor pointer to self. Note that this
220 | object will likely be wrapped by a tf.Tensor wrapper.
221 | """
222 | if id_at_location is None:
223 | id_at_location = self.id
224 |
225 | if ptr_id is None:
226 | if location is not None and location.id != self.owner.id:
227 | ptr_id = self.id
228 | else:
229 | ptr_id = syft.ID_PROVIDER.pop()
230 |
231 | if shape is None:
232 | shape = self.shape
233 |
234 | ptr = syft.PointerTensor.create_pointer(
235 | self,
236 | location,
237 | id_at_location,
238 | register,
239 | owner,
240 | ptr_id,
241 | garbage_collect_data,
242 | shape,
243 | )
244 |
245 | return ptr
246 |
247 | def __str__(self) -> str:
248 | if self.has_child():
249 | if self.is_wrapper:
250 | return "(Wrapper)>" + self.child.__str__()
251 | else:
252 | return type(self).__name__ + ">" + self.child.__str__()
253 | else:
254 | return self.native___str__()
255 |
256 | def __repr__(self) -> str:
257 | if self.has_child():
258 | if self.is_wrapper:
259 | return "(Wrapper)>" + self.child.__str__()
260 | else:
261 | return type(self).__name__ + ">" + self.child.__repr__()
262 | else:
263 | out = self.native___repr__()
264 |
265 | big_repr = False
266 |
267 | if self.tags is not None and len(self.tags):
268 | big_repr = True
269 | out += "\n\tTags: "
270 | for tag in self.tags:
271 | out += str(tag) + " "
272 |
273 | if self.description is not None:
274 | big_repr = True
275 | out += (
276 | "\n\tDescription: " + str(self.description).split("\n")[0] + "..."
277 | )
278 |
279 | if big_repr:
280 | out += "\n\tShape: " + str(self.shape.as_list())
281 |
282 | return out
283 |
284 | @classmethod
285 | def handle_func_command(cls, command):
286 | """
287 | Operates as a router for functions. A function call always starts
288 | by being handled here and 3 scenarii must be considered:
289 |
290 | Real TensorFlow tensor:
291 | The arguments of the function are real tensors so we should
292 | run the native TensorFlow command
293 |
294 | TensorFlow wrapper:
295 | The arguments are just wrappers at the top of a chain
296 | (ex: wrapper>LoggingTensor>TensorFlow tensor), so just forward
297 | the instruction to the next layer type in the chain (in
298 | the example above to LoggingTensor.handle_func_command),
299 | get the response and replace a wrapper on top of all tensors
300 | found in the response.
301 |
302 | Syft Tensor:
303 | The arguments are syft tensors of same type: this can happen
304 | if at any node of the chain where some function is forwarded,
305 | the handle_func_command modify the function and make a new
306 | call but keeps the arguments "un-wrapped". Making a new call
307 | means that by default the command is treated here in the
308 | global router.
309 |
310 | :param command: instruction of a function command: (command name,
311 | , arguments[, kwargs])
312 | :return: the response of the function command
313 | """
314 | cmd, _, args, kwargs = command
315 |
316 | try: # will work if tensors are wrappers
317 |
318 | # Replace all TensorFlow tensor with their child attribute
319 | # Note that we return also args_type which helps handling case 3 in the docstring
320 | new_args, new_kwargs, new_type, args_type = hook_args.unwrap_args_from_function(
321 | cmd, args, kwargs, return_args_type=True
322 | )
323 | # This handles case 3: it redirects the command to the appropriate class depending
324 | # of the syft type of the arguments and returns
325 | if args_type not in FrameworkTensor:
326 | return args_type.handle_func_command(command)
327 |
328 | # build the new command
329 | new_command = (cmd, None, new_args, new_kwargs)
330 | # Send it to the appropriate class and get the response
331 | response = new_type.handle_func_command(new_command)
332 | # Put back the wrappers where needed
333 | response = hook_args.hook_response(cmd, response, wrap_type=args_type)
334 | except PureFrameworkTensorFoundError: # means that it's not a wrapper but a pure tensor
335 |
336 | # Check that the function has not been overwritten
337 | try:
338 | # Try to get recursively the attributes in cmd = "....."
339 | command = cls.rgetattr(cls, cmd)
340 | return command(*args, **kwargs)
341 | except AttributeError:
342 | pass
343 |
344 | # TODO: clean this line
345 | cmd_split = cmd.split(".")
346 | cmd_path = cmd_split[:-1]
347 | cmd_name = cmd_split[-1]
348 | cmd = "syft.local_worker.hook." + ".".join(cmd_path) + ".native_" + cmd_name
349 |
350 | # Run the native function with the new args
351 | # Note the the cmd should already be checked upon reception by the worker
352 | # in the execute_command function
353 | if isinstance(args, tuple):
354 | response = eval(cmd)(*args, **kwargs)
355 | else:
356 | response = eval(cmd)(args, **kwargs)
357 |
358 | return response
359 |
--------------------------------------------------------------------------------
/syft_tensorflow/syft_types/variable_test.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | import numpy as np
3 |
4 |
5 | def test_send_get_variable(remote):
6 | x_to_give = tf.Variable(2.0)
7 | x_ptr = x_to_give.send(remote)
8 | x_gotten = x_ptr.get()
9 | assert np.array_equal(x_to_give.numpy(), x_gotten.numpy())
10 |
11 |
12 | def test_add_variables(remote):
13 | x = tf.Variable([3.0, 3.0]).send(remote)
14 | y = tf.Variable([2.0, 2.0]).send(remote)
15 | z_ptr = x + y
16 | z = z_ptr.get()
17 | assert np.array_equal(z.numpy(), [5., 5.])
18 |
19 |
20 | def test_variable_repr(remote):
21 | x = tf.Variable([1, 2, 3]).send(remote)
22 | repr = str(x)
23 | assert repr.startswith('(Wrapper)>[PointerTensor | me')
24 |
25 |
26 | def test_func_on_variables(remote):
27 | x = tf.Variable([3.0, 3.0]).send(remote)
28 | y = tf.Variable([2.0, 2.0]).send(remote)
29 | z_ptr = tf.multiply(x, y)
30 | z = z_ptr.get()
31 | assert np.array_equal(z.numpy(), [6., 6.])
32 |
33 |
34 | def test_assign_variable(remote):
35 | x = tf.Variable([3.0, 3.0]).send(remote)
36 | y = tf.Variable([2.0, 2.0]).send(remote)
37 | x.assign(y)
38 | z = x.get()
39 | assert np.array_equal(z.numpy(), [2., 2.])
40 |
--------------------------------------------------------------------------------