{"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":"markdown","source":"#### What are you trying to do in this notebook?\nWe recognize the works of artists through their unique style, such as color choices or brush strokes. Artists like Claude Monet can now be imitated with algorithms thanks to generative adversarial networks (GANs). In this competition, I will bring that style to my photos or recreate the style from scratch!\n\nComputer vision has advanced tremendously in recent years and GANs are now capable of mimicking objects in a very convincing way. But creating museum-worthy masterpieces is thought of to be, well, more art than science. So can (data) science, in the form of GANs, trick classifiers into believing I’ve created a true Monet? That’s the challenge I’ll take on!\n\n#### Why are you trying it?\nA GAN consists of at least two neural networks: a generator model and a discriminator model. The generator is a neural network that creates the images. For this competition, I generate images in the style of Monet. This generator is trained using a discriminator.\nThe two models will work against each other, with the generator trying to trick the discriminator, and the discriminator trying to accurately classify the real vs. generated images.\n\nMy task is to build a GAN that generates 7,000 to 10,000 Monet-style images.","metadata":{}},{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"execution":{"iopub.status.busy":"2021-12-25T10:00:06.920962Z","iopub.execute_input":"2021-12-25T10:00:06.921288Z","iopub.status.idle":"2021-12-25T10:00:12.466431Z","shell.execute_reply.started":"2021-12-25T10:00:06.921250Z","shell.execute_reply":"2021-12-25T10:00:12.434090Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install wandb\nimport wandb\n# Wandb Login\nwandb.login()\n# wandb config\n#WANDB_CONFIG = {'competition': 'Sartorius', '_wandb_kernel': 'neuracort', 'entity':\"kohyun1207\"}\nrun = wandb.init(project='Monet Cycle GAN')","metadata":{"execution":{"iopub.status.busy":"2021-12-25T10:00:12.479207Z","iopub.execute_input":"2021-12-25T10:00:12.479484Z","iopub.status.idle":"2021-12-25T10:00:57.431314Z","shell.execute_reply.started":"2021-12-25T10:00:12.479457Z","shell.execute_reply":"2021-12-25T10:00:57.430522Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport os\nfrom PIL import Image\nimport itertools\nfrom tqdm import tqdm\n\n\nimport torch\nimport torchvision\n\n\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as T\nimport torch.nn as nn\nimport torch.optim.lr_scheduler as lr_scheduler","metadata":{"execution":{"iopub.status.busy":"2021-12-25T10:00:57.433608Z","iopub.execute_input":"2021-12-25T10:00:57.434333Z","iopub.status.idle":"2021-12-25T10:00:57.442481Z","shell.execute_reply.started":"2021-12-25T10:00:57.434269Z","shell.execute_reply":"2021-12-25T10:00:57.441554Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"path = ['../input/gan-getting-started/monet_jpg/','../input/gan-getting-started/photo_jpg/']\nmonet = os.listdir('../input/gan-getting-started/monet_jpg')\nphoto = os.listdir('../input/gan-getting-started/photo_jpg')","metadata":{"execution":{"iopub.status.busy":"2021-12-25T10:00:57.444704Z","iopub.execute_input":"2021-12-25T10:00:57.445491Z","iopub.status.idle":"2021-12-25T10:00:57.460486Z","shell.execute_reply.started":"2021-12-25T10:00:57.445446Z","shell.execute_reply":"2021-12-25T10:00:57.459379Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#visulization\ndef visulization(x,y):\n    plt.figure(figsize = (10,5))\n    plt.subplot(1,2,1)\n    plt.title('actual')\n    plt.imshow(x.cpu().detach().numpy().transpose(1,2,0))\n    plt.tick_params(left = False, bottom = False, labelleft = False, labelbottom = False)\n    plt.subplot(1,2,2)\n    plt.title('fake')\n    plt.imshow(y.cpu().detach().numpy().transpose(1,2,0))\n    plt.tick_params(left = False, bottom = False, labelleft = False, labelbottom = False)\n    plt.show()\n    \n# weight initialization \ndef weight_init(m : 'model'):\n    classname = m.__class__.__name__\n    if classname.find('Conv') != -1:\n        nn.init.normal_(m.weight.data, 0.0, 0.02)\n    elif classname.find('Instance') != -1:\n        nn.init.normal_(m.weight.data, 1.0, 0.02)\n        nn.init.constant_(m.bias.data, 0)\n#Nomalizer \nclass normalize(object):\n    def __call__(self, inputs):\n        mean = 0.5 \n        std = 0.5\n        inputs = ((inputs - mean) / std)\n        return inputs\n\ndef update_req_grad(models, requires_grad=True):\n    for model in models:\n        for param in model.parameters():\n            param.requires_grad = requires_grad","metadata":{"execution":{"iopub.status.busy":"2021-12-25T10:00:57.461928Z","iopub.execute_input":"2021-12-25T10:00:57.462707Z","iopub.status.idle":"2021-12-25T10:00:57.475721Z","shell.execute_reply.started":"2021-12-25T10:00:57.462668Z","shell.execute_reply":"2021-12-25T10:00:57.474852Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Defining Dataset\n\ntrain_transform = T.Compose([T.RandomHorizontalFlip(p = 0.5),\n                             T.RandomVerticalFlip(p = 0.5),\n                             T.RandomRotation(180),\n                             T.ToTensor(),\n                             normalize()])\n\nclass CustomDataset(Dataset):\n    def __init__(self, path, monet, photo, transforms = None, seed = 777):\n        self.path = path\n        self.monet = monet\n        self.photo = photo\n        self.seed = seed\n        self.transforms = transforms\n        self.photo_len = len(self.monet)\n        self.monet_len = len(self.photo)\n        self.length_dataset = max(self.photo_len, self.monet_len)\n        \n        \n    def __len__(self):\n        return len(self.photo)\n    \n    def __getitem__(self, idx):\n        \n        \n        #get path\n        monet_path = self.path[0] + self.monet[idx % 300]\n        photo_path = self.path[1] + self.photo[idx]\n        #get image\n        monet = Image.open(monet_path).convert('RGB')\n        photo = Image.open(photo_path).convert('RGB')\n        #image Transform\n        if self.transforms:\n            torch.manual_seed(self.seed)\n            monet = self.transforms(monet)\n        if self.transforms:\n            torch.manual_seed(self.seed)\n            photo = self.transforms(photo)\n        return monet, photo","metadata":{"execution":{"iopub.status.busy":"2021-12-25T10:00:57.477146Z","iopub.execute_input":"2021-12-25T10:00:57.477578Z","iopub.status.idle":"2021-12-25T10:00:57.492725Z","shell.execute_reply.started":"2021-12-25T10:00:57.477549Z","shell.execute_reply":"2021-12-25T10:00:57.492001Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Cycle GAN\n#Generator(Photo <--> Monet)\n#Defining layers\nclass Generator(nn.Module):\n    def __init__(self):\n        super(Generator, self).__init__()\n        \n        #1. Conv layers\n        \n        def CLayer(in_ch, out_ch, kernel_size = 3, stride = 1, padding = 1, bias = True, norm = 'bnorm', relu = 'relu'):\n            layers = []\n\n            layers += [nn.ReflectionPad2d(padding)]\n                         \n            layers += [nn.Conv2d(in_channels = in_ch,\n                                 out_channels = out_ch,\n                                 kernel_size = kernel_size,\n                                 stride = stride,\n                                 padding = 0,\n                                 bias = bias)]\n            if not norm is None:\n                if norm == 'bnorm':\n                    layers += [nn.BatchNorm2d(num_features = out_ch)]\n                elif norm =='inorm':\n                    layers += [nn.InstanceNorm2d(num_features = out_ch)]\n            \n            if relu == 'relu':\n                layers += [nn.ReLU()]\n            elif relu == 'leakyrelu':\n                layers += [nn.LeakyReLU()]\n                \n            return nn.Sequential(*layers)\n        \n        #2. Residual Blocks\n        \n        def Rblock(in_ch, out_ch, kernel_size = 3, stride = 1, padding = 1, bias = True, norm = 'bnorm', relu = 0.0):\n            layers = []\n            layers += [CLayer(in_ch = in_ch,\n                              out_ch = out_ch,\n                              kernel_size = kernel_size,\n                              stride = stride,\n                              padding = padding,\n                              bias = bias,\n                              norm = norm)]\n            \n            layers += [CLayer(in_ch = in_ch,\n                              out_ch = out_ch,\n                              kernel_size = kernel_size,\n                              stride = stride,\n                              padding = padding,\n                              bias = bias,\n                              norm = norm,\n                             relu = None)]\n            return nn.Sequential(*layers)\n        \n        #3. Transpose Conv layers\n        \n        def TCLayer(in_ch, out_ch, kernel_size = 3, stride = 1, padding = 1, output_padding = 1, bias = True, norm = 'bnorm', relu = 'relu'):\n            layers = []\n            layers += [nn.ConvTranspose2d(in_channels = in_ch,\n                                         out_channels = out_ch,\n                                         kernel_size = kernel_size,\n                                         stride = stride,\n                                         padding = padding,\n                                         output_padding = output_padding,\n                                         bias = bias)]\n            if not norm is None:\n                if norm == 'bnorm':\n                    layers += [nn.BatchNorm2d(num_features = out_ch)]\n                elif norm =='inorm':\n                    layers += [nn.InstanceNorm2d(num_features = out_ch)]\n                    \n            if relu == 'relu':\n                layers += [nn.ReLU()]\n            elif relu == 'leakyrelu':\n                layers += [nn.LeakyReLU(0.2)]\n                \n            return nn.Sequential(*layers)\n        \n        #Encoder\n        self.encoder1 = CLayer(in_ch = 3, out_ch = 64 , kernel_size = 7, stride = 1, padding = 3, norm = 'inorm', relu = 'relu')\n        self.encoder2 = CLayer(in_ch = 64, out_ch = 128 , kernel_size = 3, stride = 2, padding = 1, norm = 'inorm', relu = 'relu')\n        self.encoder3 = CLayer(in_ch = 128, out_ch = 256 , kernel_size = 3, stride = 2, padding = 1, norm = 'inorm', relu = 'relu')\n\n        #Transformer\n        res_layer = []\n        \n        for i in range(6):\n            res_layer += [Rblock(in_ch = 256, out_ch = 256, kernel_size = 3, stride = 1, padding = 1, norm = 'inorm', relu = 'relu')]\n            \n        self.trans = nn.Sequential(*res_layer)\n\n        #Decoder\n        self.decoder1 = TCLayer(in_ch = 256, out_ch = 128, kernel_size = 3, stride = 2, padding = 1, output_padding = 1, norm = 'inorm', relu = 'relu')\n        self.decoder2 = TCLayer(in_ch = 128, out_ch = 64, kernel_size = 3, stride = 2, padding = 1, output_padding = 1, norm = 'inorm', relu = 'relu')\n        self.decoder3 = CLayer(in_ch = 64, out_ch = 3 , kernel_size = 7, stride = 1, padding = 3, norm = 'inorm', relu = 'relu')\n        \n    def forward(self, input):\n        \n        #Encoder\n        x = self.encoder1(input)\n        x = self.encoder2(x)\n        x = self.encoder3(x)\n        \n        #Transformer\n        x = self.trans(x)\n        \n        #Decoder\n        x = self.decoder1(x)\n        x = self.decoder2(x)\n        x = self.decoder3(x)\n        \n        output = torch.tanh(x)\n              \n        return output","metadata":{"execution":{"iopub.status.busy":"2021-12-25T10:00:57.494204Z","iopub.execute_input":"2021-12-25T10:00:57.494696Z","iopub.status.idle":"2021-12-25T10:00:57.524393Z","shell.execute_reply.started":"2021-12-25T10:00:57.494663Z","shell.execute_reply":"2021-12-25T10:00:57.523475Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Cycle GAN\n#Discriminator(Photo <--> Monet)\n#Defining layers\nclass Discriminator(nn.Module):\n    def __init__(self):\n        super(Discriminator, self).__init__()\n        \n        #1. Conv layers\n        \n        def CLayer(in_ch, out_ch, kernel_size = 3, stride = 1, padding = 1, bias = True, norm = 'bnorm', relu = 'relu'):\n            layers = []\n\n            layers += [nn.ReflectionPad2d(padding)]\n                         \n            layers += [nn.Conv2d(in_channels = in_ch,\n                                 out_channels = out_ch,\n                                 kernel_size = kernel_size,\n                                 stride = stride,\n                                 padding = 0,\n                                 bias = bias)]\n            if not norm is None:\n                if norm == 'bnorm':\n                    layers += [nn.BatchNorm2d(num_features = out_ch)]\n                elif norm =='inorm':\n                    layers += [nn.InstanceNorm2d(num_features = out_ch)]\n            \n            if relu == 'relu':\n                layers += [nn.ReLU()]\n            elif relu == 'leakyrelu':\n                layers += [nn.LeakyReLU(0.2)]\n                \n            return nn.Sequential(*layers)\n        \n        self.decoder1 = CLayer(in_ch = 3, out_ch = 64, kernel_size = 4, stride = 2, padding = 1, bias = False, norm = 'bnorm', relu = 'leakyrelu')\n        self.decoder2 = CLayer(in_ch = 64, out_ch = 128, kernel_size = 4, stride = 2, padding = 1, bias = False, norm = 'bnorm', relu = 'leakyrelu')\n        self.decoder3 = CLayer(in_ch = 128, out_ch = 256, kernel_size = 4, stride = 2, padding = 1, bias = False, norm = 'bnorm', relu = 'leakyrelu')\n        self.decoder4 = CLayer(in_ch = 256, out_ch = 512, kernel_size = 4, stride = 1, padding = 1, bias = False, norm = 'bnorm', relu = 'leakyrelu')\n        self.decoder5 = CLayer(in_ch = 512, out_ch = 1, kernel_size = 4, stride = 1, padding = 1, bias = False, norm = None, relu = None)\n        \n    def forward(self, input):\n        \n        x = self.decoder1(input)\n        x = self.decoder2(x)\n        x = self.decoder3(x)\n        x = self.decoder4(x)\n        x = self.decoder5(x)\n        \n        output = torch.sigmoid(x)\n        \n        return output","metadata":{"execution":{"iopub.status.busy":"2021-12-25T10:00:57.525855Z","iopub.execute_input":"2021-12-25T10:00:57.526295Z","iopub.status.idle":"2021-12-25T10:00:57.542237Z","shell.execute_reply.started":"2021-12-25T10:00:57.526264Z","shell.execute_reply":"2021-12-25T10:00:57.541437Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Training\n#configure\nlr = 0.001\nn_epoch = 10\nbatch_size = 2\nsetting_patience = 7\n\n\n\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\n#Defineing Model\nGM2P = Generator().to(device) #Monet ---> Photo\nGP2M = Generator().to(device) #Photo ---> Monet\nDM2P = Discriminator().to(device) #Monet ---> Photo\nDP2M = Discriminator().to(device) #Photo ---> Monet\n\n#Weight Initialize\nweight_init(GM2P)\nweight_init(GP2M)\nweight_init(DM2P)\nweight_init(DP2M)\n\n#Loss Functions\n#1. GAN loss - L2\ngan_loss = nn.BCELoss().to(device)\n#2. Cycle loss - L1\ncyc_loss = nn.L1Loss().to(device)\n#3. Identity loss - L1\niden_loss = nn.L1Loss().to(device)\n\n#Optimizer (Connet Monet <---> Photo)\nOptimG = torch.optim.Adam(itertools.chain(GM2P.parameters(), GP2M.parameters()), lr = lr, betas=(0.5, 0.999))\nOptimD = torch.optim.Adam(itertools.chain(DM2P.parameters(), DP2M.parameters()), lr = lr, betas=(0.5, 0.999))\n\nschedulerG = lr_scheduler.LambdaLR(optimizer=OptimG,\n                                        lr_lambda=lambda epoch: 0.95 ** epoch,\n                                        last_epoch=-1,\n                                        verbose=False)\nschedulerD = lr_scheduler.LambdaLR(optimizer=OptimD,\n                                        lr_lambda=lambda epoch: 0.95 ** epoch,\n                                        last_epoch=-1,\n                                        verbose=False)\n\n#Get Data from Dataloader\ntrain_dataset = CustomDataset(path, monet, photo, transforms = train_transform, seed = 777)\ntrain_dataloader = DataLoader(train_dataset, batch_size = batch_size, shuffle  = True, num_workers = 2)\ntrain_total_batch = len(train_dataloader)\nloss_G_list = []\nloss_D_list = []","metadata":{"execution":{"iopub.status.busy":"2021-12-25T10:00:57.544038Z","iopub.execute_input":"2021-12-25T10:00:57.545172Z","iopub.status.idle":"2021-12-25T10:00:57.762856Z","shell.execute_reply.started":"2021-12-25T10:00:57.545122Z","shell.execute_reply":"2021-12-25T10:00:57.761961Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Training & Evaluation\n\nbest_G_loss = 100\nbest_D_loss = 100\n\ntotal_patience = 0\nfor epoch in range(n_epoch):\n    \n    \n    GM2P.train()\n    GP2M.train()\n    DM2P.train()\n    DP2M.train()\n\n    loss_G_avg = 0.0\n    loss_D_avg = 0.0\n    \n    with tqdm(train_dataloader, unit = 'batch') as train_bar:\n        \n        for monets, photos in train_bar:  \n            \n            torch.cuda.empty_cache()\n            monets = monets.float().to(device)\n            photos = photos.float().to(device)\n            \n            #forward Generator\n            update_req_grad([DM2P, DP2M], False)\n            OptimG.zero_grad()\n            \n            fake_photo = GM2P(monets)\n            fake_monet = GP2M(photos)\n\n            cycl_monet = GP2M(fake_photo)\n            cycl_photo = GM2P(fake_monet)\n            \n            ident_monet = GP2M(monets)\n            ident_photo = GM2P(photos)\n            \n            \n            #Caculating loss (Identity, Advrsarial, cycle consistency)\n            #identity loss\n            ident_loss_monet = iden_loss(ident_monet, monets) * 10 * 0.5\n            ident_loss_photo = iden_loss(ident_photo, photos) * 10 * 0.5\n            #Cycle loss\n            cycle_loss_monet = cyc_loss(cycl_monet, monets) * 10\n            cycle_loss_photo = cyc_loss(cycl_photo, photos) * 10\n            #Adversarial loss\n            pred_fake_monet = DM2P(fake_monet.detach())\n            pred_fake_photo = DP2M(fake_photo.detach())    \n            \n            adv_loss_monet = gan_loss(pred_fake_monet, torch.ones_like(pred_fake_monet))\n            adv_loss_photo = gan_loss(pred_fake_photo, torch.ones_like(pred_fake_photo))\n            \n            #Generater Loss\n            loss_G = (ident_loss_monet + ident_loss_photo ) + (cycle_loss_monet + cycle_loss_photo) + (adv_loss_monet + adv_loss_photo)\n            loss_G_avg += loss_G.item() / train_total_batch\n            #Generator Backward\n    \n            loss_G.backward(retain_graph=True)\n            OptimG.step()\n            \n            #forward Discriminator\n            update_req_grad([DM2P, DP2M], True)\n            OptimD.zero_grad()\n            \n            pred_real_monet = DP2M(photos)\n            pred_real_photo = DM2P(monets)\n            \n            #Discriminator loss\n            loss_D_monet_real = gan_loss(pred_real_monet, torch.ones_like(pred_real_monet))\n            loss_D_monet_fake = gan_loss(pred_fake_monet, torch.zeros_like(pred_fake_monet))\n            loss_D_photo_real = gan_loss(pred_real_photo, torch.ones_like(pred_real_photo))\n            loss_D_photo_fake = gan_loss(pred_fake_photo, torch.zeros_like(pred_fake_photo))\n            \n            \n            monet_D_loss = (loss_D_monet_real + loss_D_monet_fake) / 2\n            photo_D_loss = (loss_D_photo_real + loss_D_photo_fake) / 2\n            \n            loss_D = monet_D_loss + photo_D_loss\n            loss_D_avg += loss_D.item() / train_total_batch\n            \n                      \n            #backward\n            loss_D.backward()\n            OptimD.step()\n\n            train_bar.set_postfix(epoch = epoch+1, loss_G = loss_G.item(),loss_D = loss_D.item())\n    \n    schedulerD.step()\n    schedulerG.step()\n          \n    wandb.log({'Epoch' : epoch+1, \"Generator loss\": loss_G_avg, \n               \"Discriminator loss\": loss_D_avg, \n               'Generator Learning rate' : OptimG.param_groups[0]['lr'], \n               'Discriminator Learning rate' : OptimD.param_groups[0]['lr']})\n    \n    if ((epoch+1) == 1) | ((epoch+1) % 1 == 0):\n        \n        with torch.no_grad():\n            fake_photos = GM2P(monets)\n            fake_monets = GP2M(photos)\n\n            monets_ = monets[0,:,:,:]\n            fake_photos = fake_photos[0,:,:,:]\n            photos_ = photos[0,:,:,:]\n            fake_monets = fake_monets[0,:,:,:]\n            visulization(photos_, fake_monets)\n        \n\n        \n        print('🔨Model Save🔨')\n        torch.save(GM2P.state_dict(), './GM2P.pt')\n        torch.save(GP2M.state_dict(), './GP2M.pt')\n        torch.save(DM2P.state_dict(), './DM2P.pt')\n        torch.save(DP2M.state_dict(), './DP2M.pt')\n        \nwandb.finish()","metadata":{"execution":{"iopub.status.busy":"2021-12-25T10:00:57.765455Z","iopub.execute_input":"2021-12-25T10:00:57.765698Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Did it work?\nThe dataset contains four directories: monet_tfrec, photo_tfrec, monet_jpg, and photo_jpg. The monet_tfrec and monet_jpg directories contain the same painting images, and the photo_tfrec and photo_jpg directories contain the same photos.\nThe monet directories contain Monet paintings. Use these images to train your model.\n\nThe photo directories contain photos. Add Monet-style to these images and submit your generated jpeg images as a zip file. Other photos outside of this dataset can be transformed but keep your submission file limited to 10,000 images.\n\n#### What did you not understand about this process?\nWell, everything provides in the competition data page. I've no problem while working on it. If you guys don't understand the thing that I'll do in this notebook then please comment on this notebook.\n\n#### What else do you think you can try as part of this approach?\nMonet-style art can be created from scratch using other GAN architectures like DCGAN.\nBecome more familiar with these concepts:\n\n- Computer vision\n- Generative models\n- Tensor Processing Units (TPUs)\n- TFRecords format for TensorFlow","metadata":{}}]}