import os import numpy as np import tensorflow as tf from tensorflow.keras.preprocessing.image import ImageDataGenerator from tensorflow.keras.applications import EfficientNetB0 from tensorflow.keras.applications.efficientnet import preprocess_input from tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout, BatchNormalization from tensorflow.keras.models import Model from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint from sklearn.utils.class_weight import compute_class_weight # --- CONFIGURATION --- BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # IMPORTANT: Pointing to the new split dataset folder DATASET_DIR = os.path.join(BASE_DIR, '..', '..', '..', 'dataset', 'mission_dataset_split', 'train') MODEL_SAVE_PATH = os.path.join(BASE_DIR, '..', '..', 'mission_model.h5') LABELS_SAVE_PATH = os.path.join(BASE_DIR, '..', '..', 'labels.txt') IMG_SIZE = (224, 224) BATCH_SIZE = 32 EPOCHS_INITIAL = 20 EPOCHS_FINETUNE = 15 LR_INITIAL = 1e-3 LR_FINETUNE = 1e-5 def build_generators(): print("šŸ“ø Preparing Image Generators...") train_datagen = ImageDataGenerator( preprocessing_function=preprocess_input, rotation_range=30, width_shift_range=0.2, height_shift_range=0.2, horizontal_flip=True, brightness_range=[0.7, 1.3], zoom_range=0.2, validation_split=0.2 # 20% of the train/ folder becomes validation ) train_generator = train_datagen.flow_from_directory( DATASET_DIR, target_size=IMG_SIZE, batch_size=BATCH_SIZE, class_mode='categorical', subset='training', shuffle=True ) validation_generator = train_datagen.flow_from_directory( DATASET_DIR, target_size=IMG_SIZE, batch_size=BATCH_SIZE, class_mode='categorical', subset='validation', shuffle=False ) return train_generator, validation_generator def get_class_weights(train_generator): """ Calculates class weights to handle imbalanced datasets. This stops the AI from being biased toward the majority class. """ print("āš–ļø Calculating Class Weights for balanced training...") class_indices = train_generator.class_indices classes = train_generator.classes weights = compute_class_weight( class_weight='balanced', classes=np.unique(classes), y=classes ) class_weights = dict(enumerate(weights)) print(" Weights applied:") for cls_name, cls_idx in class_indices.items(): print(f" - {cls_name}: {class_weights[cls_idx]:.2f}") return class_weights def build_model(num_classes): print("🧠 Building Model (EfficientNetB0)...") base_model = EfficientNetB0( weights='imagenet', include_top=False, input_shape=IMG_SIZE + (3,) ) base_model.trainable = False x = base_model.output x = GlobalAveragePooling2D()(x) x = BatchNormalization()(x) x = Dropout(0.3)(x) x = Dense(256, activation='relu')(x) x = BatchNormalization()(x) x = Dropout(0.3)(x) predictions = Dense(num_classes, activation='softmax')(x) model = Model(inputs=base_model.input, outputs=predictions) return model, base_model def get_callbacks(phase_name): return [ EarlyStopping(monitor='val_accuracy', patience=5, restore_best_weights=True, verbose=1), ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=3, min_lr=1e-7, verbose=1), ModelCheckpoint(filepath=MODEL_SAVE_PATH, monitor='val_accuracy', save_best_only=True, verbose=1) ] def train_brain(): print("šŸš€ Initializing Mission 17 AI Training v2 (Optimized)...") if not os.path.exists(DATASET_DIR): print(f"āŒ ERROR: Training Dataset not found at {DATASET_DIR}") print(" Did you run scripts/testing/split_dataset.py first?") return train_generator, validation_generator = build_generators() # Save Labels class_names = list(train_generator.class_indices.keys()) with open(LABELS_SAVE_PATH, 'w') as f: for name in class_names: f.write(name + '\n') num_classes = len(class_names) # Get Class Weights class_weights = get_class_weights(train_generator) model, base_model = build_model(num_classes) # --- PHASE 1 --- print("\n" + "="*50) print("šŸ‹ļø PHASE 1: Training Top Layers (Base Frozen)") print("="*50) model.compile(optimizer=Adam(learning_rate=LR_INITIAL), loss='categorical_crossentropy', metrics=['accuracy']) model.fit( train_generator, epochs=EPOCHS_INITIAL, validation_data=validation_generator, class_weight=class_weights, # Apply weights! callbacks=get_callbacks('phase1') ) # --- PHASE 2 --- print("\n" + "="*50) print("šŸ”¬ PHASE 2: Fine-Tuning Top Base Layers") print("="*50) base_model.trainable = True for layer in base_model.layers[:150]: layer.trainable = False model.compile(optimizer=Adam(learning_rate=LR_FINETUNE), loss='categorical_crossentropy', metrics=['accuracy']) model.fit( train_generator, epochs=EPOCHS_FINETUNE, validation_data=validation_generator, class_weight=class_weights, # Apply weights! callbacks=get_callbacks('phase2') ) print(f"\nāœ… Training complete! Model saved to {MODEL_SAVE_PATH}") if __name__ == '__main__': train_brain()