Files
concrete/examples/QuantizedLinearRegression.ipynb
2021-09-14 17:31:55 +02:00

795 lines
115 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "markdown",
"id": "73e4f53d",
"metadata": {},
"source": [
"# Quantized Linear Regression\n",
"\n",
"Currently, **concrete** only supports unsigned integers up to 7-bits. Nevertheless, we want to evaluate a linear regression model with it. Luckily, we can make use of **quantization** to overcome this limitation!"
]
},
{
"cell_type": "markdown",
"id": "15e6e686",
"metadata": {},
"source": [
"### Let's start by importing some libraries to develop our linear regression model"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "0c2101de",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np"
]
},
{
"cell_type": "markdown",
"id": "6f02c3d4",
"metadata": {},
"source": [
"### And some helpers for visualization"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "91260335",
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"\n",
"import matplotlib.pyplot as plt\n",
"from IPython.display import display"
]
},
{
"cell_type": "markdown",
"id": "27f67e43",
"metadata": {},
"source": [
"### We need an inputset, a handcrafted one for simplicity"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "84b42c42",
"metadata": {},
"outputs": [],
"source": [
"x = np.array([[130], [110], [100], [145], [160], [185], [200], [80], [50]], dtype=np.float32)\n",
"y = np.array([325, 295, 268, 400, 420, 500, 520, 220, 120], dtype=np.float32)"
]
},
{
"cell_type": "markdown",
"id": "fba2eecb",
"metadata": {},
"source": [
"### Let's visualize our inputset to get a grasp of it"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "a8c83085",
"metadata": {},
"outputs": [],
"source": [
"plt.ioff()\n",
"fig, ax = plt.subplots(1)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "93e61f29",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"ax.scatter(x[:, 0], y, marker=\"x\", color=\"red\")\n",
"display(fig)"
]
},
{
"cell_type": "markdown",
"id": "fd40fedf",
"metadata": {},
"source": [
"### Now, we need a model so let's define it\n",
"\n",
"The main purpose of this tutorial is not to train a linear regression model but to use it homomorphically. So we will not discuss about how the model is trained."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "4f95ae45",
"metadata": {},
"outputs": [],
"source": [
"class Model:\n",
" w = None\n",
" b = None\n",
"\n",
" def fit(self, x, y):\n",
" a = np.ones((x.shape[0], x.shape[1] + 1), dtype=np.float32)\n",
" a[:, 1:] = x\n",
"\n",
" regularization_contribution = np.identity(x.shape[1] + 1, dtype=np.float32)\n",
" regularization_contribution[0][0] = 0\n",
"\n",
" parameters = np.linalg.pinv(a.T @ a + regularization_contribution) @ a.T @ y\n",
"\n",
" self.b = parameters[0]\n",
" self.w = parameters[1:].reshape(-1, 1)\n",
"\n",
" return self\n",
"\n",
" def evaluate(self, x):\n",
" return x @ self.w + self.b"
]
},
{
"cell_type": "markdown",
"id": "9b0c8d49",
"metadata": {},
"source": [
"### And create one"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "ad97e3e0",
"metadata": {},
"outputs": [],
"source": [
"model = Model().fit(x, y)"
]
},
{
"cell_type": "markdown",
"id": "e18b52fd",
"metadata": {},
"source": [
"### Time to make some predictions"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "5fd2e6bf",
"metadata": {},
"outputs": [],
"source": [
"inputs = np.linspace(40, 210, 100).reshape(-1, 1)\n",
"predictions = model.evaluate(inputs)"
]
},
{
"cell_type": "markdown",
"id": "fd49b135",
"metadata": {},
"source": [
"### Let's visualize our predictions to see how our model performs"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "e76b0343",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"ax.plot(inputs, predictions, color=\"blue\")\n",
"display(fig)"
]
},
{
"cell_type": "markdown",
"id": "0e080f5b",
"metadata": {},
"source": [
"### As a bonus let's inspect the model parameters"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "32ebe574",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[2.669915]]\n",
"-3.2335143\n"
]
}
],
"source": [
"print(model.w)\n",
"print(model.b)"
]
},
{
"cell_type": "markdown",
"id": "b1b90d66",
"metadata": {},
"source": [
"They are floating point numbers and we can't directly work with them!"
]
},
{
"cell_type": "markdown",
"id": "b3e45e1f",
"metadata": {},
"source": [
"### So, let's abstract quantization\n",
"\n",
"Here is a quick summary of quantization. We have a range of values and we want to represent them using small number of bits (n). To do this, we split the range into 2^n sections and map each section to a value. Here is a visualization of the process!"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "7d878724",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" width=\"420px\" height=\"195px\" viewBox=\"-0.5 -0.5 420 195\" content=\"&lt;mxfile host=&quot;app.diagrams.net&quot; modified=&quot;2021-08-13T09:47:25.144Z&quot; agent=&quot;5.0 (X11)&quot; etag=&quot;5QhM0DGu1eUjmjeXuyFL&quot; version=&quot;14.9.6&quot; type=&quot;device&quot;&gt;&lt;diagram id=&quot;6rZNNX4_K12e_kCXuZoG&quot; name=&quot;Page-1&quot;&gt;7Zzdb5s6FMD/mkjdw66MHZL0sUl376SrStM6rc8euAGNYAZOk/avnw2YD5t8QAmhJA+tyLFzbM7v2D7HdjtCi9X2vxAHzgO1iTeCwN6O0P0IQhPd8t9C8JoI0MRMBMvQtRORkQse3TeSCkEqXbs2iUoVGaUec4Oy0KK+TyxWkuEwpJtytWfqlVsN8JJogkcLe7r0ybWZk0hnJsjlX4m7dGTLBkhLVlhWTgWRg226KYjQlxFahJSy5Gm1XRBP2E7aJfnevztKs46FxGfHfOFpYrx9//Hw//Lpq8mc+yd4H5mfUy0v2FunL5x2lr1KCxDfvhOG5J8sD0eRa43QPGI4ZLrYYSuPCwz+mOghtmbevL9GZgXuPYSuCAtfeZVNbmdpZqdgYikLiYeZ+1JWj1Pcy0xd1sI36vKGIUg905ikelLHhGNQVhHRdWiR9FtFuyqKTHhAETfVkjBNEX8ovHYuirHVQAhrIfSpTz4WKAgU+5oNQWmKULeg0NBBjdsCpSrqGNR46KBmbYFSFXUMyhw4KKQuLU1BaYo6BjUZOig1mGgMSlXUMajpwEGN2womNEUdg5oNHVRbwYSmqGNQt0MH1VYwoSnqGJTccaidC38gWFoKBKfNYGmRn6poByxuPfxaqBaICtGeDquBC5ju75e6fpbr84ekB+16DmzoOR9/F0Xzg+ltM4dCqmeqik49+utl50NieND0RzNUl9quGdZL3AfF8JDpj2Z4aECfmmG9nH5QDNuaS7XcpGuGVen+xOPWmtvuC39ciseV699sP8kC3lChTEPOyJaVaUYspL/Jgno0zOPmZ9fzFBH23KUvXIJDJlw+fyEhcy3s3aUFK9e2RTPzjeMy8hhgS7S5CXHAZSFd+zYRLwuybgkFZFvXh3YFKNJ5Cj42rvAxCHa7U4lfbVh6yr/C25jMUBloA+3sDBpl8zaOnNgsRpmKkH/DjFvajyUQoIyVPMOEH2A+hLdlTAiMm82HqiI4UxSdej6s2gMozIcFzJM/a3EePH+mPvscxafhd7yCAYJtTEyW5xNlokfUf5eiBz7U8qk4UVduQp2he/YCjI7i7DZR9ytspVdHmqKXE6XoeHqfwmhr8ZqqI3J23olTKr6OrFOPLOM6sk45stQc/Pwjq97NoAsJSdRz1MYhiXYg23FIImle8e47fW2OV83yusbb6ArL4PG2lVCoijrHW+8+xZA2ybTjXDj+ZzJphlE7cazQdWqSVRcuGsRtWpx173LWPo9hCmFbUjInbEOIrxcsqB8Rax3D0Qp/ig5GO6O3XsZpo/fHZaq7GdPcRc4VmqGqtbtnOcMwkh5Y8x16OQpOka2Me5atoKrT25551DCGBLoOieoh0betMaSnADcbR6z7QPzEEgD7espzis1LJdpD8n5ZgZABO0Wkn3WDy+Fh9O0UDh11bF17OoVV4Xky/EQqLfrM1cR/cdh4eThl1/5cZ/zq1GByVGpgGJ36cAv5ZKVXbOWasdcX3ul5ENwIMV4JgP6vKEgKlc/cduL3W1BR9KnotmBnby/FR9U1L5tziw7a7SRbddXh/TNZ5R7IdebaEavKCwmZV5hnXnkPXJDIpqAL52Ya/eImG7ty289t2rPxNq463NW4vZFQZPxBsvetbggfHZWKdVqmm5fuCerf3xjy4KjgCbN2PIF/zP+3SHJckf+DFvTlLw==&lt;/diagram&gt;&lt;/mxfile&gt;\"><defs/><g><path d=\"M 14.37 84 L 361.63 84\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 9.12 84 L 16.12 80.5 L 14.37 84 L 16.12 87.5 Z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"all\"/><path d=\"M 366.88 84 L 359.88 87.5 L 361.63 84 L 359.88 80.5 Z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"all\"/><path d=\"M 48 94 L 48 74\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 88 94 L 88 74\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 128 94 L 128 74\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 168 94 L 168 74\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 208 94 L 208 74\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 248 94 L 248 74\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 288 94 L 288 74\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 328 94 L 328 74\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 48 71 L 60.93 58.07 Q 68 51 78 51 L 98 51 Q 108 51 115.07 58.07 L 123.5 66.5\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 127.21 70.21 L 119.78 67.73 L 123.5 66.5 L 124.73 62.78 Z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"all\"/><path d=\"M 134.37 123 L 141.63 123\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 129.12 123 L 136.12 119.5 L 134.37 123 L 136.12 126.5 Z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"all\"/><path d=\"M 146.88 123 L 139.88 126.5 L 141.63 123 L 139.88 119.5 Z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"all\"/><path d=\"M 154.37 123 L 181.63 123\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 149.12 123 L 156.12 119.5 L 154.37 123 L 156.12 126.5 Z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"all\"/><path d=\"M 186.88 123 L 179.88 126.5 L 181.63 123 L 179.88 119.5 Z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"all\"/><path d=\"M 194.37 123 L 221.63 123\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 189.12 123 L 196.12 119.5 L 194.37 123 L 196.12 126.5 Z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"all\"/><path d=\"M 226.88 123 L 219.88 126.5 L 221.63 123 L 219.88 119.5 Z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"all\"/><path d=\"M 234.37 123 L 241.63 123\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 229.12 123 L 236.12 119.5 L 234.37 123 L 236.12 126.5 Z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"all\"/><path d=\"M 246.88 123 L 239.88 126.5 L 241.63 123 L 239.88 119.5 Z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"all\"/><rect x=\"108\" y=\"94\" width=\"40\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 104px; margin-left: 109px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \"><div>min(x)</div></div></div></div></foreignObject><text x=\"128\" y=\"108\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"12px\" text-anchor=\"middle\">min(x)</text></switch></g><rect x=\"228\" y=\"94\" width=\"40\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 104px; margin-left: 229px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \">max(x)</div></div></div></foreignObject><text x=\"248\" y=\"108\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"12px\" text-anchor=\"middle\">max(x)</text></switch></g><path d=\"M 138 148 L 138 128\" fill=\"none\" stroke=\"#000000\" stroke-width=\"2\" stroke-miterlimit=\"10\" stroke-dasharray=\"2 6\" pointer-events=\"stroke\"/><rect x=\"118\" y=\"152\" width=\"40\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 162px; margin-left: 119px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 10px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \"><div style=\"font-size: 10px\"><font style=\"font-size: 10px\">Map</font></div><div style=\"font-size: 10px\"><font style=\"font-size: 10px\">to 0<br style=\"font-size: 10px\"/></font></div></div></div></div></foreignObject><text x=\"138\" y=\"165\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"10px\" text-anchor=\"middle\">Map...</text></switch></g><rect x=\"148\" y=\"152\" width=\"40\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 162px; margin-left: 149px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 10px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \"><div style=\"font-size: 10px\"><font style=\"font-size: 10px\">Map</font></div><div style=\"font-size: 10px\"><font style=\"font-size: 10px\">to 1<br style=\"font-size: 10px\"/></font></div></div></div></div></foreignObject><text x=\"168\" y=\"165\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"10px\" text-anchor=\"middle\">Map...</text></switch></g><path d=\"M 168 148 L 168 128\" fill=\"none\" stroke=\"#000000\" stroke-width=\"2\" stroke-miterlimit=\"10\" stroke-dasharray=\"2 6\" pointer-events=\"stroke\"/><path d=\"M 208 148 L 208 128\" fill=\"none\" stroke=\"#000000\" stroke-width=\"2\" stroke-miterlimit=\"10\" stroke-dasharray=\"2 6\" pointer-events=\"stroke\"/><path d=\"M 238 148 L 238 128\" fill=\"none\" stroke=\"#000000\" stroke-width=\"2\" stroke-miterlimit=\"10\" stroke-dasharray=\"2 6\" pointer-events=\"stroke\"/><path d=\"M 294.37 68.66 L 321.63 68.66\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"stroke\"/><path d=\"M 289.12 68.66 L 296.12 65.16 L 294.37 68.66 L 296.12 72.16 Z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"all\"/><path d=\"M 326.88 68.66 L 319.88 72.16 L 321.63 68.66 L 319.88 65.16 Z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" pointer-events=\"all\"/><rect x=\"288\" y=\"18.66\" width=\"40\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 29px; margin-left: 289px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \"><font style=\"font-size: 10px\">Distance<br/>Between<br/>Consecutive<br/>Values</font></div></div></div></foreignObject><text x=\"308\" y=\"32\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"12px\" text-anchor=\"middle\">Distan...</text></switch></g><rect x=\"188\" y=\"152\" width=\"40\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 162px; margin-left: 189px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 10px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \"><div style=\"font-size: 10px\"><font style=\"font-size: 10px\">Map</font></div><div style=\"font-size: 10px\"><font style=\"font-size: 10px\">to 2</font></div></div></div></div></foreignObject><text x=\"208\" y=\"165\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"10px\" text-anchor=\"middle\">Map...</text></switch></g><rect x=\"218\" y=\"152\" width=\"40\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 162px; margin-left: 219px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 10px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \"><div style=\"font-size: 10px\"><font style=\"font-size: 10px\">Map</font></div><div style=\"font-size: 10px\"><font style=\"font-size: 10px\">to 3</font></div></div></div></div></foreignObject><text x=\"238\" y=\"165\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"10px\" text-anchor=\"middle\">Map...</text></switch></g><rect x=\"128\" y=\"174\" width=\"120\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 184px; margin-left: 129px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 10px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \">(when n = 2)</div></div></div></foreignObject><text x=\"188\" y=\"187\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"10px\" text-anchor=\"middle\">(when n = 2)</text></switch></g><rect x=\"28\" y=\"94\" width=\"40\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 104px; margin-left: 29px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 10px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \">0</div></div></div></foreignObject><text x=\"48\" y=\"107\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"10px\" text-anchor=\"middle\">0</text></switch></g><rect x=\"308\" y=\"18.66\" width=\"110\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 108px; height: 1px; padding-top: 29px; margin-left: 309px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 10px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \"><div><font style=\"font-size: 12px\">= 1 / scale</font></div><div><font style=\"font-size: 12px\">= 1 / q</font></div></div></div></div></foreignObject><text x=\"363\" y=\"32\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"10px\" text-anchor=\"middle\">= 1 / scale...</text></switch></g><rect x=\"128\" y=\"24\" width=\"140\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 138px; height: 1px; padding-top: 34px; margin-left: 129px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 10px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \"><font style=\"font-size: 12px\">x =</font><font style=\"font-size: 12px\"> (x   + zp  ) / q </font></div></div></div></foreignObject><text x=\"198\" y=\"37\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"10px\" text-anchor=\"middle\">x = (x   + zp  ) / q </text></switch></g><rect x=\"167\" y=\"29\" width=\"40\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 39px; margin-left: 168px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 10px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \"><div><font style=\"font-size: 10px\">q</font></div></div></div></div></foreignObject><text x=\"187\" y=\"42\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"10px\" text-anchor=\"middle\">q</text></switch></g><rect x=\"199\" y=\"29\" width=\"40\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 39px; margin-left: 200px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 10px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \"><div>x</div></div></div></div></foreignObject><text x=\"219\" y=\"42\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"10px\" text-anchor=\"middle\">x</text></switch></g><rect x=\"227\" y=\"29\" width=\"40\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 39px; margin-left: 228px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 10px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \"><div>x</div></div></div></div></foreignObject><text x=\"247\" y=\"42\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"10px\" text-anchor=\"middle\">x</text></switch></g><rect x=\"48\" y=\"28\" width=\"80\" height=\"20\" fill=\"none\" stroke=\"none\" pointer-events=\"all\"/><g transform=\"translate(-0.5 -0.5)\"><switch><foreignObject style=\"overflow: visible; text-align: left;\" pointer-events=\"none\" width=\"100%\" height=\"100%\" requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display: flex; align-items: unsafe center; justify-content: unsafe center; width: 78px; height: 1px; padding-top: 38px; margin-left: 49px;\"><div style=\"box-sizing: border-box; font-size: 0; text-align: center; \"><div style=\"display: inline-block; font-size: 10px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; \"><div>zero point<br/></div><div>zp = 2</div></div></div></div></foreignObject><text x=\"88\" y=\"41\" fill=\"#000000\" font-family=\"Helvetica\" font-size=\"10px\" text-anchor=\"middle\">zero point...</text></switch></g></g><switch><g requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"/><a transform=\"translate(0,-5)\" xlink:href=\"https://www.diagrams.net/doc/faq/svg-export-text-problems\" target=\"_blank\"><text text-anchor=\"middle\" font-size=\"10px\" x=\"50%\" y=\"100%\">Viewer does not support full SVG 1.1</text></a></switch></svg>"
],
"text/plain": [
"<IPython.core.display.SVG object>"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from IPython.display import SVG\n",
"SVG(filename=\"figures/QuantizationVisualized.svg\")"
]
},
{
"cell_type": "markdown",
"id": "814afccd",
"metadata": {},
"source": [
"If you want to learn more, head to https://intellabs.github.io/distiller/algo_quantization.html"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "d81af434",
"metadata": {},
"outputs": [],
"source": [
"class QuantizationParameters:\n",
" def __init__(self, q, zp, n):\n",
" # q = scale factor = 1 / distance between consecutive values\n",
" # zp = zero point which is used to determine the beginning of the quantized range\n",
" # (quantized 0 = the beginning of the quantized range = zp * distance between consecutive values)\n",
" # n = number of bits\n",
" \n",
" # e.g.,\n",
" \n",
" # n = 2\n",
" # zp = 2\n",
" # q = 0.66\n",
" # distance between consecutive values = 1 / q = 1.5151\n",
" \n",
" # quantized 0 = zp / q = zp * distance between consecutive values = 3.0303\n",
" # quantized 1 = quantized 0 + distance between consecutive values = 4.5454\n",
" # quantized 2 = quantized 1 + distance between consecutive values = 6.0606\n",
" # quantized 3 = quantized 2 + distance between consecutive values = 7.5757\n",
" \n",
" self.q = q\n",
" self.zp = zp\n",
" self.n = n\n",
"\n",
"class QuantizedArray:\n",
" def __init__(self, values, parameters):\n",
" # values = quantized values\n",
" # parameters = parameters used during quantization\n",
" \n",
" # e.g.,\n",
" \n",
" # values = [1, 0, 2, 1]\n",
" # parameters = QuantizationParameters(q=0.66, zp=2, n=2)\n",
" \n",
" # original array = [4.5454, 3.0303, 6.0606, 4.5454]\n",
" \n",
" self.values = np.array(values)\n",
" self.parameters = parameters\n",
"\n",
" @staticmethod\n",
" def of(x, n):\n",
" if not isinstance(x, np.ndarray):\n",
" x = np.array(x)\n",
"\n",
" min_x = x.min()\n",
" max_x = x.max()\n",
"\n",
" if min_x == max_x: # encoding single valued arrays\n",
" \n",
" if min_x == 0.0: # encoding 0s\n",
" \n",
" # dequantization = (x_q + zp_x) / q_x = 0 --> q_x = 1 && zp_x = 0 && x_q = 0\n",
" q_x = 1\n",
" zp_x = 0\n",
" x_q = np.zeros(x.shape, dtype=np.uint)\n",
" \n",
" elif min_x < 0.0: # encoding negative scalars\n",
" \n",
" # dequantization = (x_q + zp_x) / q_x = -x --> q_x = 1 / x & zp_x = -1 & x_q = 0\n",
" q_x = abs(1 / min_x)\n",
" zp_x = -1\n",
" x_q = np.zeros(x.shape, dtype=np.uint)\n",
" \n",
" else: # encoding positive scalars\n",
" \n",
" # dequantization = (x_q + zp_x) / q_x = x --> q_x = 1 / x & zp_x = 0 & x_q = 1\n",
" q_x = 1 / min_x\n",
" zp_x = 0\n",
" x_q = np.ones(x.shape, dtype=np.uint)\n",
" \n",
" else: # encoding multi valued arrays\n",
" \n",
" # distance between consecutive values = range of x / number of different quantized values = (max_x - min_x) / (2^n - 1)\n",
" # q = 1 / distance between consecutive values\n",
" q_x = (2**n - 1) / (max_x - min_x)\n",
" \n",
" # zp = what should be added to 0 to get min_x -> min_x = (0 + zp) / q -> zp = min_x * q\n",
" zp_x = int(round(min_x * q_x))\n",
" \n",
" # x = (x_q + zp) / q -> x_q = (x * q) - zp\n",
" x_q = ((q_x * x) - zp_x).round().astype(np.uint)\n",
"\n",
" return QuantizedArray(x_q, QuantizationParameters(q_x, zp_x, n))\n",
"\n",
" def dequantize(self):\n",
" # x = (x_q + zp) / q\n",
" # x = (x_q + zp) / q\n",
" return (self.values.astype(np.float32) + float(self.parameters.zp)) / self.parameters.q\n",
"\n",
" def affine(self, w, b, min_y, max_y, n_y):\n",
" # the formulas used in this method was derived from the following equations\n",
" #\n",
" # x = (x_q + zp_x) / q_x\n",
" # w = (w_q + zp_w) / q_w\n",
" # b = (b_q + zp_b) / q_b\n",
" #\n",
" # (x * w) + b = ((x_q + zp_x) / q_x) * ((w_q + zp_w) / q_w) + ((b_q + zp_b) / q_b)\n",
" # = y = (y_q + zp_y) / q_y\n",
" #\n",
" # So, ((x_q + zp_x) / q_x) * ((w_q + zp_w) / q_w) + ((b_q + zp_b) / q_b) = (y_q + zp_y) / q_y\n",
" # We can calculate zp_y and q_y from min_y, max_y, n_y. So, the only unknown is y_q and it can be solved.\n",
"\n",
" x_q = self.values\n",
" w_q = w.values\n",
" b_q = b.values\n",
"\n",
" q_x = self.parameters.q\n",
" q_w = w.parameters.q\n",
" q_b = b.parameters.q\n",
"\n",
" zp_x = self.parameters.zp\n",
" zp_w = w.parameters.zp\n",
" zp_b = b.parameters.zp\n",
"\n",
" q_y = (2**n_y - 1) / (max_y - min_y)\n",
" zp_y = int(round(min_y * q_y))\n",
"\n",
" y_q = (q_y / (q_x * q_w)) * ((x_q + zp_x) @ (w_q + zp_w) + (q_x * q_w / q_b) * (b_q + zp_b))\n",
" y_q -= min_y * q_y\n",
" y_q = y_q.round().clip(0, 2**n_y - 1).astype(np.uint)\n",
"\n",
" return QuantizedArray(y_q, QuantizationParameters(q_y, zp_y, n_y))\n",
"\n",
"class QuantizedFunction:\n",
" def __init__(self, table):\n",
" self.table = table\n",
"\n",
" @staticmethod\n",
" def of(f, input_bits, output_bits):\n",
" domain = np.array(range(2**input_bits), dtype=np.uint)\n",
" table = f(domain).round().clip(0, 2**output_bits - 1).astype(np.uint)\n",
" return QuantizedFunction(table)"
]
},
{
"cell_type": "markdown",
"id": "0ddd6342",
"metadata": {},
"source": [
"### Let's quantize our model parameters\n",
"\n",
"Since the parameters only consist of scalars, we can use a single bit quantization."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "9189e38d",
"metadata": {},
"outputs": [],
"source": [
"parameter_bits = 1\n",
"\n",
"w_q = QuantizedArray.of(model.w, parameter_bits)\n",
"b_q = QuantizedArray.of(model.b, parameter_bits)"
]
},
{
"cell_type": "markdown",
"id": "add5b6c2",
"metadata": {},
"source": [
"### And quantize our inputs"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "e1f94ff2",
"metadata": {},
"outputs": [],
"source": [
"input_bits = 6\n",
"\n",
"x_q = QuantizedArray.of(inputs, input_bits)"
]
},
{
"cell_type": "markdown",
"id": "37209a62",
"metadata": {},
"source": [
"### Time to make quantized inference"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "131be184",
"metadata": {},
"outputs": [],
"source": [
"output_bits = 7\n",
"\n",
"min_y = predictions.min()\n",
"max_y = predictions.max()\n",
"y_q = x_q.affine(w_q, b_q, min_y, max_y, output_bits)\n",
"\n",
"quantized_predictions = y_q.dequantize()"
]
},
{
"cell_type": "markdown",
"id": "ea94c049",
"metadata": {},
"source": [
"### And visualize the results"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "2ab0f580",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"ax.plot(inputs, quantized_predictions, color=\"black\")\n",
"display(fig)"
]
},
{
"cell_type": "markdown",
"id": "ea85b0ea",
"metadata": {},
"source": [
"### Now it's time to make the inference homomorphic"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "2d341f26",
"metadata": {},
"outputs": [],
"source": [
"q_y = (2**output_bits - 1) / (max_y - min_y)\n",
"zp_y = int(round(min_y * q_y))\n",
"\n",
"q_x = x_q.parameters.q\n",
"q_w = w_q.parameters.q\n",
"q_b = b_q.parameters.q\n",
"\n",
"zp_x = x_q.parameters.zp\n",
"zp_w = w_q.parameters.zp\n",
"zp_b = b_q.parameters.zp\n",
"\n",
"x_q = x_q.values\n",
"w_q = w_q.values\n",
"b_q = b_q.values"
]
},
{
"cell_type": "markdown",
"id": "b6c4b6c0",
"metadata": {},
"source": [
"### Simplification to rescue!\n",
"\n",
"The `y_q` formula in `QuantizedArray.affine(...)` can be rewritten to make it easier to implement in homomorphically. Here is the breakdown.\n",
"```\n",
"(q_y / (q_x * q_w)) * ((x_q + zp_x) @ (w_q + zp_w) + (q_x * q_w / q_b) * (b_q + zp_b)) - (min_y * q_y)\n",
"^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^\n",
"constant (c1) can be done constant (c2) constant (c3) constant (c4)\n",
" on the circuit \n",
" \n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" can be done on the circuit\n",
" \n",
"^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
"cannot be done on the circuit because of floating point operation so will be a single table lookup\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "907fc5b1",
"metadata": {},
"source": [
"### Let's import the concrete numpy package now!"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "15e7e265",
"metadata": {},
"outputs": [],
"source": [
"import concrete.numpy as hnp"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "85034b43",
"metadata": {},
"outputs": [],
"source": [
"c1 = q_y / (q_x * q_w)\n",
"c2 = w_q + zp_w\n",
"c3 = (q_x * q_w / q_b) * (b_q + zp_b)\n",
"c4 = min_y * q_y\n",
"\n",
"f = lambda intermediate: (c1 * (intermediate + c3)) - c4\n",
"f_q = QuantizedFunction.of(f, input_bits + parameter_bits, output_bits)\n",
"\n",
"table = hnp.LookupTable([int(entry) for entry in f_q.table])\n",
"\n",
"w_0 = int(c2.flatten()[0])\n",
"\n",
"def infer(x_0):\n",
" return table[(x_0 + zp_x) * w_0]"
]
},
{
"cell_type": "markdown",
"id": "91d4f22b",
"metadata": {},
"source": [
"### Let's compile our quantized inference function to it's operation graph for visualization"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "d6bc9eee",
"metadata": {},
"outputs": [],
"source": [
"inputset = []\n",
"for x_i in x_q:\n",
" inputset.append((int(x_i[0]),))\n",
"\n",
"homomorphic_model = hnp.compile_numpy_function_into_op_graph(\n",
" infer,\n",
" {\"x_0\": hnp.EncryptedScalar(hnp.Integer(input_bits, is_signed=False))},\n",
" iter(inputset),\n",
")"
]
},
{
"cell_type": "markdown",
"id": "2177fbd9",
"metadata": {},
"source": [
"### Here are some representations of the operation graph"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "e284fcc3",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"%0 = Constant(1) # ClearScalar<Integer<unsigned, 8 bits>>\n",
"%1 = x_0 # EncryptedScalar<Integer<unsigned, 7 bits>>\n",
"%2 = Constant(15) # ClearScalar<Integer<unsigned, 8 bits>>\n",
"%3 = Add(1, 2) # EncryptedScalar<Integer<unsigned, 7 bits>>\n",
"%4 = Mul(3, 0) # EncryptedScalar<Integer<unsigned, 7 bits>>\n",
"%5 = TLU(4) # EncryptedScalar<Integer<unsigned, 7 bits>>\n",
"return(%5)\n",
"\n"
]
}
],
"source": [
"print(hnp.get_printable_graph(homomorphic_model, show_data_types=True))"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "bee209f2",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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smULZ8+e1WLUmId25ihWr17NlVdeSRsPpUT3NnoGjh8T7DNz7rjjDqKjo8nIyDDbFHegZ+D4M8HszCkpKWHdunUB00QF3Uz1a4LZmfPDDz+Qm5urxajxHYLVmZOZmUmDBg1o27at2aa4DS1GPydYnTmZmZn06NGDkJDAeYQD506CmGCbmVNaWsratWsDqokKWowBQzA5c7Zt28apU6f8fmV/ebQYA4RgcuZkZmZSv359UlJSzDbFrWgxBhDB4syx9RcDbWxVizGACAZnjogEZH8RtBgDDk84c7Zu3Urfvn2Ji4sjNjaW3r17s27dOrd8d03ZsWMHv/zyS5X9xaVLl5KUlERYmP9EI9ViDEDc6czZuHEj3bp1IzY2ll27drF//36aN29OWloay5cvd4O1NSMzM5N69erRoUMHl8f37dtH//79ef755zl58qR3jaslem5qgPLss88yc+ZMfvrpp0sOgFxaWkr79u3Jyclh3759REdHA2oqWtu2bblw4QLZ2dlERka60/RKufvuuzl//jxLlixxefzee++lffv2PPPMMzRt2pQTJ05QXFxROhufQs9NDVTc4czJyspix44dDB48uEyIAKGhoQwbNozDhw+zePFid5hbLUSErKysSvuLM2fOZMKECX7VPLWhxRiguMOZs2rVKgCuv/56p2O2fStXrrx0I2vI7t27OXnyZKVitP/R8De0GAOY2jpzdu/eDcBVV13ldCwhIQGAPXv21M7IGpCZmUlMTAzXXXed167pTbQYA5zaOHNyc3MBqFvXOUdTTEwMAKdPn66VfTUhMzOT7t27Ex4e7rVrehMtxgDHUzNzbH4/bwaCqqq/6O9oMQYBl+rMiYuLA+D8+fNOx2z7bOd4mj179nDs2DEtRo1/c6nOnNatWwNw5MgRp2NHjx4F8Fq80szMTOrUqePSmRQoaDEGCZfizLHlr9i8ebPTMdu+Xr16uc/ISsjMzKRbt25ERER45XpmoMUYRNicOdOnT6/W+T179iQ5OZn58+eTn59ftr+kpIR58+aRmJhI3759PWWuA2vWrAnoJipoMQYVycnJPPnkk0ycOLFazpyQkBBmzpxJTk4Oo0eP5sSJE5w6dYpx48aRnZ3NjBkziIqK8rjd+/bt49ChQ1qMmsCips6crl27sn79es6cOUOrVq1o2rQp2dnZrF69mt///vcetlaRmZlJVFQUnTt3rvLcxYsXY7FYsFgsHD16lJKSkrLPH374oResvXT03NQgZN68eQwfPpx169bRtWtXs82pklGjRnH48OGyGUEBip6bGoz4W8wcW3KbQEeLMUipqTPHLA4dOsTBgwe1GDWBS02dOWbxzTffEBkZSZcuXcw2xeNoMQYx/hAzJzMzky5duvj1aozqosUYxMTExPh8zJxg6S+C9qZq8N1sVkeOHCExMZGvv/7aazN9TER7UzW+68xZvXo1ERERfjH84g60GDU+68zJzMykc+fOLtdTBiJajBrAN505wdRfBC1GjRV7Z85///tfs83h+PHjZGdnB5UYtQNH44CvOHPmzp3LyJEjycnJITY21jQ7vIh24GgcmTp1qk84czIzM7n++uuDRYiAbqZqytGmTRufcOYEW38RtBg1LvCmM+f06dO89NJLfP3112VxdX755Rd++umnoBOj7jNqXGJbZrV27VpuvPFGp+MlJSVu6VMWFBQQHR2NiBAaGsp1111HQkIC//73vzl8+DBNmjSp9TX8hAwtRk2FuHLm7N+/nyeffJKRI0cyaNAgt1ynXr165OXllX0ODw+nqKiI0NBQ2rdvz+9+9zt69uxJampqIPchMxCNpgJ27twp4eHh8t5778nFixflL3/5i0RERAggzz33nNuu06JFCwEqfIWHhwsgf/vb39x2TR8kXdeMmkr505/+xCeffEKdOnU4ePBg2WLk1NRUsrKy3HKNnj17VvpdYWFhXHvttWzcuNGn5s66mQz/S9Wj8RpHjhzhwIEDnDhxgpCQEEpLS8uOff/995SWlhISUnsfYGJiotP322OxWJg9e3YgCxHQ3lSNCwoLC3n33Xdp2bIlCxcuBHASyvnz58nOznbL9Ro3blxh/ozQ0FBeeeUV2rZt65Zr+TK6ZtQ4cPDgQW6++WYOHDhAZT2YkJAQvvvuO1q1alXrazZq1MjltcLCwkhKSuLZZ5+t9TX8AV0zahy4+uqr+dOf/kRISEilzcKwsDC+++47t1yzcePGLrMLiwhz5swJ2KxT5dFi1DgxduxYvvnmG2JjYyvMAFxYWMi6devccr34+HinZnBoaCjPP/88nTp1css1/AHtTdVUyL59++jTpw8HDhygqKjI6XhERATnzp2rdc21fft2UlJSyj6HhYVx9dVXs337dq9ELPcR9ERxTcW0aNGC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\n",
"text/plain": [
"<PIL.PngImagePlugin.PngImageFile image mode=RGBA size=227x423 at 0x7F4C8CC98370>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"hnp.draw_graph(homomorphic_model).show()"
]
},
{
"cell_type": "markdown",
"id": "759bc39c",
"metadata": {},
"source": [
"### It's time to compile the function to its homomorphic equivalent"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "5c62c8b2",
"metadata": {},
"outputs": [],
"source": [
"engine = hnp.compile_numpy_function(\n",
" infer,\n",
" {\"x_0\": hnp.EncryptedScalar(hnp.Integer(input_bits, is_signed=False))},\n",
" iter(inputset),\n",
")"
]
},
{
"cell_type": "markdown",
"id": "2d6865f7",
"metadata": {},
"source": [
"### Finally, let's make homomorphic inference"
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "29374aa5",
"metadata": {},
"outputs": [],
"source": [
"homomorphic_predictions = []\n",
"for x_i in map(lambda x_i: int(x_i[0]), x_q):\n",
" inference = QuantizedArray(engine.run(x_i), y_q.parameters)\n",
" homomorphic_predictions.append(inference.dequantize())\n",
"homomorphic_predictions = np.array(homomorphic_predictions, dtype=np.float32)"
]
},
{
"cell_type": "markdown",
"id": "420b654c",
"metadata": {},
"source": [
"### And visualize it"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "1fc3156e",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"ax.plot(inputs, homomorphic_predictions, color=\"green\")\n",
"display(fig)"
]
},
{
"cell_type": "markdown",
"id": "1692814f",
"metadata": {},
"source": [
"### Enjoy!"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}