Spaces:
Runtime error
Runtime error
Upload 10 files
Browse files- training/__init__.py +16 -0
- training/__pycache__/__init__.cpython-312.pyc +0 -0
- training/__pycache__/data_split.cpython-312.pyc +0 -0
- training/__pycache__/evaluation.cpython-312.pyc +0 -0
- training/__pycache__/model_io.cpython-312.pyc +0 -0
- training/__pycache__/trainer.cpython-312.pyc +0 -0
- training/data_split.py +14 -0
- training/evaluation.py +27 -0
- training/model_io.py +62 -0
- training/trainer.py +49 -0
training/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared training, evaluation and model-persistence logic."""
|
| 2 |
+
|
| 3 |
+
from training.data_split import split_data
|
| 4 |
+
from training.evaluation import evaluate_predictions, select_best_model
|
| 5 |
+
from training.model_io import load_model, save_model
|
| 6 |
+
from training.trainer import build_text_pipeline, train_and_evaluate_models
|
| 7 |
+
|
| 8 |
+
__all__ = [
|
| 9 |
+
"build_text_pipeline",
|
| 10 |
+
"evaluate_predictions",
|
| 11 |
+
"load_model",
|
| 12 |
+
"save_model",
|
| 13 |
+
"select_best_model",
|
| 14 |
+
"split_data",
|
| 15 |
+
"train_and_evaluate_models",
|
| 16 |
+
]
|
training/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (615 Bytes). View file
|
|
|
training/__pycache__/data_split.cpython-312.pyc
ADDED
|
Binary file (756 Bytes). View file
|
|
|
training/__pycache__/evaluation.cpython-312.pyc
ADDED
|
Binary file (1.37 kB). View file
|
|
|
training/__pycache__/model_io.cpython-312.pyc
ADDED
|
Binary file (2.75 kB). View file
|
|
|
training/__pycache__/trainer.cpython-312.pyc
ADDED
|
Binary file (1.62 kB). View file
|
|
|
training/data_split.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Train/test split."""
|
| 2 |
+
|
| 3 |
+
from sklearn.model_selection import train_test_split
|
| 4 |
+
|
| 5 |
+
from config.constants import LABEL_COLUMN, RANDOM_STATE, TEST_SIZE, TEXT_COLUMN
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def split_data(df, text_column=TEXT_COLUMN, label_column=LABEL_COLUMN,
|
| 9 |
+
test_size=TEST_SIZE, random_state=RANDOM_STATE):
|
| 10 |
+
"""Split the cleaned DataFrame into train/test text and labels."""
|
| 11 |
+
X = df[text_column]
|
| 12 |
+
y = df[label_column]
|
| 13 |
+
return train_test_split(X, y, test_size=test_size, shuffle=True,
|
| 14 |
+
random_state=random_state)
|
training/evaluation.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Metrics computed for every trained model."""
|
| 2 |
+
|
| 3 |
+
from sklearn.metrics import accuracy_score, classification_report
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def evaluate_predictions(y_test, y_pred):
|
| 7 |
+
"""Return the weighted F1/precision/recall plus accuracy for one model."""
|
| 8 |
+
report = classification_report(y_test, y_pred, output_dict=True)
|
| 9 |
+
f1 = report['weighted avg']['f1-score']
|
| 10 |
+
precision = report['weighted avg']['precision']
|
| 11 |
+
recall = report['weighted avg']['recall']
|
| 12 |
+
accuracy = accuracy_score(y_test, y_pred)
|
| 13 |
+
|
| 14 |
+
return {
|
| 15 |
+
'F1 Score': f1,
|
| 16 |
+
'Precision': precision,
|
| 17 |
+
'Recall': recall,
|
| 18 |
+
'Accuracy': accuracy,
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def select_best_model(results_df):
|
| 23 |
+
"""Return ``(best_model_name, best_model)`` - the row with the highest F1."""
|
| 24 |
+
best_model_name = results_df.loc[results_df['F1 Score'].idxmax(), 'Model']
|
| 25 |
+
best_model = results_df.loc[
|
| 26 |
+
results_df['Model'] == best_model_name, 'Trained Model'].values[0]
|
| 27 |
+
return best_model_name, best_model
|
training/model_io.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Saving and loading the trained pipeline."""
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
from datetime import datetime, timezone
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import joblib
|
| 8 |
+
|
| 9 |
+
from config.paths import ARTIFACTS_DIR, DEFAULT_MODEL_PATH
|
| 10 |
+
|
| 11 |
+
#: Machine-readable summary of a training run, read by the web API.
|
| 12 |
+
DEFAULT_METRICS_PATH = ARTIFACTS_DIR / "metrics.json"
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def save_model(model, path=DEFAULT_MODEL_PATH):
|
| 16 |
+
"""Persist ``model`` with joblib"""
|
| 17 |
+
path = Path(path)
|
| 18 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 19 |
+
joblib.dump(model, path)
|
| 20 |
+
return path
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def load_model(path=DEFAULT_MODEL_PATH):
|
| 24 |
+
"""Load a pipeline previously saved by :func:`save_model`."""
|
| 25 |
+
return joblib.load(path)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def save_metrics(results_df, best_model_name, path=DEFAULT_METRICS_PATH,
|
| 29 |
+
dataset=None, plots=None):
|
| 30 |
+
"""Write the run's scores to JSON so the web app can display them.
|
| 31 |
+
|
| 32 |
+
``run_pipeline`` prints this table and then throws it away; the API needs it
|
| 33 |
+
to show which models were compared and which one won, without hardcoding
|
| 34 |
+
numbers in the front end.
|
| 35 |
+
"""
|
| 36 |
+
path = Path(path)
|
| 37 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 38 |
+
|
| 39 |
+
scores = results_df.drop(columns=['Trained Model'])
|
| 40 |
+
models = [
|
| 41 |
+
{
|
| 42 |
+
'model': row['Model'],
|
| 43 |
+
'f1': float(row['F1 Score']),
|
| 44 |
+
'precision': float(row['Precision']),
|
| 45 |
+
'recall': float(row['Recall']),
|
| 46 |
+
'accuracy': float(row['Accuracy']),
|
| 47 |
+
'is_best': row['Model'] == best_model_name,
|
| 48 |
+
}
|
| 49 |
+
for _, row in scores.iterrows()
|
| 50 |
+
]
|
| 51 |
+
|
| 52 |
+
payload = {
|
| 53 |
+
'best_model': best_model_name,
|
| 54 |
+
'selected_by': 'highest weighted F1 on the test set',
|
| 55 |
+
'generated_at': datetime.now(timezone.utc).isoformat(timespec='seconds'),
|
| 56 |
+
'dataset': dataset or {},
|
| 57 |
+
'models': models,
|
| 58 |
+
'plots': plots or {},
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
path.write_text(json.dumps(payload, indent=2), encoding='utf-8')
|
| 62 |
+
return path
|
training/trainer.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Training loop shared by all four classifiers."""
|
| 2 |
+
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
| 5 |
+
from sklearn.pipeline import Pipeline
|
| 6 |
+
|
| 7 |
+
from training.evaluation import evaluate_predictions, select_best_model
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def build_text_pipeline(model):
|
| 11 |
+
"""Wrap ``model`` behind a ``TfidfVectorizer``."""
|
| 12 |
+
return Pipeline([('vect', TfidfVectorizer()),
|
| 13 |
+
('clf', model)])
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def train_and_evaluate_models(models, X_train, X_test, y_train, y_test):
|
| 17 |
+
"""Train every model in ``models`` and compare them on the test set."""
|
| 18 |
+
results = []
|
| 19 |
+
|
| 20 |
+
for model_name, model in models.items():
|
| 21 |
+
# Create a text classification pipeline with TF-IDF vectorizer and the specified model
|
| 22 |
+
text_clf = build_text_pipeline(model)
|
| 23 |
+
|
| 24 |
+
# Train the model
|
| 25 |
+
text_clf.fit(X_train, y_train)
|
| 26 |
+
|
| 27 |
+
# Make predictions on the test set
|
| 28 |
+
y_pred = text_clf.predict(X_test)
|
| 29 |
+
|
| 30 |
+
# Evaluate the model
|
| 31 |
+
metrics = evaluate_predictions(y_test, y_pred)
|
| 32 |
+
|
| 33 |
+
# Store the results in a dictionary
|
| 34 |
+
result_dict = {
|
| 35 |
+
'Model': model_name,
|
| 36 |
+
'Trained Model': text_clf, # Store the trained model
|
| 37 |
+
**metrics,
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
# Append the results to the list
|
| 41 |
+
results.append(result_dict)
|
| 42 |
+
|
| 43 |
+
# Convert the list of dictionaries to a DataFrame
|
| 44 |
+
results_df = pd.DataFrame(results)
|
| 45 |
+
|
| 46 |
+
# Find the best model based on the highest F1 Score
|
| 47 |
+
best_model_name, best_model = select_best_model(results_df)
|
| 48 |
+
|
| 49 |
+
return best_model_name, best_model, results_df
|