verifyai / ml /train_model.py
Erich Schlaepfer
backend
87adc05
Raw
History Blame Contribute Delete
6.57 kB
"""
Treino de modelo de classificação multiclasse: Real (0) vs IA (1) vs CGI (2).
Treina XGBoost + Random Forest, compara, salva o melhor.
Uso:
python ml/train_model.py
python ml/train_model.py --input dataset_treino.csv --output ml/model.joblib
"""
import argparse
import sys
from pathlib import Path
import numpy as np
import pandas as pd
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report, confusion_matrix
import joblib
# Tenta importar XGBoost — fallback para GradientBoosting se não disponível
try:
from xgboost import XGBClassifier
HAS_XGB = True
except ImportError:
HAS_XGB = False
print("[AVISO] xgboost não instalado. Usando GradientBoosting do sklearn como fallback.")
# Colunas que NÃO são features (metadados)
META_COLUMNS = {"video_file", "frame_index", "label"}
def load_dataset(csv_path: str) -> tuple[pd.DataFrame, np.ndarray, np.ndarray, list[str]]:
"""Carrega CSV e separa features / labels."""
df = pd.read_csv(csv_path)
print(f"Dataset carregado: {len(df)} amostras")
print(f"Distribuição de classes:")
label_names = {0: "Real", 1: "IA", 2: "CGI"}
for label, count in df["label"].value_counts().sort_index().items():
print(f" {label} ({label_names.get(label, '?')}): {count}")
feature_cols = [c for c in df.columns if c not in META_COLUMNS]
X = df[feature_cols].values.astype(np.float32)
y = df["label"].values.astype(np.int32)
# Checa NaN/Inf
nan_mask = np.isnan(X) | np.isinf(X)
if nan_mask.any():
nan_count = nan_mask.sum()
print(f"[AVISO] {nan_count} valores NaN/Inf encontrados. Substituindo por 0.")
X = np.nan_to_num(X, nan=0.0, posinf=0.0, neginf=0.0)
return df, X, y, feature_cols
def build_models() -> dict:
"""Retorna dict de modelos candidatos."""
models = {}
# Random Forest — robusto, bom baseline
models["RandomForest"] = Pipeline([
("scaler", StandardScaler()),
("clf", RandomForestClassifier(
n_estimators=300,
max_depth=12,
min_samples_leaf=5,
class_weight="balanced",
random_state=42,
n_jobs=-1,
)),
])
# XGBoost ou GradientBoosting
if HAS_XGB:
models["XGBoost"] = Pipeline([
("scaler", StandardScaler()),
("clf", XGBClassifier(
n_estimators=500,
max_depth=8,
learning_rate=0.05,
subsample=0.8,
colsample_bytree=0.8,
objective="multi:softprob",
eval_metric="mlogloss",
random_state=42,
n_jobs=-1,
)),
])
else:
models["GradientBoosting"] = Pipeline([
("scaler", StandardScaler()),
("clf", GradientBoostingClassifier(
n_estimators=300,
max_depth=6,
learning_rate=0.05,
subsample=0.8,
random_state=42,
)),
])
return models
def evaluate_models(models: dict, X: np.ndarray, y: np.ndarray) -> str:
"""Avalia modelos com cross-validation e retorna o nome do melhor."""
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
results = {}
for name, model in models.items():
print(f"\n--- {name} ---")
scores = cross_val_score(model, X, y, cv=cv, scoring="f1_macro", n_jobs=-1)
mean_score = float(np.mean(scores))
std_score = float(np.std(scores))
results[name] = mean_score
print(f" F1-macro (5-fold): {mean_score:.4f} (+/- {std_score:.4f})")
print(f" Scores por fold: {[f'{s:.4f}' for s in scores]}")
best = max(results, key=results.get)
print(f"\nMelhor modelo: {best} (F1={results[best]:.4f})")
return best
def train_final(model, X: np.ndarray, y: np.ndarray, feature_cols: list[str]):
"""Treina o modelo final em todo o dataset e exibe relatório."""
model.fit(X, y)
y_pred = model.predict(X)
label_names = ["Real", "IA", "CGI"]
print("\n" + "=" * 50)
print("RELATÓRIO FINAL (treino completo)")
print("=" * 50)
print(classification_report(y, y_pred, target_names=label_names))
print("Matriz de Confusão:")
cm = confusion_matrix(y, y_pred)
print(f"{'':>8} {'Real':>8} {'IA':>8} {'CGI':>8}")
for i, row in enumerate(cm):
print(f"{label_names[i]:>8} {row[0]:>8} {row[1]:>8} {row[2]:>8}")
# Feature importance
clf = model.named_steps["clf"]
if hasattr(clf, "feature_importances_"):
importances = clf.feature_importances_
sorted_idx = np.argsort(importances)[::-1]
print("\nTop 10 features mais importantes:")
for rank, idx in enumerate(sorted_idx[:10]):
print(f" {rank+1}. {feature_cols[idx]}: {importances[idx]:.4f}")
return model
def main():
parser = argparse.ArgumentParser(description="Treina modelo de classificação multiclasse")
parser.add_argument("--input", default="dataset_treino.csv", help="CSV de entrada")
parser.add_argument("--output", default="ml/model.joblib", help="Arquivo do modelo salvo")
args = parser.parse_args()
if not Path(args.input).exists():
print(f"Arquivo não encontrado: {args.input}")
print("Execute primeiro: python ml/extract_features.py")
sys.exit(1)
df, X, y, feature_cols = load_dataset(args.input)
if len(np.unique(y)) < 2:
print("Dataset precisa de pelo menos 2 classes. Adicione mais vídeos.")
sys.exit(1)
print(f"\nFeatures: {len(feature_cols)}")
print(f"Shape: {X.shape}")
# Avalia modelos
models = build_models()
best_name = evaluate_models(models, X, y)
# Treina modelo final
best_model = models[best_name]
train_final(best_model, X, y, feature_cols)
# Salva
output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
save_data = {
"model": best_model,
"feature_columns": feature_cols,
"label_map": {0: "Real", 1: "IA", 2: "CGI"},
"model_name": best_name,
}
joblib.dump(save_data, output_path)
print(f"\nModelo salvo: {output_path}")
print(f"Para carregar: joblib.load('{output_path}')")
if __name__ == "__main__":
main()