{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!ls ../input/gan-getting-started","metadata":{"execution":{"iopub.status.busy":"2022-01-03T11:05:33.807135Z","iopub.execute_input":"2022-01-03T11:05:33.807833Z","iopub.status.idle":"2022-01-03T11:05:34.568024Z","shell.execute_reply.started":"2022-01-03T11:05:33.807794Z","shell.execute_reply":"2022-01-03T11:05:34.567011Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport cv2\nimport matplotlib.pyplot as plt","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:08:47.882068Z","iopub.execute_input":"2022-01-03T17:08:47.882335Z","iopub.status.idle":"2022-01-03T17:08:48.068409Z","shell.execute_reply.started":"2022-01-03T17:08:47.882305Z","shell.execute_reply":"2022-01-03T17:08:48.067697Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"определяю генератор","metadata":{}},{"cell_type":"code","source":"class ResidualBlock(nn.Module):\n    def __init__(self, in_channels):\n        super(ResidualBlock, self).__init__()\n        \n        self.block = nn.Sequential(\n            nn.ReflectionPad2d(1),\n            nn.Conv2d(in_channels, in_channels, 3), # тот же размер изображения на выходе, что изначально\n            nn.InstanceNorm2d(in_channels),\n            nn.ReLU(inplace=True),\n            nn.ReflectionPad2d(1),\n            nn.Conv2d(in_channels, in_channels, 3),\n            nn.InstanceNorm2d(in_channels)\n        )\n    def forward(self, x):\n        return x + self.block(x)","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:08:48.262316Z","iopub.execute_input":"2022-01-03T17:08:48.262858Z","iopub.status.idle":"2022-01-03T17:08:48.269065Z","shell.execute_reply.started":"2022-01-03T17:08:48.262822Z","shell.execute_reply":"2022-01-03T17:08:48.267894Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class GeneratorResNet(nn.Module):\n    def __init__(self, in_channels, num_residual_blocks = 9):\n        super(GeneratorResNet, self).__init__()\n        \n#         initial convolution 3 * 256 * 256 -> 64 * 256 * 256\n        out_channels = 64\n        \n        self.conv = nn.Sequential(\n            nn.ReflectionPad2d(in_channels), # чтоб размер остался таким же после след conv слоя\n            nn.Conv2d(in_channels, out_channels, 2*in_channels + 1),\n            nn.InstanceNorm2d(out_channels),\n            nn.ReLU(inplace=True)\n        )\n        channels = out_channels\n        \n#         downsampling 64 * 256 * 256 -> 128 * 128 * 128 -> 256 * 64 * 64\n        self.down = []\n        for _ in range(2):\n            out_channels = channels * 2\n            self.down += [\n                nn.Conv2d(channels, out_channels, 3, stride = 2, padding = 1),\n                nn.InstanceNorm2d(out_channels),\n                nn.ReLU(inplace=True)\n            ]\n            channels = out_channels\n        self.down = nn.Sequential(*self.down)\n        \n#         transformation (ResNet) 256 * 64 * 64\n        self.trans = [ResidualBlock(channels) for _ in range(num_residual_blocks)]\n        self.trans = nn.Sequential(*self.trans)\n        \n        # upsamping 256 * 64 * 64 -> 128 * 128 * 128 -> 64 * 256 * 256\n        self.up = []\n        \n        for _ in range(2):\n            out_channels = channels // 2\n            self.up += [\n                nn.Upsample(scale_factor = 2),\n                nn.Conv2d(channels, out_channels, 3, stride= 1, padding = 1),\n                nn.InstanceNorm2d(out_channels),\n                nn.ReLU(inplace=True)\n            ]\n            channels = out_channels\n        self.up = nn.Sequential(*self.up)\n        \n        # Out layer  64*256*256 -> 3*256*256\n        self.out = nn.Sequential(\n            nn.ReflectionPad2d(in_channels),\n            nn.Conv2d(channels, in_channels, 2*in_channels+1),\n            nn.Tanh()\n        )\n        \n    def forward(self, x):\n        x = self.conv(x)\n        x = self.down(x)\n        x = self.trans(x)\n        x = self.up(x)\n        x = self.out(x)\n        \n        return x","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:08:48.398422Z","iopub.execute_input":"2022-01-03T17:08:48.398756Z","iopub.status.idle":"2022-01-03T17:08:48.410837Z","shell.execute_reply.started":"2022-01-03T17:08:48.398726Z","shell.execute_reply":"2022-01-03T17:08:48.410122Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Discriminator(nn.Module):\n    def __init__(self, in_channels):\n        super(Discriminator, self).__init__()\n        \n        self.model = nn.Sequential(\n#         why normalize = False?\n            *self.block(in_channels, 64, normalize = False), # 3 * 256 * 256 -> 64*128*128\n            *self.block(64, 128), # 64 * 128 * 128 -> 128*64*64\n            *self.block(128, 256),\n            *self.block(256, 512), # 256 * 32 * 32 -> 512*16*16\n            \n#             why padding = first then convolution?\n            nn.ZeroPad2d((1, 0, 1, 0)), # 512 * 16 * 16 -> 512 * 17 * 17\n            nn.Conv2d(512, 1, 4, padding = 1) # 512 * 17 * 17 -> 1 * 16 * 16\n        )\n        self.scale_factor = 16\n        \n    @staticmethod\n    def block(in_channels, out_channels, normalize = True):\n        layers = [nn.Conv2d(in_channels, out_channels, 4 , stride = 2, padding = 1)]\n        if normalize:\n            layers.append(nn.InstanceNorm2d(out_channels))\n        layers.append(nn.LeakyReLU(0.2, inplace=True))\n        \n        return layers\n    \n    def forward(self, x):\n        return self.model(x)","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:08:48.835949Z","iopub.execute_input":"2022-01-03T17:08:48.836621Z","iopub.status.idle":"2022-01-03T17:08:48.845777Z","shell.execute_reply.started":"2022-01-03T17:08:48.836550Z","shell.execute_reply":"2022-01-03T17:08:48.844930Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"step 3. Define loss ","metadata":{}},{"cell_type":"code","source":"criterion_GAN = nn.MSELoss()\ncriterion_cycle = nn.L1Loss()\ncriterion_identity = nn.L1Loss()","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:08:49.764453Z","iopub.execute_input":"2022-01-03T17:08:49.764985Z","iopub.status.idle":"2022-01-03T17:08:49.769461Z","shell.execute_reply.started":"2022-01-03T17:08:49.764947Z","shell.execute_reply":"2022-01-03T17:08:49.768734Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"step 4. Initialize G and D","metadata":{}},{"cell_type":"code","source":"G_AB = GeneratorResNet(3, num_residual_blocks=9)\nD_B  = Discriminator(3)\n\nG_BA = GeneratorResNet(3, num_residual_blocks=9)\nD_A = Discriminator(3)","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:08:50.634515Z","iopub.execute_input":"2022-01-03T17:08:50.635238Z","iopub.status.idle":"2022-01-03T17:08:50.940305Z","shell.execute_reply.started":"2022-01-03T17:08:50.635200Z","shell.execute_reply":"2022-01-03T17:08:50.939457Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cuda = torch.cuda.is_available()\nprint('cuda:', cuda)\nif cuda:\n    G_AB = G_AB.cuda()\n    G_BA = G_BA.cuda()\n    \n    D_A = D_A.cuda()\n    D_B = D_B.cuda()\n    \n    criterion_GAN = criterion_GAN.cuda()\n    criterion_cycle = criterion_cycle.cuda()\n    criterion_identity = criterion_identity.cuda()","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:08:51.100474Z","iopub.execute_input":"2022-01-03T17:08:51.101082Z","iopub.status.idle":"2022-01-03T17:08:54.167491Z","shell.execute_reply.started":"2022-01-03T17:08:51.101045Z","shell.execute_reply":"2022-01-03T17:08:54.166719Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Step 5. Configure Optimizers","metadata":{}},{"cell_type":"code","source":"import itertools\nlr = 0.0002\nb1 = 0.5\nb2 = 0.999\n\noptimizer_G = torch.optim.Adam(\n    itertools.chain(G_AB.parameters(), G_BA.parameters()), lr=lr, betas=(b1, b2)\n)\n\noptimizer_D_A = torch.optim.Adam(\n    D_A.parameters(), lr=lr, betas=(b1, b2)\n)\n\noptimizer_D_B = torch.optim.Adam(\n    D_B.parameters(), lr=lr, betas=(b1, b2)\n)","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:08:54.169009Z","iopub.execute_input":"2022-01-03T17:08:54.169242Z","iopub.status.idle":"2022-01-03T17:08:54.176831Z","shell.execute_reply.started":"2022-01-03T17:08:54.169207Z","shell.execute_reply":"2022-01-03T17:08:54.175712Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Step 6. Learning Rate Scheduler Setting","metadata":{}},{"cell_type":"code","source":"n_epoches = 100\ndecay_epoch = 20\n\nlambda_func = lambda epoch: 1 - max(0, epoch-decay_epoch)/(n_epoches-decay_epoch)\n\nlr_scheduler_G = torch.optim.lr_scheduler.LambdaLR(optimizer_G, lr_lambda=lambda_func)\n\nlr_scheduler_D_A = torch.optim.lr_scheduler.LambdaLR(optimizer_D_A, lr_lambda=lambda_func)\nlr_scheduler_D_B = torch.optim.lr_scheduler.LambdaLR(optimizer_D_B, lr_lambda=lambda_func)","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:08:54.178322Z","iopub.execute_input":"2022-01-03T17:08:54.178703Z","iopub.status.idle":"2022-01-03T17:08:54.186767Z","shell.execute_reply.started":"2022-01-03T17:08:54.178668Z","shell.execute_reply":"2022-01-03T17:08:54.185999Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Step 7. DataLoader","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import Dataset, DataLoader\nfrom PIL import Image\n\n\nclass ImageDataset(Dataset):\n    def __init__(self, data_dir, mode='train', transforms=None):\n        A_dir = os.path.join(data_dir, 'monet_jpg')\n        B_dir = os.path.join(data_dir, 'photo_jpg')\n        \n        if mode == 'train':\n            self.files_A = [os.path.join(A_dir, name) for name in sorted(os.listdir(A_dir))[:250]]\n            self.files_B = [os.path.join(B_dir, name) for name in sorted(os.listdir(B_dir))[:250]]\n        elif mode == 'test':\n            self.files_A = [os.path.join(A_dir, name) for name in sorted(os.listdir(A_dir))[250:]]\n            self.files_B = [os.path.join(B_dir, name) for name in sorted(os.listdir(B_dir))[250:301]]\n        \n        self.transforms = transforms\n        \n    def __len__(self):\n        return len(self.files_A)\n    \n    def __getitem__(self, index):\n        file_A = self.files_A[index]\n        file_B = self.files_B[index]\n        \n        img_A = Image.open(file_A)\n        img_B = Image.open(file_B)\n        \n        if self.transforms is not None:\n            img_A = self.transforms(img_A)\n            img_B = self.transforms(img_B)\n        \n        return img_A, img_B\n    ","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:08:54.188464Z","iopub.execute_input":"2022-01-03T17:08:54.188663Z","iopub.status.idle":"2022-01-03T17:08:54.201327Z","shell.execute_reply.started":"2022-01-03T17:08:54.188633Z","shell.execute_reply":"2022-01-03T17:08:54.200664Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torchvision.transforms as transforms\n\ndata_dir = '/kaggle/input/gan-getting-started'\n\ntransforms_ = transforms.Compose([\n   # transforms.Resize(int(256*1.12), Image.BICUBIC),\n    #transforms.RandomCrop(256, 256),\n    transforms.RandomHorizontalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))\n])\nbatch_size = 5\n\ntrainloader = DataLoader(\n    ImageDataset(data_dir, mode='train', transforms=transforms_),\n    batch_size = batch_size,\n    shuffle = True,\n    num_workers = 2\n)\n\ntestloader = DataLoader(\n    ImageDataset(data_dir, mode='test', transforms=transforms_),\n    batch_size = batch_size,\n    shuffle = False,\n    num_workers = 2\n)","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:08:54.202800Z","iopub.execute_input":"2022-01-03T17:08:54.203556Z","iopub.status.idle":"2022-01-03T17:08:54.414373Z","shell.execute_reply.started":"2022-01-03T17:08:54.203520Z","shell.execute_reply":"2022-01-03T17:08:54.413637Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Step 8. Sample images to show","metadata":{}},{"cell_type":"code","source":"from torchvision.utils import make_grid\n\nTensor = torch.cuda.FloatTensor if cuda else torch.Tensor\n\ndef sample_images(real_A, real_B, figside=1.5):\n    assert real_A.size() == real_B.size(), 'The image size for two domains must be the same'\n    \n    G_AB.eval()\n    G_BA.eval()\n    \n    real_A = real_A.type(Tensor)\n    fake_B = G_AB(real_A).detach()\n    real_B = real_B.type(Tensor)\n    fake_A = G_BA(real_B).detach()\n    \n    nrows = real_A.size(0)\n    real_A = make_grid(real_A, nrow=nrows, normalize=True)\n    fake_B = make_grid(fake_B, nrow=nrows, normalize=True)\n    real_B = make_grid(real_B, nrow=nrows, normalize=True)\n    fake_A = make_grid(fake_A, nrow=nrows, normalize=True)\n    \n    image_grid = torch.cat((real_A, fake_B, real_B, fake_A), 1).cpu().permute(1, 2, 0)\n    \n    plt.figure(figsize=(figside*nrows, figside*4))\n    plt.imshow(image_grid)\n    plt.axis('off')\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:08:54.415476Z","iopub.execute_input":"2022-01-03T17:08:54.416353Z","iopub.status.idle":"2022-01-03T17:08:54.427494Z","shell.execute_reply.started":"2022-01-03T17:08:54.416321Z","shell.execute_reply":"2022-01-03T17:08:54.426540Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"real_A, real_B = next(iter(testloader))\nsample_images(real_A, real_B)","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:08:54.429208Z","iopub.execute_input":"2022-01-03T17:08:54.429677Z","iopub.status.idle":"2022-01-03T17:09:00.179182Z","shell.execute_reply.started":"2022-01-03T17:08:54.429641Z","shell.execute_reply":"2022-01-03T17:09:00.178432Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"real_A.size()","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:09:00.180919Z","iopub.execute_input":"2022-01-03T17:09:00.181164Z","iopub.status.idle":"2022-01-03T17:09:00.188471Z","shell.execute_reply.started":"2022-01-03T17:09:00.181129Z","shell.execute_reply":"2022-01-03T17:09:00.187752Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"step 9. Training","metadata":{}},{"cell_type":"code","source":"out_shape = [real_A.size(0), 1, real_A.size(2)//D_A.scale_factor, real_A.size(3)//D_A.scale_factor]","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:09:03.105347Z","iopub.execute_input":"2022-01-03T17:09:03.105997Z","iopub.status.idle":"2022-01-03T17:09:03.111127Z","shell.execute_reply.started":"2022-01-03T17:09:03.105957Z","shell.execute_reply":"2022-01-03T17:09:03.110132Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"real_A.size()","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:09:03.170221Z","iopub.execute_input":"2022-01-03T17:09:03.170817Z","iopub.status.idle":"2022-01-03T17:09:03.175356Z","shell.execute_reply.started":"2022-01-03T17:09:03.170784Z","shell.execute_reply":"2022-01-03T17:09:03.174576Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"out_shape","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:09:03.392887Z","iopub.execute_input":"2022-01-03T17:09:03.393426Z","iopub.status.idle":"2022-01-03T17:09:03.399082Z","shell.execute_reply.started":"2022-01-03T17:09:03.393388Z","shell.execute_reply":"2022-01-03T17:09:03.398257Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for epoch in range(n_epoches):\n    for i, (real_A, real_B) in enumerate(trainloader):\n        real_A, real_B = real_A.type(Tensor), real_B.type(Tensor)\n        \n        # groud truth\n        \n        valid = torch.ones(out_shape).type(Tensor)\n        fake = torch.zeros(out_shape).type(Tensor)\n        \n        \"\"\"Train Generators\"\"\"\n        # set to training mode in the begining, beacause sample_images will set it to eval mode\n        G_AB.train()\n        G_BA.train()\n        \n        optimizer_G.zero_grad()\n        \n        fake_B = G_AB(real_A)\n        fake_A = G_BA(real_B)\n        \n        # identity loss\n        loss_id_A = criterion_identity(fake_B, real_A)\n        loss_id_B = criterion_identity(fake_A, real_B)\n        loss_identity = (loss_id_A + loss_id_B) / 2\n        \n        # GAN loss, train G to make D think it's true\n        loss_GAN_AB = criterion_GAN(D_B(fake_B), valid) \n        loss_GAN_BA = criterion_GAN(D_A(fake_A), valid)\n        loss_GAN = (loss_GAN_AB + loss_GAN_BA) / 2\n        \n        # cycle loss\n        recov_A = G_BA(fake_B)\n        recov_B = G_AB(fake_A)\n        loss_cycle_A = criterion_cycle(recov_A, real_A)\n        loss_cycle_B = criterion_cycle(recov_B, real_B)\n        loss_cycle = (loss_cycle_A + loss_cycle_B) / 2\n        \n        # G totol loss\n        loss_G = 5.0*loss_identity + loss_GAN + 10.0*loss_cycle\n        \n        loss_G.backward()\n        optimizer_G.step()\n        \n        \"\"\"Train Discriminator A\"\"\"\n        optimizer_D_A.zero_grad()\n        \n        loss_real = criterion_GAN(D_A(real_A), valid)\n        loss_fake = criterion_GAN(D_A(fake_A.detach()), fake)\n        loss_D_A = (loss_real + loss_fake) / 2\n        \n        loss_D_A.backward()\n        optimizer_D_A.step()\n        \n        \"\"\"Train Discriminator B\"\"\"\n        optimizer_D_B.zero_grad()\n        \n        loss_real = criterion_GAN(D_B(real_B), valid)\n        loss_fake = criterion_GAN(D_B(fake_B.detach()), fake)\n        loss_D_B = (loss_real + loss_fake) / 2\n        \n        loss_D_B.backward()\n        optimizer_D_B.step()\n    \n    lr_scheduler_G.step()\n    lr_scheduler_D_A.step()\n    lr_scheduler_D_B.step()\n    \n    # test\n    if (epoch+1) % 10 == 0:\n        test_real_A, test_real_B = next(iter(testloader))\n        sample_images(test_real_A, test_real_B)\n\n        loss_D = (loss_D_A + loss_D_B) / 2\n        print(f'[Epoch {epoch+1}/{n_epoches}]')\n        print(f'[G loss: {loss_G.item()} | identity: {loss_identity.item()} GAN: {loss_GAN.item()} cycle: {loss_cycle.item()}]')\n        print(f'[D loss: {loss_D.item()} | D_A: {loss_D_A.item()} D_B: {loss_D_B.item()}]')   ","metadata":{"execution":{"iopub.status.busy":"2022-01-03T17:09:03.635710Z","iopub.execute_input":"2022-01-03T17:09:03.636214Z","iopub.status.idle":"2022-01-03T18:30:53.781304Z","shell.execute_reply.started":"2022-01-03T17:09:03.636162Z","shell.execute_reply":"2022-01-03T18:30:53.780444Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Step 10. Generate Images","metadata":{}},{"cell_type":"code","source":"photo_dir = os.path.join(data_dir, 'photo_jpg')\nfiles = [os.path.join(photo_dir, name) for name in os.listdir(photo_dir)]\nlen(files)","metadata":{"execution":{"iopub.status.busy":"2022-01-03T18:48:03.196502Z","iopub.execute_input":"2022-01-03T18:48:03.197098Z","iopub.status.idle":"2022-01-03T18:48:03.221149Z","shell.execute_reply.started":"2022-01-03T18:48:03.197059Z","shell.execute_reply":"2022-01-03T18:48:03.220468Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"save_dir = '../images'\nif not os.path.exists(save_dir):\n    os.makedirs(save_dir)","metadata":{"execution":{"iopub.status.busy":"2022-01-03T18:49:02.830490Z","iopub.execute_input":"2022-01-03T18:49:02.831169Z","iopub.status.idle":"2022-01-03T18:49:02.835304Z","shell.execute_reply.started":"2022-01-03T18:49:02.831135Z","shell.execute_reply":"2022-01-03T18:49:02.834634Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"generate_transforms = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))\n])\n\nto_image = transforms.ToPILImage()\n\nG_BA.eval()\nfor i in range(0, len(files), batch_size):\n    # read images\n    imgs = []\n    for j in range(i, min(len(files), i+batch_size)):\n        img = Image.open(files[j])\n        img = generate_transforms(img)\n        imgs.append(img)\n    imgs = torch.stack(imgs, 0).type(Tensor)\n    \n    # generate\n    fake_imgs = G_BA(imgs).detach().cpu()\n    \n    # save\n    for j in range(fake_imgs.size(0)):\n        img = fake_imgs[j].squeeze().permute(1, 2, 0)\n        img_arr = img.numpy()\n        img_arr = (img_arr - np.min(img_arr)) * 255 / (np.max(img_arr) - np.min(img_arr))\n        img_arr = img_arr.astype(np.uint8)\n        \n        img = to_image(img_arr)\n        _, name = os.path.split(files[i+j])\n        img.save(os.path.join(save_dir, name))","metadata":{"execution":{"iopub.status.busy":"2022-01-03T18:52:30.735428Z","iopub.execute_input":"2022-01-03T18:52:30.735887Z","iopub.status.idle":"2022-01-03T18:55:39.044482Z","shell.execute_reply.started":"2022-01-03T18:52:30.735849Z","shell.execute_reply":"2022-01-03T18:55:39.043716Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import shutil\nshutil.make_archive(\"/kaggle/working/images\", 'zip', \"/kaggle/images\")","metadata":{"execution":{"iopub.status.busy":"2022-01-03T18:55:39.046697Z","iopub.execute_input":"2022-01-03T18:55:39.046980Z","iopub.status.idle":"2022-01-03T18:55:42.715878Z","shell.execute_reply.started":"2022-01-03T18:55:39.046937Z","shell.execute_reply":"2022-01-03T18:55:42.715166Z"},"trusted":true},"execution_count":null,"outputs":[]}]}