├── .gitignore
├── ACGAN_intro_for_MNIST.ipynb
├── ACGAN_intro_for_cifar10.ipynb
├── CGAN_intro_for_MNIST.ipynb
├── DCGAN_intro_for_Fashion_MNIST.ipynb
├── DCGAN_intro_for_MNIST.ipynb
├── DCGAN_intro_for_cifar10.ipynb
├── LICENSE
├── README.md
├── gan
├── CycleGAN-keras.ipynb
├── dragan-keras.ipynb
├── pix2pix-keras-font.ipynb
└── wgan-keras.ipynb
├── pix2pix.ipynb
├── pix2pix_eager.ipynb
├── stylegan_train.ipynb
├── stylegan_use.ipynb
└── stylegan_use2.ipynb
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
--------------------------------------------------------------------------------
/DCGAN_intro_for_MNIST.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "accelerator": "GPU",
6 | "colab": {
7 | "name": "DCGAN-intro for MNIST",
8 | "provenance": [],
9 | "collapsed_sections": [],
10 | "include_colab_link": true
11 | },
12 | "kernelspec": {
13 | "display_name": "Python 3",
14 | "language": "python",
15 | "name": "python3"
16 | },
17 | "language_info": {
18 | "codemirror_mode": {
19 | "name": "ipython",
20 | "version": 3
21 | },
22 | "file_extension": ".py",
23 | "mimetype": "text/x-python",
24 | "name": "python",
25 | "nbconvert_exporter": "python",
26 | "pygments_lexer": "ipython3",
27 | "version": "3.6.8"
28 | }
29 | },
30 | "cells": [
31 | {
32 | "cell_type": "markdown",
33 | "metadata": {
34 | "id": "view-in-github",
35 | "colab_type": "text"
36 | },
37 | "source": [
38 | "
"
39 | ]
40 | },
41 | {
42 | "cell_type": "code",
43 | "metadata": {
44 | "colab": {
45 | "base_uri": "https://localhost:8080/",
46 | "height": 35
47 | },
48 | "id": "ckOHCuiArv5c",
49 | "outputId": "b796dc2e-b9c0-48ef-c0cb-b6ad6eafbbd9"
50 | },
51 | "source": [
52 | "import tensorflow as tf\n",
53 | "\n",
54 | "import matplotlib.pyplot as plt\n",
55 | "import numpy as np\n",
56 | "import os\n",
57 | "import PIL\n",
58 | "import time\n",
59 | "from skimage.io import imshow\n",
60 | "\n",
61 | "from IPython.display import display\n",
62 | "tf.__version__"
63 | ],
64 | "execution_count": 1,
65 | "outputs": [
66 | {
67 | "output_type": "execute_result",
68 | "data": {
69 | "application/vnd.google.colaboratory.intrinsic+json": {
70 | "type": "string"
71 | },
72 | "text/plain": [
73 | "'2.4.1'"
74 | ]
75 | },
76 | "metadata": {
77 | "tags": []
78 | },
79 | "execution_count": 1
80 | }
81 | ]
82 | },
83 | {
84 | "cell_type": "code",
85 | "metadata": {
86 | "colab": {
87 | "base_uri": "https://localhost:8080/"
88 | },
89 | "id": "1V2PJHrDrxf2",
90 | "outputId": "e45bcfed-45b1-46fc-a953-71e706b3676b"
91 | },
92 | "source": [
93 | "(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()\n"
94 | ],
95 | "execution_count": 2,
96 | "outputs": [
97 | {
98 | "output_type": "stream",
99 | "text": [
100 | "Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz\n",
101 | "11493376/11490434 [==============================] - 0s 0us/step\n"
102 | ],
103 | "name": "stdout"
104 | }
105 | ]
106 | },
107 | {
108 | "cell_type": "code",
109 | "metadata": {
110 | "colab": {
111 | "base_uri": "https://localhost:8080/"
112 | },
113 | "id": "L0iNo21Arykj",
114 | "outputId": "1d172878-0d12-4da4-8080-1f4538f166ae"
115 | },
116 | "source": [
117 | "train_images.dtype, train_images.shape"
118 | ],
119 | "execution_count": 3,
120 | "outputs": [
121 | {
122 | "output_type": "execute_result",
123 | "data": {
124 | "text/plain": [
125 | "(dtype('uint8'), (60000, 28, 28))"
126 | ]
127 | },
128 | "metadata": {
129 | "tags": []
130 | },
131 | "execution_count": 3
132 | }
133 | ]
134 | },
135 | {
136 | "cell_type": "code",
137 | "metadata": {
138 | "colab": {
139 | "base_uri": "https://localhost:8080/",
140 | "height": 314
141 | },
142 | "id": "uAcCsSinus1d",
143 | "outputId": "f87865f8-ca2f-42b4-e1a1-dd3251e91acf"
144 | },
145 | "source": [
146 | "imshow(train_images[0])"
147 | ],
148 | "execution_count": 4,
149 | "outputs": [
150 | {
151 | "output_type": "execute_result",
152 | "data": {
153 | "text/plain": [
154 | ""
155 | ]
156 | },
157 | "metadata": {
158 | "tags": []
159 | },
160 | "execution_count": 4
161 | },
162 | {
163 | "output_type": "display_data",
164 | "data": {
165 | "image/png": "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\n",
166 | "text/plain": [
167 | ""
168 | ]
169 | },
170 | "metadata": {
171 | "tags": [],
172 | "needs_background": "light"
173 | }
174 | }
175 | ]
176 | },
177 | {
178 | "cell_type": "code",
179 | "metadata": {
180 | "id": "uSDHJmuwtxT4"
181 | },
182 | "source": [
183 | "def img_to_float(img):\n",
184 | " return (np.float32(img)[..., None]-127.5)/127.5\n",
185 | "def img_to_uint8(img):\n",
186 | " return np.uint8(img*127.5+128).clip(0, 255)[...,0]"
187 | ],
188 | "execution_count": 5,
189 | "outputs": []
190 | },
191 | {
192 | "cell_type": "code",
193 | "metadata": {
194 | "colab": {
195 | "base_uri": "https://localhost:8080/",
196 | "height": 314
197 | },
198 | "id": "9yGVvMP9vUn0",
199 | "outputId": "450d4a4b-603f-464c-8f5b-c271b0b67863"
200 | },
201 | "source": [
202 | "train_img_f32 = img_to_float(train_images)\n",
203 | "imshow(img_to_uint8(train_img_f32[0]))"
204 | ],
205 | "execution_count": 6,
206 | "outputs": [
207 | {
208 | "output_type": "execute_result",
209 | "data": {
210 | "text/plain": [
211 | ""
212 | ]
213 | },
214 | "metadata": {
215 | "tags": []
216 | },
217 | "execution_count": 6
218 | },
219 | {
220 | "output_type": "display_data",
221 | "data": {
222 | "image/png": "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\n",
223 | "text/plain": [
224 | ""
225 | ]
226 | },
227 | "metadata": {
228 | "tags": [],
229 | "needs_background": "light"
230 | }
231 | }
232 | ]
233 | },
234 | {
235 | "cell_type": "code",
236 | "metadata": {
237 | "id": "oSrY5b6ev0kO"
238 | },
239 | "source": [
240 | "BUFFER_SIZE = train_img_f32.shape[0]\n",
241 | "BATCH_SIZE = 32\n",
242 | "train_dataset = tf.data.Dataset.from_tensor_slices(train_img_f32).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)"
243 | ],
244 | "execution_count": 7,
245 | "outputs": []
246 | },
247 | {
248 | "cell_type": "code",
249 | "metadata": {
250 | "id": "ZaJFsMs2v1R1"
251 | },
252 | "source": [
253 | "from tensorflow.keras.layers import Dense, BatchNormalization, LeakyReLU, Reshape, Conv2DTranspose\n",
254 | "latent_dim = 100\n",
255 | "generator = tf.keras.Sequential([\n",
256 | " Dense(7*7*256, use_bias=False, input_shape=(latent_dim,)),\n",
257 | " BatchNormalization(),\n",
258 | " LeakyReLU(),\n",
259 | " Reshape((7, 7, 256)),\n",
260 | " Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False),\n",
261 | " BatchNormalization(),\n",
262 | " LeakyReLU(),\n",
263 | " Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False),\n",
264 | " BatchNormalization(),\n",
265 | " LeakyReLU(),\n",
266 | " Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh')\n",
267 | "]\n",
268 | ")"
269 | ],
270 | "execution_count": 10,
271 | "outputs": []
272 | },
273 | {
274 | "cell_type": "code",
275 | "metadata": {
276 | "id": "kqdl08rxvWiS"
277 | },
278 | "source": [
279 | "from tensorflow.keras.layers import Conv2D, Dropout, Flatten\n",
280 | "discriminator = tf.keras.Sequential([\n",
281 | " Conv2D(64, (5, 5), strides=(2, 2), padding='same', input_shape=(28,28, 1)),\n",
282 | " BatchNormalization(),\n",
283 | " LeakyReLU(),\n",
284 | " Dropout(0.3),\n",
285 | " Conv2D(128, (5, 5), strides=(2, 2), padding='same'),\n",
286 | " BatchNormalization(),\n",
287 | " LeakyReLU(),\n",
288 | " Dropout(0.3),\n",
289 | " Flatten(),\n",
290 | " Dense(1)]\n",
291 | ")"
292 | ],
293 | "execution_count": 11,
294 | "outputs": []
295 | },
296 | {
297 | "cell_type": "code",
298 | "metadata": {
299 | "id": "nd8zcnhjzKWq"
300 | },
301 | "source": [
302 | "loss_fn = tf.keras.losses.BinaryCrossentropy(from_logits=True)\n",
303 | "def generator_loss(generated_output):\n",
304 | " return loss_fn(tf.ones_like(generated_output), generated_output)"
305 | ],
306 | "execution_count": 12,
307 | "outputs": []
308 | },
309 | {
310 | "cell_type": "code",
311 | "metadata": {
312 | "id": "fgWHoC6FzbUA"
313 | },
314 | "source": [
315 | "def discriminator_loss(real_output, generated_output):\n",
316 | " # [1,1,...,1] with real output since it is true and we want our generated examples to look like it\n",
317 | " real_loss = loss_fn(tf.ones_like(real_output), real_output)\n",
318 | "\n",
319 | " # [0,0,...,0] with generated images since they are fake\n",
320 | " generated_loss = loss_fn(tf.zeros_like(generated_output), generated_output)\n",
321 | "\n",
322 | " total_loss = real_loss + generated_loss\n",
323 | "\n",
324 | " return total_loss"
325 | ],
326 | "execution_count": 13,
327 | "outputs": []
328 | },
329 | {
330 | "cell_type": "code",
331 | "metadata": {
332 | "id": "KCYwQqxgz8o_"
333 | },
334 | "source": [
335 | "generator_optimizer = tf.keras.optimizers.Adam(1e-4)\n",
336 | "discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)"
337 | ],
338 | "execution_count": 14,
339 | "outputs": []
340 | },
341 | {
342 | "cell_type": "code",
343 | "metadata": {
344 | "id": "Rb2-KU6K1J0I"
345 | },
346 | "source": [
347 | "EPOCHS = 50\n",
348 | "num_examples_to_generate = 16\n",
349 | "\n",
350 | "# We'll re-use this random vector used to seed the generator so\n",
351 | "# it will be easier to see the improvement over time.\n",
352 | "random_vector_for_generation = tf.random.normal([num_examples_to_generate,\n",
353 | " latent_dim])"
354 | ],
355 | "execution_count": 15,
356 | "outputs": []
357 | },
358 | {
359 | "cell_type": "code",
360 | "metadata": {
361 | "id": "Xnu88uGR1etc"
362 | },
363 | "source": [
364 | "@tf.function\n",
365 | "def train_step(images):\n",
366 | " # generating noise from a normal distribution\n",
367 | " noise = tf.random.normal([BATCH_SIZE, latent_dim])\n",
368 | " \n",
369 | " with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:\n",
370 | " generated_images = generator(noise, training=True)\n",
371 | " \n",
372 | " real_logits = discriminator(images, training=True)\n",
373 | " generated_logits = discriminator(generated_images, training=True)\n",
374 | " \n",
375 | " gen_loss = generator_loss(generated_logits)\n",
376 | " disc_loss = discriminator_loss(real_logtis, generated_logits)\n",
377 | " \n",
378 | " gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)\n",
379 | " gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)\n",
380 | " \n",
381 | " generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))\n",
382 | " discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))"
383 | ],
384 | "execution_count": null,
385 | "outputs": []
386 | },
387 | {
388 | "cell_type": "code",
389 | "metadata": {
390 | "colab": {
391 | "base_uri": "https://localhost:8080/",
392 | "height": 706
393 | },
394 | "id": "1DPQkOVx2yon",
395 | "outputId": "0f39dec5-31b6-4ffb-99d9-c0020ba94748"
396 | },
397 | "source": [
398 | "for epoch in range(15):\n",
399 | " start_time = time.time()\n",
400 | " for images in train_dataset:\n",
401 | " train_step(images)\n",
402 | " fake = generator(random_vector_for_generation, training=False)\n",
403 | " fake_concat = np.transpose(img_to_uint8(fake), [1,0,2]).reshape((28,-1))\n",
404 | " print(epoch, time.time()-start_time)\n",
405 | " display(PIL.Image.fromarray(fake_concat))\n",
406 | " \n",
407 | " "
408 | ],
409 | "execution_count": null,
410 | "outputs": [
411 | {
412 | "output_type": "stream",
413 | "text": [
414 | "0 11.480594396591187\n"
415 | ],
416 | "name": "stdout"
417 | },
418 | {
419 | "output_type": "display_data",
420 | "data": {
421 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAcAAAAAcCAAAAADTxTBPAAAQTklEQVR4nH1aaZQdxXX+blV3v/fmvadZNJrRSIxmEBJIQkKAg1jEbiG2QPA5EMBY2OYgjuzEhsRgs8dExjFbHHwOhjhhszEhMYfFYKxYh9XsByQbgUAKCAlpJJC1jpjlzbzumx9V1VXV/R71p6vq1t2qbt26dbvQWpBCCCJCvlRko15VypIIyOKpVjVsQhBA0eknEt6wcpC2KUOASlJ3UBAWMkzbClbQLNvWyNAkEhlgSQAgIQQAQEjhwKoBkQgrpcDIk4EZbpSZgwmR1GxSvqloFQcvq32RAJIT2opWrbTW1VZurUQNF4PESB1MTMwpmh1XS9gd601OLWEGiF2ogY/GYHBjvHG24jGHOZoKTOxjgWuJoTixELliATxaZ4eqX4ZjaGAAkYHXEgAINXwCAgc2EgOy3B8nAADBvpxWO0bgUR2JoWaNhC8lwKOxwWMAQlScBRxjgMR+g7W0x0q7b7xS7BbI0tPshWi2WSCa9DeAeTSylu4N9FpB0BzWDK9Yysolm2rgwvJ66p2iW1O6XbqCSITC7Icg9PRrIpnPL/vN4omWik+FQs+EZFortnQePLmJiiIBnKUlQQ6iv+Q+BR/WNj90YY1spSFeEDYZlnrSPP/amNoV1hmzz9ATNLE1s7szspgRF020YwEGUZKMaoo/PaK5DhlXkalRjp8pheOk02LIQoi6IUlEM9P1jkW0eTsDQLiogEyRIjCTJarFk46aU53b8Dwh0SpdYTypozi+tYlGJIpSUMGVFUC/lmPuFPUNMnhm4Sa1WjrmbNEg8csJDWWhY46sOuue23dBf2rm5FgI9b86R1UUWIgo0jPY8o87xtd4rkJ1t+qRXq85UXWRh3SVHa1cWU748QxXbNl7it6RdOiXzzy5v7wiXasg6tDzN5hsUbwtYlg03iHsv+mEr6596da/tkQd+rOO6yhIaTeMK2YwwnyFK4yjT6UtKBcnzS1703jQUyVV+e8SABSPzGxEESqBCw+1p13FltClXtgzmjE0tVQ0bdWHP5hiucnQO/zlAU89YhdQBMYd07F39gMAnbxMTY4sd1YUYnEo4ZHjkC0TlixSS+MvYFR0daGLt99ql94L7eiha51lgFy85R69SrPrq05Yfu7hv01hxXZtx5J5DwARnm4RA2kUlJXp4pCHn+/oszyshRYuiEoBUUe6k1yxr1vHw+4Wc2ZMlkEkw3/5srcri4t18zchgGigzccjSLXA3bXvpYRkSbMQAsDswZGxVASzgGrgp59c76xZINwTmaa/f98L6QwTBTqgFY+sUl3hhpImk3qmQea7W5EtVP7m/iKrLYBiEDktccMOZ6L8Bfzj/S5mccHH31G1M5O4TCTpEqtDGh8nw2WAvt1/hUWUKZAKAqWtb3T1Wx4kTBR9972BlELcc1IKsyS69629NgRQzfhJANRfAEDBlg3e7cGYjGgHgOEk79jLANDzOu9wlNe1eZ2EjjgZW2MJAgDp6RRvrJ3quGw/oKpu233HyQ2OiD28U1Ve3ysyeMRczwcQpOdNFESbuhNoQOD5+WDwPRfJ1qb2nT+43O2Neo/VMOYBAMAD6XBjjzouPal+wyOWqPRuP+KiP0xztrwIdUN8/3YBIHxrSSoCQapZquyqzwaAYOORBiaMDxTdBQBo2fKEw8RELvfU1SEQc3eqirk7FQBUbxpLfpbKYr4X3nwW4ZdJstrKTURBYJZMlCIgSE3CN6oLd+2cbFvpDN3E/Knqee3TDKz1L8wLMsMBivQ+m/PIdaEH1BPWp/hGb3/V4Z5GZr1vju3bt22OK1loLOZR5ggACrdkjfBIfZoWk5psgKgY3vmnCc75UenSch72X7cBQLjVnh9EreqQGIhHAACbx+43sKDUplh0ViQAdA91OGpU1NS/wOpyNdneQdO7diAAOnRNfSwVxpxVS1efGwIr4nFvS4uOSOukYtO2dHWlGQIAeGbwcUdbQ6LAzFMBAPOuWOLDztscx5d7lAAQBR2KQbh57DGflhp10GXKgpb9net8zZji8eOcjL8Mt5gLy//oWZl/1w9TmJr0Fz7Xhv9dXuUghkISQHqXTt7wsbMhw5JZwH/bWAUA7LKRH8m51xMADNYVc+Z3jBLhxB7FbOo0AoBw0F3AFtLj1cIdxmnwnHqi6kQC6IhT/y/j7ui0fVUCEJ5tYztAyHCajnYmdwIArpjl44kutckf/jB/mkG8sZ3fV9Xv/8C/vS39/LN0PuZbWUSpXTVe5kSncXx+v9+i5BisNbq+hcHukdrP7/WDZ/Wp83RVibd8O4UVhBDBXUmix3jpFX3xJC3QSh52I5CC8USfDxEAiL3TrRItS08FANSGVQ/zKRompInAl5wNALT/sF1AMj6bOVa0+Ru+EgAuuRoAVYt/m9F/0pgKo+kET/mg2q0Gdo3fAABY9U4G70YV8W0dyl8HxIuPTTT13R5d0JN1Lfg1b/zVYkeFUOuQaB2Cq71YlFY+aHTMxwUAQFd+97Afzsr3DxpvVOdBS1GCovCa2rhqfYczCwgApHdnjbc7sDT6+V2yGQDQ/156tohC3+kqXh1Vh3HA6T04vQuJe3sBoPC/G21oRkYl5nXqm+Kl1n/wi4sBQEzp9BeQBkZ/AwCodLrdotim5Y9ZpaI2/b2H17l8GQFAV31fbkLpxQ0LVW3+k3/e7N4RhTxx9dmqdU2SLLaiUJp/YN4FAHj0Hdeji/3OVs01zRYQED2XfyXfaVOJfK7tlkSROP/NGw3PvR6OQlAMO5PkLAdmAum5cdwGAMe/u9QSLR/bRQAwLVEedIO1izSKv+huAoDnPv6m1S7UV8AJzJOVrNNSmGZ7zGtPEACcPsWmdgEA5ZGNygVe420kWWklo/fnABCu8d3TgvNCAKAtyQvIltlj76tJnjKU7DzK4gBR9OCj2hLHedShKALdvZQThTt6uMWjakHb3UHMYzl+ulTOXlHMdQ4yXwwAiL1dJgGBDi3bNAMyUYFL4ECOJzlNPSTcdN4pANC1777U1yAsTlaTKPk5AJjR+3wWD51hBQAOGDraXm7J2YGXAUAfp75JydJ63x4VkdCKKzNeJriskwBQX/wnt1s4NIcBgJ52z05Z0aHRKZ/x+uyZJA56RidAEo6dOziBOnpm9OhGElc9flpDWVug4a8bmJS9Xzta10diPsCXs7K/Obsvvrc2E9nyilkc5mddPKMJAJrKj3k47tYv/cFYlAd7bp1yswmvt55QFnS9L3kWwNDYGTKL13WpatUTJ3KwuSsVbpU/4IN8LVYkOpR/ML7F85Qmr1i9Y0M84CUGUppxLAAQbXAvC+ZS2LtlaPcp8EuQRuFtbF2B4lXV6ynbHvZDxjTi1IyDK/TVDQDJ/W/T58yC5AIAwMW9BibaF2hZ5o9/vmm/jCwU/iyerapNk7tElLk0u55h1q54fR7Wcp9y1rczO9BAK95y19oe4Drm1Vm88Ld3AQCeYRZZGIA7aucAQNtgUvVgU8fjX6nWGG/3jMKs08LPPn6k1EgHKp6vZ/SteVZjaU6Cn4++W8yonP5m+Om+4VF3k4GINN7K+tgn58CDqU+HEeKfhuJFBiZK8w7W8BF9uP+nOc5IVrXbnJrwh7l/PYecfL7SdyKzZ7kNfrbI3hRoO4sr6zU3Padha99SljjGvNLC9F2AXmsBgCHmb2XxHlo/FwCQsJvlN/w2xucBAHbEn/iwFRwrR/0q8ytO4JD63pbXhrx/AynN4swZ2nRu3uGE/AV9Xl4Sx9n9B2pR03g788YMxFxJRcLxLxrxm3GndiyVJLE6yIkLdS5yWAdU8tdXGTxZSm8CnMmuEqpr/lkJ87SN51XJ/wiT/YemVREYC53w/EvbPcuWACDXXa9aMfPRFqaTiN+4xwjUllXwRG2VzOPIFnqB9bH/0arbPTz6+tg2MjQdb0EkdGw0573DAABRh4sHunm7Sa4tGUwsTER6Addr91KYae96ZpexZ2Sanx42VH8z+/MTAOj+T3QCaYDXtacwUdiv29BUFvdAcoylqWt5fqJw2rqbAQD361QMYA6sQBQyv+MXv2svnilVenzzfYd7US8BQOEX2lcnHDtU1KxEq6UWiLN46W+frD0BwFFmcZ5MfiI8POr93feMkiMuzdQKnxxVvM/5QDh4CHbyh6r5QJKMTcvj7YmV2/nVgA1hNYyYH1K1TpnDo8tUH031sOi5LYr/IuYpjpxBUXVLY7fM1t1pmgXmeZ4AgGi99Ok+PV7P5dZNll/GhG4wGT8BIYypTXp24BLhEwWEPH2p1jdJ3nRIKKWuPdMImV9AXTbyKHJlN3MJAETMv87gtakkTrCJEzfOTgOHq0beVvWda90FpIXj8QcAgFkJJ/9hXaiZtOkjbwkACGvv546UrzOrs+PgJ4wLIv8vxtEd4vg0+UMA0P7ylaoVe4Ytw1AAKH5pXIduLTYESF/EbGKdVbE/D4OZt96kjirWmanp8WYNE+Tk5ImKUct2c58j5xfHY3u3zkvFM4JK+RXt0m+sv+bGPwIACgNpVsV16eQQeSlpdBUa1Xmhbam7SDmXiwAweTcnjQIVmr9tVO2uiSPtLhwLx+tPAwBqzHvzT4No9h0RAGp9b3gCMoXGjZGtTYMHIVxntPypM4pVH+mQx5VhB8wfO90qMhXLtw2r9MBZbFL1VpaZQ4mOkq0vKEy/XN9ZmBWn+pjmSO58ClmV4Wk7nXtZahePD/wxY5oEkM7GFA9vO8MLcAQAyOW6T2eUDJ519mcNz2qUi5DLNmphs68vlD31jdb/oSWLBADBj3qXhQBQjc7zpaVFt6vrf8yJM9dmkBJozttD1+f/zYrJ8TOqdoFNggcTHE87OuwmOJQL1XMYJTYETfnIE69Rzc94/G9ysgTqdCse4x5JpQO01dV0XnarE9fbQDQkQYLOavRc6dzV++c7NaL48+WZY9Q754KRL/kww0/se7UBJ0D/vRP+OWcpLuShfOgMAFJv2CMX5V7ZiSkVQJCo874G/NRnWTLS04hsFAL4ycWRc21xHyHKLSP5HKrm2vGXAfcXrkY6UM3wbcxLcjBdQt7hdAgTo6FbW+7Vbr4+91SqAdFCMxkBFKa3Z3pcQqUjfFT74LHvWxPxBSVMxqNMl0Kc+ft8msIb1V5uBhLvPVtqBsOP1lzYFFZc/+/+jdk2gtnNUpqQPX2eCv4E5z2MLVcNX+3ifcHKEFGU++eti/NUMo6zQPu6tiUabArDlOuiug9Lz+YjduxuwhgAcOmeS90TkmAemrau/OiL8BAUa81A5Qcu9KJXT7Cejx5tSvNrPa84s03OGzIxbcA/ImxVyK7NTfOdIGBjU+CpO/7VHZpZaek8KyXZ0hM39kiZdxpNYblHzZA2SmvrT8XVeCksjJoSBwAc4P+FIbP0gX4zaGXz6QiPrg/z3095rh7d+QRyWjrO8J4judwyI505oyzMl2XXJ83dWsmTJRt+eI/GovbepoJLyslgYdJspfwC2sdQ6NL+pcvB0zDzP8oq7NPxPZNMV968KBLNFtBD9BWQjRbQXMDy99y0RJNcOu7zowbGm77wMBae4adbtxzY/HG0P6UmPd4IGEXlyc3o/D/5oEWVJt4YEQAAAABJRU5ErkJggg==\n",
422 | "text/plain": [
423 | ""
424 | ]
425 | },
426 | "metadata": {
427 | "tags": []
428 | }
429 | },
430 | {
431 | "output_type": "stream",
432 | "text": [
433 | "1 7.339160680770874\n"
434 | ],
435 | "name": "stdout"
436 | },
437 | {
438 | "output_type": "display_data",
439 | "data": {
440 | "image/png": "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\n",
441 | "text/plain": [
442 | ""
443 | ]
444 | },
445 | "metadata": {
446 | "tags": []
447 | }
448 | },
449 | {
450 | "output_type": "stream",
451 | "text": [
452 | "2 7.42051887512207\n"
453 | ],
454 | "name": "stdout"
455 | },
456 | {
457 | "output_type": "display_data",
458 | "data": {
459 | "image/png": "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\n",
460 | "text/plain": [
461 | ""
462 | ]
463 | },
464 | "metadata": {
465 | "tags": []
466 | }
467 | },
468 | {
469 | "output_type": "stream",
470 | "text": [
471 | "3 7.219139337539673\n"
472 | ],
473 | "name": "stdout"
474 | },
475 | {
476 | "output_type": "display_data",
477 | "data": {
478 | "image/png": "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\n",
479 | "text/plain": [
480 | ""
481 | ]
482 | },
483 | "metadata": {
484 | "tags": []
485 | }
486 | },
487 | {
488 | "output_type": "stream",
489 | "text": [
490 | "4 7.330965757369995\n"
491 | ],
492 | "name": "stdout"
493 | },
494 | {
495 | "output_type": "display_data",
496 | "data": {
497 | "image/png": "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\n",
498 | "text/plain": [
499 | ""
500 | ]
501 | },
502 | "metadata": {
503 | "tags": []
504 | }
505 | },
506 | {
507 | "output_type": "stream",
508 | "text": [
509 | "5 7.140302658081055\n"
510 | ],
511 | "name": "stdout"
512 | },
513 | {
514 | "output_type": "display_data",
515 | "data": {
516 | "image/png": "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\n",
517 | "text/plain": [
518 | ""
519 | ]
520 | },
521 | "metadata": {
522 | "tags": []
523 | }
524 | },
525 | {
526 | "output_type": "stream",
527 | "text": [
528 | "6 7.489628314971924\n"
529 | ],
530 | "name": "stdout"
531 | },
532 | {
533 | "output_type": "display_data",
534 | "data": {
535 | "image/png": "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\n",
536 | "text/plain": [
537 | ""
538 | ]
539 | },
540 | "metadata": {
541 | "tags": []
542 | }
543 | },
544 | {
545 | "output_type": "stream",
546 | "text": [
547 | "7 7.206034898757935\n"
548 | ],
549 | "name": "stdout"
550 | },
551 | {
552 | "output_type": "display_data",
553 | "data": {
554 | "image/png": "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\n",
555 | "text/plain": [
556 | ""
557 | ]
558 | },
559 | "metadata": {
560 | "tags": []
561 | }
562 | },
563 | {
564 | "output_type": "stream",
565 | "text": [
566 | "8 7.269426584243774\n"
567 | ],
568 | "name": "stdout"
569 | },
570 | {
571 | "output_type": "display_data",
572 | "data": {
573 | "image/png": "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\n",
574 | "text/plain": [
575 | ""
576 | ]
577 | },
578 | "metadata": {
579 | "tags": []
580 | }
581 | },
582 | {
583 | "output_type": "stream",
584 | "text": [
585 | "9 7.2453320026397705\n"
586 | ],
587 | "name": "stdout"
588 | },
589 | {
590 | "output_type": "display_data",
591 | "data": {
592 | "image/png": "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\n",
593 | "text/plain": [
594 | ""
595 | ]
596 | },
597 | "metadata": {
598 | "tags": []
599 | }
600 | },
601 | {
602 | "output_type": "stream",
603 | "text": [
604 | "10 7.195350646972656\n"
605 | ],
606 | "name": "stdout"
607 | },
608 | {
609 | "output_type": "display_data",
610 | "data": {
611 | "image/png": "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\n",
612 | "text/plain": [
613 | ""
614 | ]
615 | },
616 | "metadata": {
617 | "tags": []
618 | }
619 | },
620 | {
621 | "output_type": "stream",
622 | "text": [
623 | "11 7.311627149581909\n"
624 | ],
625 | "name": "stdout"
626 | },
627 | {
628 | "output_type": "display_data",
629 | "data": {
630 | "image/png": "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\n",
631 | "text/plain": [
632 | ""
633 | ]
634 | },
635 | "metadata": {
636 | "tags": []
637 | }
638 | },
639 | {
640 | "output_type": "stream",
641 | "text": [
642 | "12 7.385121583938599\n"
643 | ],
644 | "name": "stdout"
645 | },
646 | {
647 | "output_type": "display_data",
648 | "data": {
649 | "image/png": "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\n",
650 | "text/plain": [
651 | ""
652 | ]
653 | },
654 | "metadata": {
655 | "tags": []
656 | }
657 | },
658 | {
659 | "output_type": "stream",
660 | "text": [
661 | "13 7.402552843093872\n"
662 | ],
663 | "name": "stdout"
664 | },
665 | {
666 | "output_type": "display_data",
667 | "data": {
668 | "image/png": "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\n",
669 | "text/plain": [
670 | ""
671 | ]
672 | },
673 | "metadata": {
674 | "tags": []
675 | }
676 | },
677 | {
678 | "output_type": "stream",
679 | "text": [
680 | "14 7.439730405807495\n"
681 | ],
682 | "name": "stdout"
683 | },
684 | {
685 | "output_type": "display_data",
686 | "data": {
687 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAcAAAAAcCAAAAADTxTBPAAAOU0lEQVR4nO06eZjUxbHVv2NmL9iLvWBBLlG5XBRQUIOYREBBjSYEk2hUDPLM88WHiaiAR6JRPp8BjzwEAwHUF1BEI4eCCmzEm0sixwLLCrvL3rOzc/6O7qr8MXvMTPfsh0dM3vfZ/+xs1a+6qrqqq6qrGxikHv9OuO7GNyIL6wJ9jfwYU+EY0wxT0zpgWjdzfjv+pYMZarCebnxrtX/7wTx3rL1ctWO9T/xtUdE3bkBPrzxdM9P1b5rv1zeY0bvwy0bxL8Gt5AUH7Z8oMPpe139DvCCnJZTWbnGWqd7Vnkca1/ZKLU36WgujvhrLd28cNy1D65Y3S+VlzEwzvd5uiPXlLUNSkJ4eBwXh+fcHBF/0JRywGx2ZntU7TYVn2StcJHxFgfMsiDaXni6DzqENKojp2qvpuJLhz1q5GznYU0ls3N3iupE3+xQ/7gQLu8AFu6u2fB6yj805s2xchoqu/4xCk6kyu+49b12Dv3nPJZ4Usms+/I1alCvLOssCM7PQkOgzLvcqCT33nWoNOzx4tZph6qFdfl4qo+fXEhGeKJIQrOjid2wi8YEpE/UojwZ/ploUlS92Ac0xV+cBAMB9iKov9adOHKwUdEKB8t4QEXy7BgAwwj3Qxbr30ciJw42WEKGm1obzFSJl7vFVzPvN7qpXb7p6WmYy0hj0niUqh6TYQXqI7lUjNtn2wgzNOHth+dHjuyd2kqfnmowxpp153PlQRWfsCLvNa15xMaR0GWaYOdlqZxrduFhhBgAo2ciREBEflwj7HI0gkXhXsR8y1wj+SbpiOm3YqLFJcO9FP+yEeLPHxH4fIlK5aP6SOb3O5cRljPcEUiA39vsx99FO+CInenu2xzQziqa3IV8ga2/+xRLccqL+BktgKFfCF9hkT485t0TcV+DtCikB2CJOhALdqqf7GV0xWF9VuWXGyJyepY+6JBQbhj1ku43Tsi/lhB5IYKmN2FrrdxCFCH1PyfE9sbefyrSTHXTfzTLGc4omM/S8JIiIWs9SuOdqQeFxSfMxAABPVdhX7wgicq/VgLHMC9bveeUCWRc/oUqc/J/nMTNIQsY8gOS0+4HX4lkdYPNYXeeu2oN4s0THplQKEWpYO7zP1REiZ47EtZCTdaF6B7KNwlUnZON3ISG4c+qSJILrDlW/PERjYDQT9ZDJCqt4QyljwwQJT+J8Nx+yBRERkfizMreEcLVHAdd8WOcBgDyLgslVxY0uEZG9vKc8oWGTmKLUTKsNtVT6OBERYeRkOPT291U7n7WRpSLPnpyjmdXUJiEyWgnnt/9+AFs7RdImdok9F3G0ROgttzFylwcg7UGHyB4vffA4UkWx2oAD/VitxLD+FS4Pb5JDk+bNixFMIBoj090fsKcBsGVIofZVieVmbXQrF45/50cWER5WLZih0g0APEJ4AQDWcNyWZCetnogwuushhQ8WITWoQzV7tq3mHAYAbLrFheB7VW4DwFw6pIKXzh6Tlh+k9yTE7wWFhsZ+DgyKxXEzdf38K2G2RNj3GMeTOoD2Nkdsvk6SWmsh3KHwUQAo2Mjxc6UBjWUc+XtSQo0bM4iGSkB9n7VLAygNIlZ3GBAAQOuzQ6AzAQCWEFFQrkYALiMcpdyZrtABoNgvgsn8ejpEJA6tf0lWGmYRjgQAAG3QFYWJKg6trWvfEyVthJ+ndTBKnMCLtFMhDpz123GDb3DxxmS4VksUGAEAkHFYYGSAihaaiUtryi5r4c4SnRXXEYmDctplq5Hcj0oVa1OwzxGInyorB/MoUbCvUoj28SbRORJwYMidDsBOIuGW9t3LAMAse6ONiyYDALYTUbQjSsQLtY5ok7K8aaWpAPBAMDw3ydXYOEGE7tEX930sHYVYNdbHJutb6djrEmp3Y01VcezXUEGoruEAJhJdoYKfs2Hm6Oe5I62a1yVcpQFo21zCwN3KDeMVFJAQnvnNvhP/+1+fIaFPVQRcZaMIbCqQ4PpDNhKitb5QpgHQtyNWdnv24wpZ2DN2awmwXyNReOHYLrT+q2bLafu5BjDBIiL/lQqnWUDkL1YxGotiXbr3B1t/nZwB2SQkQvvAAQdFdFlWgjQeP06KffS6SyQOJaRr42H/sRyNMU8lUaOqUAUA9glFVKcatjC0eeSdLcskRAZS0AMwLEJEkeuVPQA2A3GBBB3w5J9m7a6xkKgmS0GU3iCEdWSavNa3WiisaLhq2e1y3Qpg/M7FaArdYvIS9ZOAWeUVVxjGQkEUfe0/Z8aUYACg/SjEna1eZv6fICKMPq2QtLegsDpk7yOKbrtv5uBkHbQxSGSvWxwmIiK+It7fvLXOKACA3GMxjrclEgeJiEJ1SM4FKfQbxOlJFZwdQWfj4q3ycbwI6QlNexaJMDBbXVcMridXyh5sxLSzfhxBIjylKAohbScXrm+xIrLefXL/Zel5w+98bvPzc66ZNOXSyQlMs25qRGzpJgdWUETe7hf+8arcfmsEkf3sjJnnxnlhxk4u3u+RuytWhzobFKJmWhQpUbIqsYnQOjhFKjYKkaju+k8xVtxG4/HmizaPVkdiKEQnuVthvhGo2VuNVJWi0CluI/cMJWp80LbsSnllyhDvyPyFS2i1fNKpR+cZSmNa1tTjAgN5Eqs+w8bsIyJqUPEz93Pk0X2KPaZdOq2fBgD6sO/d9kyLg2ifEadL2qS5a/1ItE2pAwBbSviWrPvwJ/9c7hKR8+a484Z64/G5bSiaotReu/+3wkPzW9H5DyUzz25BROJvfZPbQToSVT5gEboNASQnJ169oW3Uzs2NtAqxSDVxb0FH1AYsCxKuVLdCgY2NolgnbYm+HN0qX9RurXn/Fkm9jIEDi+6p4cJtkGKPduXGRk5E7ncVvHpsdxHRXaUolI2C9u4aY4ZRONVCNy4XsqKybB20cqIPlDoYhxD9D8qJ7Kc2EhFFb81K61uYmEFKHCJCJySI+HpVh0O7Meo+r2KWNfmGa8e/7mL9H2Y/mBTUOZFdzzH8y1sCSCcSl02bHRA8uP/V15evaEGuLFUYp2alfvAOUmWaGgXA3hCibV6ykdgxIkInHGl9QdXuZNp1YRT2fmnRPPs5YbSeRxVGyqtwkIjE8dHyiqVlxPueXodvJ3CL/a0iVJXDpo/QN0nuj7JaIiK01+ak9ZpSnFQCZEbQWT5xBRJW9lfMCWC04BYFOLO23AMA5ubDVX7/Q4mTnkFEiFi/PETER6omZSPWV0cFNivLI+aSTymK1yFLzvCdY1qDG1wpFQjnBYRjRQQGB6mpeviFG35W2pyTHKKmAXPRkUum9LdcRETe9M7Ke5PNmzUqJ/62Y6rdoCq5eljUIEc779+JjuYAQPLZqZATHp+3cf8rv7zzqXljks8EWp6pDwgQOcrqHEAXtFwBrsA6AADI3xB23I+TOgvXcyF464tRIlQe2sBc4iCi/bwyHPbA+EQeJ+5WpD1SzdulyP0RHnpYjj+6qWmlFkXkij9Gts22fNMk6GMcralDfarG5GVtSOgev/vs3hMfnpXgMGzQgZrXLk4z0s8o6GFoWsELjtrhetrkyqeMF4iiI1Sfr0B8Z9CYFQcP792wePpV/eX04t0sCCtS5JYionEy1HToqZgs5Y6wVifHNdb7+j7GECRqUB5n2S2tiMjry2L/Jjqj+VckR9WGucQlN646ZQwAWEnHQhjzIyiaxqe43rnGFTPVGPhBk//jwRL07JqWT5c2CjwpHxTmO4TOh2XpmqaXvrws/orVuyLqCvvUu0drqitbG2t9lit1tJlmZIyuFdgmGTDDJnxQJaAeJGfulev2bXt09qRR/foXSHuXzbSJrFT3THcRqrrjFt4JAMDmtqHwXysvG2OsLxKVqaY0ZtXYAjH8XLv7JlzujvosIMjJ7wJ0/OgVQlzT9anpZcBAeyo8FwCA5W+1Ee2tqY5YZgMqb2kAYPCunb9XlN95t34Q4c6RC+WpDgi01188IMdrmgXPbb+j46IZQMu6p8ZFQu4KRCIUUfk+lBX+6lBQCN98KZRsRvwo7nPd6LiJLBbk7HjprZdH53tNTdd1XTq4HUTi69WtR4BNigYxANwjjnlYxrj/qUfiW1TnGsYKuPpmpHBpXch2BW+Y3qV7x8jcGwqFOdqyO5lvuKK6y67ahFh5/n2Bp3LMnPt8AkVgoerIBgAA+hGUeu7tI3/n28/kK+Ba/yd23JUlu2bPU8I9Pvvyybf/qNTUdW+8y2iZw+f4ERERhR08/idVo23IUVfwlgnScqcHsDJewv59OohHOsLZf1VB37T25TLS+yV2IbPCRP7JKWIPi9KrKrjm4xWvHYi6SOJ95W02sCJBliJeT1pyoClic+HsG9KO7bz0Nv8YEY7tIgZu7rR8O9J4JGhVxYV/VtJPBwDw1iIhdzjn9tEUdzsAAOwvIlUDRH+z+uRPlMbVlC+vvhPhoYOLZ48dXaTsERvf/fjvu15/vH+qV1tsnm19fot8E18aCMcnQDa4V8cnuQuf+WzDRZnejglZzoJZCTkrL0pYo6wHASAXaaoSUWjFDnT2Yyk8G/pxOiGHkILffljd5gjBfU/kt4ukGYYOwMw+qy0UAhGtdQVJhJ5V0cjKREfRTQ0AgJ1ZEXV9Tw/PStBJegihDRma6uXGxL11S7rrbiWNglXzhqRJYewLDDPbq6DWhyWGj0QNzKw4f2CexAnyXMJGVeMOAOBcjKZwJaMcEVsvTq2J4eevKZJj8eIDTY4bLR+dZPismypDjuMKjCzvJZHdZmM0+UCidfO+R/8Cj95Y5nfGfIGnQl/BdP+k4RVEDanaczda15zuPJJm2X+4SNl4TsvMVDwGKl4ajOwaq36+AQWLV57/7UvSFIMtrX0k5YO8jNMPLl/RNbXc3ur3WADAtO7f/3390vy/Gl+Trl/VgMod9g8xnhMJXzl9LQAAAABJRU5ErkJggg==\n",
688 | "text/plain": [
689 | ""
690 | ]
691 | },
692 | "metadata": {
693 | "tags": []
694 | }
695 | }
696 | ]
697 | }
698 | ]
699 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GAN_Tutorial
--------------------------------------------------------------------------------
/gan/dragan-keras.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "kernelspec": {
6 | "display_name": "Python 3",
7 | "language": "python",
8 | "name": "python3"
9 | },
10 | "language_info": {
11 | "codemirror_mode": {
12 | "name": "ipython",
13 | "version": 3
14 | },
15 | "file_extension": ".py",
16 | "mimetype": "text/x-python",
17 | "name": "python",
18 | "nbconvert_exporter": "python",
19 | "pygments_lexer": "ipython3",
20 | "version": "3.5.3"
21 | },
22 | "colab": {
23 | "name": "dragan-keras.ipynb",
24 | "version": "0.3.2",
25 | "provenance": []
26 | }
27 | },
28 | "cells": [
29 | {
30 | "cell_type": "code",
31 | "metadata": {
32 | "id": "HwZckpzYY5x1",
33 | "colab_type": "code",
34 | "colab": {}
35 | },
36 | "source": [
37 | "import os\n",
38 | "os.environ['KERAS_BACKEND']='tensorflow' # 也可以使用 tensorflow\n",
39 | "#os.environ['THEANO_FLAGS']='floatX=float32,device=cuda,exception_verbosity=high'\n",
40 | "os.environ['THEANO_FLAGS']='floatX=float32,device=cuda,optimizer=fast_compile'"
41 | ],
42 | "execution_count": 0,
43 | "outputs": []
44 | },
45 | {
46 | "cell_type": "markdown",
47 | "metadata": {
48 | "id": "FjC5lbRqY5x_",
49 | "colab_type": "text"
50 | },
51 | "source": [
52 | "modifed from https://github.com/martinarjovsky/WassersteinGAN "
53 | ]
54 | },
55 | {
56 | "cell_type": "code",
57 | "metadata": {
58 | "id": "8JBgz3JzY5yC",
59 | "colab_type": "code",
60 | "colab": {}
61 | },
62 | "source": [
63 | "import keras.backend as K\n",
64 | "K.set_image_data_format('channels_first')\n",
65 | "from keras.models import Sequential, Model\n",
66 | "from keras.layers import Conv2D, ZeroPadding2D, BatchNormalization, Input\n",
67 | "from keras.layers import Conv2DTranspose, Reshape, Activation, Cropping2D, Flatten\n",
68 | "from keras.layers.advanced_activations import LeakyReLU\n",
69 | "from keras.activations import relu\n",
70 | "from keras.initializers import RandomNormal\n",
71 | "conv_init = RandomNormal(0, 0.02)\n",
72 | "gamma_init = RandomNormal(1., 0.02)\n"
73 | ],
74 | "execution_count": 0,
75 | "outputs": []
76 | },
77 | {
78 | "cell_type": "code",
79 | "metadata": {
80 | "id": "s6-9m626Y5yL",
81 | "colab_type": "code",
82 | "colab": {}
83 | },
84 | "source": [
85 | "def DCGAN_D(isize, nz, nc, ndf, n_extra_layers=0):\n",
86 | " assert isize%2==0\n",
87 | " _ = inputs = Input(shape=(nc, isize, isize))\n",
88 | " _ = Conv2D(filters=ndf, kernel_size=4, strides=2, use_bias=False,\n",
89 | " padding = \"same\",\n",
90 | " kernel_initializer = conv_init, \n",
91 | " name = 'initial.conv.{0}-{1}'.format(nc, ndf) \n",
92 | " ) (_)\n",
93 | " _ = LeakyReLU(alpha=0.2, name = 'initial.relu.{0}'.format(ndf))(_)\n",
94 | " csize, cndf = isize// 2, ndf\n",
95 | " while csize > 5:\n",
96 | " assert csize%2==0\n",
97 | " in_feat = cndf\n",
98 | " out_feat = cndf*2\n",
99 | " _ = Conv2D(filters=out_feat, kernel_size=4, strides=2, use_bias=False,\n",
100 | " padding = \"same\",\n",
101 | " kernel_initializer = conv_init,\n",
102 | " name = 'pyramid.{0}-{1}.conv'.format(in_feat, out_feat) \n",
103 | " ) (_)\n",
104 | " if 0: # toggle batchnormalization\n",
105 | " _ = BatchNormalization(name = 'pyramid.{0}.batchnorm'.format(out_feat), \n",
106 | " momentum=0.9, axis=1, epsilon=1.01e-5,\n",
107 | " gamma_initializer = gamma_init, \n",
108 | " )(_, training=1) \n",
109 | " _ = LeakyReLU(alpha=0.2, name = 'pyramid.{0}.relu'.format(out_feat))(_)\n",
110 | " csize, cndf = (csize+1)//2, cndf*2\n",
111 | " _ = Conv2D(filters=1, kernel_size=csize, strides=1, use_bias=False,\n",
112 | " kernel_initializer = conv_init,\n",
113 | " name = 'final.{0}-{1}.conv'.format(cndf, 1) \n",
114 | " ) (_)\n",
115 | " outputs = Flatten()(_)\n",
116 | " return Model(inputs=inputs, outputs=outputs)\n"
117 | ],
118 | "execution_count": 0,
119 | "outputs": []
120 | },
121 | {
122 | "cell_type": "code",
123 | "metadata": {
124 | "id": "M0862KAxY5yT",
125 | "colab_type": "code",
126 | "colab": {}
127 | },
128 | "source": [
129 | "def DCGAN_G(isize, nz, nc, ngf, n_extra_layers=0):\n",
130 | " cngf= ngf//2\n",
131 | " tisize = isize\n",
132 | " while tisize > 5:\n",
133 | " cngf = cngf * 2\n",
134 | " assert tisize%2==0\n",
135 | " tisize = tisize // 2\n",
136 | " _ = inputs = Input(shape=(nz,))\n",
137 | " _ = Reshape((nz, 1,1))(_)\n",
138 | " _ = Conv2DTranspose(filters=cngf, kernel_size=tisize, strides=1, use_bias=False,\n",
139 | " kernel_initializer = conv_init, \n",
140 | " name = 'initial.{0}-{1}.convt'.format(nz, cngf))(_)\n",
141 | " _ = BatchNormalization(gamma_initializer = gamma_init, momentum=0.9, axis=1, epsilon=1.01e-5,\n",
142 | " name = 'initial.{0}.batchnorm'.format(cngf))(_, training=1)\n",
143 | " _ = Activation(\"relu\", name = 'initial.{0}.relu'.format(cngf))(_)\n",
144 | " csize, cndf = tisize, cngf\n",
145 | " \n",
146 | "\n",
147 | " while csize < isize//2:\n",
148 | " in_feat = cngf\n",
149 | " out_feat = cngf//2\n",
150 | " _ = Conv2DTranspose(filters=out_feat, kernel_size=4, strides=2, use_bias=False,\n",
151 | " kernel_initializer = conv_init, padding=\"same\",\n",
152 | " name = 'pyramid.{0}-{1}.convt'.format(in_feat, out_feat) \n",
153 | " ) (_)\n",
154 | " _ = BatchNormalization(gamma_initializer = gamma_init, \n",
155 | " momentum=0.9, axis=1, epsilon=1.01e-5,\n",
156 | " name = 'pyramid.{0}.batchnorm'.format(out_feat))(_, training=1)\n",
157 | " \n",
158 | " _ = Activation(\"relu\", name = 'pyramid.{0}.relu'.format(out_feat))(_)\n",
159 | " csize, cngf = csize*2, cngf//2\n",
160 | " _ = Conv2DTranspose(filters=nc, kernel_size=4, strides=2, use_bias=False,\n",
161 | " kernel_initializer = conv_init, padding=\"same\",\n",
162 | " name = 'final.{0}-{1}.convt'.format(cngf, nc)\n",
163 | " )(_)\n",
164 | " outputs = Activation(\"tanh\", name = 'final.{0}.tanh'.format(nc))(_)\n",
165 | " return Model(inputs=inputs, outputs=outputs)\n"
166 | ],
167 | "execution_count": 0,
168 | "outputs": []
169 | },
170 | {
171 | "cell_type": "markdown",
172 | "metadata": {
173 | "id": "eQBeIh2bY5yb",
174 | "colab_type": "text"
175 | },
176 | "source": [
177 | "Parameters"
178 | ]
179 | },
180 | {
181 | "cell_type": "code",
182 | "metadata": {
183 | "id": "CK2upJ5xY5ye",
184 | "colab_type": "code",
185 | "colab": {}
186 | },
187 | "source": [
188 | "nc = 3\n",
189 | "nz = 100\n",
190 | "ngf = 64\n",
191 | "ndf = 64\n",
192 | "n_extra_layers = 0\n",
193 | "Diters = 5\n",
194 | "λ = 10\n",
195 | "\n",
196 | "imageSize = 32\n",
197 | "batchSize = 64\n",
198 | "lrD = 1e-4\n",
199 | "lrG = 1e-4\n"
200 | ],
201 | "execution_count": 0,
202 | "outputs": []
203 | },
204 | {
205 | "cell_type": "markdown",
206 | "metadata": {
207 | "id": "HYbTJfm-Y5yn",
208 | "colab_type": "text"
209 | },
210 | "source": [
211 | "print models"
212 | ]
213 | },
214 | {
215 | "cell_type": "code",
216 | "metadata": {
217 | "id": "VSfTYSaGY5yq",
218 | "colab_type": "code",
219 | "colab": {}
220 | },
221 | "source": [
222 | "netD = DCGAN_D(imageSize, nz, nc, ndf, n_extra_layers)\n",
223 | "netD.summary()"
224 | ],
225 | "execution_count": 0,
226 | "outputs": []
227 | },
228 | {
229 | "cell_type": "code",
230 | "metadata": {
231 | "id": "opAyrS2yY5yx",
232 | "colab_type": "code",
233 | "colab": {}
234 | },
235 | "source": [
236 | "netG = DCGAN_G(imageSize, nz, nc, ngf, n_extra_layers)\n",
237 | "netG.summary()"
238 | ],
239 | "execution_count": 0,
240 | "outputs": []
241 | },
242 | {
243 | "cell_type": "code",
244 | "metadata": {
245 | "id": "ENxECYB8Y5y4",
246 | "colab_type": "code",
247 | "colab": {}
248 | },
249 | "source": [
250 | "from keras.optimizers import RMSprop, SGD, Adam"
251 | ],
252 | "execution_count": 0,
253 | "outputs": []
254 | },
255 | {
256 | "cell_type": "markdown",
257 | "metadata": {
258 | "id": "iYBF6jt3Y5zB",
259 | "colab_type": "text"
260 | },
261 | "source": [
262 | "compute Wasserstein loss and gradient penalty"
263 | ]
264 | },
265 | {
266 | "cell_type": "code",
267 | "metadata": {
268 | "id": "j341F66AY5zF",
269 | "colab_type": "code",
270 | "colab": {}
271 | },
272 | "source": [
273 | "netD_real_input = Input(shape=(nc, imageSize, imageSize))\n",
274 | "noisev = Input(shape=(nz,))\n",
275 | "netD_fake_input = netG(noisev)\n",
276 | "\n",
277 | "ϵ_input = K.placeholder(shape=(None, nc,imageSize,imageSize))\n",
278 | "netD_mixed_input = Input(shape=(nc, imageSize, imageSize), tensor=netD_real_input + ϵ_input)\n",
279 | "\n",
280 | "\n",
281 | "loss_real = K.mean(netD(netD_real_input))\n",
282 | "loss_fake = K.mean(netD(netD_fake_input))\n",
283 | "\n",
284 | "grad_mixed = K.gradients(netD(netD_mixed_input), [netD_mixed_input])[0]\n",
285 | "norm_grad_mixed = K.sqrt(K.sum(K.square(grad_mixed), axis=[1,2,3]))\n",
286 | "grad_penalty = K.mean(K.square(norm_grad_mixed -1))\n",
287 | "\n",
288 | "loss = loss_fake - loss_real + λ * grad_penalty\n",
289 | "\n",
290 | "\n",
291 | "training_updates = Adam(lr=lrD).get_updates(netD.trainable_weights,[],loss)\n",
292 | "netD_train = K.function([netD_real_input, noisev, ϵ_input],\n",
293 | " [loss_real, loss_fake], \n",
294 | " training_updates)"
295 | ],
296 | "execution_count": 0,
297 | "outputs": []
298 | },
299 | {
300 | "cell_type": "markdown",
301 | "metadata": {
302 | "id": "x1eefes6Y5zS",
303 | "colab_type": "text"
304 | },
305 | "source": [
306 | "loss for netG"
307 | ]
308 | },
309 | {
310 | "cell_type": "code",
311 | "metadata": {
312 | "id": "H7ITg8GpY5zU",
313 | "colab_type": "code",
314 | "colab": {}
315 | },
316 | "source": [
317 | "loss = -loss_fake \n",
318 | "training_updates = Adam(lr=lrG).get_updates(netG.trainable_weights,[], loss)\n",
319 | "netG_train = K.function([noisev], [loss], training_updates)\n"
320 | ],
321 | "execution_count": 0,
322 | "outputs": []
323 | },
324 | {
325 | "cell_type": "markdown",
326 | "metadata": {
327 | "collapsed": false,
328 | "id": "NzqYN2o9Y5zf",
329 | "colab_type": "text"
330 | },
331 | "source": [
332 | "Download CIFAR10 if needed"
333 | ]
334 | },
335 | {
336 | "cell_type": "code",
337 | "metadata": {
338 | "id": "8KI6vzTrY5zi",
339 | "colab_type": "code",
340 | "colab": {}
341 | },
342 | "source": [
343 | "from PIL import Image\n",
344 | "import numpy as np\n",
345 | "import tarfile\n",
346 | "\n",
347 | "# Download dataset\n",
348 | "url = \"https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz\"\n",
349 | "import os\n",
350 | "import urllib\n",
351 | "from urllib.request import urlretrieve\n",
352 | "def reporthook(a,b,c):\n",
353 | " print(\"\\rdownloading: %5.1f%%\"%(a*b*100.0/c), end=\"\")\n",
354 | "tar_gz = \"cifar-10-python.tar.gz\"\n",
355 | "if not os.path.isfile(tar_gz):\n",
356 | " print('Downloading data from %s' % url)\n",
357 | " urlretrieve(url, tar_gz, reporthook=reporthook)\n",
358 | "\n",
359 | "import pickle\n",
360 | "train_X=[]\n",
361 | "train_y=[]\n",
362 | "tar_gz = \"cifar-10-python.tar.gz\"\n",
363 | "with tarfile.open(tar_gz) as tarf:\n",
364 | " for i in range(1, 6):\n",
365 | " dataset = \"cifar-10-batches-py/data_batch_%d\"%i\n",
366 | " print(\"load\",dataset)\n",
367 | " with tarf.extractfile(dataset) as f:\n",
368 | " result = pickle.load(f, encoding='latin1')\n",
369 | " train_X.extend( result['data'].reshape(-1,3,32,32)/255*2-1)\n",
370 | " train_y.extend(result['labels'])\n",
371 | " train_X=np.float32(train_X)\n",
372 | " train_y=np.int32(train_y)\n",
373 | " dataset = \"cifar-10-batches-py/test_batch\"\n",
374 | " print(\"load\",dataset)\n",
375 | " with tarf.extractfile(dataset) as f:\n",
376 | " result = pickle.load(f, encoding='latin1')\n",
377 | " test_X=np.float32(result['data'].reshape(-1,3,32,32)/255*2-1)\n",
378 | " test_y=np.int32(result['labels'])\n",
379 | " "
380 | ],
381 | "execution_count": 0,
382 | "outputs": []
383 | },
384 | {
385 | "cell_type": "markdown",
386 | "metadata": {
387 | "id": "yE7ODz5bY5zp",
388 | "colab_type": "text"
389 | },
390 | "source": [
391 | "also using test_X"
392 | ]
393 | },
394 | {
395 | "cell_type": "code",
396 | "metadata": {
397 | "id": "vVV6oIdaY5zr",
398 | "colab_type": "code",
399 | "colab": {}
400 | },
401 | "source": [
402 | "train_X = np.concatenate([train_X, test_X])\n",
403 | "train_X = np.concatenate([train_X[:,:,:,::-1], train_X])"
404 | ],
405 | "execution_count": 0,
406 | "outputs": []
407 | },
408 | {
409 | "cell_type": "markdown",
410 | "metadata": {
411 | "id": "j5zFXXoRY5zz",
412 | "colab_type": "text"
413 | },
414 | "source": [
415 | "utility to show images"
416 | ]
417 | },
418 | {
419 | "cell_type": "code",
420 | "metadata": {
421 | "id": "aH7Z0q41Y5z1",
422 | "colab_type": "code",
423 | "colab": {}
424 | },
425 | "source": [
426 | "from IPython.display import display\n",
427 | "def showX(X, rows=1):\n",
428 | " assert X.shape[0]%rows == 0\n",
429 | " int_X = ( (X+1)/2*255).clip(0,255).astype('uint8')\n",
430 | " # N*3072 -> N*3*32*32 -> 32 * 32N * 3\n",
431 | " int_X = np.moveaxis(int_X.reshape(-1,3,32,32), 1, 3)\n",
432 | " int_X = int_X.reshape(rows, -1, 32, 32,3).swapaxes(1,2).reshape(rows*32,-1, 3)\n",
433 | " display(Image.fromarray(int_X))\n",
434 | "# 訓練資料, X 的前 20 筆\n",
435 | "showX(train_X[:20])\n",
436 | "print(train_y[:20])\n",
437 | "name_array = np.array(\"airplane car bird cat deer dog frog horse boat truck\".split(' '))\n",
438 | "print(name_array[train_y[:20]])"
439 | ],
440 | "execution_count": 0,
441 | "outputs": []
442 | },
443 | {
444 | "cell_type": "code",
445 | "metadata": {
446 | "id": "FYuV48BHY5z8",
447 | "colab_type": "code",
448 | "colab": {}
449 | },
450 | "source": [
451 | "fixed_noise = np.random.normal(size=(batchSize, nz)).astype('float32')"
452 | ],
453 | "execution_count": 0,
454 | "outputs": []
455 | },
456 | {
457 | "cell_type": "code",
458 | "metadata": {
459 | "scrolled": false,
460 | "id": "TKmySl5HY50E",
461 | "colab_type": "code",
462 | "colab": {}
463 | },
464 | "source": [
465 | "import time\n",
466 | "t0 = time.time()\n",
467 | "niter = 100\n",
468 | "gen_iterations = 0\n",
469 | "errG = 0\n",
470 | "targetD = np.float32([2]*batchSize+[-2]*batchSize)[:, None]\n",
471 | "targetG = np.ones(batchSize, dtype=np.float32)[:, None]\n",
472 | "for epoch in range(niter):\n",
473 | " i = 0\n",
474 | " # 每個 epoch 洗牌一下\n",
475 | " np.random.shuffle(train_X)\n",
476 | " batches = train_X.shape[0]//batchSize\n",
477 | " while i < batches:\n",
478 | " if gen_iterations < 25 or gen_iterations % 500 == 0:\n",
479 | " _Diters = 100\n",
480 | " else:\n",
481 | " _Diters = Diters\n",
482 | " j = 0\n",
483 | " while j < _Diters and i < batches:\n",
484 | " j+=1\n",
485 | " real_data = train_X[i*batchSize:(i+1)*batchSize]\n",
486 | " i+=1\n",
487 | " noise = np.random.normal(size=(batchSize, nz)) \n",
488 | " ϵ = real_data.std() * np.random.uniform(-0.5,0.5, size=real_data.shape) \n",
489 | " ϵ *= np.random.uniform(size=(batchSize, 1,1,1))\n",
490 | " errD_real, errD_fake = netD_train([real_data, noise, ϵ]) \n",
491 | " errD = errD_real - errD_fake\n",
492 | " \n",
493 | " if gen_iterations%500==0:\n",
494 | " print('[%d/%d][%d/%d][%d] Loss_D: %f Loss_G: %f Loss_D_real: %f Loss_D_fake %f'\n",
495 | " % (epoch, niter, i, batches, gen_iterations,errD, errG, errD_real, errD_fake), time.time()-t0)\n",
496 | " fake = netG.predict(fixed_noise)\n",
497 | " showX(fake, 4)\n",
498 | " \n",
499 | " noise = np.random.normal(size=(batchSize, nz)) \n",
500 | " errG, = netG_train([noise])\n",
501 | " gen_iterations+=1 \n",
502 | " "
503 | ],
504 | "execution_count": 0,
505 | "outputs": []
506 | },
507 | {
508 | "cell_type": "code",
509 | "metadata": {
510 | "id": "IAVN_iV6Y50K",
511 | "colab_type": "code",
512 | "colab": {}
513 | },
514 | "source": [
515 | ""
516 | ],
517 | "execution_count": 0,
518 | "outputs": []
519 | },
520 | {
521 | "cell_type": "code",
522 | "metadata": {
523 | "id": "Fo-8KzKiY50Q",
524 | "colab_type": "code",
525 | "colab": {}
526 | },
527 | "source": [
528 | ""
529 | ],
530 | "execution_count": 0,
531 | "outputs": []
532 | }
533 | ]
534 | }
--------------------------------------------------------------------------------
/pix2pix.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "colab_type": "text",
7 | "id": "view-in-github"
8 | },
9 | "source": [
10 | "
"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "metadata": {
16 | "colab_type": "text",
17 | "id": "0TD5ZrvEMbhZ"
18 | },
19 | "source": [
20 | "##### Copyright 2018 The TensorFlow Authors.\n",
21 | "\n",
22 | "Licensed under the Apache License, Version 2.0 (the \"License\").\n",
23 | "\n",
24 | "# Pix2Pix: An example with tf.keras and eager\n",
25 | "\n"
26 | ]
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "metadata": {
31 | "colab_type": "text",
32 | "id": "e1_Y75QXJS6h"
33 | },
34 | "source": [
35 | "## Import TensorFlow and enable eager execution"
36 | ]
37 | },
38 | {
39 | "cell_type": "code",
40 | "execution_count": null,
41 | "metadata": {
42 | "colab": {},
43 | "colab_type": "code",
44 | "id": "YfIk2es3hJEd"
45 | },
46 | "outputs": [],
47 | "source": [
48 | "import tensorflow as tf\n",
49 | "import os\n",
50 | "import time\n",
51 | "import numpy as np\n",
52 | "import matplotlib.pyplot as plt\n",
53 | "import PIL\n",
54 | "from IPython.display import clear_output, display"
55 | ]
56 | },
57 | {
58 | "cell_type": "markdown",
59 | "metadata": {
60 | "colab_type": "text",
61 | "id": "iYn4MdZnKCey"
62 | },
63 | "source": [
64 | "## Load the dataset\n",
65 | "\n",
66 | "You can download this dataset and similar datasets from [here](https://people.eecs.berkeley.edu/~tinghuiz/projects/pix2pix/datasets). As mentioned in the [paper](https://arxiv.org/abs/1611.07004) we apply random jittering and mirroring to the training dataset.\n",
67 | "* In random jittering, the image is resized to `286 x 286` and then randomly cropped to `256 x 256`\n",
68 | "* In random mirroring, the image is randomly flipped horizontally i.e left to right."
69 | ]
70 | },
71 | {
72 | "cell_type": "code",
73 | "execution_count": null,
74 | "metadata": {
75 | "colab": {},
76 | "colab_type": "code",
77 | "id": "Kn-k8kTXuAlv"
78 | },
79 | "outputs": [],
80 | "source": [
81 | "path_to_zip = tf.keras.utils.get_file('facades.tar.gz',\n",
82 | " cache_subdir=os.path.abspath('.'),\n",
83 | " origin='https://people.eecs.berkeley.edu/~tinghuiz/projects/pix2pix/datasets/facades.tar.gz', \n",
84 | " extract=True)\n",
85 | "\n",
86 | "PATH = os.path.join(os.path.dirname(path_to_zip), 'facades/')"
87 | ]
88 | },
89 | {
90 | "cell_type": "code",
91 | "execution_count": null,
92 | "metadata": {
93 | "colab": {},
94 | "colab_type": "code",
95 | "id": "2CbTEt448b4R"
96 | },
97 | "outputs": [],
98 | "source": [
99 | "BUFFER_SIZE = 400\n",
100 | "BATCH_SIZE = 1\n",
101 | "IMG_WIDTH = 256\n",
102 | "IMG_HEIGHT = 256"
103 | ]
104 | },
105 | {
106 | "cell_type": "code",
107 | "execution_count": null,
108 | "metadata": {
109 | "colab": {},
110 | "colab_type": "code",
111 | "id": "tyaP4hLJ8b4W"
112 | },
113 | "outputs": [],
114 | "source": [
115 | "def load_image(image_file, is_train):\n",
116 | " image = tf.io.read_file(image_file)\n",
117 | " image = tf.image.decode_jpeg(image)\n",
118 | "\n",
119 | " w = tf.shape(image)[1]\n",
120 | "\n",
121 | " w = w // 2\n",
122 | " real_image = image[:, :w, :]\n",
123 | " input_image = image[:, w:, :]\n",
124 | "\n",
125 | " input_image = tf.cast(input_image, tf.float32)\n",
126 | " real_image = tf.cast(real_image, tf.float32)\n",
127 | "\n",
128 | " if is_train:\n",
129 | " # random jittering\n",
130 | " \n",
131 | " # resizing to 286 x 286 x 3 \n",
132 | " input_image = tf.image.resize(input_image, [286, 286])\n",
133 | " real_image = tf.image.resize(real_image, [286, 286])\n",
134 | " \n",
135 | " # randomly cropping to 256 x 256 x 3\n",
136 | " stacked_image = tf.stack([input_image, real_image], axis=0)\n",
137 | " cropped_image = tf.image.random_crop(stacked_image, size=[2, IMG_HEIGHT, IMG_WIDTH, 3])\n",
138 | " input_image, real_image = cropped_image[0], cropped_image[1]\n",
139 | "\n",
140 | " if np.random.random() > 0.5:\n",
141 | " # random mirroring\n",
142 | " input_image = tf.image.flip_left_right(input_image)\n",
143 | " real_image = tf.image.flip_left_right(real_image)\n",
144 | " else:\n",
145 | " input_image = tf.image.resize(input_image, size=[IMG_HEIGHT, IMG_WIDTH])\n",
146 | " real_image = tf.image.resize(real_image, size=[IMG_HEIGHT, IMG_WIDTH])\n",
147 | " \n",
148 | " # normalizing the images to [-1, 1]\n",
149 | " input_image = (input_image / 127.5) - 1\n",
150 | " real_image = (real_image / 127.5) - 1\n",
151 | "\n",
152 | " return input_image, real_image"
153 | ]
154 | },
155 | {
156 | "cell_type": "markdown",
157 | "metadata": {
158 | "colab_type": "text",
159 | "id": "PIGN6ouoQxt3"
160 | },
161 | "source": [
162 | "## Use tf.data to create batches, map(do preprocessing) and shuffle the dataset"
163 | ]
164 | },
165 | {
166 | "cell_type": "code",
167 | "execution_count": null,
168 | "metadata": {
169 | "colab": {},
170 | "colab_type": "code",
171 | "id": "SQHmYSmk8b4b"
172 | },
173 | "outputs": [],
174 | "source": [
175 | "train_dataset = tf.data.Dataset.list_files(PATH+'train/*.jpg')\n",
176 | "train_dataset = train_dataset.shuffle(BUFFER_SIZE)\n",
177 | "train_dataset = train_dataset.map(lambda x: load_image(x, True))\n",
178 | "train_dataset = train_dataset.batch(1)"
179 | ]
180 | },
181 | {
182 | "cell_type": "code",
183 | "execution_count": null,
184 | "metadata": {
185 | "colab": {},
186 | "colab_type": "code",
187 | "id": "MS9J0yA58b4g",
188 | "scrolled": false
189 | },
190 | "outputs": [],
191 | "source": [
192 | "test_dataset = tf.data.Dataset.list_files(PATH+'test/*.jpg')\n",
193 | "test_dataset = test_dataset.map(lambda x: load_image(x, False))\n",
194 | "test_dataset = test_dataset.batch(1)"
195 | ]
196 | },
197 | {
198 | "cell_type": "markdown",
199 | "metadata": {
200 | "colab_type": "text",
201 | "id": "THY-sZMiQ4UV"
202 | },
203 | "source": [
204 | "## Write the generator and discriminator models\n",
205 | "\n",
206 | "* **Generator** \n",
207 | " * The architecture of generator is a modified U-Net.\n",
208 | " * Each block in the encoder is (Conv -> Batchnorm -> Leaky ReLU)\n",
209 | " * Each block in the decoder is (Transposed Conv -> Batchnorm -> Dropout(applied to the first 3 blocks) -> ReLU)\n",
210 | " * There are skip connections between the encoder and decoder (as in U-Net).\n",
211 | " \n",
212 | "* **Discriminator**\n",
213 | " * The Discriminator is a PatchGAN.\n",
214 | " * Each block in the discriminator is (Conv -> BatchNorm -> Leaky ReLU)\n",
215 | " * The shape of the output after the last layer is (batch_size, 30, 30, 1)\n",
216 | " * Each 30x30 patch of the output classifies a 70x70 portion of the input image (such an architecture is called a PatchGAN).\n",
217 | " * Discriminator receives 2 inputs.\n",
218 | " * Input image and the target image, which it should classify as real.\n",
219 | " * Input image and the generated image (output of generator), which it should classify as fake. \n",
220 | " * We concatenate these 2 inputs together in the code (`tf.concat([inp, tar], axis=-1)`)\n",
221 | "\n",
222 | "* Shape of the input travelling through the generator and the discriminator is in the comments in the code.\n",
223 | "\n",
224 | "To learn more about the architecture and the hyperparameters you can refer the [paper](https://arxiv.org/abs/1611.07004).\n",
225 | " "
226 | ]
227 | },
228 | {
229 | "cell_type": "code",
230 | "execution_count": null,
231 | "metadata": {
232 | "colab": {},
233 | "colab_type": "code",
234 | "id": "tqqvWxlw8b4l"
235 | },
236 | "outputs": [],
237 | "source": [
238 | "OUTPUT_CHANNELS = 3"
239 | ]
240 | },
241 | {
242 | "cell_type": "code",
243 | "execution_count": null,
244 | "metadata": {
245 | "colab": {},
246 | "colab_type": "code",
247 | "id": "lFPI4Nu-8b4q"
248 | },
249 | "outputs": [],
250 | "source": [
251 | "class Downsample(tf.keras.Model):\n",
252 | " \n",
253 | " def __init__(self, filters, size, apply_batchnorm=True):\n",
254 | " super(Downsample, self).__init__()\n",
255 | " self.apply_batchnorm = apply_batchnorm\n",
256 | " initializer = tf.random_normal_initializer(0., 0.02)\n",
257 | "\n",
258 | " self.conv1 = tf.keras.layers.Conv2D(filters, \n",
259 | " (size, size), \n",
260 | " strides=2, \n",
261 | " padding='same',\n",
262 | " kernel_initializer=initializer,\n",
263 | " use_bias=False)\n",
264 | " if self.apply_batchnorm:\n",
265 | " self.batchnorm = tf.keras.layers.BatchNormalization()\n",
266 | " \n",
267 | " def call(self, x, training):\n",
268 | " x = self.conv1(x)\n",
269 | " if self.apply_batchnorm:\n",
270 | " x = self.batchnorm(x, training=training)\n",
271 | " x = tf.nn.leaky_relu(x)\n",
272 | " return x \n",
273 | "\n",
274 | "\n",
275 | "class Upsample(tf.keras.Model):\n",
276 | " \n",
277 | " def __init__(self, filters, size, apply_dropout=False):\n",
278 | " super(Upsample, self).__init__()\n",
279 | " self.apply_dropout = apply_dropout\n",
280 | " initializer = tf.random_normal_initializer(0., 0.02)\n",
281 | "\n",
282 | " self.up_conv = tf.keras.layers.Conv2DTranspose(filters, \n",
283 | " (size, size), \n",
284 | " strides=2, \n",
285 | " padding='same',\n",
286 | " kernel_initializer=initializer,\n",
287 | " use_bias=False)\n",
288 | " self.batchnorm = tf.keras.layers.BatchNormalization()\n",
289 | " if self.apply_dropout:\n",
290 | " self.dropout = tf.keras.layers.Dropout(0.5)\n",
291 | "\n",
292 | " def call(self, x1, x2, training):\n",
293 | " x = self.up_conv(x1)\n",
294 | " x = self.batchnorm(x, training=training)\n",
295 | " if self.apply_dropout:\n",
296 | " x = self.dropout(x, training=training)\n",
297 | " x = tf.nn.relu(x)\n",
298 | " x = tf.concat([x, x2], axis=-1)\n",
299 | " return x\n",
300 | "\n",
301 | "\n",
302 | "class Generator(tf.keras.Model):\n",
303 | " \n",
304 | " def __init__(self):\n",
305 | " super(Generator, self).__init__()\n",
306 | " initializer = tf.random_normal_initializer(0., 0.02)\n",
307 | " \n",
308 | " self.down1 = Downsample(64, 4, apply_batchnorm=False)\n",
309 | " self.down2 = Downsample(128, 4)\n",
310 | " self.down3 = Downsample(256, 4)\n",
311 | " self.down4 = Downsample(512, 4)\n",
312 | " self.down5 = Downsample(512, 4)\n",
313 | " self.down6 = Downsample(512, 4)\n",
314 | " self.down7 = Downsample(512, 4)\n",
315 | " self.down8 = Downsample(512, 4)\n",
316 | "\n",
317 | " self.up1 = Upsample(512, 4, apply_dropout=True)\n",
318 | " self.up2 = Upsample(512, 4, apply_dropout=True)\n",
319 | " self.up3 = Upsample(512, 4, apply_dropout=True)\n",
320 | " self.up4 = Upsample(512, 4)\n",
321 | " self.up5 = Upsample(256, 4)\n",
322 | " self.up6 = Upsample(128, 4)\n",
323 | " self.up7 = Upsample(64, 4)\n",
324 | "\n",
325 | " self.last = tf.keras.layers.Conv2DTranspose(OUTPUT_CHANNELS, \n",
326 | " (4, 4), \n",
327 | " strides=2, \n",
328 | " padding='same',\n",
329 | " kernel_initializer=initializer)\n",
330 | " \n",
331 | " @tf.function\n",
332 | " def call(self, x, training):\n",
333 | " # x shape == (bs, 256, 256, 3) \n",
334 | " x1 = self.down1(x, training=training) # (bs, 128, 128, 64)\n",
335 | " x2 = self.down2(x1, training=training) # (bs, 64, 64, 128)\n",
336 | " x3 = self.down3(x2, training=training) # (bs, 32, 32, 256)\n",
337 | " x4 = self.down4(x3, training=training) # (bs, 16, 16, 512)\n",
338 | " x5 = self.down5(x4, training=training) # (bs, 8, 8, 512)\n",
339 | " x6 = self.down6(x5, training=training) # (bs, 4, 4, 512)\n",
340 | " x7 = self.down7(x6, training=training) # (bs, 2, 2, 512)\n",
341 | " x8 = self.down8(x7, training=training) # (bs, 1, 1, 512)\n",
342 | "\n",
343 | " x9 = self.up1(x8, x7, training=training) # (bs, 2, 2, 1024)\n",
344 | " x10 = self.up2(x9, x6, training=training) # (bs, 4, 4, 1024)\n",
345 | " x11 = self.up3(x10, x5, training=training) # (bs, 8, 8, 1024)\n",
346 | " x12 = self.up4(x11, x4, training=training) # (bs, 16, 16, 1024)\n",
347 | " x13 = self.up5(x12, x3, training=training) # (bs, 32, 32, 512)\n",
348 | " x14 = self.up6(x13, x2, training=training) # (bs, 64, 64, 256)\n",
349 | " x15 = self.up7(x14, x1, training=training) # (bs, 128, 128, 128)\n",
350 | "\n",
351 | " x16 = self.last(x15) # (bs, 256, 256, 3)\n",
352 | " x16 = tf.nn.tanh(x16)\n",
353 | "\n",
354 | " return x16"
355 | ]
356 | },
357 | {
358 | "cell_type": "code",
359 | "execution_count": null,
360 | "metadata": {
361 | "colab": {},
362 | "colab_type": "code",
363 | "id": "ll6aNeQx8b4v"
364 | },
365 | "outputs": [],
366 | "source": [
367 | "class DiscDownsample(tf.keras.Model):\n",
368 | " \n",
369 | " def __init__(self, filters, size, apply_batchnorm=True):\n",
370 | " super(DiscDownsample, self).__init__()\n",
371 | " self.apply_batchnorm = apply_batchnorm\n",
372 | " initializer = tf.random_normal_initializer(0., 0.02)\n",
373 | "\n",
374 | " self.conv1 = tf.keras.layers.Conv2D(filters, \n",
375 | " (size, size), \n",
376 | " strides=2, \n",
377 | " padding='same',\n",
378 | " kernel_initializer=initializer,\n",
379 | " use_bias=False)\n",
380 | " if self.apply_batchnorm:\n",
381 | " self.batchnorm = tf.keras.layers.BatchNormalization()\n",
382 | " \n",
383 | " def call(self, x, training):\n",
384 | " x = self.conv1(x)\n",
385 | " if self.apply_batchnorm:\n",
386 | " x = self.batchnorm(x, training=training)\n",
387 | " x = tf.nn.leaky_relu(x)\n",
388 | " return x \n",
389 | "\n",
390 | "class Discriminator(tf.keras.Model):\n",
391 | " \n",
392 | " def __init__(self):\n",
393 | " super(Discriminator, self).__init__()\n",
394 | " initializer = tf.random_normal_initializer(0., 0.02)\n",
395 | " \n",
396 | " self.down1 = DiscDownsample(64, 4, False)\n",
397 | " self.down2 = DiscDownsample(128, 4)\n",
398 | " self.down3 = DiscDownsample(256, 4)\n",
399 | " \n",
400 | " # we are zero padding here with 1 because we need our shape to \n",
401 | " # go from (batch_size, 32, 32, 256) to (batch_size, 31, 31, 512)\n",
402 | " self.zero_pad1 = tf.keras.layers.ZeroPadding2D()\n",
403 | " self.conv = tf.keras.layers.Conv2D(512, \n",
404 | " (4, 4), \n",
405 | " strides=1, \n",
406 | " kernel_initializer=initializer, \n",
407 | " use_bias=False)\n",
408 | " self.batchnorm1 = tf.keras.layers.BatchNormalization()\n",
409 | " \n",
410 | " # shape change from (batch_size, 31, 31, 512) to (batch_size, 30, 30, 1)\n",
411 | " self.zero_pad2 = tf.keras.layers.ZeroPadding2D()\n",
412 | " self.last = tf.keras.layers.Conv2D(1, \n",
413 | " (4, 4), \n",
414 | " strides=1,\n",
415 | " kernel_initializer=initializer)\n",
416 | " \n",
417 | " @tf.function\n",
418 | " def call(self, inp, tar, training):\n",
419 | " # concatenating the input and the target\n",
420 | " x = tf.concat([inp, tar], axis=-1) # (bs, 256, 256, channels*2)\n",
421 | " x = self.down1(x, training=training) # (bs, 128, 128, 64)\n",
422 | " x = self.down2(x, training=training) # (bs, 64, 64, 128)\n",
423 | " x = self.down3(x, training=training) # (bs, 32, 32, 256)\n",
424 | "\n",
425 | " x = self.zero_pad1(x) # (bs, 34, 34, 256)\n",
426 | " x = self.conv(x) # (bs, 31, 31, 512)\n",
427 | " x = self.batchnorm1(x, training=training)\n",
428 | " x = tf.nn.leaky_relu(x)\n",
429 | " \n",
430 | " x = self.zero_pad2(x) # (bs, 33, 33, 512)\n",
431 | " # don't add a sigmoid activation here since\n",
432 | " # the loss function expects raw logits.\n",
433 | " x = self.last(x) # (bs, 30, 30, 1)\n",
434 | "\n",
435 | " return x"
436 | ]
437 | },
438 | {
439 | "cell_type": "code",
440 | "execution_count": null,
441 | "metadata": {
442 | "colab": {},
443 | "colab_type": "code",
444 | "id": "gDkA05NE6QMs"
445 | },
446 | "outputs": [],
447 | "source": [
448 | "# The call function of Generator and Discriminator have been decorated\n",
449 | "# with tf.contrib.eager.defun()\n",
450 | "# We get a performance speedup if defun is used (~25 seconds per epoch)\n",
451 | "generator = Generator()\n",
452 | "discriminator = Discriminator()"
453 | ]
454 | },
455 | {
456 | "cell_type": "markdown",
457 | "metadata": {
458 | "colab_type": "text",
459 | "id": "0FMYgY_mPfTi"
460 | },
461 | "source": [
462 | "## Define the loss functions and the optimizer\n",
463 | "\n",
464 | "* **Discriminator loss**\n",
465 | " * The discriminator loss function takes 2 inputs; **real images, generated images**\n",
466 | " * real_loss is a sigmoid cross entropy loss of the **real images** and an **array of ones(since these are the real images)**\n",
467 | " * generated_loss is a sigmoid cross entropy loss of the **generated images** and an **array of zeros(since these are the fake images)**\n",
468 | " * Then the total_loss is the sum of real_loss and the generated_loss\n",
469 | " \n",
470 | "* **Generator loss**\n",
471 | " * It is a sigmoid cross entropy loss of the generated images and an **array of ones**.\n",
472 | " * The [paper](https://arxiv.org/abs/1611.07004) also includes L1 loss which is MAE (mean absolute error) between the generated image and the target image.\n",
473 | " * This allows the generated image to become structurally similar to the target image.\n",
474 | " * The formula to calculate the total generator loss = gan_loss + LAMBDA * l1_loss, where LAMBDA = 100. This value was decided by the authors of the [paper](https://arxiv.org/abs/1611.07004)."
475 | ]
476 | },
477 | {
478 | "cell_type": "code",
479 | "execution_count": null,
480 | "metadata": {
481 | "colab": {},
482 | "colab_type": "code",
483 | "id": "cyhxTuvJyIHV"
484 | },
485 | "outputs": [],
486 | "source": [
487 | "LAMBDA = 100"
488 | ]
489 | },
490 | {
491 | "cell_type": "code",
492 | "execution_count": null,
493 | "metadata": {
494 | "colab": {},
495 | "colab_type": "code",
496 | "id": "wkMNfBWlT-PV"
497 | },
498 | "outputs": [],
499 | "source": [
500 | "BCE = tf.keras.losses.BinaryCrossentropy(from_logits=True)\n",
501 | "CCE = tf.keras.losses.CategoricalCrossentropy(from_logits=True)\n",
502 | "def discriminator_loss(disc_real_output, disc_generated_output):\n",
503 | " real_loss = BCE(tf.ones_like(disc_real_output), disc_real_output)\n",
504 | " generated_loss = BCE(tf.zeros_like(disc_generated_output), disc_generated_output)\n",
505 | " total_disc_loss = real_loss + generated_loss\n",
506 | " return total_disc_loss"
507 | ]
508 | },
509 | {
510 | "cell_type": "code",
511 | "execution_count": null,
512 | "metadata": {
513 | "colab": {},
514 | "colab_type": "code",
515 | "id": "90BIcCKcDMxz"
516 | },
517 | "outputs": [],
518 | "source": [
519 | "def generator_loss(disc_generated_output, gen_output, target):\n",
520 | " gan_loss = BCE(tf.ones_like(disc_generated_output), disc_generated_output) \n",
521 | " # mean absolute error\n",
522 | " l1_loss = tf.reduce_mean(tf.abs(target - gen_output))\n",
523 | "\n",
524 | " total_gen_loss = gan_loss + (LAMBDA * l1_loss)\n",
525 | "\n",
526 | " return total_gen_loss"
527 | ]
528 | },
529 | {
530 | "cell_type": "code",
531 | "execution_count": null,
532 | "metadata": {
533 | "colab": {},
534 | "colab_type": "code",
535 | "id": "iWCn_PVdEJZ7"
536 | },
537 | "outputs": [],
538 | "source": [
539 | "generator_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)\n",
540 | "discriminator_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)"
541 | ]
542 | },
543 | {
544 | "cell_type": "markdown",
545 | "metadata": {
546 | "colab_type": "text",
547 | "id": "Rw1fkAczTQYh"
548 | },
549 | "source": [
550 | "## Training\n",
551 | "\n",
552 | "* We start by iterating over the dataset\n",
553 | "* The generator gets the input image and we get a generated output.\n",
554 | "* The discriminator receives the input_image and the generated image as the first input. The second input is the input_image and the target_image.\n",
555 | "* Next, we calculate the generator and the discriminator loss.\n",
556 | "* Then, we calculate the gradients of loss with respect to both the generator and the discriminator variables(inputs) and apply those to the optimizer.\n",
557 | "\n",
558 | "## Generate Images\n",
559 | "\n",
560 | "* After training, its time to generate some images!\n",
561 | "* We pass images from the test dataset to the generator.\n",
562 | "* The generator will then translate the input image into the output we expect.\n",
563 | "* Last step is to plot the predictions and **voila!**"
564 | ]
565 | },
566 | {
567 | "cell_type": "code",
568 | "execution_count": null,
569 | "metadata": {
570 | "colab": {},
571 | "colab_type": "code",
572 | "id": "NS2GWywBbAWo"
573 | },
574 | "outputs": [],
575 | "source": [
576 | "EPOCHS = 200"
577 | ]
578 | },
579 | {
580 | "cell_type": "code",
581 | "execution_count": null,
582 | "metadata": {
583 | "colab": {},
584 | "colab_type": "code",
585 | "id": "RmdVsmvhPxyy"
586 | },
587 | "outputs": [],
588 | "source": [
589 | "def generate_images(model, test_input, tar):\n",
590 | " # the training=True is intentional here since\n",
591 | " # we want the batch statistics while running the model\n",
592 | " # on the test dataset. If we use training=False, we will get \n",
593 | " # the accumulated statistics learned from the training dataset\n",
594 | " # (which we don't want)\n",
595 | " prediction = model(test_input, training=True)\n",
596 | " plt.figure(figsize=(15,15))\n",
597 | "\n",
598 | " display_list = [test_input[0], tar[0], prediction[0]]\n",
599 | " title = ['Input Image', 'Ground Truth', 'Predicted Image']\n",
600 | "\n",
601 | " for i in range(3):\n",
602 | " plt.subplot(1, 3, i+1)\n",
603 | " plt.title(title[i])\n",
604 | " # getting the pixel values between [0, 1] to plot it.\n",
605 | " plt.imshow(display_list[i] * 0.5 + 0.5)\n",
606 | " plt.axis('off')\n",
607 | " plt.show()"
608 | ]
609 | },
610 | {
611 | "cell_type": "code",
612 | "execution_count": null,
613 | "metadata": {
614 | "colab": {},
615 | "colab_type": "code",
616 | "id": "2M7LmLtGEMQJ"
617 | },
618 | "outputs": [],
619 | "source": [
620 | "def train(dataset, epochs): \n",
621 | " for epoch in range(epochs):\n",
622 | " start = time.time()\n",
623 | "\n",
624 | " for input_image, target in dataset:\n",
625 | "\n",
626 | " with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:\n",
627 | " gen_output = generator(input_image, training=True)\n",
628 | "\n",
629 | " disc_real_output = discriminator(input_image, target, training=True)\n",
630 | " disc_generated_output = discriminator(input_image, gen_output, training=True)\n",
631 | "\n",
632 | " gen_loss = generator_loss(disc_generated_output, gen_output, target)\n",
633 | " disc_loss = discriminator_loss(disc_real_output, disc_generated_output)\n",
634 | "\n",
635 | " generator_gradients = gen_tape.gradient(gen_loss, \n",
636 | " generator.trainable_variables)\n",
637 | " discriminator_gradients = disc_tape.gradient(disc_loss, \n",
638 | " discriminator.trainable_variables)\n",
639 | "\n",
640 | " generator_optimizer.apply_gradients(zip(generator_gradients, \n",
641 | " generator.trainable_variables))\n",
642 | " discriminator_optimizer.apply_gradients(zip(discriminator_gradients, \n",
643 | " discriminator.trainable_variables))\n",
644 | "\n",
645 | " if epoch % 1 == 0:\n",
646 | " for inp, tar in test_dataset.take(3):\n",
647 | " generate_images(generator, inp, tar)\n",
648 | " \n",
649 | " print ('Time taken for epoch {} is {} sec\\n'.format(epoch + 1,\n",
650 | " time.time()-start))"
651 | ]
652 | },
653 | {
654 | "cell_type": "code",
655 | "execution_count": null,
656 | "metadata": {
657 | "colab": {},
658 | "colab_type": "code",
659 | "id": "a1zZmKmvOH85"
660 | },
661 | "outputs": [],
662 | "source": [
663 | "train(train_dataset, EPOCHS)"
664 | ]
665 | },
666 | {
667 | "cell_type": "markdown",
668 | "metadata": {
669 | "colab_type": "text",
670 | "id": "1RGysMU_BZhx"
671 | },
672 | "source": [
673 | "## Testing on the entire test dataset"
674 | ]
675 | },
676 | {
677 | "cell_type": "code",
678 | "execution_count": null,
679 | "metadata": {
680 | "colab": {},
681 | "colab_type": "code",
682 | "id": "KUgSnmy2nqSP"
683 | },
684 | "outputs": [],
685 | "source": [
686 | "# Run the trained model on the entire test dataset\n",
687 | "for inp, tar in test_dataset:\n",
688 | " generate_images(generator, inp, tar)"
689 | ]
690 | },
691 | {
692 | "cell_type": "code",
693 | "execution_count": null,
694 | "metadata": {
695 | "colab": {},
696 | "colab_type": "code",
697 | "id": "3AJXOByaZVOf"
698 | },
699 | "outputs": [],
700 | "source": []
701 | }
702 | ],
703 | "metadata": {
704 | "accelerator": "GPU",
705 | "colab": {
706 | "collapsed_sections": [],
707 | "include_colab_link": true,
708 | "name": "pix2pix_eager.ipynb",
709 | "private_outputs": true,
710 | "provenance": [],
711 | "version": "0.3.2"
712 | },
713 | "kernelspec": {
714 | "display_name": "Python 3",
715 | "language": "python",
716 | "name": "python3"
717 | },
718 | "language_info": {
719 | "codemirror_mode": {
720 | "name": "ipython",
721 | "version": 3
722 | },
723 | "file_extension": ".py",
724 | "mimetype": "text/x-python",
725 | "name": "python",
726 | "nbconvert_exporter": "python",
727 | "pygments_lexer": "ipython3",
728 | "version": "3.6.8"
729 | }
730 | },
731 | "nbformat": 4,
732 | "nbformat_minor": 1
733 | }
734 |
--------------------------------------------------------------------------------
/pix2pix_eager.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "accelerator": "GPU",
6 | "colab": {
7 | "name": "pix2pix_eager.ipynb",
8 | "version": "0.3.2",
9 | "provenance": [],
10 | "private_outputs": true,
11 | "collapsed_sections": [],
12 | "include_colab_link": true
13 | },
14 | "kernelspec": {
15 | "display_name": "Python 3",
16 | "language": "python",
17 | "name": "python3"
18 | }
19 | },
20 | "cells": [
21 | {
22 | "cell_type": "markdown",
23 | "metadata": {
24 | "id": "view-in-github",
25 | "colab_type": "text"
26 | },
27 | "source": [
28 | "
"
29 | ]
30 | },
31 | {
32 | "cell_type": "markdown",
33 | "metadata": {
34 | "colab_type": "text",
35 | "id": "0TD5ZrvEMbhZ"
36 | },
37 | "source": [
38 | "##### Copyright 2018 The TensorFlow Authors.\n",
39 | "\n",
40 | "Licensed under the Apache License, Version 2.0 (the \"License\").\n",
41 | "\n",
42 | "# Pix2Pix: An example with tf.keras and eager\n",
43 | "\n"
44 | ]
45 | },
46 | {
47 | "cell_type": "markdown",
48 | "metadata": {
49 | "colab_type": "text",
50 | "id": "e1_Y75QXJS6h"
51 | },
52 | "source": [
53 | "## Import TensorFlow and enable eager execution"
54 | ]
55 | },
56 | {
57 | "cell_type": "code",
58 | "metadata": {
59 | "colab_type": "code",
60 | "id": "YfIk2es3hJEd",
61 | "colab": {}
62 | },
63 | "source": [
64 | "# Import TensorFlow >= 1.10 and enable eager execution\n",
65 | "import tensorflow as tf\n",
66 | "tf.enable_eager_execution()\n",
67 | "\n",
68 | "import os\n",
69 | "import time\n",
70 | "import numpy as np\n",
71 | "import matplotlib.pyplot as plt\n",
72 | "import PIL\n",
73 | "from IPython.display import clear_output, display"
74 | ],
75 | "execution_count": 0,
76 | "outputs": []
77 | },
78 | {
79 | "cell_type": "markdown",
80 | "metadata": {
81 | "colab_type": "text",
82 | "id": "iYn4MdZnKCey"
83 | },
84 | "source": [
85 | "## Load the dataset\n",
86 | "\n",
87 | "You can download this dataset and similar datasets from [here](https://people.eecs.berkeley.edu/~tinghuiz/projects/pix2pix/datasets). As mentioned in the [paper](https://arxiv.org/abs/1611.07004) we apply random jittering and mirroring to the training dataset.\n",
88 | "* In random jittering, the image is resized to `286 x 286` and then randomly cropped to `256 x 256`\n",
89 | "* In random mirroring, the image is randomly flipped horizontally i.e left to right."
90 | ]
91 | },
92 | {
93 | "cell_type": "code",
94 | "metadata": {
95 | "colab_type": "code",
96 | "id": "Kn-k8kTXuAlv",
97 | "colab": {}
98 | },
99 | "source": [
100 | "path_to_zip = tf.keras.utils.get_file('facades.tar.gz',\n",
101 | " cache_subdir=os.path.abspath('.'),\n",
102 | " origin='https://people.eecs.berkeley.edu/~tinghuiz/projects/pix2pix/datasets/facades.tar.gz', \n",
103 | " extract=True)\n",
104 | "\n",
105 | "PATH = os.path.join(os.path.dirname(path_to_zip), 'facades/')"
106 | ],
107 | "execution_count": 0,
108 | "outputs": []
109 | },
110 | {
111 | "cell_type": "code",
112 | "metadata": {
113 | "colab_type": "code",
114 | "id": "2CbTEt448b4R",
115 | "colab": {}
116 | },
117 | "source": [
118 | "BUFFER_SIZE = 400\n",
119 | "BATCH_SIZE = 1\n",
120 | "IMG_WIDTH = 256\n",
121 | "IMG_HEIGHT = 256"
122 | ],
123 | "execution_count": 0,
124 | "outputs": []
125 | },
126 | {
127 | "cell_type": "code",
128 | "metadata": {
129 | "colab_type": "code",
130 | "id": "tyaP4hLJ8b4W",
131 | "colab": {}
132 | },
133 | "source": [
134 | "def load_image(image_file, is_train):\n",
135 | " image = tf.read_file(image_file)\n",
136 | " image = tf.image.decode_jpeg(image)\n",
137 | "\n",
138 | " w = tf.shape(image)[1]\n",
139 | "\n",
140 | " w = w // 2\n",
141 | " real_image = image[:, :w, :]\n",
142 | " input_image = image[:, w:, :]\n",
143 | "\n",
144 | " input_image = tf.cast(input_image, tf.float32)\n",
145 | " real_image = tf.cast(real_image, tf.float32)\n",
146 | "\n",
147 | " if is_train:\n",
148 | " # random jittering\n",
149 | " \n",
150 | " # resizing to 286 x 286 x 3\n",
151 | " input_image = tf.image.resize_images(input_image, [286, 286], \n",
152 | " align_corners=True, \n",
153 | " method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)\n",
154 | " real_image = tf.image.resize_images(real_image, [286, 286], \n",
155 | " align_corners=True, \n",
156 | " method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)\n",
157 | " \n",
158 | " # randomly cropping to 256 x 256 x 3\n",
159 | " stacked_image = tf.stack([input_image, real_image], axis=0)\n",
160 | " cropped_image = tf.random_crop(stacked_image, size=[2, IMG_HEIGHT, IMG_WIDTH, 3])\n",
161 | " input_image, real_image = cropped_image[0], cropped_image[1]\n",
162 | "\n",
163 | " if np.random.random() > 0.5:\n",
164 | " # random mirroring\n",
165 | " input_image = tf.image.flip_left_right(input_image)\n",
166 | " real_image = tf.image.flip_left_right(real_image)\n",
167 | " else:\n",
168 | " input_image = tf.image.resize_images(input_image, size=[IMG_HEIGHT, IMG_WIDTH], \n",
169 | " align_corners=True, method=2)\n",
170 | " real_image = tf.image.resize_images(real_image, size=[IMG_HEIGHT, IMG_WIDTH], \n",
171 | " align_corners=True, method=2)\n",
172 | " \n",
173 | " # normalizing the images to [-1, 1]\n",
174 | " input_image = (input_image / 127.5) - 1\n",
175 | " real_image = (real_image / 127.5) - 1\n",
176 | "\n",
177 | " return input_image, real_image"
178 | ],
179 | "execution_count": 0,
180 | "outputs": []
181 | },
182 | {
183 | "cell_type": "markdown",
184 | "metadata": {
185 | "colab_type": "text",
186 | "id": "PIGN6ouoQxt3"
187 | },
188 | "source": [
189 | "## Use tf.data to create batches, map(do preprocessing) and shuffle the dataset"
190 | ]
191 | },
192 | {
193 | "cell_type": "code",
194 | "metadata": {
195 | "colab_type": "code",
196 | "id": "SQHmYSmk8b4b",
197 | "colab": {}
198 | },
199 | "source": [
200 | "train_dataset = tf.data.Dataset.list_files(PATH+'train/*.jpg')\n",
201 | "train_dataset = train_dataset.shuffle(BUFFER_SIZE)\n",
202 | "train_dataset = train_dataset.map(lambda x: load_image(x, True))\n",
203 | "train_dataset = train_dataset.batch(1)"
204 | ],
205 | "execution_count": 0,
206 | "outputs": []
207 | },
208 | {
209 | "cell_type": "code",
210 | "metadata": {
211 | "colab_type": "code",
212 | "id": "MS9J0yA58b4g",
213 | "colab": {}
214 | },
215 | "source": [
216 | "test_dataset = tf.data.Dataset.list_files(PATH+'test/*.jpg')\n",
217 | "test_dataset = test_dataset.map(lambda x: load_image(x, False))\n",
218 | "test_dataset = test_dataset.batch(1)"
219 | ],
220 | "execution_count": 0,
221 | "outputs": []
222 | },
223 | {
224 | "cell_type": "markdown",
225 | "metadata": {
226 | "colab_type": "text",
227 | "id": "THY-sZMiQ4UV"
228 | },
229 | "source": [
230 | "## Write the generator and discriminator models\n",
231 | "\n",
232 | "* **Generator** \n",
233 | " * The architecture of generator is a modified U-Net.\n",
234 | " * Each block in the encoder is (Conv -> Batchnorm -> Leaky ReLU)\n",
235 | " * Each block in the decoder is (Transposed Conv -> Batchnorm -> Dropout(applied to the first 3 blocks) -> ReLU)\n",
236 | " * There are skip connections between the encoder and decoder (as in U-Net).\n",
237 | " \n",
238 | "* **Discriminator**\n",
239 | " * The Discriminator is a PatchGAN.\n",
240 | " * Each block in the discriminator is (Conv -> BatchNorm -> Leaky ReLU)\n",
241 | " * The shape of the output after the last layer is (batch_size, 30, 30, 1)\n",
242 | " * Each 30x30 patch of the output classifies a 70x70 portion of the input image (such an architecture is called a PatchGAN).\n",
243 | " * Discriminator receives 2 inputs.\n",
244 | " * Input image and the target image, which it should classify as real.\n",
245 | " * Input image and the generated image (output of generator), which it should classify as fake. \n",
246 | " * We concatenate these 2 inputs together in the code (`tf.concat([inp, tar], axis=-1)`)\n",
247 | "\n",
248 | "* Shape of the input travelling through the generator and the discriminator is in the comments in the code.\n",
249 | "\n",
250 | "To learn more about the architecture and the hyperparameters you can refer the [paper](https://arxiv.org/abs/1611.07004).\n",
251 | " "
252 | ]
253 | },
254 | {
255 | "cell_type": "code",
256 | "metadata": {
257 | "colab_type": "code",
258 | "id": "tqqvWxlw8b4l",
259 | "colab": {}
260 | },
261 | "source": [
262 | "OUTPUT_CHANNELS = 3"
263 | ],
264 | "execution_count": 0,
265 | "outputs": []
266 | },
267 | {
268 | "cell_type": "code",
269 | "metadata": {
270 | "colab_type": "code",
271 | "id": "lFPI4Nu-8b4q",
272 | "colab": {}
273 | },
274 | "source": [
275 | "class Downsample(tf.keras.Model):\n",
276 | " \n",
277 | " def __init__(self, filters, size, apply_batchnorm=True):\n",
278 | " super(Downsample, self).__init__()\n",
279 | " self.apply_batchnorm = apply_batchnorm\n",
280 | " initializer = tf.random_normal_initializer(0., 0.02)\n",
281 | "\n",
282 | " self.conv1 = tf.keras.layers.Conv2D(filters, \n",
283 | " (size, size), \n",
284 | " strides=2, \n",
285 | " padding='same',\n",
286 | " kernel_initializer=initializer,\n",
287 | " use_bias=False)\n",
288 | " if self.apply_batchnorm:\n",
289 | " self.batchnorm = tf.keras.layers.BatchNormalization()\n",
290 | " \n",
291 | " def call(self, x, training):\n",
292 | " x = self.conv1(x)\n",
293 | " if self.apply_batchnorm:\n",
294 | " x = self.batchnorm(x, training=training)\n",
295 | " x = tf.nn.leaky_relu(x)\n",
296 | " return x \n",
297 | "\n",
298 | "\n",
299 | "class Upsample(tf.keras.Model):\n",
300 | " \n",
301 | " def __init__(self, filters, size, apply_dropout=False):\n",
302 | " super(Upsample, self).__init__()\n",
303 | " self.apply_dropout = apply_dropout\n",
304 | " initializer = tf.random_normal_initializer(0., 0.02)\n",
305 | "\n",
306 | " self.up_conv = tf.keras.layers.Conv2DTranspose(filters, \n",
307 | " (size, size), \n",
308 | " strides=2, \n",
309 | " padding='same',\n",
310 | " kernel_initializer=initializer,\n",
311 | " use_bias=False)\n",
312 | " self.batchnorm = tf.keras.layers.BatchNormalization()\n",
313 | " if self.apply_dropout:\n",
314 | " self.dropout = tf.keras.layers.Dropout(0.5)\n",
315 | "\n",
316 | " def call(self, x1, x2, training):\n",
317 | " x = self.up_conv(x1)\n",
318 | " x = self.batchnorm(x, training=training)\n",
319 | " if self.apply_dropout:\n",
320 | " x = self.dropout(x, training=training)\n",
321 | " x = tf.nn.relu(x)\n",
322 | " x = tf.concat([x, x2], axis=-1)\n",
323 | " return x\n",
324 | "\n",
325 | "\n",
326 | "class Generator(tf.keras.Model):\n",
327 | " \n",
328 | " def __init__(self):\n",
329 | " super(Generator, self).__init__()\n",
330 | " initializer = tf.random_normal_initializer(0., 0.02)\n",
331 | " \n",
332 | " self.down1 = Downsample(64, 4, apply_batchnorm=False)\n",
333 | " self.down2 = Downsample(128, 4)\n",
334 | " self.down3 = Downsample(256, 4)\n",
335 | " self.down4 = Downsample(512, 4)\n",
336 | " self.down5 = Downsample(512, 4)\n",
337 | " self.down6 = Downsample(512, 4)\n",
338 | " self.down7 = Downsample(512, 4)\n",
339 | " self.down8 = Downsample(512, 4)\n",
340 | "\n",
341 | " self.up1 = Upsample(512, 4, apply_dropout=True)\n",
342 | " self.up2 = Upsample(512, 4, apply_dropout=True)\n",
343 | " self.up3 = Upsample(512, 4, apply_dropout=True)\n",
344 | " self.up4 = Upsample(512, 4)\n",
345 | " self.up5 = Upsample(256, 4)\n",
346 | " self.up6 = Upsample(128, 4)\n",
347 | " self.up7 = Upsample(64, 4)\n",
348 | "\n",
349 | " self.last = tf.keras.layers.Conv2DTranspose(OUTPUT_CHANNELS, \n",
350 | " (4, 4), \n",
351 | " strides=2, \n",
352 | " padding='same',\n",
353 | " kernel_initializer=initializer)\n",
354 | " \n",
355 | " @tf.contrib.eager.defun\n",
356 | " def call(self, x, training):\n",
357 | " # x shape == (bs, 256, 256, 3) \n",
358 | " x1 = self.down1(x, training=training) # (bs, 128, 128, 64)\n",
359 | " x2 = self.down2(x1, training=training) # (bs, 64, 64, 128)\n",
360 | " x3 = self.down3(x2, training=training) # (bs, 32, 32, 256)\n",
361 | " x4 = self.down4(x3, training=training) # (bs, 16, 16, 512)\n",
362 | " x5 = self.down5(x4, training=training) # (bs, 8, 8, 512)\n",
363 | " x6 = self.down6(x5, training=training) # (bs, 4, 4, 512)\n",
364 | " x7 = self.down7(x6, training=training) # (bs, 2, 2, 512)\n",
365 | " x8 = self.down8(x7, training=training) # (bs, 1, 1, 512)\n",
366 | "\n",
367 | " x9 = self.up1(x8, x7, training=training) # (bs, 2, 2, 1024)\n",
368 | " x10 = self.up2(x9, x6, training=training) # (bs, 4, 4, 1024)\n",
369 | " x11 = self.up3(x10, x5, training=training) # (bs, 8, 8, 1024)\n",
370 | " x12 = self.up4(x11, x4, training=training) # (bs, 16, 16, 1024)\n",
371 | " x13 = self.up5(x12, x3, training=training) # (bs, 32, 32, 512)\n",
372 | " x14 = self.up6(x13, x2, training=training) # (bs, 64, 64, 256)\n",
373 | " x15 = self.up7(x14, x1, training=training) # (bs, 128, 128, 128)\n",
374 | "\n",
375 | " x16 = self.last(x15) # (bs, 256, 256, 3)\n",
376 | " x16 = tf.nn.tanh(x16)\n",
377 | "\n",
378 | " return x16"
379 | ],
380 | "execution_count": 0,
381 | "outputs": []
382 | },
383 | {
384 | "cell_type": "code",
385 | "metadata": {
386 | "colab_type": "code",
387 | "id": "ll6aNeQx8b4v",
388 | "colab": {}
389 | },
390 | "source": [
391 | "class DiscDownsample(tf.keras.Model):\n",
392 | " \n",
393 | " def __init__(self, filters, size, apply_batchnorm=True):\n",
394 | " super(DiscDownsample, self).__init__()\n",
395 | " self.apply_batchnorm = apply_batchnorm\n",
396 | " initializer = tf.random_normal_initializer(0., 0.02)\n",
397 | "\n",
398 | " self.conv1 = tf.keras.layers.Conv2D(filters, \n",
399 | " (size, size), \n",
400 | " strides=2, \n",
401 | " padding='same',\n",
402 | " kernel_initializer=initializer,\n",
403 | " use_bias=False)\n",
404 | " if self.apply_batchnorm:\n",
405 | " self.batchnorm = tf.keras.layers.BatchNormalization()\n",
406 | " \n",
407 | " def call(self, x, training):\n",
408 | " x = self.conv1(x)\n",
409 | " if self.apply_batchnorm:\n",
410 | " x = self.batchnorm(x, training=training)\n",
411 | " x = tf.nn.leaky_relu(x)\n",
412 | " return x \n",
413 | "\n",
414 | "class Discriminator(tf.keras.Model):\n",
415 | " \n",
416 | " def __init__(self):\n",
417 | " super(Discriminator, self).__init__()\n",
418 | " initializer = tf.random_normal_initializer(0., 0.02)\n",
419 | " \n",
420 | " self.down1 = DiscDownsample(64, 4, False)\n",
421 | " self.down2 = DiscDownsample(128, 4)\n",
422 | " self.down3 = DiscDownsample(256, 4)\n",
423 | " \n",
424 | " # we are zero padding here with 1 because we need our shape to \n",
425 | " # go from (batch_size, 32, 32, 256) to (batch_size, 31, 31, 512)\n",
426 | " self.zero_pad1 = tf.keras.layers.ZeroPadding2D()\n",
427 | " self.conv = tf.keras.layers.Conv2D(512, \n",
428 | " (4, 4), \n",
429 | " strides=1, \n",
430 | " kernel_initializer=initializer, \n",
431 | " use_bias=False)\n",
432 | " self.batchnorm1 = tf.keras.layers.BatchNormalization()\n",
433 | " \n",
434 | " # shape change from (batch_size, 31, 31, 512) to (batch_size, 30, 30, 1)\n",
435 | " self.zero_pad2 = tf.keras.layers.ZeroPadding2D()\n",
436 | " self.last = tf.keras.layers.Conv2D(1, \n",
437 | " (4, 4), \n",
438 | " strides=1,\n",
439 | " kernel_initializer=initializer)\n",
440 | " \n",
441 | " @tf.contrib.eager.defun\n",
442 | " def call(self, inp, tar, training):\n",
443 | " # concatenating the input and the target\n",
444 | " x = tf.concat([inp, tar], axis=-1) # (bs, 256, 256, channels*2)\n",
445 | " x = self.down1(x, training=training) # (bs, 128, 128, 64)\n",
446 | " x = self.down2(x, training=training) # (bs, 64, 64, 128)\n",
447 | " x = self.down3(x, training=training) # (bs, 32, 32, 256)\n",
448 | "\n",
449 | " x = self.zero_pad1(x) # (bs, 34, 34, 256)\n",
450 | " x = self.conv(x) # (bs, 31, 31, 512)\n",
451 | " x = self.batchnorm1(x, training=training)\n",
452 | " x = tf.nn.leaky_relu(x)\n",
453 | " \n",
454 | " x = self.zero_pad2(x) # (bs, 33, 33, 512)\n",
455 | " # don't add a sigmoid activation here since\n",
456 | " # the loss function expects raw logits.\n",
457 | " x = self.last(x) # (bs, 30, 30, 1)\n",
458 | "\n",
459 | " return x"
460 | ],
461 | "execution_count": 0,
462 | "outputs": []
463 | },
464 | {
465 | "cell_type": "code",
466 | "metadata": {
467 | "colab_type": "code",
468 | "id": "gDkA05NE6QMs",
469 | "colab": {}
470 | },
471 | "source": [
472 | "# The call function of Generator and Discriminator have been decorated\n",
473 | "# with tf.contrib.eager.defun()\n",
474 | "# We get a performance speedup if defun is used (~25 seconds per epoch)\n",
475 | "generator = Generator()\n",
476 | "discriminator = Discriminator()"
477 | ],
478 | "execution_count": 0,
479 | "outputs": []
480 | },
481 | {
482 | "cell_type": "markdown",
483 | "metadata": {
484 | "colab_type": "text",
485 | "id": "0FMYgY_mPfTi"
486 | },
487 | "source": [
488 | "## Define the loss functions and the optimizer\n",
489 | "\n",
490 | "* **Discriminator loss**\n",
491 | " * The discriminator loss function takes 2 inputs; **real images, generated images**\n",
492 | " * real_loss is a sigmoid cross entropy loss of the **real images** and an **array of ones(since these are the real images)**\n",
493 | " * generated_loss is a sigmoid cross entropy loss of the **generated images** and an **array of zeros(since these are the fake images)**\n",
494 | " * Then the total_loss is the sum of real_loss and the generated_loss\n",
495 | " \n",
496 | "* **Generator loss**\n",
497 | " * It is a sigmoid cross entropy loss of the generated images and an **array of ones**.\n",
498 | " * The [paper](https://arxiv.org/abs/1611.07004) also includes L1 loss which is MAE (mean absolute error) between the generated image and the target image.\n",
499 | " * This allows the generated image to become structurally similar to the target image.\n",
500 | " * The formula to calculate the total generator loss = gan_loss + LAMBDA * l1_loss, where LAMBDA = 100. This value was decided by the authors of the [paper](https://arxiv.org/abs/1611.07004)."
501 | ]
502 | },
503 | {
504 | "cell_type": "code",
505 | "metadata": {
506 | "colab_type": "code",
507 | "id": "cyhxTuvJyIHV",
508 | "colab": {}
509 | },
510 | "source": [
511 | "LAMBDA = 100"
512 | ],
513 | "execution_count": 0,
514 | "outputs": []
515 | },
516 | {
517 | "cell_type": "code",
518 | "metadata": {
519 | "colab_type": "code",
520 | "id": "wkMNfBWlT-PV",
521 | "colab": {}
522 | },
523 | "source": [
524 | "def discriminator_loss(disc_real_output, disc_generated_output):\n",
525 | " real_loss = tf.losses.sigmoid_cross_entropy(multi_class_labels = tf.ones_like(disc_real_output), \n",
526 | " logits = disc_real_output)\n",
527 | " generated_loss = tf.losses.sigmoid_cross_entropy(multi_class_labels = tf.zeros_like(disc_generated_output), \n",
528 | " logits = disc_generated_output)\n",
529 | "\n",
530 | " total_disc_loss = real_loss + generated_loss\n",
531 | "\n",
532 | " return total_disc_loss"
533 | ],
534 | "execution_count": 0,
535 | "outputs": []
536 | },
537 | {
538 | "cell_type": "code",
539 | "metadata": {
540 | "colab_type": "code",
541 | "id": "90BIcCKcDMxz",
542 | "colab": {}
543 | },
544 | "source": [
545 | "def generator_loss(disc_generated_output, gen_output, target):\n",
546 | " gan_loss = tf.losses.sigmoid_cross_entropy(multi_class_labels = tf.ones_like(disc_generated_output),\n",
547 | " logits = disc_generated_output) \n",
548 | " # mean absolute error\n",
549 | " l1_loss = tf.reduce_mean(tf.abs(target - gen_output))\n",
550 | "\n",
551 | " total_gen_loss = gan_loss + (LAMBDA * l1_loss)\n",
552 | "\n",
553 | " return total_gen_loss"
554 | ],
555 | "execution_count": 0,
556 | "outputs": []
557 | },
558 | {
559 | "cell_type": "code",
560 | "metadata": {
561 | "colab_type": "code",
562 | "id": "iWCn_PVdEJZ7",
563 | "colab": {}
564 | },
565 | "source": [
566 | "generator_optimizer = tf.train.AdamOptimizer(2e-4, beta1=0.5)\n",
567 | "discriminator_optimizer = tf.train.AdamOptimizer(2e-4, beta1=0.5)"
568 | ],
569 | "execution_count": 0,
570 | "outputs": []
571 | },
572 | {
573 | "cell_type": "markdown",
574 | "metadata": {
575 | "colab_type": "text",
576 | "id": "aKUZnDiqQrAh"
577 | },
578 | "source": [
579 | "## Checkpoints (Object-based saving)"
580 | ]
581 | },
582 | {
583 | "cell_type": "code",
584 | "metadata": {
585 | "colab_type": "code",
586 | "id": "WJnftd5sQsv6",
587 | "colab": {}
588 | },
589 | "source": [
590 | "checkpoint_dir = './training_checkpoints'\n",
591 | "checkpoint_prefix = os.path.join(checkpoint_dir, \"ckpt\")\n",
592 | "checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,\n",
593 | " discriminator_optimizer=discriminator_optimizer,\n",
594 | " generator=generator,\n",
595 | " discriminator=discriminator)"
596 | ],
597 | "execution_count": 0,
598 | "outputs": []
599 | },
600 | {
601 | "cell_type": "markdown",
602 | "metadata": {
603 | "colab_type": "text",
604 | "id": "Rw1fkAczTQYh"
605 | },
606 | "source": [
607 | "## Training\n",
608 | "\n",
609 | "* We start by iterating over the dataset\n",
610 | "* The generator gets the input image and we get a generated output.\n",
611 | "* The discriminator receives the input_image and the generated image as the first input. The second input is the input_image and the target_image.\n",
612 | "* Next, we calculate the generator and the discriminator loss.\n",
613 | "* Then, we calculate the gradients of loss with respect to both the generator and the discriminator variables(inputs) and apply those to the optimizer.\n",
614 | "\n",
615 | "## Generate Images\n",
616 | "\n",
617 | "* After training, its time to generate some images!\n",
618 | "* We pass images from the test dataset to the generator.\n",
619 | "* The generator will then translate the input image into the output we expect.\n",
620 | "* Last step is to plot the predictions and **voila!**"
621 | ]
622 | },
623 | {
624 | "cell_type": "code",
625 | "metadata": {
626 | "colab_type": "code",
627 | "id": "NS2GWywBbAWo",
628 | "colab": {}
629 | },
630 | "source": [
631 | "EPOCHS = 200"
632 | ],
633 | "execution_count": 0,
634 | "outputs": []
635 | },
636 | {
637 | "cell_type": "code",
638 | "metadata": {
639 | "colab_type": "code",
640 | "id": "RmdVsmvhPxyy",
641 | "colab": {}
642 | },
643 | "source": [
644 | "def generate_images(model, test_input, tar):\n",
645 | " # the training=True is intentional here since\n",
646 | " # we want the batch statistics while running the model\n",
647 | " # on the test dataset. If we use training=False, we will get \n",
648 | " # the accumulated statistics learned from the training dataset\n",
649 | " # (which we don't want)\n",
650 | " prediction = model(test_input, training=True)\n",
651 | " plt.figure(figsize=(15,15))\n",
652 | "\n",
653 | " display_list = [test_input[0], tar[0], prediction[0]]\n",
654 | " title = ['Input Image', 'Ground Truth', 'Predicted Image']\n",
655 | "\n",
656 | " for i in range(3):\n",
657 | " plt.subplot(1, 3, i+1)\n",
658 | " plt.title(title[i])\n",
659 | " # getting the pixel values between [0, 1] to plot it.\n",
660 | " plt.imshow(display_list[i] * 0.5 + 0.5)\n",
661 | " plt.axis('off')\n",
662 | " plt.show()"
663 | ],
664 | "execution_count": 0,
665 | "outputs": []
666 | },
667 | {
668 | "cell_type": "code",
669 | "metadata": {
670 | "colab_type": "code",
671 | "id": "2M7LmLtGEMQJ",
672 | "colab": {}
673 | },
674 | "source": [
675 | "def train(dataset, epochs): \n",
676 | " for epoch in range(epochs):\n",
677 | " start = time.time()\n",
678 | "\n",
679 | " for input_image, target in dataset:\n",
680 | "\n",
681 | " with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:\n",
682 | " gen_output = generator(input_image, training=True)\n",
683 | "\n",
684 | " disc_real_output = discriminator(input_image, target, training=True)\n",
685 | " disc_generated_output = discriminator(input_image, gen_output, training=True)\n",
686 | "\n",
687 | " gen_loss = generator_loss(disc_generated_output, gen_output, target)\n",
688 | " disc_loss = discriminator_loss(disc_real_output, disc_generated_output)\n",
689 | "\n",
690 | " generator_gradients = gen_tape.gradient(gen_loss, \n",
691 | " generator.variables)\n",
692 | " discriminator_gradients = disc_tape.gradient(disc_loss, \n",
693 | " discriminator.variables)\n",
694 | "\n",
695 | " generator_optimizer.apply_gradients(zip(generator_gradients, \n",
696 | " generator.variables))\n",
697 | " discriminator_optimizer.apply_gradients(zip(discriminator_gradients, \n",
698 | " discriminator.variables))\n",
699 | "\n",
700 | " if epoch % 1 == 0:\n",
701 | " for inp, tar in test_dataset.take(3):\n",
702 | " generate_images(generator, inp, tar)\n",
703 | " \n",
704 | " # saving (checkpoint) the model every 20 epochs\n",
705 | " if (epoch + 1) % 20 == 0:\n",
706 | " checkpoint.save(file_prefix = checkpoint_prefix)\n",
707 | "\n",
708 | " print ('Time taken for epoch {} is {} sec\\n'.format(epoch + 1,\n",
709 | " time.time()-start))"
710 | ],
711 | "execution_count": 0,
712 | "outputs": []
713 | },
714 | {
715 | "cell_type": "code",
716 | "metadata": {
717 | "colab_type": "code",
718 | "id": "a1zZmKmvOH85",
719 | "colab": {}
720 | },
721 | "source": [
722 | "train(train_dataset, EPOCHS)"
723 | ],
724 | "execution_count": 0,
725 | "outputs": []
726 | },
727 | {
728 | "cell_type": "markdown",
729 | "metadata": {
730 | "colab_type": "text",
731 | "id": "kz80bY3aQ1VZ"
732 | },
733 | "source": [
734 | "## Restore the latest checkpoint and test"
735 | ]
736 | },
737 | {
738 | "cell_type": "code",
739 | "metadata": {
740 | "colab_type": "code",
741 | "id": "4t4x69adQ5xb",
742 | "colab": {}
743 | },
744 | "source": [
745 | "# restoring the latest checkpoint in checkpoint_dir\n",
746 | "checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))"
747 | ],
748 | "execution_count": 0,
749 | "outputs": []
750 | },
751 | {
752 | "cell_type": "markdown",
753 | "metadata": {
754 | "colab_type": "text",
755 | "id": "1RGysMU_BZhx"
756 | },
757 | "source": [
758 | "## Testing on the entire test dataset"
759 | ]
760 | },
761 | {
762 | "cell_type": "code",
763 | "metadata": {
764 | "colab_type": "code",
765 | "id": "KUgSnmy2nqSP",
766 | "colab": {}
767 | },
768 | "source": [
769 | "# Run the trained model on the entire test dataset\n",
770 | "for inp, tar in test_dataset:\n",
771 | " generate_images(generator, inp, tar)"
772 | ],
773 | "execution_count": 0,
774 | "outputs": []
775 | },
776 | {
777 | "cell_type": "code",
778 | "metadata": {
779 | "colab_type": "code",
780 | "id": "3AJXOByaZVOf",
781 | "colab": {}
782 | },
783 | "source": [
784 | ""
785 | ],
786 | "execution_count": 0,
787 | "outputs": []
788 | }
789 | ]
790 | }
--------------------------------------------------------------------------------
/stylegan_use.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "kernelspec": {
6 | "name": "python3",
7 | "display_name": "Python 3"
8 | },
9 | "language_info": {
10 | "codemirror_mode": {
11 | "name": "ipython",
12 | "version": 3
13 | },
14 | "file_extension": ".py",
15 | "mimetype": "text/x-python",
16 | "name": "python",
17 | "nbconvert_exporter": "python",
18 | "pygments_lexer": "ipython3",
19 | "version": "3.6.5"
20 | },
21 | "colab": {
22 | "name": "convert-frozen-sep.ipynb",
23 | "version": "0.3.2",
24 | "provenance": []
25 | },
26 | "accelerator": "GPU"
27 | },
28 | "cells": [
29 | {
30 | "cell_type": "code",
31 | "metadata": {
32 | "id": "huNsqyrQjb_R",
33 | "colab_type": "code",
34 | "colab": {
35 | "base_uri": "https://localhost:8080/",
36 | "height": 86
37 | },
38 | "outputId": "d780ebc3-e805-45d0-9437-8a9b64d4964c"
39 | },
40 | "source": [
41 | "!git clone https://github.com/NVlabs/stylegan"
42 | ],
43 | "execution_count": 1,
44 | "outputs": [
45 | {
46 | "output_type": "stream",
47 | "text": [
48 | "Cloning into 'stylegan'...\n",
49 | "remote: Enumerating objects: 77, done.\u001b[K\n",
50 | "remote: Total 77 (delta 0), reused 0 (delta 0), pack-reused 77\u001b[K\n",
51 | "Unpacking objects: 100% (77/77), done.\n"
52 | ],
53 | "name": "stdout"
54 | }
55 | ]
56 | },
57 | {
58 | "cell_type": "code",
59 | "metadata": {
60 | "id": "1BWYmZqDjxjt",
61 | "colab_type": "code",
62 | "colab": {}
63 | },
64 | "source": [
65 | "import os\n",
66 | "os.chdir('stylegan')"
67 | ],
68 | "execution_count": 0,
69 | "outputs": []
70 | },
71 | {
72 | "cell_type": "code",
73 | "metadata": {
74 | "id": "MzaH2gk5jWwm",
75 | "colab_type": "code",
76 | "colab": {
77 | "base_uri": "https://localhost:8080/",
78 | "height": 157
79 | },
80 | "outputId": "7eeee15d-e247-4893-eb8a-72ede28076b0"
81 | },
82 | "source": [
83 | "import time\n",
84 | "import re\n",
85 | "import bisect\n",
86 | "from collections import OrderedDict\n",
87 | "import numpy as np\n",
88 | "import tensorflow as tf\n",
89 | "import scipy.ndimage\n",
90 | "import scipy.misc\n",
91 | "import pickle\n",
92 | "import dnnlib\n",
93 | "import dnnlib.tflib as tflib\n",
94 | "from PIL import Image\n",
95 | "from IPython.display import display"
96 | ],
97 | "execution_count": 3,
98 | "outputs": [
99 | {
100 | "output_type": "stream",
101 | "text": [
102 | "WARNING: Logging before flag parsing goes to stderr.\n",
103 | "W0828 23:46:56.914453 139852722513792 deprecation_wrapper.py:119] From /content/stylegan/dnnlib/tflib/tfutil.py:34: The name tf.Dimension is deprecated. Please use tf.compat.v1.Dimension instead.\n",
104 | "\n",
105 | "W0828 23:46:56.915941 139852722513792 deprecation_wrapper.py:119] From /content/stylegan/dnnlib/tflib/tfutil.py:74: The name tf.variable_scope is deprecated. Please use tf.compat.v1.variable_scope instead.\n",
106 | "\n",
107 | "W0828 23:46:56.916904 139852722513792 deprecation_wrapper.py:119] From /content/stylegan/dnnlib/tflib/tfutil.py:128: The name tf.Session is deprecated. Please use tf.compat.v1.Session instead.\n",
108 | "\n"
109 | ],
110 | "name": "stderr"
111 | }
112 | ]
113 | },
114 | {
115 | "cell_type": "code",
116 | "metadata": {
117 | "id": "uY4h81mIjWw6",
118 | "colab_type": "code",
119 | "colab": {}
120 | },
121 | "source": [
122 | "sess = tf.InteractiveSession()"
123 | ],
124 | "execution_count": 0,
125 | "outputs": []
126 | },
127 | {
128 | "cell_type": "code",
129 | "metadata": {
130 | "id": "Fk699HyBj9S5",
131 | "colab_type": "code",
132 | "colab": {
133 | "base_uri": "https://localhost:8080/",
134 | "height": 175
135 | },
136 | "outputId": "ae473fb2-ac22-4094-f5f7-5663fd477283"
137 | },
138 | "source": [
139 | "url = 'https://drive.google.com/uc?id=1MEGjdvVpUsu1jB4zrXZN7Y4kBBOzizDQ' # karras2019stylegan-ffhq-1024x1024.pkl\n",
140 | "with dnnlib.util.open_url(url, '.') as f:\n",
141 | " _G, _D, Gs = pickle.load(f)"
142 | ],
143 | "execution_count": 5,
144 | "outputs": [
145 | {
146 | "output_type": "stream",
147 | "text": [
148 | "Downloading https://drive.google.com/uc?id=1MEGjdvVpUsu1jB4zrXZN7Y4kBBOzizDQ .... done\n"
149 | ],
150 | "name": "stdout"
151 | },
152 | {
153 | "output_type": "stream",
154 | "text": [
155 | "W0828 23:47:01.073774 139852722513792 deprecation_wrapper.py:119] From /content/stylegan/dnnlib/tflib/tfutil.py:124: The name tf.get_default_session is deprecated. Please use tf.compat.v1.get_default_session instead.\n",
156 | "\n",
157 | "W0828 23:47:01.084098 139852722513792 deprecation_wrapper.py:119] From /content/stylegan/dnnlib/tflib/network.py:142: The name tf.get_default_graph is deprecated. Please use tf.compat.v1.get_default_graph instead.\n",
158 | "\n",
159 | "W0828 23:47:05.489010 139852722513792 deprecation.py:323] From :364: add_dispatch_support..wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.\n",
160 | "Instructions for updating:\n",
161 | "Use tf.where in 2.0, which has the same broadcast rule as np.where\n"
162 | ],
163 | "name": "stderr"
164 | }
165 | ]
166 | },
167 | {
168 | "cell_type": "code",
169 | "metadata": {
170 | "id": "7iGxUNeQjWw-",
171 | "colab_type": "code",
172 | "colab": {}
173 | },
174 | "source": [
175 | "fmt = dict(func=tflib.convert_images_to_uint8, nchw_to_nhwc=True)\n"
176 | ],
177 | "execution_count": 0,
178 | "outputs": []
179 | },
180 | {
181 | "cell_type": "code",
182 | "metadata": {
183 | "id": "O7FaWfSMjWxB",
184 | "colab_type": "code",
185 | "colab": {}
186 | },
187 | "source": [
188 | "L = 5"
189 | ],
190 | "execution_count": 0,
191 | "outputs": []
192 | },
193 | {
194 | "cell_type": "code",
195 | "metadata": {
196 | "id": "FVwHBbSOjWxD",
197 | "colab_type": "code",
198 | "colab": {}
199 | },
200 | "source": [
201 | "src_latents = np.random.randn(L, *Gs.input_shape[1:])\n",
202 | "dst_latents = np.random.randn(L, *Gs.input_shape[1:])"
203 | ],
204 | "execution_count": 0,
205 | "outputs": []
206 | },
207 | {
208 | "cell_type": "code",
209 | "metadata": {
210 | "id": "Qm7NVRJnjWxF",
211 | "colab_type": "code",
212 | "colab": {}
213 | },
214 | "source": [
215 | "src_dlatents = Gs.components.mapping.run(src_latents, None) # [seed, layer, component]\n",
216 | "dst_dlatents = Gs.components.mapping.run(dst_latents, None) # [seed, layer, component]"
217 | ],
218 | "execution_count": 0,
219 | "outputs": []
220 | },
221 | {
222 | "cell_type": "code",
223 | "metadata": {
224 | "id": "0uVVisryjWxH",
225 | "colab_type": "code",
226 | "colab": {
227 | "base_uri": "https://localhost:8080/",
228 | "height": 67
229 | },
230 | "outputId": "0e4d5314-112a-47a8-82ad-17f33c9fcca8"
231 | },
232 | "source": [
233 | "for k,v in Gs.components.mapping._run_cache.items():\n",
234 | " print(k)\n",
235 | " print(v[0])\n",
236 | " print(v[1])"
237 | ],
238 | "execution_count": 10,
239 | "outputs": [
240 | {
241 | "output_type": "stream",
242 | "text": [
243 | "[('assume_frozen', False), ('dynamic_kwargs', []), ('input_transform', None), ('num_gpus', 1), ('output_transform', None)]\n",
244 | "[ dtype=float32>, dtype=float32>]\n",
245 | "[]\n"
246 | ],
247 | "name": "stdout"
248 | }
249 | ]
250 | },
251 | {
252 | "cell_type": "code",
253 | "metadata": {
254 | "id": "DMZZ9VB3jWxL",
255 | "colab_type": "code",
256 | "colab": {
257 | "base_uri": "https://localhost:8080/",
258 | "height": 34
259 | },
260 | "outputId": "2c15c151-9ec2-4ff8-a8ac-12306f2eefe8"
261 | },
262 | "source": [
263 | "v[1][0].op.inputs[0]"
264 | ],
265 | "execution_count": 11,
266 | "outputs": [
267 | {
268 | "output_type": "execute_result",
269 | "data": {
270 | "text/plain": [
271 | ""
272 | ]
273 | },
274 | "metadata": {
275 | "tags": []
276 | },
277 | "execution_count": 11
278 | }
279 | ]
280 | },
281 | {
282 | "cell_type": "code",
283 | "metadata": {
284 | "id": "4xAFfKRajWxP",
285 | "colab_type": "code",
286 | "colab": {}
287 | },
288 | "source": [
289 | "synthesis_kwargs = dict(output_transform=fmt)\n",
290 | "src_images = Gs.components.synthesis.run(src_dlatents, randomize_noise=False, **synthesis_kwargs)\n",
291 | "dst_images = Gs.components.synthesis.run(dst_dlatents, randomize_noise=False, **synthesis_kwargs)"
292 | ],
293 | "execution_count": 0,
294 | "outputs": []
295 | },
296 | {
297 | "cell_type": "code",
298 | "metadata": {
299 | "id": "76WZLXlVozVY",
300 | "colab_type": "code",
301 | "colab": {}
302 | },
303 | "source": [
304 | "Image.fromarray(src_images[0])"
305 | ],
306 | "execution_count": 0,
307 | "outputs": []
308 | },
309 | {
310 | "cell_type": "code",
311 | "metadata": {
312 | "id": "CGeqNpi4jWxR",
313 | "colab_type": "code",
314 | "colab": {
315 | "base_uri": "https://localhost:8080/",
316 | "height": 87
317 | },
318 | "outputId": "994bee60-1224-4085-c0b4-c62309468c05"
319 | },
320 | "source": [
321 | "for k,v in Gs.components.synthesis._run_cache.items():\n",
322 | " print(k)\n",
323 | " print(v[0])\n",
324 | " print(v[1])"
325 | ],
326 | "execution_count": 13,
327 | "outputs": [
328 | {
329 | "output_type": "stream",
330 | "text": [
331 | "[('assume_frozen', False), ('dynamic_kwargs', [('randomize_noise', False)]), ('input_transform', None), ('num_gpus', 1), ('output_transform', [('func', 'dnnlib.tflib.tfutil.convert_images_to_uint8'), ('nchw_to_nhwc', True)])]\n",
332 | "[ dtype=float32>]\n",
333 | "[]\n"
334 | ],
335 | "name": "stdout"
336 | }
337 | ]
338 | },
339 | {
340 | "cell_type": "code",
341 | "metadata": {
342 | "id": "XlcUFsApjWxT",
343 | "colab_type": "code",
344 | "colab": {
345 | "base_uri": "https://localhost:8080/",
346 | "height": 34
347 | },
348 | "outputId": "6c4413ab-3078-4dd8-e16c-38df15969b6e"
349 | },
350 | "source": [
351 | "v[1][0].op.inputs[0]"
352 | ],
353 | "execution_count": 14,
354 | "outputs": [
355 | {
356 | "output_type": "execute_result",
357 | "data": {
358 | "text/plain": [
359 | ""
360 | ]
361 | },
362 | "metadata": {
363 | "tags": []
364 | },
365 | "execution_count": 14
366 | }
367 | ]
368 | },
369 | {
370 | "cell_type": "code",
371 | "metadata": {
372 | "id": "3hoZ9P40jWxV",
373 | "colab_type": "code",
374 | "colab": {}
375 | },
376 | "source": [
377 | ""
378 | ],
379 | "execution_count": 0,
380 | "outputs": []
381 | },
382 | {
383 | "cell_type": "code",
384 | "metadata": {
385 | "id": "oOc7__DejWxX",
386 | "colab_type": "code",
387 | "colab": {
388 | "base_uri": "https://localhost:8080/",
389 | "height": 34
390 | },
391 | "outputId": "57a1371a-0399-4691-fb89-9da8ccf8d814"
392 | },
393 | "source": [
394 | "src_dlatents.shape"
395 | ],
396 | "execution_count": 15,
397 | "outputs": [
398 | {
399 | "output_type": "execute_result",
400 | "data": {
401 | "text/plain": [
402 | "(5, 18, 512)"
403 | ]
404 | },
405 | "metadata": {
406 | "tags": []
407 | },
408 | "execution_count": 15
409 | }
410 | ]
411 | },
412 | {
413 | "cell_type": "code",
414 | "metadata": {
415 | "id": "7olCYpB4jWxZ",
416 | "colab_type": "code",
417 | "colab": {}
418 | },
419 | "source": [
420 | "w,h=256,256\n",
421 | "style_ranges=[range(0,1), range(0,3), range(0,5), range(0,8), range(0,11)]\n",
422 | "canvas = Image.new('RGB', (w * (L + 1), h * (L + 1)) , 'white')\n",
423 | "for col, src_image in enumerate(list(src_images)):\n",
424 | " canvas.paste(Image.fromarray(src_image, 'RGB').resize((w,h)), ((col + 1) * w, 0))\n",
425 | "for row, dst_image in enumerate(list(dst_images)):\n",
426 | " canvas.paste(Image.fromarray(dst_image, 'RGB').resize((w,h)), (0, (row + 1) * h))\n",
427 | " row_dlatents = np.stack([dst_dlatents[row]] * L)\n",
428 | " row_dlatents[:, style_ranges[row]] = src_dlatents[:, style_ranges[row]]\n",
429 | " row_images = Gs.components.synthesis.run(row_dlatents, randomize_noise=False, **synthesis_kwargs)\n",
430 | " for col, image in enumerate(list(row_images)):\n",
431 | " canvas.paste(Image.fromarray(image, 'RGB').resize((w,h)), ((col + 1) * w, (row + 1) * h))\n",
432 | "display(canvas)"
433 | ],
434 | "execution_count": 0,
435 | "outputs": []
436 | },
437 | {
438 | "cell_type": "code",
439 | "metadata": {
440 | "id": "PrERxHQhjWxk",
441 | "colab_type": "code",
442 | "colab": {}
443 | },
444 | "source": [
445 | ""
446 | ],
447 | "execution_count": 0,
448 | "outputs": []
449 | },
450 | {
451 | "cell_type": "code",
452 | "metadata": {
453 | "id": "Uj247q3OjWxm",
454 | "colab_type": "code",
455 | "colab": {}
456 | },
457 | "source": [
458 | ""
459 | ],
460 | "execution_count": 0,
461 | "outputs": []
462 | },
463 | {
464 | "cell_type": "code",
465 | "metadata": {
466 | "id": "lxI5ohkujWxq",
467 | "colab_type": "code",
468 | "colab": {}
469 | },
470 | "source": [
471 | ""
472 | ],
473 | "execution_count": 0,
474 | "outputs": []
475 | }
476 | ]
477 | }
478 |
--------------------------------------------------------------------------------