rishigupta04's picture
Upload 27 files
1399137 verified
Raw
History Blame Contribute Delete
14.8 kB
import json
import logging
from pathlib import Path
import joblib
import numpy as np
import pandas as pd
import torch
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import (
accuracy_score,
precision_score,
recall_score,
f1_score,
classification_report,
confusion_matrix
)
from torch.utils.data import Dataset, DataLoader
from transformers import (
AutoTokenizer,
AutoModelForSequenceClassification
)
# =====================================================
# LOGGING
# =====================================================
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# =====================================================
# PATHS
# =====================================================
BASE_DIR = Path("artifacts")
MODEL_DIR = BASE_DIR / "models"
FEATURE_DIR = BASE_DIR / "features"
OUTPUT_DIR = BASE_DIR / "evaluation"
OUTPUT_DIR.mkdir(
parents=True,
exist_ok=True
)
TEST_DATA_PATH = (
Path("data/processed/test.parquet")
)
MODEL_PATH = (
MODEL_DIR / "best_model.pt"
)
MODEL_CONFIG_PATH = (
MODEL_DIR / "model_config.json"
)
LABEL_ENCODER_PATH = (
FEATURE_DIR / "label_encoder.pkl"
)
TOKENIZER_PATH = (
FEATURE_DIR / "tokenizer"
)
# =====================================================
# GPU
# =====================================================
DEVICE = (
"cuda"
if torch.cuda.is_available()
else "cpu"
)
logger.info(
f"Using Device: {DEVICE}"
)
# =====================================================
# DATASET
# =====================================================
class EvaluationDataset(Dataset):
def __init__(
self,
texts,
labels,
tokenizer,
max_length=192
):
self.texts = texts
self.labels = labels
self.tokenizer = tokenizer
self.max_length = max_length
def __len__(self):
return len(self.texts)
def __getitem__(self, idx):
encoding = self.tokenizer(
str(self.texts[idx]),
truncation=True,
padding="max_length",
max_length=self.max_length,
return_tensors="pt"
)
return {
"input_ids":
encoding["input_ids"]
.squeeze(0),
"attention_mask":
encoding["attention_mask"]
.squeeze(0),
"label":
torch.tensor(
self.labels[idx],
dtype=torch.long
)
}
# =====================================================
# LOAD ARTIFACTS
# =====================================================
def load_tokenizer():
logger.info(
"Loading tokenizer..."
)
tokenizer = AutoTokenizer.from_pretrained(
TOKENIZER_PATH
)
return tokenizer
def load_label_encoder():
logger.info(
"Loading LabelEncoder..."
)
return joblib.load(
LABEL_ENCODER_PATH
)
def load_model():
logger.info(
"Loading model config..."
)
with open(
MODEL_CONFIG_PATH,
"r"
) as f:
config = json.load(f)
model_name = config["model_name"]
logger.info(
f"Model: {model_name}"
)
model = (
AutoModelForSequenceClassification
.from_pretrained(
model_name
)
)
logger.info(
"Loading best_model.pt..."
)
state_dict = torch.load(
MODEL_PATH,
map_location=DEVICE
)
model.load_state_dict(
state_dict
)
model.to(
DEVICE
)
model.eval()
logger.info(
"Model Loaded Successfully"
)
return model
# =====================================================
# TEST DATA
# =====================================================
def load_test_data():
logger.info(
"Loading test dataset..."
)
df = pd.read_parquet(
TEST_DATA_PATH
)
logger.info(
f"Test Shape: {df.shape}"
)
return df
# =====================================================
# DATALOADER
# =====================================================
def build_dataloader(
df,
tokenizer,
label_encoder,
batch_size=32
):
labels = label_encoder.transform(
df["Sentiment"]
)
dataset = EvaluationDataset(
texts=df["CommentText"].tolist(),
labels=labels,
tokenizer=tokenizer
)
loader = DataLoader(
dataset,
batch_size=batch_size,
shuffle=False,
num_workers=2,
pin_memory=True
)
return loader
# =====================================================
# INFERENCE
# =====================================================
@torch.no_grad()
def predict(
model,
dataloader
):
all_preds = []
all_probs = []
all_labels = []
logger.info(
"Running inference..."
)
for batch in dataloader:
input_ids = (
batch["input_ids"]
.to(DEVICE)
)
attention_mask = (
batch["attention_mask"]
.to(DEVICE)
)
labels = (
batch["label"]
.cpu()
.numpy()
)
outputs = model(
input_ids=input_ids,
attention_mask=attention_mask
)
logits = outputs.logits
probs = torch.softmax(
logits,
dim=1
)
preds = torch.argmax(
probs,
dim=1
)
all_preds.extend(
preds.cpu().numpy()
)
all_probs.extend(
probs.cpu().numpy()
)
all_labels.extend(
labels
)
return (
np.array(all_labels),
np.array(all_preds),
np.array(all_probs)
)
# =====================================================
# METRICS
# =====================================================
def compute_metrics(
y_true,
y_pred
):
metrics = {
"accuracy":
float(
accuracy_score(
y_true,
y_pred
)
),
"precision_macro":
float(
precision_score(
y_true,
y_pred,
average="macro"
)
),
"recall_macro":
float(
recall_score(
y_true,
y_pred,
average="macro"
)
),
"f1_macro":
float(
f1_score(
y_true,
y_pred,
average="macro"
)
),
"f1_weighted":
float(
f1_score(
y_true,
y_pred,
average="weighted"
)
)
}
return metrics
# =====================================================
# REPORTS
# =====================================================
def save_classification_report(
y_true,
y_pred,
label_encoder
):
report = classification_report(
y_true,
y_pred,
target_names=label_encoder.classes_
)
report_path = (
OUTPUT_DIR /
"classification_report.txt"
)
with open(
report_path,
"w",
encoding="utf-8"
) as f:
f.write(report)
logger.info(
"classification_report.txt saved"
)
return report
# =====================================================
# CONFUSION MATRIX
# =====================================================
def save_confusion_matrix(
y_true,
y_pred,
label_encoder
):
cm = confusion_matrix(
y_true,
y_pred
)
plt.figure(
figsize=(8, 6)
)
sns.heatmap(
cm,
annot=True,
fmt="d",
cmap="Blues",
xticklabels=label_encoder.classes_,
yticklabels=label_encoder.classes_
)
plt.xlabel(
"Predicted"
)
plt.ylabel(
"Actual"
)
plt.title(
"Confusion Matrix"
)
plt.tight_layout()
plt.savefig(
OUTPUT_DIR /
"confusion_matrix.png"
)
plt.close()
logger.info(
"confusion_matrix.png saved"
)
# =====================================================
# NORMALIZED CONFUSION MATRIX
# =====================================================
def save_normalized_confusion_matrix(
y_true,
y_pred,
label_encoder
):
cm = confusion_matrix(
y_true,
y_pred
)
cm = (
cm.astype(float)
/
cm.sum(axis=1)[:, np.newaxis]
)
plt.figure(
figsize=(8, 6)
)
sns.heatmap(
cm,
annot=True,
fmt=".2f",
cmap="Greens",
xticklabels=label_encoder.classes_,
yticklabels=label_encoder.classes_
)
plt.xlabel(
"Predicted"
)
plt.ylabel(
"Actual"
)
plt.title(
"Normalized Confusion Matrix"
)
plt.tight_layout()
plt.savefig(
OUTPUT_DIR /
"normalized_confusion_matrix.png"
)
plt.close()
logger.info(
"normalized_confusion_matrix.png saved"
)
# =====================================================
# PREDICTION DISTRIBUTION
# =====================================================
def save_prediction_distribution(
y_pred,
label_encoder
):
labels = (
label_encoder.inverse_transform(
y_pred
)
)
counts = (
pd.Series(labels)
.value_counts()
.sort_index()
)
plt.figure(
figsize=(8, 5)
)
counts.plot(
kind="bar"
)
plt.title(
"Prediction Distribution"
)
plt.ylabel(
"Count"
)
plt.tight_layout()
plt.savefig(
OUTPUT_DIR /
"prediction_distribution.png"
)
plt.close()
logger.info(
"prediction_distribution.png saved"
)
# =====================================================
# CONFIDENCE DISTRIBUTION
# =====================================================
def save_confidence_distribution(
probabilities
):
confidence = (
probabilities.max(axis=1)
)
plt.figure(
figsize=(8, 5)
)
plt.hist(
confidence,
bins=30
)
plt.title(
"Prediction Confidence Distribution"
)
plt.xlabel(
"Confidence"
)
plt.ylabel(
"Frequency"
)
plt.tight_layout()
plt.savefig(
OUTPUT_DIR /
"confidence_distribution.png"
)
plt.close()
logger.info(
"confidence_distribution.png saved"
)
# =====================================================
# MISCLASSIFIED SAMPLES
# =====================================================
def save_misclassified_samples(
df,
y_true,
y_pred,
probabilities,
label_encoder
):
confidence = (
probabilities.max(axis=1)
)
actual = (
label_encoder.inverse_transform(
y_true
)
)
predicted = (
label_encoder.inverse_transform(
y_pred
)
)
mask = (
actual != predicted
)
errors = pd.DataFrame({
"CommentText":
df.loc[
mask,
"CommentText"
].values,
"Actual":
actual[mask],
"Predicted":
predicted[mask],
"Confidence":
confidence[mask]
})
errors = errors.sort_values(
by="Confidence",
ascending=False
)
errors.to_csv(
OUTPUT_DIR /
"misclassified_samples.csv",
index=False
)
logger.info(
"misclassified_samples.csv saved"
)
# =====================================================
# SAVE SUMMARY
# =====================================================
def save_summary(
metrics
):
summary_path = (
OUTPUT_DIR /
"evaluation_summary.json"
)
with open(
summary_path,
"w"
) as f:
json.dump(
metrics,
f,
indent=4
)
logger.info(
"evaluation_summary.json saved"
)
# =====================================================
# MAIN
# =====================================================
def main():
tokenizer = load_tokenizer()
label_encoder = (
load_label_encoder()
)
model = load_model()
test_df = load_test_data()
dataloader = build_dataloader(
test_df,
tokenizer,
label_encoder,
batch_size=32
)
y_true, y_pred, probs = predict(
model,
dataloader
)
metrics = compute_metrics(
y_true,
y_pred
)
save_summary(
metrics
)
save_classification_report(
y_true,
y_pred,
label_encoder
)
save_confusion_matrix(
y_true,
y_pred,
label_encoder
)
save_normalized_confusion_matrix(
y_true,
y_pred,
label_encoder
)
save_prediction_distribution(
y_pred,
label_encoder
)
save_confidence_distribution(
probs
)
save_misclassified_samples(
test_df,
y_true,
y_pred,
probs,
label_encoder
)
logger.info(
"=" * 50
)
logger.info(
"Evaluation Complete"
)
logger.info(
json.dumps(
metrics,
indent=4
)
)
logger.info(
"=" * 50
)
if __name__ == "__main__":
main()