mirror of
https://github.com/zama-ai/concrete.git
synced 2026-02-09 03:55:04 -05:00
845 lines
113 KiB
Plaintext
845 lines
113 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"# Quantized Logistic Regression\n",
|
||
"\n",
|
||
"Currently, **concrete** only supports unsigned integers up to 7-bits. Nevertheless, we want to evaluate a logistic regression model with it. Luckily, we can make use of **quantization** to overcome this limitation!"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### Let's start by importing some libraries to develop our logistic regression model"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"source": [
|
||
"import numpy as np\n",
|
||
"import torch"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### And some helpers for visualization"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"source": [
|
||
"import matplotlib.pyplot as plt\n",
|
||
"from IPython.display import display"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### We need a dataset, a handcrafted one for simplicity"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"source": [
|
||
"x = torch.tensor([[1, 1], [1, 2], [2, 1], [4, 1], [3, 2], [4, 2]]).float()\n",
|
||
"y = torch.tensor([[0], [0], [0], [1], [1], [1]]).float()"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### Let's visualize our dataset to get a grasp of it"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"source": [
|
||
"plt.ioff()\n",
|
||
"fig, ax = plt.subplots(1)"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"source": [
|
||
"x_min, x_max = x[:, 0].min(), x[:, 0].max()\n",
|
||
"x_deviation = x_max - x_min\n",
|
||
"\n",
|
||
"y_min, y_max = x[:, 1].min(), x[:, 1].max()\n",
|
||
"y_deviation = y_max - y_min\n",
|
||
"\n",
|
||
"ax.set_xlim(x_min - (x_deviation / 10), x_max + (x_deviation / 10))\n",
|
||
"ax.set_ylim(y_min - (y_deviation / 10), y_max + (y_deviation / 10))\n",
|
||
"\n",
|
||
"ax.scatter(\n",
|
||
" np.array([x_i[0] for x_i, y_i in zip(x, y) if y_i == 0], dtype=np.float32),\n",
|
||
" np.array([x_i[1] for x_i, y_i in zip(x, y) if y_i == 0], dtype=np.float32),\n",
|
||
" marker=\"x\",\n",
|
||
" color=\"red\",\n",
|
||
")\n",
|
||
"ax.scatter(\n",
|
||
" np.array([x_i[0] for x_i, y_i in zip(x, y) if y_i == 1], dtype=np.float32),\n",
|
||
" np.array([x_i[1] for x_i, y_i in zip(x, y) if y_i == 1], dtype=np.float32),\n",
|
||
" marker=\"o\",\n",
|
||
" color=\"blue\",\n",
|
||
")\n",
|
||
"display(fig)"
|
||
],
|
||
"outputs": [
|
||
{
|
||
"output_type": "display_data",
|
||
"data": {
|
||
"text/plain": [
|
||
"<Figure size 432x288 with 1 Axes>"
|
||
],
|
||
"image/png": "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"
|
||
},
|
||
"metadata": {}
|
||
}
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### Now, we need a model so let's define it"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 6,
|
||
"source": [
|
||
"class Model(torch.nn.Module):\n",
|
||
" def __init__(self, n):\n",
|
||
" super(Model, self).__init__()\n",
|
||
" self.fc = torch.nn.Linear(n, 1)\n",
|
||
"\n",
|
||
" def forward(self, x):\n",
|
||
" output = torch.sigmoid(self.fc(x))\n",
|
||
" return output"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### And create one\n",
|
||
"\n",
|
||
"The main purpose of this tutorial is not to train a logistic regression model but to use it homomorphically. So we will not discuss about how the model is trained."
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 7,
|
||
"source": [
|
||
"model = Model(x.shape[1])\n",
|
||
"\n",
|
||
"optimizer = torch.optim.SGD(model.parameters(), lr=1)\n",
|
||
"criterion = torch.nn.BCELoss()\n",
|
||
"\n",
|
||
"epochs = 1501\n",
|
||
"for e in range(1, epochs + 1):\n",
|
||
" optimizer.zero_grad()\n",
|
||
"\n",
|
||
" out = model(x)\n",
|
||
" loss = criterion(out, y)\n",
|
||
"\n",
|
||
" loss.backward()\n",
|
||
" optimizer.step()\n",
|
||
"\n",
|
||
" if e % 100 == 1 or e == epochs:\n",
|
||
" print(\"Epoch:\", e, \"|\", \"Loss:\", loss.item())"
|
||
],
|
||
"outputs": [
|
||
{
|
||
"output_type": "stream",
|
||
"name": "stdout",
|
||
"text": [
|
||
"Epoch: 1 | Loss: 0.9475528597831726\n",
|
||
"Epoch: 101 | Loss: 0.13412582874298096\n",
|
||
"Epoch: 201 | Loss: 0.07946280390024185\n",
|
||
"Epoch: 301 | Loss: 0.05598355457186699\n",
|
||
"Epoch: 401 | Loss: 0.04308217763900757\n",
|
||
"Epoch: 501 | Loss: 0.034963589161634445\n",
|
||
"Epoch: 601 | Loss: 0.02939651347696781\n",
|
||
"Epoch: 701 | Loss: 0.025346478447318077\n",
|
||
"Epoch: 801 | Loss: 0.022270068526268005\n",
|
||
"Epoch: 901 | Loss: 0.019855139777064323\n",
|
||
"Epoch: 1001 | Loss: 0.01790979877114296\n",
|
||
"Epoch: 1101 | Loss: 0.016309652477502823\n",
|
||
"Epoch: 1201 | Loss: 0.014970536343753338\n",
|
||
"Epoch: 1301 | Loss: 0.013833633624017239\n",
|
||
"Epoch: 1401 | Loss: 0.012856445275247097\n",
|
||
"Epoch: 1501 | Loss: 0.012007634155452251\n"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### Time to make some predictions"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"source": [
|
||
"contour_plot_x_data = np.linspace(x_min - (x_deviation / 10), x_max + 2 * (x_deviation / 10), 250)\n",
|
||
"contour_plot_y_data = np.linspace(y_min - (y_deviation / 10), y_max + 2 * (y_deviation / 10), 250)\n",
|
||
"contour_plot_x_data, contour_plot_y_data = np.meshgrid(contour_plot_x_data, contour_plot_y_data)\n",
|
||
"\n",
|
||
"inputs = np.stack((contour_plot_x_data.flatten(), contour_plot_y_data.flatten()), axis=1)\n",
|
||
"predictions = model(torch.tensor(inputs).float()).detach().numpy()"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### Let's visualize our predictions to see how our model performs"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 9,
|
||
"source": [
|
||
"contour = ax.contourf(\n",
|
||
" contour_plot_x_data,\n",
|
||
" contour_plot_y_data,\n",
|
||
" predictions.round().reshape(contour_plot_x_data.shape),\n",
|
||
" cmap=\"gray\",\n",
|
||
" alpha=0.50,\n",
|
||
")\n",
|
||
"display(fig)"
|
||
],
|
||
"outputs": [
|
||
{
|
||
"output_type": "display_data",
|
||
"data": {
|
||
"text/plain": [
|
||
"<Figure size 432x288 with 1 Axes>"
|
||
],
|
||
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAXQAAAD4CAYAAAD8Zh1EAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/MnkTPAAAACXBIWXMAAAsTAAALEwEAmpwYAAAT40lEQVR4nO3df2xdZ33H8c+XxluYkyURQcyuw7I/6KbDDeVHRjpRbVnQltJBqilMo9tgrYYibV06tElD449WG39NaAjPFURRqbLescIEFasrWIdy6aKV1VNoC27syaprg22sBRzfkPg2P6773R/nGhzX9r22j+9z7nPfL8nKvec88fn0afLJ8XPOvdfcXQCA1ve60AEAANmg0AEgEhQ6AESCQgeASFDoABCJLaEO3NnZ6bt27Qp1eOTMlStXtGPHDm3btk033XRT6DhAbj3//PM/cvc3LrcvWKHv2rVLx48fD3V45MzQ0JCOHDmi22+/Xdu3bw8dB8itzs7O7620jyUX5EKSJJqfn9fExISmp6dDxwFaEoWO3BgbG1OxWNTVq1dDRwFaEoUOAJGg0AEgEhQ6AESCQkfuXLt2LXQEoCVR6MiVcrmsarWq4eHh0FGAlkOhI1eSJFFfX1/oGEBLotABIBIUOgBEgkJHLrm7xsfHQ8cAWgqFjtxJkkS9vb2qVCq8DQCwBvEX+tLPTOUzVFtCoVBQqVQKHQNoKXXfbdHM9kh6VNKbJLmkk+7eu2SMSeqVdKekiqR73P257OOu0dNPS1euSIcPS2ZpmT/1lLR1q3TwYOh0QGYGB6VSSbp4UdqxQzp0SNq3L3SquOVxzhs5Q69K+it3TyTdJuk+M0uWjHmfpLfUvo5J+lymKdfDPS3zgYG0xBfKfGAg3c6ZOiIxOCj190vlcvrHulxOnw8Ohk4Wr7zOed0zdHefljRde3zJzIYl3SxpaNGwuyQ96u4u6Vkz22lmXbXfG4ZZemYupSU+MJA+PnDgp2fsQARKJen69Ru3Xb+ebg99xhirvM75mtbQzWyvpHdIGliy62ZJE4ueT9a2Lf39x8zsrJmdnZubW2PUdVhc6gso85YxNTWl2dlZ7nap4+LFtW3HxuV1zhsudDPbJukrkj7m7j9ez8Hc/aS773f3/Z2dnev5Fms9YLrMstjC8gtyr7u7W729vXrllVdCR8m1HTvWth0bl9c5b6jQzaxDaZl/wd0fX2bIlKQ9i5731LaFs3jN/MAB6YEH0l8Xr6kj9173uvhvxNqoQ4ekjo4bt3V0pNuxOfI6543c5WKSPi9p2N0/vcKwJyT9uZl9UdIBSReDrp9L6bLK1q03rpkvLL9s3cqyC6KxsGabtzsuYpbXOW/kQ6LfI+nDkgbN7IXatk9IerMkufsJSV9TesviS0pvW7w386TrcfBgeia+UN4LpU6ZIzL79oUvk3aTxzlv5C6X/5K0agPW7m65L6tQmVpa3pQ5gEg1coYOBJMkiUZHR+Xu2rVrl7q6ukJHAnKLK07IvbGxMd4GAGgAhQ4AkaDQASASFDoARIJCR8sol8uhIwC5RqGjJbi7RkdHNTIyEjoKkFsUOlpGf39/6AhArlHoABAJCh0AIkGho6VUq1XW0YEVUOhoGYVCQX19fRoZGdH0dNg38wTyiEJHS0mShE8wAlZAoQNAJCh0AIgEhQ4AkaDQ0XKmpqY0OzvL3S7AEhQ6Wk53d7d6e3tDxwByh0IHgEhQ6AAQCQodACJBoaNlVatVXbp0KXQMIDcodLSkQqGgUqmkiYkJSh2oodDRstxdL7/8cugYQG5Q6AAQCQodACJBoQNAJCh0tDwujAKpuoVuZo+Y2Xkze3GF/TvMrN/MvmNm58zs3uxjAssbGxtTqVTSzMxM6ChAcI2coZ+SdMcq+++TNOTut0o6KOkfzOxnNh4NaMzU1FToCEAu1C10dz8j6cJqQyRtNzOTtK02tppNPABAo7Zk8D0ekvSEpB9I2i7p99391eUGmtkxScckaefOnRkcGgCwIIuLooclvSCpW9LbJT1kZj+/3EB3P+nu+919f2dnZwaHBqQLFy6oUqloeHiYi6Noa1kU+r2SHvfUS5LGJP1KBt8XaEihUFBfXx8fHo22l0Whf1/SeyXJzN4k6Zcl8XpsAGiyumvoZvaY0rtXdpvZpKQHJXVIkrufkPRJSafMbFCSSfq4u/9o0xIDAJZVt9Dd/e46+38g6bczSwQAWBdeKYooJEmi+fl5Xb58OXQUIBgKHdF45plnNDs7y8VRtC0KHdHo7u5WsVgMHQMIhkIHgEhQ6AAQCQodACJBoSM6lUpF09PToWMATUehIyoLF0bL5XLoKEDTUeiITrlc5tZFtCUKHQAiQaEDQCQodACIBIWOKM3Pz2toaIi7XdBWKHREJ0kSjY2NqVgs6urVq6HjAE1DoQNAJCh0AIgEhY6oXbt2LXQEoGkodESrXC6rWq1qeHg4dBSgKSh0RCtJEvX19YWOATQNhQ4AkaDQASASFDoARIJCR/TcnXdfRFug0BG1JEnU29vLh16gLVDoiF6hUFCpVAodA9h0FDoARIJCB4BI1C10M3vEzM6b2YurjDloZi+Y2Tkz+89sIwIAGtHIGfopSXestNPMdkr6rKQj7v5WSb+XSTIgY7Ozs9ztgqjVLXR3PyPpwipD/kDS4+7+/dr48xllAzLj7urt7eXNuhC1LNbQb5G0y8yeNrNvm9lHVhpoZsfM7KyZnZ2bm8vg0ACABVsy+h7vkvReSa+X9N9m9qy7jywd6O4nJZ2UpJ6eHs/g2ACAmiwKfVLSjLvPSZozszOSbpX0mkIHAGyeLJZc/k3S7Wa2xcx+TtIBSbwBNXJpfn5ely5dCh0D2BR1z9DN7DFJByXtNrNJSQ9K6pAkdz/h7sNm9u+SvivpVUkPu/uKtzgCoRQKBY2OjsrdtWvXLnV1dYWOBGSqbqG7+90NjPmUpE9lkgjYRGNjYxofH9fRo0dDRwEyxytFASASFDoARIJCR1viwihilMVti0BLOXfunPbu3StJuuWWW8KGATLEGTraTpIk6u/vDx0DyByFDgCRoNABIBIUOgBEgkJH26pWqxoZ4S2HEA8KHW2pUCior69PIyMj3MKIaFDoaFtJkoSOAGSKQgeASFDoABAJCh1t7/Lly6EjAJmg0NHWnnnmGc3Ozmp8fDx0FGDDKHS0te7ubhWLxdAxgExQ6AAQCQodACJBoaPtXbhwQZVKhRcYoeVR6Gh7hUJBpVJJExMTlDpaGoUOSHJ3vfzyy6FjABtCoQNAJCh0AIgEhQ4AkaDQgUUmJye5MIqWRaEDNWNjYzp9+rRmZmZCRwHWhUIHFpmamgodAVi3uoVuZo+Y2Xkze7HOuF81s6qZfTC7eACARjVyhn5K0h2rDTCzmyT9vaT/yCATAGAd6ha6u5+RdKHOsOOSviLpfBahAABrt+E1dDO7WdLvSvpcA2OPmdlZMzs7Nze30UMDmVt4X5fh4eHQUYA1y+Ki6GckfdzdX6030N1Puvt+d9/f2dmZwaGBbBUKBfX19Wl8fJzbF9FytmTwPfZL+qKZSdJuSXeaWdXdv5rB9wYANGjDhe7uv7Tw2MxOSXqSMgeA5qtb6Gb2mKSDknab2aSkByV1SJK7n9jUdACAhtUtdHe/u9Fv5u73bCgNkBPz8/OamZnR9u3bQ0cBGsYrRYElkiRRf3+/KpWKxsfHQ8cBGkahA8soFAoqFouhYwBrQqEDQCQodACIBIUOrKJSqWh6ejp0DKAhFDqwgu7ubhWLRZXL5dBRgIZQ6MAqKHO0EgodACJBoQNAJCh0AIgEhQ7U4e4aGhribhfkHoUOrCJJEp0+fVqlUil0FKAuCh0AIkGhA0AkKHQAiASFDjSIzxhF3lHoQAPOnTunarWqkZGR0FGAFVHoQAOSJFFvb2/oGMCqKHQAiASFDgCRoNABIBIUOrAG1WqVD45GblHoQIMKhYJ6e3v5FCPkFoUOrEGhUOB9XZBbFDoARIJCB4BIUOjAOszOznJxFLlTt9DN7BEzO29mL66w/w/N7LtmNmhm3zKzW7OPCeSHu6u3t1fXrl0LHQW4QSNn6Kck3bHK/jFJv+Hu+yR9UtLJDHIBANZoS70B7n7GzPausv9bi54+K6kng1wAgDXKeg39TyR9faWdZnbMzM6a2dm5ubmMDw0A7a3uGXqjzOw3lRb67SuNcfeTqi3J9PT0eFbHBkKYn58PHQG4QSaFbmZvk/SwpPe5+0wW3xPIs0KhoNHRUbm79uzZo+3bt4eOBGx8ycXM3izpcUkfdnfe/R9tY2xsjFeNIlfqnqGb2WOSDkrabWaTkh6U1CFJ7n5C0gOS3iDps2YmSVV3379ZgQEAy2vkLpe76+z/qKSPZpYIALAuvFIUACJBoQMbNDk5GToCIIlCBzbE3TU6OqqREe4HQHgUOrBB/f39oSMAkih0AIgGhQ4AkaDQASASFDqQgWq1yoVRBEehAxtUKBTU19enkZERXbp0KXQctDEKHchAkiShIwAUOgDEgkIHgEhQ6EBGxsfHNTExofHx8dBR0KYodCAj7q5isRg6BtoYhQ4AkaDQASASFDoARIJCBzJWqVR4gRGCoNCBDHV3d6tUKmlycpJSR9NR6EDGzp07x62LCIJCB4BIUOgAEAkKHQAiQaEDm2B+fp4Lo2g6Ch3IWJIkGhsb0+nTpzUzMxM6DtoIhQ5skqmpqdAR0GYodACIRPyF7r76c2SPOQeC2FJvgJk9Iun9ks67e2GZ/SapV9KdkiqS7nH357IOui5PPy1duSIdPiyZpcXy1FPS1q3SwYOh08WJOUebGByUSiXp4kVpxw7p0CFp376wmRo5Qz8l6Y5V9r9P0ltqX8ckfW7jsTLgnhbLwEBaKAvFMjCQbuesMXvM+Q0uXLigSqWi4eHh0FGQscFBqb9fKpfTP9blcvp8cDBsrrpn6O5+xsz2rjLkLkmPurtLetbMdppZl7tPZxVyXczSs0QpLZSBgfTxgQM/PXtEtpjzGxQKBfX19en+++8PHQUZK5Wk69dv3Hb9ero95Fl6FmvoN0uaWPR8srbtNczsmJmdNbOzc3NzGRy6jsUFs6ANi6WpmHO0gYsX17a9WZp6UdTdT7r7fnff39nZ2YwDpj/yL7awFIDNwZyjDezYsbbtzZJFoU9J2rPoeU9tW1iL128PHJAeeCD9dfH6LrLFnC/L3TU9HXYFEtk6dEjq6LhxW0dHuj2kLAr9CUkfsdRtki4GXz+X0h/xt269cf328OH0+datLAFsBub8NZIkUbFY1OzsLKUekX37pA98QNq5M/1jvXNn+jz0XS7mdc6azOwxSQcl7Zb0f5IelNQhSe5+onbb4kNK74SpSLrX3c/WO3BPT48fP358Q+Eb4n5jkSx9juwx569hZjp69Ki6urpCR0GL6+zs/La7719uXyN3udxdZ79Lum+d2Tbf0iJp82JpCuYcCCL+V4oCQJug0AEgEhQ60CRcGMVmo9CBJnB3FYtFPvACm4pCB5qkXC6HjoDIUegAEAkKHQAiQaEDQCQodKCJqtWqhoaGuNsFm4JCB5okSRKdPn1apVIpdBREikIHgEhQ6AAQibrvtrhpBzb7oaTvNfGQuyX9qInHy1KrZm/V3FLrZm/V3FLrZm927l909zcutyNYoTebmZ1d6S0n865Vs7dqbql1s7dqbql1s+cpN0suABAJCh0AItFOhX4ydIANaNXsrZpbat3srZpbat3sucndNmvoABC7djpDB4CoUegAEImoCt3MHjGz82b24gr7zcz+0cxeMrPvmtk7m51xJQ1kP2hmF83shdrXA83OuBwz22Nm3zSzITM7Z2Z/scyY3M17g7nzOudbzex/zOw7tex/u8yYnzWzL9XmfMDM9gaIujRTI7nvMbMfLprzj4bIuhIzu8nMnjezJ5fZF37O3T2aL0m/Lumdkl5cYf+dkr4uySTdJmkgdOY1ZD8o6cnQOZfJ1SXpnbXH2yWNSEryPu8N5s7rnJukbbXHHZIGJN22ZMyfSTpRe/whSV9qkdz3SHoodNZV/hv+UtK/LPfnIg9zHtUZurufkXRhlSF3SXrUU89K2mlmXc1Jt7oGsueSu0+7+3O1x5ckDUu6ecmw3M17g7lzqTaPl2tPO2pfS+9uuEvSP9Uef1nSe83MmhRxWQ3mzi0z65H0O5IeXmFI8DmPqtAbcLOkiUXPJ9Uif4lrfq324+rXzeytocMsVfsR8x1Kz7wWy/W8r5Jbyumc1370f0HSeUnfcPcV59zdq5IuSnpDU0Muo4HcknS0tjT3ZTPb09yEq/qMpL+W9OoK+4PPebsVeit7Tul7ONwqqU/SV8PGuZGZbZP0FUkfc/cfh87TqDq5czvn7j7v7m+X1CPp3WZWCBypIQ3k7pe0193fJukb+ukZb1Bm9n5J593926GzrKbdCn1K0uJ/8Xtq23LP3X+88OOqu39NUoeZ7Q4cS5JkZh1KS/EL7v74MkNyOe/1cud5zhe4e1nSNyXdsWTXT+bczLZI2iFppqnhVrFSbnefcfertacPS3pXk6Ot5D2SjpjZuKQvSjpkZv+8ZEzwOW+3Qn9C0kdqd13cJumiu7fER8eY2S8srMeZ2buV/r8L/he0lunzkobd/dMrDMvdvDeSO8dz/kYz21l7/HpJvyXpf5cMe0LSH9cef1BSyWtX60JpJPeSaytHlF7bCM7d/8bde9x9r9ILniV3/6Mlw4LP+ZZmHmyzmdljSu9M2G1mk5IeVHrhRe5+QtLXlN5x8ZKkiqR7wyR9rQayf1DSn5pZVdIrkj4U+i9ozXskfVjSYG1tVJI+IenNUq7nvZHceZ3zLkn/ZGY3Kf1H5l/d/Ukz+ztJZ939CaX/WBXN7CWlF9s/FC7uTzSS+34zOyKpqjT3PcHSNiBvc85L/wEgEu225AIA0aLQASASFDoARIJCB4BIUOgAEAkKHQAiQaEDQCT+H+Ww0z5fuBGwAAAAAElFTkSuQmCC"
|
||
},
|
||
"metadata": {}
|
||
}
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### As a bonus let's inspect the model parameters"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"source": [
|
||
"w = np.array(model.fc.weight.flatten().tolist()).reshape((-1, 1))\n",
|
||
"b = model.fc.bias.flatten().tolist()[0]\n",
|
||
"\n",
|
||
"print(w)\n",
|
||
"print(b)"
|
||
],
|
||
"outputs": [
|
||
{
|
||
"output_type": "stream",
|
||
"name": "stdout",
|
||
"text": [
|
||
"[[4.53723335]\n",
|
||
" [2.37176466]]\n",
|
||
"-14.666179656982422\n"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"They are floating point numbers and we can't directly work with them!"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"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!"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 11,
|
||
"source": [
|
||
"from IPython.display import SVG\n",
|
||
"SVG(filename=\"figures/QuantizationVisualized.svg\")"
|
||
],
|
||
"outputs": [
|
||
{
|
||
"output_type": "execute_result",
|
||
"data": {
|
||
"text/plain": [
|
||
"<IPython.core.display.SVG object>"
|
||
],
|
||
"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>"
|
||
},
|
||
"metadata": {},
|
||
"execution_count": 11
|
||
}
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"If you want to learn more, head to https://intellabs.github.io/distiller/algo_quantization.html"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 12,
|
||
"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, input_parameters=None, output_parameters=None):\n",
|
||
" self.table = table\n",
|
||
" self.input_parameters = input_parameters\n",
|
||
" self.output_parameters = output_parameters\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)\n",
|
||
"\n",
|
||
" @staticmethod\n",
|
||
" def plain(f, input_parameters, output_bits):\n",
|
||
" n = input_parameters.n\n",
|
||
"\n",
|
||
" domain = np.array(range(2**n), dtype=np.uint)\n",
|
||
" inputs = QuantizedArray(domain, input_parameters).dequantize()\n",
|
||
"\n",
|
||
" outputs = f(inputs)\n",
|
||
" quantized_outputs = QuantizedArray.of(outputs, output_bits)\n",
|
||
"\n",
|
||
" table = quantized_outputs.values\n",
|
||
" output_parameters = quantized_outputs.parameters\n",
|
||
"\n",
|
||
" return QuantizedFunction(table, input_parameters, output_parameters)\n",
|
||
"\n",
|
||
" def apply(self, x):\n",
|
||
" assert x.parameters == self.input_parameters\n",
|
||
" return QuantizedArray(self.table[x.values], self.output_parameters)"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### Let's quantize our model parameters\n",
|
||
"\n",
|
||
"Since the parameters only consist of scalars, we can use a single bit quantization."
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 13,
|
||
"source": [
|
||
"parameter_bits = 1\n",
|
||
"\n",
|
||
"w_q = QuantizedArray.of(w, parameter_bits)\n",
|
||
"b_q = QuantizedArray.of(b, parameter_bits)"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### And quantize our inputs"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 14,
|
||
"source": [
|
||
"input_bits = 5\n",
|
||
"\n",
|
||
"x = inputs\n",
|
||
"x_q = QuantizedArray.of(inputs, input_bits)"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### Time to make quantized inference"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 15,
|
||
"source": [
|
||
"output_bits = 7\n",
|
||
"\n",
|
||
"intermediate = x @ w + b\n",
|
||
"intermediate_q = x_q.affine(w_q, b_q, intermediate.min(), intermediate.max(), output_bits)\n",
|
||
"\n",
|
||
"sigmoid = QuantizedFunction.plain(lambda x: 1 / (1 + np.exp(-x)), intermediate_q.parameters, output_bits)\n",
|
||
"y_q = sigmoid.apply(intermediate_q)\n",
|
||
"\n",
|
||
"quantized_predictions = y_q.dequantize()"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### And visualize the results"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 16,
|
||
"source": [
|
||
"for column in contour.collections:\n",
|
||
" plt.gca().collections.remove(column)\n",
|
||
" \n",
|
||
"contour = ax.contourf(\n",
|
||
" contour_plot_x_data,\n",
|
||
" contour_plot_y_data,\n",
|
||
" quantized_predictions.round().reshape(contour_plot_x_data.shape),\n",
|
||
" cmap=\"gray\",\n",
|
||
" alpha=0.50,\n",
|
||
")\n",
|
||
"display(fig)"
|
||
],
|
||
"outputs": [
|
||
{
|
||
"output_type": "display_data",
|
||
"data": {
|
||
"text/plain": [
|
||
"<Figure size 432x288 with 1 Axes>"
|
||
],
|
||
"image/png": "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"
|
||
},
|
||
"metadata": {}
|
||
}
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### Now it's time to make the inference homomorphic"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 17,
|
||
"source": [
|
||
"q_y = (2**output_bits - 1) / (intermediate.max() - intermediate.min())\n",
|
||
"zp_y = int(round(intermediate.min() * 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"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"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",
|
||
"```"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### Let's import the concrete numpy package now!"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"source": [
|
||
"import concrete.numpy as hnp"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 18,
|
||
"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 = intermediate.min() * q_y\n",
|
||
"\n",
|
||
"def f(x):\n",
|
||
" values = ((c1 * (x + c3)) - c4).round().clip(0, 2**output_bits - 1).astype(np.uint)\n",
|
||
" after_affine_q = QuantizedArray(values, intermediate_q.parameters)\n",
|
||
" \n",
|
||
" sigmoid = QuantizedFunction.plain(lambda x: 1 / (1 + np.exp(-x)), after_affine_q.parameters, output_bits)\n",
|
||
" y_q = sigmoid.apply(after_affine_q)\n",
|
||
" \n",
|
||
" return y_q.values\n",
|
||
"\n",
|
||
"f_q = QuantizedFunction.of(f, output_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",
|
||
"w_1 = int(c2.flatten()[1])\n",
|
||
"\n",
|
||
"def infer(x_0, x_1):\n",
|
||
" return table[((x_0 + zp_x) * w_0) + ((x_1 + zp_x) * w_1)]"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### Time to compile our quantized inference function"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 19,
|
||
"source": [
|
||
"dataset = []\n",
|
||
"for x_i in x_q:\n",
|
||
" dataset.append((int(x_i[0]), int(x_i[1])))\n",
|
||
" \n",
|
||
"homomorphic_model = hnp.compile_numpy_function_into_op_graph(\n",
|
||
" infer,\n",
|
||
" {\n",
|
||
" \"x_0\": hnp.EncryptedScalar(hnp.Integer(input_bits, is_signed=False)),\n",
|
||
" \"x_1\": hnp.EncryptedScalar(hnp.Integer(input_bits, is_signed=False)),\n",
|
||
" },\n",
|
||
" iter(dataset),\n",
|
||
")"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### Here are some representations of the operation graph"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 20,
|
||
"source": [
|
||
"print(hnp.get_printable_graph(homomorphic_model, show_data_types=True))"
|
||
],
|
||
"outputs": [
|
||
{
|
||
"output_type": "stream",
|
||
"name": "stdout",
|
||
"text": [
|
||
"\n",
|
||
"%0 = Constant(2) # Integer<unsigned, 8 bits>\n",
|
||
"%1 = Constant(1) # Integer<unsigned, 8 bits>\n",
|
||
"%2 = x_0 # Integer<unsigned, 7 bits>\n",
|
||
"%3 = Constant(6) # Integer<unsigned, 8 bits>\n",
|
||
"%4 = x_1 # Integer<unsigned, 7 bits>\n",
|
||
"%5 = Constant(6) # Integer<unsigned, 8 bits>\n",
|
||
"%6 = Add(2, 3) # Integer<unsigned, 7 bits>\n",
|
||
"%7 = Add(4, 5) # Integer<unsigned, 7 bits>\n",
|
||
"%8 = Mul(6, 0) # Integer<unsigned, 7 bits>\n",
|
||
"%9 = Mul(7, 1) # Integer<unsigned, 7 bits>\n",
|
||
"%10 = Add(8, 9) # Integer<unsigned, 7 bits>\n",
|
||
"%11 = TLU(10) # Integer<unsigned, 7 bits>\n",
|
||
"return(%11)\n"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 21,
|
||
"source": [
|
||
"hnp.draw_graph(homomorphic_model).show()"
|
||
],
|
||
"outputs": [
|
||
{
|
||
"output_type": "display_data",
|
||
"data": {
|
||
"text/plain": [
|
||
"<PIL.PngImagePlugin.PngImageFile image mode=RGBA size=443x539 at 0x7FBD882EAFD0>"
|
||
],
|
||
"image/png": "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cz2TijvbU0aWLl3K7NmzGTt2rE4J5kE02WXDJ598QlJSEq+88kq2/22hQoUAMlyR2f6c/T2utHv3bi5duqTJTnmkevXqZWn4gae0p+tdvXqVQYMG0aVLF1q3bu32/avMabLLhiJFijB69GgmTpzIunXrsvVvK1euDJBhJ5cTJ04AEBkZmfsgb2Lz5s3kyZOHatWquXxfSmVXvXr1+PPPP2969cRT2tP1Xn31VS5evMjo0aPdvm91Y5rssqlr1660atWKp556KltTid13330AbN26Nd1r9ueaN2/unCBvYMuWLdx9993azVl5pPr16yMiGbaT1DylPaW2ZcsWxo4dy8cff0zx4sXdum91czYRH52MzoWOHj1K1apVefHFF7O8JlVSUhLVq1fnwoULHDx4kLCwMAASExOpXr060dHR7Nu3L+V5V6lVqxbNmzfn448/zva/LV26NKdOnSIhIcEFkSlllClThmeeeeaGtws8pT3ZJSQkUK9ePQoUKMDKlStzfL9Q25jLzNUzuxy47bbbePPNN3n33XfZs2dPlv5NQEAAU6ZMISoqit69e3Pq1CnOnTvHoEGD2L9/P5MmTXJ5w7x27Rq7d+/WyZ+VR6tXr95NO6l4QntK7eOPP2bPnj2MHz/eso4x6sY02eXQ0KFDqVGjBn379iUpKSlL/6ZBgwZs2LCBixcvUqlSJcqWLcv+/ftZtWoVDz30kIsjNpd34uPjs9U5ZfHixdhsNmw2GydOnCAxMTHl8eTJk10YrfJX9erVY+PGjTd9n9Xtye7IkSO88847jBw5MuVeYnZoG3MPvYyZC3/88Qd16tRhzJgx9O/f3+pwburTTz/l/fff58yZM1aHolSmVq5cyf3338+xY8cyHDTuaR588EFOnTrF1q1bbzhTirKUXsbMjRo1avDcc8/x0ksvpfQA82RZXelAKSvVqVOHwMBAr1jMddq0aSxfvvymU4Ip62myy6U333yTokWLMnToUKtDualNmzZpslMeL3/+/FSuXDnXg8td7ezZs7z00ksMHjyYe+65x+pw1E1ossul8PBwJk2axIIFC1i4cKHV4WTqn3/+4fDhw5rslFeoX79+tta2s8Jzzz1HWFgY77zzjtWhqCzQZOcE9913H926deOZZ55x+lRizrJ582ZsNpv2xFReoW7duvz2228kJiZaHUqGfv75Z7755hvG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lFI+T5OdUkopn6fJTimllM/TZKeUUsrnabJTSinl8zTZKaWU8nma7JRSSvk8TXZKKaV8niY7pZRSPk+TnVJKKZ+nyU4ppZTP02SnlFLK52myU0op5fM02SmllPJ5muyUUkr5PE12Sqmb2rFjB61ataJQoULkz5+fFi1asH79eqvDUirLNNkppW5o06ZNNGzYkPz587N3714OHz5M+fLladasGb/88ovV4SmVJTYREauDUEpZY86cOXTq1InM/gwkJSVRo0YNoqKiOHjwIOHh4QAkJiZStWpVrl69yv79+wkNDXVn2Epl11w9s1NKZWrNmjXs3r2bJ554IiXRAQQGBtKlSxeOHTvG4sWLLYxQqazRZKeUytSKFSsAqFOnTrrX7M8tX77crTEplROa7JRSmfrzzz8BKF26dLrXIiIiANi3b59bY1IqJzTZKaUydeHCBQDy5s2b7rV8+fIBcP78eXeGpFSOaLJTSuWIvVOLzWazOBKlbk6TnVIqU4UKFQLgypUr6V6zP2d/j1KeTJOdUipTlStXBuD48ePpXjtx4gQAkZGRbo1JqZzQZKeUytR9990HwNatW9O9Zn+uefPmbo1JqZzQZKeUylTTpk2pUqUK8+bNIyYmJuX5xMREZs2aRZkyZWjVqpWFESqVNZrslFKZCggIYMqUKURFRdG7d29OnTrFuXPnGDRoEPv372fSpEmEhYVZHaZSN6XJTil1Qw0aNGDDhg1cvHiRSpUqUbZsWfbv38+qVat46KGHrA5PqSwJsjoApZTnq1WrFkuWLLE6DKVyTM/slFJK+TxNdkoppXyeJjullFI+T5OdUkopn6fJTimllM/TZKeUUsrnabJTSinl8zTZKaWU8nma7JRSSvk8TXZKKaV8niY7pZRSPk+TnVJKKZ+nyU4ppZTP02SnlFLK5+kSP0r5iRMnTlC9enXi4+PTPJ8nTx7y58+f8thms3HPPffw888/uztEpVxGk51SfiIiIoI77riD3377DRHJ9H02m42WLVu6MTKlXE8vYyrlR3r06EFAwM2bfYcOHdwQjVLuo8lOKT/SqVOnG74eEBBAkyZNiIiIcFNESrmHJjul/EjRokVp1qwZgYGBGb5us9no3r27m6NSyvU02SnlZ7p3757pPTubzcZjjz3m5oiUcj1Ndkr5mccee4ygoPR904KCgmjZsiVFihSxICqlXEuTnVL+IAk4CeyEAvsL0Kp+K4IC0ya8xMREujXoBluB/cAlC+JUykVscqM+yEop7xEL7AF2AruAg5gEdxQ4DSQ43jqf+XSgA4Kj+YcTzlnOkoc8jjfmBcoAJYDSwJ1ANaA6UBawue7rKOVEc3WcnVLe6giwClgJbAYOkCah3UgrWpGHPFzhCgDBBPM4j6dNdABXgD+Tf66XH5P4GgFNgSbJzynlgfTMTilvcQ34Cfgek+D+usn7A4HiQARQEnOGVhQIA/JAr5m9mLllJnEJcQAseXYJLcu1hDggGjiOOTM8DvwNnM/C/moD9wOPA3Wy+wWVcpm5muyU8mRXgR+BecASTBLKyO1ATcyZVo3k35HccI6kX375hYceegiAggUL8s8//xAcHJz5P7gE7MZcJrVfKt1G5vf2ymKS3hNAffSSp7KSJjulPNJ+YAowCYjK4PWSmMuHLYAHgHLZ30VCQgLFixcnKiqKgQMH8uWXX2b/QxIxlzjXA/9N/snoDDAS6AP0A7Szp3I/TXZKeQwBFgFjgRXJj1O7C3OW9Dimo0hmEoHDmDOvI8AJ4FSq37HARSAJhlwcwpikMawJW0Pj8MbmEmc4UBAohemYEpG8HQlUBYrdYN8JybHPB74D/rnu9XxAN2AoUPkGn6OUc2myU8oj/AD8G9hx3fNlgKeBzsAdGfw7wfTAXIs5u9oF7MUktCzYwAY605kjHCEgqyORbsEkvbuBxsC9mHuD10sEVgMzgNlATKrXAoGumO+c0fdSyrk02SllqWXAa5jelHY2oDnwDNAGkxhSO43ppPIjsA44l8V92c/W8gAFzOdKfmHi0Yn0L9HfJMhrmKR0Nnk/MZl+WlqVgGZAW0wHldDrXj8LfAWMx5x12gUBPYA3MIldKdfQZKeUJU4CzwFzUj1nw1yiHInpYJLa38A3wEJgI2aQeEbK4BgLdyfmUmEpzD2+8Iz/iYhgs2XSeyQqOdZjmM4pe3GcPWbWMSU/8HDyd2lH2sSXhOls8ybmjNQuX/JzQ9CFx5QraLJTyq0E+Bp4nrQdT1oAH2AuDdolYnpgTk7+ff0YugDMfbzGmM4qjTBJzR2SMMlvDeby6RrMPcHr3QJ0x3RMqXLdv58PvA78L9XzNYAJQAPnh6z8miY7pdzmJPAkZoycXVXMpb1GqZ67hklwH2NmP0ktDHOJsy3mEmdG98qssh3TwWYR6e89grm8+SomfrsEYAzm3p19WEUg5tLu66S/hKtUzmiyU8otVmIS3cnkx+HAS8ArOC7zXQG+BD7B3C9L7R7M2VEHzCU/T3cQmJr88/d1r90DjABapXrub2A45qzXrhnwLe47W1W+TJOdUi4lwFvJP/b7bHWBmUCFVO+ZDfwLM1uJXR6gNzCA9PfwvEUC5hLsGMwYvNSaA5+T9vLmXExSv5j8uATmvmZj14apfJ4mO6VcJhHTo3JiqueexvzhD0l+vAsYjOmib5c/+d+9wI3HtHmbTcAoYDGOMYTBmO//BqaHKJhp0Dolvx/Mme//YcYYKpUzc3WJH6VcIQbzx9me6PIBCzCdL0Iwf+w/wcwfaU90QZjB1keA9/GtRAdmyrDvgd9w3KOMB0Zjpjr7Nfm52zFl0j/5cSzQBTN0Qakc0mSnlLPFAo9ihgkA3AosB+wLgJ8AHgRexDH4+z5MB4/P8P3ptO7G9N78P8ywCDBj75pghh8kYM7mxgNvJ7+eADyFmV1GqRzQy5hKOZMAPXF0tCiFWamgevLj34DWmGm7wKwXNxpzn8ofXcJcxkzdMeVhzH06+3JB0zDlk4A5PJ+NXtJU2aWXMZVyqhdx/OGOADbgSHQ/Ybrf2xNdHcyq4P6a6MDcp5uB6ZhiP6P9CWiIY9hFL8wlTBumk093zPRoSmWDJjulnGUS8GnydkFML8Tbkx9PxVzavJz8uCdmMHYldwbowZ7AdEiJTH68CzPn5sHkx92B95K3YzAzs1w/BlGpG9Bkp5QzHMD0ngTTw3AuZjYQMPfunsb0zrRhpgObiqNHpjLuwHRSaZr8+DjmXqY9qb2MmU4MzOwzT2LKVKks0GSnVG4lYJatsc8A8iFmjTmAnzHd6BMwie5LTDd7Xcg0Y0UwZdYy+fGx5G37ZNefYAalg5kE+2O3Rqe8mCY7pXLrPRxjwprjOPs4hlnGJi758fuYAeLqxkIx82Y2SX68B3MZUzDDM77G0Xnl35hLnkrdhCY7pXLjNOZMDqAwpudgAGb8WAcckz3/CzM9mMqacMyYPPvMMUtxnMVVAP6TvB2HmXJNqZvQZKdUbryF4/Llv4HSydsjcZztNcHRuUJlXUHMvU/7XKAjML1XwUyj1jB5ezGwyq2RKS+k4+yUyqlDmDXj4oCywJ+YS3AHMasZxGLuQW0HbrMmRJ8wCzODCpj7desx9zxXYyaLBjMjiw5HUJnTcXZK5dgEHPfj3sSxesEwHDOjfIImutzqjBloDqa35tzk7aY4OrKswxxUKJUJTXZK5UQiZvkZMHNY2s88NmHWcwMzLVYPN8flqz7BsYL5qzhWkHgu1XumuzMg5W002SmVEz/jWI6nO2ZsHZj5HO0+xCUtrGbNmthstiz/vPPOO+TLly/d8x9/nL7f/vHjxzP8jIULF6Z532uvvZbuPX/++afzv6xdFcx9OjCXiZclb7fAceb8fzjOqJW6jiY7pXJibqrtnsm/L6Z6vhJmajBX7X7uXEQk5ad/f7NEwNKlS9M836lTJwCio6PZvt1c52vbti0iwosvvpjuc0uXLo2IMHPmTABefvllRIR27dqled8777yDiNC0aVMmTZqEiFC5cmXXfWEwc2jaTUr+HYA52AAzFm+Fa0NQ3kuTnVI5sT75dzkcc18uwKw2Dma+Sx047lw1MAvfghmWYF/gtU2q92xwa0TKiwTd/C1KqTTOYqYHA0f3dzDL1th1ct3ud+zYkeX3zpo1y3WBWKETsAUzjnEDpoNKLcy4vGs41sRT6jp6ZqdUdv2KY6Xt+qmet3d9L4djvJ1yrsaptu1n18GYzkAAm9H5MlWGNNkplV2HUm3XTP59DscM/fe6NRr/UguzBiA4Bu2D4//DZeCMOwNS3kKTnVLZdS7VdrHk36dSPXeHG2PxN8E4lk1KXeZFU21HoVQ6muyUyq7Uyc6+4OjZVM/d6sZY/JG9fDMr89T/f5RKpslOqey6mGq7UPLvC6meK+y2SFwmMDAQgMTEG98AS0xMTHmv29gPMFKfwaUu8/NujEV5DU12SmVXnlTbV5N/50313BW8Xr58ZvblS5cu3fB9Fy5coECBAu4IycE+8Xa+DJ6DtP8vlEqmyU6p7Lol1fa5DJ5LfXnNS0VGRgKwe/fuTN8TGxvLgQMHqFixorvCMv5J/p3ZpcvU/y+USqbJTqnsKpJq2/5HNvUf3pNujMXJgoKC+PPPP6lQoQKVK1dm48aN7N+/P8P3zpkzh6JFi1KtWrUMX3cZe/lqslPZoMlOqewqmWrbngdK40iCG90bjquMHj2agIAAWrZsyYIFC4iKiiIxMZG///6bL7/8ksGDB/Ppp58SEODGPyMHcQwtqJ7qefv/hyDS9sxUKpmuZ6dUdu3DzH0JZr7GMcnbrTELiQZhOk/kd30o06ZNo3fv3umev3z5csp9NzD34K5cydrNxL1796bMc7lt2zZGjx7NunXrOHnyJCJCsWLFuOeee3juuedo2LDhTT7NyabhmBD6a6Bb8nYJzKrxtYBt7g1JeYW5muyUyi7BjK87C9QGfkt+/gNgePL2POBx94fm8zrimGz7MGbR3AOA/bbhM8AX7g9LeTxdvFWpbLMBDZK3/8DRIeUxHJM/T3Z3UH7gLGYCaIC7MIkOYHmq9zRAqQxpslMqJx5J/h2PYxHXSMzq2QC/AEfcHJOvm4pjvboBqZ63L9oaBDzg1oiUF9Fkp1ROdMXMtA9pV8jul/w7CRjp1oh82yXAvtZsPkz5g7l/au8Q1Apz706pDGiyUyonCuJYR20bZrZ9MPeUqiZvf41Zjkbl3igcvTCfA+zj2MfjWIGil3tDUt5FO6golVP/xXHZrAWwLHl7MaZnJph7SGvRlSNzYzdQB4jBnLntw/R0PY65dHwNKIW5bBxsTYjK42kHFaVyrAVwf/L2f5N/AB7FkQQ3Aq+5OS5fcgVzthyT/HgUjiEdb2ASHcC/0USnbkjP7JTKjY2Y1coFs6baZswf3WOYMV/nMDcLFmNW1VbZ0wvHPdE2wEJMj9ffMWd7CZhhB7vRZKduRM/slMqVBkC75O0dwFvJ22WAGZg/zElABxwra6us+TeORFcG+ApTnjGYweQJya+9gyY6dVOa7JTKrTE45mN8F1idvP0IjkHmVzBnJrvcG5rX+hR4O3k7DzAHRxkPx1GOLTEHEkrdhCY7pXIrAhiXvJ2Emc7K3nNwFPBU8nYU0BzY5NbovM97wIvJ28GYGVPsg8UXAp8nbxfHnPnZUOqmNNkp5QwdgB7J24cxY76iMX+IJwCdk187gxl4PsvdAXqBBMx0X69i7oEGYC4F2wfwb8FcvhRMuU5GJ31WWabJTilnGQfck7z9G9AJ8wfc/ke7T/JrscCTmN6ECSiAv4EHcZwhh2MuXdoPEg5ierna57J+M/mxUlmkyU4pZ8mDucxWIfnxEsxMH7GYy3FTgM8wrS4J8wf7XsxExv5sIVADWJn8+BbMdGv2ibT/xAzxsF8a7ge87sb4lE/QZKeUMxUDfsbcTwJzv6klZrorgKGYS5gFkx9vxqyc8AX+d5Z3GjO04DEci6/WwVyubJT8+DegCXA0+fEjwJfuC1H5Dk12SjlbBcwA81LJj1diOqacSn7cAbNaQpPkx5cw6+JVxyRKXxcP/AezJqB9aIENGIIZnlEu+bmlwH3AP8mP22KWTtLZaFQOaLJTyhWqARtwLPL6G+ZS3S/Jj28DVmB6a+ZJfu5P4GHMVGO+2GMzHrNyQVXM/JYXk5+/A7NMz3+AEEwHlA8w5RCd/J4emEQXjlI5oslOKVe5HVgD1E1+/A/mMtxbmHt2gZieh3tJO1ZsMaar/QOYhOjtcxxdwVx6rIjppLM/+fl8mGEGuzBncGDOfltgxtIlYs74XsGsUK5ndCoXdLowpVwtFniBtPea7sXM2F8t1XMrgRHAr9f9+0jMWL2emHuC3uI3zPCAmTjuWYI5e+uB6aBjv9QrwP9hxtfZO6IUxMya0t4dwSofN1eTnVLu8h3mzOZC8uMgzLiyUZizHLvlmCmwVl3370Mw3fPbYWZj8cQxZr8Di4D5mPuSqYUBfYGXMJdx7Q5gymFZqudqYYYe3OGySJV/0WSnlFsdBJ7GXJ60K4s5y3kSc2nTbhNmQPocHOPL7AIxE1A/CDQG6mHN/axTwDrM5drFmAH11yuHSfJPkXZx1X8w9+bG4liBPBgYhhmDGOqSiJV/0mSnlCXmAs9iut/blcPcq+pL2qR3CfgW03NxM+Z+3/VCMN3262A6gFQFqgCFnRSvAH8BezArDOzCdMDJbIxgPsyg7z6YnqipewdEYab8Gk3ay5uNMIPKU1/aVco5NNkpZZnzmLXuJmF6KtpVwQxF6IZj7Ta7k8D3mIHYK3GcEWWmGFASM39ncaA0kDf5JyTV7ytAHKb3YzwmCZ8BTiRvHyX92WVG+2qDGSLQAnPZMrX9mGT2FY6emGDu272NmVNU57lUrqHJTinLHcb00Pw/0g4sLwB0BwaQ8dnOVWAbZmzauuTf510aaVolMWdj9yb/rkX6/t0JmMub4zD35FL/tSmGOZMdgA4pUK6myU4pj/E/TGeVOaQ/Y6sOPJH8UyWTf5+ESZx7cFxuPIQ5G/wbx2rf2VEYc+ZVCjNmsCpwJyb53pLJv4nH3JOch+mUc+6610thBpAPxpxZKuV6muyU8jhnMPNoTsDcJ7teFcx4vaaYzikFM3hPRs5jEt81HJct7b/zYTqH2H8XwZy5XX8pMjOHMOv4rQR+xNyXS80GNMP0umyHjplT7qbJTimPlYiZTHom5lLg5QzeE4i5fNgYqIk546qKa3synsMMMdiFGUu3CjiWyXvLY85Ge2HOCJWyhiY7pbxCDPAT5tLgjzjG6mUkCDNbSWWgDI7LkKUxY/PyYO6RhSVvh+LomHIJk2TtZ4F/J/8cxyS0ncnP30hFzIoFT2AmuVbKeprslPI6iZh5NNdjJpxeTvrLhu5k76jSAjPFWbkbv10pC2iyU8rrJQL7MGddqX/+Sn7NWfJgzharYy6X1kjeLunEfSjlGprslPJZiZgxcscxlx6PYS5/RmN6e17FdFaJwQxzCAQKYYYPFMKMyyuBufxZCtNpRSnvNFf7RCnlqwJx3K9Tys/pEj9KKaV8niY7pZRSPi8IMyWtUkop5as2/j+OlP2jmx65gwAAAABJRU5ErkJggg=="
|
||
},
|
||
"metadata": {}
|
||
}
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### Finally, it's time to make homomorphic inference\n",
|
||
"\n",
|
||
"Or, at least, simulate it until the compiler integration is complete."
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 22,
|
||
"source": [
|
||
"homomorphic_predictions = []\n",
|
||
"for x_0, x_1 in map(lambda x_i: (int(x_i[0]), int(x_i[1])), x_q):\n",
|
||
" evaluation = homomorphic_model.evaluate({0: x_0, 1: x_1})\n",
|
||
" inference = QuantizedArray(evaluation[homomorphic_model.output_nodes[0]], y_q.parameters)\n",
|
||
" homomorphic_predictions.append(inference.dequantize())\n",
|
||
"homomorphic_predictions = np.array(homomorphic_predictions, dtype=np.float32)"
|
||
],
|
||
"outputs": [],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### And visualize it"
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 23,
|
||
"source": [
|
||
"for column in contour.collections:\n",
|
||
" plt.gca().collections.remove(column)\n",
|
||
" \n",
|
||
"contour = ax.contourf(\n",
|
||
" contour_plot_x_data,\n",
|
||
" contour_plot_y_data,\n",
|
||
" homomorphic_predictions.round().reshape(contour_plot_x_data.shape),\n",
|
||
" cmap=\"gray\",\n",
|
||
" alpha=0.50,\n",
|
||
")\n",
|
||
"display(fig)"
|
||
],
|
||
"outputs": [
|
||
{
|
||
"output_type": "display_data",
|
||
"data": {
|
||
"text/plain": [
|
||
"<Figure size 432x288 with 1 Axes>"
|
||
],
|
||
"image/png": "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"
|
||
},
|
||
"metadata": {}
|
||
}
|
||
],
|
||
"metadata": {}
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"source": [
|
||
"### Enjoy!"
|
||
],
|
||
"metadata": {}
|
||
}
|
||
],
|
||
"metadata": {},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 5
|
||
}
|