{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":87323,"databundleVersionId":9934427,"sourceType":"competition"}],"dockerImageVersionId":30840,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"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":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-01-16T01:04:55.216218Z","iopub.execute_input":"2025-01-16T01:04:55.216512Z","iopub.status.idle":"2025-01-16T01:04:57.230433Z","shell.execute_reply.started":"2025-01-16T01:04:55.216482Z","shell.execute_reply":"2025-01-16T01:04:57.229527Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import warnings\nwarnings.simplefilter(action='ignore')\n\nimport os\nimport random\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torch.utils.data\nimport torchvision\nimport torchvision.transforms as transforms\nfrom torchvision.models import convnext_large, ConvNeXt_Large_Weights\nfrom PIL import Image\nfrom tqdm import tqdm\nfrom torch.optim.lr_scheduler import CosineAnnealingLR\nimport torch.backends.cudnn as cudnn\n\n# 시드 고정\nseed = 42\nprint(\"Random Seed: \", seed)\nrandom.seed(seed)\nnp.random.seed(seed)\ntorch.manual_seed(seed)\ntorch.cuda.manual_seed(seed)\ntorch.cuda.manual_seed_all(seed)\ncudnn.deterministic = True\ncudnn.benchmark = False\n\n# GPU 설정\nngpu = torch.cuda.device_count()\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Number of GPUs Available: \", ngpu)\nprint(\"Device being used: \", device)\n\n# 하이퍼파라미터 (필요에 따라 조정)\nbatch_size = 8 * ngpu  # 더 큰 배치 사이즈\nnum_epochs = 25  # 에폭 증가\nlearning_rate = 5e-4\nimage_size = 224\naccumulation_steps = 4  # Gradient Accumulation steps\nweight_decay = 1e-5 # weight decay 추가\nclip_grad_norm = 1 # Gradient Clipping 추가\n\n# 데이터셋 경로 (Kaggle Dataset 기준)\ntrain_dir = '/kaggle/input/cidaut-ai-fake-scene-classification-2024/Train'\ntest_dir = '/kaggle/input/cidaut-ai-fake-scene-classification-2024/Test'\ntrain_csv_path = '/kaggle/input/cidaut-ai-fake-scene-classification-2024/train.csv'\nsample_submission_path = '/kaggle/input/cidaut-ai-fake-scene-classification-2024/sample_submission.csv'\n\n# 데이터셋 transform\ntransform = transforms.Compose([\n    transforms.Resize((image_size, image_size)),\n    transforms.RandomRotation(15),\n    transforms.RandomHorizontalFlip(p=0.5),\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], [0.229, 0.224, 0.225])\n])\n\n# 데이터셋 로드\nclass CustomDataset(torch.utils.data.Dataset):\n    def __init__(self, data_dir, csv_path, transform=None):\n        self.data_dir = data_dir\n        self.data_info = pd.read_csv(csv_path)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.data_info)\n\n    def __getitem__(self, idx):\n        img_name = self.data_info.iloc[idx, 0]\n        img_path = os.path.join(self.data_dir, img_name)\n        image = Image.open(img_path).convert(\"RGB\")\n        label_str = self.data_info.iloc[idx, 1]\n        if label_str == 'editada':\n            label = 1  # Assuming 'editada' means Fake\n        elif label_str == 'real':\n            label = 0  # Assuming 'real' means Real\n        else:\n            raise ValueError(f\"Unknown label: {label_str}\")\n\n        if self.transform:\n            image = self.transform(image)\n        return image, torch.tensor(label, dtype=torch.float32) # Ensure label is a float tensor\n\ntrain_dataset = CustomDataset(train_dir, train_csv_path, transform=transform)\ntrain_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=4, pin_memory=True)\n\n# 테스트 데이터셋 로드 (라벨 없음)\nclass TestDataset(torch.utils.data.Dataset):\n    def __init__(self, data_dir, transform=None):\n        self.data_dir = data_dir\n        self.img_names = os.listdir(data_dir)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.img_names)\n\n    def __getitem__(self, idx):\n        img_name = self.img_names[idx]\n        img_path = os.path.join(self.data_dir, img_name)\n        image = Image.open(img_path).convert(\"RGB\")\n        if self.transform:\n            image = self.transform(image)\n        return img_name, image\n\ntest_dataset = TestDataset(test_dir, transform=transform)\ntest_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=4, pin_memory=True)\n\n# ConvNeXt 모델 정의 (이번에는 ConvNeXt-Large 전체 모델을 사용하고 마지막 레이어만 수정)\nclass ConvNextModel(nn.Module):\n    def __init__(self, num_classes=1):  # 이진 분류이므로 num_classes=1\n        super().__init__()\n        self.convnext = convnext_large(weights=ConvNeXt_Large_Weights.DEFAULT)\n        # Replace the classifier\n        self.convnext.classifier[-1] = nn.Linear(self.convnext.classifier[-1].in_features, num_classes)\n\n    def forward(self, x):\n        return self.convnext(x) # Forward pass\n\n# 모델 초기화 및 GPU 설정\nmodel = ConvNextModel().float()\nif ngpu > 1:\n    model = nn.DataParallel(model, device_ids=list(range(ngpu))).to(device)\nelse:\n    model.to(device)\n\n# 손실 함수 및 옵티마이저\ncriterion = nn.BCEWithLogitsLoss()\noptimizer = optim.AdamW(model.parameters(), lr=learning_rate, weight_decay=weight_decay)\n\n# Learning Rate Scheduler\nscheduler = CosineAnnealingLR(optimizer, T_max=num_epochs, eta_min=0)\n\n# 학습\nprint(\"Starting Training...\")\nfor epoch in range(num_epochs):\n    model.train()\n    running_loss = 0.0\n    optimizer.zero_grad()\n    for i, (inputs, labels) in enumerate(tqdm(train_loader, desc=f\"Epoch {epoch+1}/{num_epochs}\")):\n        inputs = inputs.to(device)\n        labels = labels.to(device).unsqueeze(1)\n\n        outputs = model(inputs)\n        loss = criterion(outputs, labels)\n\n        # Gradient Accumulation\n        loss = loss / accumulation_steps\n        loss.backward()\n        \n        # Gradient Clipping\n        torch.nn.utils.clip_grad_norm_(model.parameters(), clip_grad_norm)\n\n        if (i + 1) % accumulation_steps == 0 or (i + 1) == len(train_loader):\n            optimizer.step()\n            optimizer.zero_grad()\n\n        running_loss += loss.item() * accumulation_steps\n\n        if i % 50 == 0 and i > 0:\n            print(f\"  Batch {i+1}/{len(train_loader)}, Loss: {running_loss / (i+1):.4f}, LR: {optimizer.param_groups[0]['lr']:.6f}\")\n\n    epoch_loss = running_loss / len(train_loader)\n    print(f\"Epoch {epoch+1}/{num_epochs} - Average Loss: {epoch_loss:.4f}\")\n\n    # 에폭마다 스케줄러 업데이트\n    scheduler.step()\n\n    # 에폭마다 모델 저장 (Kaggle Notebook의 /kaggle/working/ 디렉토리에 저장)\n    torch.save(model.state_dict(), f\"/kaggle/working/convnext_large_epoch_{epoch+1}.pth\")\n\n# 제출 파일 로드\nfsc_submission = pd.read_csv(sample_submission_path, index_col=\"image\")\n\n# 추론\nmodel.eval()\npredictions = {}\nwith torch.no_grad():\n    for img_name, img in tqdm(test_loader, desc=\"Inference\"):\n        img = img.to(device)\n        outputs = model(img)\n        outputs = outputs.sigmoid().cpu()\n        for i, img_name in enumerate(img_name):\n            predictions[img_name] = outputs[i].item()  # 원본 레이블 사용\n\n# 결과 저장\nsubmission = pd.DataFrame(columns=['image', 'label'])\nsubmission['image'] = list(predictions.keys())\nsubmission['label'] = list(predictions.values())\nsubmission['label'] = 1 - submission['label'] # Label Inversion for submission\nsubmission.to_csv(f\"/kaggle/working/submission.csv\", index=False)\nprint(submission)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-16T06:12:40.395483Z","iopub.execute_input":"2025-01-16T06:12:40.395699Z","iopub.status.idle":"2025-01-16T06:32:23.025200Z","shell.execute_reply.started":"2025-01-16T06:12:40.395678Z","shell.execute_reply":"2025-01-16T06:32:23.024077Z"}},"outputs":[],"execution_count":null}]}