From 85a8927f5e71575f1c7dfa3bd14734b139f1727d Mon Sep 17 00:00:00 2001 From: Umut Date: Thu, 16 Dec 2021 12:05:22 +0300 Subject: [PATCH] docs: improve working with tensors tutorial --- docs/user/tutorial/tensor_operations.ipynb | 374 +++++++++++---------- 1 file changed, 203 insertions(+), 171 deletions(-) diff --git a/docs/user/tutorial/tensor_operations.ipynb b/docs/user/tutorial/tensor_operations.ipynb index 681a450cd..76ca12e0b 100644 --- a/docs/user/tutorial/tensor_operations.ipynb +++ b/docs/user/tutorial/tensor_operations.ipynb @@ -5,7 +5,7 @@ "id": "34d13212", "metadata": {}, "source": [ - "# Tensor Operations" + "# Working With Tensors" ] }, { @@ -13,7 +13,7 @@ "id": "6999361c", "metadata": {}, "source": [ - "In this tutorial, we'll go over what you can do with tensors. Each supported operation will be written out as a function. Then, all of them will be compiled in a loop and executed with a random input to demonstrate their semantics." + "In this tutorial, we'll go over what you can do with encrypted tensors. Each supported operation will be written out as a function. Then, all of them will be compiled in a loop and executed with a random input to demonstrate their semantics." ] }, { @@ -44,6 +44,14 @@ "### Inputset Definition" ] }, + { + "cell_type": "markdown", + "id": "ab71e23f", + "metadata": {}, + "source": [ + "We will generate some random input tensors as calibration data for our encrypted tensor functions." + ] + }, { "cell_type": "code", "execution_count": 2, @@ -303,182 +311,28 @@ }, { "cell_type": "markdown", - "id": "09311480", + "id": "e917b82a", "metadata": {}, "source": [ - "### Compilation and Homomorphic Evaluation of Supported Operations" + "### Prepare Supported Operations List " ] }, { "cell_type": "markdown", - "id": "cf0152a2", + "id": "9495a29d", "metadata": {}, "source": [ - "Note that some operations require programmable bootstrapping to work and programmable bootstrapping has a certain probability of failure. Usually, it has more than a 99% probability of success but with big bit-widths, this probability can drop to 95%." + "We will create a list of supported operations to showcase them in a loop." ] }, { "cell_type": "code", "execution_count": 24, - "id": "0cdbc545", + "id": "0cb14b31", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "#######################################################################################\n", - "\n", - "def reshape(x):\n", - " return x.reshape((2, 3))\n", - "\n", - "reshape([[7, 10], [9, 10], [7, 9]]) homomorphically evaluates to [[7, 10, 9], [10, 7, 9]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def flatten(x):\n", - " return x.flatten()\n", - "\n", - "flatten([[4, 7], [6, 3], [9, 8]]) homomorphically evaluates to [4, 7, 6, 3, 9, 8]\n", - "\n", - "#######################################################################################\n", - "\n", - "def index(x):\n", - " return x[2, 0]\n", - "\n", - "index([[8, 4], [9, 6], [9, 10]]) homomorphically evaluates to 9\n", - "\n", - "#######################################################################################\n", - "\n", - "def slice_(x):\n", - " return x.flatten()[1:5]\n", - "\n", - "slice_([[5, 9], [7, 4], [7, 3]]) homomorphically evaluates to [9, 7, 4, 7]\n", - "\n", - "#######################################################################################\n", - "\n", - "def add_scalar(x):\n", - " return x + 10\n", - "\n", - "add_scalar([[10, 9], [9, 9], [8, 9]]) homomorphically evaluates to [[20, 19], [19, 19], [18, 19]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def add_tensor(x):\n", - " return x + np.array([[1, 2], [3, 3], [2, 1]], dtype=np.uint8)\n", - "\n", - "add_tensor([[6, 9], [9, 7], [7, 5]]) homomorphically evaluates to [[7, 11], [12, 10], [9, 6]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def add_tensor_broadcasted(x):\n", - " return x + np.array([1, 10], dtype=np.uint8)\n", - "\n", - "add_tensor_broadcasted([[3, 3], [4, 7], [9, 10]]) homomorphically evaluates to [[4, 13], [5, 17], [10, 20]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def sub_scalar(x):\n", - " return x + (-1)\n", - "\n", - "sub_scalar([[3, 8], [10, 4], [5, 8]]) homomorphically evaluates to [[2, 7], [9, 3], [4, 7]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def sub_tensor(x):\n", - " return x + (-np.array([[1, 2], [3, 3], [2, 1]], dtype=np.uint8))\n", - "\n", - "sub_tensor([[4, 6], [6, 8], [4, 3]]) homomorphically evaluates to [[3, 4], [3, 5], [2, 2]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def sub_tensor_broadcasted(x):\n", - " return x + (-np.array([3, 0], dtype=np.uint8))\n", - "\n", - "sub_tensor_broadcasted([[6, 7], [9, 10], [6, 3]]) homomorphically evaluates to [[3, 7], [6, 10], [3, 3]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def mul_scalar(x):\n", - " return x * 2\n", - "\n", - "mul_scalar([[8, 5], [10, 4], [10, 4]]) homomorphically evaluates to [[16, 10], [20, 8], [20, 8]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def mul_tensor(x):\n", - " return x * np.array([[1, 2], [3, 3], [2, 1]], dtype=np.uint8)\n", - "\n", - "mul_tensor([[7, 3], [10, 3], [6, 10]]) homomorphically evaluates to [[7, 6], [30, 9], [12, 10]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def mul_tensor_broadcasted(x):\n", - " return x * np.array([2, 3], dtype=np.uint8)\n", - "\n", - "mul_tensor_broadcasted([[6, 7], [3, 4], [7, 6]]) homomorphically evaluates to [[12, 21], [6, 12], [14, 18]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def power(x):\n", - " return x ** 2\n", - "\n", - "power([[6, 4], [6, 8], [3, 10]]) homomorphically evaluates to [[36, 16], [36, 64], [9, 100]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def truediv(x):\n", - " return x // 2\n", - "\n", - "truediv([[9, 5], [4, 7], [3, 7]]) homomorphically evaluates to [[4, 2], [2, 3], [1, 3]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def dot(x):\n", - " return x.flatten() @ np.array([1, 1, 1, 2, 1, 1], dtype=np.uint8)\n", - "\n", - "dot([[4, 10], [4, 3], [5, 8]]) homomorphically evaluates to 37\n", - "\n", - "#######################################################################################\n", - "\n", - "def matmul(x):\n", - " return x @ np.array([[1, 2, 3], [3, 2, 1]], dtype=np.uint8)\n", - "\n", - "matmul([[7, 10], [3, 8], [3, 3]]) homomorphically evaluates to [[37, 34, 31], [27, 22, 17], [12, 12, 12]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def clip(x):\n", - " return x.clip(6, 11)\n", - "\n", - "clip([[9, 5], [10, 10], [4, 6]]) homomorphically evaluates to [[9, 6], [10, 10], [6, 6]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def comparison(x):\n", - " return x > np.array([[10, 5], [8, 11], [3, 7]], dtype=np.uint8)\n", - "\n", - "comparison([[4, 4], [5, 10], [7, 6]]) homomorphically evaluates to [[0, 0], [0, 0], [1, 0]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def maximum(x):\n", - " return np.maximum(x, np.array([[10, 5], [8, 11], [3, 7]], dtype=np.uint8))\n", - "\n", - "maximum([[9, 7], [4, 10], [6, 8]]) homomorphically evaluates to [[10, 7], [8, 11], [6, 8]]\n", - "\n", - "#######################################################################################\n", - "\n", - "def minimum(x):\n", - " return np.minimum(x, np.array([[10, 5], [8, 11], [3, 7]], dtype=np.uint8))\n", - "\n", - "minimum([[4, 9], [10, 8], [10, 7]]) homomorphically evaluates to [[4, 5], [8, 8], [3, 7]]\n", - "\n" - ] - } - ], + "outputs": [], "source": [ - "functions = [\n", + "supported_operations = [\n", " reshape,\n", " flatten,\n", " index,\n", @@ -500,23 +354,201 @@ " comparison,\n", " maximum,\n", " minimum,\n", - "]\n", - "\n", - "for function in functions:\n", - " compiler = hnp.NPFHECompiler(function, {\"x\": \"encrypted\"})\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "09311480", + "metadata": {}, + "source": [ + "### Compilation and Homomorphic Evaluation of Supported Operations" + ] + }, + { + "cell_type": "markdown", + "id": "cf0152a2", + "metadata": {}, + "source": [ + "Note that some operations require programmable bootstrapping to work and programmable bootstrapping has a certain probability of failure. Usually, it has more than a 99% probability of success but with big bit-widths, this probability can drop to 95%." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "0cdbc545", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "#######################################################################################\n", + "\n", + "def reshape(x):\n", + " return x.reshape((2, 3))\n", + "\n", + "reshape([[3, 6], [5, 6], [9, 10]]) homomorphically evaluates to [[3, 6, 5], [6, 9, 10]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def flatten(x):\n", + " return x.flatten()\n", + "\n", + "flatten([[7, 8], [10, 9], [8, 9]]) homomorphically evaluates to [7, 8, 10, 9, 8, 9]\n", + "\n", + "#######################################################################################\n", + "\n", + "def index(x):\n", + " return x[2, 0]\n", + "\n", + "index([[3, 10], [5, 4], [6, 4]]) homomorphically evaluates to 6\n", + "\n", + "#######################################################################################\n", + "\n", + "def slice_(x):\n", + " return x.flatten()[1:5]\n", + "\n", + "slice_([[5, 7], [5, 6], [9, 5]]) homomorphically evaluates to [7, 5, 6, 9]\n", + "\n", + "#######################################################################################\n", + "\n", + "def add_scalar(x):\n", + " return x + 10\n", + "\n", + "add_scalar([[3, 5], [4, 8], [9, 5]]) homomorphically evaluates to [[13, 15], [14, 18], [19, 15]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def add_tensor(x):\n", + " return x + np.array([[1, 2], [3, 3], [2, 1]], dtype=np.uint8)\n", + "\n", + "add_tensor([[4, 3], [4, 9], [8, 3]]) homomorphically evaluates to [[5, 5], [7, 12], [10, 4]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def add_tensor_broadcasted(x):\n", + " return x + np.array([1, 10], dtype=np.uint8)\n", + "\n", + "add_tensor_broadcasted([[9, 3], [4, 4], [8, 6]]) homomorphically evaluates to [[10, 13], [5, 14], [9, 16]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def sub_scalar(x):\n", + " return x + (-1)\n", + "\n", + "sub_scalar([[6, 6], [5, 10], [4, 9]]) homomorphically evaluates to [[5, 5], [4, 9], [3, 8]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def sub_tensor(x):\n", + " return x + (-np.array([[1, 2], [3, 3], [2, 1]], dtype=np.uint8))\n", + "\n", + "sub_tensor([[7, 3], [6, 3], [9, 5]]) homomorphically evaluates to [[6, 1], [3, 0], [7, 4]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def sub_tensor_broadcasted(x):\n", + " return x + (-np.array([3, 0], dtype=np.uint8))\n", + "\n", + "sub_tensor_broadcasted([[6, 7], [10, 6], [3, 10]]) homomorphically evaluates to [[3, 7], [7, 6], [0, 10]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def mul_scalar(x):\n", + " return x * 2\n", + "\n", + "mul_scalar([[10, 4], [8, 6], [7, 7]]) homomorphically evaluates to [[20, 8], [16, 12], [14, 14]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def mul_tensor(x):\n", + " return x * np.array([[1, 2], [3, 3], [2, 1]], dtype=np.uint8)\n", + "\n", + "mul_tensor([[10, 8], [3, 6], [8, 4]]) homomorphically evaluates to [[10, 16], [9, 18], [16, 4]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def mul_tensor_broadcasted(x):\n", + " return x * np.array([2, 3], dtype=np.uint8)\n", + "\n", + "mul_tensor_broadcasted([[4, 5], [9, 7], [9, 5]]) homomorphically evaluates to [[8, 15], [18, 21], [18, 15]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def power(x):\n", + " return x ** 2\n", + "\n", + "power([[10, 9], [9, 10], [8, 7]]) homomorphically evaluates to [[100, 81], [81, 100], [64, 49]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def truediv(x):\n", + " return x // 2\n", + "\n", + "truediv([[10, 7], [7, 7], [4, 8]]) homomorphically evaluates to [[5, 3], [3, 3], [2, 4]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def dot(x):\n", + " return x.flatten() @ np.array([1, 1, 1, 2, 1, 1], dtype=np.uint8)\n", + "\n", + "dot([[3, 10], [4, 7], [7, 6]]) homomorphically evaluates to 44\n", + "\n", + "#######################################################################################\n", + "\n", + "def matmul(x):\n", + " return x @ np.array([[1, 2, 3], [3, 2, 1]], dtype=np.uint8)\n", + "\n", + "matmul([[8, 9], [5, 5], [8, 9]]) homomorphically evaluates to [[35, 34, 33], [20, 20, 20], [35, 34, 33]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def clip(x):\n", + " return x.clip(6, 11)\n", + "\n", + "clip([[3, 4], [4, 4], [8, 7]]) homomorphically evaluates to [[6, 6], [6, 6], [8, 7]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def comparison(x):\n", + " return x > np.array([[10, 5], [8, 11], [3, 7]], dtype=np.uint8)\n", + "\n", + "comparison([[3, 5], [8, 8], [3, 7]]) homomorphically evaluates to [[0, 0], [0, 0], [0, 0]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def maximum(x):\n", + " return np.maximum(x, np.array([[10, 5], [8, 11], [3, 7]], dtype=np.uint8))\n", + "\n", + "maximum([[5, 10], [4, 9], [9, 6]]) homomorphically evaluates to [[10, 10], [8, 11], [9, 7]]\n", + "\n", + "#######################################################################################\n", + "\n", + "def minimum(x):\n", + " return np.minimum(x, np.array([[10, 5], [8, 11], [3, 7]], dtype=np.uint8))\n", + "\n", + "minimum([[9, 8], [4, 3], [5, 9]]) homomorphically evaluates to [[9, 5], [4, 3], [3, 7]]\n", + "\n" + ] + } + ], + "source": [ + "for operation in supported_operations:\n", + " compiler = hnp.NPFHECompiler(operation, {\"x\": \"encrypted\"})\n", " circuit = compiler.compile_on_inputset(inputset)\n", " \n", + " # We setup an example tensor that will be encrypted and passed on to the current operation\n", " sample = np.random.randint(3, 11, size=(3, 2), dtype=np.uint8)\n", " result = circuit.run(sample)\n", " \n", " print(\"#######################################################################################\")\n", - " \n", " print()\n", - " print(f\"{inspect.getsource(function)}\")\n", - " print(f\"{function.__name__}({sample.tolist()}) homomorphically evaluates to {result if isinstance(result, int) else result.tolist()}\")\n", + " print(f\"{inspect.getsource(operation)}\")\n", + " print(f\"{operation.__name__}({sample.tolist()}) homomorphically evaluates to {result if isinstance(result, int) else result.tolist()}\")\n", " print()\n", "\n", - " expected = function(sample)\n", + " expected = operation(sample)\n", " if not np.array_equal(result, expected):\n", " print(f\"(It should have been evaluated to {expected if isinstance(expected, int) else expected.tolist()} but it didn't due to an error during PBS)\")\n", " print()"