File size: 10,642 Bytes
ae3aa18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "e11600f1",
   "metadata": {},
   "source": [
    "# EmpathRAG β€” Corpus Annotation (Day 10)\n",
    "Annotate all 1,674,369 FAISS chunks with emotion labels using the trained RoBERTa checkpoint.  \n",
    "**Runtime: A100 GPU. Expected time: ~25 minutes.**\n",
    "\n",
    "This notebook:\n",
    "1. Copies `metadata.db` from your local machine (uploaded here) to Colab\n",
    "2. Loads the RoBERTa+LoRA checkpoint from Drive\n",
    "3. Runs inference in batches of 512 on GPU\n",
    "4. Writes emotion labels back to the SQLite db\n",
    "5. Saves the annotated db back to Drive for you to download\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e85f0c1f",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "gpu = torch.cuda.get_device_name(0) if torch.cuda.is_available() else \"NO GPU\"\n",
    "print(f\"GPU  : {gpu}\")\n",
    "print(f\"VRAM : {torch.cuda.get_device_properties(0).total_memory/1e9:.1f} GB\")\n",
    "assert torch.cuda.is_available(), \"Switch to A100 runtime.\"\n",
    "print(\"βœ… GPU ready.\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "90dcada6",
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install -q peft>=0.18.0 accelerate>=1.0.0\n",
    "import peft, accelerate\n",
    "print(f\"peft: {peft.__version__}  |  accelerate: {accelerate.__version__}\")\n",
    "print(\"βœ… Packages ready.\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "51d42310",
   "metadata": {},
   "outputs": [],
   "source": [
    "from google.colab import drive\n",
    "import os, shutil\n",
    "\n",
    "drive.mount(\"/content/drive\")\n",
    "\n",
    "CHECKPOINT_DIR = \"/content/drive/MyDrive/empathrag/emotion_classifier\"\n",
    "DB_DRIVE_PATH  = \"/content/drive/MyDrive/empathrag/metadata.db\"\n",
    "DB_LOCAL_PATH  = \"/content/metadata.db\"\n",
    "\n",
    "# Verify checkpoint exists\n",
    "required = [\"adapter_config.json\", \"adapter_model.safetensors\", \"tokenizer.json\"]\n",
    "for f in required:\n",
    "    path = os.path.join(CHECKPOINT_DIR, f)\n",
    "    assert os.path.exists(path), f\"Missing: {path}\"\n",
    "print(f\"βœ… Checkpoint found at: {CHECKPOINT_DIR}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6533428",
   "metadata": {},
   "source": [
    "## ⬆️ Upload metadata.db\n",
    "\n",
    "Run the cell below. It will prompt you to upload a file.  \n",
    "Upload `data/indexes/metadata.db` from your local machine.\n",
    "\n",
    "The file is ~1.26 GB β€” upload will take 2-5 minutes depending on your connection.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f3928e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "from google.colab import files\n",
    "import shutil, os\n",
    "\n",
    "print(\"Click 'Choose Files' and select data/indexes/metadata.db from your local machine.\")\n",
    "print(\"File size: ~1.26 GB. Wait for upload to complete before proceeding.\")\n",
    "\n",
    "uploaded = files.upload()\n",
    "\n",
    "# Move to working path\n",
    "for fname in uploaded:\n",
    "    dest = \"/content/metadata.db\"\n",
    "    if fname != \"metadata.db\":\n",
    "        os.rename(fname, dest)\n",
    "    else:\n",
    "        pass  # already at /content/metadata.db if uploaded directly\n",
    "\n",
    "# Verify\n",
    "import sqlite3\n",
    "conn = sqlite3.connect(DB_LOCAL_PATH)\n",
    "total      = conn.execute(\"SELECT COUNT(*) FROM chunks\").fetchone()[0]\n",
    "annotated  = conn.execute(\"SELECT COUNT(*) FROM chunks WHERE emotion_label != -1\").fetchone()[0]\n",
    "unannotated= conn.execute(\"SELECT COUNT(*) FROM chunks WHERE emotion_label = -1\").fetchone()[0]\n",
    "conn.close()\n",
    "\n",
    "print(f\"\\nDB loaded:\")\n",
    "print(f\"  Total rows   : {total:,}\")\n",
    "print(f\"  Annotated    : {annotated:,}  (already done β€” will skip)\")\n",
    "print(f\"  Unannotated  : {unannotated:,}  (will process these)\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "444f0fea",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from transformers import AutoTokenizer, AutoModelForSequenceClassification\n",
    "from peft import PeftModel\n",
    "\n",
    "LABEL_NAMES = [\"distress\", \"anxiety\", \"frustration\", \"neutral\", \"hopeful\"]\n",
    "SAFETY_SCORE_MAP = {0: 0.0, 1: 0.0, 2: 0.3, 3: 0.7, 4: 1.0}\n",
    "DEVICE = torch.device(\"cuda\")\n",
    "\n",
    "print(\"Loading RoBERTa + LoRA checkpoint onto GPU...\")\n",
    "tokenizer = AutoTokenizer.from_pretrained(CHECKPOINT_DIR)\n",
    "base = AutoModelForSequenceClassification.from_pretrained(\"roberta-base\", num_labels=5)\n",
    "model = PeftModel.from_pretrained(base, CHECKPOINT_DIR).to(DEVICE).eval()\n",
    "\n",
    "# Quick sanity check\n",
    "enc = tokenizer(\"I feel completely overwhelmed and hopeless\",\n",
    "                return_tensors=\"pt\", truncation=True, max_length=128).to(DEVICE)\n",
    "with torch.no_grad():\n",
    "    pred = model(**enc).logits.argmax(-1).item()\n",
    "print(f\"Sanity check: 'overwhelmed and hopeless' β†’ {LABEL_NAMES[pred]}\")\n",
    "assert LABEL_NAMES[pred] == \"distress\", f\"Expected distress, got {LABEL_NAMES[pred]}\"\n",
    "print(\"βœ… Model loaded and verified on GPU.\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e8ef3c2",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sqlite3\n",
    "from tqdm import tqdm\n",
    "\n",
    "BATCH_SIZE = 512   # A100 can handle 512 at max_len=128 comfortably\n",
    "\n",
    "conn = sqlite3.connect(DB_LOCAL_PATH)\n",
    "\n",
    "# Only fetch rows not yet annotated β€” resumes safely if interrupted\n",
    "rows = conn.execute(\n",
    "    \"SELECT id, text FROM chunks WHERE emotion_label = -1\"\n",
    ").fetchall()\n",
    "\n",
    "print(f\"Rows to annotate: {len(rows):,}\")\n",
    "if len(rows) == 0:\n",
    "    print(\"Nothing to do β€” all rows already annotated.\")\n",
    "else:\n",
    "    updates = []\n",
    "\n",
    "    for i in tqdm(range(0, len(rows), BATCH_SIZE), desc=\"Annotating\"):\n",
    "        batch  = rows[i : i + BATCH_SIZE]\n",
    "        ids    = [r[0] for r in batch]\n",
    "        texts  = [r[1] for r in batch]\n",
    "\n",
    "        enc = tokenizer(\n",
    "            texts,\n",
    "            truncation=True,\n",
    "            max_length=128,\n",
    "            padding=True,\n",
    "            return_tensors=\"pt\"\n",
    "        ).to(DEVICE)\n",
    "\n",
    "        with torch.no_grad():\n",
    "            logits = model(**enc).logits\n",
    "\n",
    "        labels = logits.argmax(-1).tolist()\n",
    "\n",
    "        for rid, lbl in zip(ids, labels):\n",
    "            score = SAFETY_SCORE_MAP[lbl]\n",
    "            updates.append((lbl, score, rid))\n",
    "\n",
    "        # Commit every 50 batches (~25,600 rows) to avoid losing work if disconnected\n",
    "        if (i // BATCH_SIZE) % 50 == 0 and updates:\n",
    "            conn.executemany(\n",
    "                \"UPDATE chunks SET emotion_label=?, safety_score=? WHERE id=?\",\n",
    "                updates\n",
    "            )\n",
    "            conn.commit()\n",
    "            updates = []\n",
    "\n",
    "    # Final commit\n",
    "    if updates:\n",
    "        conn.executemany(\n",
    "            \"UPDATE chunks SET emotion_label=?, safety_score=? WHERE id=?\",\n",
    "            updates\n",
    "        )\n",
    "        conn.commit()\n",
    "\n",
    "print(\"\\nβœ… Annotation complete.\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f434d2b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import Counter\n",
    "\n",
    "total      = conn.execute(\"SELECT COUNT(*) FROM chunks\").fetchone()[0]\n",
    "annotated  = conn.execute(\"SELECT COUNT(*) FROM chunks WHERE emotion_label != -1\").fetchone()[0]\n",
    "unannotated= conn.execute(\"SELECT COUNT(*) FROM chunks WHERE emotion_label = -1\").fetchone()[0]\n",
    "\n",
    "print(f\"Total rows   : {total:,}\")\n",
    "print(f\"Annotated    : {annotated:,}\")\n",
    "print(f\"Unannotated  : {unannotated:,}  ← must be 0\")\n",
    "\n",
    "rows = conn.execute(\"SELECT emotion_label FROM chunks\").fetchall()\n",
    "dist = Counter(r[0] for r in rows)\n",
    "print(\"\\nEmotion distribution:\")\n",
    "for i, name in enumerate(LABEL_NAMES):\n",
    "    pct = 100 * dist[i] / total\n",
    "    print(f\"  {i} {name:<15} {dist[i]:>9,}  ({pct:.1f}%)\")\n",
    "\n",
    "conn.close()\n",
    "assert unannotated == 0, f\"{unannotated:,} rows still unannotated!\"\n",
    "print(\"\\nβœ… All rows annotated.\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "945552e5",
   "metadata": {},
   "outputs": [],
   "source": [
    "import shutil\n",
    "\n",
    "print(f\"Copying annotated DB to Drive: {DB_DRIVE_PATH}\")\n",
    "os.makedirs(os.path.dirname(DB_DRIVE_PATH), exist_ok=True)\n",
    "shutil.copy2(DB_LOCAL_PATH, DB_DRIVE_PATH)\n",
    "\n",
    "size_mb = os.path.getsize(DB_DRIVE_PATH) / 1e6\n",
    "print(f\"Saved: {DB_DRIVE_PATH}  ({size_mb:.1f} MB)\")\n",
    "print(\"\\nβœ… Done. Download metadata.db from Drive and replace data/indexes/metadata.db locally.\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ae95d9cd",
   "metadata": {},
   "source": [
    "## βœ… Annotation Complete β€” Download Steps\n",
    "\n",
    "1. In Google Drive, navigate to `My Drive/empathrag/metadata.db`\n",
    "2. Right-click β†’ Download\n",
    "3. Replace your local `data/indexes/metadata.db` with the downloaded file\n",
    "4. Verify locally:\n",
    "```python\n",
    "import sqlite3\n",
    "from collections import Counter\n",
    "conn = sqlite3.connect('data/indexes/metadata.db')\n",
    "unannotated = conn.execute('SELECT COUNT(*) FROM chunks WHERE emotion_label = -1').fetchone()[0]\n",
    "print(f'Unannotated: {unannotated}')  # must be 0\n",
    "conn.close()\n",
    "```\n"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "gpuType": "A100",
   "name": "colab_annotate_corpus.ipynb",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}