{
 "nbformat": 4,
 "nbformat_minor": 5,
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  },
  "accelerator": "GPU",
  "colab": {
   "provenance": []
  }
 },
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# PyTorch ile Derin Öğrenme · Evrişimli sinir ağları (CNN)\n",
    "\n",
    "Macademia · Büyükler için"
   ],
   "id": "c00"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **Colab'da çalıştırma:** Bu dosyayı [colab.research.google.com](https://colab.research.google.com) adresinde **Dosya → Not defteri yükle** ile aç. GPU için **Çalışma zamanı → Çalışma zamanı türünü değiştir → T4 GPU** seç. Hücreleri yukarıdan aşağı sırayla çalıştır (Shift + Enter). PyTorch Colab'da kuruludur; bilgisayarında çalıştırmak istersen `pip install torch torchvision`."
   ],
   "id": "c01"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch, random, numpy as np\n",
    "torch.manual_seed(0); random.seed(0); np.random.seed(0)          # tekrarlanabilirlik\n",
    "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "print('PyTorch', torch.__version__, '| device:', device)"
   ],
   "id": "c02"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Evrişim aritmetiği"
   ],
   "id": "c03"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from torch import nn\n",
    "x = torch.randn(1, 1, 28, 28)\n",
    "for k, s, p in [(3, 1, 1), (3, 2, 1), (5, 1, 0), (7, 2, 3)]:\n",
    "    out = nn.Conv2d(1, 4, k, stride=s, padding=p)(x)\n",
    "    formul = (28 + 2 * p - (k - 1) - 1) // s + 1\n",
    "    print(f'k={k} s={s} p={p}: {tuple(out.shape)} formula={formul}')            # formülle aynı"
   ],
   "id": "c04"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Model ve MNIST"
   ],
   "id": "c05"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from torch import nn\n",
    "class KucukCNN(nn.Module):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        self.conv1 = nn.Conv2d(1, 8, 3, padding=1)\n",
    "        self.conv2 = nn.Conv2d(8, 16, 3, padding=1)\n",
    "        self.pool = nn.MaxPool2d(2)\n",
    "        self.fc = nn.Linear(16 * 7 * 7, 10)\n",
    "    def forward(self, x):\n",
    "        x = self.pool(torch.relu(self.conv1(x)))                     # (N, 8, 14, 14)\n",
    "        x = self.pool(torch.relu(self.conv2(x)))                     # (N, 16, 7, 7)\n",
    "        return self.fc(x.flatten(1))                                 # (N, 10)\n",
    "print(sum(p.numel() for p in KucukCNN().parameters()))"
   ],
   "id": "c06"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from torch.utils.data import DataLoader\n",
    "from torchvision import datasets, transforms\n",
    "tf = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])\n",
    "tr = datasets.MNIST('veri', train=True, download=True, transform=tf)\n",
    "te = datasets.MNIST('veri', train=False, download=True, transform=tf)\n",
    "tr_dl = DataLoader(tr, batch_size=64, shuffle=True); te_dl = DataLoader(te, batch_size=1000)\n",
    "print(len(tr), len(te))"
   ],
   "id": "c07"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate(model):\n",
    "    model.eval(); ok = 0\n",
    "    with torch.no_grad():\n",
    "        for xb, yb in te_dl:\n",
    "            ok += (model(xb.to(device)).argmax(1).cpu() == yb).sum().item()\n",
    "    return ok / len(te)\n",
    "\n",
    "model = KucukCNN().to(device)\n",
    "opt = torch.optim.Adam(model.parameters(), lr=1e-3)\n",
    "for epoch in range(4):\n",
    "    model.train()\n",
    "    for xb, yb in tr_dl:\n",
    "        xb, yb = xb.to(device), yb.to(device)\n",
    "        opt.zero_grad(); nn.functional.cross_entropy(model(xb), yb).backward(); opt.step()\n",
    "    print(f'epoch {epoch + 1}: test acc {evaluate(model):.4f}')"
   ],
   "id": "c08"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Öznitelik haritaları"
   ],
   "id": "c09"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "maps = {}\n",
    "model.conv1.register_forward_hook(lambda m, i, o: maps.__setitem__('c1', o.detach().cpu()))\n",
    "img, label = te[0]\n",
    "model(img.unsqueeze(0).to(device))\n",
    "fig, ax = plt.subplots(2, 8, figsize=(12, 3))\n",
    "for i in range(8):\n",
    "    ax[0, i].imshow(model.conv1.weight[i, 0].detach().cpu(), cmap='bwr'); ax[0, i].axis('off')\n",
    "    ax[1, i].imshow(maps['c1'][0, i], cmap='magma'); ax[1, i].axis('off')\n",
    "plt.suptitle(f'çekirdekler ve haritalar, etiket: {label}'); plt.show()"
   ],
   "id": "c10"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Alıştırmalar\n",
    "1. Kanal sayılarını iki katına çıkar; parametre ve doğruluk nasıl değişti?\n",
    "2. `nn.BatchNorm2d` ekle.\n",
    "3. Yanlış sınıflanan 10 görüntüyü göster."
   ],
   "id": "c11"
  }
 ]
}