{
 "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 · Bitirme projesi: el yazısı rakam tanıyan 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": [
    "## Plan\n",
    "1. Veri ve doğrulama ayrımı  2. Model  3. Eğitim + checkpoint  4. Değerlendirme (karışıklık matrisi)  5. Dışa aktarma (JSON + ONNX)  6. Tarayıcıda dene"
   ],
   "id": "c03"
  },
  {
   "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": "c04"
  },
  {
   "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)"
   ],
   "id": "c05"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Birim testleri"
   ],
   "id": "c06"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m = KucukCNN()\n",
    "assert m(torch.randn(4, 1, 28, 28)).shape == (4, 10)\n",
    "assert sum(p.numel() for p in m.parameters()) == 9098\n",
    "print('testler geçti')"
   ],
   "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": [
    "## Karışıklık matrisi ve en çok karıştırılan çiftler"
   ],
   "id": "c09"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "cm = torch.zeros(10, 10, dtype=torch.long); wrong = []\n",
    "model.eval()\n",
    "with torch.no_grad():\n",
    "    for xb, yb in te_dl:\n",
    "        p = model(xb.to(device)).argmax(1).cpu()\n",
    "        for t, q in zip(yb, p):\n",
    "            cm[t, q] += 1\n",
    "plt.imshow(cm, cmap='Blues'); plt.colorbar(); plt.xlabel('tahmin'); plt.ylabel('gerçek'); plt.show()\n",
    "off = cm.clone(); off.fill_diagonal_(0)\n",
    "top = torch.topk(off.flatten(), 3)\n",
    "for v, i in zip(top.values, top.indices):\n",
    "    print(f'{i.item() // 10} -> {i.item() % 10}: {v.item()}')"
   ],
   "id": "c10"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dışa aktarma\n",
    "`agirliklar.json` dosyasını indir ve bitirme sayfasındaki tanıyıcıya **Kendi ağırlıklarını yükle** ile ver."
   ],
   "id": "c11"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "r = lambda t: [round(v, 6) for v in t.detach().cpu().flatten().tolist()]\n",
    "sd = model.state_dict()\n",
    "json.dump({'mean': 0.1307, 'std': 0.3081, 'testAcc': round(evaluate(model), 4),\n",
    "           'conv1': {'w': r(sd['conv1.weight']), 'b': r(sd['conv1.bias'])},\n",
    "           'conv2': {'w': r(sd['conv2.weight']), 'b': r(sd['conv2.bias'])},\n",
    "           'fc': {'w': r(sd['fc.weight']), 'b': r(sd['fc.bias'])}}, open('agirliklar.json', 'w'))\n",
    "torch.onnx.export(model.cpu().eval(), torch.randn(1, 1, 28, 28), 'rakam.onnx', input_names=['x'], output_names=['logit'])\n",
    "try:\n",
    "    from google.colab import files\n",
    "    files.download('agirliklar.json')\n",
    "except ImportError:\n",
    "    print('Colab dışında: dosya bu klasörde')"
   ],
   "id": "c12"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Teslim kontrol listesi\n",
    "- [ ] G1: test doğruluğu ≥ %98\n",
    "- [ ] G2: < 50 000 parametre\n",
    "- [ ] G3: karışıklık matrisi + 3 çift\n",
    "- [ ] G4: aynı tohumla tekrar\n",
    "- [ ] G5: tarayıcıda aynı tahminler\n",
    "- [ ] G6: kendi 30 çizimin"
   ],
   "id": "c13"
  }
 ]
}