{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"colab":{"machine_shape":"hm","gpuType":"T4"},"accelerator":"GPU","kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":12500,"databundleVersionId":1375107,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":14074956,"sourceType":"datasetVersion","datasetId":8959624}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================\n# CONFIGURATION\n# ============================================\nMODEL_NAME = \"distilbert-base-uncased\"\nEPOCHS = 2\nMAX_LENGTH = 128\nTRAIN_BATCH_SIZE = 16\nVAL_BATCH_SIZE = 32\nLEARNING_RATE = 3e-5\nDATA_PATH = \"/kaggle/input/c/jigsaw-unintended-bias-in-toxicity-classification/all_data.csv\"\nSAVE_PATH = \"/kaggle/working/toxic_model\"\nSEED = 42\n\n# ============================================\n# SUPPRESS WARNINGS\n# ============================================\nimport warnings\nwarnings.filterwarnings('ignore')\nimport os\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'\nos.environ['TOKENIZERS_PARALLELISM'] = 'false'\nos.environ['TF_FORCE_GPU_ALLOW_GROWTH'] = 'true'\n\n# Suppress TensorFlow and CUDA warnings\nimport sys\nimport logging\nlogging.getLogger('tensorflow').setLevel(logging.ERROR)\nlogging.getLogger('transformers').setLevel(logging.ERROR)\nlogging.getLogger('torch').setLevel(logging.ERROR)\n\n# ============================================\n# IMPORTS\n# ============================================\nimport subprocess\nimport sys\nsubprocess.run([sys.executable, \"-m\", \"pip\", \"install\", \"transformers\", \"datasets\", \"accelerate\", \"tqdm\", \"-q\"], \n               stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)\n\nimport pandas as pd\nimport numpy as np\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom transformers import DistilBertTokenizerFast, DistilBertForSequenceClassification\nfrom sklearn.model_selection import train_test_split\nfrom torch.optim import AdamW\nfrom tqdm import tqdm\n\n# ============================================\n# SETUP\n# ============================================\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\ntorch.manual_seed(SEED)\nnp.random.seed(SEED)\n\n# ============================================\n# LOAD DATA - FULL VERSION WITH ALL COLUMNS\n# ============================================\nprint(\"Loading Jigsaw dataset with all columns...\")\n\n# Load all columns first to see what we have\ndf = pd.read_csv(DATA_PATH)\n\nprint(f\"\\nDataset shape: {df.shape}\")\nprint(f\"Columns: {len(df.columns)}\")\nprint(\"\\nFirst few column names:\")\nfor i, col in enumerate(df.columns[:20]):\n    print(f\"  {i+1}. {col}\")\nif len(df.columns) > 20:\n    print(f\"  ... and {len(df.columns)-20} more columns\")\n\n# The Jigsaw dataset has multiple toxicity annotations:\n# - 'toxicity': Overall toxicity score (0-1)\n# - 'severe_toxicity', 'obscene', 'identity_attack', 'insult', 'threat'\n# - Many identity columns for bias analysis\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"DATA PREPARATION\")\nprint(\"=\"*60)\n\n# Create binary label from toxicity score (>= 0.5 means toxic)\ndf['label'] = (df['toxicity'] >= 0.5).astype(int)\n\n# Let's see the distribution\nprint(f\"\\nLabel distribution:\")\nprint(f\"Non-toxic (label=0): {(df['label'] == 0).sum():,} samples\")\nprint(f\"Toxic (label=1): {(df['label'] == 1).sum():,} samples\")\nprint(f\"Toxicity rate: {df['label'].mean()*100:.2f}%\")\n\n# Check for missing values\nprint(f\"\\nMissing values in comment_text: {df['comment_text'].isnull().sum()}\")\ndf = df.dropna(subset=['comment_text', 'toxicity'])\n\n# ============================================\n# ANALYZE BIAS IN THE DATA\n# ============================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"BIAS ANALYSIS BY IDENTITY GROUP\")\nprint(\"=\"*60)\n\n# Identity columns in Jigsaw dataset\nidentity_columns = [\n    'male', 'female', 'transgender', 'other_gender',\n    'heterosexual', 'homosexual_gay_or_lesbian', 'bisexual', 'other_sexual_orientation',\n    'christian', 'jewish', 'muslim', 'hindu', 'buddhist', 'atheist', 'other_religion',\n    'black', 'white', 'asian', 'latino', 'other_race_or_ethnicity',\n    'physical_disability', 'intellectual_or_learning_disability',\n    'psychiatric_or_mental_illness', 'other_disability'\n]\n\n# Count how many comments mention each identity\nprint(\"\\nComments mentioning each identity (>0.5 probability):\")\nfor col in identity_columns:\n    if col in df.columns:\n        count = (df[col] > 0.5).sum()\n        if count > 0:\n            toxic_rate = df[df[col] > 0.5]['label'].mean() * 100\n            print(f\"  {col}: {count:,} comments ({toxic_rate:.1f}% toxic)\")\n\n# ============================================\n# SPLIT DATA\n# ============================================\n# Use the 'split' column if available, otherwise do random split\nif 'split' in df.columns:\n    print(\"\\nUsing existing 'split' column for train/val split...\")\n    train_df = df[df['split'] == 'train'].copy()\n    val_df = df[df['split'] == 'test'].copy()  # or 'val' depending on dataset\n    \n    # If no test split, split train further\n    if len(val_df) == 0:\n        train_df, val_df = train_test_split(\n            train_df[['comment_text', 'label']], \n            test_size=0.1, \n            random_state=SEED, \n            stratify=train_df['label']\n        )\n    else:\n        train_df = train_df[['comment_text', 'label']]\n        val_df = val_df[['comment_text', 'label']]\nelse:\n    print(\"\\nSplitting data randomly (80/20)...\")\n    train_df, val_df = train_test_split(\n        df[['comment_text', 'label']], \n        test_size=0.2, \n        random_state=SEED, \n        stratify=df['label']\n    )\n\nprint(f\"\\nTraining set: {len(train_df):,} samples\")\nprint(f\"Validation set: {len(val_df):,} samples\")\n\n# ============================================\n# LOAD MODEL\n# ============================================\nprint(\"\\nLoading model...\")\ntokenizer = DistilBertTokenizerFast.from_pretrained(MODEL_NAME)\nmodel = DistilBertForSequenceClassification.from_pretrained(MODEL_NAME, num_labels=2)\nmodel.to(device)\n\n# ============================================\n# DATASET - OPTIMIZED FOR JIGSAW\n# ============================================\nclass JigsawToxicDataset(Dataset):\n    def __init__(self, texts, labels):\n        # Store texts as Python list to save memory\n        self.texts = texts.tolist()\n        self.labels = labels.tolist()\n        \n    def __len__(self):\n        return len(self.texts)\n        \n    def __getitem__(self, idx):\n        text = str(self.texts[idx])\n        \n        # Tokenize with all special tokens needed\n        enc = tokenizer(\n            text,\n            max_length=MAX_LENGTH,\n            truncation=True,\n            padding=\"max_length\",\n            return_tensors=\"pt\"\n        )\n        \n        return {\n            \"input_ids\": enc[\"input_ids\"].squeeze(0),\n            \"attention_mask\": enc[\"attention_mask\"].squeeze(0),\n            \"labels\": torch.tensor(self.labels[idx], dtype=torch.long)\n        }\n\n# ============================================\n# CREATE DATALOADERS\n# ============================================\nprint(\"Creating datasets...\")\ntrain_ds = JigsawToxicDataset(train_df[\"comment_text\"], train_df[\"label\"])\nval_ds = JigsawToxicDataset(val_df[\"comment_text\"], val_df[\"label\"])\n\ntrain_loader = DataLoader(train_ds, batch_size=TRAIN_BATCH_SIZE, shuffle=True)\nval_loader = DataLoader(val_ds, batch_size=VAL_BATCH_SIZE, shuffle=False)\n\nprint(f\"\\nTraining batches: {len(train_loader)}\")\nprint(f\"Validation batches: {len(val_loader)}\")\n\n# ============================================\n# TRAINING WITH BIAS AWARENESS\n# ============================================\noptimizer = AdamW(model.parameters(), lr=LEARNING_RATE)\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"TRAINING STARTING\")\nprint(\"=\"*60)\n\nfor epoch in range(EPOCHS):\n    # Train\n    model.train()\n    train_loss = 0\n    train_bar = tqdm(train_loader, desc=f\"Training Epoch {epoch+1}/{EPOCHS}\", leave=True)\n    \n    for batch in train_bar:\n        batch = {k: v.to(device) for k, v in batch.items()}\n        outputs = model(**batch)\n        loss = outputs.loss\n        \n        optimizer.zero_grad()\n        loss.backward()\n        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)  # Gradient clipping\n        optimizer.step()\n        \n        train_loss += loss.item()\n        train_bar.set_postfix({'loss': f'{loss.item():.4f}'})\n    \n    avg_train_loss = train_loss / len(train_loader)\n    \n    # Validate\n    model.eval()\n    correct = 0\n    total = 0\n    all_preds = []\n    all_labels = []\n    \n    val_bar = tqdm(val_loader, desc=f\"Validating Epoch {epoch+1}/{EPOCHS}\", leave=True)\n    \n    with torch.no_grad():\n        for batch in val_bar:\n            batch = {k: v.to(device) for k, v in batch.items()}\n            outputs = model(**batch)\n            preds = torch.argmax(outputs.logits, dim=1)\n            \n            correct += (preds == batch[\"labels\"]).sum().item()\n            total += len(preds)\n            \n            all_preds.extend(preds.cpu().numpy())\n            all_labels.extend(batch[\"labels\"].cpu().numpy())\n            \n            current_acc = correct / total if total > 0 else 0\n            val_bar.set_postfix({'acc': f'{current_acc:.4f}'})\n    \n    val_acc = correct / total\n    \n    # Calculate F1 score for better evaluation\n    from sklearn.metrics import f1_score\n    val_f1 = f1_score(all_labels, all_preds, zero_division=0)\n    \n    print(f\"\\nEpoch {epoch+1} Summary:\")\n    print(f\"  Train Loss: {avg_train_loss:.4f}\")\n    print(f\"  Val Accuracy: {val_acc:.4f}\")\n    print(f\"  Val F1 Score: {val_f1:.4f}\")\n\n# ============================================\n# SAVE MODEL\n# ============================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"SAVING MODEL\")\nprint(\"=\"*60)\n\nos.makedirs(SAVE_PATH, exist_ok=True)\nmodel.save_pretrained(SAVE_PATH)\ntokenizer.save_pretrained(SAVE_PATH)\n\nprint(f\"Model saved to: {SAVE_PATH}\")\nprint(f\"Files saved:\")\nprint(f\"  - {SAVE_PATH}/config.json\")\nprint(f\"  - {SAVE_PATH}/pytorch_model.bin\")\nprint(f\"  - {SAVE_PATH}/tokenizer files\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T18:28:08.450994Z","iopub.execute_input":"2025-12-09T18:28:08.451575Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"--------------------------------------------------\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# CONFIGURATION\n# ============================================\nMODEL_NAME = \"distilbert-base-uncased\"\nEPOCHS = 2\nMAX_LENGTH = 128\nTRAIN_BATCH_SIZE = 16\nVAL_BATCH_SIZE = 32\nLEARNING_RATE = 3e-5\nDATA_PATH = \"/kaggle/input/dataset/data.csv\"\nSAVE_PATH = \"/kaggle/working/toxic_model\"\nSEED = 42\n\n# ============================================\n# SUPPRESS WARNINGS\n# ============================================\nimport warnings\nwarnings.filterwarnings('ignore')\nimport os\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'\nos.environ['TOKENIZERS_PARALLELISM'] = 'false'\n\n# ============================================\n# IMPORTS\n# ============================================\nimport subprocess\nimport sys\nsubprocess.run([sys.executable, \"-m\", \"pip\", \"install\", \"transformers\", \"datasets\", \"accelerate\", \"tqdm\", \"-q\"], \n               stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)\n\nimport pandas as pd\nimport numpy as np\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom transformers import DistilBertTokenizerFast, DistilBertForSequenceClassification\nfrom sklearn.model_selection import train_test_split\nfrom torch.optim import AdamW\nfrom tqdm import tqdm\n\n# ============================================\n# SETUP\n# ============================================\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\ntorch.manual_seed(SEED)\nnp.random.seed(SEED)\n\n# ============================================\n# LOAD DATA\n# ============================================\ndf = pd.read_csv(DATA_PATH, usecols=['comment_text', 'target'])\ndf = df.dropna()\ndf['label'] = (df['target'] >= 0.5).astype(int)\n\ntrain_df, val_df = train_test_split(\n    df[['comment_text', 'label']], \n    test_size=0.1, \n    random_state=SEED, \n    stratify=df['label']\n)\n\n# ============================================\n# LOAD MODEL\n# ============================================\ntokenizer = DistilBertTokenizerFast.from_pretrained(MODEL_NAME)\nmodel = DistilBertForSequenceClassification.from_pretrained(MODEL_NAME, num_labels=2)\nmodel.to(device)\n\n# ============================================\n# DATASET\n# ============================================\nclass ToxicDataset(Dataset):\n    def __init__(self, texts, labels):\n        self.texts = texts.tolist()\n        self.labels = labels.tolist()\n        \n    def __len__(self):\n        return len(self.texts)\n        \n    def __getitem__(self, idx):\n        text = str(self.texts[idx])\n        enc = tokenizer(\n            text,\n            max_length=MAX_LENGTH,\n            truncation=True,\n            padding=\"max_length\",\n            return_tensors=\"pt\"\n        )\n        return {\n            \"input_ids\": enc[\"input_ids\"].squeeze(0),\n            \"attention_mask\": enc[\"attention_mask\"].squeeze(0),\n            \"labels\": torch.tensor(self.labels[idx], dtype=torch.long)\n        }\n\n# ============================================\n# CREATE DATALOADERS\n# ============================================\ntrain_ds = ToxicDataset(train_df[\"comment_text\"], train_df[\"label\"])\nval_ds = ToxicDataset(val_df[\"comment_text\"], val_df[\"label\"])\n\ntrain_loader = DataLoader(train_ds, batch_size=TRAIN_BATCH_SIZE, shuffle=True)\nval_loader = DataLoader(val_ds, batch_size=VAL_BATCH_SIZE, shuffle=False)\n\n# ============================================\n# TRAINING\n# ============================================\noptimizer = AdamW(model.parameters(), lr=LEARNING_RATE)\n\nfor epoch in range(EPOCHS):\n    # Train\n    model.train()\n    train_loss = 0\n    train_bar = tqdm(train_loader, desc=f\"Training Epoch {epoch+1}/{EPOCHS}\", leave=True)\n    \n    for batch in train_bar:\n        batch = {k: v.to(device) for k, v in batch.items()}\n        outputs = model(**batch)\n        loss = outputs.loss\n        \n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n        \n        train_loss += loss.item()\n        train_bar.set_postfix({'loss': f'{loss.item():.4f}'})\n    \n    avg_train_loss = train_loss / len(train_loader)\n    \n    # Validate\n    model.eval()\n    correct = 0\n    total = 0\n    val_bar = tqdm(val_loader, desc=f\"Validating Epoch {epoch+1}/{EPOCHS}\", leave=True)\n    \n    with torch.no_grad():\n        for batch in val_bar:\n            batch = {k: v.to(device) for k, v in batch.items()}\n            outputs = model(**batch)\n            preds = torch.argmax(outputs.logits, dim=1)\n            correct += (preds == batch[\"labels\"]).sum().item()\n            total += len(preds)\n            val_bar.set_postfix({'acc': f'{(correct/total):.4f}'})\n    \n    val_acc = correct / total\n\n# ============================================\n# SAVE MODEL\n# ============================================\nos.makedirs(SAVE_PATH, exist_ok=True)\nmodel.save_pretrained(SAVE_PATH)\ntokenizer.save_pretrained(SAVE_PATH)\nprint(f\"Model saved to {SAVE_PATH}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# CONFIGURATION - OPTIMIZED FOR SPEED\n# ============================================\nMODEL_NAME = \"distilbert-base-uncased\"\nEPOCHS = 1\nMAX_LENGTH = 128\nTRAIN_BATCH_SIZE = 32\nVAL_BATCH_SIZE = 64\nLEARNING_RATE = 3e-5\nDATA_PATH = \"/kaggle/input/dataset/data.csv\"\nSAVE_PATH = \"/kaggle/working/toxic_model\"\nSEED = 42\n\n# ============================================\n# IMPORTS - WITH PARALLELISM FIX\n# ============================================\nimport warnings\nwarnings.filterwarnings('ignore')\nimport os\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'\nos.environ['TOKENIZERS_PARALLELISM'] = 'false'  # FIX: Disable tokenizer parallelism warning\n\n# Quiet install\n!pip install transformers torch scikit-learn pandas --quiet\n\nimport pandas as pd\nimport numpy as np\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom transformers import DistilBertTokenizerFast, DistilBertForSequenceClassification\nfrom sklearn.model_selection import train_test_split\nfrom torch.optim import AdamW\n\n# ============================================\n# SETUP\n# ============================================\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\ntorch.manual_seed(SEED)\nnp.random.seed(SEED)\n\n# ============================================\n# LOAD DATA\n# ============================================\nprint(\"Loading data...\")\n\n# Load data\ndf = pd.read_csv(DATA_PATH, usecols=['comment_text', 'target'])\ndf = df.dropna()\ndf['label'] = (df['target'] >= 0.5).astype(int)\n\nprint(f\"Loaded {len(df):,} samples\")\nprint(f\"Toxic samples: {df['label'].sum():,} ({df['label'].mean()*100:.1f}%)\")\n\n# Split\ntrain_df, val_df = train_test_split(\n    df[['comment_text', 'label']], test_size=0.1, random_state=SEED, stratify=df['label'])\ndel df\n\n# ============================================\n# LOAD MODEL\n# ============================================\nprint(\"Loading model...\")\ntokenizer = DistilBertTokenizerFast.from_pretrained(MODEL_NAME)\nmodel = DistilBertForSequenceClassification.from_pretrained(MODEL_NAME, num_labels=2)\nmodel.to(device)\n\n# ============================================\n# DATASET\n# ============================================\nclass ToxicDataset(Dataset):\n    def __init__(self, texts, labels):\n        self.texts = texts.tolist()\n        self.labels = labels.tolist()\n        \n    def __len__(self):\n        return len(self.texts)\n        \n    def __getitem__(self, idx):\n        text = str(self.texts[idx])\n        enc = tokenizer(\n            text, max_length=MAX_LENGTH, truncation=True, padding=\"max_length\", return_tensors=\"pt\")\n        return {\n            \"input_ids\": enc[\"input_ids\"].squeeze(0),\n            \"attention_mask\": enc[\"attention_mask\"].squeeze(0),\n            \"labels\": torch.tensor(self.labels[idx], dtype=torch.long)\n        }\n\n# Create datasets\ntrain_ds = ToxicDataset(train_df[\"comment_text\"], train_df[\"label\"])\nval_ds = ToxicDataset(val_df[\"comment_text\"], val_df[\"label\"])\n\n# WITHOUT num_workers to avoid tokenizer warning\ntrain_loader = DataLoader(train_ds, batch_size=TRAIN_BATCH_SIZE, shuffle=True)\nval_loader = DataLoader(val_ds, batch_size=VAL_BATCH_SIZE, shuffle=False)\n\n# ============================================\n# TRAINING\n# ============================================\noptimizer = AdamW(model.parameters(), lr=LEARNING_RATE)\n\nprint(f\"Training for {EPOCHS} epoch...\")\n\n# Train\nmodel.train()\ntotal_loss = 0\nfor batch in train_loader:\n    batch = {k: v.to(device) for k, v in batch.items()}\n    outputs = model(**batch)\n    loss = outputs.loss\n    \n    optimizer.zero_grad()\n    loss.backward()\n    optimizer.step()\n    \n    total_loss += loss.item()\n\nprint(f\"Training complete. Average loss: {total_loss/len(train_loader):.4f}\")\n\n# ============================================\n# VALIDATION\n# ============================================\nmodel.eval()\ncorrect = 0\ntotal = 0\nwith torch.no_grad():\n    for batch in val_loader:\n        batch = {k: v.to(device) for k, v in batch.items()}\n        out = model(**batch)\n        preds = torch.argmax(out.logits, dim=1)\n        correct += (preds == batch[\"labels\"]).sum().item()\n        total += len(preds)\n\nprint(f\"Validation Accuracy: {correct/total:.4f}\")\n\n# ============================================\n# SAVE MODEL\n# ============================================\nprint(f\"Saving model to {SAVE_PATH}\")\nos.makedirs(SAVE_PATH, exist_ok=True)\nmodel.save_pretrained(SAVE_PATH)\ntokenizer.save_pretrained(SAVE_PATH)\nprint(\"Model saved\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# CONTEXT-AWARE TOXICITY DETECTOR\n# ============================================\n\nimport warnings\nwarnings.filterwarnings('ignore')\nimport os\nos.environ['TOKENIZERS_PARALLELISM'] = 'false'\n\nimport torch\nfrom transformers import DistilBertTokenizerFast, DistilBertForSequenceClassification\n\nMODEL_PATH = \"/kaggle/working/toxic_model\"\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# Load model\ntokenizer = DistilBertTokenizerFast.from_pretrained(MODEL_PATH)\nmodel = DistilBertForSequenceClassification.from_pretrained(MODEL_PATH)\nmodel.to(device)\nmodel.eval()\n\ndef analyze_toxicity_with_context(text, threshold=0.7):\n    \"\"\"\n    Analyzes toxicity with better context awareness\n    \"\"\"\n    # Check overall toxicity\n    inputs = tokenizer(text, return_tensors=\"pt\", truncation=True, padding=True, max_length=128)\n    inputs = {k: v.to(device) for k, v in inputs.items()}\n    \n    with torch.no_grad():\n        outputs = model(**inputs)\n        probs = torch.softmax(outputs.logits, dim=1)\n        toxic_prob = probs[0, 1].item()\n    \n    is_toxic = toxic_prob >= threshold\n    \n    if not is_toxic:\n        return {\n            \"text\": text,\n            \"is_toxic\": False,\n            \"toxic_prob\": toxic_prob,\n            \"toxic_words\": [],\n            \"reasoning\": \"Text is not toxic overall\"\n        }\n    \n    # If toxic, analyze words in context\n    import re\n    \n    # First, check individual words\n    words = re.findall(r'\\b\\w+\\b', text.lower())\n    toxic_candidates = []\n    \n    for word in words:\n        # Check word in isolation\n        word_inputs = tokenizer(word, return_tensors=\"pt\", truncation=True, padding=True, max_length=128)\n        word_inputs = {k: v.to(device) for k, v in word_inputs.items()}\n        \n        with torch.no_grad():\n            word_outputs = model(**word_inputs)\n            word_probs = torch.softmax(word_outputs.logits, dim=1)\n            word_toxic_prob = word_probs[0, 1].item()\n        \n        # Word is potentially toxic in isolation\n        if word_toxic_prob > 0.5:\n            # Now check if removing this word reduces toxicity\n            text_without_word = re.sub(r'\\b' + word + r'\\b', '', text, flags=re.IGNORECASE).strip()\n            \n            if text_without_word:  # Make sure we don't have empty string\n                without_inputs = tokenizer(text_without_word, return_tensors=\"pt\", truncation=True, padding=True, max_length=128)\n                without_inputs = {k: v.to(device) for k, v in without_inputs.items()}\n                \n                with torch.no_grad():\n                    without_outputs = model(**without_inputs)\n                    without_probs = torch.softmax(without_outputs.logits, dim=1)\n                    without_toxic_prob = without_probs[0, 1].item()\n                \n                # Calculate how much this word contributes\n                contribution = toxic_prob - without_toxic_prob\n                \n                if contribution > 0.1:  # Word contributes at least 10% to toxicity\n                    toxic_candidates.append({\n                        \"word\": word,\n                        \"individual_toxicity\": word_toxic_prob,\n                        \"contribution\": contribution\n                    })\n    \n    # Sort by contribution\n    toxic_candidates.sort(key=lambda x: x[\"contribution\"], reverse=True)\n    toxic_words = [candidate[\"word\"] for candidate in toxic_candidates]\n    \n    # Provide reasoning\n    reasoning = f\"Text is toxic ({toxic_prob:.1%} probability)\"\n    if toxic_words:\n        reasoning += f\". Main toxic words: {', '.join(toxic_words)}\"\n    \n    return {\n        \"text\": text,\n        \"is_toxic\": True,\n        \"toxic_prob\": toxic_prob,\n        \"toxic_words\": toxic_words,\n        \"toxic_details\": toxic_candidates,\n        \"reasoning\": reasoning\n    }\n\n# ============================================\n# TEST WITH DIFFERENT CONTEXTS\n# ============================================\ntest_cases = [\n    \"You're an idiot and stupid!\",\n    \"based on bible, hell is hot\",\n    \"Go to hell, you moron!\",\n    \"The weather is hell today\",\n    \"I hate you so much\",\n    \"The concept of hell in religion\",\n    \"You're going to hell for that\",\n    \"This is hell of a good show\"\n]\n\nprint(\"Context-Aware Toxicity Analysis:\")\nprint(\"=\" * 60)\n\nfor text in test_cases:\n    result = analyze_toxicity_with_context(text, threshold=0.5)\n    \n    print(f\"\\nText: '{text}'\")\n    print(f\"Overall toxic: {result['is_toxic']} ({result['toxic_prob']:.1%})\")\n    if result['toxic_words']:\n        print(f\"Toxic words detected: {result['toxic_words']}\")\n    else:\n        print(\"No specific toxic words identified (context matters)\")\n    print(f\"Reasoning: {result['reasoning']}\")\n    print(\"-\" * 60)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}