{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.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":14774,"databundleVersionId":875431,"sourceType":"competition"}],"dockerImageVersionId":31260,"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":"2026-01-24T14:20:38.954916Z","iopub.execute_input":"2026-01-24T14:20:38.955244Z","iopub.status.idle":"2026-01-24T14:20:46.318166Z","shell.execute_reply.started":"2026-01-24T14:20:38.955206Z","shell.execute_reply":"2026-01-24T14:20:46.317168Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"5+4","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T14:23:14.287496Z","iopub.execute_input":"2026-01-24T14:23:14.287722Z","iopub.status.idle":"2026-01-24T14:23:14.295915Z","shell.execute_reply.started":"2026-01-24T14:23:14.287701Z","shell.execute_reply":"2026-01-24T14:23:14.295152Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nimport timm\n\n# =========================\n# Paths\n# =========================\nBASE_PATH = \"/kaggle/input/aptos2019-blindness-detection\"\nTRAIN_IMG_PATH = os.path.join(BASE_PATH, \"train_images\")\n\n# =========================\n# SimCLR Augmentations\n# =========================\nclass SimCLRAugment:\n    def __init__(self, size=224):\n        self.transform = transforms.Compose([\n            transforms.RandomResizedCrop(size),\n            transforms.RandomHorizontalFlip(),\n            transforms.RandomApply(\n                [transforms.ColorJitter(0.4, 0.4, 0.4, 0.1)], p=0.8\n            ),\n            transforms.RandomGrayscale(p=0.2),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.5]*3, std=[0.5]*3)\n        ])\n\n    def __call__(self, x):\n        return self.transform(x), self.transform(x)\n\n# =========================\n# Unlabeled Dataset\n# =========================\nclass UnlabeledImageDataset(Dataset):\n    def __init__(self, img_dir, transform):\n        self.img_dir = img_dir\n        self.images = os.listdir(img_dir)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.images)\n\n    def __getitem__(self, idx):\n        img_path = os.path.join(self.img_dir, self.images[idx])\n        image = Image.open(img_path).convert(\"RGB\")\n        x1, x2 = self.transform(image)\n        return x1, x2\n\n# =========================\n# SimCLR Model\n# =========================\nclass SimCLR(nn.Module):\n    def __init__(self, encoder_name=\"vit_small_patch16_224\", proj_dim=128):\n        super().__init__()\n\n        self.encoder = timm.create_model(\n            encoder_name,\n            pretrained=True,      # ✔️ allowed\n            num_classes=0\n        )\n\n        feat_dim = self.encoder.num_features\n\n        self.projector = nn.Sequential(\n            nn.Linear(feat_dim, 512),\n            nn.ReLU(),\n            nn.Linear(512, proj_dim)\n        )\n\n    def forward(self, x):\n        h = self.encoder(x)\n        z = self.projector(h)\n        return z\n\n# =========================\n# NT-Xent Loss\n# =========================\ndef nt_xent_loss(z1, z2, temperature=0.1):\n    z1 = nn.functional.normalize(z1, dim=1)\n    z2 = nn.functional.normalize(z2, dim=1)\n\n    batch_size = z1.size(0)\n    z = torch.cat([z1, z2], dim=0)\n\n    similarity = torch.matmul(z, z.T) / temperature\n\n    labels = torch.arange(batch_size, device=z.device)\n    labels = torch.cat([labels + batch_size, labels])\n\n    mask = torch.eye(2 * batch_size, device=z.device).bool()\n    \n \n    similarity = similarity.float()\n    similarity.masked_fill_(mask, -9e15)\n\n    loss = nn.CrossEntropyLoss()(similarity, labels)\n    return loss\n# =========================\n# Device\n# =========================\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\n# =========================\n# DataLoader\n# =========================\ndataset = UnlabeledImageDataset(\n    TRAIN_IMG_PATH,\n    transform=SimCLRAugment()\n)\n\nloader = DataLoader(\n    dataset,\n    batch_size=32,          #  safe for GPU\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\n# =========================\n# Model & Optimizer\n# =========================\nmodel = SimCLR().to(device)\noptimizer = optim.Adam(model.parameters(), lr=3e-4)\n\n# Mixed Precision\nscaler = torch.cuda.amp.GradScaler()\n\n# =========================\n# Training\n# =========================\nepochs = 10\n\nfor epoch in range(epochs):\n    model.train()\n    total_loss = 0\n\n    for batch_idx, (x1, x2) in enumerate(loader):\n        x1 = x1.to(device)\n        x2 = x2.to(device)\n\n        optimizer.zero_grad()\n\n        with torch.cuda.amp.autocast():\n            z1 = model(x1)\n            z2 = model(x2)\n            loss = nt_xent_loss(z1, z2)\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        total_loss += loss.item()\n\n        if (batch_idx + 1) % max(1, len(loader) // 5) == 0:\n            progress = (batch_idx + 1) / len(loader) * 100\n            print(\n                f\"Epoch [{epoch+1}/{epochs}] \"\n                f\"- Progress: {progress:.1f}% \"\n                f\"- Batch Loss: {loss.item():.4f}\"\n            )\n\n    print(\n        f\"✅ Epoch [{epoch+1}/{epochs}] Completed \"\n        f\"- Avg Loss: {total_loss/len(loader):.4f}\\n\"\n    )\n\n# =========================\n# Result: Trained Encoder\n# =========================\nencoder = model.encoder\nprint(\"🎯 Encoder auto-supervisé prêt.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T14:25:37.189475Z","iopub.execute_input":"2026-01-24T14:25:37.189886Z","iopub.status.idle":"2026-01-24T15:11:02.141583Z","shell.execute_reply.started":"2026-01-24T14:25:37.189850Z","shell.execute_reply":"2026-01-24T15:11:02.140728Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nENCODER_PATH = \"simclr_encoder.pth\"\n\n# save weights  encoder\ntorch.save(model.encoder.state_dict(), ENCODER_PATH)\nprint(\"Encoder sauvegardé\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T15:17:10.806304Z","iopub.execute_input":"2026-01-24T15:17:10.807155Z","iopub.status.idle":"2026-01-24T15:17:10.930494Z","shell.execute_reply.started":"2026-01-24T15:17:10.807112Z","shell.execute_reply":"2026-01-24T15:17:10.929756Z"}},"outputs":[],"execution_count":null}]}