adaptive-wafer-rl / run_fair_adversarial_validation.py
DHDRL's picture
Update run_fair_adversarial_validation.py
bc36a84 verified
Raw
History Blame Contribute Delete
6.48 kB
"""
RUN FAIR ADVERSARIAL VALIDATION
================================
Usage:
python run_fair_adversarial_validation.py \
--model_path path/to/model.zip \
--num_episodes 100 \
--gpu \
--output_dir ./fair_test_results \
--seed # \
--start_test #
"""
import argparse
import sys
from pathlib import Path
import torch
import numpy as np
from gru_belief_policy_v3_agnostic import GRUAugmentedFeaturesExtractor
from gru_env_wrappers import GRUStateManager
from masked_dqn_policy import MaskedDQN, MaskedDQNPolicy
from stable_baselines3 import DQN
# Add parent directory to path for imports
sys.path.append(str(Path(__file__).parent))
from fair_adversarial_validation_framework import FairAdversarialTestSuite
from mems_adaptive_inspection_env_curriculum_v5_SOFT_RESET_STABLE import (
ResolutionAgnosticInspectionEnv,
InspectionConfig,
NaNSafetyWrapper
)
def parse_args():
"""Parse command line arguments"""
parser = argparse.ArgumentParser(
description='Run fair adversarial validation on MEMS inspection model'
)
parser.add_argument(
'--model_path',
type=str,
required=True,
help='Path to trained model (.zip file)'
)
parser.add_argument(
'--num_episodes',
type=int,
default=50,
help='Number of episodes per test case (default: 50)'
)
parser.add_argument(
'--output_dir',
type=str,
default='./fair_adversarial_results',
help='Output directory for results'
)
parser.add_argument(
'--grid_size',
type=int,
default=64,
help='Environment grid size (default: 64)'
)
parser.add_argument(
'--budget',
type=int,
default=3000,
help='Inspection budget (default: 3000)'
)
parser.add_argument(
'--gpu',
action='store_true',
help='Use GPU acceleration for environment'
)
parser.add_argument(
'--seed',
type=int,
default=42,
help='Random seed (default: 42)'
)
# Resume support
parser.add_argument(
'--start_test',
type=int,
default=1,
help='Start from this test number (1-based). Use to resume after timeout/crash.'
)
return parser.parse_args()
def main():
"""Main execution"""
args = parse_args()
print("""
╔════════════════════════════════════════════════════════════════╗
β•‘ β•‘
β•‘ FAIR ADVERSARIAL VALIDATION FOR MEMS INSPECTION DQN β•‘
β•‘ β•‘
β•‘ Testing model with realistic, interpretable scenarios β•‘
β•‘ β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
""")
# Set random seeds
torch.manual_seed(args.seed)
np.random.seed(args.seed)
# Load model
print(f"\n{'='*80}")
print(f"Loading Model from: {args.model_path}")
print(f"{'='*80}")
if not Path(args.model_path).exists():
print(f"❌ Model file not found: {args.model_path}")
sys.exit(1)
custom_objects = {
"features_extractor_class": GRUAugmentedFeaturesExtractor,
"policy_class": MaskedDQNPolicy,
}
model = MaskedDQN.load(
args.model_path,
custom_objects=custom_objects,
device="cuda" if args.gpu else "cpu"
)
print(f"βœ… Model loaded successfully")
# Create environment configuration
print(f"\n{'='*80}")
print(f"Creating Environment Configuration")
print(f"{'='*80}")
config = InspectionConfig(
grid_size=args.grid_size,
wafer_diameter_mm=300.0,
die_size_mm=5.0,
inspection_budget=args.budget,
inspection_cost=1.0,
defect_catch_value=100.0,
false_alarm_penalty=2.0,
miss_penalty=50.0,
max_steps=None,
prior_belief=0.1,
belief_update_radius=3,
belief_increase_rate=0.3,
belief_decrease_rate=0.2,
soft_reset=True,
seed=args.seed
)
print(f"βœ… Configuration created")
print(f" Grid size: {config.grid_size}x{config.grid_size}")
print(f" Budget: {config.inspection_budget}")
print(f" GPU acceleration: {args.gpu and torch.cuda.is_available()}")
# Environment factory
def env_factory():
import copy
fresh_config = copy.deepcopy(config)
e = ResolutionAgnosticInspectionEnv(config=fresh_config, use_gpu=args.gpu)
e = NaNSafetyWrapper(e)
e = GRUStateManager(e, policy=model.policy)
return e
# Base env for cleanup
env = env_factory()
# Create test suite with resume support
print(f"\n{'='*80}")
print(f"Initializing Fair Test Suite")
print(f"{'='*80}")
test_suite = FairAdversarialTestSuite(
model=model,
env_factory=env_factory,
output_dir=args.output_dir,
start_test=args.start_test
)
print(f"Running {len(test_suite.test_cases)} fair tests")
print(f" - Production scenarios: {sum(1 for tc in test_suite.test_cases if tc.difficulty == 'production')}")
print(f" - Stress tests: {sum(1 for tc in test_suite.test_cases if tc.difficulty == 'stress')}")
print(f" - Extreme tests: {sum(1 for tc in test_suite.test_cases if tc.difficulty == 'extreme')}")
if args.start_test > 1:
print(f" - Resuming from Test #{args.start_test}")
# Run tests
try:
test_suite.run_all_tests(num_episodes_per_test=args.num_episodes)
print(f"\n{'='*80}")
print(f"βœ… FAIR ADVERSARIAL VALIDATION COMPLETE")
print(f"{'='*80}")
print(f"Results saved to: {args.output_dir}")
print(f"Review the files in {args.output_dir}")
print(f"{'='*80}\n")
except KeyboardInterrupt:
print("\n\n⚠️ Validation interrupted by user")
sys.exit(1)
except Exception as e:
print(f"\n\n❌ Error during validation: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
finally:
env.close()
if __name__ == "__main__":
main()