{ "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": [ "
" ] }, "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": [ "
" ] }, "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.2335143\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": [ "
min(x)
min(x)
max(x)
max(x)
Map
to 0
Map...
Map
to 1
Map...
Distance
Between
Consecutive
Values
Distan...
Map
to 2
Map...
Map
to 3
Map...
(when n = 2)
(when n = 2)
0
0
= 1 / scale
= 1 / q
= 1 / scale...
x = (x   + zp  ) / q
x = (x   + zp  ) / q
q
q
x
x
x
x
zero point
zp = 2
zero point...
Viewer does not support full SVG 1.1
" ], "text/plain": [ "" ] }, "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": "iVBORw0KGgoAAAANSUhEUgAAAXcAAAD4CAYAAAAXUaZHAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/MnkTPAAAACXBIWXMAAAsTAAALEwEAmpwYAAAnKUlEQVR4nO3dd3hUVf7H8fehCCglIogsiNhX13URIwaV3hEElPYDpIgCghqCKIgisa0UMcZdUFhxEZUuXSwIIohESRCluqCAgkgJCaTX8/tjbnSICQmQcCczn9fzzDN3zr0z+Wae4cPJuWfONdZaRETEv5RyuwARESl6CncRET+kcBcR8UMKdxERP6RwFxHxQ2XcLgCgWrVqtm7dum6XISJSosTExByz1lbPa59PhHvdunWJjo52uwwRkRLFGLM/v30alhER8UMKdxERP6RwFxHxQwp3ERE/pHAXEfFDCncRET+kcBcR8UMKdxERFyQmZnLHHSPZtOmXYnl9hbuIyHm2fn0WtWr1Y+PGyUyevLJYfobCXUTkPElMhEceyaJx4wGcPDmb/v3/ydy5g4vlZ/nE8gMiIv4qPT2dRYsWERWVwLvvwvHjq4F5PPPMC7zwwlPF9nMV7iIixSQjI4N77+3Jhx8uPqX9ueee49lnnynWn61wFxEpBpmZmTRu3IuoqMUYM5lHHulBaChUqVKOatWqFfvPV7iLiBSxAwcyadiwDwcOLKRWrVdZvjyMW245vzXohKqISBGxFt5+O4urr+7HgQPzaN9+Evv2nf9gB4W7iEiR2LcP2rTJYuDAAaSnz2bEiJf58MORlHFpfETDMiIiZ8lay65d/+PddzN57TXIyHgFeJcXXniRZ54Z7WptCncRkbOQkZFBhw7/x6effnBK+3PPPcczzzztUlV/ULiLiJyhlJRMGjTozbZtH1C+/DM8+ODNNGoENWpcSpMmTdwuD1C4i4ickW++yaRNmz7Exy/gH/94lU8+CaNGDber+jOdUBURKYSUFHjyySxCQvoRHz+Pvn0nsWWLbwY7KNxFRAq0fj384x9ZTJrUH2tnM27ceN55Z6Rnp7XuFpcPhbuISD5OnoRhw6Bx4ywOHRoIvMdLDRsSPu5JzwHWQlgYhIe7WWaeFO4iIrlkZGTQokVfgoIqM3VqZcqUqUxi4js8HxLCmI0bPYGeE+yRkRAf73M9eJ1QFRHx8ttvGdx+ey9+/nkhVar0o127qtSsCfXq1aPv/ff/EeiRkZ4nhIZCRAQY427huRjrA//bBAcH2+joaLfLEJEAZi3MnZvJgAG9SUubT+vWr7JsWRjlyuVxYCmvQY/sbNeC3RgTY60NzmufhmVEJOD9+it06ZJFr159SUubz4gRk/jkk3yCPSzs1LacIRofo3AXkYBlLcyYATfckMXy5f2BOfzzn+OZPHlk3gfnDMmEhnp67KGhnsc+GPAacxeRgPTTTzBoEKxenU2NGgM5efI9XnrpJZ56alTeTzAGgoJOHWOPiPDsCwrSmHteNOYuIufE2lPDNfdjL6mpGbRr9xTr1m3DGKhdO5b9+6N5/vnnGTt2bJH+rOJ2zmPuxph9xpitxpgtxphop62qMWaVMWa3c3+x026MMa8bY/YYY743xtQvul9FRCSX8PBTh0VOM/d8y5YMatXqxdq1k6lUKZabb47nsstKM3ny5MIFO/w5yH2sx57jTMbcm1lr63n9LzEaWG2tvRZY7TwGaAdc69wGAW8UVbEiIqew1jPH3HvcO4+55+npMG5cJvXr9+H48YX07v0qcXGb2Lw5iqioKEaMGOHqr1EczmXMvRPQ1Nl+B1gLjHLaZ1nPeE+UMSbIGFPTWnvoXAoVEfkT73HvXHPP08aP5+XnnmPLloOsWwdxcbuBLxg3bhLh4WH5vqS/KNSYuzFmLxAHWGCatXa6MSbeWhvk7DdAnLU2yBizAhhvrf3S2bcaGGWtjc71moPw9OypU6fOrfv37y/CX0tEAkquuefpqal0ubcbK1cuB2pSqpShatXSjBkTRljuqYwl2OnG3Avbc7/LWnvQGHMpsMoYs8t7p7XWGmPO6MystXY6MB08J1TP5LkiIr/LNfc8HWh+5W1sOLQVmMqgQQ8zcSJUqeJaha4o1Ji7tfagc38EWAw0AA4bY2oCOPdHnMMPApd7Pb220yYiUrRyzT0/diSN6ys3ZsOhrVQr/zJrVg9h2rTAC3YoRLgbYy4yxlTK2QZaA9uAZUA/57B+wFJnexnQ15k1EwKc0Hi7iBQLr7nnS5pM5PI6vdh3ch3Naw9j/4gsmjX3zZks50NhhmVqAIs9w+qUAWZbaz82xmwC5htjBgL7ge7O8SuB9sAeIBkYUORVi4g4jg4L59FHM5h3bx/gA8LCInh1cqjPTlE8XwoMd2vtT8A/8miPBVrk0W6BYUVSnYhIHtLS0li8eAmff57E7NmQlPQhsIjx419h1KjhbpfnE7T8gIiUKGlpaXTo0JXPPltxSvv48eMZNepxl6ryPQp3ESkxUlPTCQnpznffraBs2dcZPboTAwZAxYoVqF69utvl+RSFu4iUCDt3ZtC4cU+OHVvGddf9m5Urh3H11W5X5bu05K+I+LTMTJgwIYObburFsWOL6dnzdXbtUrAXROEuIj5r61YICclk9Og+ZGcvZNy4V5kz59FAnwhTKBqWERGfYq1lx449TJ2axbRpUKbM88B8Jk6cxBNP+M/SAcVN4S4iPiMtLY2WLbvx5ZfLf2/LyvLMhHniiTyujiT5UriLiE+Ii0vnttt68OOPy6lceRwPP/xX6tWDv/zlLzRu3Njt8kochbuIuO6TTzK4996eJCcvpVGjf7NixTAqV3a7qpJNJ1RFxDXx8fDAAxm0bduL5OTFPPbY66xbp2AvCuq5i4grliyBhx/O5PDhPsBCJk6M4IknHnW7LL+hnruInFeHD0P37tClSyapqfdj7XxeeeUVnnhiuNul+RX13EXkvEhNTaNZswF8/fVHWAsXXJBJfHwiEyZM4PHHtSZMUVO4i0ix27MnnTvv7M6RI8uoXr0fbdpUoWpVaNCgAb1793a7PL+kcBeRYpOdDVOnZjB8eE+yspbRtesU5s4dSunSblfm/xTuIlKkUlJSGDJkCFFR33LgACQnJwD7GDfudcLDh7pdXsBQuItIkUlNTaVz5y6sWvUpxtxN6dJlqV8fHn00nP79+xX8AlJkFO4iUiTS0tJo1eo+vvzyE2AGnTs/wJQpULOm25UFJoW7iJyzEyfSCA7uyp49K6lUaTr//e8D3Hef21UFNs1zF5Fz8sUX6dSu3Z09e1YQEvIG+/Y9pGD3AQp3ETkriYkwbFgGTZv2JDFxGUOH/puNG4dQtarblQloWEZEzkBKSgrjx48nOvow69ZBYuJOYB2TJr3OyJHD3C5PvCjcRaRQUlNTufvuznz++SrgUkqXhurVyzBu3L8ZNkzB7msU7iJSoNTUVBo27MKWLZ9izAxGj36AZ5+F8uXdrkzyo3AXkdPavz+NkJCu/Pbbx1x++X9YtuwB6tVzuyopiE6oisiprP397q230rnmmm789tuHdO78Bj/++KCCvYRQuIvIH8LDISyMfXstrVtn8NBDPcjMXM6zjbuxePEQypZ1u0ApLA3LiAgAqSkpLPl6Eys+TmfBv2eQxXJgGZHAY7f8xdOVN8btMqWQFO4iQmpqKi1bdWbDhk89DVmfYYAI4LHQUIiIULCXMBqWEQlwCQmp3HxzFzZsWMWFF77B5Fd+5EfgN2A4KNhLKIW7SADbuDGNWrW6snv3xwQH/4e9Pw1mxC+vcxVwac5BYWG/n2SVkkPhLhKAUlLgiSfSueOObiQkfMjDD09j0zcPcOnLYRAZCaGhnitthIZ6HivgSxyNuYsEmPXr4YEHMtizpwewnEmTpjBy5CDPzqAgT6DnDMVERPzRrqGZEsVYH/jfODg42EZHR7tdhojfys7O5vvv9zJxYjZz5lguvHAMyckf8K9//YtHHnnk1INzz4rRLBmfZYyJsdYG57VPPXeRkuAcAjc1NZW77upMTMwnv7clJ8Nrr73252CHP7+ugr1EKnS4G2NKA9HAQWttB2PMlcBc4BIgBrjfWptujCkHzAJuBWKBHtbafUVeuUigCA+H+Pg/hkqs9YyBBwV59p3GgQOp3H57F3799VOqV3+OYcOu5pproE6dOjRq1Og8FC9uOZOeeyiwE6jsPJ4ARFhr5xpj3gQGAm8493HW2muMMT2d43oUYc0igcNaT7BHRnoeR0R4gj3npGeuHry1ltTUVKyF+fMzGDy4F+npH9Ox41ssWDCQcuXc+TXEBdbaAm9AbWA10BxYARjgGFDG2d8Q+MTZ/gRo6GyXcY4zp3v9W2+91YpIPrKzrQ0NtdYT5Z5baKin3UtiYqJt3bq1BU65jR07zZWypfgB0TafXC1sz/014EmgkvP4EiDeWpvpPD4A1HK2awG/OP9xZBpjTjjHH/N+QWPMIGAQeP5EFJF85Mxayem9w5++WJScnEzHjh1Zu/YLLrjgSbKzq9K6NQwbVo/27du4ULS4rcBwN8Z0AI5Ya2OMMU2L6gdba6cD08EzW6aoXlfE7+SMsXsLC/s94FNSUmjduhMbNqwFZhES0oe33oJrr3WjWPEVhfkS053APcaYfXhOoDYHIoEgY0zOfw61gYPO9kHgcgBnfxU8J1ZF5EzlBHs+XyxKSkzhlls6s2HDasqXn8mbb/bh888V7FKInru19ingKQCn5z7SWtvbGLMA6Ion8PsBS52nLHMeb3T2r3HGhkTkTBmT7xeLvk2pS7Pa93HixCpuvnkGH37Yl9q13S1XfMe5zHMfBcw1xrwIfAvMcNpnAO8aY/YAx4Ge51aiSIALDz9lVkx6huGlyuN54fWuWPsRDz30FtOmDdB0dDnFGYW7tXYtsNbZ/glokMcxqUC3IqhNRBxJyckMHjyYTZu28/PPkJoaD+zjlVem8fjjA90uT3yQvqEq4uOSk5Np374j69d/gbXtKF++NA0a1OGxx16kd+/ebpcnPkrhLuLDUlJSaNy4EzExa4F3eeih3kyaBFWquF2Z+DqFu4iPOnw4leDgzhw4sJpLL53J3Lm9adbM7aqkpNB67iI+aNGiVK64ogsHDqyiTZsZ7N3bV8EuZ0ThLuJDjh6FHj3SuO+++0hL+5inn/4PH388gAsvdLsyKWk0LCPisqSkJCZMmEhU1DHWr4e0tO+BL5kyZRpDh2omjJwdhbuIi5KTk2nduiNffbUWuIQyZaBatbK89NJ0HnroIbfLkxJM4S7ikqSkFIKD72HXri8oW/ZdJkzozWOPQenSblcm/kDhLuKC7dtTadSoE3Fxa7jhhndYvrw3V1/tdlXiT3RCVeQ8ysyEl19O5eabOxMX9xn9+7/N9u33K9ilyKnnLnKebN0KAwakERNzH/AJkyfPYMSI/m6XJX5K4S5SjFJSUli8eAULFqSxbBmUKTMHWMmbb05j8OAH3C5P/JjCXaSYJCcn07hxB2JiPv+9LSPDMHXqVAYPHuRiZRIIFO4ixeDYsRTq17+HX375gosvfouJE5vQtClUqlSJGjVquF2eBACFu0gRSEtL45tvviE7O5voaMszz/yT1NQ1tGjxDosW3U/lym5XKIFG4S5yjhISEmjbti1fffWVV6th9Oi3efnl+12rSwKbwl3kHCQmJtK+fXuior6mcuU3SEy8nu7dYdSomtSr91e3y5MApnAXOUtJSUm0anU3X3+9EWvncOWV3ZgxA2691e3KRPQlJpGzkpSUzG23dSAq6ktKlXqPl17qxqZNCnbxHeq5i5yhH35I5s47OxIbu45rrpnFsmU9ueEGt6sSOZV67iIFsNaSnp5Oamo6r76awN/+1pnY2M/p02cmu3b1VrCLT1LPXeQ0EhIS6Ny5M2vWrPFqNUya9DYjR2omjPguhbtIPhITE2nXrj0bN26kdOlRlC1bmbvvhkGDbqN161ZgLRjzxxNyPxZxkcJdJA9JSUk0aXI3mzdvBOZw773dmDIFLrvMOSA8HOLjISLCE+jWQlgYBAV59om4TGPuIrkcP57MjTd2YPPmL6lS5X0WLuzGBx94Bbu1nmCPjPQEek6wR0Z62q11sXoRD/XcRbysWZNMx44dSU5ex113zWLp0h5UrZrrIGM8PXbwBHpkpGc7NPSPnryIy9RzFwESE2Ho0BRatOhEcvLnjBw5k/Xre/852HN4B3wOBbv4EPXcJWAlJCQwaNAgYmJ2s38/pKfHAvt54423GTKkgJkwOUMx3sLCFPDiM9Rzl4CUmJhIq1btmDdvAbt3X0rZspdxxx03MXfuHIYM6X/6J3uPsYeGQna25957DF7EZeq5S8BJSkri9tvvZseOKEqVmsOYMd145hkoX76QL2CMZ1aM9xh7zhBNUJB67uITjPWBXkZwcLCNjo52uwwJAD/9lExIyN0cPbqOunVns3hxD+rVO8sX0zx3cZkxJsZaG5zXPg3LSECwFqZNS+b66zty9Og6unefxf/+dw7BDn8OcgW7+BCFu/i9ffugVasUhgzpTGbm50yYMJN583pTtqzblYkUH425i19KSEhg0qRX+OKLOL76CrKzYzBmIzNm/JcBA7QmjPi/AsPdGFMeWAeUc45faK0dZ4y5EpgLXALEAPdba9ONMeWAWcCtQCzQw1q7r5jqF/mTxMREmjZtz+bNG4AgypaFSy4px8SJb9O/fz+3yxM5LwozLJMGNLfW/gOoB7Q1xoQAE4AIa+01QBww0Dl+IBDntEc4x4mcF/HxSfz97541YSpWnMesWcdJSzvOkSOH6N+/v9vliZw3BfbcrWc6TaLzsKxzs0BzoJfT/g4QDrwBdHK2ARYC/zbGGOsL03Kk5Ms1IyU+Lo5333uPlJQUDh6EGTOWk5T0FSEhs1mypBs1arhYq4iLCjXmbowpjWfo5RpgCvAjEG+tzXQOOQDUcrZrAb8AWGszjTEn8AzdHMv1moOAQQB16tQ5t99CAkOulRjj4+Jo9de/En3kiNdB5QkLe5dXX+3hUpEivqFQs2WstVnW2npAbaABcM6XdbfWTrfWBltrg6tXr36uLyf+LtdKjCfi42lzww1sOXKUv1w0E0iiX78kDh8+wauv9irgxUT83xnNlrHWxhtjPgcaAkHGmDJO7702cNA57CBwOXDAGFMGqILnxKrI2fP6FmhCZCStI/9FNJDNYspdeg+f/cfQooW7JYr4kgJ77saY6saYIGe7AtAK2Al8DnR1DusHLHW2lzmPcfav0Xi7FAljSHj+eUII4hsM2cxjeOg9bN2qYBfJrTA995rAO864eylgvrV2hTFmBzDXGPMi8C0wwzl+BvCuMWYPcBzoWQx1SwDatzeB2//WhCMkUJtJLOAVQvgSLowA9O1QEW+FmS3zPXBLHu0/4Rl/z92eCnQrkuokoKWmphITE0N2tmXN6mxeeuFpMrK/576rn+T9bcMpN3r/HxfK0FK7IqfQN1TFJ8XHx9O6dWs2bdrk1VqK8Xd0Z9SX/9RKjCIFULiLzzl58iRt27Zl8+YtVKgwjczMq+jfHx57rDY3/e36P4I8J+AV7CJ/onAXn5KQkEDTpm3ZsiUGaxfSoEEn3noLrrkmnyco2EXypHAXnxEfn0D9+u3Yu/cbypefT2RkJx58EEpp7VKRM6Zwl/OjgAtbbNqUSIsWd5OQEEX9+nNZuvReatd2oU4RP6E+kRS/8PBTry1qLXb4cLKefZaUlCyefjqB22/vQELCBh599H2io7sq2EXOkXruUry8lw0AiIggfuhQOr75Jl8CvPACAMaU4s0332PwYK0JI1IUFO5SvLynLEZGciIyklYYNpsyYEdQqVJFOnaEBx64gxb6mqlIkdEFsuX8sJaTpUrRkErsIAVYyKBBnZg4EapUcbs4kZLpdBfIVs9dip+1HHhwBMFczWH2U4NI5t73C03ftJrKKFJMdEJVipe1zL/731z99kYOs4+OHWbz09C9NP3g0VNPsopIkVLPXYrN0aMwbFgyCz76ANjESy/NZsyYbmC7QtkMLRsgUowU7lKk4uPjGTJkCNHRe9m/HzIzj2DMz7wz813u7+vMhNGyASLFTuEuRebEiRM0a9aG7777FmubU6WK4eabL2H48Mnce++9px6sYBcpVgp3KRLx8SepX78te/du5oILFvLyy50IDYXSpd2uTCQwKdzlrMTGxjJmzBhiY2NJTIT163eSnPwDN900nyVLOnH11W5XKBLYFO5yxo4fP07Lli3ZsWMHF198LUeOgDEX8PDDC5gypYtGXER8gMJdzkhcXBytWrVi+/YdXHnlUv73v7bccw9MnQq1arldnYjkULhLocXHx9OqVWu++24b1i4mLq4tc+dC9+46PyriaxTuUignTpzgzjvbsHPnd1i7iD592hMRAdWquV2ZiORF4S4FOnToJLfe2pZDhzZzySUfMGtWB9q3d7sqETkdhbv8SVxcHO+//z5paWns3g0zZy4kLS2aNm3mM3/+PVSu7HaFIlIQhbuc4vjx47Ro0YItW7b83mZMeZ5/fi5jx3ZxrzAROSMKd/ndHzNhdnLxxSs4caIxjz0G48ZdQFBQObfLE5EzoHAXwDMTplmz1mzduo3s7CVcfnk7Vq2CW291uzIRORta8tdf5V5K9zRL68bHn+DWW9vw3XffUarUB7z4YjuioxXsIiWZeu7+KDzcc93SnJUXrfWsnR4U5NnnZfv2k9x1V1vi4zdz/fULWby4Azfc4ELNIlKk1HP3N94XpM65GEZYmOdxfDypKSlERUWxYcNGRo78iptvbkd8fDQDB85n+/ZOCnYRP6Geu7/JdUFqIiM926GhHB87lhZ33JFrJkxppk6dx8MPayaMiD/RBbL9lbVQ6o8/zOJiY2nRshVbt24HplCuXG0GD4YHH7yCG274q3t1ishZ0wWyA03OUIwjHrjz2pvZFXcUa5fQpUs7pkyBmjVdq1BEipnG3P2N9xh7aCiHD8Vx40XXsfP4Eapc8C4LF7Rl0SIFu4i/U8/d3xjjmRUTGsqn7Z6j05VtSU3dS7Pa/2Rhr71U7arlG0UCgcLdDyWODGfk4yeZ1rYdEM3YsfN5/rnOWpdXJIAo3P1EbGwsHTp0ICoq6vc2Y0oza9Y8+vTRTBiRQFNguBtjLgdmATUAC0y31kYaY6oC84C6wD6gu7U2zhhjgEigPZAM9LfWbi6e8gU8a8I0b96Kbdt2AKO45JLy3HMP9O3blKZNm7pdnoi4oDA990zgcWvtZmNMJSDGGLMK6A+sttaON8aMBkYDo4B2wLXO7XbgDedeikF8fDzBwa346aftlCq1hKeeasezz0L58m5XJiJuKjDcrbWHgEPOdoIxZidQC+gENHUOewdYiyfcOwGzrGcCfZQxJsgYU9N5HTlHR44coWfPnuzevZusLDh2LJGMjCSuumoRH3zQjnr13K5QRHzBGU2FNMbUBW4BvgZqeAX2b3iGbcAT/L94Pe2A05b7tQYZY6KNMdFHjx4907oD0tGjR2nevDlRUVFccUVLYmNbk5V1HwMHfsQPP3RQsIvI7wp9QtUYUxH4ABhurT1pvGZeWGutMeaMvupqrZ0OTAfPN1TP5LmB6NixY7Ro0YI9e37kpps+ZMOG5jRqBG+9Bddd53Z1IuJrCtVzN8aUxRPs71trFznNh40xNZ39NYEjTvtB4HKvp9d22uQsxcbG0rJlS3bt2o0xy/nhh+ZMmQJr1yrYRSRvBYa7M/tlBrDTWvuq165lQD9nux+w1Ku9r/EIAU5ovP3sxcXF0ahRK77/fhcZGUto2rQl27fD0KGnLB0jInKKwgzL3AncD2w1xmxx2sYA44H5xpiBwH6gu7NvJZ5pkHvwTIUcUJQFB5KjR+OpV68Vv/66nYoVlzJ1ahv69NF3kUSkYIWZLfMlkF+ctMjjeAsMO8e6AtKRI0cYO3Ys8fHxxMXBunVbSUvbw113LWbhwrbUqFHwa4iIgL6h6jNyZsLs2bOHiy66kuPHoUyZcowZs4iXXrrb7fJEpIRRuPuAnJkwu3f/SPXqKzl4sDkDB8KkSXDxxW5XJyIlkcLdZSdOnKB585bs2LGbrKzllC3bnFWroGVLtysTkZJM8y1c1q1bGFu3biMrawnDh7dk2zYFu4icO/XcXXLsGPTo8Qlr1vyXSy4ZzYoVbQgJcbsqEfEXCvfzzFpYsACGDj1JbOxDVKt2A3v2jKNKFbcrExF/omGZ8+jXX6FLF+jRA4wZhTEHWL78bapU0RKOIlK01HM/D6yFQYPe5+23x5GdnUFQEBw79jMjRowgRGMxIlIMFO7F7KefoGPH99mx434qVqxP69Z/p3JluOyyyxg7dqzb5YmIn1K4F5OsLHj9dRg9eg7p6X25/vqmREevoGLFC90uTUQCgMK9GGzfDgMHwtdfzwf60LBhY1atWs5FFynYReT80AnVIpSeDs8/D7fcAjt2LKRUqV40anQnn366nIsuusjt8kQkgCjci8imTRAcDOPGwe23LyYl5f9o2DCElStXUrFiRbfLE5EAo2GZc5ScDL16LWbp0nlUqAB33JFFVNQSbrvtNj766CMFu4i4QuF+DtauhR493uPIkb5ceOFl1KpVmdhY6NChAzNnzqRSpUpulygiAUrhfhZOnIAnn4Tp02cD/bjllmZ8+eVyLrxQJ0xFxDco3M/AsmXLmDlzPatWQWJiEsZMo1GjxqxcuUzBLiI+ReFeSBERbzFixENAOYwpTfny0KxZG+bPn6+ZMCLicxTuBbAWhgz5L9OnD8KYtjz99GLGji3PBRe4XZmISP4U7qdx4AB07DiLLVsGUqVKK9asWUz9+lrkS0R8n+a55yE7G6ZNg2uvfY8tW/pz3XUtOHBgiYJdREoMhXsue/ZAixYwZMhsUlP70bBhM779dikVK1ZwuzQRkULTsIzj669jmDr1Z+bMgdKl92LMEzRp0pgVKzQTRkRKHoU7EB7+Fs8999DvjzMyoHHjxqxYsUIzYUSkRArocE9Lg+7d/8uyZYO44IK2vPjiy7RqZShVynDjjTdSpkxAvz0iUoIFbHpFRcF9973Dr78OpGbNVmzatJhatcp75j4a43Z5IiLnJOBOqCYlQVgYNGz4Hr/+OoB61a7nxz1ewR4WBuHhbpcpInJOAircV6+Gv/8dXnttNsb0o3Gty9lwbBcVxoz5I9gjIyE+3vNYRKSECohw37//BL16/UbLlr+RnPwepUrdT5Mmjfnohx1cGBrqCfRSpTz3oaEQEaGhGREp0Yz1gR5qcHCwjY6OLpbXHjbsLaZOHQJk/d7WqFEjPvroI89MGGs9wZ4jO1vBLiIlgjEmxlobnNc+vz2hevgwdOjwNtHRD1GxYiseeeRerrgCKlSoQNeuXf8I9rCwU58YFqaeu4iUeH4X7tbCe+/Bww+/Q1LSg1x7bRtiYpZQqVL5Px+YM8aeMxST8xgU8CJSovlVuP/8MwweDB9//B4wgDvuaMlnny2mQoU81oQxBoKCTh1jj4jw7AsKUrCLSInmF2Pu2dnw5pswahRkZMwmPf1+mjRpwocfrih46YDc89o1z11ESojTjbkXOFvGGPO2MeaIMWabV1tVY8wqY8xu5/5ip90YY143xuwxxnxvjKlfdL9G3n74AZo0gWHDoG7deWRk3O+sCVPIy97lDnIFu4j4gcJMhZwJtM3VNhpYba29FljtPAZoB1zr3AYBbxRNmXnr1286f/3rVXz11VVUr34VO3f25s4779SaMCIS8Aocc7fWrjPG1M3V3Alo6my/A6wFRjnts6xnrCfKGBNkjKlprT1UZBV7+dvfalGnzl00aAAVKsCll17KuHHjFOwiEvDO9oRqDa/A/g2o4WzXAn7xOu6A0/ancDfGDMLTu6dOnTpnVcSTT97Nk0/efVbPFRHxZ+f8DVWnl37GZ2WttdOttcHW2uDq1aufaxkiIuLlbMP9sDGmJoBzf8RpPwhc7nVcbadNRETOo7MN92VAP2e7H7DUq72vM2smBDhRXOPtIiKSvwLH3I0xc/CcPK1mjDkAjAPGA/ONMQOB/UB35/CVQHtgD5AMDCiGmkVEpACFmS3zf/nsapHHsRYYdq5FiYjIuQmIJX9FRAKNwl1ExA8p3EVE/JBPLBxmjDmK58Ssm6oBx1yu4Uyp5uJX0uoF1Xy++ELNV1hr8/yikE+Euy8wxkTnt7qar1LNxa+k1Quq+Xzx9Zo1LCMi4ocU7iIifkjh/ofpbhdwFlRz8Stp9YJqPl98umaNuYuI+CH13EVE/JDCXUTEDwVsuBtj9hljthpjthhjop22PK8N6zZjzPVOnTm3k8aY4caYcGPMQa/29i7X6dPX2z2DmicZY3Y5dS02xgQ57XWNMSle7/ebPlRzvp8FY8xTzvv8gzGmjQ/VPM+r3n3GmC1Ou+vvszHmcmPM58aYHcaY7caYUKfdpz/Pp7DWBuQN2AdUy9U2ERjtbI8GJrhdZx51l8Zz9asrgHBgpNs1edXWGKgPbCvoPcWzeuhHgAFCgK99qObWQBlne4JXzXW9j/Ox9znPzwJwI/AdUA64EvgRKO0LNefaPxl41lfeZ6AmUN/ZrgT8z3kvffrz7H0L2J57PjrhuSYszn1n90rJVwvgR2ut29/o/RNr7TrgeK7m/N7T36+3a62NAoJyLgBzPuVVs7X2U2ttpvMwCs9FZ3xGPu9zfjoBc621adbavXiW425QbMXl43Q1G2MMnmXD55zXok7DWnvIWrvZ2U4AduK5ZKhPf569BXK4W+BTY0yMcz1XyP/asL6kJ6f+I3jE+TPwbV8ZRsrlTK+362sewNMjy3GlMeZbY8wXxphGbhWVj7w+CyXhfW4EHLbW7vZq85n32RhTF7gF+JoS9HkO5HC/y1pbH2gHDDPGNPbeaT1/a/nUPFFjzAXAPcACp+kN4GqgHp6LkE92p7LC8cX39HSMMU8DmcD7TtMhoI619hZgBDDbGFPZrfpyKVGfhVz+j1M7LD7zPhtjKgIfAMOttSe99/n65zlgw91ae9C5PwIsxvOnan7XhvUV7YDN1trDANbaw9baLGttNvAfXPhzuxBK5PV2jTH9gQ5Ab+cfMc7QRqyzHYNn/Po614r0cprPgq+/z2WAe4F5OW2+8j4bY8riCfb3rbWLnOYS83kOyHA3xlxkjKmUs43nBNo28r82rK84pYeTa0yvC57fwdeUuOvtGmPaAk8C91hrk73aqxtjSjvbVwHXAj+5U+WpTvNZWAb0NMaUM8Zciafmb853fafREthlrT2Q0+AL77NzHmAGsNNa+6rXrpLzeXb7jK4bN+AqPDMIvgO2A0877ZcAq4HdwGdAVbdr9ar5IiAWqOLV9i6wFfgez4erpss1zsHzJ3UGnjHHgfm9p3hmFUzB0yvbCgT7UM178IyfbnFubzrH3ud8XrYAm4GOPlRzvp8F4Gnnff4BaOcrNTvtM4EhuY51/X0G7sIz5PK91+egva9/nr1vWn5ARMQPBeSwjIiIv1O4i4j4IYW7iIgfUriLiPghhbuIiB9SuIuI+CGFu4iIH/p/oA139txnoBoAAAAASUVORK5CYII=\n", "text/plain": [ "
" ] }, "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 homomorphic equivalent" ] }, { "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", "circuit = 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": "c62af039", "metadata": {}, "source": [ "### Here are some representations of the fhe circuit" ] }, { "cell_type": "code", "execution_count": 21, "id": "0c533af6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "%0 = Constant(1) # ClearScalar>\n", "%1 = x_0 # EncryptedScalar>\n", "%2 = Constant(15) # ClearScalar>\n", "%3 = Add(1, 2) # EncryptedScalar>\n", "%4 = Mul(3, 0) # EncryptedScalar>\n", "%5 = TLU(4) # EncryptedScalar>\n", "return(%5)\n", "\n" ] } ], "source": [ "print(circuit)" ] }, { "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": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from PIL import Image\n", "file = Image.open(circuit.draw())\n", "file.show()\n", "file.close()" ] }, { "cell_type": "markdown", "id": "46753da7", "metadata": {}, "source": [ "### Finally, let's make homomorphic inference" ] }, { "cell_type": "code", "execution_count": 23, "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(circuit.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": 24, "id": "92c7f2f5", "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "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 }