Spaces:
Runtime error
Runtime error
| 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 | |
| # ===================================================== | |
| 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() |