{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# NLP · Hafta 2 Ödevi — Klasik Temsiller ve Baseline Modeller\n",
    "\n",
    "**Ad Soyad:** …  **Numara:** …\n",
    "\n",
    "Bu notebook bir **iskelettir**. `# TODO` ile işaretli yerleri sen dolduracaksın; yorum ve analiz bölümleri tamamen senin.\n",
    "\n",
    "**Ödev maddeleri**\n",
    "1. Notebook + sonuç tablosu + hata analizi\n",
    "2. Perplexity karşılaştırması: kelime vs lemma vs subword bigram\n",
    "3. Proje veri setinde aynı baseline'ı kur ve `baseline.joblib` olarak kaydet\n",
    "\n",
    "> Kontrol aracı: https://nlp.dijital.plus (🏷️ Sınıflandırma, 📏 Perplexity sekmeleri)"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Gerekirse: %pip install scikit-learn pandas matplotlib joblib\n",
    "import math, random, re\n",
    "from collections import Counter, defaultdict\n",
    "\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "import joblib\n",
    "\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.pipeline import Pipeline\n",
    "from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer\n",
    "from sklearn.dummy import DummyClassifier\n",
    "from sklearn.naive_bayes import MultinomialNB\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.svm import LinearSVC\n",
    "from sklearn.metrics import accuracy_score, precision_recall_fscore_support, classification_report, ConfusionMatrixDisplay\n",
    "\n",
    "SEED = 42\n",
    "random.seed(SEED); np.random.seed(SEED)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Veri\n",
    "Örnek veri: `haber_kategorileri.csv` veya `urun_yorumlari.csv` (ayraç `;`, sütunlar `etiket;metin`)."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "df = pd.read_csv(\"haber_kategorileri.csv\", sep=\";\")   # TODO: kendi dosyan / yolun\n",
    "print(df.shape)\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# TODO: sınıf dağılımını yazdır ve bar grafiği çiz. Veri dengeli mi?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Tabakalı bölme\n",
    "`stratify=y` her sınıfın train/test'te aynı oranda olmasını sağlar. `random_state` sonuçları tekrarlanabilir yapar."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "X, y = df[\"metin\"], df[\"etiket\"]\n",
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X, y, test_size=0.25, stratify=y, random_state=SEED\n",
    ")\n",
    "print(len(X_train), len(X_test))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Baseline + modeller\n",
    "**Kural:** Vektörleştirici yalnızca eğitim verisine fit edilir. `Pipeline` bunu otomatik yapar (veri sızıntısı olmaz)."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def evaluate(name, pipe, rep, ngram):\n",
    "    pipe.fit(X_train, y_train)\n",
    "    pred = pipe.predict(X_test)\n",
    "    p, r, f1, _ = precision_recall_fscore_support(y_test, pred, average=\"macro\", zero_division=0)\n",
    "    return {\"Model\": name, \"Temsil\": rep, \"n-gram\": str(ngram),\n",
    "            \"Accuracy\": accuracy_score(y_test, pred), \"Macro-P\": p, \"Macro-R\": r, \"Macro-F1\": f1}\n",
    "\n",
    "results = []\n",
    "\n",
    "# Baseline: her zaman en sık sınıf\n",
    "results.append(evaluate(\"Dummy\", Pipeline([(\"vec\", CountVectorizer()), (\"clf\", DummyClassifier(strategy=\"most_frequent\"))]), \"-\", \"-\"))\n",
    "\n",
    "# Örnek bir kurulum:\n",
    "results.append(evaluate(\"MultinomialNB\",\n",
    "                        Pipeline([(\"vec\", TfidfVectorizer(ngram_range=(1, 1))), (\"clf\", MultinomialNB(alpha=1.0))]),\n",
    "                        \"TF-IDF\", (1, 1)))\n",
    "\n",
    "# TODO: LogisticRegression ve LinearSVC'yi ekle.\n",
    "# TODO: Aynı modelleri BoW (CountVectorizer) ve ngram_range=(1, 2) ile de dene.\n",
    "# İpucu: modeller ve vektörleştiriciler üzerinde iç içe bir döngü kurabilirsin.\n",
    "\n",
    "results_df = pd.DataFrame(results).sort_values(\"Macro-F1\", ascending=False)\n",
    "results_df.round(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Sonuç tablosu yorumu\n",
    "*TODO:* Hangi model en iyi? Dummy'ye göre ne kadar iyi? BoW vs TF-IDF, (1,1) vs (1,2) farkı ne? Neden accuracy yerine macro-F1'e bakıyoruz?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. En iyi modelin ayrıntılı değerlendirmesi"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "best_pipe = None  # TODO: en iyi macro-F1'i veren pipeline'ı yeniden kur ve fit et\n",
    "# best_pipe.fit(X_train, y_train)\n",
    "# y_pred = best_pipe.predict(X_test)\n",
    "# print(classification_report(y_test, y_pred, digits=3, zero_division=0))\n",
    "# ConfusionMatrixDisplay.from_predictions(y_test, y_pred, xticks_rotation=45); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Hata analizi\n",
    "Yanlış sınıflanan örnekleri tabloya dök ve **en az 10 tanesini** incele.\n",
    "Her hatayı bir kategoriye koy: olumsuzluk/ironi, konu örtüşmesi, OOV (eğitimde görülmemiş kelime), etiket belirsizliği, çok kısa metin…"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# TODO:\n",
    "# errors = pd.DataFrame({\"metin\": X_test, \"gercek\": y_test, \"tahmin\": y_pred})\n",
    "# errors = errors[errors.gercek != errors.tahmin]\n",
    "# errors.head(20)"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# (İsteğe bağlı) Doğrusal modellerde sınıf başına en belirleyici kelimeler:\n",
    "# vec, clf = best_pipe.named_steps[\"vec\"], best_pipe.named_steps[\"clf\"]\n",
    "# names = np.array(vec.get_feature_names_out())\n",
    "# for i, c in enumerate(clf.classes_):\n",
    "#     print(c, names[np.argsort(clf.coef_[i])[-10:]][::-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Hata analizi bulguları\n",
    "| Kategori | Örnek sayısı | Örnek | Olası çözüm |\n",
    "|---|---|---|---|\n",
    "| … | … | … | … |\n",
    "\n",
    "*TODO:* Bulgularını 5–10 cümleyle yaz."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Perplexity: kelime vs lemma vs subword bigram\n",
    "Aynı train/test cümle bölmesi üzerinde üç tokenizasyonu karşılaştır.\n",
    "\n",
    "$$PP = \\exp\\Big(-\\frac{1}{N}\\sum_i \\ln P(w_i \\mid w_{i-1})\\Big)$$\n",
    "\n",
    "**Dikkat:** Tokenizasyonlar arasında adil karşılaştırma için toplam log-olasılığı **kelime sayısına** da böl (kelime başına PP). OOV oranını raporla."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Korpus: cümle listesi (örnek olarak veri setinin metinleri)\n",
    "sentences = df[\"metin\"].astype(str).tolist()     # TODO: istersen daha büyük bir korpus kullan\n",
    "random.Random(SEED).shuffle(sentences)\n",
    "cut = int(len(sentences) * 0.8)\n",
    "train_sents, test_sents = sentences[:cut], sentences[cut:]\n",
    "\n",
    "def word_tokenize(s):\n",
    "    s = s.replace(\"I\", \"ı\").replace(\"İ\", \"i\").lower()\n",
    "    return re.findall(r\"\\w+\", s)\n",
    "\n",
    "def lemma_tokenize(s):\n",
    "    # TODO: Türkçe lemmatizer kullan (ör. zeyrek, stanza 'tr', spaCy-tr ...).\n",
    "    raise NotImplementedError\n",
    "\n",
    "def subword_tokenize(s):\n",
    "    # TODO: yalnızca train_sents ile BPE eğit (ör. sentencepiece veya HF `tokenizers`), sonra uygula.\n",
    "    raise NotImplementedError"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "UNK, BOS, EOS = \"<UNK>\", \"<s>\", \"</s>\"\n",
    "\n",
    "def train_bigram(token_lists):\n",
    "    # TODO: unigram ve bigram sayımlarını çıkar. Her cümleye BOS ... EOS ekle.\n",
    "    # Döndür: (unigram Counter, bigram dict/Counter, sözlük kümesi)\n",
    "    raise NotImplementedError\n",
    "\n",
    "def prob(prev, w, uni, bi, V, method=\"laplace\", k=0.1):\n",
    "    # TODO: MLE, Laplace (add-1) ve add-k formüllerini uygula.\n",
    "    #   Laplace: (C(prev,w) + 1) / (C(prev) + V)\n",
    "    #   add-k  : (C(prev,w) + k) / (C(prev) + k*V)\n",
    "    raise NotImplementedError\n",
    "\n",
    "def perplexity(token_lists, uni, bi, vocab, n_words, method=\"laplace\", k=0.1):\n",
    "    # TODO: bilinmeyen tokenları UNK'ya çevir, log-olasılıkları topla.\n",
    "    # Hem token başına hem kelime başına (n_words) PP döndür.\n",
    "    raise NotImplementedError"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# TODO: her tokenizasyon için bu tabloyu doldur\n",
    "ppl_rows = []\n",
    "# for name, tok in [(\"kelime\", word_tokenize), (\"lemma\", lemma_tokenize), (\"subword\", subword_tokenize)]:\n",
    "#     ...\n",
    "#     ppl_rows.append({\"Tokenizasyon\": name, \"V\": ..., \"OOV\": ..., \"PP/token (Laplace)\": ..., \"PP/kelime (Laplace)\": ..., \"PP/kelime (add-k)\": ...})\n",
    "pd.DataFrame(ppl_rows)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Perplexity yorumu\n",
    "*TODO:* Hangi tokenizasyon kelime başına en düşük PP'yi verdi? OOV oranı sonucu nasıl etkiledi? Token başına PP neden yanıltıcı olabilir? k değerini nasıl seçmek gerekir (test kümesinde değil!)?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. Proje veri setinde baseline ve `baseline.joblib`"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# TODO: proje verini yükle, aynı bölme + aynı baseline'ı kur, macro-F1'i raporla.\n",
    "# proj = pd.read_csv(\"proje_verim.csv\")\n",
    "# ...\n",
    "# final_pipe = Pipeline([...])\n",
    "# final_pipe.fit(Xp_train, yp_train)\n",
    "\n",
    "# Kaydet ve geri yükleyip dene:\n",
    "# joblib.dump(final_pipe, \"baseline.joblib\")\n",
    "# loaded = joblib.load(\"baseline.joblib\")\n",
    "# loaded.predict([\"örnek bir cümle\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. Sonuç\n",
    "*TODO:* 1 paragraf: en iyi baseline, beklenen iyileştirme yolları (daha çok veri, lemma, n-gram, hiperparametre araması, cross-validation) ve bir sonraki adım."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}