{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":15062,"databundleVersionId":545987,"sourceType":"competition"}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os, zipfile, glob, random\nimport numpy as np\nfrom PIL import Image\nimport xml.etree.ElementTree as ET\nimport matplotlib.pyplot as plt\n\n# --- User settings (keep your flags) ---\nComputeLB = False\nDogsOnly = False\n\n# --- base root depending on context (Kaggle vs local) ---\nif ComputeLB:\n    ROOT = '/kaggle/input/generative-dog-images'\nelse:\n    ROOT = '/kaggle/input/generative-dog-images/'\n\nprint(\"Initial ROOT:\", ROOT)\n\n# --- helper: try to find an images folder ---\ndef find_images_dir(root):\n    # Common possibilities\n    candidates = [\n        os.path.join(root, 'all-dogs', 'all-dogs'),\n        os.path.join(root, 'all-dogs'),\n        os.path.join(root, 'all_dogs'),\n        root\n    ]\n    # Also search recursively for a folder with many .jpg files\n    for c in candidates:\n        if os.path.isdir(c):\n            jpgs = glob.glob(os.path.join(c, '*.jpg'))\n            if len(jpgs) > 50:\n                return c\n    # fallback: walk to find dir with many jpgs\n    for dirpath, dirnames, filenames in os.walk(root):\n        jpgs = [f for f in filenames if f.lower().endswith('.jpg')]\n        if len(jpgs) > 50:\n            return dirpath\n    return None\n\n# --- look for zipped dataset and extract if necessary ---\nall_dogs_zip_candidates = [\n    os.path.join(ROOT, 'all-dogs.zip'),\n    os.path.join(ROOT, 'All-Dogs.zip'),\n    os.path.join(ROOT, 'all-dogs_all-dogs.zip')\n]\nfor zpath in all_dogs_zip_candidates:\n    if os.path.isfile(zpath):\n        print(\"Found zip at:\", zpath, \" -> extracting to /tmp/all-dogs_extracted\")\n        extract_to = '/tmp/all-dogs_extracted'\n        os.makedirs(extract_to, exist_ok=True)\n        with zipfile.ZipFile(zpath, 'r') as z:\n            z.extractall(extract_to)\n        # reset ROOT to extracted folder for scanning\n        ROOT = extract_to + '/'\n        break\n\nIMG_DIR = find_images_dir(ROOT)\nif IMG_DIR is None:\n    raise FileNotFoundError(f\"Could not find images folder under {ROOT}. Try checking file tree or adjust ROOT.\")\nprint(\"Using image directory:\", IMG_DIR)\n\n# --- annotation directory (if DogsOnly) ---\nANN_DIR = None\nif DogsOnly:\n    # possible annotation path\n    cand = os.path.join(ROOT, 'annotation', 'Annotation')\n    if os.path.isdir(cand):\n        ANN_DIR = cand\n    else:\n        # try to find folder named 'Annotation' recursively\n        for dirpath, dirnames, filenames in os.walk(ROOT):\n            if 'Annotation' in dirnames:\n                ANN_DIR = os.path.join(dirpath, 'Annotation')\n                break\n    if ANN_DIR is None:\n        raise FileNotFoundError(f\"Could not find annotation folder under {ROOT}. Needed for DogsOnly=True.\")\n\nprint(\"Annotation directory:\", ANN_DIR)\n\n# --- gather image file list ---\nIMAGES = sorted(glob.glob(os.path.join(IMG_DIR, '*.jpg')))\nn_total = len(IMAGES)\nprint(\"Found\", n_total, \"jpg images.\")\n\n# --- parameters ---\nMAX_IMAGES = 10000  # you used 10000; change as needed\nsel_count = min(MAX_IMAGES, n_total)\nprint(\"Selecting\", sel_count, \"images.\")\n\n# choose a random subset like your original code\nrandom.seed(810)\nindices = random.sample(range(n_total), sel_count)\n\n# --- processing ---\nimages_list = []\nnames_list = []\n\nfor idx, i in enumerate(indices):\n    image_path = IMAGES[i]\n    try:\n        img = Image.open(image_path).convert('RGB')\n    except Exception as e:\n        print(\"skip (open error):\", image_path, e)\n        continue\n\n    if DogsOnly:\n        # expect annotation file path based on filename without extension\n        fname = os.path.splitext(os.path.basename(image_path))[0]\n        # search for annotation file - could be in breed subfolders\n        ann_file = None\n        for root_dir, dirs, files in os.walk(ANN_DIR):\n            # many annotation files end with the same base name (no extension)\n            if fname in files:\n                ann_file = os.path.join(root_dir, fname)\n                break\n        if ann_file is None:\n            # skip images without annotation\n            continue\n        try:\n            tree = ET.parse(ann_file)\n            r = tree.getroot()\n            objects = r.findall('object')\n            for o in objects:\n                bndbox = o.find('bndbox')\n                xmin = int(float(bndbox.find('xmin').text))\n                ymin = int(float(bndbox.find('ymin').text))\n                xmax = int(float(bndbox.find('xmax').text))\n                ymax = int(float(bndbox.find('ymax').text))\n                w = min((xmax - xmin, ymax - ymin))\n                img2 = img.crop((xmin, ymin, xmin + w, ymin + w))\n                img2 = img2.resize((64, 64), Image.LANCZOS)\n                images_list.append(np.asarray(img2, dtype=np.uint8))\n                # breed could be derived from ann file path (parent folder)\n                breed = os.path.basename(os.path.dirname(ann_file))\n                names_list.append(breed)\n        except Exception as e:\n            print(\"skip (annotation parse error):\", ann_file, e)\n            continue\n    else:\n        # random crop/resizing logic similar to your original\n        w, h = img.size\n        if (idx % 2 == 0) or (idx % 3 == 0):\n            w2 = 100\n            h2 = int(h / (w / 100)) if w != 0 else h\n            a = 18; b = 0\n        else:\n            a = 0; b = 0\n            if w < h:\n                w2 = 64\n                h2 = int((64 / w) * h) if w != 0 else h\n                b = (h2 - 64) // 2\n            else:\n                h2 = 64\n                w2 = int((64 / h) * w) if h != 0 else w\n                a = (w2 - 64) // 2\n        try:\n            img2 = img.resize((w2, h2), Image.LANCZOS)\n            img2 = img2.crop((0 + a, 0 + b, 64 + a, 64 + b))\n            images_list.append(np.asarray(img2, dtype=np.uint8))\n            names_list.append(os.path.basename(image_path))\n        except Exception as e:\n            print(\"skip (resize/crop error):\", image_path, e)\n            continue\n\nprint(\"Collected\", len(images_list), \"processed images.\")\n\n# convert to numpy array\nif len(images_list) == 0:\n    raise RuntimeError(\"No processed images collected. Check DogsOnly flag and annotation availability.\")\nimagesIn = np.stack(images_list, axis=0)  # shape (N,64,64,3)\nnamesIn = np.array(names_list)\n\n# --- display random samples ---\nimport matplotlib.pyplot as plt\nrnd = np.random.randint(0, len(imagesIn), size=min(25, len(imagesIn)))\nfor k in range(5):\n    plt.figure(figsize=(15,3))\n    for j in range(5):\n        idx_plot = rnd[k*5 + j] if (k*5 + j) < len(rnd) else rnd[0]\n        plt.subplot(1,5,j+1)\n        plt.axis('off')\n        if not DogsOnly:\n            plt.title(namesIn[idx_plot], fontsize=9)\n        else:\n            # if breed names have format 'n02085620-Chihuahua' this keeps after '-'\n            nm = namesIn[idx_plot]\n            if '-' in nm:\n                try:\n                    plt.title(nm.split('-')[1], fontsize=9)\n                except:\n                    plt.title(nm, fontsize=9)\n            else:\n                plt.title(nm, fontsize=9)\n        plt.imshow(imagesIn[idx_plot])\n    plt.show()\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-06T13:11:25.051575Z","iopub.execute_input":"2025-11-06T13:11:25.051917Z","iopub.status.idle":"2025-11-06T13:12:14.774076Z","shell.execute_reply.started":"2025-11-06T13:11:25.051895Z","shell.execute_reply":"2025-11-06T13:12:14.773311Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import gzip, pickle\nimport os\nimport numpy as np\nimport pandas as pd\nimport random\nimport shutil\nimport numpy as np\nimport os\n\nimport torch\nfrom torch import nn, optim\nimport torch.nn.functional as F\nfrom torchvision import datasets, transforms, utils\nfrom torchvision.utils import save_image\nfrom torch.utils.data import Dataset, DataLoader, TensorDataset\n\n\nfrom scipy import linalg\nimport pathlib\nimport urllib\nimport warnings\nfrom PIL import Image\nfrom tqdm import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-06T13:12:14.775257Z","iopub.execute_input":"2025-11-06T13:12:14.775475Z","iopub.status.idle":"2025-11-06T13:12:20.920378Z","shell.execute_reply.started":"2025-11-06T13:12:14.775459Z","shell.execute_reply":"2025-11-06T13:12:20.919679Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Hyperparams ---\nbatch_size = 512\nimage_size = 64\nnc = 3           # num channels\nnz = 100         # latent dim\nngf = 256        # used in generator fc -> 256*4*4 (matches your Generator)\nndf = 64         # feature size for discriminator convs (you used 64 first)\nlr = 2e-4\nbeta1 = 0.5\nnum_epochs = 500\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(\"Device:\", device)\n\ntry:\n    #imagesIn  # if exists, use it\n    #assert isinstance(imagesIn, np.ndarray)\n    # normalize to [0,1] then to [-1,1]\n    x = imagesIn.astype(np.float32) / 255.0\n    # Permute channels to (N, C, H, W)\n    x = np.transpose(x, (0, 3, 1, 2))\n    # Convert to tensor and normalize\n    tx = torch.from_numpy(x)\n    # apply normalization to [-1,1]\n    tx = (tx - 0.5) / 0.5\n    dataset = TensorDataset(tx)\n    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=4, pin_memory=True)\n    print(\"Using imagesIn TensorDataset with\", len(dataset), \"samples.\")\nexcept Exception:\n    # -------------------------\n    # OPTION B: Load from disk using ImageFolder\n    # -------------------------\n    data_root = '/kaggle/input/generative-dog-images'  # <-- update to folder that contains images (or attach dataset)\n    tfms = transforms.Compose([\n        transforms.Resize(image_size),\n        transforms.CenterCrop(image_size),\n        transforms.ToTensor(),\n        transforms.Normalize([0.5]*3, [0.5]*3)   # map to [-1,1]\n    ])\n    dataset = datasets.ImageFolder(data_root, transform=tfms)\n    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=1, pin_memory=True)\n    print(\"Using ImageFolder at\", data_root, \"with\", len(dataset), \"samples.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-06T13:12:20.920983Z","iopub.execute_input":"2025-11-06T13:12:20.921376Z","iopub.status.idle":"2025-11-06T13:12:21.711241Z","shell.execute_reply.started":"2025-11-06T13:12:20.921355Z","shell.execute_reply":"2025-11-06T13:12:21.710575Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n# bs = 256\n\n# train_y = (imagesIn[:10000,:,:,:]/255.).reshape((-1,12288))\n# train_X = np.zeros((10000,10000))\n# for i in range(10000): train_X[i,i] = 1\n# zeros = np.zeros((10000,12288))\n\n# data_set = dog_dataset(train_y, train_X, zeros, device)    \n# train_loader = DataLoader(data_set, bs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-06T13:12:21.713005Z","iopub.execute_input":"2025-11-06T13:12:21.713217Z","iopub.status.idle":"2025-11-06T13:12:21.716508Z","shell.execute_reply.started":"2025-11-06T13:12:21.713201Z","shell.execute_reply":"2025-11-06T13:12:21.715939Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Discriminator(nn.Module):\n    def __init__(self):\n        super(Discriminator, self).__init__()\n        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1)\n        self.leaky_relu1 = nn.LeakyReLU(0.2)\n        self.conv2 = nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1)\n        self.leaky_relu2 = nn.LeakyReLU(0.2)\n        self.conv3 = nn.Conv2d(128, 128, kernel_size=3, stride=2, padding=1)\n        self.leaky_relu3 = nn.LeakyReLU(0.2)\n        self.conv4 = nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1)\n        self.leaky_relu4 = nn.LeakyReLU(0.2)\n\n        # force the spatial dims to (4,4) regardless of previous size\n        self.pool = nn.AdaptiveAvgPool2d((4, 4))\n\n        self.flatten = nn.Flatten()\n        self.dropout = nn.Dropout(0.4)\n        self.fc = nn.Linear(256 * 4 * 4, 1)\n        self.sigmoid = nn.Sigmoid()\n\n    def forward(self, x):\n        x = self.leaky_relu1(self.conv1(x))\n        x = self.leaky_relu2(self.conv2(x))\n        x = self.leaky_relu3(self.conv3(x))\n        x = self.leaky_relu4(self.conv4(x))\n        x = self.pool(x)                # <-- ensures fixed (4,4)\n        x = self.flatten(x)\n        x = self.dropout(x)\n        x = self.fc(x)\n        x = self.sigmoid(x)\n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-06T13:12:21.717226Z","iopub.execute_input":"2025-11-06T13:12:21.717423Z","iopub.status.idle":"2025-11-06T13:12:21.743101Z","shell.execute_reply.started":"2025-11-06T13:12:21.717408Z","shell.execute_reply":"2025-11-06T13:12:21.742506Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Generator(nn.Module):\n    def __init__(self):\n        super(Generator, self).__init__()\n        self.fc = nn.Linear(100, 256 * 4 * 4)\n        self.relu1 = nn.ReLU(True)\n        self.unflatten = nn.Unflatten(1, (256, 4, 4))\n        self.deconv1 = nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1)\n        self.relu2 = nn.ReLU(True)\n        self.deconv2 = nn.ConvTranspose2d(128, 128, kernel_size=4, stride=2, padding=1)\n        self.relu3 = nn.ReLU(True)\n        self.deconv3 = nn.ConvTranspose2d(128, 3, kernel_size=4, stride=2, padding=1)\n        self.tanh = nn.Tanh()\n\n    def forward(self, x):\n        x = self.fc(x)\n        x = self.relu1(x)\n        x = self.unflatten(x)\n        x = self.deconv1(x)\n        x = self.relu2(x)\n        x = self.deconv2(x)\n        x = self.relu3(x)\n        x = self.deconv3(x)\n        x = self.tanh(x)\n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-06T13:12:21.743933Z","iopub.execute_input":"2025-11-06T13:12:21.744104Z","iopub.status.idle":"2025-11-06T13:12:21.762792Z","shell.execute_reply.started":"2025-11-06T13:12:21.744084Z","shell.execute_reply":"2025-11-06T13:12:21.762045Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# D = Discriminator()\n# G = Generator()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-06T13:12:21.763649Z","iopub.execute_input":"2025-11-06T13:12:21.764157Z","iopub.status.idle":"2025-11-06T13:12:21.807474Z","shell.execute_reply.started":"2025-11-06T13:12:21.764135Z","shell.execute_reply":"2025-11-06T13:12:21.806971Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# criterion = nn.BCELoss()\n# d_optimizer = optim.Adam(discriminator.parameters(), lr=0.0002, betas=(0.5, 0.999))\n# g_optimizer = optim.Adam(generator.parameters(), lr=0.0002, betas=(0.5, 0.999))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-06T13:12:21.808084Z","iopub.execute_input":"2025-11-06T13:12:21.808335Z","iopub.status.idle":"2025-11-06T13:12:21.811783Z","shell.execute_reply.started":"2025-11-06T13:12:21.808309Z","shell.execute_reply":"2025-11-06T13:12:21.811078Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show_image():\n    plt.figure(figsize=(15,3))\n    for j in range(5):\n        with torch.no_grad():\n            xx = np.zeros((10000))\n            xx[np.random.randint(10000)] = 1\n            plt.subplot(1,5,j+1)\n            inp = torch.Tensor(xx.reshape((-1,10000))).to(device)\n            img = netG(inp)[0].reshape((-1,64,64,3)).to(\"cpu\").clone().detach().numpy()\n            img = Image.fromarray( (img).astype('uint8').reshape((64,64,3)))\n            plt.axis('off')\n            plt.imshow(img)\n    plt.show()  \n    \ndef show_d_images(imgs, title):\n    with torch.no_grad():\n        imgs = imgs.reshape((-1,64,64,3))\n        plt.figure(figsize=(15,3))\n        for j in range(4):\n            plt.subplot(1,5,j+1)\n            img = imgs[j].detach().cpu().numpy()\n            img = Image.fromarray((255*img).astype('uint8').reshape((64,64,3)))\n            plt.title(title)\n            plt.imshow(img)\n        plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-06T13:12:21.812425Z","iopub.execute_input":"2025-11-06T13:12:21.812677Z","iopub.status.idle":"2025-11-06T13:12:21.829646Z","shell.execute_reply.started":"2025-11-06T13:12:21.812663Z","shell.execute_reply":"2025-11-06T13:12:21.828901Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Assumes previous code defined: G, D, dataloader, criterion, optimizerG, optimizerD, device, nz, fixed_noise, num_epochs, utils, plt ---\nimport torchvision.utils as vutils\n\n\nG = Generator().to(device)\nD = Discriminator().to(device)\n\n# Initialize weights (recommended for GANs)\ndef weights_init(m):\n    if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d, nn.Linear)):\n        nn.init.normal_(m.weight.data, 0.0, 0.02)\n        if m.bias is not None:\n            nn.init.constant_(m.bias.data, 0)\n    elif isinstance(m, nn.BatchNorm2d):\n        nn.init.normal_(m.weight.data, 1.0, 0.02)\n        nn.init.constant_(m.bias.data, 0)\n\nG.apply(weights_init)\nD.apply(weights_init)\n\n# Loss and optimizers\ncriterion = nn.BCELoss()\noptimizerD = optim.Adam(D.parameters(), lr=lr, betas=(beta1, 0.999))\noptimizerG = optim.Adam(G.parameters(), lr=lr, betas=(beta1, 0.999))\n\n# Fixed noise for visualizing progress\nfixed_noise = torch.randn(64, nz, device=device)\n\n# Labels\nreal_label = 1.0\nfake_label = 0.0\n\nos.makedirs('samples', exist_ok=True)\n\nbest_errG = float('inf')\nbest_epoch = -1\nbest_path = 'best_generator.pth'\n\nfor epoch in range(1, num_epochs + 1):\n    running_errD = 0.0\n    running_errG = 0.0\n    for i, data in enumerate(dataloader, 0):\n        real_images = data[0].to(device)\n        b_size = real_images.size(0)\n\n        # ---------------------------\n        # (1) Update D\n        # ---------------------------\n        D.zero_grad()\n        # real\n        label = torch.full((b_size,), 1.0, device=device)\n        output = D(real_images).view(-1)\n        errD_real = criterion(output, label)\n        errD_real.backward()\n        D_x = output.mean().item()\n\n        # fake\n        noise = torch.randn(b_size, nz, device=device)\n        fake = G(noise)\n        label.fill_(0.0)\n        output = D(fake.detach()).view(-1)\n        errD_fake = criterion(output, label)\n        errD_fake.backward()\n\n        errD = errD_real + errD_fake\n        optimizerD.step()\n\n        # ---------------------------\n        # (2) Update G\n        # ---------------------------\n        G.zero_grad()\n        label.fill_(1.0)\n        output = D(fake).view(-1)\n        errG = criterion(output, label)\n        errG.backward()\n        optimizerG.step()\n\n        running_errD += errD.item()\n        running_errG += errG.item()\n\n        if i % 20 == 0:\n            print(f\"Epoch [{epoch}/{num_epochs}] Step [{i}/{len(dataloader)}] \"\n                  f\"Loss_D: {errD.item():.6f} Loss_G: {errG.item():.6f} D(x): {D_x:.6f}\")\n\n    # epoch averages\n    avg_errD = running_errD / len(dataloader)\n    avg_errG = running_errG / len(dataloader)\n    \n    if epoch % 20 == 0: \n        print(f\"--- Epoch {epoch} summary: avg Loss_D={avg_errD:.6f}, avg Loss_G={avg_errG:.6f}\")\n        show_d_images(fake, \"Generator output\") \n        \n    # Save sample grid each epoch (optional, keeps visual progress)\n    with torch.no_grad():\n        fake_images = G(fixed_noise).detach().cpu()\n    grid = vutils.make_grid((fake_images * 0.5 + 0.5), nrow=8, padding=2)\n    \n    vutils.save_image(grid, f\"samples/epoch_{epoch:03d}.png\")\n\n    # --- Save only the best Generator (based on average Generator loss) ---\n    # Lower errG is considered better here\n    if avg_errG < best_errG:\n        best_errG = avg_errG\n        best_epoch = epoch\n        # Save only G weights and metadata (small)\n        torch.save({\n            'epoch': epoch,\n            'best_errG': best_errG,\n            'G_state_dict': G.state_dict()\n        }, best_path)\n        print(f\"Saved new best generator at epoch {epoch} with avg_errG={best_errG:.6f} -> {best_path}\")\n\nprint(\"Training finished.\")\nprint(f\"Best Generator found at epoch {best_epoch} with avg_errG={best_errG:.6f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-06T13:12:21.831544Z","iopub.execute_input":"2025-11-06T13:12:21.831732Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Load best generator and display samples ---\nckpt = torch.load('best_generator.pth', map_location=device)\nG_best = Generator().to(device)\nG_best.load_state_dict(ckpt['G_state_dict'])\nG_best.eval()\n\n# Generate from fixed noise (the same you used during training)\nwith torch.no_grad():\n    fake_images = G_best(fixed_noise.to(device)).cpu()\n\n# Grid for display (map to [0,1])\ngrid = vutils.make_grid((fake_images * 0.5 + 0.5), nrow=8, padding=2)\nnp_grid = grid.permute(1, 2, 0).numpy()\n\nplt.figure(figsize=(8,8))\nplt.imshow(np_grid)\nplt.axis('off')\nplt.title(f'Best Generator: epoch {ckpt.get(\"epoch\", \"?\")}, errG={ckpt.get(\"best_errG\", \"?\"):.6f}')\nplt.show()\n\n# Save preview file\nvutils.save_image(grid, 'best_generator_preview.png')\nprint(\"Saved preview: best_generator_preview.png\")\n\n# Also show one individual random sample (single image)\nrand_z = torch.randn(1, nz, device=device)\nwith torch.no_grad():\n    single_fake = G_best(rand_z).cpu()\n\nsingle_img = single_fake.squeeze(0)           # (C,H,W)\nsingle_img = (single_img * 0.5 + 0.5).permute(1,2,0).numpy().clip(0,1)\n\nplt.figure(figsize=(4,4))\nplt.imshow(single_img)\nplt.axis('off')\nplt.title('Single random sample from best generator')\nplt.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}