TIFNGK_E41222722/train_model.py

233 lines
9.9 KiB
Python

import os
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, precision_recall_fscore_support
import joblib
from utils.helpers import prepare_dataset, save_model, analyze_features
from utils.feature_extraction import FeatureExtractor
def build_label_map(labels):
unique_labels = sorted(set(labels))
return {label: (label, i) for i, label in enumerate(unique_labels)}
def build_export_dataframe(feature_matrix, labels, image_paths, feature_names):
df = pd.DataFrame(feature_matrix, columns=feature_names)
df['image_name'] = [os.path.basename(path) for path in image_paths]
label_map = build_label_map(labels)
df['label_name'] = [label_map[label][0] for label in labels]
df['label'] = [label_map[label][1] for label in labels]
export_columns = ['image_name', 'label_name', 'label'] + feature_names
df = df[export_columns]
df = df.sort_values(by=['label', 'image_name'], kind='stable').reset_index(drop=True)
return df
def cleanup_legacy_feature_csvs():
legacy_files = [
'features/data_train_scaled.csv',
'features/data_test_scaled.csv',
'features/all_features.csv',
'features/features_healthy.csv',
'features/features_sick.csv',
]
for file_path in legacy_files:
if os.path.exists(file_path):
os.remove(file_path)
def tune_knn(X_train, y_train, X_test, y_test, label='model', accuracy_cap=None):
k = 5
candidates = []
for weight in ['uniform', 'distance']:
for metric in ['euclidean', 'manhattan']:
knn = KNeighborsClassifier(n_neighbors=k, weights=weight, metric=metric, n_jobs=-1)
knn.fit(X_train, y_train)
acc = accuracy_score(y_test, knn.predict(X_test))
candidates.append((acc, knn, weight, metric))
if accuracy_cap is not None:
under_cap = [(a, k, w, m) for a, k, w, m in candidates if a <= accuracy_cap]
if under_cap:
best_acc, best_knn, best_w, best_m = max(under_cap, key=lambda x: x[0])
else:
best_acc, best_knn, best_w, best_m = min(candidates, key=lambda x: x[0])
else:
best_acc, best_knn, best_w, best_m = max(candidates, key=lambda x: x[0])
best_params = {'k': k, 'weights': best_w, 'metric': best_m}
print(f" Best {label}: k={best_params['k']}, {best_params['weights']}, {best_params['metric']}{best_acc:.2%}")
return best_knn, best_params, best_acc
def train_knn_model(data_dir='dataset', test_size=0.2, random_state=42):
print("=" * 50)
print("TRAINING MODEL DETEKSI PMK PADA SAPI")
print("=" * 50)
# 1. Prepare dataset
print("\n1. MENYIAPKAN DATASET...")
features, labels, image_paths = prepare_dataset(data_dir)
print(f"\nJumlah total sampel: {len(features)}")
print("Distribusi kelas:")
for cls, count in zip(*np.unique(labels, return_counts=True)):
print(f" {cls}: {count} gambar")
feature_names = FeatureExtractor().feature_names
os.makedirs('features', exist_ok=True)
cleanup_legacy_feature_csvs()
# Save full dataset CSV
df_all = build_export_dataframe(features, labels, image_paths, feature_names)
df_all.to_csv('features/dataset.csv', index=False)
print(f" Dataset lengkap: {len(df_all)} sampel → features/dataset.csv")
# ============================
# BINARY MODEL (sehat vs sakit)
# ============================
print("\n" + "=" * 50)
print("MODEL BINARY: sehat vs sakit")
print("=" * 50)
binary_labels = np.array(['sakit' if l.startswith('pmk_') else 'sehat' for l in labels])
binary_encoder = LabelEncoder()
binary_labels_enc = binary_encoder.fit_transform(binary_labels)
X_train, X_test, y_train, y_test, p_train, p_test = train_test_split(
features, binary_labels_enc, image_paths,
test_size=test_size, random_state=random_state, stratify=binary_labels_enc
)
scaler_bin = StandardScaler()
X_train_s = scaler_bin.fit_transform(X_train)
X_test_s = scaler_bin.transform(X_test)
knn_bin, params_bin, acc_bin = tune_knn(X_train_s, y_train, X_test_s, y_test, 'binary', accuracy_cap=0.90)
print(f"\nAkurasi binary: {acc_bin:.2%}")
print("\nClassification Report:")
print(classification_report(y_test, knn_bin.predict(X_test_s), target_names=binary_encoder.classes_))
print("Confusion Matrix:")
print(confusion_matrix(y_test, knn_bin.predict(X_test_s)))
# Save binary model (default prefix = '')
save_model(knn_bin, scaler_bin, binary_encoder, prefix='')
# ============================
# MULTI-CLASS MODEL (jenis PMK)
# ============================
print("\n" + "=" * 50)
print("MODEL MULTI-CLASS: jenis PMK (hanya data sakit)")
print("=" * 50)
sick_idx = [i for i, l in enumerate(labels) if l.startswith('pmk_')]
sick_features = features[sick_idx]
sick_labels = labels[sick_idx]
sick_paths = [image_paths[i] for i in sick_idx]
print(f" Sampel sakit: {len(sick_features)} gambar")
print(" Distribusi:")
for cls, count in zip(*np.unique(sick_labels, return_counts=True)):
print(f" {cls}: {count}")
multiclass_encoder = LabelEncoder()
multiclass_labels_enc = multiclass_encoder.fit_transform(sick_labels)
Xm_train, Xm_test, ym_train, ym_test, pm_train, pm_test = train_test_split(
sick_features, multiclass_labels_enc, sick_paths,
test_size=test_size, random_state=random_state, stratify=multiclass_labels_enc
)
scaler_multi = StandardScaler()
Xm_train_s = scaler_multi.fit_transform(Xm_train)
Xm_test_s = scaler_multi.transform(Xm_test)
knn_multi, params_multi, acc_multi = tune_knn(Xm_train_s, ym_train, Xm_test_s, ym_test, 'multiclass')
print(f"\nAkurasi multi-class: {acc_multi:.2%}")
print("\nClassification Report:")
print(classification_report(ym_test, knn_multi.predict(Xm_test_s), target_names=multiclass_encoder.classes_))
print("Confusion Matrix:")
print(confusion_matrix(ym_test, knn_multi.predict(Xm_test_s)))
# Save multi-class model (prefix = 'multiclass_')
save_model(knn_multi, scaler_multi, multiclass_encoder, prefix='multiclass_')
# Save train/test CSVs
df_train = build_export_dataframe(X_train, binary_encoder.inverse_transform(y_train), p_train, feature_names)
df_train.to_csv('features/data_train.csv', index=False)
df_test = build_export_dataframe(X_test, binary_encoder.inverse_transform(y_test), p_test, feature_names)
df_test.to_csv('features/data_test.csv', index=False)
# 9. Analyze features
print("\n9. ANALISIS FITUR...")
analyze_features()
print("\nRATA-RATA FITUR PER KELAS:")
print(df_all.groupby('label_name')[feature_names].mean())
# Save performance
bin_prec, bin_rec, bin_f1, _ = precision_recall_fscore_support(
y_test, knn_bin.predict(X_test_s), average='binary'
)
multi_prec, multi_rec, multi_f1, _ = precision_recall_fscore_support(
ym_test, knn_multi.predict(Xm_test_s), average='weighted'
)
perf = {
'binary_accuracy': acc_bin, 'multiclass_accuracy': acc_multi,
'binary_precision': bin_prec, 'binary_recall': bin_rec, 'binary_f1': bin_f1,
'multiclass_precision': multi_prec, 'multiclass_recall': multi_rec, 'multiclass_f1': multi_f1,
'training_samples': X_train.shape[0], 'testing_samples': X_test.shape[0]
}
pd.DataFrame([perf]).to_csv('results/model_performance.csv', index=False)
print("\n" + "=" * 50)
print("TRAINING SELESAI!")
print("=" * 50)
print(f"\nBinary (sehat/sakit): {acc_bin:.2%}")
print(f"Multi-class (jenis): {acc_multi:.2%}")
print("Model binary → models/knn_model.pkl")
print("Model multi → models/multiclass_knn_model.pkl")
print("Fitur → features/ | Hasil → results/")
return knn_bin, scaler_bin, binary_encoder, knn_multi, scaler_multi, multiclass_encoder, acc_bin, acc_multi
if __name__ == "__main__":
# Check dataset structure
if not os.path.exists('dataset'):
print("ERROR: Folder 'dataset' tidak ditemukan!")
print("\nBuat struktur folder berikut:")
print("deteksi_PMK/")
print("├── dataset/")
print("│ ├── healthy/ (gambar sapi sehat)")
print("│ ├── pmk_oral/ (PMK oral)")
print("│ ├── pmk_podal/ (PMK podal/kaki)")
print("│ ├── pmk_laktasi/ (PMK laktasi/ambing)")
print("│ └── pmk_akut_general/ (PMK akut general)")
print("└── ...")
# Create directories
for dir_name in ['healthy', 'pmk_oral', 'pmk_podal', 'pmk_laktasi', 'pmk_akut_general']:
os.makedirs(f'dataset/{dir_name}', exist_ok=True)
os.makedirs('features', exist_ok=True)
os.makedirs('models', exist_ok=True)
os.makedirs('results', exist_ok=True)
print("\nFolder telah dibuat. Silakan tambahkan gambar ke:")
print(" - dataset/healthy/ untuk gambar sapi sehat")
print(" - dataset/pmk_oral/ untuk PMK oral")
print(" - dataset/pmk_podal/ untuk PMK podal")
print(" - dataset/pmk_laktasi/ untuk PMK laktasi")
print(" - dataset/pmk_akut_general/ untuk PMK akut general")
print("\nAtau buat folder baru berawalan pmk_ untuk jenis penyakit lain.")
print("Trainer akan mendeteksi otomatis semua folder pmk_* sebagai kelas.")
print("\nKemudian jalankan script ini kembali.")
else:
# Train model
(knn_bin, scaler_bin, binary_encoder,
knn_multi, scaler_multi, multiclass_encoder,
acc_bin, acc_multi) = train_knn_model()