Last active
October 10, 2025 22:04
-
-
Save jc4p/334d52de25624e6419aa21f4ccd35f10 to your computer and use it in GitHub Desktop.
Making and running a BERT classifier on Farcaster casts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Classify Farcaster casts using Google Gemini Batch API. | |
| Classifies each cast into one of 5 categories: | |
| - Personal Life | |
| - Spam | |
| - App/Bot Interactions | |
| - Crypto Content | |
| - Not Enough Context To Tell | |
| """ | |
| import json | |
| import time | |
| from google import genai | |
| from google.genai import types | |
| def create_classification_prompt(cast_text): | |
| """Create the prompt for classifying the cast.""" | |
| return f"""Classify this Farcaster cast into exactly ONE of these categories: | |
| 1. Personal Life | |
| 2. Spam | |
| 3. App/Bot Interactions | |
| 4. Crypto Content | |
| 5. Not Enough Context To Tell | |
| Cast: "{cast_text}" | |
| Respond in this exact format: | |
| Category: [category name] | |
| Confidence: [number from 0-10]""" | |
| def main(): | |
| # Initialize the client | |
| client = genai.Client() | |
| # Load the filtered casts from JSON | |
| print("Loading data/casts_filtered.json...") | |
| with open('data/casts_filtered.json', 'r') as f: | |
| casts = [] | |
| for line in f: | |
| casts.append(json.loads(line)) | |
| # Sample to 10k records | |
| import random | |
| if len(casts) > 10000: | |
| print(f"Loaded {len(casts)} casts, sampling 10,000...") | |
| random.seed(42) # For reproducibility | |
| casts = random.sample(casts, 10000) | |
| print(f"Processing {len(casts)} casts") | |
| # Create JSONL file with batch requests | |
| jsonl_filename = 'classification_batch_requests.jsonl' | |
| print(f"Creating {jsonl_filename}...") | |
| with open(jsonl_filename, 'w') as f: | |
| for i, cast in enumerate(casts): | |
| cast_hash = cast['Hash'] | |
| cast_text = cast['Text'] | |
| # Create the classification prompt | |
| prompt = create_classification_prompt(cast_text) | |
| # Create the request object | |
| request = { | |
| "key": f"cast-{i}-{cast_hash}", | |
| "request": { | |
| "contents": [ | |
| { | |
| "parts": [{"text": prompt}], | |
| "role": "user" | |
| } | |
| ], | |
| "generation_config": { | |
| "temperature": 0.1 # Very low temperature for consistent classification | |
| } | |
| } | |
| } | |
| f.write(json.dumps(request) + '\n') | |
| print(f"Created {jsonl_filename} with {len(casts)} requests") | |
| # Upload the file to the File API | |
| print("Uploading JSONL file to Google File API...") | |
| uploaded_file = client.files.upload( | |
| file=jsonl_filename, | |
| config=types.UploadFileConfig( | |
| display_name='farcaster-cast-classification', | |
| mime_type='application/jsonl' | |
| ) | |
| ) | |
| print(f"Uploaded file: {uploaded_file.name}") | |
| # Create the batch job | |
| print("Creating batch job...") | |
| batch_job = client.batches.create( | |
| model="gemini-flash-latest", | |
| src=uploaded_file.name, | |
| config={ | |
| 'display_name': "farcaster-cast-classification", | |
| }, | |
| ) | |
| job_name = batch_job.name | |
| print(f"Created batch job: {job_name}") | |
| # Poll for job completion | |
| print("\nPolling job status...") | |
| completed_states = { | |
| 'JOB_STATE_SUCCEEDED', | |
| 'JOB_STATE_FAILED', | |
| 'JOB_STATE_CANCELLED', | |
| 'JOB_STATE_EXPIRED', | |
| } | |
| while batch_job.state.name not in completed_states: | |
| print(f"Current state: {batch_job.state.name}") | |
| time.sleep(30) # Wait 30 seconds before polling again | |
| batch_job = client.batches.get(name=job_name) | |
| print(f"\nJob finished with state: {batch_job.state.name}") | |
| # Handle results | |
| if batch_job.state.name == 'JOB_STATE_SUCCEEDED': | |
| if batch_job.dest and batch_job.dest.file_name: | |
| result_file_name = batch_job.dest.file_name | |
| print(f"Results are in file: {result_file_name}") | |
| # Download the results | |
| print("Downloading results...") | |
| file_content = client.files.download(file=result_file_name) | |
| # Save raw results (bulk file) | |
| raw_results_filename = 'cast_classification_raw.jsonl' | |
| with open(raw_results_filename, 'wb') as f: | |
| f.write(file_content) | |
| print(f"Saved raw results to {raw_results_filename}") | |
| # Parse and format results | |
| print("Parsing results...") | |
| results = [] | |
| for line in file_content.decode('utf-8').strip().split('\n'): | |
| result = json.loads(line) | |
| results.append(result) | |
| # Create a structured output with the original cast data + classification | |
| structured_results = [] | |
| for i, cast in enumerate(casts): | |
| # Find matching result | |
| key = f"cast-{i}-{cast['Hash']}" | |
| matching_result = next((r for r in results if r.get('key') == key), None) | |
| output = { | |
| 'Fid': cast['Fid'], | |
| 'Hash': cast['Hash'], | |
| 'Text': cast['Text'], | |
| 'Timestamp': cast['Timestamp'], | |
| 'Classification': None, | |
| 'Confidence': None, | |
| 'error': None | |
| } | |
| if matching_result: | |
| if 'response' in matching_result: | |
| # Extract the classification text | |
| response_text = matching_result['response']['candidates'][0]['content']['parts'][0]['text'].strip() | |
| # Parse the response to extract category and confidence | |
| lines = response_text.split('\n') | |
| for line in lines: | |
| if line.startswith('Category:'): | |
| output['Classification'] = line.replace('Category:', '').strip() | |
| elif line.startswith('Confidence:'): | |
| try: | |
| output['Confidence'] = int(line.replace('Confidence:', '').strip()) | |
| except ValueError: | |
| output['Confidence'] = None | |
| elif 'status' in matching_result: | |
| output['error'] = matching_result['status'] | |
| structured_results.append(output) | |
| # Save structured results (filtered file for analysis) | |
| output_filename = 'cast_classification_results.json' | |
| with open(output_filename, 'w') as f: | |
| json.dump(structured_results, f, indent=2) | |
| print(f"Saved structured results to {output_filename}") | |
| # Print summary by category | |
| successful = sum(1 for r in structured_results if r['Classification']) | |
| failed = sum(1 for r in structured_results if r['error']) | |
| # Count by category | |
| category_counts = {} | |
| for r in structured_results: | |
| if r['Classification']: | |
| cat = r['Classification'] | |
| category_counts[cat] = category_counts.get(cat, 0) + 1 | |
| # Calculate average confidence per category | |
| category_confidence = {} | |
| for r in structured_results: | |
| if r['Classification'] and r['Confidence'] is not None: | |
| cat = r['Classification'] | |
| if cat not in category_confidence: | |
| category_confidence[cat] = [] | |
| category_confidence[cat].append(r['Confidence']) | |
| print(f"\nSummary:") | |
| print(f" Total casts: {len(structured_results)}") | |
| print(f" Successful: {successful}") | |
| print(f" Failed: {failed}") | |
| print(f"\nClassification breakdown:") | |
| for cat, count in sorted(category_counts.items(), key=lambda x: x[1], reverse=True): | |
| avg_conf = sum(category_confidence.get(cat, [0])) / len(category_confidence.get(cat, [1])) if cat in category_confidence else 0 | |
| print(f" {cat}: {count} (avg confidence: {avg_conf:.1f})") | |
| elif batch_job.state.name == 'JOB_STATE_FAILED': | |
| print(f"Error: {batch_job.error}") | |
| print("\nDone!") | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "cells": [ | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Farcaster Cast Classification Training\n", | |
| "\n", | |
| "Training a BERT base model to classify casts into 5 categories:\n", | |
| "- Personal Life - Salient\n", | |
| "- Personal Life - Not Salient\n", | |
| "- Crypto Content\n", | |
| "- App/Bot Interactions\n", | |
| "- Not Enough Context To Tell\n", | |
| "\n", | |
| "(Spam category removed - low confidence predictions will be treated as spam)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 1, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "/home/ubuntu/.venv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", | |
| " from .autonotebook import tqdm as notebook_tqdm\n", | |
| "/home/ubuntu/.venv/lib/python3.10/site-packages/pydantic/_internal/_generate_schema.py:2249: UnsupportedFieldAttributeWarning: The 'repr' attribute with value False was provided to the `Field()` function, which has no effect in the context it was used. 'repr' is field-specific metadata, and can only be attached to a model field using `Annotated` metadata or by assignment. This may have happened because an `Annotated` type alias using the `type` statement was used, or if the `Field()` function was attached to a single member of a union type.\n", | |
| " warnings.warn(\n", | |
| "/home/ubuntu/.venv/lib/python3.10/site-packages/pydantic/_internal/_generate_schema.py:2249: UnsupportedFieldAttributeWarning: The 'frozen' attribute with value True was provided to the `Field()` function, which has no effect in the context it was used. 'frozen' is field-specific metadata, and can only be attached to a model field using `Annotated` metadata or by assignment. This may have happened because an `Annotated` type alias using the `type` statement was used, or if the `Field()` function was attached to a single member of a union type.\n", | |
| " warnings.warn(\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "# Imports and environment setup\n", | |
| "import os\n", | |
| "os.environ[\"TOKENIZERS_PARALLELISM\"] = \"false\"\n", | |
| "\n", | |
| "import torch\n", | |
| "from torch.utils.data import Dataset, DataLoader\n", | |
| "from transformers import (\n", | |
| " AutoTokenizer, \n", | |
| " AutoModelForSequenceClassification, \n", | |
| " Trainer, \n", | |
| " TrainingArguments\n", | |
| ")\n", | |
| "from sklearn.model_selection import train_test_split\n", | |
| "from sklearn.metrics import f1_score, classification_report, precision_recall_fscore_support, accuracy_score\n", | |
| "import numpy as np\n", | |
| "from collections import Counter\n", | |
| "import wandb\n", | |
| "import json\n", | |
| "import random" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 2, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\u001b[34m\u001b[1mwandb\u001b[0m: \u001b[32m\u001b[41mERROR\u001b[0m Failed to detect the name of this notebook. You can set it manually with the WANDB_NOTEBOOK_NAME environment variable to enable code saving.\n", | |
| "\u001b[34m\u001b[1mwandb\u001b[0m: Currently logged in as: \u001b[33mjc4p\u001b[0m to \u001b[32mhttps://api.wandb.ai\u001b[0m. Use \u001b[1m`wandb login --relogin`\u001b[0m to force relogin\n" | |
| ] | |
| }, | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "True" | |
| ] | |
| }, | |
| "execution_count": 2, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "# Login to Weights & Biases\n", | |
| "import wandb\n", | |
| "wandb.login()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 3, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Loaded 8,030 samples\n", | |
| "Number of classes: 5\n", | |
| "Labels: ['Personal Life - Salient', 'Personal Life - Not Salient', 'Crypto Content', 'App/Bot Interactions', 'Not Enough Context To Tell']\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "# Load and prepare balanced training data\n", | |
| "def load_and_prepare_data(data_file):\n", | |
| " \"\"\"\n", | |
| " Load merged and balanced training data.\n", | |
| " \"\"\"\n", | |
| " # Load the training data\n", | |
| " with open(data_file, 'r') as f:\n", | |
| " data = json.load(f)\n", | |
| " \n", | |
| " # Define label mapping for 5 categories (no Spam)\n", | |
| " label_map = {\n", | |
| " 'Personal Life - Salient': 0,\n", | |
| " 'Personal Life - Not Salient': 1,\n", | |
| " 'Crypto Content': 2,\n", | |
| " 'App/Bot Interactions': 3,\n", | |
| " 'Not Enough Context To Tell': 4\n", | |
| " }\n", | |
| " \n", | |
| " # Prepare data\n", | |
| " texts = []\n", | |
| " labels = []\n", | |
| " \n", | |
| " for item in data:\n", | |
| " classification = item.get('Classification')\n", | |
| " if classification and classification in label_map:\n", | |
| " texts.append(item['Text'])\n", | |
| " labels.append(label_map[classification])\n", | |
| " \n", | |
| " return texts, labels, label_map\n", | |
| "\n", | |
| "# Load the balanced dataset\n", | |
| "texts, labels, label_map = load_and_prepare_data('training_data_balanced.json')\n", | |
| "\n", | |
| "print(f\"Loaded {len(texts):,} samples\")\n", | |
| "print(f\"Number of classes: {len(label_map)}\")\n", | |
| "print(f\"Labels: {list(label_map.keys())}\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 4, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Dataset statistics:\n", | |
| "Total samples: 8030\n", | |
| "\n", | |
| "Class distribution:\n", | |
| " Personal Life - Salient: 486 (6.1%)\n", | |
| " Personal Life - Not Salient: 2582 (32.2%)\n", | |
| " Crypto Content: 2582 (32.2%)\n", | |
| " App/Bot Interactions: 1089 (13.6%)\n", | |
| " Not Enough Context To Tell: 1291 (16.1%)\n", | |
| "\n", | |
| "Sample texts per category:\n", | |
| "\n", | |
| "Personal Life - Salient:\n", | |
| " Agreed, Syracuse's terrain makes for pleasant walks. Flat surfaces are great for leisurely strolls and easy biking too....\n", | |
| "\n", | |
| "Personal Life - Not Salient:\n", | |
| " You for talk since nau 😂😭...\n", | |
| "\n", | |
| "Crypto Content:\n", | |
| " Nike’s .SWOOSH platform hits 5M users – Sneaker NFTs minted. 👟 #NFT #Fashion...\n", | |
| "\n", | |
| "App/Bot Interactions:\n", | |
| " I currently rank #421 on The Leaderboard. Where do you rank?...\n", | |
| "\n", | |
| "Not Enough Context To Tell:\n", | |
| " I couldn't have said it better myself. Well done!...\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "# Display dataset statistics\n", | |
| "from collections import Counter\n", | |
| "\n", | |
| "label_counts = Counter(labels)\n", | |
| "label_names = sorted(label_map.keys(), key=lambda x: label_map[x])\n", | |
| "\n", | |
| "print(\"Dataset statistics:\")\n", | |
| "print(f\"Total samples: {len(texts)}\")\n", | |
| "print(\"\\nClass distribution:\")\n", | |
| "for name in label_names:\n", | |
| " idx = label_map[name]\n", | |
| " count = label_counts[idx]\n", | |
| " percentage = (count / len(texts)) * 100\n", | |
| " print(f\" {name}: {count} ({percentage:.1f}%)\")\n", | |
| "\n", | |
| "# Show sample texts\n", | |
| "print(\"\\nSample texts per category:\")\n", | |
| "for name in label_names:\n", | |
| " idx = label_map[name]\n", | |
| " sample_indices = [i for i, l in enumerate(labels) if l == idx]\n", | |
| " if sample_indices:\n", | |
| " sample_idx = sample_indices[0]\n", | |
| " print(f\"\\n{name}:\")\n", | |
| " print(f\" {texts[sample_idx][:150]}...\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 5, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Define dataset class for single-label classification\n", | |
| "class TextClassificationDataset(Dataset):\n", | |
| " def __init__(self, texts, labels, tokenizer, max_length=512):\n", | |
| " self.encodings = tokenizer(\n", | |
| " texts,\n", | |
| " padding=True,\n", | |
| " truncation=True,\n", | |
| " max_length=max_length,\n", | |
| " return_tensors=None\n", | |
| " )\n", | |
| " self.labels = labels\n", | |
| "\n", | |
| " def __getitem__(self, idx):\n", | |
| " item = {\n", | |
| " key: torch.tensor(val[idx]) \n", | |
| " for key, val in self.encodings.items()\n", | |
| " }\n", | |
| " item['labels'] = torch.tensor(self.labels[idx])\n", | |
| " return item\n", | |
| "\n", | |
| " def __len__(self):\n", | |
| " return len(self.labels)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 6, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Define weighted trainer for handling remaining class imbalance\n", | |
| "class WeightedTrainer(Trainer):\n", | |
| " def __init__(self, class_weights, *args, **kwargs):\n", | |
| " super().__init__(*args, **kwargs)\n", | |
| " self.class_weights = torch.FloatTensor(class_weights)\n", | |
| "\n", | |
| " def compute_loss(self, model, inputs, return_outputs=False, num_items_in_batch=None):\n", | |
| " labels = inputs.pop(\"labels\")\n", | |
| " outputs = model(**inputs)\n", | |
| " logits = outputs.logits\n", | |
| " \n", | |
| " # Move class weights to same device as model\n", | |
| " weights = self.class_weights.to(logits.device)\n", | |
| " \n", | |
| " # Apply class weights to CrossEntropyLoss\n", | |
| " loss_fct = torch.nn.CrossEntropyLoss(weight=weights)\n", | |
| " loss = loss_fct(logits.view(-1, model.config.num_labels), labels.view(-1))\n", | |
| " \n", | |
| " return (loss, outputs) if return_outputs else loss" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 7, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Training function\n", | |
| "def train_classifier(\n", | |
| " texts,\n", | |
| " labels,\n", | |
| " label_map,\n", | |
| " model_name='bert-base-uncased',\n", | |
| " batch_size=128,\n", | |
| " num_epochs=10,\n", | |
| " learning_rate=2e-5,\n", | |
| " max_length=512,\n", | |
| " weight_decay=0.01,\n", | |
| " warmup_ratio=0.1,\n", | |
| "):\n", | |
| " \"\"\"\n", | |
| " Training function for single-label classification with class weighting\n", | |
| " \"\"\"\n", | |
| " wandb.init(project=\"farcaster-cast-classification\")\n", | |
| " \n", | |
| " # Calculate class weights (inverse frequency) for remaining imbalance\n", | |
| " label_counts = Counter(labels)\n", | |
| " num_samples = len(labels)\n", | |
| " num_classes = len(label_map)\n", | |
| " \n", | |
| " class_weights = []\n", | |
| " for i in range(num_classes):\n", | |
| " count = label_counts.get(i, 1)\n", | |
| " weight = num_samples / (num_classes * count)\n", | |
| " class_weights.append(weight)\n", | |
| " \n", | |
| " print(\"Class weights:\")\n", | |
| " label_names = sorted(label_map.keys(), key=lambda x: label_map[x])\n", | |
| " for i, name in enumerate(label_names):\n", | |
| " print(f\" {name}: {class_weights[i]:.2f}\")\n", | |
| " \n", | |
| " # Initialize tokenizer and model\n", | |
| " tokenizer = AutoTokenizer.from_pretrained(model_name)\n", | |
| " model = AutoModelForSequenceClassification.from_pretrained(\n", | |
| " model_name,\n", | |
| " num_labels=len(label_map),\n", | |
| " problem_type=\"single_label_classification\"\n", | |
| " )\n", | |
| " \n", | |
| " model.gradient_checkpointing_enable()\n", | |
| " \n", | |
| " # Split data\n", | |
| " train_texts, val_texts, train_labels, val_labels = train_test_split(\n", | |
| " texts, labels, test_size=0.15, stratify=labels, random_state=42\n", | |
| " )\n", | |
| " \n", | |
| " # Create datasets\n", | |
| " train_dataset = TextClassificationDataset(train_texts, train_labels, tokenizer, max_length)\n", | |
| " val_dataset = TextClassificationDataset(val_texts, val_labels, tokenizer, max_length)\n", | |
| " \n", | |
| " # Calculate training steps\n", | |
| " num_training_steps = (len(train_dataset) // batch_size) * num_epochs\n", | |
| " num_warmup_steps = int(num_training_steps * warmup_ratio)\n", | |
| " \n", | |
| " # Define training arguments\n", | |
| " training_args = TrainingArguments(\n", | |
| " output_dir=\"./farcaster-classifier\",\n", | |
| " num_train_epochs=num_epochs,\n", | |
| " per_device_train_batch_size=batch_size,\n", | |
| " per_device_eval_batch_size=batch_size * 2,\n", | |
| " warmup_steps=num_warmup_steps,\n", | |
| " weight_decay=weight_decay,\n", | |
| " logging_dir=\"./logs\",\n", | |
| " logging_steps=10,\n", | |
| " eval_strategy=\"steps\",\n", | |
| " eval_steps=50,\n", | |
| " save_strategy=\"steps\",\n", | |
| " save_steps=50,\n", | |
| " load_best_model_at_end=True,\n", | |
| " metric_for_best_model=\"eval_f1_macro\",\n", | |
| " learning_rate=learning_rate,\n", | |
| " bf16=True,\n", | |
| " dataloader_num_workers=4,\n", | |
| " group_by_length=True,\n", | |
| " optim=\"adamw_torch_fused\",\n", | |
| " lr_scheduler_type=\"cosine\",\n", | |
| " gradient_checkpointing=True\n", | |
| " )\n", | |
| " \n", | |
| " def compute_metrics(eval_pred):\n", | |
| " predictions, labels = eval_pred\n", | |
| " predictions = np.argmax(predictions, axis=1)\n", | |
| " \n", | |
| " # Calculate per-class metrics\n", | |
| " metrics = {}\n", | |
| " label_names = sorted(label_map.keys(), key=lambda x: label_map[x])\n", | |
| " \n", | |
| " f1_scores = []\n", | |
| " for i, name in enumerate(label_names):\n", | |
| " # Get binary labels for this class\n", | |
| " binary_labels = (labels == i).astype(int)\n", | |
| " binary_preds = (predictions == i).astype(int)\n", | |
| " \n", | |
| " precision, recall, f1, _ = precision_recall_fscore_support(\n", | |
| " binary_labels, binary_preds, average='binary', zero_division=0\n", | |
| " )\n", | |
| " metrics[f\"f1_{name.lower().replace(' ', '_').replace('-', '')}\"[:20]] = f1\n", | |
| " metrics[f\"precision_{name.lower().replace(' ', '_').replace('-', '')}\"[:20]] = precision\n", | |
| " metrics[f\"recall_{name.lower().replace(' ', '_').replace('-', '')}\"[:20]] = recall\n", | |
| " f1_scores.append(f1)\n", | |
| " \n", | |
| " # Overall metrics\n", | |
| " metrics[\"f1_macro\"] = np.mean(f1_scores)\n", | |
| " metrics[\"accuracy\"] = accuracy_score(labels, predictions)\n", | |
| " \n", | |
| " return metrics\n", | |
| " \n", | |
| " # Initialize trainer with class weights\n", | |
| " trainer = WeightedTrainer(\n", | |
| " class_weights=class_weights,\n", | |
| " model=model,\n", | |
| " args=training_args,\n", | |
| " train_dataset=train_dataset,\n", | |
| " eval_dataset=val_dataset,\n", | |
| " compute_metrics=compute_metrics,\n", | |
| " )\n", | |
| " \n", | |
| " # Train the model\n", | |
| " trainer.train()\n", | |
| " \n", | |
| " # Final evaluation\n", | |
| " final_metrics = trainer.evaluate()\n", | |
| " print(\"\\nFinal Evaluation Metrics:\")\n", | |
| " for key, value in final_metrics.items():\n", | |
| " print(f\"{key}: {value}\")\n", | |
| " \n", | |
| " # Save the model and tokenizer\n", | |
| " model.save_pretrained(\"./farcaster-classifier-final\")\n", | |
| " tokenizer.save_pretrained(\"./farcaster-classifier-final\")\n", | |
| " \n", | |
| " # Save label map\n", | |
| " with open(\"./farcaster-classifier-final/label_map.json\", 'w') as f:\n", | |
| " json.dump(label_map, f)\n", | |
| " \n", | |
| " wandb.finish()\n", | |
| " \n", | |
| " return model, tokenizer" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/html": [], | |
| "text/plain": [ | |
| "<IPython.core.display.HTML object>" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "output_type": "display_data" | |
| }, | |
| { | |
| "data": { | |
| "text/html": [ | |
| "Tracking run with wandb version 0.22.2" | |
| ], | |
| "text/plain": [ | |
| "<IPython.core.display.HTML object>" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "output_type": "display_data" | |
| }, | |
| { | |
| "data": { | |
| "text/html": [ | |
| "Run data is saved locally in <code>/home/ubuntu/wandb/run-20251009_204124-j5y5edzx</code>" | |
| ], | |
| "text/plain": [ | |
| "<IPython.core.display.HTML object>" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "output_type": "display_data" | |
| }, | |
| { | |
| "data": { | |
| "text/html": [ | |
| "Syncing run <strong><a href='https://wandb.ai/jc4p/farcaster-cast-classification/runs/j5y5edzx' target=\"_blank\">vital-glitter-3</a></strong> to <a href='https://wandb.ai/jc4p/farcaster-cast-classification' target=\"_blank\">Weights & Biases</a> (<a href='https://wandb.me/developer-guide' target=\"_blank\">docs</a>)<br>" | |
| ], | |
| "text/plain": [ | |
| "<IPython.core.display.HTML object>" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "output_type": "display_data" | |
| }, | |
| { | |
| "data": { | |
| "text/html": [ | |
| " View project at <a href='https://wandb.ai/jc4p/farcaster-cast-classification' target=\"_blank\">https://wandb.ai/jc4p/farcaster-cast-classification</a>" | |
| ], | |
| "text/plain": [ | |
| "<IPython.core.display.HTML object>" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "output_type": "display_data" | |
| }, | |
| { | |
| "data": { | |
| "text/html": [ | |
| " View run at <a href='https://wandb.ai/jc4p/farcaster-cast-classification/runs/j5y5edzx' target=\"_blank\">https://wandb.ai/jc4p/farcaster-cast-classification/runs/j5y5edzx</a>" | |
| ], | |
| "text/plain": [ | |
| "<IPython.core.display.HTML object>" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "output_type": "display_data" | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Class weights:\n", | |
| " Personal Life - Salient: 3.30\n", | |
| " Personal Life - Not Salient: 0.62\n", | |
| " Crypto Content: 0.62\n", | |
| " App/Bot Interactions: 1.47\n", | |
| " Not Enough Context To Tell: 1.24\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "Some weights of BertForSequenceClassification were not initialized from the model checkpoint at bert-base-uncased and are newly initialized: ['classifier.bias', 'classifier.weight']\n", | |
| "You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n" | |
| ] | |
| }, | |
| { | |
| "data": { | |
| "text/html": [ | |
| "\n", | |
| " <div>\n", | |
| " \n", | |
| " <progress value='1668' max='1712' style='width:300px; height:20px; vertical-align: middle;'></progress>\n", | |
| " [1668/1712 02:33 < 00:04, 10.88 it/s, Epoch 7.79/8]\n", | |
| " </div>\n", | |
| " <table border=\"1\" class=\"dataframe\">\n", | |
| " <thead>\n", | |
| " <tr style=\"text-align: left;\">\n", | |
| " <th>Step</th>\n", | |
| " <th>Training Loss</th>\n", | |
| " <th>Validation Loss</th>\n", | |
| " <th>F1 Personal Life Sa</th>\n", | |
| " <th>Precision Personal L</th>\n", | |
| " <th>Recall Personal Life</th>\n", | |
| " <th>F1 Personal Life No</th>\n", | |
| " <th>F1 Crypto Content</th>\n", | |
| " <th>Precision Crypto Con</th>\n", | |
| " <th>Recall Crypto Conten</th>\n", | |
| " <th>F1 App/bot Interacti</th>\n", | |
| " <th>Precision App/bot In</th>\n", | |
| " <th>Recall App/bot Inter</th>\n", | |
| " <th>F1 Not Enough Contex</th>\n", | |
| " <th>Precision Not Enough</th>\n", | |
| " <th>Recall Not Enough Co</th>\n", | |
| " <th>F1 Macro</th>\n", | |
| " <th>Accuracy</th>\n", | |
| " </tr>\n", | |
| " </thead>\n", | |
| " <tbody>\n", | |
| " <tr>\n", | |
| " <td>50</td>\n", | |
| " <td>1.595800</td>\n", | |
| " <td>1.591155</td>\n", | |
| " <td>0.030303</td>\n", | |
| " <td>0.434368</td>\n", | |
| " <td>0.469072</td>\n", | |
| " <td>0.451053</td>\n", | |
| " <td>0.577519</td>\n", | |
| " <td>0.462016</td>\n", | |
| " <td>0.770026</td>\n", | |
| " <td>0.000000</td>\n", | |
| " <td>0.000000</td>\n", | |
| " <td>0.000000</td>\n", | |
| " <td>0.085714</td>\n", | |
| " <td>0.562500</td>\n", | |
| " <td>0.046392</td>\n", | |
| " <td>0.228918</td>\n", | |
| " <td>0.408299</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>100</td>\n", | |
| " <td>1.525800</td>\n", | |
| " <td>1.460845</td>\n", | |
| " <td>0.119403</td>\n", | |
| " <td>0.538462</td>\n", | |
| " <td>0.306701</td>\n", | |
| " <td>0.390805</td>\n", | |
| " <td>0.682927</td>\n", | |
| " <td>0.620253</td>\n", | |
| " <td>0.759690</td>\n", | |
| " <td>0.215962</td>\n", | |
| " <td>0.460000</td>\n", | |
| " <td>0.141104</td>\n", | |
| " <td>0.448802</td>\n", | |
| " <td>0.388679</td>\n", | |
| " <td>0.530928</td>\n", | |
| " <td>0.371580</td>\n", | |
| " <td>0.460581</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>150</td>\n", | |
| " <td>1.369000</td>\n", | |
| " <td>1.313050</td>\n", | |
| " <td>0.084211</td>\n", | |
| " <td>0.661905</td>\n", | |
| " <td>0.358247</td>\n", | |
| " <td>0.464883</td>\n", | |
| " <td>0.609756</td>\n", | |
| " <td>0.743494</td>\n", | |
| " <td>0.516796</td>\n", | |
| " <td>0.543326</td>\n", | |
| " <td>0.439394</td>\n", | |
| " <td>0.711656</td>\n", | |
| " <td>0.438486</td>\n", | |
| " <td>0.315909</td>\n", | |
| " <td>0.716495</td>\n", | |
| " <td>0.428132</td>\n", | |
| " <td>0.496266</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>200</td>\n", | |
| " <td>1.214200</td>\n", | |
| " <td>1.148946</td>\n", | |
| " <td>0.370044</td>\n", | |
| " <td>0.796875</td>\n", | |
| " <td>0.657216</td>\n", | |
| " <td>0.720339</td>\n", | |
| " <td>0.758270</td>\n", | |
| " <td>0.746867</td>\n", | |
| " <td>0.770026</td>\n", | |
| " <td>0.580460</td>\n", | |
| " <td>0.545946</td>\n", | |
| " <td>0.619632</td>\n", | |
| " <td>0.516129</td>\n", | |
| " <td>0.598639</td>\n", | |
| " <td>0.453608</td>\n", | |
| " <td>0.589048</td>\n", | |
| " <td>0.650622</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>250</td>\n", | |
| " <td>1.010600</td>\n", | |
| " <td>1.004799</td>\n", | |
| " <td>0.453988</td>\n", | |
| " <td>0.841270</td>\n", | |
| " <td>0.546392</td>\n", | |
| " <td>0.662500</td>\n", | |
| " <td>0.734993</td>\n", | |
| " <td>0.847973</td>\n", | |
| " <td>0.648579</td>\n", | |
| " <td>0.592593</td>\n", | |
| " <td>0.495868</td>\n", | |
| " <td>0.736196</td>\n", | |
| " <td>0.535645</td>\n", | |
| " <td>0.427692</td>\n", | |
| " <td>0.716495</td>\n", | |
| " <td>0.595944</td>\n", | |
| " <td>0.629876</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>300</td>\n", | |
| " <td>0.931000</td>\n", | |
| " <td>0.896634</td>\n", | |
| " <td>0.450980</td>\n", | |
| " <td>0.762136</td>\n", | |
| " <td>0.809278</td>\n", | |
| " <td>0.785000</td>\n", | |
| " <td>0.824632</td>\n", | |
| " <td>0.855556</td>\n", | |
| " <td>0.795866</td>\n", | |
| " <td>0.653061</td>\n", | |
| " <td>0.622222</td>\n", | |
| " <td>0.687117</td>\n", | |
| " <td>0.575949</td>\n", | |
| " <td>0.745902</td>\n", | |
| " <td>0.469072</td>\n", | |
| " <td>0.657925</td>\n", | |
| " <td>0.722822</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>350</td>\n", | |
| " <td>0.879800</td>\n", | |
| " <td>0.839245</td>\n", | |
| " <td>0.490196</td>\n", | |
| " <td>0.800000</td>\n", | |
| " <td>0.752577</td>\n", | |
| " <td>0.775564</td>\n", | |
| " <td>0.800000</td>\n", | |
| " <td>0.838527</td>\n", | |
| " <td>0.764858</td>\n", | |
| " <td>0.655949</td>\n", | |
| " <td>0.689189</td>\n", | |
| " <td>0.625767</td>\n", | |
| " <td>0.621891</td>\n", | |
| " <td>0.600962</td>\n", | |
| " <td>0.644330</td>\n", | |
| " <td>0.668720</td>\n", | |
| " <td>0.717842</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>400</td>\n", | |
| " <td>0.751500</td>\n", | |
| " <td>0.808259</td>\n", | |
| " <td>0.494624</td>\n", | |
| " <td>0.816456</td>\n", | |
| " <td>0.664948</td>\n", | |
| " <td>0.732955</td>\n", | |
| " <td>0.814921</td>\n", | |
| " <td>0.916129</td>\n", | |
| " <td>0.733850</td>\n", | |
| " <td>0.672131</td>\n", | |
| " <td>0.605911</td>\n", | |
| " <td>0.754601</td>\n", | |
| " <td>0.621444</td>\n", | |
| " <td>0.539924</td>\n", | |
| " <td>0.731959</td>\n", | |
| " <td>0.667215</td>\n", | |
| " <td>0.707884</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>450</td>\n", | |
| " <td>0.580500</td>\n", | |
| " <td>0.793636</td>\n", | |
| " <td>0.523810</td>\n", | |
| " <td>0.781170</td>\n", | |
| " <td>0.791237</td>\n", | |
| " <td>0.786172</td>\n", | |
| " <td>0.848322</td>\n", | |
| " <td>0.882682</td>\n", | |
| " <td>0.816537</td>\n", | |
| " <td>0.674221</td>\n", | |
| " <td>0.626316</td>\n", | |
| " <td>0.730061</td>\n", | |
| " <td>0.639118</td>\n", | |
| " <td>0.686391</td>\n", | |
| " <td>0.597938</td>\n", | |
| " <td>0.694329</td>\n", | |
| " <td>0.748548</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>500</td>\n", | |
| " <td>0.619300</td>\n", | |
| " <td>0.752609</td>\n", | |
| " <td>0.507177</td>\n", | |
| " <td>0.852843</td>\n", | |
| " <td>0.657216</td>\n", | |
| " <td>0.742358</td>\n", | |
| " <td>0.838174</td>\n", | |
| " <td>0.901786</td>\n", | |
| " <td>0.782946</td>\n", | |
| " <td>0.702857</td>\n", | |
| " <td>0.657754</td>\n", | |
| " <td>0.754601</td>\n", | |
| " <td>0.657596</td>\n", | |
| " <td>0.587045</td>\n", | |
| " <td>0.747423</td>\n", | |
| " <td>0.689633</td>\n", | |
| " <td>0.729461</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>550</td>\n", | |
| " <td>0.604800</td>\n", | |
| " <td>0.790841</td>\n", | |
| " <td>0.532374</td>\n", | |
| " <td>0.787879</td>\n", | |
| " <td>0.804124</td>\n", | |
| " <td>0.795918</td>\n", | |
| " <td>0.866920</td>\n", | |
| " <td>0.850746</td>\n", | |
| " <td>0.883721</td>\n", | |
| " <td>0.699422</td>\n", | |
| " <td>0.661202</td>\n", | |
| " <td>0.742331</td>\n", | |
| " <td>0.659091</td>\n", | |
| " <td>0.734177</td>\n", | |
| " <td>0.597938</td>\n", | |
| " <td>0.710745</td>\n", | |
| " <td>0.770124</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>600</td>\n", | |
| " <td>0.560500</td>\n", | |
| " <td>0.717147</td>\n", | |
| " <td>0.544503</td>\n", | |
| " <td>0.858491</td>\n", | |
| " <td>0.703608</td>\n", | |
| " <td>0.773371</td>\n", | |
| " <td>0.865789</td>\n", | |
| " <td>0.882038</td>\n", | |
| " <td>0.850129</td>\n", | |
| " <td>0.727811</td>\n", | |
| " <td>0.702857</td>\n", | |
| " <td>0.754601</td>\n", | |
| " <td>0.674699</td>\n", | |
| " <td>0.633484</td>\n", | |
| " <td>0.721649</td>\n", | |
| " <td>0.717235</td>\n", | |
| " <td>0.760996</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>650</td>\n", | |
| " <td>0.492000</td>\n", | |
| " <td>0.872252</td>\n", | |
| " <td>0.480000</td>\n", | |
| " <td>0.821023</td>\n", | |
| " <td>0.744845</td>\n", | |
| " <td>0.781081</td>\n", | |
| " <td>0.875989</td>\n", | |
| " <td>0.894879</td>\n", | |
| " <td>0.857881</td>\n", | |
| " <td>0.715084</td>\n", | |
| " <td>0.656410</td>\n", | |
| " <td>0.785276</td>\n", | |
| " <td>0.675991</td>\n", | |
| " <td>0.617021</td>\n", | |
| " <td>0.747423</td>\n", | |
| " <td>0.705629</td>\n", | |
| " <td>0.766805</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>700</td>\n", | |
| " <td>0.396000</td>\n", | |
| " <td>0.870288</td>\n", | |
| " <td>0.503817</td>\n", | |
| " <td>0.843844</td>\n", | |
| " <td>0.724227</td>\n", | |
| " <td>0.779473</td>\n", | |
| " <td>0.858942</td>\n", | |
| " <td>0.837838</td>\n", | |
| " <td>0.881137</td>\n", | |
| " <td>0.709877</td>\n", | |
| " <td>0.714286</td>\n", | |
| " <td>0.705521</td>\n", | |
| " <td>0.663636</td>\n", | |
| " <td>0.593496</td>\n", | |
| " <td>0.752577</td>\n", | |
| " <td>0.703149</td>\n", | |
| " <td>0.760166</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>750</td>\n", | |
| " <td>0.343400</td>\n", | |
| " <td>0.845435</td>\n", | |
| " <td>0.519481</td>\n", | |
| " <td>0.818697</td>\n", | |
| " <td>0.744845</td>\n", | |
| " <td>0.780027</td>\n", | |
| " <td>0.840499</td>\n", | |
| " <td>0.907186</td>\n", | |
| " <td>0.782946</td>\n", | |
| " <td>0.711111</td>\n", | |
| " <td>0.649746</td>\n", | |
| " <td>0.785276</td>\n", | |
| " <td>0.691244</td>\n", | |
| " <td>0.625000</td>\n", | |
| " <td>0.773196</td>\n", | |
| " <td>0.708472</td>\n", | |
| " <td>0.755187</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>800</td>\n", | |
| " <td>0.401300</td>\n", | |
| " <td>0.907402</td>\n", | |
| " <td>0.518519</td>\n", | |
| " <td>0.804178</td>\n", | |
| " <td>0.793814</td>\n", | |
| " <td>0.798962</td>\n", | |
| " <td>0.865409</td>\n", | |
| " <td>0.843137</td>\n", | |
| " <td>0.888889</td>\n", | |
| " <td>0.713846</td>\n", | |
| " <td>0.716049</td>\n", | |
| " <td>0.711656</td>\n", | |
| " <td>0.692708</td>\n", | |
| " <td>0.700000</td>\n", | |
| " <td>0.685567</td>\n", | |
| " <td>0.717889</td>\n", | |
| " <td>0.776763</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>850</td>\n", | |
| " <td>0.360000</td>\n", | |
| " <td>0.906668</td>\n", | |
| " <td>0.500000</td>\n", | |
| " <td>0.795918</td>\n", | |
| " <td>0.804124</td>\n", | |
| " <td>0.800000</td>\n", | |
| " <td>0.869677</td>\n", | |
| " <td>0.868557</td>\n", | |
| " <td>0.870801</td>\n", | |
| " <td>0.703226</td>\n", | |
| " <td>0.741497</td>\n", | |
| " <td>0.668712</td>\n", | |
| " <td>0.685851</td>\n", | |
| " <td>0.641256</td>\n", | |
| " <td>0.737113</td>\n", | |
| " <td>0.711751</td>\n", | |
| " <td>0.774274</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>900</td>\n", | |
| " <td>0.209300</td>\n", | |
| " <td>0.899608</td>\n", | |
| " <td>0.571429</td>\n", | |
| " <td>0.835165</td>\n", | |
| " <td>0.783505</td>\n", | |
| " <td>0.808511</td>\n", | |
| " <td>0.864230</td>\n", | |
| " <td>0.873351</td>\n", | |
| " <td>0.855297</td>\n", | |
| " <td>0.709877</td>\n", | |
| " <td>0.714286</td>\n", | |
| " <td>0.705521</td>\n", | |
| " <td>0.705314</td>\n", | |
| " <td>0.663636</td>\n", | |
| " <td>0.752577</td>\n", | |
| " <td>0.731872</td>\n", | |
| " <td>0.780083</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>950</td>\n", | |
| " <td>0.226400</td>\n", | |
| " <td>0.973592</td>\n", | |
| " <td>0.523810</td>\n", | |
| " <td>0.799479</td>\n", | |
| " <td>0.791237</td>\n", | |
| " <td>0.795337</td>\n", | |
| " <td>0.855643</td>\n", | |
| " <td>0.869333</td>\n", | |
| " <td>0.842377</td>\n", | |
| " <td>0.707965</td>\n", | |
| " <td>0.681818</td>\n", | |
| " <td>0.736196</td>\n", | |
| " <td>0.676399</td>\n", | |
| " <td>0.640553</td>\n", | |
| " <td>0.716495</td>\n", | |
| " <td>0.711831</td>\n", | |
| " <td>0.767635</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>1000</td>\n", | |
| " <td>0.240300</td>\n", | |
| " <td>0.921706</td>\n", | |
| " <td>0.551724</td>\n", | |
| " <td>0.814324</td>\n", | |
| " <td>0.791237</td>\n", | |
| " <td>0.802614</td>\n", | |
| " <td>0.851406</td>\n", | |
| " <td>0.883333</td>\n", | |
| " <td>0.821705</td>\n", | |
| " <td>0.685714</td>\n", | |
| " <td>0.641711</td>\n", | |
| " <td>0.736196</td>\n", | |
| " <td>0.684864</td>\n", | |
| " <td>0.660287</td>\n", | |
| " <td>0.711340</td>\n", | |
| " <td>0.715264</td>\n", | |
| " <td>0.765975</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>1050</td>\n", | |
| " <td>0.217900</td>\n", | |
| " <td>1.024940</td>\n", | |
| " <td>0.481203</td>\n", | |
| " <td>0.773956</td>\n", | |
| " <td>0.811856</td>\n", | |
| " <td>0.792453</td>\n", | |
| " <td>0.869340</td>\n", | |
| " <td>0.870466</td>\n", | |
| " <td>0.868217</td>\n", | |
| " <td>0.711656</td>\n", | |
| " <td>0.711656</td>\n", | |
| " <td>0.711656</td>\n", | |
| " <td>0.678851</td>\n", | |
| " <td>0.687831</td>\n", | |
| " <td>0.670103</td>\n", | |
| " <td>0.706701</td>\n", | |
| " <td>0.770954</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>1100</td>\n", | |
| " <td>0.196000</td>\n", | |
| " <td>1.072047</td>\n", | |
| " <td>0.487805</td>\n", | |
| " <td>0.778846</td>\n", | |
| " <td>0.835052</td>\n", | |
| " <td>0.805970</td>\n", | |
| " <td>0.869001</td>\n", | |
| " <td>0.872396</td>\n", | |
| " <td>0.865633</td>\n", | |
| " <td>0.704819</td>\n", | |
| " <td>0.692308</td>\n", | |
| " <td>0.717791</td>\n", | |
| " <td>0.689474</td>\n", | |
| " <td>0.704301</td>\n", | |
| " <td>0.675258</td>\n", | |
| " <td>0.711414</td>\n", | |
| " <td>0.777593</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>1150</td>\n", | |
| " <td>0.113100</td>\n", | |
| " <td>1.009274</td>\n", | |
| " <td>0.565789</td>\n", | |
| " <td>0.794344</td>\n", | |
| " <td>0.796392</td>\n", | |
| " <td>0.795367</td>\n", | |
| " <td>0.861809</td>\n", | |
| " <td>0.838631</td>\n", | |
| " <td>0.886305</td>\n", | |
| " <td>0.689189</td>\n", | |
| " <td>0.766917</td>\n", | |
| " <td>0.625767</td>\n", | |
| " <td>0.683805</td>\n", | |
| " <td>0.682051</td>\n", | |
| " <td>0.685567</td>\n", | |
| " <td>0.719192</td>\n", | |
| " <td>0.771784</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>1200</td>\n", | |
| " <td>0.160200</td>\n", | |
| " <td>1.015507</td>\n", | |
| " <td>0.567568</td>\n", | |
| " <td>0.782082</td>\n", | |
| " <td>0.832474</td>\n", | |
| " <td>0.806492</td>\n", | |
| " <td>0.867280</td>\n", | |
| " <td>0.882353</td>\n", | |
| " <td>0.852713</td>\n", | |
| " <td>0.727273</td>\n", | |
| " <td>0.696629</td>\n", | |
| " <td>0.760736</td>\n", | |
| " <td>0.674095</td>\n", | |
| " <td>0.733333</td>\n", | |
| " <td>0.623711</td>\n", | |
| " <td>0.728541</td>\n", | |
| " <td>0.780083</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>1250</td>\n", | |
| " <td>0.117900</td>\n", | |
| " <td>1.075018</td>\n", | |
| " <td>0.514706</td>\n", | |
| " <td>0.802667</td>\n", | |
| " <td>0.775773</td>\n", | |
| " <td>0.788991</td>\n", | |
| " <td>0.859671</td>\n", | |
| " <td>0.841584</td>\n", | |
| " <td>0.878553</td>\n", | |
| " <td>0.701538</td>\n", | |
| " <td>0.703704</td>\n", | |
| " <td>0.699387</td>\n", | |
| " <td>0.678481</td>\n", | |
| " <td>0.666667</td>\n", | |
| " <td>0.690722</td>\n", | |
| " <td>0.708677</td>\n", | |
| " <td>0.766805</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>1300</td>\n", | |
| " <td>0.103900</td>\n", | |
| " <td>1.106329</td>\n", | |
| " <td>0.516129</td>\n", | |
| " <td>0.791774</td>\n", | |
| " <td>0.793814</td>\n", | |
| " <td>0.792793</td>\n", | |
| " <td>0.871595</td>\n", | |
| " <td>0.875000</td>\n", | |
| " <td>0.868217</td>\n", | |
| " <td>0.703226</td>\n", | |
| " <td>0.741497</td>\n", | |
| " <td>0.668712</td>\n", | |
| " <td>0.668224</td>\n", | |
| " <td>0.611111</td>\n", | |
| " <td>0.737113</td>\n", | |
| " <td>0.710393</td>\n", | |
| " <td>0.770124</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>1350</td>\n", | |
| " <td>0.130300</td>\n", | |
| " <td>1.165310</td>\n", | |
| " <td>0.504348</td>\n", | |
| " <td>0.788413</td>\n", | |
| " <td>0.806701</td>\n", | |
| " <td>0.797452</td>\n", | |
| " <td>0.868895</td>\n", | |
| " <td>0.864450</td>\n", | |
| " <td>0.873385</td>\n", | |
| " <td>0.696486</td>\n", | |
| " <td>0.726667</td>\n", | |
| " <td>0.668712</td>\n", | |
| " <td>0.668258</td>\n", | |
| " <td>0.622222</td>\n", | |
| " <td>0.721649</td>\n", | |
| " <td>0.707088</td>\n", | |
| " <td>0.770954</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>1400</td>\n", | |
| " <td>0.113800</td>\n", | |
| " <td>1.137059</td>\n", | |
| " <td>0.515625</td>\n", | |
| " <td>0.776442</td>\n", | |
| " <td>0.832474</td>\n", | |
| " <td>0.803483</td>\n", | |
| " <td>0.863402</td>\n", | |
| " <td>0.861183</td>\n", | |
| " <td>0.865633</td>\n", | |
| " <td>0.723404</td>\n", | |
| " <td>0.716867</td>\n", | |
| " <td>0.730061</td>\n", | |
| " <td>0.664879</td>\n", | |
| " <td>0.692737</td>\n", | |
| " <td>0.639175</td>\n", | |
| " <td>0.714159</td>\n", | |
| " <td>0.775104</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>1450</td>\n", | |
| " <td>0.096600</td>\n", | |
| " <td>1.125042</td>\n", | |
| " <td>0.511628</td>\n", | |
| " <td>0.785000</td>\n", | |
| " <td>0.809278</td>\n", | |
| " <td>0.796954</td>\n", | |
| " <td>0.868895</td>\n", | |
| " <td>0.864450</td>\n", | |
| " <td>0.873385</td>\n", | |
| " <td>0.719512</td>\n", | |
| " <td>0.715152</td>\n", | |
| " <td>0.723926</td>\n", | |
| " <td>0.671835</td>\n", | |
| " <td>0.673575</td>\n", | |
| " <td>0.670103</td>\n", | |
| " <td>0.713765</td>\n", | |
| " <td>0.774274</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>1500</td>\n", | |
| " <td>0.157300</td>\n", | |
| " <td>1.149831</td>\n", | |
| " <td>0.516129</td>\n", | |
| " <td>0.787500</td>\n", | |
| " <td>0.811856</td>\n", | |
| " <td>0.799492</td>\n", | |
| " <td>0.870013</td>\n", | |
| " <td>0.866667</td>\n", | |
| " <td>0.873385</td>\n", | |
| " <td>0.721212</td>\n", | |
| " <td>0.712575</td>\n", | |
| " <td>0.730061</td>\n", | |
| " <td>0.670077</td>\n", | |
| " <td>0.664975</td>\n", | |
| " <td>0.675258</td>\n", | |
| " <td>0.715385</td>\n", | |
| " <td>0.775934</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>1550</td>\n", | |
| " <td>0.112300</td>\n", | |
| " <td>1.122491</td>\n", | |
| " <td>0.515625</td>\n", | |
| " <td>0.780247</td>\n", | |
| " <td>0.814433</td>\n", | |
| " <td>0.796974</td>\n", | |
| " <td>0.868557</td>\n", | |
| " <td>0.866324</td>\n", | |
| " <td>0.870801</td>\n", | |
| " <td>0.717325</td>\n", | |
| " <td>0.710843</td>\n", | |
| " <td>0.723926</td>\n", | |
| " <td>0.661458</td>\n", | |
| " <td>0.668421</td>\n", | |
| " <td>0.654639</td>\n", | |
| " <td>0.711988</td>\n", | |
| " <td>0.772614</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>1600</td>\n", | |
| " <td>0.108300</td>\n", | |
| " <td>1.167189</td>\n", | |
| " <td>0.508197</td>\n", | |
| " <td>0.779412</td>\n", | |
| " <td>0.819588</td>\n", | |
| " <td>0.798995</td>\n", | |
| " <td>0.867779</td>\n", | |
| " <td>0.862245</td>\n", | |
| " <td>0.873385</td>\n", | |
| " <td>0.716049</td>\n", | |
| " <td>0.720497</td>\n", | |
| " <td>0.711656</td>\n", | |
| " <td>0.668380</td>\n", | |
| " <td>0.666667</td>\n", | |
| " <td>0.670103</td>\n", | |
| " <td>0.711880</td>\n", | |
| " <td>0.774274</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <td>1650</td>\n", | |
| " <td>0.069700</td>\n", | |
| " <td>1.164802</td>\n", | |
| " <td>0.528000</td>\n", | |
| " <td>0.782716</td>\n", | |
| " <td>0.817010</td>\n", | |
| " <td>0.799496</td>\n", | |
| " <td>0.867779</td>\n", | |
| " <td>0.862245</td>\n", | |
| " <td>0.873385</td>\n", | |
| " <td>0.716049</td>\n", | |
| " <td>0.720497</td>\n", | |
| " <td>0.711656</td>\n", | |
| " <td>0.668380</td>\n", | |
| " <td>0.666667</td>\n", | |
| " <td>0.670103</td>\n", | |
| " <td>0.715941</td>\n", | |
| " <td>0.775104</td>\n", | |
| " </tr>\n", | |
| " </tbody>\n", | |
| "</table><p>" | |
| ], | |
| "text/plain": [ | |
| "<IPython.core.display.HTML object>" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "output_type": "display_data" | |
| } | |
| ], | |
| "source": [ | |
| "# Train the model with BERT base\n", | |
| "model, tokenizer = train_classifier(\n", | |
| " texts,\n", | |
| " labels,\n", | |
| " label_map,\n", | |
| " model_name='bert-base-uncased',\n", | |
| " batch_size=32,\n", | |
| " num_epochs=8,\n", | |
| " learning_rate=2e-5,\n", | |
| " weight_decay=0.01,\n", | |
| " warmup_ratio=0.2\n", | |
| ")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 12, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Text: GM everyone! Hope you have a great day\n", | |
| "Prediction: Personal Life - Not Salient (confidence: 0.974)\n", | |
| "\n", | |
| "Text: Just bought more ETH, bullish on the merge\n", | |
| "Prediction: Crypto Content (confidence: 0.988)\n", | |
| "\n", | |
| "Text: This bot is spamming my feed with nonsense\n", | |
| "Prediction: App/Bot Interactions (confidence: 0.971)\n", | |
| "\n", | |
| "Text: I've been reflecting on my journey as a developer and realized that the best projects come from solving real problems I face daily\n", | |
| "Prediction: Personal Life - Salient (confidence: 0.860)\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "# Batch prediction function\n", | |
| "def predict_batch(texts, model, tokenizer, device='cuda' if torch.cuda.is_available() else 'cpu'):\n", | |
| " \"\"\"\n", | |
| " Efficient batch prediction for single-label classification\n", | |
| " \"\"\"\n", | |
| " model.to(device)\n", | |
| " model.eval()\n", | |
| " \n", | |
| " inputs = tokenizer(\n", | |
| " texts,\n", | |
| " padding=True,\n", | |
| " truncation=True,\n", | |
| " max_length=512,\n", | |
| " return_tensors=\"pt\"\n", | |
| " ).to(device)\n", | |
| " \n", | |
| " with torch.no_grad():\n", | |
| " outputs = model(**inputs)\n", | |
| " # Apply softmax for single-label\n", | |
| " predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)\n", | |
| " predicted_labels = torch.argmax(predictions, dim=-1)\n", | |
| " \n", | |
| " return predicted_labels.cpu().numpy(), predictions.cpu().numpy()\n", | |
| "\n", | |
| "# Test on some examples\n", | |
| "test_texts = [\n", | |
| " \"GM everyone! Hope you have a great day\",\n", | |
| " \"Just bought more ETH, bullish on the merge\",\n", | |
| " \"This bot is spamming my feed with nonsense\",\n", | |
| " \"I've been reflecting on my journey as a developer and realized that the best projects come from solving real problems I face daily\"\n", | |
| "]\n", | |
| "\n", | |
| "pred_labels, pred_probs = predict_batch(test_texts, model, tokenizer)\n", | |
| "\n", | |
| "# Get reverse label mapping\n", | |
| "idx_to_label = {v: k for k, v in label_map.items()}\n", | |
| "\n", | |
| "for i, text in enumerate(test_texts):\n", | |
| " print(f\"\\nText: {text}\")\n", | |
| " predicted_class = idx_to_label[pred_labels[i]]\n", | |
| " confidence = pred_probs[i][pred_labels[i]]\n", | |
| " print(f\"Prediction: {predicted_class} (confidence: {confidence:.3f})\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 19, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Saving model to ./farcaster-classifier-final...\n", | |
| "✓ Model saved successfully!\n", | |
| "✓ Tokenizer saved successfully!\n", | |
| "✓ Label map saved successfully!\n", | |
| "\n", | |
| "Model location: ./farcaster-classifier-final/\n", | |
| "Files saved:\n", | |
| " - config.json\n", | |
| " - model.safetensors (or pytorch_model.bin)\n", | |
| " - tokenizer files\n", | |
| " - label_map.json\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "# Save the final model and configuration\n", | |
| "print(\"Saving model to ./farcaster-classifier-final...\")\n", | |
| "\n", | |
| "# Save model and tokenizer\n", | |
| "model.save_pretrained(\"./farcaster-classifier-final\")\n", | |
| "tokenizer.save_pretrained(\"./farcaster-classifier-final\")\n", | |
| "\n", | |
| "# Save label map\n", | |
| "with open(\"./farcaster-classifier-final/label_map.json\", 'w') as f:\n", | |
| " json.dump(label_map, f, indent=2)\n", | |
| "\n", | |
| "print(\"✓ Model saved successfully!\")\n", | |
| "print(\"✓ Tokenizer saved successfully!\")\n", | |
| "print(\"✓ Label map saved successfully!\")\n", | |
| "print(f\"\\nModel location: ./farcaster-classifier-final/\")\n", | |
| "print(f\"Files saved:\")\n", | |
| "print(f\" - config.json\")\n", | |
| "print(f\" - model.safetensors (or pytorch_model.bin)\")\n", | |
| "print(f\" - tokenizer files\")\n", | |
| "print(f\" - label_map.json\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 15, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Random Samples from Each Category with Model Predictions\n", | |
| "================================================================================\n", | |
| "\n", | |
| "\n", | |
| "================================================================================\n", | |
| "CATEGORY: Personal Life - Salient\n", | |
| "================================================================================\n", | |
| "\n", | |
| "1. ✓ Text:\n", | |
| " many such cases\n", | |
| "\n", | |
| "born and raised in LA\n", | |
| "\n", | |
| "26 years later have still never developed the muscle to check the weather daily\n", | |
| "\n", | |
| " Predicted: Personal Life - Salient (confidence: 0.991)\n", | |
| "\n", | |
| "\n", | |
| "2. ✓ Text:\n", | |
| " facebook just reminded me that 4 years ago i came 3rd at the diamond league\n", | |
| "\n", | |
| "1st and 2nd place in the photo are now olympic medalists!\n", | |
| "\n", | |
| "and they are both 6+ years older than me \n", | |
| "\n", | |
| "by the time LA 2028 c...\n", | |
| "\n", | |
| " Predicted: Personal Life - Salient (confidence: 0.991)\n", | |
| "\n", | |
| "\n", | |
| "3. ✓ Text:\n", | |
| " The Butterfly Nebula from Hubble. Stars can make beautiful patterns as they age -- sometimes similar to flowers or insects. NGC 6302, the Butterfly Nebula, is a notable example.\n", | |
| "\n", | |
| " Predicted: Personal Life - Salient (confidence: 0.983)\n", | |
| "\n", | |
| "\n", | |
| "4. ✓ Text:\n", | |
| " Và khi điều quan trọng nhất còn chưa được thực hiện, các mắt xích dậm chân tại chỗ, tổ chức chưa thể tiến về phía trước, sự bận rộn với những điều nhỏ nhặt đó cũng chẳng khác gì 1 sự lười biếng được v...\n", | |
| "\n", | |
| " Predicted: Personal Life - Salient (confidence: 0.988)\n", | |
| "\n", | |
| "\n", | |
| "5. ✓ Text:\n", | |
| " Republican love to lied. I sold insurance for years, and my wife used to works for Medicaid—you need proof of legal status to obtain insurance. The only type of insurance someone can get without that ...\n", | |
| "\n", | |
| " Predicted: Personal Life - Salient (confidence: 0.936)\n", | |
| "\n", | |
| "\n", | |
| "\n", | |
| "================================================================================\n", | |
| "CATEGORY: Personal Life - Not Salient\n", | |
| "================================================================================\n", | |
| "\n", | |
| "1. ✓ Text:\n", | |
| " “naomi can u make a video about ai”\n", | |
| "\n", | |
| "me: yes i can i am quite the expert \n", | |
| "\n", | |
| "also me:\n", | |
| "\n", | |
| " Predicted: Personal Life - Not Salient (confidence: 0.574)\n", | |
| "\n", | |
| "\n", | |
| "2. ✗ Text:\n", | |
| " すごい計画👏子ども達も体力あるのね👀❤️素晴らしい👏\n", | |
| "\n", | |
| " Predicted: Personal Life - Salient (confidence: 0.539)\n", | |
| " Top 3 predictions:\n", | |
| " - Personal Life - Salient: 0.539\n", | |
| " - Personal Life - Not Salient: 0.397\n", | |
| " - App/Bot Interactions: 0.023\n", | |
| "\n", | |
| "\n", | |
| "3. ✗ Text:\n", | |
| " ITAP\n", | |
| "I took this photo \n", | |
| "What do you think?\n", | |
| "\n", | |
| " Predicted: Not Enough Context To Tell (confidence: 0.879)\n", | |
| " Top 3 predictions:\n", | |
| " - Not Enough Context To Tell: 0.879\n", | |
| " - Personal Life - Not Salient: 0.104\n", | |
| " - App/Bot Interactions: 0.009\n", | |
| "\n", | |
| "\n", | |
| "4. ✓ Text:\n", | |
| " Happy tree Tuesday friend\n", | |
| "\n", | |
| " Predicted: Personal Life - Not Salient (confidence: 0.982)\n", | |
| "\n", | |
| "\n", | |
| "5. ✓ Text:\n", | |
| " Good morning, dear friend.\n", | |
| "\n", | |
| " Predicted: Personal Life - Not Salient (confidence: 0.984)\n", | |
| "\n", | |
| "\n", | |
| "\n", | |
| "================================================================================\n", | |
| "CATEGORY: Crypto Content\n", | |
| "================================================================================\n", | |
| "\n", | |
| "1. ✓ Text:\n", | |
| " so glad you like it mate!! 👍 \n", | |
| "https://www.empirebuilder.world/empire/0x0c22b5e951683f6fadebdcb931cdf801d2aaa70e\n", | |
| "\n", | |
| " Predicted: Crypto Content (confidence: 0.907)\n", | |
| "\n", | |
| "\n", | |
| "2. ✗ Text:\n", | |
| " Help me get Fuel by liking this cast!\n", | |
| "5 Likes = 1 Fuel🔋\n", | |
| "Support my mech battles in Wreck League Versus 🤖 by \n", | |
| "\n", | |
| " Predicted: App/Bot Interactions (confidence: 0.982)\n", | |
| " Top 3 predictions:\n", | |
| " - App/Bot Interactions: 0.982\n", | |
| " - Crypto Content: 0.012\n", | |
| " - Personal Life - Salient: 0.002\n", | |
| "\n", | |
| "\n", | |
| "3. ✓ Text:\n", | |
| " does token trade history in the app help?\n", | |
| "\n", | |
| "https://warpcast.com/rainbow/0x3c1185b9\n", | |
| "\n", | |
| " Predicted: Crypto Content (confidence: 0.930)\n", | |
| "\n", | |
| "\n", | |
| "4. ✓ Text:\n", | |
| " Cryptocurrency derivatives amplify trading opportunities, catering to experienced traders exclusively.\n", | |
| "\n", | |
| " Predicted: Crypto Content (confidence: 0.989)\n", | |
| "\n", | |
| "\n", | |
| "5. ✓ Text:\n", | |
| " Exit scams vanish with funds, betraying community trust completely.\n", | |
| "\n", | |
| " Predicted: Crypto Content (confidence: 0.989)\n", | |
| "\n", | |
| "\n", | |
| "\n", | |
| "================================================================================\n", | |
| "CATEGORY: App/Bot Interactions\n", | |
| "================================================================================\n", | |
| "\n", | |
| "1. ✓ Text:\n", | |
| " \\#Privasea\n", | |
| "\n", | |
| "Privasea Update : All tasks will be removed tomorrow March 31st🔶\n", | |
| "\n", | |
| "⭕️ Complete all tasks in ImHuman app ➡️ privasea.ai/download-app\n", | |
| "\n", | |
| "⏺ ref code ➡️ ZwkQxkV 👈\n", | |
| "\n", | |
| "📌You have also time to complete...\n", | |
| "\n", | |
| " Predicted: App/Bot Interactions (confidence: 0.960)\n", | |
| "\n", | |
| "\n", | |
| "2. ✓ Text:\n", | |
| " I currently rank #1000+ on The Leaderboard. Where do you rank?\n", | |
| "\n", | |
| " Predicted: App/Bot Interactions (confidence: 0.988)\n", | |
| "\n", | |
| "\n", | |
| "3. ✓ Text:\n", | |
| " 🌅 Just sent my daily GM on RISE!\n", | |
| "\n", | |
| "Join me in this amazing journey and discover a new way to connect with the community.\n", | |
| "\n", | |
| "Start your journey here: https://onchaingm.com?ref=0xB68B3e0BfA96fd90c1D889e6F8...\n", | |
| "\n", | |
| " Predicted: App/Bot Interactions (confidence: 0.977)\n", | |
| "\n", | |
| "\n", | |
| "4. ✓ Text:\n", | |
| " I currently rank #1000+ on The Leaderboard. Where do you rank?\n", | |
| "\n", | |
| " Predicted: App/Bot Interactions (confidence: 0.988)\n", | |
| "\n", | |
| "\n", | |
| "5. ✓ Text:\n", | |
| " has blocked \n", | |
| " has blocked \n", | |
| " has sneakily blocked \n", | |
| "\n", | |
| "\n", | |
| " Predicted: App/Bot Interactions (confidence: 0.813)\n", | |
| "\n", | |
| "\n", | |
| "\n", | |
| "================================================================================\n", | |
| "CATEGORY: Not Enough Context To Tell\n", | |
| "================================================================================\n", | |
| "\n", | |
| "1. ✓ Text:\n", | |
| " 11 👏 Tally-ho! Spectacular!\n", | |
| "\n", | |
| " Predicted: Not Enough Context To Tell (confidence: 0.700)\n", | |
| "\n", | |
| "\n", | |
| "2. ✓ Text:\n", | |
| " I couldn't have said it better myself. Well done!\n", | |
| "\n", | |
| " Predicted: Not Enough Context To Tell (confidence: 0.972)\n", | |
| "\n", | |
| "\n", | |
| "3. ✓ Text:\n", | |
| " This is exactly how I feel. Great perspective!\n", | |
| "\n", | |
| " Predicted: Not Enough Context To Tell (confidence: 0.977)\n", | |
| "\n", | |
| "\n", | |
| "4. ✓ Text:\n", | |
| " Yes! This is such an important topic, and you nailed it.\n", | |
| "\n", | |
| " Predicted: Not Enough Context To Tell (confidence: 0.966)\n", | |
| "\n", | |
| "\n", | |
| "5. ✓ Text:\n", | |
| " That's holds a deep meaning\n", | |
| "\n", | |
| " Predicted: Not Enough Context To Tell (confidence: 0.906)\n", | |
| "\n", | |
| "\n", | |
| "================================================================================\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "# Show 5 random samples from each category with predictions\n", | |
| "import random\n", | |
| "\n", | |
| "print(\"Random Samples from Each Category with Model Predictions\")\n", | |
| "print(\"=\"*80)\n", | |
| "\n", | |
| "# Get reverse label mapping\n", | |
| "idx_to_label = {v: k for k, v in label_map.items()}\n", | |
| "\n", | |
| "# For each category\n", | |
| "for category_name in sorted(label_map.keys(), key=lambda x: label_map[x]):\n", | |
| " category_idx = label_map[category_name]\n", | |
| " \n", | |
| " print(f\"\\n\\n{'='*80}\")\n", | |
| " print(f\"CATEGORY: {category_name}\")\n", | |
| " print('='*80)\n", | |
| " \n", | |
| " # Get all indices for this category\n", | |
| " category_indices = [i for i, l in enumerate(labels) if l == category_idx]\n", | |
| " \n", | |
| " # Sample 5 random examples\n", | |
| " sample_indices = random.sample(category_indices, min(5, len(category_indices)))\n", | |
| " sample_texts = [texts[i] for i in sample_indices]\n", | |
| " \n", | |
| " # Get predictions\n", | |
| " pred_labels, pred_probs = predict_batch(sample_texts, model, tokenizer)\n", | |
| " \n", | |
| " # Display each sample\n", | |
| " for i, (text, pred_label, probs) in enumerate(zip(sample_texts, pred_labels, pred_probs), 1):\n", | |
| " predicted_category = idx_to_label[pred_label]\n", | |
| " confidence = probs[pred_label]\n", | |
| " is_correct = \"✓\" if pred_label == category_idx else \"✗\"\n", | |
| " \n", | |
| " print(f\"\\n{i}. {is_correct} Text:\")\n", | |
| " print(f\" {text[:200]}{'...' if len(text) > 200 else ''}\")\n", | |
| " print(f\"\\n Predicted: {predicted_category} (confidence: {confidence:.3f})\")\n", | |
| " \n", | |
| " # Show top 3 predictions if wrong\n", | |
| " if pred_label != category_idx:\n", | |
| " top3_indices = np.argsort(probs)[-3:][::-1]\n", | |
| " print(f\" Top 3 predictions:\")\n", | |
| " for idx in top3_indices:\n", | |
| " print(f\" - {idx_to_label[idx]}: {probs[idx]:.3f}\")\n", | |
| " print()\n", | |
| "\n", | |
| "print(\"\\n\" + \"=\"*80)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 18, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "image/png": "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LF25TtlDeIbxk+zI4T/Vq2dX6+uZ0x78m9KkeSvrKsfr166WIUMNG7GCPp4svr62q8A7OTmpSJEiGjNmjBo2TPmzmC1GSlY5Qrr55ZdfVK9ePZ07d07ZstkuQmSxWLRs2TK1atXqsccVeZ+PBxIXy48OJCGoyhtmh4AM6twvM8wOARmUsxNVLiQui6djzFjou/zRa+uklxktmZpOpdZkUVFRunLlikaNGqW2bdsmSGgBAAAA4Em0b98+622aJUqUUNmyZVM1Dkmtyb766iv16NFDZcqU0WeffWZ2OAAAAACQri5fvqz27dtr8+bN8vPzk/Rgcd06depoyZIlCgwMTNF4PFLJZF27dlVsbKz27dunHDkSv3fGMAxTph4DAAAAeDQni3mbI+rTp49u376tP//8U9evX9f169d16NAh3bp1S2+8kfLbmKjUAgAAAAAemzVr1mjDhg3WR5RKUvHixTV79uxULRRlV1J74cIFbd26VZcvX1abNm2UM2dOxcbG6ubNm/L19WUpegAAAABPPB7pkzJxcXFycXFJsN/FxUVxcXEpHi9V048Nw1D//v2VL18+derUSf3799dff/0lSYqIiFDevHn1/vvvp2ZoAAAAAMATrG7dunrzzTd18eJF674LFy6oX79+qlevXorHS1VSO2XKFM2cOVMDBw7U+vXr9e+nAvn6+qp169b67rvvUjM0AAAAADgU7qlNmQ8++EC3bt1S3rx5VaBAARUoUED58uXTrVu3UlUcTdX0408++UQvvfSSxo8fr2vXriVoL1WqlH766afUDA0AAAAAeILlypVLv/32mzZs2KCjRx8847dYsWKqX79+qsZLVaX23LlzqlatWpLtXl5eunXrVqoCAgAAAAA8eX7++WcVL15ct27dksViUYMGDdSnTx/16dNHFStWVIkSJfTLL7+keNxUJbVBQUE6d+5cku379u1T7ty5UzM0AAAAADgUi8W8zZHMmDFDPXv2lI+PT4I2X19fvfLKK5o+fXqKx01VUtu6dWvNnTtXf//9t3Vf/Ipf69at08KFC9W2bdvUDA0AAAAAeAIdOHBAjRo1SrK9YcOG2rdvX4rHTVVSO3r0aGXPnl1lypTRSy+9JIvFokmTJqlGjRpq3LixSpUqpaFDh6ZmaAAAAABwKE4Wi2mbI7l06VKij/KJlylTJl25ciXF46YqqfX1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YxERERERkddNE0VZlyq1IiIiIiIikmypUisiIiIiIvIvmDRVlFWpUisiIiIiIiLJliq1IiIiIiIi/4YKtValSq2IiIiIiIgkW0pqRUREREREJNlS92MREREREZF/Qb2PrUuVWhEREREREUm2VKkVERERERH5F0wq1VqVKrUiIiIiIiKSbCmpFRERERERkWRL3Y9FRERERET+BZOmirIqVWpFREREREQk2VKlVkRERERE5N9QodaqVKkVERERERGRZEuVWhERERERkX9BhVrrUqVWREREREREki0ltSIiIiIiIpJsqfuxiIiIiIjIv2BS/2OrUqVWREREREREki1VakVERERERP4Fk6aKsipVakVERERERCTZUlIrIiIiIiIiyZa6H4uIiIiIiPwLmijKulSpFRERERERkWRLSa2IiIiIiIgkW0pqRUREREREJNlSUisiIiIiIiLJliaKEhERERER+Rc0UZR1qVIrIiIiIiIiyZYqtSIiIiIiIv+CCZVqrUmVWhEREREREUm2VKkVERERERH5FzSm1rpUqRUREREREZFkS0mtiIiIiIiIJFvqfiwiIiIiIvIvqPexdalSKyIiIiIiIsmWKrUiIiIiIiL/hkq1VqVKrYiIiIiIiCRbSmpFREREREQk2VL3YxERERERkX/BpP7HVqVKrYiIiIiIiCRbqtSKiIiIiIj8CyYVaq1KlVoRERERERFJtlSpFRERERER+RdUqLUuVWpFREREREQk2VJSKyIiIiIiIsmWuh+LiIiIiIj8G+p/bFWq1IqIiIiIiEiypUqtiIiIiIjIv2BSqdaqVKkVERERERGRZEtJrYiIiIiIiCRb6n4sIiIiIiLyL5jU+9iqVKkVERERERGRZMtkGIZh7SBEkoPo6GjGjRvH4MGDcXR0tHY48gZR25CXUduQl1HbkJdR2xD555TUivxNDx48wNXVlYiICNKnT2/tcOQNorYhL6O2IS+jtiEvo7Yh8s+p+7GIiIiIiIgkW0pqRUREREREJNlSUisiIiIiIiLJlpJakb/J0dGRYcOGadIGeY7ahryM2oa8jNqGvIzahsg/p4miREREREREJNlSpVZERERERESSLSW1IiIiIiIikmwpqRUREREREZFkS0mtiIiIiIiIJFtKakVERERERCTZUlIrIiIiIiIiyZaSWkn1tKqVvMif24XaiYiI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PRiGQbZs2Th+/Di9e/dmzpw5tGnTBkhchY2NjeXMmTO89957VoxeRERSkps3bwLg7u5u5UhE5E2ipFZEXiohoT1x4gT169fHwcGBixcv4uPjQ58+fXj48CF9+vRh/vz5fPLJJ5b3xcTE0L17d+bOncudO3dInz69KrQiIiIi8lqo+7GIvNCzCW2ZMmVo1KgRW7duZdWqVXh4eDBjxgwqV67MkCFDaNOmDYsWLQIgOjqaPn36sHTpUvbt24erq6sSWhERERF5bTRlnIi8kI2NDb/99htVqlShdu3ajB8/HgBPT09CQ0Pp27cvTk5ODBs2DJPJROvWrYmPj+f8+fPMnTuX3bt3U6xYMSt/ChERERFJ6ZTUishLxcfHkydPHqKjo9m9ezfly5cHIE+ePDg6OhIdHY2dnR0DBw7E1taWdu3aAXDo0CEltCIiIiKSJDSmVkT+UnBwMD169MBsNuPv70/OnDnJmzcvbdu2tVRvASIiIli0aBFVqlShQIECVoxYRERERFITJbUi8j8FBwfTs2dPoqKiOHHiBK1bt2by5MnA02qura0t8Mc4XBERERGRpKJvnyLyP7311ltMmTIFW1tb0qdPT/369S37nk1ildCKiIiISFJTpVZE/raLFy/y2WefYRgGQ4cOpVy5ctYOSURERERSOZVVRORv8/b2JiAgAHt7e/r168e+ffusHZKIiIiIpHJKakXkH3nrrbeYOHEiOXLkwNPT09rhiIiIiEgqp+7HIvL/EhMTg4ODg7XDEBEREZFUTkmtiIiIiIiIJFvqfiwiIiIiIiLJlpJaERERERERSbaU1IqIiIiIiEiypaRWREREREREki0ltSIiIiIiIpJsKakVERERERGRZEtJrYiIpApeXl60adPG8nrHjh2YTCZ27NhhtZj+7M8xJoVKlSrx7rvvvtJzWuNziIhI6qWkVkREXrv58+djMpks/9KkSYOPjw/du3fn5s2b1g7vH1m/fj3Dhw+3agwmk4nu3btbNQYREZE3hZ21AxARkdRj5MiR5MmThydPnrB7925mzJjB+vXrOXXqFM7Ozkkai6+vL48fP8bBweEfvW/9+vVMmzbN6omtiIiIPKWkVkREkkytWrUoUaIEAB06dCBz5sx88803rFmzho8//viF73n06BFp06Z95bHY2NiQJk2aV35eERERSVrqfiwiIlbzwQcf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", | |
| "text/plain": [ | |
| "<Figure size 1000x800 with 2 Axes>" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "output_type": "display_data" | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Normalized Confusion Matrix (row percentages):\n", | |
| "============================================================\n", | |
| "\n", | |
| "PL-Salient:\n", | |
| " -> PL-Salient: 60.3%\n", | |
| " -> PL-NotSalient: 24.7%\n", | |
| " -> Crypto: 2.7%\n", | |
| " -> App/Bot: 2.7%\n", | |
| " -> No Context: 9.6%\n", | |
| "\n", | |
| "PL-NotSalient:\n", | |
| " -> PL-Salient: 5.9%\n", | |
| " -> PL-NotSalient: 78.4%\n", | |
| " -> Crypto: 1.3%\n", | |
| " -> App/Bot: 3.9%\n", | |
| " -> No Context: 10.6%\n", | |
| "\n", | |
| "Crypto:\n", | |
| " -> PL-Salient: 1.8%\n", | |
| " -> PL-NotSalient: 2.6%\n", | |
| " -> Crypto: 85.5%\n", | |
| " -> App/Bot: 5.7%\n", | |
| " -> No Context: 4.4%\n", | |
| "\n", | |
| "App/Bot:\n", | |
| " -> PL-Salient: 2.5%\n", | |
| " -> PL-NotSalient: 3.1%\n", | |
| " -> Crypto: 18.4%\n", | |
| " -> App/Bot: 70.6%\n", | |
| " -> No Context: 5.5%\n", | |
| "\n", | |
| "No Context:\n", | |
| " -> PL-Salient: 1.5%\n", | |
| " -> PL-NotSalient: 13.9%\n", | |
| " -> Crypto: 5.7%\n", | |
| " -> App/Bot: 3.6%\n", | |
| " -> No Context: 75.3%\n", | |
| "\n", | |
| "============================================================\n", | |
| "Per-Class Accuracy:\n", | |
| "============================================================\n", | |
| "PL-Salient: 60.3%\n", | |
| "PL-NotSalient: 78.4%\n", | |
| "Crypto: 85.5%\n", | |
| "App/Bot: 70.6%\n", | |
| "No Context: 75.3%\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "# Generate confusion matrix on validation set\n", | |
| "import matplotlib.pyplot as plt\n", | |
| "import seaborn as sns\n", | |
| "from sklearn.metrics import confusion_matrix\n", | |
| "\n", | |
| "# Get validation dataset predictions\n", | |
| "train_texts, val_texts, train_labels, val_labels = train_test_split(\n", | |
| " texts, labels, test_size=0.15, stratify=labels, random_state=42\n", | |
| ")\n", | |
| "\n", | |
| "# Predict on validation set\n", | |
| "val_pred_labels, val_pred_probs = predict_batch(val_texts, model, tokenizer)\n", | |
| "\n", | |
| "# Create confusion matrix\n", | |
| "cm = confusion_matrix(val_labels, val_pred_labels)\n", | |
| "\n", | |
| "# Get label names in correct order\n", | |
| "label_names = sorted(label_map.keys(), key=lambda x: label_map[x])\n", | |
| "label_names_short = [\n", | |
| " \"PL-Salient\",\n", | |
| " \"PL-NotSalient\", \n", | |
| " \"Crypto\",\n", | |
| " \"App/Bot\",\n", | |
| " \"No Context\"\n", | |
| "]\n", | |
| "\n", | |
| "# Plot confusion matrix\n", | |
| "plt.figure(figsize=(10, 8))\n", | |
| "sns.heatmap(\n", | |
| " cm, \n", | |
| " annot=True, \n", | |
| " fmt='d', \n", | |
| " cmap='Blues',\n", | |
| " xticklabels=label_names_short,\n", | |
| " yticklabels=label_names_short,\n", | |
| " cbar_kws={'label': 'Count'}\n", | |
| ")\n", | |
| "plt.title('Confusion Matrix - Validation Set', fontsize=16, pad=20)\n", | |
| "plt.ylabel('True Label', fontsize=12)\n", | |
| "plt.xlabel('Predicted Label', fontsize=12)\n", | |
| "plt.xticks(rotation=45, ha='right')\n", | |
| "plt.yticks(rotation=0)\n", | |
| "plt.tight_layout()\n", | |
| "plt.savefig('confusion_matrix.png', dpi=300, bbox_inches='tight')\n", | |
| "plt.show()\n", | |
| "\n", | |
| "# Print normalized confusion matrix (percentages)\n", | |
| "cm_normalized = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]\n", | |
| "print(\"\\nNormalized Confusion Matrix (row percentages):\")\n", | |
| "print(\"=\"*60)\n", | |
| "for i, true_label in enumerate(label_names_short):\n", | |
| " print(f\"\\n{true_label}:\")\n", | |
| " for j, pred_label in enumerate(label_names_short):\n", | |
| " percentage = cm_normalized[i, j] * 100\n", | |
| " if percentage > 0:\n", | |
| " print(f\" -> {pred_label}: {percentage:.1f}%\")\n", | |
| "\n", | |
| "# Print per-class accuracy\n", | |
| "print(\"\\n\" + \"=\"*60)\n", | |
| "print(\"Per-Class Accuracy:\")\n", | |
| "print(\"=\"*60)\n", | |
| "for i, label_name in enumerate(label_names_short):\n", | |
| " class_accuracy = cm[i, i] / cm[i, :].sum() * 100\n", | |
| " print(f\"{label_name}: {class_accuracy:.1f}%\")" | |
| ] | |
| } | |
| ], | |
| "metadata": { | |
| "kernelspec": { | |
| "display_name": "My Project (uv)", | |
| "language": "python", | |
| "name": "myproject" | |
| }, | |
| "language_info": { | |
| "codemirror_mode": { | |
| "name": "ipython", | |
| "version": 3 | |
| }, | |
| "file_extension": ".py", | |
| "mimetype": "text/x-python", | |
| "name": "python", | |
| "nbconvert_exporter": "python", | |
| "pygments_lexer": "ipython3", | |
| "version": "3.10.12" | |
| } | |
| }, | |
| "nbformat": 4, | |
| "nbformat_minor": 4 | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment