{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.10.14"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":3649,"databundleVersionId":46718,"sourceType":"competition"},{"sourceId":13254116,"sourceType":"datasetVersion","datasetId":8398845}],"dockerImageVersionId":30776,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# -----------------------------\n# 1️⃣ Imports\n# -----------------------------\nimport os\nimport glob\nimport random\nimport shutil\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import DataLoader, WeightedRandomSampler\nfrom torchvision import transforms, models, datasets\nfrom PIL import Image\nimport numpy as np\n\n# -----------------------------\n# 2️⃣ Device\n# -----------------------------\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", DEVICE)\n\n# -----------------------------\n# 3️⃣ Paths\n# -----------------------------\nRAW_DIR = \"/kaggle/input/dataset-nlp/dataset_raw\"\nSPLIT_DIR = \"/kaggle/working/dataset_split\"\nos.makedirs(SPLIT_DIR, exist_ok=True)\n\n# -----------------------------\n# 4️⃣ Dataset Split\n# -----------------------------\ndef split_dataset(raw_dir, split_dir, test_ratio=0.2):\n    classes = [d for d in os.listdir(raw_dir) if os.path.isdir(os.path.join(raw_dir, d))]\n    for cls in classes:\n        cls_path = os.path.join(raw_dir, cls)\n        images = glob.glob(os.path.join(cls_path, \"*.bmp\"))\n        random.shuffle(images)\n        split_idx = int(len(images) * (1 - test_ratio))\n\n        train_images = images[:split_idx]\n        test_images  = images[split_idx:]\n\n        train_cls_dir = os.path.join(split_dir, \"train\", cls)\n        test_cls_dir  = os.path.join(split_dir, \"test\", cls)\n        os.makedirs(train_cls_dir, exist_ok=True)\n        os.makedirs(test_cls_dir, exist_ok=True)\n\n        for img in train_images:\n            shutil.copy(img, train_cls_dir)\n        for img in test_images:\n            shutil.copy(img, test_cls_dir)\n\nsplit_dataset(RAW_DIR, SPLIT_DIR)\nprint(\"Dataset split complete!\")\n\n# -----------------------------\n# 5️⃣ Data Augmentation\n# -----------------------------\ntrain_transform = transforms.Compose([\n    transforms.RandomResizedCrop(224),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(15),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\nval_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\n# -----------------------------\n# 6️⃣ Load datasets\n# -----------------------------\ntrain_dataset = datasets.ImageFolder(os.path.join(SPLIT_DIR, \"train\"), transform=train_transform)\nval_dataset   = datasets.ImageFolder(os.path.join(SPLIT_DIR, \"test\"), transform=val_transform)\n\n# -----------------------------\n# 7️⃣ Weighted Sampler\n# -----------------------------\nlabels = [label for _, label in train_dataset.samples]\nclass_sample_count = np.array([labels.count(i) for i in range(len(train_dataset.classes))])\nweights = 1. / class_sample_count\nsamples_weights = np.array([weights[t] for t in labels])\nsamples_weights = torch.from_numpy(samples_weights).float()\nsampler = WeightedRandomSampler(samples_weights, len(samples_weights))\n\n# -----------------------------\n# 8️⃣ DataLoaders\n# -----------------------------\ntrain_loader = DataLoader(train_dataset, batch_size=16, sampler=sampler)\nval_loader   = DataLoader(val_dataset, batch_size=16, shuffle=False)\n\n# -----------------------------\n# 9️⃣ Model (ResNet18)\n# -----------------------------\nmodel = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)\nnum_ftrs = model.fc.in_features\nmodel.fc = nn.Linear(num_ftrs, len(train_dataset.classes))\nmodel = model.to(DEVICE)\n\n# -----------------------------\n# 🔟 Loss & Optimizer\n# -----------------------------\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-4)\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=5, verbose=True)\n\n# -----------------------------\n# 1️⃣1️⃣ Training with early stopping & best model saving\n# -----------------------------\nbest_val_acc = 0.0\npatience = 10\ntrigger_times = 0\nbest_model_path = \"best_model.pth\"\n\nfor epoch in range(1, 101):\n    model.train()\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for images, labels in train_loader:\n        images, labels = images.to(DEVICE), labels.to(DEVICE)\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item() * images.size(0)\n        _, predicted = torch.max(outputs, 1)\n        total += labels.size(0)\n        correct += (predicted == labels).sum().item()\n\n    train_loss = running_loss / total\n    train_acc = correct / total\n\n    # Validation\n    model.eval()\n    correct_val = 0\n    total_val = 0\n    with torch.no_grad():\n        for images, labels in val_loader:\n            images, labels = images.to(DEVICE), labels.to(DEVICE)\n            outputs = model(images)\n            _, predicted = torch.max(outputs, 1)\n            total_val += labels.size(0)\n            correct_val += (predicted == labels).sum().item()\n\n    val_acc = correct_val / total_val\n    scheduler.step(val_acc)\n\n    print(f\"Epoch {epoch}/100: train_loss={train_loss:.4f}, train_acc={train_acc:.4f}, val_acc={val_acc:.4f}\")\n\n    # Early stopping\n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        torch.save(model.state_dict(), best_model_path)\n        trigger_times = 0\n    else:\n        trigger_times += 1\n        if trigger_times >= patience:\n            print(\"Early stopping!\")\n            break\n\nprint(\"Training complete! Best val_acc:\", best_val_acc)\nprint(\"Best model saved at:\", best_model_path)\n\n# -----------------------------\n# 1️⃣2️⃣ Inference function\n# -----------------------------\ndef predict_image(image_path, model_path=\"best_model.pth\"):\n    model.load_state_dict(torch.load(model_path, map_location=DEVICE))\n    model.eval()\n    img = Image.open(image_path).convert(\"RGB\")\n    transform = transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406],\n                             [0.229, 0.224, 0.225])\n    ])\n    img = transform(img).unsqueeze(0).to(DEVICE)\n    with torch.no_grad():\n        output = model(img)\n        _, pred = torch.max(output, 1)\n    return train_dataset.classes[pred.item()]\n\n# Example usage:\n# result = predict_image(\"/kaggle/working/dataset_split/test/ClassName/sample.bmp\")\n# print(\"Predicted class:\", result)\n","metadata":{"execution":{"iopub.status.busy":"2025-10-03T21:27:54.936197Z","iopub.execute_input":"2025-10-03T21:27:54.936675Z","iopub.status.idle":"2025-10-03T21:28:36.81576Z","shell.execute_reply.started":"2025-10-03T21:27:54.936643Z","shell.execute_reply":"2025-10-03T21:28:36.814798Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -----------------------------\n# 1️⃣ Imports\n# -----------------------------\nimport torch\nfrom torchvision import transforms, models, datasets\nfrom PIL import Image\n\n# -----------------------------\n# 2️⃣ Device\n# -----------------------------\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", DEVICE)\n\n# -----------------------------\n# 3️⃣ Load dataset classes (from train folder)\n# -----------------------------\nfrom torchvision.datasets import ImageFolder\n\nTRAIN_DIR = \"/kaggle/working/dataset_split/train\"\ntrain_dataset = ImageFolder(TRAIN_DIR)  # Only to get class names\n\n# -----------------------------\n# 4️⃣ Load model\n# -----------------------------\nmodel = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)\nnum_ftrs = model.fc.in_features\nmodel.fc = torch.nn.Linear(num_ftrs, len(train_dataset.classes))\nmodel.load_state_dict(torch.load(\"best_model.pth\", map_location=DEVICE))\nmodel = model.to(DEVICE)\nmodel.eval()\n\n# -----------------------------\n# 5️⃣ Inference function\n# -----------------------------\ndef predict_image(image_path):\n    img = Image.open(image_path).convert(\"RGB\")\n    transform = transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406],\n                             [0.229, 0.224, 0.225])\n    ])\n    img = transform(img).unsqueeze(0).to(DEVICE)\n\n    with torch.no_grad():\n        output = model(img)\n        _, pred = torch.max(output, 1)\n\n    return train_dataset.classes[pred.item()]\n\n# -----------------------------\n# 6️⃣ Example usage\n# -----------------------------\nimage_path = \"/kaggle/working/dataset_split/test/Z/fingerprint-10.bmp\"\nresult = predict_image(image_path)\nprint(\"Predicted class:\", result)\n","metadata":{"execution":{"iopub.status.busy":"2025-10-03T21:32:10.769756Z","iopub.execute_input":"2025-10-03T21:32:10.770603Z","iopub.status.idle":"2025-10-03T21:32:11.044869Z","shell.execute_reply.started":"2025-10-03T21:32:10.770568Z","shell.execute_reply":"2025-10-03T21:32:11.043907Z"},"trusted":true},"outputs":[],"execution_count":null}]}