├── Lecture.ipynb ├── README.md ├── main.py └── save ├── loss.pdf └── result.png /Lecture.ipynb: -------------------------------------------------------------------------------- 1 | {"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"name":"Lecture.ipynb","provenance":[],"collapsed_sections":[],"mount_file_id":"16YeIonTTR3PifruBx4ubCRGsrY0lvksL","authorship_tag":"ABX9TyNNKlol00SM+BKOnUGOyIg+"},"kernelspec":{"name":"python3","display_name":"Python 3"},"accelerator":"GPU"},"cells":[{"cell_type":"code","metadata":{"colab":{"base_uri":"https://localhost:8080/","height":568},"id":"YbVKAnEsyRor","executionInfo":{"status":"ok","timestamp":1608630334006,"user_tz":-540,"elapsed":30076,"user":{"displayName":"gakusyu umelab","photoUrl":"","userId":"09554957494174248921"}},"outputId":"40bcfd0f-5db1-418d-bd8f-77306a1387f3"},"source":["import os\r\n","import numpy as np\r\n","import torch\r\n","from torch import nn\r\n","from torch.autograd import Variable\r\n","from torch.utils.data import DataLoader,Dataset\r\n","from torchvision import transforms\r\n","from torchvision.datasets import MNIST\r\n","import pylab\r\n","import matplotlib.pyplot as plt\r\n","\r\n","mount_dir = './drive/MyDrive/Colab Notebooks/anomaly-detection-using-autoencoder-PyTorch-master'\r\n","\r\n","class Mnisttox(Dataset):\r\n"," def __init__(self, datasets ,labels:list):\r\n"," self.dataset = [datasets[i][0] for i in range(len(datasets))\r\n"," if datasets[i][1] in labels ]\r\n"," self.labels = labels\r\n"," self.len_oneclass = int(len(self.dataset)/10)\r\n","\r\n"," def __len__(self):\r\n"," return int(len(self.dataset))\r\n","\r\n"," def __getitem__(self, index):\r\n"," img = self.dataset[index]\r\n"," return img,[]\r\n","\r\n","class Autoencoder(nn.Module):\r\n"," def __init__(self,z_dim):\r\n"," super(Autoencoder, self).__init__()\r\n"," self.encoder = nn.Sequential(\r\n"," nn.Linear(28 * 28, 256),\r\n"," nn.ReLU(True),\r\n"," nn.Linear(256, 128),\r\n"," nn.ReLU(True),\r\n"," nn.Linear(128, z_dim))\r\n","\r\n"," self.decoder = nn.Sequential(\r\n"," nn.Linear(z_dim, 128),\r\n"," nn.ReLU(True),\r\n"," nn.Linear(128, 256),\r\n"," nn.ReLU(True),\r\n"," nn.Linear(256, 28 * 28),\r\n"," nn.Tanh()\r\n"," )\r\n","\r\n"," def forward(self, x):\r\n"," z = self.encoder(x)\r\n"," xhat = self.decoder(z)\r\n"," return xhat\r\n","\r\n","z_dim = 64\r\n","batch_size = 16\r\n","num_epochs = 10\r\n","learning_rate = 3.0e-4\r\n","n = 6 #number of test sample\r\n","cuda = True\r\n","model = Autoencoder(z_dim)\r\n","mse_loss = nn.MSELoss()\r\n","optimizer = torch.optim.Adam(model.parameters(),\r\n"," lr=learning_rate,\r\n"," weight_decay=1e-5)\r\n","\r\n","if cuda:\r\n"," model.cuda()\r\n","\r\n","img_transform = transforms.Compose([\r\n"," transforms.ToTensor(),\r\n"," transforms.Normalize((0.5, ), (0.5, )) # [0,1] => [-1,1]\r\n","])\r\n","train_dataset = MNIST('./data', download=True,train=True, transform=img_transform)\r\n","train_1 = Mnisttox(train_dataset,[1])\r\n","train_loader = DataLoader(train_1, batch_size=batch_size, shuffle=True)\r\n","losses = np.zeros(num_epochs)\r\n","\r\n","for epoch in range(num_epochs):\r\n"," i = 0\r\n"," for img,_ in train_loader:\r\n"," # print(\"now\")\r\n","\r\n"," x = img.view(img.size(0), -1)\r\n","\r\n"," if cuda:\r\n"," x = Variable(x).cuda()\r\n"," else:\r\n"," x = Variable(x)\r\n","\r\n"," xhat = model(x)\r\n","\r\n"," # 出力画像(再構成画像)と入力画像の間でlossを計算\r\n"," loss = mse_loss(xhat, x)\r\n"," losses[epoch] = losses[epoch] * (i / (i + 1.)) + loss * (1. / (i + 1.))\r\n"," optimizer.zero_grad()\r\n"," loss.backward()\r\n"," optimizer.step()\r\n"," i += 1\r\n","\r\n"," plt.figure()\r\n"," pylab.xlim(0, num_epochs)\r\n"," plt.plot(range(0, num_epochs), losses, label='loss')\r\n"," plt.legend()\r\n"," plt.savefig(os.path.join(mount_dir+\"/save/\", 'loss.pdf'))\r\n"," plt.close()\r\n","\r\n"," print('epoch [{}/{}], loss: {:.4f}'.format(\r\n"," epoch + 1,\r\n"," num_epochs,\r\n"," loss))\r\n","\r\n","test_dataset = MNIST('./data', train=False,download=True, transform=img_transform)\r\n","test_1_9 = Mnisttox(test_dataset,[1,9])\r\n","test_loader = DataLoader(test_1_9, batch_size=len(test_dataset), shuffle=True)\r\n","\r\n","for img,_ in test_loader:\r\n"," x = img.view(img.size(0), -1)\r\n","\r\n"," if cuda:\r\n"," x = Variable(x).cuda()\r\n"," else:\r\n"," x = Variable(x)\r\n","\r\n"," xhat = model(x)\r\n"," x = x.cpu().detach().numpy()\r\n"," xhat = xhat.cpu().detach().numpy()\r\n"," x = x/2 + 0.5\r\n"," xhat = xhat/2 + 0.5\r\n","\r\n","# サンプル画像表示\r\n","plt.figure(figsize=(12, 6))\r\n","for i in range(n):\r\n"," # テスト画像を表示\r\n"," ax = plt.subplot(3, n, i + 1)\r\n"," plt.imshow(x[i].reshape(28, 28))\r\n"," plt.gray()\r\n"," ax.get_xaxis().set_visible(False)\r\n"," ax.get_yaxis().set_visible(False)\r\n","\r\n"," # 出力画像を表示\r\n"," ax = plt.subplot(3, n, i + 1 + n)\r\n"," plt.imshow(xhat[i].reshape(28, 28))\r\n"," plt.gray()\r\n"," ax.get_xaxis().set_visible(False)\r\n"," ax.get_yaxis().set_visible(False)\r\n","\r\n"," # 入出力の差分画像を計算\r\n"," diff_img = np.abs(x[i] - xhat[i])\r\n","\r\n"," # 入出力の差分数値を計算\r\n"," diff = np.sum(diff_img)\r\n","\r\n"," # 差分画像と差分数値の表示\r\n"," ax = plt.subplot(3, n, i + 1 + n * 2)\r\n"," plt.imshow(diff_img.reshape(28, 28),cmap=\"jet\")\r\n"," #plt.gray()\r\n"," ax.get_xaxis().set_visible(True)\r\n"," ax.get_yaxis().set_visible(True)\r\n"," ax.set_xlabel('score = ' + str(diff))\r\n","\r\n","plt.savefig(mount_dir+\"/save/result.png\")\r\n","plt.show()\r\n","plt.close()\r\n","\r\n"],"execution_count":2,"outputs":[{"output_type":"stream","text":["epoch [1/10], loss: 0.0754\n","epoch [2/10], loss: 0.0461\n","epoch [3/10], loss: 0.0380\n","epoch [4/10], loss: 0.0418\n","epoch [5/10], loss: 0.0262\n","epoch [6/10], loss: 0.0172\n","epoch [7/10], loss: 0.0111\n","epoch [8/10], loss: 0.0133\n","epoch [9/10], loss: 0.0122\n","epoch [10/10], loss: 0.0300\n"],"name":"stdout"},{"output_type":"display_data","data":{"image/png":"iVBORw0KGgoAAAANSUhEUgAAAr8AAAFzCAYAAAA322G8AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3defwd0/0/8NdBQlYSkRDBxxKSNJZoxBbEEkUtsRUVX0qr2lJrf01rrZ22avvWUtrY6ZdakopdYmkjSEIjEoIEWQlBaEji/P648znzPu985mTu/cxd5/V8PDy853Pmzj13zszcyZz3PcdYa0FERERElAerVLsCRERERESVwptfIiIiIsoN3vwSERERUW7w5peIiIiIcoM3v0RERESUG7z5JSIiIqLcWK2Sb2aM4bhqVWStNVlti21ZXVm1Jdux6j621q6TxYbYllXHtmwcbMsGkfRdySe/RETVM6vaFaDMsC0bB9uywfHml4iIiIhygze/RERERJQbvPklIiIiotzgzS8RERER5QZvfomIiIgoN3jzS0RERES5wZtfIiIiIsoN3vwSERERUW7w5peIiIiIcoM3v0RERESUG7z5JSIiIqLc4M0vEREREeXGatWuQC1o3769ix977DGv7IgjjnDx3LlzK1YnIiIiolq1cOFCF1944YVe2TXXXFPp6hSFT36JiIiIKDd480tEREREucG0BwDffPONi5csWeKVPfLIIy7ebrvtKlYnKq+mpiYX/+hHP/LKzjnnHBevsor/78MpU6a4eP/99/fKZs2alWENKUt33HGHt3z00Ue72FrrlR122GEufvDBB8tbMSKiOrHXXnt5y/LeaYcddvDKmPZARERERFQjePNLRERERLnBm18iIiIiyg3m/AJYtmyZiy+44AKv7PHHH69wbagS+vbt6+Kzzz7bK5M5oN9++23i6+68806vbJdddsmyitRKffr0cfGwYcO8MtnGOuf3qquucvHzzz/v4o8//jjrKjYMY4yLu3Tp4pV9//vfd/Hee+/tlcnca7kN3SZffvmlizt16tS6ylIm2rRp4y2fdtppLj7wwANdPHjwYG89+Z361FNPeWVXX321i+X3MtWGQw891FteffXVXVxvv4/gk18iIiIiyg3e/BIRERFRbjDtQZk/f763PHPmTBfvu+++Lh4zZkylqkQ1qlu3btWuAgXcdNNNLpazOAJ+F7sWKqOWde/e3cVz5sxJ/bpQ+onUtm1bF+++++5e2bPPPpv6/ah12rVr5+KRI0d6ZbpLvJlOHZPDZemhs3bbbTcX/+pXv/LKpk2bVlRdKXvvvPOOt/zwww+7WKZA1AM++SUiIiKi3ODNLxERERHlBtMelPPPP99blr8Y79mzZ6WrQ2VyyCGHVLsKlLGDDz7YW7700ktd/M9//jPxdbq7/aOPPnIxR3hIJ7R/pcWLFyeWdezYMbFstdXir6oRI0Z4ZUx7KB+ZhgAA5513XmJZkoULF3rLn3zyiYv1iBEytfDuu+/2ypj2UB1Dhw518XvvveeVXXTRRS6+6667KlanLPDJLxERERHlBm9+iYiIiCg3ePNLRERERLnBnF9l7ty53rLMNaPGseuuu7Z6G3JGI6oOmXOm87jlbHw6rzc0nJmc1Y1advrpp3vLW2+9deK6cna2H//4x16ZbIdbbrnFxR06dEjc3hZbbJG6nlS8IUOGuPi+++7zytZee+3E102ZMsXFV1xxhYsnTZrkrSdzdzfccEOvbOLEiS4+44wzvLIJEya4WA+5ReXTtWtXF8vfQAHA6NGjXTxu3LiK1SkLfPJLRERERLnBm18iIiIiyg326St6qDPZlfo///M/Lr7tttu89ZYtW1beilGryfbT3W1pcRis2jJ9+nQXH3PMMV7Zyy+/7OLQ7GFTp071luUQaRRrampy8W9/+1uvbJVVkp+jPPbYYy4eO3asVyZn+Prss89cHEp7oGxtuumm3vK9997r4lCaw09+8hNvWQ5N9vXXX6d6b3k9BYDXXnvNxXootauvvtrFBxxwQKrtU+v16tXLxdtuu61X9pe//MXFS5curVidssAnv0RERESUG7z5JSIiIqLc4M0vEREREeUGc36VJUuWeMuzZs1y8R577OFiOQ0jAIwaNaq8FaNWk7ltelrNtMaMGePiV199tdV1otZ5//33XfzVV195ZaHhzGTZQw895JUxl7tlO+64o4vl8Ecr065dOxe//vrrXln37t2LrscNN9xQ9GsoWadOnbzlbt26Ja576623uljmBgPp83z79evnYnlMAemnTKbKefHFF12sh35dvnx5pauTGT75JSIiIqLc4M0vEREREeUG0x5WQnaJyrSHAw880FuPaQ+175xzznFxaOirkEsuuSSr6lAGnnvuORfLlBTAb2Pd3g8++KCLL7vssjLVrrG88cYbLl60aJFXttZaayW+br/99nOxHhJStksoTUUOo1RvM0k1krffftvF+lp4++23u3jjjTd28bnnnuut17NnTxeHUiyoemR6w6BBg1x85plneusdddRRFatT1vjkl4iIiIhygze/RERERJQbvPklIiIiotxgzu9K3HzzzS6+9tprq1gTai05Beu3335b9GuoNqyzzjou/uUvf+nivffe21tP5pDq4cvuuusuF+sh0qhlcpgy+fsHANh9991dLHMEAWDChAkufuSRR7wyOU38TjvtlPjecmjCAQMGeGXjx48PVZtW4pNPPvGWZ8+e7eL111/fK7v88stdLPN/AWDx4sUulrmha6yxhrdeqb+30MPkUfnIc3jnnXd2sR7Orp7PPX6zExEREVFu8OaXiIiIiHKDaQ8rIYfm0V12VNv0EDsy1SFt19t7773nLbOLvPoOPvhgF//2t791cahNjznmGG/5iSeeyL5iOfLaa68Fl6l+yFkSAWDkyJEuPvvssxNf16tXL2/5lFNOcfHqq6/e6nrNmTPHW5azy1F5bbjhhi7+wQ9+4OLHHnvMW+/LL7+sWJ2yxie/RERERJQbvPklIiIiotxg2sNKyK5y+Yvx448/3ltPdg8tWLCg/BWjFslfJx933HGt3t7f/vY3b3nu3Lmt3iYV5/TTT/eW//CHP7g4NCvYeeed52KmORClc+GFF7r4gQce8MoOP/xwF990001e2aefftri9jp27Ogty9EkQn7xi194y++++26q11Hx9Kge3//+9108ceJEF+vjoZ7xyS8RERER5QZvfomIiIgoN3jzS0RERES5wZzfEulcwyOOOMLF1113XaWrQxE5pNVGG23U6u1ddNFFrd4GFU8OZzZs2DCvLGlIs6lTp3rLl1xySfYVo6pYunSpiydNmlTFmjS+5cuXu1jPqlbKLGs63z6Up3/11Ve7eNSoUUW/F6XXoUMHF997771e2RZbbOHimTNnulj/Bqae8ckvEREREeUGb36JiIiIKDeY9pARPZwLVceuu+7qYt29tsoq8b/15BB22qmnnpp9xShIdsEBwMUXX+zivn37emUy7UEOw7PvvvuWqXZUbW3atHHxgAEDvLLx48dXujq0EnvvvbeLd9hhB69Mnr8fffSRV3bDDTeUt2LkyPOoa9euXtmYMWNc3LlzZxfLlJh6xye/RERERJQbvPklIiIiotzgzS8RERER5QZzfjMyfPhwF1922WVVrEm+7Lbbbt7y4MGDXayHxJJ5vknDZVF1jBgxwluWQ+3otpLLcupjOf04EVWWnPr4V7/6VeJ6oaHUZsyYkX3FqEVbbrmli9dbbz2vbKuttnLxT3/604rVqZL45JeIiIiIcoM3v0RERESUG0x7KMIdd9zh4uOPP76KNaFmeha39u3bF72NOXPmeMucWagy5CxuZ599tlcmUxv0kHWXXnqpi1944YUy1Y6IQtZee21v+X//939dLIfO0sNKyvP3d7/7XZlqRyuzZMkSF5900kmJ602YMKES1ak4PvklIiIiotzgzS8RERER5QZvfomIiIgoN5jzW4Qvv/wysaxXr14u7tevn1c2derUstUpj+TwZn/6059avb3bbrvNW541a1art0kr6tOnj7d8++23uzg0nNmDDz7olXEowfxZunSpiydNmlTFmlCzO++801vWU+Q2mzZtmrfMPN/q6Nixo7e8++67u/juu+/2ysaNG1eROlUTn/wSERERUW7w5peIiIiIcoNpD0WYMmWKi3VX+bHHHuvibt26VaxOebT11lu7eK211kr9ulVWif+tN3r0aBefe+652VSMgpqamrxlOSydHs5MztZ2zjnneGVfffVV9pWjmtamTRsXDxgwwCsbP358pauTG+uss463PHDgQBf3798/1Tb222+/TOtEpTnyyCO9ZTkjX9u2bb0yOfynnoWvUfDJLxERERHlBm9+iYiIiCg3mPZQBDkjyogRI7yy4cOHu3iPPfbwyp577rnyVixn5EgAepSAED3TEJWfnMXtxhtv9MpCbXfMMce4WP9anIjKR6Y63HfffV7Zrrvummobv/rVr1z8wQcfZFMxapXvfe973rJMdZApnUDjpjpIfPJLRERERLnBm18iIiIiyg3e/BIRERFRbjDnt0SbbbaZtyyH0dp8880rXZ1c+eyzz1wsh2QBgJ49e6baxk033ZRpnahlhxxyiIu7d+/ulckc7MMOO8wre+KJJ8pbMaorcni7Bx54oIo1aXxXXXWVi9Pm+AJ+nu8111yTaZ2oNJ06dXLxNtts45V98803Ll64cGHF6lQr+OSXiIiIiHKDN79ERERElBtMeyjRiy++6C2vuuqqVapJ/tx+++0u/uijj7yyUaNGJb5ODjn3/PPPZ18xAgD06dPHxcOGDXOxHmrukksucfGDDz5Y/opR3ZLHzoIFC6pYk8bXtWvXVOvJNAcAuPbaa13MYSVrwxdffOHi3r17V7EmtYdPfomIiIgoN3jzS0RERES5wZtfIiIiIsoN5vxSXRszZoy3vNpqPKSrTU5HLIfaISqVzCel8jr11FNd/NRTT3llModU/vYCAJYvX17eihFliE9+iYiIiCg3ePNLRERERLlhrLWVezNjKvdmtAJrrclqW2zL6sqqLdmOVfeqtXZgFhtiW1Yd27JxsC0bRNJ3JZ/8EhEREVFu8OaXiIiIiHKDN79ERERElBu8+SUiIiKi3ODNLxERERHlBm9+iYiIiCg3Kj0d1scAZlX4Palgo4y3x7asnizbku1YXWzLxsG2bBxsy8aQ2I4VHeeXiIiIiKiamPZARERERLnBm18iIiIiyg3e/BIRERFRbvDml4iIiIhygze/RERERJQbvPklIiIiotzgzS8RERER5QZvfomIiIgoN3jzS0RERES5wZtfIiIiIsoN3vwSERERUW7w5peIiIiIcoM3v0RERESUG7z5JSIiIqLc4M0vEREREeUGb36JiIiIKDd480tEREREubFaJd/MGGMr+X7ks9aarLbFtqyurNqS7Vh1H1tr18liQ2zLqqt6WxoTXxasLe/hIN+rEu9XYVVvS8pG0ndlRW9+iYjIMyvLja2ySqEzr5gbEbmuvqHJYhvl3n6tsNZm2parrdby1/O3337rYr0vmts/qk/ittO2V4h8r2JeJ+uvtxHaXtob+9DxkfZzL1++PNO2pNrDtAciIiIiyg0++SUiahCldD2HnqglPUVLux6Q/mlkSKlP80pZr1Y011fXddVVV3WxfIqql0NtJGO5PQBYvnx54jaS3kuvq7cphY4HuVzqk+W0x0Dovanx8ckvEREREeUGb36JiIiIKDd480tEREREucGcXyKiBqPzJXV+ZpJy5MKmHXUgJJQnmrbOpX62Sg4f1tL7htpOf6akvF4gue7Lli0LbjPte3fs2NHFixcvTlxPtqUuS5uzHMrdlTnL+jwIfTaZp/zNN98krkeNgU9+iYiIiCg3ePNLRERERLmRy7SHUHeQHlg87bAvlA/V6gJtdOWYLSrUBdymTRsXL126NHEboe7VWpY2zQFIPwRYqcOZZbHfQp9HXrPl9VordZKE0DGQxTBuK1PqRBah14X2U9JrAD81YNNNN/XKvvjiCxfL9mrbtm3iejq9QJ6XWmj4NElus5iJMtLukzzT90ehCVfk/i3melQpfPJLRERERLnBm18iIiIiyg3e/BIRERFRbuQm51fmC+m8lfXWW8/FgwcP9srmz5/v4nHjxrmYQ6E0Dp2r1KFDBxfrXCWZF/b111+Xt2LUKqFc/nXWWcfFOidRnvNLlixxsT5OGuX3AKUMj6XJ8ySUm6nzKkvJw9Xr6aG6kqT9LKXmA1dKKH8yVB+Zvytza0PfZTrn9zvf+Y6Lt9pqK6+sb9++Lr777rtdPGPGDG89eXzofHu5HBqmTNZZr5dWLbRlvVl33XW95YsuusjFEydO9MpuvPFGFzPnl4iIiIioinjzS0RERES5kZu0h1C3nOz2PPjgg72y6dOnu1g+1v/kk0+89dhlUvtkN5dMbTj66KO99c4//3wXy1mLAGDy5MkuvuSSS7yy8ePHu/jzzz93MY+NlSvHPlp99dVdvMMOO3hlJ5xwgovXXHNNr+zJJ5908b333uviTz/91FuvEYe9Szsbm16vffv2Lt522229snfffdfFm2yyiVcmZwJbY401XDx06FBvvRdeeMHFr776qlcmu8p1WoUsK7WNyjHrXWuEjrtQCoB8ndxPobQvfW507drVxdttt51XJr9HDz/8cBfL6yLgpyzMmzfPK5OpZPqzyJREeV7+97//9daTaTDF7J9ShoLLG3mOAkC/fv1c3K1bN6/stttuc3FoOMlq4ZNfIiIiIsoN3vwSERERUW7w5peIiIiIciM3Ob8hMo9F5z9ts802Ll577bVd/Nlnn3nrpR1uh7IVGq4mNKXt5ptv7uKTTz7ZW08Og6VzxLbffnsXn3baaV7ZiSee6OIvv/zSxTw2KkO3d+fOnV186KGHemU77bSTi3Ubz5o1y8Uy/3HhwoWZ1LNehXJm5THep08fr0zmhsqcUQDo37+/i+X1dbPNNvPWO/DAA118xRVXeGX/+te/XLxgwQKvrF7zso0xibnGaXOQ9eeVObky71b/BkbmysvjHwCamppc3KNHD69MDhkqc4V1W8rfy0ydOtUrk++38847e2VPPfWUi+VvMXTOr/w8+tob2nf1dHxUS8+ePb1l2c6vvPKKV6bbpdbwyS8RERER5QZvfomIiIgoN3KT9hDq0vjqq69c/MYbb3hlcsgd2Q3zwQcfeOuxa7s6dLuGZquSaQ9ySDs9/JKeDSyJ7s6j6tLpCzK1RQ/LJLts9bkrh0XUQxpKtdhNmlSnLIbrCqUQyDLdNSqHOZKzgAH+sGhdunRxsR5SSXZz77333l7Z2LFjXaxn65PX9nKoRFpF2uHnNJ3OIJfltVCT59H666/vlcm0FZnaBQAfffSRi7fYYovE7c+ZM8fFG264oVcmU4569erllc2dO9fFsp1D3716H4TS4mrxfK4F8ngYMGCAVybP2WnTpnlltT5cHJ/8EhEREVFu8OaXiIiIiHIjN2kPkpxFBgA+/vhjF+uZSGQXeO/evV3MLpLqkd1VultLdlnrbi35y9StttrKxbqrNES2+/vvv5+4HtNgKk+34/777+9iOWoL4Hf7ylnGAGDcuHEuzmKGsEoqJb2hlNED9GtkF6fsugaAI444wsV6JAg5K5RsE31ey+uwHnVDjuqxaNGi5A+QUi2MCGCtde+l6xNKtQjNwievjXJfh7avz6mNN97YxbLLG/Cvr/K927Vr560nR4XQ557c5pQpU7wy2bbz589PrL9879D3g1ZrM/nVCpn2sOOOO3plcl9PmjQpsawW8ckvEREREeUGb36JiIiIKDd480tEREREuZHLnF+d9yOHbHn66ae9sgMOOMDFgwYNcrHOY1qyZEmWVSQhlIulh0SSs8rImYoAf/idDTbYwMU6L6zUuujZpSg9PUxZKDdPku3Rr18/r+yHP/yhi0N53Z9++qm3PHv2bBfr3wc0olJy80J5lu+8845XJs/JtdZayyuTuacyrzc0TKGeJe6LL75wcRbDKxUzfGI58xqTcn7luRGqqyb3jcy11r9PkMPKyZkQAX9mOP06ef2T12U9S9y6667b4msA4N1333Wxzt+eOHGii+V5qfeBvJ6Hjgfm+KYj73Xkb2UAvx3ee++9itUpC3zyS0RERES5wZtfIiIiIsqNXKY9aKFheuQwaHI2mu7du3vr6a5Tyo7u1pJd5DrdRHa36bQHOVuRnIWqmO4v2U13zz33eGUc3qx0adMcNNnFqWcfkueoTquQx41ux8mTJ7u41ofrqRa9X2RaiRwSEvDbQc+eKNtdxrq9JD07mUyrSCuUvlDM9aASM7yF6P0Uqrvcb5999pmL27dv7623zjrruPjzzz/3yuQ19cMPP/TK5HelnO3t0EMP9daTaRB6nz3//PMuvu+++7wymXIRSkWRQu1cTMpI3sh9IY+HTp06eevJa6WeDbPWr5188ktEREREucGbXyIiIiLKDd78EhEREVFu5CbnN+2UkDIXCvBzeeVQSnLYLMAfokVPkUzlo3NFZV6Yzg3ccsstXaxzl5LoPF45LJ7MTwOyGWYpT0rNl5Svk1Oq/vKXv/TWk/nfevtyetRnnnnGK5PHUL0qdUiutFPphobf2nXXXb0ymWuvc37lUEkyf1UPPyjPLZlbCvjnaBZ5hrWWC6rzeuW+CE1hrF8n97Ucck4P5yenHJ4wYYJXFsqjnz59uosPOeQQF+u2lENnzZ071yuT11edU5yU86ulzd+utXauJbJt5fBmemhRee2st2Eh+eSXiIiIiHKDN79ERERElBu5SXsIkd0dX331lVc2depUF++xxx4u1jOdjB071sW6q7zWh/yoRWnTVDRZ1qtXL69sn332cXFoxq/Qe02bNs3F9TajTTWU2o4hcriloUOHurh///6Jr9GpSHJ4s+eee84rq/bwVVkotd6h18mUBX3+7LLLLi7WqUiyq1QPCSmH0tp4440T6yGX5ZCTQDZpZrXc5R0aBjBtmgqQnFaiv6/k0Fbvv/9+4vZ1+olMl5gzZ46LO3fu7K0n6yXTjwB/uKzQ8KGh4zTt+RsaTi/v5PB3I0aMcLEePnTUqFEuLnW4ymph6xMRERFRbvDml4iIiIhygze/RERERJQbDZvzW0wOl8wL0nkr48ePd/GJJ57o4kGDBnnrde3a1cV6SkhKJ22bpc1nlEObAUCXLl2Kfi+dk3bhhRe6mEPaVYZuK5nbe8IJJySuJ48TORQhAFx88cUu1u1Yr3m+5SavjTqPUw5vpnPt5fCRMhcUAGbOnOlimU+40UYbeevJNnnrrbcSy0od4i2k1KmPW8MY43JS9RCKaafz1d9lMrdXDkulh3184403XKx/AyPbSA8LKqcMnzVrlov1UGeyXno4yr///e8t1le/Tm5T759S27yW874rTZ5/ffv2dbGczhhYcUrjesInv0RERESUG7z5JSIiIqLcaNi0h9AwL2mHygL8WWtk91CPHj289fTMRVS8LLoXZTvsvvvuXpnsqtVdcZLsXnv22We9Mnk86G3objrKpttZpqsAwNFHH+3ivfbaK/F1ckaoc845xyuTM0k1kub9XWraV+h18rz4zne+45XJbm59Hhx55JEuPumkk7yyjz76yMUDBw5MrIdcfvvttxPL0g5tVeqMgpVirU1sy7RDdIX2oUxn0DMayvbTKQXy+tetW7cV6txMziCn97VMM5o4cWLie+vh9GQ9Zb305wylRKRNGckbff8ihy6U35u33nqrt96iRYvKW7Ey4pNfIiIiIsoN3vwSERERUW7kpq++1NlHZLeJ/KVrU1OTt16HDh1K2j61LG23pO6qkrMT7bDDDl6Z/lVzEtkleP/993tlssuu3ma0qSeyG+6AAw7wyo477rgWX6O7OB9++GEXy5mItHKMEFBPZDd62tnE9C/95Xl36aWXemWyG1rOkAj419QFCxa4ODTD2/rrr++VtWvXzsVyxAHAPybkZ6uHLu6ktAf5OXQKhCzTXdlJqQL//e9/vfVkuoE+p+Q29ahG3bt3d7E8HnQd5TV0xowZXpm8RusZ3uRxJI8HfayU2s76s+ZJx44dveVTTz3VxbK95Ey2QH1/B/LJLxERERHlBm9+iYiIiCg3ePNLRERERLmRm5zfUskhkV5++WUX63zSzTbbzMWvv/66V5a3HMJK0vlkBx54oIv1TFNJQwTp9pk3b56LdY6TzH9iu2ZHt81OO+3k4osuusgrW3PNNV0s20AOmwX4s/Hp4ZykRmrHUnJZQzmkktxPOnf3vffea3E9wM8hlbOHAf7McJKuh2w/PSOfHIqp0kPYlfPYaW5LnVeZlPsqX9MSuU9D68nZ3zT5fnooMpmL3b59exfr+s+ePdvFU6ZM8crkjGE6ZzmpXqGcfb1/5IxyOsc3NERaI5L7bauttvLK1l57bRfL307IvPx6xye/RERERJQbvPklIiIiotxg2sNKLF682MVvvfWWi3V33dChQ1380EMPeWWldKHkffiltORMQgBw0EEHuTjt8HO6G/XRRx91sR46Ke1sUlQcmTYEALfccouL9WyKkjw/9Sxub775Zka1a2ylzHqlh7mS6+nXyOuf7iqX6RN61jhJDokWmrks7TlZTLpAFjNPtkap3wV6Bkr5OplGknZ4O71NnZqy2267uXiPPfZI3L4cSnKNNdbwyjbZZBMX69nfkoZqCw33pr97Q7Nw1vOwXaWQQwQOHz7cK5Pfe3fddZeLQykxparWdyqf/BIRERFRbvDml4iIiIhygze/RERERJQbzPldCZkHNHr0aBf/+Mc/9tbbYostXKzzmNIOv8N80nRkjtc222zjlW299dYulsPaaHL/Llq0yCu77rrrXKxzxNgu2ZFDVOl8XTl9uM5dlPloI0eOdLGewrjUHL6kvM56bvu0eauhqWJDQjm/kh6OTuZ6d+vWLfF1sh56GzKHNG1+bFa5u9W4Zsvrn74+yTI9vJ+sq9yfofxnPdyYHM5M/x5CDpclf4sh8/IBf1i8d9991yuTQ53pXN6uXbu6WOaA6+mZ5fdtaGplfV3JW85v7969XTxgwACvbO7cuS5+6b2/9EwAACAASURBVKWXXJzVNbUWrqV88ktEREREucGbXyIiIiLKDaY9FEHOTqQf/2+00UYulkOIAH63nNaIXazlJvfZ5ptv7pV16dKlxfU0OQTOK6+84pXNnz/fxcV087AtC5K6gmVXJQCcddZZLj7ssMO8MtndqofXGTdunIuvvPJKF4fOs7T11cuhLuZ6krabXx+r8vOHZs4Kdf/L/aa7mvfaay8Xh9KU5OtmzJiRWKY/p6x/Fl22xQyRlpXQ0HG6TH5GvT9ll3/a4c10Kkrfvn1drIcg7NOnj4vl7Jr6veQMjfI1gH+u6/SWnj17ulh2y0+dOtVbLzQcWz2fw62lr7/77befi2UKGuBfVxcuXNjq99bnjWyjaqWb8MkvEREREeUGb36JiIiIKDd480tEREREucGc3yLIKT3nzJnjlck8qS233NIre+GFF1ysp9Ll8GbFk7lsO+20k1emh+ZJ8sUXX7j43HPP9crkEEE6ZywttmWBzDPT+X0/+9nPWlwP8PffrFmzvLJTTz3VxZ999pmL9VSmcpt62KcQ+d6lTE1eb0J5q/L41/m6ktxPobxY3c76ephErrfeeut5ZVOmTEl8XdIQb7qOclmf89We3jhtHjYQHsJMXhvlevrzymHK9G8q5BCfO++8s1cmjwHZzvr8lbn5w4YN88pknR988EGvrGPHji6Ww5np3wSkPRbzRrYrABx66KEuljnUgP+7ilLzpOW+Dg0rV63vSj75JSIiIqLc4M0vEREREeUG0x6KILtX9Kw1HTp0cLHuKpJpD6GZTthVno7sUpNDJQHphzeT3Tp6SB3ZJaOHYQnNZJW3GYKSyC6uddZZx8XXX3+9t54clk53vcrzS8/+Nnv2bBfLc7Jt27beep06dXKxnDkKaPy2KmZGJbkvQuePTD0IHfu6i1Mu62Eg9fmbRKatzJs3L/G9tVCKQL0opqtefg/pmUbljGxyf8pzVC+fccYZXtkOO+zQ4nvpespZ13S3+cyZM12sZ9eU570+juS5/umnn7pYt6v8ftCz0IWG2JLHSqOkO8nPtPvuu3tlcsi51157zSv78MMPXRxKkQmdU/J4qMX9ySe/RERERJQbvPklIiIiotxg2kMRZFfO3Xff7ZUdfvjhLu7atatXJrtj0/66uR40d2uUuztRd/vJ7uzQL9A12W127bXXuliP3BHqomGaysrJrrE999zTxf37909cT3dP/v73v3fxqFGjvDJ5Dsk2kOenXmZbpRMa3SQ06oEs0+knshta/+Jcpr7I807XQ3aPy+5avayPgaQ6h9KZijlWynVcGWNcnYpJ0ZH7Xo6IACTPhKdH4JCjaeh2lqkUukxeX0ePHu1ieS4Dftt+/PHHidvX6S1J53Mxs7iF2qsRrxHy3Nhtt928Mpk6MmbMGK8s6T6lmGOx1vcnn/wSERERUW7w5peIiIiIcoM3v0RERESUG8z5XYmkHM9HH33UW2/QoEEu1vkyMiepmCGIal216i73oZzhCwC6devmYl0/OdzVpEmTXFyLw7DUM7k/N9xwQxfrY1/m+T799NNe2Z///GcX6/Op0Ycpy0JoBjPdDqUMSRRaT87gpZd1fulbb73l4t69e7tY5/LL/M/58+d7ZfK81vWSnzv0+4BSr2XlmqHTWuuO89BQZ/pckNdDXZZ0DLz//vveegsWLHBxU1OTV7bJJpu4uGfPnl7ZhAkTXHz77be7eNq0ad56so10HTt37uxiPZyoFMrfDuUDJ63X0nI90p9X5vzqWdzGjx/v4okTJ3plebjG8skvEREREeUGb36JiIiIKDeY9lAE2f2qZ0Q5+uijXRwaAoaKp7ujZLfceeed55VdeeWVLtZD/Zx22mku1ukSlB3ZvSy7VF966SVvPdl9feaZZ3pleggkKo4+Z4qZJayc9GyKclnGHTt29NaTs2TKGf6A9NfXUrtyQykjlRBKlSsmjS5pdkqdiiJTx+RMX4A/M6b25JNPuviZZ55xcTHDe37xxRcu1l34STMRho710Cxuoe3Xk1BbyuHhbrvtNq9MDh8o9zvQGCkgK8Mnv0RERESUG7z5JSIiIqLc4M0vEREREeWGqWRuhzGm8RNJapi1NrOEtWq2ZSjvLjS0kczpqvecpqzaspLtmLbdcuZVa+3ALDZkjLHN+7iYXNAsclrTtl9oKKYePXq4uH379t56kydPLvq9tHINSya2mWlbJg3NlpS7uzJJOa16Omq5b/TQcXK5e/fuXpnMvZbDlBUzlXTaKahlmd5PaXOiQ++9fPnyTNsyi+1QaZK+K/nkl4iIiIhygze/RERERJQbHOqM6k7aLkvO3FZbcpzaUHGV3tdpu991veRwS3L4wdAsdKV+trRd3qEyXa9yDY9ljHEpIvo9Qu8p00r0ejI9QJYVMxSZ3H7a4QhDs6zp/SnXDbVzaLa+tEOdZTHTIdUvPvklIiIiotzgzS8RERER5QZvfomIiIgoN5jzS0SUY6UMb1bMUGpphXJys5bVkG7lGj7NWpuY25u27qH85FKHGwtJyikOSTsEn65X2m2ElOMYpvrBJ79ERERElBu8+SUiIiKi3Kh02sPHAGZV+D2pYKOMt8e2rJ4s25LtWF2ZtqW1tiJtmacu4iI+a6ZtuXz58ro6L7MY9q3cQ4wVUUdeYxtDYjtWdHpjIiIiIqJqYtoDEREREeUGb36JiIiIKDd480tEREREucGbXyIiIiLKDd78EhEREVFu8OaXiIiIiHKDN79ERERElBu8+SUiIiKi3ODNLxERERHlBm9+iYiIiCg3ePNLRERERLnBm18iIiIiyg3e/BIRERFRbvDml4iIiIhygze/RERERJQbrbr5NcbsY4yZboyZYYwZkVWlqPLYlo2Dbdk42JaNge3YONiWjcFYa0t7oTGrAngLwFAAHwJ4GcBR1tqp2VWPKoFt2TjYlo2DbdkY2I6Ng23ZOFZrxWsHAZhhrX0XAIwx9wI4CEDiQWBMewus1Yq3rASjlkv7x0HtWQRrv9Ifrlkr2zKrfSa3E9pG2vXqQSn7Lru2rI9zspHN/dhau05CYZFt2cECXaKlYs4LuW7SYdWabZR7+7ViTlJblnB97WiBrgml38o1VZnszA0dA2nbK2TVQL1C5Hqhzmddj1K+H4oht/lhhm3Ja2z1JH9Xtubmd30AH4jlDwFsH37JWgBObMVbVkIbtby0KrXI3s2hwla2ZVb7TG4ntI2069WDUvZdlm1ZD+dkI/vdrEBhkW3ZBcApUbysiDrIY04fj1lso9zbrxXnJLVlCdfXrgDOSij7r4j1vmgn4tC1RB4f+jYg7bHTKVCvELleu8S1VqzHaoGypPWKIbd5ZoZtyWts9SR/V7bm5jcVY8yJcC2/ZrnfjsqIbdkY2I6Nw29LPl2qZ35bdgmuS7WN19ja15qb39kANhDLvaK/eay1NyO6/TamZx30UdfDk8TMn07XSFsmfY5GfRoPlOGzrLQt6++czK0i23J9W9wT32ahnpSkp6xp1wP8p3ul1G9l20/7VLhqT49LuL5uINpSf013FrF+2iqXQ20kt9lZrfe5iEPt9YValu+nt5mkmCe4SU+r9TZCbRt6Op3qWlwj35XUWq0Z7eFlAL2NMRsbY9oCOBLAI9lUiyqMbdk42JaNg23ZGNiOjYNt2SBKfvJrrV1mjDkZwOMoZL7/1Vr7RmY1o4phWzYOtmXjYFs2BrZj42BbNo5W5fxaax8F8GhGdaEqYls2DrZl42BbNga2Y+NgWzaGsv/grTTVzPFMmzNWbrVSjzSKqU8pIzXU2uetluZ9V6tDPVHt0L+iT/tL/HLkwoZGFkhbr1DecNo6l/rZ0o4ykCUj3jeU16s/02oJMZBc908C2wjR6w0S8YTAerItdf1DOctJ7RD6ftDvHfpsevQKWrms71Mqd+/H6Y2JiIiIKDd480tEREREuVGjaQ9pJzgo5nVpu1o2U2UrjGKS4r1K1UiTNyRJOxh+WsV0ZWaxT0sZFiqrejS/rhFHzin3saDTADYScWieCVkP3W1cy+do2nQCIP0QYKGvi9DkCssS4mKEPo+cCU2vl3Z4LCmULqA/W2iShqyUOpFF6HV6mLIk+vOJ1IBthvtFk+WCbIf11TZeFLH+fu0eqItMSwhdh5O+s4HwMZB2n+RZk1qW7RxKadKpNUkqd03lk18iIiIiyg3e/BIRERFRbvDml4iIiIhyo0ZzfkPS5jjp9XqIuK9ftP/2LvzmeX8YqfUXxfmAH5l3RcmL8GWRq1LLOYRZyTqvOZR3m3b7Oq/tByLWeWDzRTxBlZU7p5jCQvtrI3+xz6EiVqs+9KpYkOe5Pk5kHls95QNrSdPeAulzdGV+X2jIKH0+pT3eQ+tlnU9YTB5tpYY+k9JO0avXk8evzK0N5ciqY35YnOfb68G3vaLL8BsXH7Pv/XHBY2PUNuXUxwtUmVzW55s8NmcE1gsJ5bRXoy3rzP7HeYvvjFrPxZuam9TK8lhMe45WDp/8EhEREVFu8OaXiIiIiHKjDtMeSiW7q/f1i46Lw7aP+cNIvSmG9ujbf2ZcMGWi2n49dXNWU9b7qZghskR321pnxLHu9l4k4mnzVeFWcThyqF90nHzvK1PWSeNxlJ1tXTTBnu2VTMdDLh6FA7yyv29wbLzw4XdFybVq+/LYK2ZosVoW6voNDfkV72ucc6BfdPFXcXxWe79sioh7xeFv/nKet9plV4j2GzFWvfdCEevuVdmNXuq5VY5Z71ojNFxXKAUg6XjVn0+0JYb4RfvH4T9wiFfUXXzH/nnMcS7++eMj/W3ME7Eq8srU3cnW/xnv4tdMb1HyptrI5yIu5rysxLB19UgcH/39kk1vnSuW7lOvk/dZ/8m4Tq3HJ79ERERElBu8+SUiIiKi3ODNLxERERHlRg3l/GYxxFNpQ9n0PTTO3/3Bsu96Za9ADIk0UhQM1FM2fg6qhtAhrMvE1NXdxJ/PUqsddpVY0DljYoig4/ZSr9s4ju+Xx8fMQB21PA11lvVn1bmLce7pIvzeKznmADEU08f+q4764K8uvsdsLUpCU+c2KnkOhfInxXBZw1TRxSJv/uqN/bKxIn4sDi8z6qSUs85PGeKX9ZfHzg3qzWX96+l8MkjONU6bg6yPT3lNksOb6aHpxLCAm/klfU+Ivyu3+2CKV2beFb+XGRLv6zUW+XnYS5rEdNSL7vTKhtv4GLvj0RO9sgPwdxe/5h1kOudX5u7qHPDQMHz1dHxUhz3eHwrWXBm3+WDb1St7wegh7moLn/wSERERUW7w5peIiIiIcqOG0h7K3eWQ3EX55rh4aJcL9vbLjBHdo4fJkm39FVfoeqkVzV1kJrhWbSilG1y3qzykdfdgPLPftm+/4OKJJtR9HTpFnlKr/iSwblp56nrL+rP63W72F/Exb17yhzDE6JfEQnevaC1vrLsZSFZrbWUR1ymr4bnkuRE6P+Pr4UXf9VMW1rLx/jzl1Vv8lw2UC4+IWO33GXHbtlnXH2JrqTfUmU5HK/cQS5X4Ck07/Jymu/jXTrkNMRzdyX7JBvjAxWbDe/3CsSIeHB8rS/b3z0tcL+KOw72iO8Uhdvt8P+1h9CyR6rCGWHFJaPawzmpZftZSZzPMm/g4Mpu/4pWcbOMhPa83P1Sv+0c5K9VqfPJLRERERLnBm18iIiIiyo0aSntI2+VdzIxeSeupXyEOi7urzVN+9+huj8c/QR738D5xwf31MqNT8+e2wbVqQ9q2lL/mbVJlgV8xD4lnBZq4gZwh6Aq1Dd1VJonjb/BxfpHXazYbVFl72A285e6YFS+YO9Xasn38Gd7+/MiZLr4BT4iSWk1tahYaISAk7ddAUgoEYH+6g4vnYU2vbL3bRBrJcVPVNh9Dy/ToOXFqytLj1Pm5hlheMiphe8UI7cNKpbrIFBbdPrJ+uqt+aaBM7tMegfcW19fTHvBKnrhajH40Wr1MZoHJKo9921/vhdfFgpoN8OT4s63ypPrOWiziJbJeofQFnfoRmtmOWnKijdvhCJzile2547/E0uvw1fY9Ep/8EhEREVFu8OaXiIiIiHKDN79ERERElBs1lPNb2uxspfnCX1wU5wDb1ffzin6M61w8doYYOqmXykf6sLaH9ag/Mh8rlNO1syoblVh24rPXuPhmMzRhe5o+3uI8JttezXZzv8wPrbVhsGqdzrNON2OiPe93Ln5AjeZ32DbyHNVDlom8xsH9vJKrDvyZWJqIZLWQG5qF4od4Oshu4i1fIfb9r3t+5q8sRh970/rX100/i/Oy2671sCjR+13kaB+mikZ/KBbUtb0kuu3aJMQtrZsVi7hd9PVP5lKG6qrJc0rm2uptxMuDbDevZG0xfNWYV/0h5+QMqPt/8H8uHvXED7zVXv9eHG+lvjbN6+KcfcEvw/VjxYI8n/XxK/N8szgeGkVpx+5OiPN6l2NVv1COXLj/Vn7ZzDqf4c0Y81djzAJjzBTxt67GmCeNMW9H/+9S3mpSFtiWjYNt2TjYlg2jie3YMNiWDS5N2sNIAPuov40A8LS1tjeAp6Nlqn0jwbZsFCPBtmwUI8G2bAQfg+3YKNiWDW6laQ/W2ueMMU3qzwcBGBLFt6Ewt8uvM6xXhcVDsZijVDqDmMTm1qvFdDc3qk3sL4ffymKIj+y7VLNtSzmsUqjrrZi6Jn1mvT/lMD1qljXsJeLeXsnNRqZE9BVx0nBLLYm71Fa9fbFftO7dRWyndRrvvEyX5lAQHydX/i4+J3+97XX+asPkcEhqlilxDB37/A1eyZnmdLF0V4vvu6LSu79ruy3lV0T89ucaP8dkgohnn6f2tYnbts95s7yij2Z3FEsyTSmQbrJI/6GUWdxCXcDFDIHlrbsYgJ5urBXX1zRZie3UcqjuvUQsZzic6a1lxx/p4ntwkFf2tLy+qlu/n31wlYt/Kr4gX/2ev55s2a1OUlUUh479gcpjejcOzSZ/FAWhzxxKmQvNDpplW5ZDKd+xxVyj4uNqG0yK43FveWvZz0Qq6Ex/9rdGHeqsh7V2bhTPQ3jQQKptbMvGwbZsHGzLxsB2bBxsywbS6h+8WWutMSZxBgVjzIkAokm610xajWoA27JxhNqS7Vhf2JaNobjrK9NJaxm/K+tfqU9+5xtj1gOA6P8Lkla01t5srR1orR0ItC/x7aiMSmzLDhWrIKWWqi15TtaFEtqS52QNKvH62jFpNaoe3vc0kFKf/D4C4FgAl0f/fzi8erHKMZxMKCdX7IYpqmhEQrz/BWrF7UX8ErJXtqGTSmxLOf1mqe2VNu9OrxfKJZIZZb1VmcwpHCdinTeHxLJtbVP8Trfqm42qT2lc5vMya6Xmycfn668vifN8zz/HzxH8Hc5P3sTgX7rwUbyvCu9IeFFFhy9rZVuGch2LeZ0492bGn39Uk7+WHKhugMgRBAD0iUvNtGf9MiPb/baW31e/w2l6GtWFIs6ijfQ25HFa9LBwGZyT+vokh+/S9ZH7U79OTjO8r4i391e7JM75PWoHv7o/7PFQvKByfm8YeoaLm558z8WDvPcFfrJbHP907NVe2bGI8++XruVvv403C3no1kXuk9B6up1XejtUQ9fXcl+L4uTrbdaJ83zt0Wp4z43lw+9rM3jfSg0lmG6os3sA/BvAFsaYD40xJ6DQ+EONMW+j8Aujy8tWQ8oM27JxsC0bB9uyYWwMtmOjYFs2uDSjPRyVULRnxnWhMmNbNg62ZeNgWzaM9wrd3CtgO9YftmWDq6EZ3qQsHnXrx+eyC0jP+iLXvdMvGj08jmU3zC0X+Ov9WC7obrlShvyopxmiSq1raIg0eWiGuhr1vhXb1KOgrTE4jgdfIQr07GJJdQIOMLu4eCIeDaxbT+1XTqF9UvzMYgXbuuiPZ//cxfef/bS/mnlZLKg27hOHH5l/lliPWlbqvg28bkjclmes7hd1/nsc/wlf+4XT5ouFjfyydTeO43kzRYEe+i4edWp/O90rGW0SUy+LEBouq9R92RpyhrfQ94f+Cg9dNzsnxIqc/O2PqmybONzknje8onf3WuLiX5s4BQJ91BCE40Ra4Ol+ysXNfzrGxY+oiQIPXS6XQm2S9rsjlO6Wd/FsbW9+1ORiY9TD7pPlgh4ZLiTpe6Fy35ul/uCNiIiIiKju8OaXiIiIiHKDN79ERERElBs1mvObBZ3PExr+ROaX+Tknf7bHufjn14yMC25RmxBFOK6YHN9y54k2b98E16oNaQ/HdPv3syFtveU1h38jluR+D+QN7+XPXvm7p+R8nH9Kfl2upZ0etsQh8fYa6sIzX41jDLzCX89r1zO8kkF/iYe6m3CLzlXLWzumzJGcGQ9ltKacaxYADpJjmS70ira28bBX/8DBXtmm680VSz8T8QXqzdd30WizhyoLDZGW9dBnxUx93BqrIP4O0/nPoTrI7z19XMvlmSIe5a/2Yhw+Ms0vshuK75Fr/LL9bDyd+KMvxb8VMzvoxGFxvH3s5/z+ZMP4Nzd/Ua+6ePszxVInEevf8JSao12ptq0Hg1x0uZzBefTP/NX2f7LE7Vf/Gssnv0RERESUG7z5JSIiIqLcqNG0h2K6rpKGx9Kv6Sri0NA4fhf4z08Z6eKrr/upiy8/1e8On/fSJgl10nWpdNdK83snTkNeIoP4s2TVhZE0O1Ex249ndVtz2Dd+0WjZfSiPFd0m8Xq7PfmYVzLO7CSWusKnuyeThFJdGmG4tKyP9//nL8rRNwdeIhZ0qlOTiGd6JROMnGmsUbo7Q7MuhsjzLvQ6sd6NG3slg34ap5Eco2bIO8XEXaV/sqd7ZevOjdMn5m0nrqGvBKrhzeJYjFJnaqvG8RFqy9AQXfJzher9pogHeSVn/e0iF3ccea7/qifi+Nf+xG149NJD44X/yBJ9m9Ejfuc7xnklX4lZnX+ys1cEY/4glmSKk/6cMiVCp7SFrq81ejtUEU1qOd5vt5n4+LBvqRnedO5LosrN3JYWn/wSERERUW7w5peIiIiIcqNGn/MX80g87boy1aGI7YtepFP/eLOLT3vsJn+9ddNv0pfURVz9boEw2S2npf0coa6Q0IgO8nW6C1D8VHl0J1X2oIhlyoKu474uGvfG9qrsARGnTXPQQvskzf6qh5E7mqVtf73e+kh0+XsJBWob1x8Rxyc/AEoiz6GUs4m94Jfs+dN4dr1TzFC/UIwycP2rfgrLbt+N04rmDU+b9qCvG1uK+E1VJkcCSJveUQvkDG/6azopPUyXpU3L8nIU8EcTpyVgGz9d7sxJF7vYmMHwxTMvbtL9Axe/gf7eWt9BnHJ0Fv7glbUXKU1md5Wq96KcfTWUwhI6hkPXIz1qRJ6oHJPJYvSWx77rwit7n+yvl3pWt9q7n+GTXyIiIiLKDd78EhEREVFu8OaXiIiIiHKjRnN+Q7KePWolxExu5iGRg3TW6/56J28lFnSuaSgvJinvsfaGBkkvbV2z+Ex6X+8r4h6qLOlwVzljp4k83/5fqXVniLiYmfyyUK5h66pFtr/OT/xJHO6lip66O2Ebaki0xXLBz2tMrkc9k8MPFiOUQ5rgzrHe4mV3niiWXoRPnIfD/JLpH2weLwRHHxP1OmmIX3SjbL8JoY2UQbmOHYP4eqWvM50D7x/6Sm+XEGsi93WyP2viH02TWFJ5+efE9foeHnfxdw5T16spcXj4nbt7RceMjX9XA6O+YxMPEP2Z5Xp6/8jvBJ0Drb9LGt0BLjofm3slv9tmfLxwS/x9+OsV8vlLHXYw6TpVuWsxn/wSERERUW7w5peIiIiIcqMO0x5CyvDIfJkYXmV/ORSKeq9uMu2hs1+WejiQpNnqtHrqpi11tr6Uw4F1/IlftPgvYiE0DJCkuu+8YeueUuvKOqZNe8gqhaUehzoLEftls1/6RTNEOz6l21Hsv5MucOGxN9zgrXWbyTotJet2rBWh+iSdk2PVejLVQV+7RPfyh/65diHOd/GJZ10dqJPYxo33qbIfJNQxJDSzYq0p9bjT3fgyHWCmiFMObwfA/27zz8tzLvqtixfI9IL7/+Kth/3ja/bbw3t5RXcaMTwhnlTvLdMU5D7RnzPwPR38Lq50Glt1rbkkPhcXru6nt+yBj1z89CPx9036Gd200DEcSsEp370On/wSERERUW7w5peIiIiIcoM3v0RERESUG3WY81vpfNd4WmT77pkuNpv80V9tmlw4RG3jusD2k4Z7Ck21WOvDoJU6TXPadcV+WjxSlW0r4nGqTOdiRzoO9xbP+XWcu3bxiG5q5Uru66R2rtehzvTnOTYO19LryuN/gSqLp7M95wbRVu0uVetdUFTtYuUehqca52toGEWpmKliJflVorcvt3mbKllVLN0V2L48d/XXlhxuKe21sZgc39C+q0ausMyR1Dmssmx2oEy2ayj/WQ1B2OvAOP7QL7p4ujj/+lwiSvzhzHBOHE5FP/XeMn+3vSqT+cByyDI93bkc7k7nk84XsR5esRrTG5f6Xdna9wJ2Wf15F18/Tg0TOWShC81w+X1zCUqTcghFDnVGRERERJQ93vwSERERUW7UYdpD9Zir5OP/O/3Ce1+I44GD/bJXQo/8ZTdP2i6QWh6WB0jfdVFq+obcn7upslEJ6wGJXbhqNLOLjew+L6abR76f/Gx6JqG0ai2dJa2k43hLf7U7xTBHw0PDGm3rFz0Wd71evOWh8d+XTE1ZD00fh7Id5fmpu5HrSakzY8p9EZo5K7Svk4c1+vn/jhRLFyRsD/C694f8zC8aK7u99VdaUld/MUKfrRLXYv2ZZPe8fn/5GdUQjl76UNrhzY72i+TXnk5V6iNnw9wnefNiG8PWHe2XvSCGcbx3X7/sZBHfKOKrdepH8nBsfppIcErBCqnkNX5nb2l0lyEunrfIHz5zXbwWL9ypZ2wshT7eZMpJqd+PrbPSJ7/GmA2MMc8aY6YaY94wxpwa/b2rMeZJY8zb0f+7lL+61Bpsy8bBtmwYbdiOrBjzHQAAGSRJREFUDYNt2TjYlg0uTdrDMgBnWmv7AdgBwC+MMf0AjADwtLW2N4Cno2WqbWzLxsG2bBxsx8bBtmwcbMsGttKbX2vtXGvtxCj+AsCbKPSnHIT4p7u3ARhWrkpSNtiWjYNt2TCWsh0bBtuycbAtG1xROb/GmCYAAwC8BKCHtXZuVDQP/tgjNa7EXNM+Ir7YHx4LD4m4Sb3ulb5i4XVVKHOS0ua+tD5PqDbasrTc4F52iItPx++9sjPNRomv89/vFBeN3X57b60huFgspc3X1rIYNifdcVobbZlE5uuqvC8vz3eCXyY/++UH+kVniXiKzPfUQ6LJE3aGKpP5fvoyKPezzBEs7xCD1WvH0NeAPP4ThgoE4F+7QvulyV/0RhJMmT87Vm9ft7sk15WfU+d7hqZblevqOra877JtS13X0DXpvwkx4F+vZL319kSu7cX+cGP2azHV7QV6yEU5jNhMEf/H38ZhWye+9dL+cdz2JH/7vbZ428UfTuktSnQuvjxn9bFS/M+cavv6ujLyeO3uF50Whz305febreL4KfWjmMTta7Jx9bByoeO0MlKP9mCM6QjgAQCnWWu9uzRrrUXC4KPGmBONMa8YY14BvmppFaowtmXjKKUt2Y61J5tz8ssK1JRWJpu2XFyBmtLK8LuycaW6+TXGtEHhALjLWvuP6M/zjTHrReXrIeGf39bam621A621A1cctJoqjW3ZOEptS7ZjbcnunOxQmQpTouzasmNlKkyJ+F3Z2FbaB2CMMQBuBfCmtfYqUfQIClM0XR79/+HWVaWSM50U895iF538kgtPsC97a93aMR6HZZUj/Scw394f6lKT/5gs79ArlWvL8vrgvc1dbPbW//C+QsSh4VXGuGjI4S+p9e4LbCN0yoRmTSpF8jaq35ahc0Z0r/WPUxbsr/3hdMwxlwe2sVcc6tNiimwvmUa0lVpRDIOGq1RZ1m3VKmVoR32chq4tcl+EujFlt3ZoiC2dDiTTJfr6RUfeEHg/aVAc7qDee3yo2zRpqLaQ0Ax12gplFWhLSR+7Ms1oM1UmU39kW6rhzIbEn3/m2X5X+SdYI174nk7hk+dl3M7274d5az25WzwU6CsY6JVNR3xtxyL/s81fKDIMvJ543f5NIn5blcljUb9uhXSSuv+u9L/z1Gx6Yqg686j6Hr1FxE/J/RRKuQldR/VwdNVJdZDSJMDsDOAYAP8xxkyO/vZbFBr/78aYEwDMAvCD8lSRMsS2bBxsy8bQEWzHRsG2bBxsywa30ptfa+0LAExC8Z7ZVofKiW3ZONiWDWOxtZbt2BjYlo2DbdngamiGt6p3PSaQ3Vrxo/pbNjzFW+uf7+/n4nlG/1hBdOHiOlWW9Pi/vL8sbz2DuI6VrduTG4sZ9Gbq0lC3jOiKG76Li82df1PryV8Ph2arqn7XTfWE2lx0LU6Jux3NMXo/y65X1R0+RbRx/0fU62T3rWyDsWo92Tdaa+dPrQqdP6FRD+T+1TOLNcXhur39onmyLWUKmDq3VvtuHI/X9ZKzPOqueFnn0GxvqwXKQsp1XMnrazH1kfte74uE0To6qrYUI3A0mbvUynJ/6pm/muJwn/j8NX9VXeq3i1hN8IZ1RTxvrFe0FG96S7HQLG5aKIWlEa8RYjbEvfySvU+NMzaeGHiQXzhYpiOl3S96vVA6VfX3derRHoiIiIiI6h1vfomIiIgoN3jzS0RERES5UUM5v5VUTL5JyzORmA/e8Nf6clMXH2nv9soeNnoIpjSqnxMTZpFcx/IOW/c3/CheWPakKpXtpfK7hsR5aLvc8UT89zt17hqtXMohsU4SOZ436pzDeGikMdYfimxfc4RY0rOzJc2EWOvnTLkZxJf00Axmuu3kchazTP4neXmeyvn18nUXiljluQ4R8QoTTv1DxHpmxaQZ3ooZCi4k5ax0RfsW8T7QdQ2111gRh/aF2Mbia7213vq/eNbML9Swdd8dMDVemPyqv/lbRF623J0n3anqIeul2nmePB70EJSS3AehnOjQjHz6deUdarQy1ExqYmQ6e4r6/Z6Y4e2vVx/lFZ2ArdGyUq+xtXdt5pNfIiIiIsoN3vwSERERUW7kNO2hGPJxveyG8bub2nUUw7mssFd117yU1G1We90E6WVdd39795hNxNIQf9X+Q+N4uF9kx8TdPsZ8LEpqNe2h9oaHiQXqMuyXLrT/jPf5Nvbf3mqv7RLP/ravORC+S9K9FwkWyV235eqeL9Yt/uLgM+L4BXnC+l3qg54c5+IJZoDa5qyU763TANIKpYxUQug6EJppL7Qd+SXV5K31lRg6bNsn3vTKBk+Kv8te6DTUK/MmlBvyF7EwG+nJa7Eewkx+Nll/fcyHUhvaJcQtrVsv5OfY2S9qikMzSs/iJo6ja/RMi6Hh4hoDn/wSERERUW7w5peIiIiIcoM3v0RERESUGw2c8xvKzcoih1APvyRyFJflLUexmNzUUoZB09uXeWFqOJwpIm9rhD/NqsHlYklPM530flm0Zam5u3V6HD10gQsNzo//bp5WK44TcSMMM1RLihnKK4uc1tKOaXuuGH7pPFGgRkS7TIzLNAEvFFGvpDzXYo63audLh6Zi1nmrcmgy/RmTclr9v29jprvYdvefjz2/8d4uNov/7G9myKCE99ZDHKZtB11f2Q4yf7sTkunzoF7zekPkZxrlF00Ty9PKfU9UX/jkl4iIiIhygze/RERERJQbDZz2UMxj/FK6ufPXTZCsmH1Ryn4LvSZUVswQO2nxWCmd3g/cL+VTjjSSUHd12tQA/3Xmexe3vP2X1Pa8ScJK7bqW762PvdB5nZQ60Zq6rMwqiFMa9Hu0POtoQbtAWeeEsvlqvTglzCy4xi9aIBf0fkoaMlKnZkj6uJHrho7hUKqDFNo/K6tLo+H1VuKTXyIiIiLKDd78EhEREVFu8OaXiIiIiHKjijm/tTR1K3NhKifrYcTKodQc46zV0jlCjauUfN1ihlJLa1lCXA6l5neWmutcLIvk8z1tXmxo6uNSpwcObT+Ub5wk7RB8+v1kHBoKLoTX1zzjk18iIiIiyg3e/BIRERFRbhhrbeXezJiPAMwC0A3AxxV742R5qsdG1tp1stpY1JZfIj/7L626akuek4kqVQ+2ZfmxLVsvb/XIui1r5bsSyFdbJrZjRW9+3Zsa84q1dmDF35j1yFSt1LtW6gHUVl2KUSv1Zj1ar1bqznq0Xq3UnfVonVqqd63Updr1YNoDEREREeUGb36JiIiIKDeqdfN7c5XeV2M9WqdW6l0r9QBqqy7FqJV6sx6tVyt1Zz1ar1bqznq0Ti3Vu1bqUtV6VCXnl4iIiIioGpj2QERERES5UdGbX2PMPsaY6caYGcaYERV+778aYxYYY6aIv3U1xjxpjHk7+n+XMtdhA2PMs8aYqcaYN4wxp1ajHlmoVlvWQjtG78m2bP37si0zlPfra/SebMvWv3fV27JR2hHg9bVW27JiN7/GmFUB/C+AfQH0A3CUMaZfpd4fwEgA+6i/jQDwtLW2N4Cno+VyWgbgTGttPwA7APhFtA8qXY9WqXJbjkT12xFgW2ZhJNiWmeD11WFbtt5IVL8t674dgaq35UhUvx2BWm1La21F/gOwI4DHxfJvAPymUu8fvWcTgClieTqA9aJ4PQDTK1yfhwEMrXY96q0ta60d2ZZsy2r/V+12ZFuyLdmOtdeWtdaOtdSWlUx7WB/AB2L5w+hv1dTDWjs3iucB6FGpNzbGNAEYAOClatajRLXWllXdf2zLTLEtS1Nr7QiwLUvFthTquB2B2mtLnpMR/uAtYgv//KjI0BfGmI4AHgBwmrX282rVoxFVev+xLcuHbdk42JaNg9+VjSHv52Qlb35nA9hALPeK/lZN840x6wFA9P8F5X5DY0wbFA6Au6y1/6hWPVqp1tqyKvuPbVkWbMvS1Fo7AmzLUrEt0RDtCNReW/KcjFTy5vdlAL2NMRsbY9oCOBLAIxV8/5Y8AuDYKD4WhVyUsjHGGAC3AnjTWntVteqRgVpry4rvP7Zl2bAtS1Nr7QiwLUuV+7ZskHYEaq8teU42q3Ci834A3gLwDoCzK/ze9wCYC2ApCnk3JwBYG4VfGb4N4CkAXctch8EoPNp/HcDk6L/9Kl2Pem7LWmhHtiXbshb/y/v1lW3ZOG3ZKO1YzbashXas5bbkDG9ERERElBv8wRsRERER5QZvfomIiIgoN3jzS0RERES5wZtfIiIiIsoN3vwSERERUW7w5rcFxpjDjTFvGGO+NcYMFH8faox51Rjzn+j/exT5+kHGmMnRf68ZYw4WZX81xiwwxkxpzbaMMRsYY541xkyNXndqlvum3mTQllsbY/4drTfKGNM5+ntbY8zfor+/ZowZIl5zhDHm9eh9rxB/3zBqm0lR+X6i7DfGmBnGmOnGmO+Jv8+M3mOyMeaVjHdPXQm0ZRtjzG3RfnrTGPObhNffFe3fKdH51ib6+5pR274Wbf9H6nWdjTEfGmOuF397TKx/ozFm1ZXU8Whxvk6OyrfJeh/VM2PMmcYYa4zpFi33ic69r40xZwVeN9IY857Yt9ukeb0xZtXoXBwt/raHMWZidIzcZoxZTb1mO2PMMmPMYdl98sYQuFamvdZ2NcY8aYx5O/p/l+jvXYwxD0bXzAnGmP7R37dQ59TnxpjT1Da9Y0r8ne2oJF27RPmGxpjFSeeiKYxl/FL0PXafKYxrDGPM6tHyjKi8Kfp70j1MYrsaY35vjJkWHQsPGmPWiv6e6jvAU+0x8Fo5ftxqZdpuXwBbABgLYKD4+wAAPaO4P4DZRb6+fXOdATTPaNK8vCuAbQFMac22onjb6O+dUBhfsF+126qO2/JlALtF8fEALoriXwD4WxR3B/AqCv+YXBvA+wDWicpuA7BnFN8M4GdR3A/ATBG/BmB1ABujMB7kqlHZTADdqt0+Nd6WPwRwbxS3j/ZZUwuv3w+Aif67R7TFbwFcEcXrAPgEQFvxumsA3A3gevG3ztH/DQozFx0ZqqOqx5YA3ql2O9VKu0bb3gDA4wBmNR/v0Xm1HYBLAJwVeO1IAIe18Pfg6wGcEbXr6Gh5FQAfANg8Wr4QwAli/VUBPAPg0Zber17+K+P5mXStTHutvRLAiCgeIc7J3wM4P4r7AHi6hdeuCmAegI1Cx1QjtGOlr6+i/H4A/5d0LgL4u7gO3iiurz8HcGMUHwngvihOvB9KalcAe4vXXCGOkVTfAfK/ij75NcZ0MMb8M7rLn2KMOSL6+3bGmH9Ff59gjOlkjFnDxE/WJhljdo/WPc4Y84gx5hkAT0fb/Gv0uknGmINaW09r7ZvW2ukt/H2StXZOtPgGgHbGmNWLeP1X1tpl0eIaEHNZW2ufQ+FLt1XbstbOtdZOjOIvALwJYP3gBy5BXtoSwOYAnoviJwEcGsX9ULiAwlq7AMAiAAMBbALgbWvtR9F6T4nXWACdo3hNAM3vfxAKJ+7X1tr3AMwAMKjYz1qqem9LFPZrB1N4StcOwDcAPl9hJWsftREAE1CYarT59Z2MMQZARxTOw2XR5/ougB4AnlDbat7+agDaIj7/kuooHQXg3pWs02r10q6RPwH4f/CviQustS+jMEh/0UKvN8b0AvB9ALeIP68N4Btr7VvRsjzfAeAUFP6hU9FpWOuoHVu8VhZxrT0IhYcFiP4/LIrltXYagCZjTA/12j1R+AflLPG3FY6pSEXbsV7aL3TtMsYMA/AeCu3XUrkBsAcKN8iA336yXe8HsKcxxoTuhwSvXa21T4jXjId/DV/pd4C0WqiwDPYBMMda+33AdTe2BXAfgCOstS+bQlfJfwGcCsBaa7c0xvQB8IQxZvNoO9sC2Mpa+4kx5lIAz1hrjzeFR+ATjDFPWWu/bH5TY0wnAM8n1OmH1tqpJXyWQwFMtNZ+XcyLjDHbA/grgI0AHCMasmgr25YpdC8MAPBSqe8RkJe2fAOFk/chAIcjnqf9NQAHGmPuif723ej/zwDYItr3H6JwAWgbveYCFD77KQA6ANgr+vv6KJzIzT5E/A8WG73GArjJWntzCZ9vZeq9Le9HoY3movCv/tOttSv8Q1K8bxsAx0SfBQCuR2GqzTko9JYcYa391hizCoA/AhiOuK3kdh5H4R8pYxBf9NM4IqpvudVFu0Zf3LOtta8VvkNLcokx5jwUZowakeK6fDUKN0adxN8+BrCaMWagtfYVAIchOt+NMesDOBjA7ig8Ta6kumhHJF8rpdC1toe1dm4Uz0PhH51A4Vp7CIDnjTGDUPi+6wVgvnjtkSj05jTXvcVjqkrtWC/t1yJjTEcAvwYwFEBS+tHaABaJexD5HbY+Cj0qsNYuM8Z8Fq3/cYr7Ia9dleNR2IdAkd8BiCpTycf1m6PwOPoKALtEf9sSwIstrPsggD3E8vMAtgJwHKLu5ujvrwCYgnjavPcB9M2ovmPR8uP/76DQNb1pKa+3cRfDBABriL81QaU9tGJbHVHoij+EbVl6W6LQzfZEtC/PB7Aw+vtqKDxZmIzCnOSPAhgWlR2Awj84/o3CzdND0d/PAHBmFO8IYCoKXa3XAxgu3vNWRN1xANaP/t8dhS+BXdmWK6Q97AzgLgBtov00HcAmgdf/BcDVYvmwqC0NgM1QeMLRGcDJAP5ftM5xEGkP4rVroPAUaWjK4217AP/Jug3rtV1R+KJ6CcCa0fJMqDQfFP7RGEp7WC9qu9VReMJ0Xuj1APYH8OcoHoIo7SFa3jH67BMAXAxgcvT3/wOwQxSPRAW7y+uhHaNttnitFOUru9YuUsufRv/vDOBvUT3vQCG9YhuxXlsU/uHSY2XHVDXasV7aT2x7LPzr6x8A/MC2cC6JdboBmCGWN0B0LxPVs5coewcrnuMt3cN47arWPzvaV82zFBf1HWCtreyTX2vtW8aYbVHIvbvYGPN09AGK9aWIDYBDbaCrMcunhabQXfYggP+x1r6T9nWatfZNY8xiFHKgWvVDJr2t6MnWAwDustb+ozXbDrxnLtrSFrrZ9o7W3xyFrlLYwr9QTxfb+hcK+dWw1o4CMCr6+4kAlkernYDCUwBYa/9tjFkDhYvGbPhPSXpFf4O1tvn/C4wxD6LwpPE5ZKgB2vKHAB6z1i4FsMAY8yIKKSjvtvCe56OQ1/tT8ecfAbjcFq6iM4wx76HwRb4jgF2MMT9H4R+TbY0xi621I5pfaK1dYox5GIWnDk+mqGvoSUam6qRdN0Uhz735CV0vABONMYOstfPSVM7GTwu/Nsb8DclPp5rtjEKvzX4o/OOlszHmTmvtcGvtvwHsEn2OvVG4cQEKx9O9UR27AdjPGLPMWvtQmjq2Rp20Y+K1MlpO87053xiznrV2rjGmOQcUtpBi9KNoOwaFf5zKc3tfFJ4mNz8JTjymUIV2rJf2C9gewGHGmCsBrAXgW2PMEmvt9WKdhQDWMsasFn03uu8wxN9vH5pCWsKa0fpOwv2Qbtfmz3UcCv+A3TO6ZgNFfAfIN63YfwB6Irqzjyr/EAp39+8C2C76eycUnqqdAeBWG//LaRYK/7I/Dv4PTy5F4clZ878ABmRY37Hw/wW0FqIumBJfvzHiZO2NUOhmlYn4TUj55DdpWyicFLdDPNliW5belgC6R/9fJdqvx0fL7QF0iOKhAJ5r4TVdUPhXefMPaMYAOC6K+0ZtZlB4IiJ/8PYuCon+HQB0itbvAOBfAPZhW67Qlr9G/OPDDig8Ud+qhdf9ONqH7dTfbwBwQRT3QOFirZ9MuM+Hwo3welG8GgpdbyeH6iiOodlYyROJvLZrtL2ZLez7C7CSJ7/R/w0K6QyXp309Vnzy23zuro5CCsUeLbxmJCr75Lcu2hHJ18q019rfw//B25Xi9W2j+CcAblevuxfAj4o5pirZjvXSfmLbY5Hcyxw6l/4P/g/efh7Fv4D/g7e/R/HK7odWaFcUHh5NRfSDcvH3VN8B3mvK3fCqgt8D8DoKNwQvN+9gFHJvxkcnyHgUvlzWQKGr4z8AJgHYPVpXHwTtANwUrfcGxIWsFfU8GIWcla9RyCt6PPr7OSj862uy+K/5hL9FfJ6k1x8T1XEygImIusmjsntQyFdZGr32hFK2BWAwCnmizft5MoD92JYlt+WpKDzRfQvA5YgvNk0odK28icKP2jZSbTk1+u9I8fd+AF6M9s1kAHuLsrNR6A6aDmDf6G+bROu+Fu2Ps3lettiWHVG48L4R7fNfidc8iviX5suifdzc3udFf++JQnftf1Doohvewnu7z4fCDfLL0T6bAuA6xBfxFusYlQ0BML4cbVjP7arqPBNxF/W60b78HIUflH6IeJQN2a7PiLa7E0DHlb1etYm8+f09Cuf0dACnJdRxJCp781sX7Yjka2Xaa+3aKPyD420Urqldo7/vGG1zOoB/AOgi3rMDCk8R10xzTFWjHeuo/RKvXWKdC+CnEMnzcBMUUhdmoHA9Xj36+xrR8oyofJPo76H7oRbbNdrGB+I4ar6pTvwOSPqv+eAkIiIiImp4nOSCiIiIiHKDN79ERERElBu8+SUiIiKi3ODNLxERERHlBm9+iYiIiCg3ePNLRERERLnBm18iIiIiyg3e/BIRERFRbvx/52edDG6gt10AAAAASUVORK5CYII=\n","text/plain":["
"]},"metadata":{"tags":[],"needs_background":"light"}}]}]} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyTorch-Autoencoder using MNIST 2 | https://qiita.com/satolab/items/8efa513e7fd6cb41fdc5 3 | 4 | Powered by [satolab](https://qiita.com/satolab) 5 | 6 | ## Overview 7 | PyTorchを用いた,AutoencoderによるMNISTの異常検知プログラムです. 8 | 9 | 10 | 11 | ## Model 12 | 13 | 14 | ## Results 15 | - 10 epochs(input,output,difference) 16 | ![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/583727/c2921e0a-dee3-2964-42cd-f9cf97ebc320.png) 17 | 18 | 19 | ## Usage 20 | - main.pyで学習.save dirにサンプルが保存されます. 21 | Learn with main.py. The sample is saved in save dir. 22 | 23 | ## References 24 | 差分画像の計算と表示部分 25 | http://cedro3.com/ai/keras-autoencoder-anomaly/ 26 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import torch 4 | from torch import nn 5 | from torch.autograd import Variable 6 | from torch.utils.data import DataLoader,Dataset 7 | from torchvision import transforms 8 | from torchvision.datasets import MNIST 9 | import pylab 10 | import matplotlib.pyplot as plt 11 | 12 | class Mnisttox(Dataset): 13 | def __init__(self, datasets ,labels:list): 14 | self.dataset = [datasets[i][0] for i in range(len(datasets)) 15 | if datasets[i][1] in labels ] 16 | self.labels = labels 17 | self.len_oneclass = int(len(self.dataset)/10) 18 | 19 | def __len__(self): 20 | return int(len(self.dataset)) 21 | 22 | def __getitem__(self, index): 23 | img = self.dataset[index] 24 | return img,[] 25 | 26 | class Autoencoder(nn.Module): 27 | def __init__(self,z_dim): 28 | super(Autoencoder, self).__init__() 29 | self.encoder = nn.Sequential( 30 | nn.Linear(28 * 28, 256), 31 | nn.ReLU(True), 32 | nn.Linear(256, 128), 33 | nn.ReLU(True), 34 | nn.Linear(128, z_dim)) 35 | 36 | self.decoder = nn.Sequential( 37 | nn.Linear(z_dim, 128), 38 | nn.ReLU(True), 39 | nn.Linear(128, 256), 40 | nn.ReLU(True), 41 | nn.Linear(256, 28 * 28), 42 | nn.Tanh() 43 | ) 44 | 45 | def forward(self, x): 46 | z = self.encoder(x) 47 | xhat = self.decoder(z) 48 | return xhat 49 | 50 | z_dim = 64 51 | batch_size = 16 52 | num_epochs = 10 53 | learning_rate = 3.0e-4 54 | n = 6 #number of test sample 55 | cuda = True 56 | model = Autoencoder(z_dim) 57 | mse_loss = nn.MSELoss() 58 | optimizer = torch.optim.Adam(model.parameters(), 59 | lr=learning_rate, 60 | weight_decay=1e-5) 61 | 62 | if cuda: 63 | model.cuda() 64 | 65 | img_transform = transforms.Compose([ 66 | transforms.ToTensor(), 67 | transforms.Normalize((0.5, ), (0.5, )) # [0,1] => [-1,1] 68 | ]) 69 | train_dataset = MNIST('./data', download=True,train=True, transform=img_transform) 70 | train_1 = Mnisttox(train_dataset,[1]) 71 | train_loader = DataLoader(train_1, batch_size=batch_size, shuffle=True) 72 | losses = np.zeros(num_epochs) 73 | 74 | for epoch in range(num_epochs): 75 | i = 0 76 | for img,_ in train_loader: 77 | 78 | x = img.view(img.size(0), -1) 79 | 80 | if cuda: 81 | x = Variable(x).cuda() 82 | else: 83 | x = Variable(x) 84 | 85 | xhat = model(x) 86 | 87 | # 出力画像(再構成画像)と入力画像の間でlossを計算 88 | loss = mse_loss(xhat, x) 89 | losses[epoch] = losses[epoch] * (i / (i + 1.)) + loss * (1. / (i + 1.)) 90 | optimizer.zero_grad() 91 | loss.backward() 92 | optimizer.step() 93 | i += 1 94 | 95 | plt.figure() 96 | pylab.xlim(0, num_epochs) 97 | plt.plot(range(0, num_epochs), losses, label='loss') 98 | plt.legend() 99 | plt.savefig(os.path.join("./save/", 'loss.pdf')) 100 | plt.close() 101 | 102 | print('epoch [{}/{}], loss: {:.4f}'.format( 103 | epoch + 1, 104 | num_epochs, 105 | loss)) 106 | 107 | test_dataset = MNIST('./data', train=False,download=True, transform=img_transform) 108 | test_1_9 = Mnisttox(test_dataset,[1,9]) 109 | test_loader = DataLoader(test_1_9, batch_size=len(test_dataset), shuffle=True) 110 | 111 | for img,_ in test_loader: 112 | x = img.view(img.size(0), -1) 113 | 114 | if cuda: 115 | x = Variable(x).cuda() 116 | else: 117 | x = Variable(x) 118 | 119 | xhat = model(x) 120 | x = x.cpu().detach().numpy() 121 | xhat = xhat.cpu().detach().numpy() 122 | x = x/2 + 0.5 123 | xhat = xhat/2 + 0.5 124 | 125 | # サンプル画像表示 126 | plt.figure(figsize=(12, 6)) 127 | for i in range(n): 128 | # テスト画像を表示 129 | ax = plt.subplot(3, n, i + 1) 130 | plt.imshow(x[i].reshape(28, 28)) 131 | plt.gray() 132 | ax.get_xaxis().set_visible(False) 133 | ax.get_yaxis().set_visible(False) 134 | 135 | # 出力画像を表示 136 | ax = plt.subplot(3, n, i + 1 + n) 137 | plt.imshow(xhat[i].reshape(28, 28)) 138 | plt.gray() 139 | ax.get_xaxis().set_visible(False) 140 | ax.get_yaxis().set_visible(False) 141 | 142 | # 入出力の差分画像を計算 143 | diff_img = np.abs(x[i] - xhat[i]) 144 | 145 | # 入出力の差分数値を計算 146 | diff = np.sum(diff_img) 147 | 148 | # 差分画像と差分数値の表示 149 | ax = plt.subplot(3, n, i + 1 + n * 2) 150 | plt.imshow(diff_img.reshape(28, 28),cmap="jet") 151 | #plt.gray() 152 | ax.get_xaxis().set_visible(True) 153 | ax.get_yaxis().set_visible(True) 154 | ax.set_xlabel('score = ' + str(diff)) 155 | 156 | plt.savefig("./save/result.png") 157 | plt.show() 158 | plt.close() 159 | 160 | -------------------------------------------------------------------------------- /save/loss.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/satolab12/anomaly-detection-using-autoencoder-PyTorch/2581f7d8045b8e22cb1234e720dd757f21e3223e/save/loss.pdf -------------------------------------------------------------------------------- /save/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/satolab12/anomaly-detection-using-autoencoder-PyTorch/2581f7d8045b8e22cb1234e720dd757f21e3223e/save/result.png --------------------------------------------------------------------------------