Spaces:
Running
Running
| """ | |
| Classifier Trainer V3 — "Supervised Brain" Pipeline | |
| Upgraded to BERT-Base for maximum accuracy and power. | |
| """ | |
| import os | |
| import pickle | |
| import json | |
| import numpy as np | |
| import pandas as pd | |
| from sklearn.model_selection import train_test_split | |
| from sklearn.preprocessing import LabelEncoder | |
| from sklearn.metrics import accuracy_score | |
| import torch | |
| import torch.nn as nn | |
| from torch.utils.data import Dataset, DataLoader | |
| from transformers import ( | |
| BertTokenizerFast, | |
| BertModel, | |
| get_linear_schedule_with_warmup, | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # Configuration (V3 - Power Mode) | |
| # --------------------------------------------------------------------------- | |
| PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) | |
| MODEL_DIR = os.path.join(PROJECT_ROOT, "Model") | |
| SAVE_DIR = os.path.join(PROJECT_ROOT, "backend", "models", "classifier-v3") | |
| DATASET_PATH = os.path.join(MODEL_DIR, "Final_Balanced_10000_IT_Support_Tickets.csv") | |
| LABEL_COLUMNS = ["category", "sub_category", "Priority", "auto_resolve", "assigned_team"] | |
| TEXT_COLUMN = "user_input_text" | |
| MAX_LEN = 256 | |
| BATCH_SIZE = 16 | |
| EPOCHS = 10 # Increased for deep learning | |
| LEARNING_RATE = 2e-5 | |
| DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| # --------------------------------------------------------------------------- | |
| # Architecture: BERT-Base (110M Params) | |
| # --------------------------------------------------------------------------- | |
| class MultiOutputClassifierV3(nn.Module): | |
| def __init__(self, num_labels_per_output: dict): | |
| super().__init__() | |
| # Moving to full BERT for more power | |
| self.bert = BertModel.from_pretrained("bert-base-uncased") | |
| hidden = self.bert.config.hidden_size | |
| self.dropout = nn.Dropout(0.3) # Slightly higher dropout for better generalization | |
| self.heads = nn.ModuleDict() | |
| for name, n_labels in num_labels_per_output.items(): | |
| self.heads[name] = nn.Sequential( | |
| nn.Linear(hidden, 256), | |
| nn.ReLU(), | |
| nn.Dropout(0.1), | |
| nn.Linear(256, n_labels) | |
| ) | |
| def forward(self, input_ids, attention_mask): | |
| outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask) | |
| # Use the pooled output for full BERT | |
| pooled_output = outputs.pooler_output | |
| pooled_output = self.dropout(pooled_output) | |
| logits = {name: head(pooled_output) for name, head in self.heads.items()} | |
| return logits | |
| class TicketDataset(Dataset): | |
| def __init__(self, encodings, labels_dict): | |
| self.encodings = encodings | |
| self.labels_dict = labels_dict | |
| def __len__(self): | |
| return len(self.labels_dict[LABEL_COLUMNS[0]]) | |
| def __getitem__(self, idx): | |
| item = {k: v[idx] for k, v in self.encodings.items()} | |
| for col in LABEL_COLUMNS: | |
| item[f"label_{col}"] = torch.tensor(self.labels_dict[col][idx], dtype=torch.long) | |
| return item | |
| # --------------------------------------------------------------------------- | |
| # Training Logic | |
| # --------------------------------------------------------------------------- | |
| def train_v3(): | |
| print("🔥 Starting POWER TRAINING (V3 - BERT-Base)") | |
| df = pd.read_csv(DATASET_PATH) | |
| df.dropna(subset=[TEXT_COLUMN] + LABEL_COLUMNS, inplace=True) | |
| df.reset_index(drop=True, inplace=True) | |
| label_encoders = {} | |
| encoded_labels = {} | |
| for col in LABEL_COLUMNS: | |
| le = LabelEncoder() | |
| df[col] = df[col].astype(str) | |
| encoded_labels[col] = le.fit_transform(df[col]) | |
| label_encoders[col] = le | |
| num_labels_per_output = {col: len(le.classes_) for col, le in label_encoders.items()} | |
| tokenizer = BertTokenizerFast.from_pretrained("bert-base-uncased") | |
| texts = df[TEXT_COLUMN].tolist() | |
| encodings = tokenizer(texts, truncation=True, padding=True, max_length=MAX_LEN, return_tensors="pt") | |
| indices = np.arange(len(df)) | |
| train_idx, test_idx = train_test_split(indices, test_size=0.1, random_state=42) | |
| def _subset(enc, idx): return {k: v[idx] for k, v in enc.items()} | |
| train_ds = TicketDataset(_subset(encodings, train_idx), {col: encoded_labels[col][train_idx] for col in LABEL_COLUMNS}) | |
| test_ds = TicketDataset(_subset(encodings, test_idx), {col: encoded_labels[col][test_idx] for col in LABEL_COLUMNS}) | |
| train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True) | |
| test_loader = DataLoader(test_ds, batch_size=BATCH_SIZE) | |
| model = MultiOutputClassifierV3(num_labels_per_output).to(DEVICE) | |
| optimizer = torch.optim.AdamW(model.parameters(), lr=LEARNING_RATE) | |
| total_steps = len(train_loader) * EPOCHS | |
| scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=200, num_training_steps=total_steps) | |
| loss_fn = nn.CrossEntropyLoss() | |
| print(f"\n[TRAIN] Executing {EPOCHS} epochs on {DEVICE}...") | |
| for epoch in range(EPOCHS): | |
| model.train() | |
| total_loss = 0 | |
| for batch in train_loader: | |
| input_ids = batch["input_ids"].to(DEVICE) | |
| attention_mask = batch["attention_mask"].to(DEVICE) | |
| logits = model(input_ids, attention_mask) | |
| loss = sum(loss_fn(logits[col], batch[f"label_{col}"].to(DEVICE)) for col in LABEL_COLUMNS) | |
| optimizer.zero_grad() | |
| loss.backward() | |
| optimizer.step() | |
| scheduler.step() | |
| total_loss += loss.item() | |
| print(f"✅ Epoch {epoch+1}/{EPOCHS} | Avg Loss: {total_loss/len(train_loader):.4f}") | |
| # Save | |
| os.makedirs(SAVE_DIR, exist_ok=True) | |
| torch.save(model.state_dict(), os.path.join(SAVE_DIR, "model.pt")) | |
| tokenizer.save_pretrained(SAVE_DIR) | |
| with open(os.path.join(SAVE_DIR, "label_encoders.pkl"), "wb") as f: pickle.dump(label_encoders, f) | |
| with open(os.path.join(SAVE_DIR, "model_config.json"), "w") as f: json.dump(num_labels_per_output, f) | |
| print(f"\n[SUCCESS] V3 Power Model saved to {SAVE_DIR}") | |
| if __name__ == "__main__": | |
| train_v3() | |