ai-helpdesk-api / training /classifier_trainer_v3.py
github-actions
deploy: sync from github main
6d32f12
Raw
History Blame Contribute Delete
6.1 kB
"""
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()