mirror of
https://github.com/zama-ai/concrete.git
synced 2026-02-08 19:44:57 -05:00
791 lines
102 KiB
Plaintext
791 lines
102 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "b760a0f6",
|
||
"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": "253288cf",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Let's start by importing some libraries to develop our linear regression model"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"id": "6200ab62",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import numpy as np"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "f43e2387",
|
||
"metadata": {},
|
||
"source": [
|
||
"### And some helpers for visualization"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"id": "d104c8df",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"%matplotlib inline\n",
|
||
"\n",
|
||
"import matplotlib.pyplot as plt\n",
|
||
"from IPython.display import display"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "53e676b8",
|
||
"metadata": {},
|
||
"source": [
|
||
"### We need an inputset, a handcrafted one for simplicity"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"id": "d451e829",
|
||
"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": "75f4fdb7",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Let's visualize our inputset to get a grasp of it"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"id": "2a124a62",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"plt.ioff()\n",
|
||
"fig, ax = plt.subplots(1)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"id": "edcd361b",
|
||
"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": "5c8310ab",
|
||
"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": "91d4a1da",
|
||
"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": "faa5247c",
|
||
"metadata": {},
|
||
"source": [
|
||
"### And create one"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 7,
|
||
"id": "682fb2d8",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"model = Model().fit(x, y)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "084fb296",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Time to make some predictions"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"id": "4953b03e",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"inputs = np.linspace(40, 210, 100).reshape(-1, 1)\n",
|
||
"predictions = model.evaluate(inputs)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "f28155cf",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Let's visualize our predictions to see how our model performs"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 9,
|
||
"id": "111574ed",
|
||
"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": "23852861",
|
||
"metadata": {},
|
||
"source": [
|
||
"### As a bonus let's inspect the model parameters"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"id": "7877cb2e",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"[[2.669915]]\n",
|
||
"-3.2335129\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"print(model.w)\n",
|
||
"print(model.b)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "de63118c",
|
||
"metadata": {},
|
||
"source": [
|
||
"They are floating point numbers and we can't directly work with them!"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "2d959640",
|
||
"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": "9da2e1a4",
|
||
"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=\"<mxfile host="app.diagrams.net" modified="2021-08-13T09:47:25.144Z" agent="5.0 (X11)" etag="5QhM0DGu1eUjmjeXuyFL" version="14.9.6" type="device"><diagram id="6rZNNX4_K12e_kCXuZoG" name="Page-1">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==</diagram></mxfile>\"><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": "45d12e7a",
|
||
"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": "2541cdb7",
|
||
"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": "ab82ae87",
|
||
"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": "c8b08ef4",
|
||
"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": "e2528092",
|
||
"metadata": {},
|
||
"source": [
|
||
"### And quantize our inputs"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 14,
|
||
"id": "affe644e",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"input_bits = 6\n",
|
||
"\n",
|
||
"x_q = QuantizedArray.of(inputs, input_bits)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "a5a50eb8",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Time to make quantized inference"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 15,
|
||
"id": "0fdfd3d9",
|
||
"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": "5fb15eb4",
|
||
"metadata": {},
|
||
"source": [
|
||
"### And visualize the results"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 16,
|
||
"id": "8076a406",
|
||
"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": "af6bc89e",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Now it's time to make the inference homomorphic"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 17,
|
||
"id": "cbda8067",
|
||
"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": "b8e95e3d",
|
||
"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": "c6e101ae",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Let's import the concrete numpy package now!"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 18,
|
||
"id": "4da7aed5",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import concrete.numpy as hnp"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 19,
|
||
"id": "d3816fa5",
|
||
"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": "01d67c28",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Let's compile our quantized inference function to it's operation graph for visualization"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 20,
|
||
"id": "81304aca",
|
||
"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",
|
||
" inputset,\n",
|
||
")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "c62af039",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Here are some representations of the operation graph"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 21,
|
||
"id": "0c533af6",
|
||
"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": "c1fc0f48",
|
||
"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 0x7FAE603FF970>"
|
||
]
|
||
},
|
||
"metadata": {},
|
||
"output_type": "display_data"
|
||
}
|
||
],
|
||
"source": [
|
||
"from PIL import Image\n",
|
||
"file = Image.open(hnp.draw_graph(homomorphic_model))\n",
|
||
"file.show()\n",
|
||
"file.close()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "de19a433",
|
||
"metadata": {},
|
||
"source": [
|
||
"### It's time to compile the function to its homomorphic equivalent"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 23,
|
||
"id": "cf89c63d",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"engine = hnp.compile_numpy_function(\n",
|
||
" infer,\n",
|
||
" {\"x_0\": hnp.EncryptedScalar(hnp.Integer(input_bits, is_signed=False))},\n",
|
||
" inputset,\n",
|
||
")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "46753da7",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Finally, let's make homomorphic inference"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "c0b246f7",
|
||
"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": "68f67b3f",
|
||
"metadata": {},
|
||
"source": [
|
||
"### And visualize it"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "92c7f2f5",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"ax.plot(inputs, homomorphic_predictions, color=\"green\")\n",
|
||
"display(fig)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "c18dbdd1",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Enjoy!"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"execution": {
|
||
"timeout": 10800
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 5
|
||
}
|