Adedayo2000 commited on
Commit
e48d12b
·
verified ·
1 Parent(s): 6cd74ad

Create train.py

Browse files
Files changed (1) hide show
  1. train.py +176 -0
train.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Support Ticket Classifier
3
+ Author: Adedayo Adebayo
4
+ Date: 2026-03-14
5
+ Description: Multi-class text classification for customer support tickets using BERTweet
6
+ """
7
+
8
+ import logging
9
+ from typing import Dict, List, Tuple, Any
10
+ import pandas as pd
11
+ import numpy as np
12
+ import torch
13
+ from sklearn.model_selection import train_test_split
14
+ from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
15
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer
16
+ from huggingface_hub import HfApi, login
17
+
18
+ CONFIG = {
19
+ "model_name": "vinai/bertweet-base",
20
+ "test_size": 0.2,
21
+ "random_state": 99,
22
+ "batch_size": 8,
23
+ "epochs": 4,
24
+ "max_length": 128,
25
+ }
26
+
27
+ def setup_logging() -> logging.Logger:
28
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
29
+ return logging.getLogger(__name__)
30
+
31
+ logger = setup_logging()
32
+
33
+ def load_dataset(filepath: str) -> Tuple[pd.DataFrame, List[str]]:
34
+ logger.info(f"Loading dataset from {filepath}")
35
+ ticket_dataframe = pd.read_csv(filepath)
36
+ ticket_dataframe = clean_dataset(ticket_dataframe)
37
+ categories = sorted(ticket_dataframe['category'].unique().tolist())
38
+ logger.info(f"Loaded {len(ticket_dataframe)} samples with {len(categories)} categories")
39
+ return ticket_dataframe, categories
40
+
41
+ def clean_dataset(ticket_dataframe: pd.DataFrame) -> pd.DataFrame:
42
+ ticket_dataframe['ticket_text'] = ticket_dataframe['ticket_text'].fillna('').astype(str)
43
+ ticket_dataframe['category'] = ticket_dataframe['category'].fillna('General Inquiry')
44
+ ticket_dataframe = ticket_dataframe[ticket_dataframe['ticket_text'].str.strip() != '']
45
+ return ticket_dataframe
46
+
47
+ def create_labels(ticket_dataframe: pd.DataFrame, categories: List[str]) -> Tuple[pd.DataFrame, Dict[str, int], Dict[int, str]]:
48
+ label2id = {label: idx for idx, label in enumerate(categories)}
49
+ id2label = {idx: label for label, idx in label2id.items()}
50
+ ticket_dataframe['label'] = ticket_dataframe['category'].map(label2id)
51
+ return ticket_dataframe, label2id, id2label
52
+
53
+ def split_dataset(ticket_dataframe: pd.DataFrame) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
54
+ ticket = ticket_dataframe['ticket_text'].values
55
+ category = ticket_dataframe['label'].values
56
+ ticket_train, ticket_test, category_train, category_test = train_test_split(ticket, category, test_size=CONFIG['test_size'], random_state=CONFIG['random_state'], stratify=category)
57
+ logger.info(f"Train: {len(ticket_train)}, Test: {len(ticket_test)}")
58
+ return ticket_train, ticket_test, category_train, category_test
59
+
60
+ class TicketDataset(torch.utils.data.Dataset):
61
+ def __init__(self, texts: np.ndarray, labels: np.ndarray, tokenizer: AutoTokenizer, max_length: int):
62
+ self.texts = texts
63
+ self.labels = labels
64
+ self.tokenizer = tokenizer
65
+ self.max_length = max_length
66
+
67
+ def __len__(self) -> int:
68
+ return len(self.texts)
69
+
70
+ def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
71
+ encoding = self.tokenizer(str(self.texts[idx]), add_special_tokens=True, max_length=self.max_length, padding='max_length', truncation=True, return_tensors='pt')
72
+ return {
73
+ 'input_ids': encoding['input_ids'].squeeze(0),
74
+ 'attention_mask': encoding['attention_mask'].squeeze(0),
75
+ 'labels': torch.tensor(int(self.labels[idx]), dtype=torch.long)
76
+ }
77
+
78
+ def create_datasets(ticket_train: np.ndarray, ticket_test: np.ndarray, category_train: np.ndarray, category_test: np.ndarray, tokenizer: AutoTokenizer):
79
+ return TicketDataset(ticket_train, category_train, tokenizer, CONFIG['max_length']), TicketDataset(ticket_test, category_test, tokenizer, CONFIG['max_length'])
80
+
81
+ def load_model_and_tokenizer(num_labels: int, label2id: Dict[str, int], id2label: Dict[int, str]):
82
+ logger.info(f"Loading model: {CONFIG['model_name']}")
83
+ tokenizer = AutoTokenizer.from_pretrained(CONFIG['model_name'])
84
+ model = AutoModelForSequenceClassification.from_pretrained(CONFIG['model_name'], num_labels=num_labels, id2label=id2label, label2id=label2id)
85
+ return model, tokenizer
86
+
87
+ def train_model(model, train_dataset, test_dataset) -> Trainer:
88
+ logger.info("Training started")
89
+ training_args = TrainingArguments(
90
+ output_dir='./results',
91
+ num_train_epochs=CONFIG['epochs'],
92
+ per_device_train_batch_size=CONFIG['batch_size'],
93
+ per_device_eval_batch_size=CONFIG['batch_size'],
94
+ warmup_steps=50,
95
+ weight_decay=0.01,
96
+ logging_steps=50,
97
+ eval_strategy="epoch",
98
+ save_strategy="epoch",
99
+ load_best_model_at_end=True,
100
+ )
101
+ trainer = Trainer(model=model, args=training_args, train_dataset=train_dataset, eval_dataset=test_dataset)
102
+ trainer.train()
103
+ logger.info("Training complete")
104
+ return trainer
105
+
106
+ def evaluate_model(trainer, test_dataset, category_test: np.ndarray, categories: List[str]) -> float:
107
+ predictions = trainer.predict(test_dataset)
108
+ preds = np.argmax(predictions.predictions, axis=-1)
109
+ accuracy = accuracy_score(category_test, preds)
110
+ logger.info(f"Accuracy: {accuracy*100:.2f}%")
111
+ print("CLASSIFICATION REPORT")
112
+ print(classification_report(category_test, preds, target_names=categories))
113
+ print(confusion_matrix(category_test, preds))
114
+ return accuracy
115
+
116
+ def push_to_huggingface(model, tokenizer, repo_name: str = "BERTweet-Support-Ticket-Classifier") -> str:
117
+ logger.info("Pushing to HuggingFace Hub...")
118
+
119
+ try:
120
+ from kaggle_secrets import UserSecretsClient
121
+ user_secrets = UserSecretsClient()
122
+ hf_token = user_secrets.get_secret("HF_TOKEN")
123
+ logger.info("Logging in to HuggingFace hub...")
124
+ login(token=hf_token)
125
+ logger.info("Logged in to HuggingFace")
126
+
127
+ api = HfApi()
128
+ user_info = api.whoami(token=hf_token)
129
+ username = user_info['name']
130
+ full_repo_name = f"{username}/{repo_name}"
131
+
132
+ logger.info(f"Uploading to: {full_repo_name}")
133
+
134
+ model.push_to_hub(repo_name, token=hf_token)
135
+ tokenizer.push_to_hub(repo_name, token=hf_token)
136
+
137
+ model_url = f"https://huggingface.co/{full_repo_name}"
138
+
139
+ logger.info("Model Uploaded Successfully!")
140
+ logger.info(f"URL: {model_url}")
141
+
142
+ return model_url
143
+
144
+ except Exception as e:
145
+ logger.error(f"Failed to push due to the following error: {e}")
146
+ raise
147
+
148
+ def predict_single(text: str, model, tokenizer, id2label: Dict[int, str]) -> Dict[str, Any]:
149
+ inputs = tokenizer(text, return_tensors='pt', truncation=True, max_length=CONFIG['max_length'], padding=True)
150
+ with torch.no_grad():
151
+ outputs = model(**inputs)
152
+ probs = torch.nn.functional.softmax(outputs.logits, dim=-1)
153
+ pred_idx = torch.argmax(probs).item()
154
+ return {'category': id2label[pred_idx], 'confidence': probs[0][pred_idx].item()}
155
+
156
+ def run_test_predictions(model, tokenizer, id2label: Dict[int, str]) -> None:
157
+ test_samples = [""]
158
+ print("TEST PREDICTIONS")
159
+ for sample in test_samples:
160
+ result = predict_single(sample, model, tokenizer, id2label)
161
+ print(f"\n{sample} → {result['category']} ({result['confidence']*100:.1f}%)")
162
+
163
+ def main() -> None:
164
+ logger.info("Loading pipeline...")
165
+ ticket_dataframe, categories = load_dataset('/kaggle/input/datasets/adedayoadebayo23/support-ticket/support_tickets_realistic.csv')
166
+ ticket_dataframe, label2id, id2label = create_labels(ticket_dataframe, categories)
167
+ ticket_train, ticket_test, category_train, category_test = split_dataset(ticket_dataframe)
168
+ model, tokenizer = load_model_and_tokenizer(len(categories), label2id, id2label)
169
+ train_dataset, test_dataset = create_datasets(ticket_train, ticket_test, category_train, category_test, tokenizer)
170
+ trainer = train_model(model, train_dataset, test_dataset)
171
+ accuracy = evaluate_model(trainer, test_dataset, category_test, categories)
172
+ push_to_huggingface(model, tokenizer, repo_name="BERTweet-Support-Ticket-Classifier")
173
+ run_test_predictions(model, tokenizer, id2label)
174
+ logger.info(f"COMPLETED | Accuracy: {accuracy*100:.2f}%")
175
+ if __name__ == "__main__":
176
+ main()