Baseline
Browse files- Dockerfile +12 -0
- README.md +189 -11
- api/__init__.py +0 -0
- api/__pycache__/__init__.cpython-312.pyc +0 -0
- api/__pycache__/server.cpython-312.pyc +0 -0
- api/server.py +310 -0
- baseline/__init__.py +0 -0
- baseline/run_baseline.py +242 -0
- kaggle_sim_env/__init__.py +15 -0
- kaggle_sim_env/__pycache__/__init__.cpython-312.pyc +0 -0
- kaggle_sim_env/__pycache__/environment.cpython-312.pyc +0 -0
- kaggle_sim_env/__pycache__/grader.cpython-312.pyc +0 -0
- kaggle_sim_env/__pycache__/hints.cpython-312.pyc +0 -0
- kaggle_sim_env/__pycache__/leaderboard.cpython-312.pyc +0 -0
- kaggle_sim_env/__pycache__/models.cpython-312.pyc +0 -0
- kaggle_sim_env/__pycache__/rewards.cpython-312.pyc +0 -0
- kaggle_sim_env/__pycache__/tasks.cpython-312.pyc +0 -0
- kaggle_sim_env/environment.py +330 -0
- kaggle_sim_env/grader.py +145 -0
- kaggle_sim_env/hints.py +30 -0
- kaggle_sim_env/leaderboard.py +62 -0
- kaggle_sim_env/models.py +389 -0
- kaggle_sim_env/rewards.py +97 -0
- kaggle_sim_env/tasks.py +1031 -0
- openenv.yaml +79 -0
- requirements.txt +5 -0
Dockerfile
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY . .
|
| 9 |
+
|
| 10 |
+
EXPOSE 7860
|
| 11 |
+
|
| 12 |
+
CMD ["uvicorn", "api.server:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,11 +1,189 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
---
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# KaggleSimEnv v3
|
| 2 |
+
|
| 3 |
+
Production-grade **OpenEnv** RL environment simulating Kaggle competitions with **hierarchical action categories**, **causal dataset properties**, **failure-mode traps**, **contextual strategy scoring**, and **50+ advanced strategies**.
|
| 4 |
+
|
| 5 |
+
## Quick Start
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
pip install -r requirements.txt
|
| 9 |
+
uvicorn api.server:app --host 0.0.0.0 --port 7860 --reload
|
| 10 |
+
```
|
| 11 |
+
|
| 12 |
+
### Baseline agent
|
| 13 |
+
|
| 14 |
+
```bash
|
| 15 |
+
export OPENAI_API_KEY=sk-...
|
| 16 |
+
python -m baseline.run_baseline --mode local
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
## Architecture
|
| 22 |
+
|
| 23 |
+
```
|
| 24 |
+
openenvHackathon/
|
| 25 |
+
├── kaggle_sim_env/
|
| 26 |
+
│ ├── models.py # Hierarchical categories, DatasetProperties, FailureMode
|
| 27 |
+
│ ├── environment.py # Causal logic, trap detection, mitigation tracking
|
| 28 |
+
│ ├── tasks.py # 5 tasks with properties, traps, context relevance
|
| 29 |
+
│ ├── grader.py # 4-axis grading (perf + strategy + combo + trap)
|
| 30 |
+
│ ├── leaderboard.py # Ghost competitor leaderboard
|
| 31 |
+
│ ├── hints.py # Per-task hint dispensing
|
| 32 |
+
│ └── rewards.py # 9-component dense reward
|
| 33 |
+
├── api/server.py # FastAPI (8 endpoints)
|
| 34 |
+
├── baseline/run_baseline.py # Structured phase-based agent
|
| 35 |
+
├── openenv.yaml / Dockerfile / requirements.txt
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
## Hierarchical Action Space
|
| 41 |
+
|
| 42 |
+
Actions use `category` to reduce search space:
|
| 43 |
+
|
| 44 |
+
```json
|
| 45 |
+
{
|
| 46 |
+
"action_type": "feature_engineering",
|
| 47 |
+
"parameters": {
|
| 48 |
+
"category": "distribution",
|
| 49 |
+
"technique": "log_transform"
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
| Action Type | Categories → Techniques |
|
| 55 |
+
|---|---|
|
| 56 |
+
| `set_cv` | standard(kfold, repeated_kfold) · group(group_kfold, stratified_group_kfold) · temporal(time_split, combined_group_time) |
|
| 57 |
+
| `feature_engineering` | distribution(log_transform, normalize, quantile_features) · interaction(interaction_terms, domain_ratios) · encoding(sin_cos_encoding, target_encoding, spatial_encoding, tfidf_features) · spatial(relative_coordinates, distance_features) · signal(frequency_features, multi_layer_features, fourier_resampling) |
|
| 58 |
+
| `detect_shift` | detection(adversarial_validation, feature_importance_shift) · mitigation(remove_identifiers, domain_invariant_features) |
|
| 59 |
+
| `train_model` | tree(xgboost, lightgbm, catboost, random_forest) · linear(linear) · neural(neural_network, pretrained_backbone, temporal_cnn, transformer_encoder) |
|
| 60 |
+
| `handle_imbalance` | weighting(scale_pos_weight, class_weighted_loss) · calibration(calibrate_probabilities, optimize_threshold) · hierarchy(hierarchical_labels, lower_thresholds_recall) |
|
| 61 |
+
| `clean_data` | removal(remove_corrupted, remove_outliers, remove_leaky_features) · reconstruction(analytical_reconstruction, nan_native_model, domain_augmentation, clean_subset_training) |
|
| 62 |
+
| `augmentation` | geometric(geometric, rotation_invariant, image_rectification) · color(color_transform, clahe) · noise(gaussian_noise, robustness_augmentation) · domain(camera_simulation, temporal_augmentation, symmetry_augmentation, multi_view_processing) |
|
| 63 |
+
| `ensemble` | averaging(weighted_average, multi_seed_averaging, swa) · stacking(stacking) · diversity(diverse_features, heterogeneous) |
|
| 64 |
+
| `postprocess` | calibration(bias_correction, prediction_shrinkage, per_group_calibration) · domain(domain_rules, physics_constraints) · inference(tta) |
|
| 65 |
+
| `tune_loss` | asymmetric(asymmetric_loss, epsilon_insensitive) · uncertainty(gaussian_nll) · multi_objective(multi_task, interval_regression, quantile_regression) · weighting(sample_weighted, auxiliary_physics_loss) |
|
| 66 |
+
| `regularize` | weight(strong_regularization, ema, dropout) · transfer(freeze_backbone) |
|
| 67 |
+
|
| 68 |
+
Plus: `pseudo_label` (iterations), `inspect_top_solution`, `submit`
|
| 69 |
+
|
| 70 |
+
---
|
| 71 |
+
|
| 72 |
+
## Causal Dataset Properties
|
| 73 |
+
|
| 74 |
+
Each task has ground-truth properties that drive **causal** reward logic:
|
| 75 |
+
|
| 76 |
+
```python
|
| 77 |
+
DatasetProperties(
|
| 78 |
+
has_shift=True, # Actions addressing shift are rewarded
|
| 79 |
+
has_leakage=True, # Cleaning leaky features is critical
|
| 80 |
+
has_noise_features=True, # Interaction terms on noise amplify it
|
| 81 |
+
has_missing_data=True, # Reconstruction strategies get bonus
|
| 82 |
+
has_imbalance=True, # Scale_pos_weight becomes relevant
|
| 83 |
+
has_images=False, # Image augmentation is irrelevant → penalty
|
| 84 |
+
needs_physics=False, # Physics loss is irrelevant → penalty
|
| 85 |
+
)
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
Actions are scored based on whether they match the dataset:
|
| 89 |
+
|
| 90 |
+
```
|
| 91 |
+
if dataset.has_shift and action == "adversarial_validation":
|
| 92 |
+
reward += context_bonus # Relevant!
|
| 93 |
+
elif not dataset.has_images and action == "geometric_augmentation":
|
| 94 |
+
reward += irrelevant_penalty # Wrong domain!
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
---
|
| 98 |
+
|
| 99 |
+
## Failure-Mode Traps
|
| 100 |
+
|
| 101 |
+
The environment contains traps that **punish common mistakes**:
|
| 102 |
+
|
| 103 |
+
| Trap | Trigger | Effect | Mitigation |
|
| 104 |
+
|---|---|---|---|
|
| 105 |
+
| kfold_on_temporal_data | Using kfold when has_shift | CV +0.08, **test -0.04** | Use time_split instead |
|
| 106 |
+
| ignoring_shift | Training without addressing shift | test **-0.06** | Detect shift first |
|
| 107 |
+
| keeping_leaky_feature | Training when has_leakage | test **-0.08** | Clean leaky features first |
|
| 108 |
+
| target_encoding_leakage | target_encoding on shifted data | CV +0.05, **test -0.06** | Don't use it |
|
| 109 |
+
| interaction_terms_on_noise | interaction_terms when noise | CV +0.05, **test -0.04** | Avoid on noisy data |
|
| 110 |
+
| tree_model_on_images | xgboost on image data | CV +0.04, **test -0.02** | Use pretrained_backbone |
|
| 111 |
+
| no_augmentation_on_images | Submit without augmentation | test **-0.04** | Apply augmentation |
|
| 112 |
+
| raw_heading_without_sincos | Submit without sin_cos_encoding | test **-0.03** | Encode angles properly |
|
| 113 |
+
|
| 114 |
+
Traps can be **mitigated** by taking the correct action first. The environment tracks mitigations.
|
| 115 |
+
|
| 116 |
+
---
|
| 117 |
+
|
| 118 |
+
## Grading (4 Axes)
|
| 119 |
+
|
| 120 |
+
```
|
| 121 |
+
final = 0.40×performance + 0.25×strategy + 0.20×combo + 0.15×trap_avoidance
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
| Component | Description |
|
| 125 |
+
|---|---|
|
| 126 |
+
| **performance** | Test score vs ghost competitors |
|
| 127 |
+
| **strategy** | Contextual — penalises irrelevant strategies used |
|
| 128 |
+
| **combo** | Fraction of synergy combos activated |
|
| 129 |
+
| **trap_avoidance** | 1.0 minus fraction of traps triggered |
|
| 130 |
+
|
| 131 |
+
---
|
| 132 |
+
|
| 133 |
+
## Reward Function (9 Components)
|
| 134 |
+
|
| 135 |
+
| Component | Description |
|
| 136 |
+
|---|---|
|
| 137 |
+
| `cv_improvement` | Δ CV score |
|
| 138 |
+
| `strategy_bonus` | +0.05 for expected strategy |
|
| 139 |
+
| `context_bonus` | +0.03×relevance (positive) or -0.04×relevance (negative) |
|
| 140 |
+
| `combo_bonus` | +0.08 per combo completed |
|
| 141 |
+
| `redundancy_penalty` | -0.03 × repeat count |
|
| 142 |
+
| `irrelevant_penalty` | -0.05 for actions with relevance ≤ -0.8 |
|
| 143 |
+
| `trap_penalty` | -0.08 per trap triggered |
|
| 144 |
+
| `overfitting_penalty` | -0.5 × gap when CV-test > 0.05 |
|
| 145 |
+
| `submission_bonus` | 0.5 × test_score |
|
| 146 |
+
|
| 147 |
+
---
|
| 148 |
+
|
| 149 |
+
## Tasks (5)
|
| 150 |
+
|
| 151 |
+
| Task | Difficulty | Traps | Combos | Key Challenge |
|
| 152 |
+
|---|---|---|---|---|
|
| 153 |
+
| `easy_churn` | Easy | 3 | 2 | Clean tabular, mild imbalance |
|
| 154 |
+
| `medium_fraud` | Medium | 3 | 3 | Shift, heavy imbalance, safety-critical |
|
| 155 |
+
| `hard_leaky_noisy` | Hard | 4 | 4 | Leakage, noise, missing data, shift |
|
| 156 |
+
| `image_quality` | Hard | 2 | 4 | Heavy-tailed, camera bias, augmentation |
|
| 157 |
+
| `trajectory_pred` | Hard | 2 | 4 | Multi-agent, physics, spatial-temporal |
|
| 158 |
+
|
| 159 |
+
---
|
| 160 |
+
|
| 161 |
+
## Baseline Agent
|
| 162 |
+
|
| 163 |
+
Structured multi-phase approach:
|
| 164 |
+
1. **Inspect** hints (1-2)
|
| 165 |
+
2. **Diagnose** dataset properties
|
| 166 |
+
3. **Clean** if needed
|
| 167 |
+
4. **CV** appropriate for domain
|
| 168 |
+
5. **Features** domain-relevant only
|
| 169 |
+
6. **Train** right model family
|
| 170 |
+
7. **Tune** imbalance/loss
|
| 171 |
+
8. **Ensemble** (1-2 techniques)
|
| 172 |
+
9. **Submit**
|
| 173 |
+
|
| 174 |
+
Keeps actions to 8-15 total. Uses hints to inform decisions.
|
| 175 |
+
|
| 176 |
+
---
|
| 177 |
+
|
| 178 |
+
## Docker
|
| 179 |
+
|
| 180 |
+
```bash
|
| 181 |
+
docker build -t kaggle-sim-env .
|
| 182 |
+
docker run -p 7860:7860 kaggle-sim-env
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
---
|
| 186 |
+
|
| 187 |
+
## License
|
| 188 |
+
|
| 189 |
+
MIT
|
api/__init__.py
ADDED
|
File without changes
|
api/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (166 Bytes). View file
|
|
|
api/__pycache__/server.cpython-312.pyc
ADDED
|
Binary file (16.4 kB). View file
|
|
|
api/server.py
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""FastAPI server for KaggleSimEnv v3.
|
| 2 |
+
|
| 3 |
+
Endpoints:
|
| 4 |
+
POST /reset, /step, /grader, /baseline
|
| 5 |
+
GET /state, /tasks, /actions, /health
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
from typing import Any
|
| 11 |
+
|
| 12 |
+
from fastapi import FastAPI, HTTPException
|
| 13 |
+
from pydantic import BaseModel, Field
|
| 14 |
+
|
| 15 |
+
from kaggle_sim_env.environment import KaggleSimEnv
|
| 16 |
+
from kaggle_sim_env.grader import GradeResult, Grader
|
| 17 |
+
from kaggle_sim_env.models import (
|
| 18 |
+
Action,
|
| 19 |
+
ActionType,
|
| 20 |
+
CATEGORY_MAP,
|
| 21 |
+
EnvState,
|
| 22 |
+
Observation,
|
| 23 |
+
StepResponse,
|
| 24 |
+
get_categories_for_action,
|
| 25 |
+
)
|
| 26 |
+
from kaggle_sim_env.tasks import TASK_REGISTRY, get_task
|
| 27 |
+
|
| 28 |
+
app = FastAPI(
|
| 29 |
+
title="KaggleSimEnv – OpenEnv API",
|
| 30 |
+
description=(
|
| 31 |
+
"RL environment simulating Kaggle competitions with hierarchical actions, "
|
| 32 |
+
"causal dataset properties, failure-mode traps, and contextual scoring."
|
| 33 |
+
),
|
| 34 |
+
version="3.0.0",
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
env = KaggleSimEnv()
|
| 38 |
+
grader = Grader()
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# ---------------------------------------------------------------------------
|
| 42 |
+
# Schemas
|
| 43 |
+
# ---------------------------------------------------------------------------
|
| 44 |
+
|
| 45 |
+
class ResetRequest(BaseModel):
|
| 46 |
+
task_id: str = "easy_churn"
|
| 47 |
+
|
| 48 |
+
class StepRequest(BaseModel):
|
| 49 |
+
action_type: str
|
| 50 |
+
parameters: dict[str, Any] = Field(default_factory=dict)
|
| 51 |
+
|
| 52 |
+
class TaskSummary(BaseModel):
|
| 53 |
+
task_id: str
|
| 54 |
+
title: str
|
| 55 |
+
difficulty: str
|
| 56 |
+
description: str
|
| 57 |
+
max_steps: int
|
| 58 |
+
num_expected_strategies: int
|
| 59 |
+
num_strategy_combos: int
|
| 60 |
+
num_failure_modes: int
|
| 61 |
+
|
| 62 |
+
class BaselineRequest(BaseModel):
|
| 63 |
+
task_id: str = "easy_churn"
|
| 64 |
+
|
| 65 |
+
class BaselineResult(BaseModel):
|
| 66 |
+
task_id: str
|
| 67 |
+
actions_taken: list[dict[str, Any]]
|
| 68 |
+
grade: GradeResult
|
| 69 |
+
|
| 70 |
+
class ActionCategoryEntry(BaseModel):
|
| 71 |
+
action_type: str
|
| 72 |
+
parameter_key: str | None = None
|
| 73 |
+
categories: dict[str, list[str]] = Field(default_factory=dict)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
# ---------------------------------------------------------------------------
|
| 77 |
+
# Endpoints
|
| 78 |
+
# ---------------------------------------------------------------------------
|
| 79 |
+
|
| 80 |
+
@app.post("/reset", response_model=Observation)
|
| 81 |
+
def reset(req: ResetRequest) -> Observation:
|
| 82 |
+
try:
|
| 83 |
+
return env.reset(task_id=req.task_id)
|
| 84 |
+
except ValueError as exc:
|
| 85 |
+
raise HTTPException(status_code=400, detail=str(exc))
|
| 86 |
+
|
| 87 |
+
@app.post("/step", response_model=StepResponse)
|
| 88 |
+
def step(req: StepRequest) -> StepResponse:
|
| 89 |
+
try:
|
| 90 |
+
action = Action(action_type=req.action_type, parameters=req.parameters)
|
| 91 |
+
except Exception as exc:
|
| 92 |
+
raise HTTPException(status_code=422, detail=f"Invalid action: {exc}")
|
| 93 |
+
try:
|
| 94 |
+
return env.step(action)
|
| 95 |
+
except RuntimeError as exc:
|
| 96 |
+
raise HTTPException(status_code=400, detail=str(exc))
|
| 97 |
+
|
| 98 |
+
@app.get("/state", response_model=EnvState)
|
| 99 |
+
def state() -> EnvState:
|
| 100 |
+
try:
|
| 101 |
+
return env.state()
|
| 102 |
+
except (AssertionError, RuntimeError):
|
| 103 |
+
raise HTTPException(status_code=400, detail="No active environment.")
|
| 104 |
+
|
| 105 |
+
@app.get("/tasks", response_model=list[TaskSummary])
|
| 106 |
+
def list_tasks() -> list[TaskSummary]:
|
| 107 |
+
return [
|
| 108 |
+
TaskSummary(
|
| 109 |
+
task_id=t.task_id, title=t.title, difficulty=t.difficulty,
|
| 110 |
+
description=t.description, max_steps=t.max_steps,
|
| 111 |
+
num_expected_strategies=len(t.expected_strategies),
|
| 112 |
+
num_strategy_combos=len(t.strategy_combos),
|
| 113 |
+
num_failure_modes=len(t.failure_modes),
|
| 114 |
+
)
|
| 115 |
+
for t in TASK_REGISTRY.values()
|
| 116 |
+
]
|
| 117 |
+
|
| 118 |
+
@app.post("/grader", response_model=GradeResult)
|
| 119 |
+
def grade() -> GradeResult:
|
| 120 |
+
try:
|
| 121 |
+
s = env.state()
|
| 122 |
+
except (AssertionError, RuntimeError):
|
| 123 |
+
raise HTTPException(status_code=400, detail="No active environment.")
|
| 124 |
+
return grader.grade(s, get_task(s.task_id))
|
| 125 |
+
|
| 126 |
+
@app.get("/actions", response_model=list[ActionCategoryEntry])
|
| 127 |
+
def action_space() -> list[ActionCategoryEntry]:
|
| 128 |
+
"""Hierarchical action space with categories."""
|
| 129 |
+
from kaggle_sim_env.models import _PARAM_KEY_MAP
|
| 130 |
+
entries: list[ActionCategoryEntry] = []
|
| 131 |
+
for at, key in _PARAM_KEY_MAP.items():
|
| 132 |
+
cats = get_categories_for_action(at)
|
| 133 |
+
entries.append(ActionCategoryEntry(action_type=at, parameter_key=key, categories=cats))
|
| 134 |
+
entries.append(ActionCategoryEntry(
|
| 135 |
+
action_type="pseudo_label", parameter_key="iterations",
|
| 136 |
+
categories={"iterations": ["1", "2", "3"]},
|
| 137 |
+
))
|
| 138 |
+
entries.append(ActionCategoryEntry(action_type="inspect_top_solution"))
|
| 139 |
+
entries.append(ActionCategoryEntry(action_type="submit"))
|
| 140 |
+
return entries
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
# ---------------------------------------------------------------------------
|
| 144 |
+
# Baseline: structured, hint-using, phase-based agent
|
| 145 |
+
# ---------------------------------------------------------------------------
|
| 146 |
+
|
| 147 |
+
@app.post("/baseline", response_model=BaselineResult)
|
| 148 |
+
def run_baseline(req: BaselineRequest) -> BaselineResult:
|
| 149 |
+
task = get_task(req.task_id)
|
| 150 |
+
bl_env = KaggleSimEnv()
|
| 151 |
+
bl_env.reset(task_id=req.task_id)
|
| 152 |
+
|
| 153 |
+
actions_log: list[dict[str, Any]] = []
|
| 154 |
+
plan = _baseline_plan(task.task_id)
|
| 155 |
+
|
| 156 |
+
for action in plan:
|
| 157 |
+
result = bl_env.step(action)
|
| 158 |
+
actions_log.append({
|
| 159 |
+
"action": action.model_dump(),
|
| 160 |
+
"cv_score": result.observation.current_cv_score,
|
| 161 |
+
"reward": result.reward.total,
|
| 162 |
+
"traps": result.info.get("traps", []),
|
| 163 |
+
"combos": result.info.get("combos_completed", []),
|
| 164 |
+
})
|
| 165 |
+
if result.done:
|
| 166 |
+
break
|
| 167 |
+
|
| 168 |
+
s = bl_env.state()
|
| 169 |
+
g = grader.grade(s, task)
|
| 170 |
+
return BaselineResult(task_id=req.task_id, actions_taken=actions_log, grade=g)
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def _baseline_plan(task_id: str) -> list[Action]:
|
| 174 |
+
"""Structured, phase-based expert plans that use hints first."""
|
| 175 |
+
A = Action
|
| 176 |
+
|
| 177 |
+
if task_id == "easy_churn":
|
| 178 |
+
return [
|
| 179 |
+
# Phase 1: Inspect
|
| 180 |
+
A(action_type=ActionType.INSPECT_TOP_SOLUTION, parameters={}),
|
| 181 |
+
# Phase 2: CV + Features
|
| 182 |
+
A(action_type=ActionType.SET_CV, parameters={"category": "standard", "strategy": "kfold"}),
|
| 183 |
+
A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "distribution", "technique": "normalize"}),
|
| 184 |
+
A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "interaction", "technique": "domain_ratios"}),
|
| 185 |
+
# Phase 3: Train
|
| 186 |
+
A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "tree", "algorithm": "xgboost"}),
|
| 187 |
+
# Phase 4: Imbalance
|
| 188 |
+
A(action_type=ActionType.HANDLE_IMBALANCE, parameters={"category": "weighting", "method": "scale_pos_weight"}),
|
| 189 |
+
A(action_type=ActionType.HANDLE_IMBALANCE, parameters={"category": "calibration", "method": "optimize_threshold"}),
|
| 190 |
+
# Phase 5: Submit
|
| 191 |
+
A(action_type=ActionType.SUBMIT, parameters={}),
|
| 192 |
+
]
|
| 193 |
+
|
| 194 |
+
if task_id == "medium_fraud":
|
| 195 |
+
return [
|
| 196 |
+
# Phase 1: Inspect + Diagnose
|
| 197 |
+
A(action_type=ActionType.INSPECT_TOP_SOLUTION, parameters={}),
|
| 198 |
+
A(action_type=ActionType.INSPECT_TOP_SOLUTION, parameters={}),
|
| 199 |
+
A(action_type=ActionType.DETECT_SHIFT, parameters={"category": "detection", "method": "adversarial_validation"}),
|
| 200 |
+
A(action_type=ActionType.DETECT_SHIFT, parameters={"category": "mitigation", "method": "remove_identifiers"}),
|
| 201 |
+
A(action_type=ActionType.DETECT_SHIFT, parameters={"category": "mitigation", "method": "domain_invariant_features"}),
|
| 202 |
+
# Phase 2: CV + Features
|
| 203 |
+
A(action_type=ActionType.SET_CV, parameters={"category": "temporal", "strategy": "time_split"}),
|
| 204 |
+
A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "distribution", "technique": "log_transform"}),
|
| 205 |
+
A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "interaction", "technique": "domain_ratios"}),
|
| 206 |
+
# Phase 3: Train + Imbalance
|
| 207 |
+
A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "tree", "algorithm": "xgboost"}),
|
| 208 |
+
A(action_type=ActionType.HANDLE_IMBALANCE, parameters={"category": "weighting", "method": "scale_pos_weight"}),
|
| 209 |
+
A(action_type=ActionType.HANDLE_IMBALANCE, parameters={"category": "calibration", "method": "optimize_threshold"}),
|
| 210 |
+
A(action_type=ActionType.TUNE_LOSS, parameters={"category": "asymmetric", "method": "asymmetric_loss"}),
|
| 211 |
+
# Phase 4: Regularize + Ensemble
|
| 212 |
+
A(action_type=ActionType.REGULARIZE, parameters={"category": "weight", "method": "strong_regularization"}),
|
| 213 |
+
A(action_type=ActionType.ENSEMBLE, parameters={"category": "averaging", "method": "weighted_average"}),
|
| 214 |
+
A(action_type=ActionType.SUBMIT, parameters={}),
|
| 215 |
+
]
|
| 216 |
+
|
| 217 |
+
if task_id == "hard_leaky_noisy":
|
| 218 |
+
return [
|
| 219 |
+
# Phase 1: Inspect + Diagnose + Clean
|
| 220 |
+
A(action_type=ActionType.INSPECT_TOP_SOLUTION, parameters={}),
|
| 221 |
+
A(action_type=ActionType.DETECT_SHIFT, parameters={"category": "detection", "method": "adversarial_validation"}),
|
| 222 |
+
A(action_type=ActionType.DETECT_SHIFT, parameters={"category": "mitigation", "method": "remove_identifiers"}),
|
| 223 |
+
A(action_type=ActionType.CLEAN_DATA, parameters={"category": "removal", "method": "remove_leaky_features"}),
|
| 224 |
+
A(action_type=ActionType.CLEAN_DATA, parameters={"category": "removal", "method": "remove_outliers"}),
|
| 225 |
+
A(action_type=ActionType.CLEAN_DATA, parameters={"category": "reconstruction", "method": "analytical_reconstruction"}),
|
| 226 |
+
A(action_type=ActionType.CLEAN_DATA, parameters={"category": "reconstruction", "method": "nan_native_model"}),
|
| 227 |
+
# Phase 2: CV + Features
|
| 228 |
+
A(action_type=ActionType.SET_CV, parameters={"category": "group", "strategy": "stratified_group_kfold"}),
|
| 229 |
+
A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "distribution", "technique": "log_transform"}),
|
| 230 |
+
A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "interaction", "technique": "domain_ratios"}),
|
| 231 |
+
# Phase 3: Train + Imbalance
|
| 232 |
+
A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "tree", "algorithm": "xgboost"}),
|
| 233 |
+
A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "tree", "algorithm": "lightgbm"}),
|
| 234 |
+
A(action_type=ActionType.HANDLE_IMBALANCE, parameters={"category": "weighting", "method": "scale_pos_weight"}),
|
| 235 |
+
A(action_type=ActionType.HANDLE_IMBALANCE, parameters={"category": "calibration", "method": "calibrate_probabilities"}),
|
| 236 |
+
A(action_type=ActionType.HANDLE_IMBALANCE, parameters={"category": "calibration", "method": "optimize_threshold"}),
|
| 237 |
+
# Phase 4: Ensemble + Regularize
|
| 238 |
+
A(action_type=ActionType.ENSEMBLE, parameters={"category": "stacking", "method": "stacking"}),
|
| 239 |
+
A(action_type=ActionType.ENSEMBLE, parameters={"category": "diversity", "method": "diverse_features"}),
|
| 240 |
+
A(action_type=ActionType.REGULARIZE, parameters={"category": "weight", "method": "strong_regularization"}),
|
| 241 |
+
A(action_type=ActionType.PSEUDO_LABEL, parameters={"iterations": 1}),
|
| 242 |
+
A(action_type=ActionType.SUBMIT, parameters={}),
|
| 243 |
+
]
|
| 244 |
+
|
| 245 |
+
if task_id == "image_quality":
|
| 246 |
+
return [
|
| 247 |
+
# Phase 1: Inspect
|
| 248 |
+
A(action_type=ActionType.INSPECT_TOP_SOLUTION, parameters={}),
|
| 249 |
+
A(action_type=ActionType.INSPECT_TOP_SOLUTION, parameters={}),
|
| 250 |
+
# Phase 2: CV + Backbone
|
| 251 |
+
A(action_type=ActionType.SET_CV, parameters={"category": "group", "strategy": "group_kfold"}),
|
| 252 |
+
A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "neural", "algorithm": "pretrained_backbone"}),
|
| 253 |
+
A(action_type=ActionType.REGULARIZE, parameters={"category": "transfer", "method": "freeze_backbone"}),
|
| 254 |
+
# Phase 3: Augmentation
|
| 255 |
+
A(action_type=ActionType.AUGMENTATION, parameters={"category": "geometric", "method": "geometric"}),
|
| 256 |
+
A(action_type=ActionType.AUGMENTATION, parameters={"category": "color", "method": "color_transform"}),
|
| 257 |
+
A(action_type=ActionType.AUGMENTATION, parameters={"category": "domain", "method": "camera_simulation"}),
|
| 258 |
+
A(action_type=ActionType.AUGMENTATION, parameters={"category": "color", "method": "clahe"}),
|
| 259 |
+
# Phase 4: Loss + Features
|
| 260 |
+
A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "distribution", "technique": "log_transform"}),
|
| 261 |
+
A(action_type=ActionType.TUNE_LOSS, parameters={"category": "uncertainty", "method": "gaussian_nll"}),
|
| 262 |
+
A(action_type=ActionType.TUNE_LOSS, parameters={"category": "multi_objective", "method": "multi_task"}),
|
| 263 |
+
# Phase 5: Stabilize + Ensemble
|
| 264 |
+
A(action_type=ActionType.REGULARIZE, parameters={"category": "weight", "method": "ema"}),
|
| 265 |
+
A(action_type=ActionType.ENSEMBLE, parameters={"category": "averaging", "method": "multi_seed_averaging"}),
|
| 266 |
+
A(action_type=ActionType.ENSEMBLE, parameters={"category": "averaging", "method": "swa"}),
|
| 267 |
+
# Phase 6: Postprocess
|
| 268 |
+
A(action_type=ActionType.POSTPROCESS, parameters={"category": "inference", "method": "tta"}),
|
| 269 |
+
A(action_type=ActionType.POSTPROCESS, parameters={"category": "calibration", "method": "per_group_calibration"}),
|
| 270 |
+
A(action_type=ActionType.POSTPROCESS, parameters={"category": "calibration", "method": "prediction_shrinkage"}),
|
| 271 |
+
A(action_type=ActionType.SUBMIT, parameters={}),
|
| 272 |
+
]
|
| 273 |
+
|
| 274 |
+
if task_id == "trajectory_pred":
|
| 275 |
+
return [
|
| 276 |
+
# Phase 1: Inspect + Clean
|
| 277 |
+
A(action_type=ActionType.INSPECT_TOP_SOLUTION, parameters={}),
|
| 278 |
+
A(action_type=ActionType.SET_CV, parameters={"category": "group", "strategy": "group_kfold"}),
|
| 279 |
+
A(action_type=ActionType.CLEAN_DATA, parameters={"category": "removal", "method": "remove_corrupted"}),
|
| 280 |
+
A(action_type=ActionType.CLEAN_DATA, parameters={"category": "removal", "method": "remove_outliers"}),
|
| 281 |
+
# Phase 2: Spatial features
|
| 282 |
+
A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "encoding", "technique": "sin_cos_encoding"}),
|
| 283 |
+
A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "spatial", "technique": "relative_coordinates"}),
|
| 284 |
+
A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "spatial", "technique": "distance_features"}),
|
| 285 |
+
# Phase 3: Train
|
| 286 |
+
A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "neural", "algorithm": "transformer_encoder"}),
|
| 287 |
+
A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "neural", "algorithm": "temporal_cnn"}),
|
| 288 |
+
# Phase 4: Augmentation
|
| 289 |
+
A(action_type=ActionType.AUGMENTATION, parameters={"category": "geometric", "method": "rotation_invariant"}),
|
| 290 |
+
A(action_type=ActionType.AUGMENTATION, parameters={"category": "domain", "method": "symmetry_augmentation"}),
|
| 291 |
+
A(action_type=ActionType.AUGMENTATION, parameters={"category": "domain", "method": "temporal_augmentation"}),
|
| 292 |
+
# Phase 5: Loss + Regularize
|
| 293 |
+
A(action_type=ActionType.TUNE_LOSS, parameters={"category": "uncertainty", "method": "gaussian_nll"}),
|
| 294 |
+
A(action_type=ActionType.TUNE_LOSS, parameters={"category": "weighting", "method": "auxiliary_physics_loss"}),
|
| 295 |
+
A(action_type=ActionType.REGULARIZE, parameters={"category": "weight", "method": "ema"}),
|
| 296 |
+
# Phase 6: Ensemble + Postprocess
|
| 297 |
+
A(action_type=ActionType.ENSEMBLE, parameters={"category": "averaging", "method": "multi_seed_averaging"}),
|
| 298 |
+
A(action_type=ActionType.POSTPROCESS, parameters={"category": "domain", "method": "physics_constraints"}),
|
| 299 |
+
A(action_type=ActionType.SUBMIT, parameters={}),
|
| 300 |
+
]
|
| 301 |
+
|
| 302 |
+
return [
|
| 303 |
+
A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "tree", "algorithm": "xgboost"}),
|
| 304 |
+
A(action_type=ActionType.SUBMIT, parameters={}),
|
| 305 |
+
]
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
@app.get("/health")
|
| 309 |
+
def health() -> dict[str, str]:
|
| 310 |
+
return {"status": "ok"}
|
baseline/__init__.py
ADDED
|
File without changes
|
baseline/run_baseline.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Baseline agent for KaggleSimEnv v3.
|
| 3 |
+
|
| 4 |
+
Structured multi-phase approach:
|
| 5 |
+
Phase 1: Inspect hints (1-2 hints)
|
| 6 |
+
Phase 2: Diagnose dataset (shift detection, cleaning)
|
| 7 |
+
Phase 3: Set CV + feature engineering
|
| 8 |
+
Phase 4: Train model + handle imbalance
|
| 9 |
+
Phase 5: Ensemble + regularize + postprocess
|
| 10 |
+
Phase 6: Submit
|
| 11 |
+
|
| 12 |
+
Usage:
|
| 13 |
+
python -m baseline.run_baseline --mode local
|
| 14 |
+
python -m baseline.run_baseline --mode api --base-url http://localhost:7860
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
from __future__ import annotations
|
| 18 |
+
|
| 19 |
+
import argparse
|
| 20 |
+
import json
|
| 21 |
+
import os
|
| 22 |
+
import sys
|
| 23 |
+
from typing import Any
|
| 24 |
+
|
| 25 |
+
import requests
|
| 26 |
+
from openai import OpenAI
|
| 27 |
+
|
| 28 |
+
from kaggle_sim_env.environment import KaggleSimEnv
|
| 29 |
+
from kaggle_sim_env.grader import Grader
|
| 30 |
+
from kaggle_sim_env.models import Action
|
| 31 |
+
from kaggle_sim_env.tasks import TASK_REGISTRY, get_task
|
| 32 |
+
|
| 33 |
+
SYSTEM_PROMPT = """\
|
| 34 |
+
You are an expert Kaggle Grand Master agent. You work in structured phases.
|
| 35 |
+
|
| 36 |
+
## PHASES (follow in order)
|
| 37 |
+
1. INSPECT: Use inspect_top_solution to get 1-2 hints before acting.
|
| 38 |
+
2. DIAGNOSE: Check dataset properties. If has_time_column → detect shift. If columns look leaky → clean data.
|
| 39 |
+
3. CV: Choose CV based on dataset properties (group/time/standard).
|
| 40 |
+
4. FEATURES: Engineer domain-appropriate features only. Don't use image/spatial features on tabular data.
|
| 41 |
+
5. TRAIN: Pick the right model family for the domain.
|
| 42 |
+
6. TUNE: Handle imbalance, tune loss if needed.
|
| 43 |
+
7. STABILIZE: Regularize + ensemble (keep it to 1-2 techniques).
|
| 44 |
+
8. POSTPROCESS: Only if domain-relevant (TTA for images, physics for trajectories).
|
| 45 |
+
9. SUBMIT: When confident or running low on steps.
|
| 46 |
+
|
| 47 |
+
## HIERARCHICAL ACTIONS (use category field!)
|
| 48 |
+
|
| 49 |
+
Example:
|
| 50 |
+
{"action_type": "feature_engineering", "parameters": {"category": "distribution", "technique": "log_transform"}}
|
| 51 |
+
|
| 52 |
+
Categories per action type:
|
| 53 |
+
set_cv: standard(kfold,repeated_kfold) | group(group_kfold,stratified_group_kfold) | temporal(time_split,combined_group_time)
|
| 54 |
+
feature_engineering: distribution(log_transform,normalize,quantile_features) | interaction(interaction_terms,domain_ratios) | encoding(sin_cos_encoding,target_encoding,spatial_encoding,tfidf_features) | spatial(relative_coordinates,distance_features) | signal(frequency_features,multi_layer_features,fourier_resampling)
|
| 55 |
+
detect_shift: detection(adversarial_validation,feature_importance_shift) | mitigation(remove_identifiers,domain_invariant_features)
|
| 56 |
+
train_model: tree(xgboost,lightgbm,catboost,random_forest) | linear(linear) | neural(neural_network,pretrained_backbone,temporal_cnn,transformer_encoder)
|
| 57 |
+
handle_imbalance: weighting(scale_pos_weight,class_weighted_loss) | calibration(calibrate_probabilities,optimize_threshold) | hierarchy(hierarchical_labels,lower_thresholds_recall)
|
| 58 |
+
clean_data: removal(remove_corrupted,remove_outliers,remove_leaky_features) | reconstruction(analytical_reconstruction,nan_native_model,domain_augmentation,clean_subset_training)
|
| 59 |
+
augmentation: geometric(geometric,rotation_invariant,image_rectification) | color(color_transform,clahe) | noise(gaussian_noise,robustness_augmentation) | domain(camera_simulation,temporal_augmentation,symmetry_augmentation,multi_view_processing)
|
| 60 |
+
ensemble: averaging(weighted_average,multi_seed_averaging,swa) | stacking(stacking) | diversity(diverse_features,heterogeneous)
|
| 61 |
+
postprocess: calibration(bias_correction,prediction_shrinkage,per_group_calibration) | domain(domain_rules,physics_constraints) | inference(tta)
|
| 62 |
+
tune_loss: asymmetric(asymmetric_loss,epsilon_insensitive) | uncertainty(gaussian_nll) | multi_objective(multi_task,interval_regression,quantile_regression) | weighting(sample_weighted,auxiliary_physics_loss)
|
| 63 |
+
regularize: weight(strong_regularization,ema,dropout) | transfer(freeze_backbone)
|
| 64 |
+
|
| 65 |
+
Also available:
|
| 66 |
+
{"action_type": "pseudo_label", "parameters": {"iterations": 1}}
|
| 67 |
+
{"action_type": "inspect_top_solution", "parameters": {}}
|
| 68 |
+
{"action_type": "submit", "parameters": {}}
|
| 69 |
+
|
| 70 |
+
## FAILURE MODE AWARENESS
|
| 71 |
+
- Using kfold on temporal/grouped data → CV inflated, test destroyed
|
| 72 |
+
- Image augmentation on tabular data → wasted step + penalty
|
| 73 |
+
- Target encoding without group CV → leakage trap
|
| 74 |
+
- Training without addressing shift → model memorises wrong distribution
|
| 75 |
+
- Using tree models on image/trajectory data → poor fit
|
| 76 |
+
|
| 77 |
+
## CRITICAL RULES
|
| 78 |
+
- Start with 1-2 hints to understand the dataset
|
| 79 |
+
- NEVER use more than 8-10 substantive actions before submitting
|
| 80 |
+
- Only use strategies relevant to THIS dataset
|
| 81 |
+
- Respond with ONLY valid JSON, nothing else
|
| 82 |
+
"""
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def build_user_message(observation: dict[str, Any]) -> str:
|
| 86 |
+
meta = observation["dataset_metadata"]
|
| 87 |
+
return (
|
| 88 |
+
f"Step {observation['step_count']}/{observation['max_steps']} "
|
| 89 |
+
f"CV: {observation['current_cv_score']:.4f} "
|
| 90 |
+
f"Rank: {observation['leaderboard_rank']}\n"
|
| 91 |
+
f"Applied: {observation['applied_strategies']}\n"
|
| 92 |
+
f"Message: {observation.get('message', '')}\n\n"
|
| 93 |
+
f"Dataset: rows={meta['num_rows']} features={meta['num_features']} "
|
| 94 |
+
f"target={meta['target_column']} type={meta['task_type']}\n"
|
| 95 |
+
f" has_time={meta.get('has_time_column',False)} "
|
| 96 |
+
f"has_group={meta.get('has_group_column',False)} "
|
| 97 |
+
f"has_image={meta.get('has_image_data',False)} "
|
| 98 |
+
f"has_spatial={meta.get('has_spatial_data',False)}\n"
|
| 99 |
+
f" target_dist={meta.get('target_distribution','normal')} "
|
| 100 |
+
f"class_balance={meta.get('class_balance',{})}\n"
|
| 101 |
+
f" columns: {', '.join(c['name']+'('+c['dtype']+',miss='+str(c['missing_pct'])+'%)' for c in meta['columns'][:8])}"
|
| 102 |
+
f"{'...' if len(meta['columns'])>8 else ''}\n\n"
|
| 103 |
+
"Choose your next action (JSON only):"
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def parse_llm_action(text: str) -> dict[str, Any]:
|
| 108 |
+
text = text.strip()
|
| 109 |
+
if text.startswith("```"):
|
| 110 |
+
lines = text.split("\n")
|
| 111 |
+
lines = [l for l in lines if not l.strip().startswith("```")]
|
| 112 |
+
text = "\n".join(lines).strip()
|
| 113 |
+
return json.loads(text)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def run_local(client: OpenAI, task_id: str) -> dict[str, Any]:
|
| 117 |
+
env = KaggleSimEnv()
|
| 118 |
+
grader_inst = Grader()
|
| 119 |
+
task = get_task(task_id)
|
| 120 |
+
|
| 121 |
+
obs = env.reset(task_id=task_id)
|
| 122 |
+
obs_dict = obs.model_dump()
|
| 123 |
+
messages: list[dict[str, str]] = [{"role": "system", "content": SYSTEM_PROMPT}]
|
| 124 |
+
actions_log: list[dict[str, Any]] = []
|
| 125 |
+
|
| 126 |
+
while not obs_dict.get("done", False):
|
| 127 |
+
messages.append({"role": "user", "content": build_user_message(obs_dict)})
|
| 128 |
+
response = client.chat.completions.create(
|
| 129 |
+
model="gpt-4o-mini",
|
| 130 |
+
messages=messages,
|
| 131 |
+
temperature=0.0,
|
| 132 |
+
max_tokens=256,
|
| 133 |
+
)
|
| 134 |
+
raw = response.choices[0].message.content or "{}"
|
| 135 |
+
messages.append({"role": "assistant", "content": raw})
|
| 136 |
+
|
| 137 |
+
try:
|
| 138 |
+
action_dict = parse_llm_action(raw)
|
| 139 |
+
action = Action(**action_dict)
|
| 140 |
+
except Exception as exc:
|
| 141 |
+
print(f" [!] Parse error: {exc}. Submitting.")
|
| 142 |
+
action = Action(action_type="submit", parameters={})
|
| 143 |
+
|
| 144 |
+
step_result = env.step(action)
|
| 145 |
+
obs_dict = step_result.observation.model_dump()
|
| 146 |
+
actions_log.append({
|
| 147 |
+
"action": action.model_dump(),
|
| 148 |
+
"cv_score": step_result.observation.current_cv_score,
|
| 149 |
+
"reward": step_result.reward.total,
|
| 150 |
+
})
|
| 151 |
+
|
| 152 |
+
trap_info = ""
|
| 153 |
+
if step_result.info.get("traps"):
|
| 154 |
+
trap_info = f" TRAP!"
|
| 155 |
+
combo_info = ""
|
| 156 |
+
if step_result.info.get("combos_completed"):
|
| 157 |
+
combo_info = f" COMBO: {step_result.info['combos_completed']}"
|
| 158 |
+
|
| 159 |
+
print(
|
| 160 |
+
f" Step {obs_dict['step_count']:2d}: {action.full_tag():50s} "
|
| 161 |
+
f"CV={obs_dict['current_cv_score']:.4f} R={step_result.reward.total:+.4f}"
|
| 162 |
+
f"{trap_info}{combo_info}"
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
state = env.state()
|
| 166 |
+
grade = grader_inst.grade(state, task)
|
| 167 |
+
return {"task_id": task_id, "actions": actions_log, "grade": grade.model_dump()}
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def run_api(client: OpenAI, task_id: str, base_url: str) -> dict[str, Any]:
|
| 171 |
+
resp = requests.post(f"{base_url}/reset", json={"task_id": task_id}, timeout=30)
|
| 172 |
+
resp.raise_for_status()
|
| 173 |
+
obs_dict = resp.json()
|
| 174 |
+
messages: list[dict[str, str]] = [{"role": "system", "content": SYSTEM_PROMPT}]
|
| 175 |
+
actions_log: list[dict[str, Any]] = []
|
| 176 |
+
|
| 177 |
+
while not obs_dict.get("done", False):
|
| 178 |
+
messages.append({"role": "user", "content": build_user_message(obs_dict)})
|
| 179 |
+
response = client.chat.completions.create(
|
| 180 |
+
model="gpt-4o-mini", messages=messages,
|
| 181 |
+
temperature=0.0, max_tokens=256,
|
| 182 |
+
)
|
| 183 |
+
raw = response.choices[0].message.content or "{}"
|
| 184 |
+
messages.append({"role": "assistant", "content": raw})
|
| 185 |
+
|
| 186 |
+
try:
|
| 187 |
+
action_dict = parse_llm_action(raw)
|
| 188 |
+
except Exception:
|
| 189 |
+
action_dict = {"action_type": "submit", "parameters": {}}
|
| 190 |
+
|
| 191 |
+
resp = requests.post(f"{base_url}/step", json=action_dict, timeout=30)
|
| 192 |
+
resp.raise_for_status()
|
| 193 |
+
step_data = resp.json()
|
| 194 |
+
obs_dict = step_data["observation"]
|
| 195 |
+
actions_log.append({
|
| 196 |
+
"action": action_dict,
|
| 197 |
+
"cv_score": obs_dict["current_cv_score"],
|
| 198 |
+
"reward": step_data["reward"]["total"],
|
| 199 |
+
})
|
| 200 |
+
|
| 201 |
+
resp = requests.post(f"{base_url}/grader", timeout=30)
|
| 202 |
+
resp.raise_for_status()
|
| 203 |
+
return {"task_id": task_id, "actions": actions_log, "grade": resp.json()}
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def main() -> None:
|
| 207 |
+
parser = argparse.ArgumentParser(description="KaggleSimEnv baseline agent")
|
| 208 |
+
parser.add_argument("--mode", choices=["local", "api"], default="local")
|
| 209 |
+
parser.add_argument("--base-url", default="http://localhost:7860")
|
| 210 |
+
parser.add_argument("--tasks", nargs="*", default=list(TASK_REGISTRY.keys()))
|
| 211 |
+
args = parser.parse_args()
|
| 212 |
+
|
| 213 |
+
api_key = os.environ.get("OPENAI_API_KEY")
|
| 214 |
+
if not api_key:
|
| 215 |
+
print("Error: OPENAI_API_KEY not set.", file=sys.stderr)
|
| 216 |
+
sys.exit(1)
|
| 217 |
+
|
| 218 |
+
client = OpenAI(api_key=api_key)
|
| 219 |
+
results: list[dict[str, Any]] = []
|
| 220 |
+
|
| 221 |
+
for task_id in args.tasks:
|
| 222 |
+
print(f"\n{'='*70}")
|
| 223 |
+
print(f" Task: {task_id}")
|
| 224 |
+
print(f"{'='*70}")
|
| 225 |
+
result = run_local(client, task_id) if args.mode == "local" else run_api(client, task_id, args.base_url)
|
| 226 |
+
results.append(result)
|
| 227 |
+
g = result["grade"]
|
| 228 |
+
print(f"\n Grade: perf={g['performance_score']:.4f} strat={g['strategy_score']:.4f} "
|
| 229 |
+
f"combo={g['combo_score']:.4f} trap={g['trap_score']:.4f} final={g['final_score']:.4f}")
|
| 230 |
+
|
| 231 |
+
print(f"\n{'='*70}")
|
| 232 |
+
print(" SUMMARY")
|
| 233 |
+
print(f"{'='*70}")
|
| 234 |
+
for r in results:
|
| 235 |
+
g = r["grade"]
|
| 236 |
+
print(f" {r['task_id']:25s} final={g['final_score']:.4f}")
|
| 237 |
+
avg = sum(r["grade"]["final_score"] for r in results) / max(len(results), 1)
|
| 238 |
+
print(f"\n Average: {avg:.4f}")
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
if __name__ == "__main__":
|
| 242 |
+
main()
|
kaggle_sim_env/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from kaggle_sim_env.models import Action, Observation, Reward, EnvState
|
| 2 |
+
from kaggle_sim_env.environment import KaggleSimEnv
|
| 3 |
+
from kaggle_sim_env.grader import Grader
|
| 4 |
+
from kaggle_sim_env.tasks import TASK_REGISTRY, get_task
|
| 5 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
"Action",
|
| 8 |
+
"Observation",
|
| 9 |
+
"Reward",
|
| 10 |
+
"EnvState",
|
| 11 |
+
"KaggleSimEnv",
|
| 12 |
+
"Grader",
|
| 13 |
+
"TASK_REGISTRY",
|
| 14 |
+
"get_task",
|
| 15 |
+
]
|
kaggle_sim_env/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (554 Bytes). View file
|
|
|
kaggle_sim_env/__pycache__/environment.cpython-312.pyc
ADDED
|
Binary file (16 kB). View file
|
|
|
kaggle_sim_env/__pycache__/grader.cpython-312.pyc
ADDED
|
Binary file (7.22 kB). View file
|
|
|
kaggle_sim_env/__pycache__/hints.cpython-312.pyc
ADDED
|
Binary file (1.78 kB). View file
|
|
|
kaggle_sim_env/__pycache__/leaderboard.cpython-312.pyc
ADDED
|
Binary file (3.09 kB). View file
|
|
|
kaggle_sim_env/__pycache__/models.cpython-312.pyc
ADDED
|
Binary file (13.1 kB). View file
|
|
|
kaggle_sim_env/__pycache__/rewards.cpython-312.pyc
ADDED
|
Binary file (3.11 kB). View file
|
|
|
kaggle_sim_env/__pycache__/tasks.cpython-312.pyc
ADDED
|
Binary file (29.2 kB). View file
|
|
|
kaggle_sim_env/environment.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Core KaggleSimEnv environment v3.
|
| 2 |
+
|
| 3 |
+
Causal logic, hierarchical categories, failure-mode traps, contextual rewards.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
from typing import Any
|
| 9 |
+
|
| 10 |
+
from kaggle_sim_env.hints import HintProvider
|
| 11 |
+
from kaggle_sim_env.leaderboard import Leaderboard
|
| 12 |
+
from kaggle_sim_env.models import (
|
| 13 |
+
Action,
|
| 14 |
+
ActionType,
|
| 15 |
+
EnvState,
|
| 16 |
+
FailureMode,
|
| 17 |
+
Observation,
|
| 18 |
+
Reward,
|
| 19 |
+
RewardBreakdown,
|
| 20 |
+
StepResponse,
|
| 21 |
+
get_allowed_values,
|
| 22 |
+
get_param_key,
|
| 23 |
+
validate_category,
|
| 24 |
+
)
|
| 25 |
+
from kaggle_sim_env.rewards import compute_reward
|
| 26 |
+
from kaggle_sim_env.tasks import TaskDefinition, get_task
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class KaggleSimEnv:
|
| 30 |
+
"""RL environment simulating a Kaggle competition with causal dataset logic."""
|
| 31 |
+
|
| 32 |
+
def __init__(self) -> None:
|
| 33 |
+
self._task: TaskDefinition | None = None
|
| 34 |
+
self._cv_score: float = 0.0
|
| 35 |
+
self._test_score: float = 0.0
|
| 36 |
+
self._applied: list[str] = []
|
| 37 |
+
self._history: list[str] = []
|
| 38 |
+
self._step_count: int = 0
|
| 39 |
+
self._done: bool = True
|
| 40 |
+
self._submitted: bool = False
|
| 41 |
+
self._leaderboard: Leaderboard | None = None
|
| 42 |
+
self._hints: HintProvider | None = None
|
| 43 |
+
self._overfitting_accum: float = 0.0
|
| 44 |
+
self._active_combos: list[str] = []
|
| 45 |
+
self._traps_triggered: list[str] = []
|
| 46 |
+
self._mitigated_traps: set[str] = set()
|
| 47 |
+
|
| 48 |
+
# ------------------------------------------------------------------
|
| 49 |
+
# OpenEnv API
|
| 50 |
+
# ------------------------------------------------------------------
|
| 51 |
+
|
| 52 |
+
def reset(self, task_id: str = "easy_churn") -> Observation:
|
| 53 |
+
self._task = get_task(task_id)
|
| 54 |
+
self._cv_score = self._task.base_cv_score
|
| 55 |
+
self._test_score = self._task.base_test_score
|
| 56 |
+
self._applied = []
|
| 57 |
+
self._history = []
|
| 58 |
+
self._step_count = 0
|
| 59 |
+
self._max_steps = 10
|
| 60 |
+
self._done = False
|
| 61 |
+
self._submitted = False
|
| 62 |
+
self._overfitting_accum = 0.0
|
| 63 |
+
self._active_combos = []
|
| 64 |
+
self._traps_triggered = []
|
| 65 |
+
self._mitigated_traps = set()
|
| 66 |
+
self._leaderboard = Leaderboard(task_id, list(self._task.ghost_scores))
|
| 67 |
+
self._hints = HintProvider(list(self._task.hints))
|
| 68 |
+
return self._observation(message="Environment reset. Choose your first action.")
|
| 69 |
+
|
| 70 |
+
def step(self, action: Action) -> StepResponse:
|
| 71 |
+
if self._task is None or self._done:
|
| 72 |
+
raise RuntimeError("Environment not active. Call reset() first.")
|
| 73 |
+
|
| 74 |
+
self._step_count += 1
|
| 75 |
+
info: dict[str, Any] = {}
|
| 76 |
+
prev_cv = self._cv_score
|
| 77 |
+
tag = action.tag()
|
| 78 |
+
|
| 79 |
+
# --- Validate ---
|
| 80 |
+
valid, msg = self._validate_action(action)
|
| 81 |
+
if not valid:
|
| 82 |
+
reward = Reward(total=-0.05, breakdown=RewardBreakdown(redundancy_penalty=-0.05))
|
| 83 |
+
info["error"] = msg
|
| 84 |
+
obs = self._observation(message=f"Invalid action: {msg}")
|
| 85 |
+
done = self._check_done()
|
| 86 |
+
return StepResponse(observation=obs, reward=reward, done=done, info=info)
|
| 87 |
+
|
| 88 |
+
# --- Apply action (mutates scores) ---
|
| 89 |
+
self._apply_action(action, info)
|
| 90 |
+
|
| 91 |
+
# --- Check failure-mode traps ---
|
| 92 |
+
traps_this_step = self._check_traps(action, info)
|
| 93 |
+
|
| 94 |
+
# --- Check strategy combos ---
|
| 95 |
+
newly_completed = self._check_combos()
|
| 96 |
+
if newly_completed:
|
| 97 |
+
info["combos_completed"] = newly_completed
|
| 98 |
+
|
| 99 |
+
# --- Contextual relevance ---
|
| 100 |
+
relevance = self._task.context_relevance.get(tag)
|
| 101 |
+
|
| 102 |
+
is_submit = action.action_type == ActionType.SUBMIT
|
| 103 |
+
reward = compute_reward(
|
| 104 |
+
prev_cv=prev_cv,
|
| 105 |
+
new_cv=self._cv_score,
|
| 106 |
+
new_test=self._test_score,
|
| 107 |
+
action_tag=tag,
|
| 108 |
+
expected_strategies=self._task.expected_strategies,
|
| 109 |
+
already_applied=self._applied[:-1] if tag in self._applied else self._applied,
|
| 110 |
+
is_submit=is_submit,
|
| 111 |
+
submitted_test_score=self._test_score if is_submit else None,
|
| 112 |
+
newly_completed_combos=newly_completed,
|
| 113 |
+
context_relevance=relevance,
|
| 114 |
+
traps_triggered_this_step=traps_this_step,
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
self._history.append(tag)
|
| 118 |
+
done = self._check_done()
|
| 119 |
+
obs = self._observation(message=info.get("message", ""))
|
| 120 |
+
return StepResponse(observation=obs, reward=reward, done=done, info=info)
|
| 121 |
+
|
| 122 |
+
def state(self) -> EnvState:
|
| 123 |
+
assert self._task is not None
|
| 124 |
+
assert self._leaderboard is not None
|
| 125 |
+
assert self._hints is not None
|
| 126 |
+
return EnvState(
|
| 127 |
+
task_id=self._task.task_id,
|
| 128 |
+
step_count=self._step_count,
|
| 129 |
+
max_steps=self._task.max_steps,
|
| 130 |
+
done=self._done,
|
| 131 |
+
cv_score=round(self._cv_score, 4),
|
| 132 |
+
test_score=round(self._test_score, 4),
|
| 133 |
+
applied_strategies=list(self._applied),
|
| 134 |
+
strategy_history=list(self._history),
|
| 135 |
+
leaderboard_rank=self._leaderboard.agent_rank(self._test_score),
|
| 136 |
+
leaderboard=self._leaderboard.full_board(self._test_score),
|
| 137 |
+
submitted=self._submitted,
|
| 138 |
+
hint_count=self._hints.hints_given,
|
| 139 |
+
active_combos=list(self._active_combos),
|
| 140 |
+
traps_triggered=list(self._traps_triggered),
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
# ------------------------------------------------------------------
|
| 144 |
+
# Observation
|
| 145 |
+
# ------------------------------------------------------------------
|
| 146 |
+
|
| 147 |
+
def _observation(self, message: str = "") -> Observation:
|
| 148 |
+
assert self._task is not None and self._leaderboard is not None
|
| 149 |
+
return Observation(
|
| 150 |
+
dataset_metadata=self._task.dataset_metadata,
|
| 151 |
+
applied_strategies=list(self._applied),
|
| 152 |
+
current_cv_score=round(self._cv_score, 4),
|
| 153 |
+
leaderboard_rank=self._leaderboard.agent_rank(self._test_score),
|
| 154 |
+
step_count=self._step_count,
|
| 155 |
+
max_steps=self._task.max_steps,
|
| 156 |
+
done=self._done,
|
| 157 |
+
message=message,
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
# ------------------------------------------------------------------
|
| 161 |
+
# Validation (hierarchical categories)
|
| 162 |
+
# ------------------------------------------------------------------
|
| 163 |
+
|
| 164 |
+
def _validate_action(self, action: Action) -> tuple[bool, str]:
|
| 165 |
+
at = action.action_type
|
| 166 |
+
p = action.parameters
|
| 167 |
+
|
| 168 |
+
if at == ActionType.PSEUDO_LABEL:
|
| 169 |
+
iters = p.get("iterations")
|
| 170 |
+
if iters is None or not isinstance(iters, int) or iters < 1:
|
| 171 |
+
return False, "pseudo_label requires 'iterations' (int >= 1)"
|
| 172 |
+
return True, ""
|
| 173 |
+
|
| 174 |
+
if at == ActionType.SUBMIT:
|
| 175 |
+
if self._submitted:
|
| 176 |
+
return False, "Already submitted."
|
| 177 |
+
if not any(s.startswith("train_model:") for s in self._applied):
|
| 178 |
+
return False, "Cannot submit without training a model first."
|
| 179 |
+
return True, ""
|
| 180 |
+
|
| 181 |
+
if at == ActionType.INSPECT_TOP_SOLUTION:
|
| 182 |
+
return True, ""
|
| 183 |
+
|
| 184 |
+
key = get_param_key(at.value)
|
| 185 |
+
if key:
|
| 186 |
+
value = p.get(key)
|
| 187 |
+
allowed = get_allowed_values(at.value)
|
| 188 |
+
if value is None or value not in allowed:
|
| 189 |
+
return False, f"{at.value} requires '{key}' in {allowed}"
|
| 190 |
+
cat_err = validate_category(at.value, p.get("category"), value)
|
| 191 |
+
if cat_err:
|
| 192 |
+
return False, cat_err
|
| 193 |
+
|
| 194 |
+
return True, ""
|
| 195 |
+
|
| 196 |
+
# ------------------------------------------------------------------
|
| 197 |
+
# Action application with causal logic
|
| 198 |
+
# ------------------------------------------------------------------
|
| 199 |
+
|
| 200 |
+
def _apply_action(self, action: Action, info: dict[str, Any]) -> None:
|
| 201 |
+
assert self._task is not None and self._hints is not None
|
| 202 |
+
tag = action.tag()
|
| 203 |
+
modifiers = self._task.score_modifiers
|
| 204 |
+
overfit_risk = self._task.overfitting_risk
|
| 205 |
+
props = self._task.dataset_properties
|
| 206 |
+
|
| 207 |
+
if action.action_type == ActionType.INSPECT_TOP_SOLUTION:
|
| 208 |
+
hint = self._hints.next_hint()
|
| 209 |
+
info["hint"] = hint
|
| 210 |
+
info["message"] = f"Hint: {hint}"
|
| 211 |
+
if tag not in self._applied:
|
| 212 |
+
self._applied.append(tag)
|
| 213 |
+
return
|
| 214 |
+
|
| 215 |
+
if action.action_type == ActionType.SUBMIT:
|
| 216 |
+
self._submitted = True
|
| 217 |
+
self._done = True
|
| 218 |
+
info["message"] = (
|
| 219 |
+
f"Submitted! Final test score: {self._test_score:.4f} "
|
| 220 |
+
f"CV score: {self._cv_score:.4f}"
|
| 221 |
+
)
|
| 222 |
+
return
|
| 223 |
+
|
| 224 |
+
is_repeat = tag in self._applied
|
| 225 |
+
|
| 226 |
+
# --- Track mitigation actions ---
|
| 227 |
+
self._update_mitigations(tag, props)
|
| 228 |
+
|
| 229 |
+
if tag in modifiers:
|
| 230 |
+
cv_delta = modifiers[tag]["cv"]
|
| 231 |
+
test_delta = modifiers[tag]["test"]
|
| 232 |
+
|
| 233 |
+
if is_repeat:
|
| 234 |
+
cv_delta *= 0.1
|
| 235 |
+
test_delta *= 0.1
|
| 236 |
+
else:
|
| 237 |
+
self._applied.append(tag)
|
| 238 |
+
|
| 239 |
+
overfit_extra = overfit_risk.get(tag, 0.0)
|
| 240 |
+
self._overfitting_accum += overfit_extra
|
| 241 |
+
cv_delta += overfit_extra
|
| 242 |
+
|
| 243 |
+
self._cv_score = min(1.0, self._cv_score + cv_delta)
|
| 244 |
+
self._test_score = min(1.0, self._test_score + test_delta)
|
| 245 |
+
|
| 246 |
+
rank = self._leaderboard.agent_rank(self._test_score) if self._leaderboard else "?"
|
| 247 |
+
info["message"] = f"Applied {action.full_tag()}. CV: {self._cv_score:.4f}, Rank: {rank}"
|
| 248 |
+
else:
|
| 249 |
+
if tag not in self._applied:
|
| 250 |
+
self._applied.append(tag)
|
| 251 |
+
info["message"] = f"Applied {action.full_tag()} (no modifier for this task)."
|
| 252 |
+
|
| 253 |
+
def _update_mitigations(self, tag: str, props: Any) -> None:
|
| 254 |
+
"""Track which failure modes the agent has pre-emptively mitigated."""
|
| 255 |
+
if tag == "detect_shift:adversarial_validation" and props.has_shift:
|
| 256 |
+
self._mitigated_traps.add("ignoring_shift")
|
| 257 |
+
if tag == "detect_shift:remove_identifiers" and props.has_shift:
|
| 258 |
+
self._mitigated_traps.add("ignoring_shift")
|
| 259 |
+
if tag == "clean_data:remove_leaky_features" and props.has_leakage:
|
| 260 |
+
self._mitigated_traps.add("keeping_leaky_feature")
|
| 261 |
+
if tag == "feature_engineering:sin_cos_encoding" and props.has_spatial_data:
|
| 262 |
+
self._mitigated_traps.add("raw_heading_without_sincos")
|
| 263 |
+
if tag.startswith("augmentation:") and props.has_images:
|
| 264 |
+
self._mitigated_traps.add("no_augmentation_on_images")
|
| 265 |
+
|
| 266 |
+
# ------------------------------------------------------------------
|
| 267 |
+
# Failure-mode trap detection
|
| 268 |
+
# ------------------------------------------------------------------
|
| 269 |
+
|
| 270 |
+
def _check_traps(self, action: Action, info: dict[str, Any]) -> list[str]:
|
| 271 |
+
assert self._task is not None
|
| 272 |
+
tag = action.tag()
|
| 273 |
+
props = self._task.dataset_properties
|
| 274 |
+
triggered: list[str] = []
|
| 275 |
+
|
| 276 |
+
for fm in self._task.failure_modes:
|
| 277 |
+
if fm.name in self._traps_triggered:
|
| 278 |
+
continue
|
| 279 |
+
if fm.name in self._mitigated_traps:
|
| 280 |
+
continue
|
| 281 |
+
if tag != fm.trigger_tag:
|
| 282 |
+
continue
|
| 283 |
+
|
| 284 |
+
prop_val = getattr(props, fm.condition_field, None)
|
| 285 |
+
if prop_val == fm.condition_value:
|
| 286 |
+
self._cv_score = min(1.0, self._cv_score + fm.cv_effect)
|
| 287 |
+
self._test_score = max(0.0, self._test_score + fm.test_effect)
|
| 288 |
+
self._traps_triggered.append(fm.name)
|
| 289 |
+
triggered.append(fm.name)
|
| 290 |
+
|
| 291 |
+
prev_msg = info.get("message", "")
|
| 292 |
+
info["message"] = f"{prev_msg} TRAP: {fm.message}"
|
| 293 |
+
info.setdefault("traps", []).append({
|
| 294 |
+
"name": fm.name,
|
| 295 |
+
"message": fm.message,
|
| 296 |
+
"cv_effect": fm.cv_effect,
|
| 297 |
+
"test_effect": fm.test_effect,
|
| 298 |
+
})
|
| 299 |
+
|
| 300 |
+
return triggered
|
| 301 |
+
|
| 302 |
+
# ------------------------------------------------------------------
|
| 303 |
+
# Combo detection
|
| 304 |
+
# ------------------------------------------------------------------
|
| 305 |
+
|
| 306 |
+
def _check_combos(self) -> list[str]:
|
| 307 |
+
assert self._task is not None
|
| 308 |
+
newly_completed: list[str] = []
|
| 309 |
+
applied_set = set(self._applied)
|
| 310 |
+
for combo in self._task.strategy_combos:
|
| 311 |
+
if combo.name in self._active_combos:
|
| 312 |
+
continue
|
| 313 |
+
if combo.required.issubset(applied_set):
|
| 314 |
+
self._active_combos.append(combo.name)
|
| 315 |
+
self._cv_score = min(1.0, self._cv_score + combo.cv_bonus)
|
| 316 |
+
self._test_score = min(1.0, self._test_score + combo.test_bonus)
|
| 317 |
+
newly_completed.append(combo.name)
|
| 318 |
+
return newly_completed
|
| 319 |
+
|
| 320 |
+
# ------------------------------------------------------------------
|
| 321 |
+
# Done
|
| 322 |
+
# ------------------------------------------------------------------
|
| 323 |
+
|
| 324 |
+
def _check_done(self) -> bool:
|
| 325 |
+
if self._done:
|
| 326 |
+
return True
|
| 327 |
+
assert self._task is not None
|
| 328 |
+
if self._step_count >= self._task.max_steps:
|
| 329 |
+
self._done = True
|
| 330 |
+
return self._done
|
kaggle_sim_env/grader.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Deterministic grader for KaggleSimEnv v3.
|
| 2 |
+
|
| 3 |
+
Score [0.0 – 1.0] combining:
|
| 4 |
+
performance_score : test score normalised vs ghost competitors
|
| 5 |
+
strategy_score : contextual — only credit strategies relevant to THIS dataset
|
| 6 |
+
combo_score : fraction of strategy combos activated
|
| 7 |
+
trap_penalty : deduction for falling into failure-mode traps
|
| 8 |
+
|
| 9 |
+
final = 0.40×perf + 0.25×strategy + 0.20×combo + 0.15×(1 - trap_rate)
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
from pydantic import BaseModel, Field
|
| 15 |
+
|
| 16 |
+
from kaggle_sim_env.models import EnvState
|
| 17 |
+
from kaggle_sim_env.tasks import TaskDefinition
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class GradeResult(BaseModel):
|
| 21 |
+
task_id: str
|
| 22 |
+
performance_score: float = Field(ge=0.0, le=1.0)
|
| 23 |
+
strategy_score: float = Field(ge=0.0, le=1.0)
|
| 24 |
+
combo_score: float = Field(ge=0.0, le=1.0)
|
| 25 |
+
trap_score: float = Field(ge=0.0, le=1.0)
|
| 26 |
+
final_score: float = Field(ge=0.0, le=1.0)
|
| 27 |
+
details: dict[str, object] = Field(default_factory=dict)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class Grader:
|
| 31 |
+
|
| 32 |
+
PERF_W = 0.40
|
| 33 |
+
STRAT_W = 0.25
|
| 34 |
+
COMBO_W = 0.20
|
| 35 |
+
TRAP_W = 0.15
|
| 36 |
+
|
| 37 |
+
def grade(self, state: EnvState, task: TaskDefinition) -> GradeResult:
|
| 38 |
+
perf = self._performance_score(state, task)
|
| 39 |
+
strat = self._contextual_strategy_score(state, task)
|
| 40 |
+
combo = self._combo_score(state, task)
|
| 41 |
+
trap = self._trap_score(state, task)
|
| 42 |
+
final = round(
|
| 43 |
+
self.PERF_W * perf + self.STRAT_W * strat
|
| 44 |
+
+ self.COMBO_W * combo + self.TRAP_W * trap,
|
| 45 |
+
4,
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
return GradeResult(
|
| 49 |
+
task_id=task.task_id,
|
| 50 |
+
performance_score=round(perf, 4),
|
| 51 |
+
strategy_score=round(strat, 4),
|
| 52 |
+
combo_score=round(combo, 4),
|
| 53 |
+
trap_score=round(trap, 4),
|
| 54 |
+
final_score=final,
|
| 55 |
+
details={
|
| 56 |
+
"test_score": state.test_score,
|
| 57 |
+
"cv_score": state.cv_score,
|
| 58 |
+
"cv_test_gap": round(abs(state.cv_score - state.test_score), 4),
|
| 59 |
+
"steps_used": state.step_count,
|
| 60 |
+
"max_steps": state.max_steps,
|
| 61 |
+
"submitted": state.submitted,
|
| 62 |
+
"expected_strategies": task.expected_strategies,
|
| 63 |
+
"applied_strategies": state.applied_strategies,
|
| 64 |
+
"matched_strategies": self._matched(state, task),
|
| 65 |
+
"missing_strategies": self._missing(state, task),
|
| 66 |
+
"irrelevant_strategies_used": self._irrelevant_used(state, task),
|
| 67 |
+
"total_combos": len(task.strategy_combos),
|
| 68 |
+
"active_combos": state.active_combos,
|
| 69 |
+
"traps_triggered": state.traps_triggered,
|
| 70 |
+
"total_failure_modes": len(task.failure_modes),
|
| 71 |
+
},
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
# --- Performance ---
|
| 75 |
+
|
| 76 |
+
@staticmethod
|
| 77 |
+
def _performance_score(state: EnvState, task: TaskDefinition) -> float:
|
| 78 |
+
if not state.submitted:
|
| 79 |
+
return 0.0
|
| 80 |
+
ghost_max = max(task.ghost_scores) if task.ghost_scores else 1.0
|
| 81 |
+
ghost_min = min(task.ghost_scores) if task.ghost_scores else 0.0
|
| 82 |
+
rng = ghost_max - ghost_min
|
| 83 |
+
if rng < 1e-9:
|
| 84 |
+
return float(state.test_score >= ghost_max)
|
| 85 |
+
raw = (state.test_score - ghost_min) / rng
|
| 86 |
+
return max(0.0, min(1.0, raw))
|
| 87 |
+
|
| 88 |
+
# --- Contextual strategy score ---
|
| 89 |
+
|
| 90 |
+
@staticmethod
|
| 91 |
+
def _contextual_strategy_score(state: EnvState, task: TaskDefinition) -> float:
|
| 92 |
+
"""Credit for relevant strategies, penalise irrelevant ones."""
|
| 93 |
+
expected = set(task.expected_strategies)
|
| 94 |
+
if not expected:
|
| 95 |
+
return 1.0
|
| 96 |
+
|
| 97 |
+
matched = expected.intersection(state.applied_strategies)
|
| 98 |
+
base = len(matched) / len(expected)
|
| 99 |
+
|
| 100 |
+
irrelevant_count = 0
|
| 101 |
+
for strat in state.applied_strategies:
|
| 102 |
+
rel = task.context_relevance.get(strat)
|
| 103 |
+
if rel is not None and rel <= -0.5:
|
| 104 |
+
irrelevant_count += 1
|
| 105 |
+
|
| 106 |
+
penalty = min(irrelevant_count * 0.05, 0.3)
|
| 107 |
+
return max(0.0, round(base - penalty, 4))
|
| 108 |
+
|
| 109 |
+
# --- Combo ---
|
| 110 |
+
|
| 111 |
+
@staticmethod
|
| 112 |
+
def _combo_score(state: EnvState, task: TaskDefinition) -> float:
|
| 113 |
+
total = len(task.strategy_combos)
|
| 114 |
+
if total == 0:
|
| 115 |
+
return 1.0
|
| 116 |
+
return len(state.active_combos) / total
|
| 117 |
+
|
| 118 |
+
# --- Trap score (1.0 = no traps, 0.0 = all traps triggered) ---
|
| 119 |
+
|
| 120 |
+
@staticmethod
|
| 121 |
+
def _trap_score(state: EnvState, task: TaskDefinition) -> float:
|
| 122 |
+
total = len(task.failure_modes)
|
| 123 |
+
if total == 0:
|
| 124 |
+
return 1.0
|
| 125 |
+
triggered = len(state.traps_triggered)
|
| 126 |
+
return max(0.0, 1.0 - triggered / total)
|
| 127 |
+
|
| 128 |
+
# --- Helpers ---
|
| 129 |
+
|
| 130 |
+
@staticmethod
|
| 131 |
+
def _matched(state: EnvState, task: TaskDefinition) -> list[str]:
|
| 132 |
+
return sorted(set(task.expected_strategies) & set(state.applied_strategies))
|
| 133 |
+
|
| 134 |
+
@staticmethod
|
| 135 |
+
def _missing(state: EnvState, task: TaskDefinition) -> list[str]:
|
| 136 |
+
return sorted(set(task.expected_strategies) - set(state.applied_strategies))
|
| 137 |
+
|
| 138 |
+
@staticmethod
|
| 139 |
+
def _irrelevant_used(state: EnvState, task: TaskDefinition) -> list[str]:
|
| 140 |
+
result = []
|
| 141 |
+
for strat in state.applied_strategies:
|
| 142 |
+
rel = task.context_relevance.get(strat)
|
| 143 |
+
if rel is not None and rel <= -0.5:
|
| 144 |
+
result.append(strat)
|
| 145 |
+
return result
|
kaggle_sim_env/hints.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Hints system for KaggleSimEnv.
|
| 2 |
+
|
| 3 |
+
When the agent takes the `inspect_top_solution` action it receives a hint
|
| 4 |
+
from the task's hint list. Hints are dispensed in order; once all hints are
|
| 5 |
+
exhausted the agent receives a generic message.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class HintProvider:
|
| 12 |
+
"""Dispenses task-specific hints one at a time."""
|
| 13 |
+
|
| 14 |
+
def __init__(self, hints: list[str]) -> None:
|
| 15 |
+
self._hints = list(hints)
|
| 16 |
+
self._index = 0
|
| 17 |
+
|
| 18 |
+
@property
|
| 19 |
+
def hints_given(self) -> int:
|
| 20 |
+
return self._index
|
| 21 |
+
|
| 22 |
+
def next_hint(self) -> str:
|
| 23 |
+
if self._index < len(self._hints):
|
| 24 |
+
hint = self._hints[self._index]
|
| 25 |
+
self._index += 1
|
| 26 |
+
return hint
|
| 27 |
+
return "No more hints available. Try combining strategies you've learned so far."
|
| 28 |
+
|
| 29 |
+
def reset(self) -> None:
|
| 30 |
+
self._index = 0
|
kaggle_sim_env/leaderboard.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Simulated leaderboard with ghost competitors.
|
| 2 |
+
|
| 3 |
+
Ghost competitors have fixed scores. The agent's rank is computed by
|
| 4 |
+
inserting its current test score into the sorted ghost list.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import hashlib
|
| 10 |
+
from typing import Any
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
_GHOST_NAMES = [
|
| 14 |
+
"GrandMaster_42",
|
| 15 |
+
"DataWizard",
|
| 16 |
+
"FeatureKing",
|
| 17 |
+
"BoostQueen",
|
| 18 |
+
"StackMaster",
|
| 19 |
+
"NeuralNinja",
|
| 20 |
+
"EnsembleGuru",
|
| 21 |
+
"CVExpert",
|
| 22 |
+
"KagglePro",
|
| 23 |
+
]
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _deterministic_name(index: int, task_id: str) -> str:
|
| 27 |
+
"""Generate a stable ghost name seeded by task + index."""
|
| 28 |
+
digest = hashlib.md5(f"{task_id}:{index}".encode()).hexdigest()[:4]
|
| 29 |
+
base = _GHOST_NAMES[index % len(_GHOST_NAMES)]
|
| 30 |
+
return f"{base}_{digest}"
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class Leaderboard:
|
| 34 |
+
"""Maintains a leaderboard of ghost competitors plus the agent."""
|
| 35 |
+
|
| 36 |
+
def __init__(self, task_id: str, ghost_scores: list[float]) -> None:
|
| 37 |
+
self.task_id = task_id
|
| 38 |
+
self.ghosts: list[dict[str, Any]] = []
|
| 39 |
+
for i, score in enumerate(sorted(ghost_scores, reverse=True)):
|
| 40 |
+
self.ghosts.append({
|
| 41 |
+
"name": _deterministic_name(i, task_id),
|
| 42 |
+
"score": round(score, 4),
|
| 43 |
+
"rank": i + 1,
|
| 44 |
+
})
|
| 45 |
+
|
| 46 |
+
def agent_rank(self, agent_score: float) -> int:
|
| 47 |
+
"""Return 1-indexed rank of the agent among ghosts."""
|
| 48 |
+
rank = 1
|
| 49 |
+
for ghost in self.ghosts:
|
| 50 |
+
if ghost["score"] > agent_score:
|
| 51 |
+
rank += 1
|
| 52 |
+
return rank
|
| 53 |
+
|
| 54 |
+
def full_board(self, agent_score: float) -> list[dict[str, Any]]:
|
| 55 |
+
"""Return complete leaderboard with agent inserted."""
|
| 56 |
+
entries = list(self.ghosts) + [
|
| 57 |
+
{"name": "Agent", "score": round(agent_score, 4), "rank": 0}
|
| 58 |
+
]
|
| 59 |
+
entries.sort(key=lambda e: e["score"], reverse=True)
|
| 60 |
+
for i, entry in enumerate(entries):
|
| 61 |
+
entry["rank"] = i + 1
|
| 62 |
+
return entries
|
kaggle_sim_env/models.py
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Pydantic models for KaggleSimEnv.
|
| 2 |
+
|
| 3 |
+
v3 — hierarchical categories, causal dataset properties, failure modes.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
from enum import Enum
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
from pydantic import BaseModel, Field, field_validator
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# =========================================================================
|
| 15 |
+
# Hierarchical category mappings
|
| 16 |
+
# technique → category (the single source of truth)
|
| 17 |
+
# =========================================================================
|
| 18 |
+
|
| 19 |
+
CATEGORY_MAP: dict[str, dict[str, str]] = {
|
| 20 |
+
"set_cv": {
|
| 21 |
+
"kfold": "standard",
|
| 22 |
+
"repeated_kfold": "standard",
|
| 23 |
+
"group_kfold": "group",
|
| 24 |
+
"stratified_group_kfold": "group",
|
| 25 |
+
"time_split": "temporal",
|
| 26 |
+
"combined_group_time": "temporal",
|
| 27 |
+
},
|
| 28 |
+
"feature_engineering": {
|
| 29 |
+
"log_transform": "distribution",
|
| 30 |
+
"normalize": "distribution",
|
| 31 |
+
"quantile_features": "distribution",
|
| 32 |
+
"interaction_terms": "interaction",
|
| 33 |
+
"domain_ratios": "interaction",
|
| 34 |
+
"sin_cos_encoding": "encoding",
|
| 35 |
+
"target_encoding": "encoding",
|
| 36 |
+
"spatial_encoding": "encoding",
|
| 37 |
+
"tfidf_features": "encoding",
|
| 38 |
+
"relative_coordinates": "spatial",
|
| 39 |
+
"distance_features": "spatial",
|
| 40 |
+
"frequency_features": "signal",
|
| 41 |
+
"multi_layer_features": "signal",
|
| 42 |
+
"fourier_resampling": "signal",
|
| 43 |
+
},
|
| 44 |
+
"detect_shift": {
|
| 45 |
+
"adversarial_validation": "detection",
|
| 46 |
+
"feature_importance_shift": "detection",
|
| 47 |
+
"remove_identifiers": "mitigation",
|
| 48 |
+
"domain_invariant_features": "mitigation",
|
| 49 |
+
},
|
| 50 |
+
"train_model": {
|
| 51 |
+
"xgboost": "tree",
|
| 52 |
+
"lightgbm": "tree",
|
| 53 |
+
"catboost": "tree",
|
| 54 |
+
"random_forest": "tree",
|
| 55 |
+
"linear": "linear",
|
| 56 |
+
"neural_network": "neural",
|
| 57 |
+
"pretrained_backbone": "neural",
|
| 58 |
+
"temporal_cnn": "neural",
|
| 59 |
+
"transformer_encoder": "neural",
|
| 60 |
+
},
|
| 61 |
+
"handle_imbalance": {
|
| 62 |
+
"scale_pos_weight": "weighting",
|
| 63 |
+
"class_weighted_loss": "weighting",
|
| 64 |
+
"calibrate_probabilities": "calibration",
|
| 65 |
+
"optimize_threshold": "calibration",
|
| 66 |
+
"hierarchical_labels": "hierarchy",
|
| 67 |
+
"lower_thresholds_recall": "hierarchy",
|
| 68 |
+
},
|
| 69 |
+
"clean_data": {
|
| 70 |
+
"remove_corrupted": "removal",
|
| 71 |
+
"remove_outliers": "removal",
|
| 72 |
+
"remove_leaky_features": "removal",
|
| 73 |
+
"analytical_reconstruction": "reconstruction",
|
| 74 |
+
"nan_native_model": "reconstruction",
|
| 75 |
+
"domain_augmentation": "reconstruction",
|
| 76 |
+
"clean_subset_training": "reconstruction",
|
| 77 |
+
},
|
| 78 |
+
"augmentation": {
|
| 79 |
+
"geometric": "geometric",
|
| 80 |
+
"rotation_invariant": "geometric",
|
| 81 |
+
"image_rectification": "geometric",
|
| 82 |
+
"color_transform": "color",
|
| 83 |
+
"clahe": "color",
|
| 84 |
+
"gaussian_noise": "noise",
|
| 85 |
+
"robustness_augmentation": "noise",
|
| 86 |
+
"camera_simulation": "domain",
|
| 87 |
+
"temporal_augmentation": "domain",
|
| 88 |
+
"symmetry_augmentation": "domain",
|
| 89 |
+
"multi_view_processing": "domain",
|
| 90 |
+
},
|
| 91 |
+
"ensemble": {
|
| 92 |
+
"weighted_average": "averaging",
|
| 93 |
+
"multi_seed_averaging": "averaging",
|
| 94 |
+
"swa": "averaging",
|
| 95 |
+
"stacking": "stacking",
|
| 96 |
+
"diverse_features": "diversity",
|
| 97 |
+
"heterogeneous": "diversity",
|
| 98 |
+
},
|
| 99 |
+
"postprocess": {
|
| 100 |
+
"bias_correction": "calibration",
|
| 101 |
+
"prediction_shrinkage": "calibration",
|
| 102 |
+
"per_group_calibration": "calibration",
|
| 103 |
+
"domain_rules": "domain",
|
| 104 |
+
"physics_constraints": "domain",
|
| 105 |
+
"tta": "inference",
|
| 106 |
+
},
|
| 107 |
+
"tune_loss": {
|
| 108 |
+
"asymmetric_loss": "asymmetric",
|
| 109 |
+
"epsilon_insensitive": "asymmetric",
|
| 110 |
+
"gaussian_nll": "uncertainty",
|
| 111 |
+
"multi_task": "multi_objective",
|
| 112 |
+
"interval_regression": "multi_objective",
|
| 113 |
+
"quantile_regression": "multi_objective",
|
| 114 |
+
"sample_weighted": "weighting",
|
| 115 |
+
"auxiliary_physics_loss": "weighting",
|
| 116 |
+
},
|
| 117 |
+
"regularize": {
|
| 118 |
+
"strong_regularization": "weight",
|
| 119 |
+
"ema": "weight",
|
| 120 |
+
"dropout": "weight",
|
| 121 |
+
"freeze_backbone": "transfer",
|
| 122 |
+
},
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def get_categories_for_action(action_type: str) -> dict[str, list[str]]:
|
| 127 |
+
"""Return {category: [techniques]} for an action type."""
|
| 128 |
+
mapping = CATEGORY_MAP.get(action_type, {})
|
| 129 |
+
result: dict[str, list[str]] = {}
|
| 130 |
+
for technique, cat in mapping.items():
|
| 131 |
+
result.setdefault(cat, []).append(technique)
|
| 132 |
+
return result
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def validate_category(action_type: str, category: str | None, technique: str) -> str | None:
|
| 136 |
+
"""Validate and return the correct category. Returns error message on failure."""
|
| 137 |
+
mapping = CATEGORY_MAP.get(action_type)
|
| 138 |
+
if mapping is None:
|
| 139 |
+
return None
|
| 140 |
+
expected_cat = mapping.get(technique)
|
| 141 |
+
if expected_cat is None:
|
| 142 |
+
return f"Unknown technique '{technique}' for {action_type}"
|
| 143 |
+
if category is not None and category != expected_cat:
|
| 144 |
+
return (
|
| 145 |
+
f"Wrong category '{category}' for {action_type}:{technique}. "
|
| 146 |
+
f"Expected '{expected_cat}'."
|
| 147 |
+
)
|
| 148 |
+
return None
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def infer_category(action_type: str, technique: str) -> str | None:
|
| 152 |
+
"""Infer category from action_type + technique."""
|
| 153 |
+
mapping = CATEGORY_MAP.get(action_type, {})
|
| 154 |
+
return mapping.get(technique)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
# =========================================================================
|
| 158 |
+
# Action types
|
| 159 |
+
# =========================================================================
|
| 160 |
+
|
| 161 |
+
class ActionType(str, Enum):
|
| 162 |
+
SET_CV = "set_cv"
|
| 163 |
+
FEATURE_ENGINEERING = "feature_engineering"
|
| 164 |
+
DETECT_SHIFT = "detect_shift"
|
| 165 |
+
TRAIN_MODEL = "train_model"
|
| 166 |
+
HANDLE_IMBALANCE = "handle_imbalance"
|
| 167 |
+
CLEAN_DATA = "clean_data"
|
| 168 |
+
AUGMENTATION = "augmentation"
|
| 169 |
+
ENSEMBLE = "ensemble"
|
| 170 |
+
PSEUDO_LABEL = "pseudo_label"
|
| 171 |
+
POSTPROCESS = "postprocess"
|
| 172 |
+
TUNE_LOSS = "tune_loss"
|
| 173 |
+
REGULARIZE = "regularize"
|
| 174 |
+
INSPECT_TOP_SOLUTION = "inspect_top_solution"
|
| 175 |
+
SUBMIT = "submit"
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
_PARAM_KEY_MAP: dict[str, str] = {
|
| 179 |
+
"set_cv": "strategy",
|
| 180 |
+
"feature_engineering": "technique",
|
| 181 |
+
"detect_shift": "method",
|
| 182 |
+
"train_model": "algorithm",
|
| 183 |
+
"handle_imbalance": "method",
|
| 184 |
+
"clean_data": "method",
|
| 185 |
+
"augmentation": "method",
|
| 186 |
+
"ensemble": "method",
|
| 187 |
+
"postprocess": "method",
|
| 188 |
+
"tune_loss": "method",
|
| 189 |
+
"regularize": "method",
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def get_param_key(action_type: str) -> str | None:
|
| 194 |
+
return _PARAM_KEY_MAP.get(action_type)
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def get_allowed_values(action_type: str) -> list[str]:
|
| 198 |
+
mapping = CATEGORY_MAP.get(action_type, {})
|
| 199 |
+
return list(mapping.keys())
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
# =========================================================================
|
| 203 |
+
# Action
|
| 204 |
+
# =========================================================================
|
| 205 |
+
|
| 206 |
+
class Action(BaseModel):
|
| 207 |
+
"""A single structured action with hierarchical category."""
|
| 208 |
+
|
| 209 |
+
action_type: ActionType
|
| 210 |
+
parameters: dict[str, Any] = Field(default_factory=dict)
|
| 211 |
+
|
| 212 |
+
@field_validator("parameters", mode="before")
|
| 213 |
+
@classmethod
|
| 214 |
+
def _coerce_parameters(cls, v: Any) -> dict[str, Any]:
|
| 215 |
+
if v is None:
|
| 216 |
+
return {}
|
| 217 |
+
return v
|
| 218 |
+
|
| 219 |
+
def technique_value(self) -> str | None:
|
| 220 |
+
"""Return the technique/strategy/method/algorithm value."""
|
| 221 |
+
key = get_param_key(self.action_type.value)
|
| 222 |
+
if key is None:
|
| 223 |
+
return None
|
| 224 |
+
return self.parameters.get(key)
|
| 225 |
+
|
| 226 |
+
def category_value(self) -> str | None:
|
| 227 |
+
"""Return the declared category, or infer it."""
|
| 228 |
+
declared = self.parameters.get("category")
|
| 229 |
+
if declared:
|
| 230 |
+
return declared
|
| 231 |
+
tv = self.technique_value()
|
| 232 |
+
if tv:
|
| 233 |
+
return infer_category(self.action_type.value, tv)
|
| 234 |
+
return None
|
| 235 |
+
|
| 236 |
+
def tag(self) -> str:
|
| 237 |
+
"""Concise string for history tracking (without category)."""
|
| 238 |
+
at = self.action_type
|
| 239 |
+
if at == ActionType.PSEUDO_LABEL:
|
| 240 |
+
return f"pseudo_label:{self.parameters.get('iterations', 1)}"
|
| 241 |
+
if at in (ActionType.INSPECT_TOP_SOLUTION, ActionType.SUBMIT):
|
| 242 |
+
return at.value
|
| 243 |
+
tv = self.technique_value()
|
| 244 |
+
if tv:
|
| 245 |
+
return f"{at.value}:{tv}"
|
| 246 |
+
return at.value
|
| 247 |
+
|
| 248 |
+
def full_tag(self) -> str:
|
| 249 |
+
"""Verbose string including category."""
|
| 250 |
+
cat = self.category_value()
|
| 251 |
+
base = self.tag()
|
| 252 |
+
if cat:
|
| 253 |
+
parts = base.split(":", 1)
|
| 254 |
+
if len(parts) == 2:
|
| 255 |
+
return f"{parts[0]}:{cat}:{parts[1]}"
|
| 256 |
+
return base
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
# =========================================================================
|
| 260 |
+
# Causal dataset properties
|
| 261 |
+
# =========================================================================
|
| 262 |
+
|
| 263 |
+
class DatasetProperties(BaseModel):
|
| 264 |
+
"""Ground-truth properties that govern causal reward logic."""
|
| 265 |
+
|
| 266 |
+
has_shift: bool = False
|
| 267 |
+
has_leakage: bool = False
|
| 268 |
+
has_noise_features: bool = False
|
| 269 |
+
has_missing_data: bool = False
|
| 270 |
+
has_imbalance: bool = False
|
| 271 |
+
imbalance_ratio: float = 0.5
|
| 272 |
+
has_heavy_tails: bool = False
|
| 273 |
+
has_time_column: bool = False
|
| 274 |
+
has_group_column: bool = False
|
| 275 |
+
has_images: bool = False
|
| 276 |
+
has_spatial_data: bool = False
|
| 277 |
+
has_text: bool = False
|
| 278 |
+
is_safety_critical: bool = False
|
| 279 |
+
needs_physics: bool = False
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
# =========================================================================
|
| 283 |
+
# Failure mode definition
|
| 284 |
+
# =========================================================================
|
| 285 |
+
|
| 286 |
+
class FailureMode(BaseModel):
|
| 287 |
+
"""A trap the agent can fall into."""
|
| 288 |
+
|
| 289 |
+
name: str
|
| 290 |
+
trigger_tag: str
|
| 291 |
+
condition_field: str
|
| 292 |
+
condition_value: bool
|
| 293 |
+
cv_effect: float
|
| 294 |
+
test_effect: float
|
| 295 |
+
message: str
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
# =========================================================================
|
| 299 |
+
# Dataset metadata (observation-facing)
|
| 300 |
+
# =========================================================================
|
| 301 |
+
|
| 302 |
+
class ColumnInfo(BaseModel):
|
| 303 |
+
name: str
|
| 304 |
+
dtype: str
|
| 305 |
+
missing_pct: float = Field(ge=0.0, le=100.0)
|
| 306 |
+
unique_count: int = Field(ge=0)
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
class DatasetMetadata(BaseModel):
|
| 310 |
+
num_rows: int
|
| 311 |
+
num_features: int
|
| 312 |
+
columns: list[ColumnInfo]
|
| 313 |
+
target_column: str
|
| 314 |
+
task_type: str = "classification"
|
| 315 |
+
has_time_column: bool = False
|
| 316 |
+
has_group_column: bool = False
|
| 317 |
+
has_image_data: bool = False
|
| 318 |
+
has_text_data: bool = False
|
| 319 |
+
has_spatial_data: bool = False
|
| 320 |
+
class_balance: dict[str, float] = Field(default_factory=dict)
|
| 321 |
+
target_distribution: str = "normal"
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
# =========================================================================
|
| 325 |
+
# Observation
|
| 326 |
+
# =========================================================================
|
| 327 |
+
|
| 328 |
+
class Observation(BaseModel):
|
| 329 |
+
dataset_metadata: DatasetMetadata
|
| 330 |
+
applied_strategies: list[str]
|
| 331 |
+
current_cv_score: float
|
| 332 |
+
leaderboard_rank: int
|
| 333 |
+
step_count: int
|
| 334 |
+
max_steps: int
|
| 335 |
+
done: bool
|
| 336 |
+
message: str = ""
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
# =========================================================================
|
| 340 |
+
# Reward
|
| 341 |
+
# =========================================================================
|
| 342 |
+
|
| 343 |
+
class RewardBreakdown(BaseModel):
|
| 344 |
+
cv_improvement: float = 0.0
|
| 345 |
+
strategy_bonus: float = 0.0
|
| 346 |
+
context_bonus: float = 0.0
|
| 347 |
+
combo_bonus: float = 0.0
|
| 348 |
+
redundancy_penalty: float = 0.0
|
| 349 |
+
irrelevant_penalty: float = 0.0
|
| 350 |
+
trap_penalty: float = 0.0
|
| 351 |
+
overfitting_penalty: float = 0.0
|
| 352 |
+
submission_bonus: float = 0.0
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
class Reward(BaseModel):
|
| 356 |
+
total: float
|
| 357 |
+
breakdown: RewardBreakdown
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
# =========================================================================
|
| 361 |
+
# Full environment state
|
| 362 |
+
# =========================================================================
|
| 363 |
+
|
| 364 |
+
class EnvState(BaseModel):
|
| 365 |
+
task_id: str
|
| 366 |
+
step_count: int
|
| 367 |
+
max_steps: int
|
| 368 |
+
done: bool
|
| 369 |
+
cv_score: float
|
| 370 |
+
test_score: float
|
| 371 |
+
applied_strategies: list[str]
|
| 372 |
+
strategy_history: list[str]
|
| 373 |
+
leaderboard_rank: int
|
| 374 |
+
leaderboard: list[dict[str, Any]]
|
| 375 |
+
submitted: bool
|
| 376 |
+
hint_count: int
|
| 377 |
+
active_combos: list[str] = Field(default_factory=list)
|
| 378 |
+
traps_triggered: list[str] = Field(default_factory=list)
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
# =========================================================================
|
| 382 |
+
# Step response
|
| 383 |
+
# =========================================================================
|
| 384 |
+
|
| 385 |
+
class StepResponse(BaseModel):
|
| 386 |
+
observation: Observation
|
| 387 |
+
reward: Reward
|
| 388 |
+
done: bool
|
| 389 |
+
info: dict[str, Any] = Field(default_factory=dict)
|
kaggle_sim_env/rewards.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Reward calculation engine for KaggleSimEnv v3.
|
| 2 |
+
|
| 3 |
+
Nine-component dense reward:
|
| 4 |
+
cv_improvement Δ CV score
|
| 5 |
+
strategy_bonus +0.05 for expected strategy (first use)
|
| 6 |
+
context_bonus +/- based on action relevance to this dataset
|
| 7 |
+
combo_bonus +0.08 per strategy combo completed
|
| 8 |
+
redundancy_penalty -0.03 × repeat count
|
| 9 |
+
irrelevant_penalty penalty for actions irrelevant to this domain
|
| 10 |
+
trap_penalty penalty for falling into a failure mode
|
| 11 |
+
overfitting_penalty penalty when CV-test gap > threshold
|
| 12 |
+
submission_bonus 0.5 × test_score on submit
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
from kaggle_sim_env.models import Reward, RewardBreakdown
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def compute_reward(
|
| 21 |
+
*,
|
| 22 |
+
prev_cv: float,
|
| 23 |
+
new_cv: float,
|
| 24 |
+
new_test: float,
|
| 25 |
+
action_tag: str,
|
| 26 |
+
expected_strategies: list[str],
|
| 27 |
+
already_applied: list[str],
|
| 28 |
+
is_submit: bool,
|
| 29 |
+
submitted_test_score: float | None = None,
|
| 30 |
+
newly_completed_combos: list[str] | None = None,
|
| 31 |
+
context_relevance: float | None = None,
|
| 32 |
+
traps_triggered_this_step: list[str] | None = None,
|
| 33 |
+
) -> Reward:
|
| 34 |
+
cv_improvement = round(new_cv - prev_cv, 6)
|
| 35 |
+
|
| 36 |
+
# --- Expected strategy bonus ---
|
| 37 |
+
strategy_bonus = 0.0
|
| 38 |
+
if action_tag in expected_strategies and action_tag not in already_applied:
|
| 39 |
+
strategy_bonus = 0.05
|
| 40 |
+
|
| 41 |
+
# --- Contextual relevance bonus/penalty ---
|
| 42 |
+
context_bonus = 0.0
|
| 43 |
+
if context_relevance is not None:
|
| 44 |
+
if context_relevance > 0:
|
| 45 |
+
context_bonus = 0.03 * context_relevance
|
| 46 |
+
elif context_relevance < 0:
|
| 47 |
+
context_bonus = 0.04 * context_relevance # harsher penalty
|
| 48 |
+
|
| 49 |
+
# --- Combo bonus ---
|
| 50 |
+
combo_bonus = 0.0
|
| 51 |
+
if newly_completed_combos:
|
| 52 |
+
combo_bonus = 0.08 * len(newly_completed_combos)
|
| 53 |
+
|
| 54 |
+
# --- Redundancy penalty ---
|
| 55 |
+
redundancy_penalty = 0.0
|
| 56 |
+
if action_tag in already_applied:
|
| 57 |
+
count = already_applied.count(action_tag)
|
| 58 |
+
redundancy_penalty = -0.03 * min(count, 3)
|
| 59 |
+
|
| 60 |
+
# --- Irrelevant action penalty ---
|
| 61 |
+
irrelevant_penalty = 0.0
|
| 62 |
+
if context_relevance is not None and context_relevance <= -0.8:
|
| 63 |
+
irrelevant_penalty = -0.05
|
| 64 |
+
|
| 65 |
+
# --- Trap penalty ---
|
| 66 |
+
trap_penalty = 0.0
|
| 67 |
+
if traps_triggered_this_step:
|
| 68 |
+
trap_penalty = -0.08 * len(traps_triggered_this_step)
|
| 69 |
+
|
| 70 |
+
# --- Overfitting penalty ---
|
| 71 |
+
gap = abs(new_cv - new_test)
|
| 72 |
+
overfitting_penalty = -gap * 0.5 if gap > 0.05 else 0.0
|
| 73 |
+
|
| 74 |
+
# --- Submission bonus ---
|
| 75 |
+
submission_bonus = 0.0
|
| 76 |
+
if is_submit and submitted_test_score is not None:
|
| 77 |
+
submission_bonus = submitted_test_score * 0.5
|
| 78 |
+
|
| 79 |
+
breakdown = RewardBreakdown(
|
| 80 |
+
cv_improvement=round(cv_improvement, 6),
|
| 81 |
+
strategy_bonus=round(strategy_bonus, 6),
|
| 82 |
+
context_bonus=round(context_bonus, 6),
|
| 83 |
+
combo_bonus=round(combo_bonus, 6),
|
| 84 |
+
redundancy_penalty=round(redundancy_penalty, 6),
|
| 85 |
+
irrelevant_penalty=round(irrelevant_penalty, 6),
|
| 86 |
+
trap_penalty=round(trap_penalty, 6),
|
| 87 |
+
overfitting_penalty=round(overfitting_penalty, 6),
|
| 88 |
+
submission_bonus=round(submission_bonus, 6),
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
total = sum([
|
| 92 |
+
cv_improvement, strategy_bonus, context_bonus, combo_bonus,
|
| 93 |
+
redundancy_penalty, irrelevant_penalty, trap_penalty,
|
| 94 |
+
overfitting_penalty, submission_bonus,
|
| 95 |
+
])
|
| 96 |
+
|
| 97 |
+
return Reward(total=round(total, 6), breakdown=breakdown)
|
kaggle_sim_env/tasks.py
ADDED
|
@@ -0,0 +1,1031 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Task definitions for KaggleSimEnv v3.
|
| 2 |
+
|
| 3 |
+
Each task now includes:
|
| 4 |
+
- DatasetProperties → causal ground truth driving reward logic
|
| 5 |
+
- failure_modes → traps the agent can fall into
|
| 6 |
+
- context_relevance → per-action relevance to THIS dataset
|
| 7 |
+
- strategy_combos → synergy bonuses
|
| 8 |
+
- score_modifiers → deterministic CV/test deltas
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
from dataclasses import dataclass, field
|
| 14 |
+
from typing import Any
|
| 15 |
+
|
| 16 |
+
from kaggle_sim_env.models import (
|
| 17 |
+
ColumnInfo,
|
| 18 |
+
DatasetMetadata,
|
| 19 |
+
DatasetProperties,
|
| 20 |
+
FailureMode,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@dataclass(frozen=True)
|
| 25 |
+
class StrategyComboDef:
|
| 26 |
+
name: str
|
| 27 |
+
required: frozenset[str]
|
| 28 |
+
cv_bonus: float
|
| 29 |
+
test_bonus: float
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
@dataclass(frozen=True)
|
| 33 |
+
class TaskDefinition:
|
| 34 |
+
task_id: str
|
| 35 |
+
title: str
|
| 36 |
+
difficulty: str
|
| 37 |
+
description: str
|
| 38 |
+
dataset_metadata: DatasetMetadata
|
| 39 |
+
dataset_properties: DatasetProperties
|
| 40 |
+
base_cv_score: float
|
| 41 |
+
base_test_score: float
|
| 42 |
+
expected_strategies: list[str]
|
| 43 |
+
ghost_scores: list[float]
|
| 44 |
+
hints: list[str]
|
| 45 |
+
score_modifiers: dict[str, dict[str, float]] = field(default_factory=dict)
|
| 46 |
+
overfitting_risk: dict[str, float] = field(default_factory=dict)
|
| 47 |
+
strategy_combos: list[StrategyComboDef] = field(default_factory=list)
|
| 48 |
+
failure_modes: list[FailureMode] = field(default_factory=list)
|
| 49 |
+
context_relevance: dict[str, float] = field(default_factory=dict)
|
| 50 |
+
max_steps: int = 20
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
# =========================================================================
|
| 54 |
+
# Contextual relevance helpers
|
| 55 |
+
# =========================================================================
|
| 56 |
+
# context_relevance maps action tags → relevance score [-1.0 .. +1.0]
|
| 57 |
+
# +1.0 = highly relevant for this dataset
|
| 58 |
+
# 0.0 = neutral
|
| 59 |
+
# -1.0 = completely irrelevant / harmful
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
# =========================================================================
|
| 63 |
+
# Task 1 – Easy: Clean tabular classification
|
| 64 |
+
# =========================================================================
|
| 65 |
+
|
| 66 |
+
def _easy_task() -> TaskDefinition:
|
| 67 |
+
columns = [
|
| 68 |
+
ColumnInfo(name="id", dtype="int64", missing_pct=0.0, unique_count=10000),
|
| 69 |
+
ColumnInfo(name="age", dtype="float64", missing_pct=0.0, unique_count=80),
|
| 70 |
+
ColumnInfo(name="income", dtype="float64", missing_pct=0.0, unique_count=5000),
|
| 71 |
+
ColumnInfo(name="credit_score", dtype="float64", missing_pct=0.0, unique_count=300),
|
| 72 |
+
ColumnInfo(name="num_products", dtype="int64", missing_pct=0.0, unique_count=4),
|
| 73 |
+
ColumnInfo(name="tenure", dtype="int64", missing_pct=0.0, unique_count=12),
|
| 74 |
+
ColumnInfo(name="balance", dtype="float64", missing_pct=0.0, unique_count=6000),
|
| 75 |
+
ColumnInfo(name="has_credit_card", dtype="int64", missing_pct=0.0, unique_count=2),
|
| 76 |
+
ColumnInfo(name="is_active_member", dtype="int64", missing_pct=0.0, unique_count=2),
|
| 77 |
+
ColumnInfo(name="target", dtype="int64", missing_pct=0.0, unique_count=2),
|
| 78 |
+
]
|
| 79 |
+
|
| 80 |
+
props = DatasetProperties(
|
| 81 |
+
has_imbalance=True, imbalance_ratio=0.20,
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
failure_modes = [
|
| 85 |
+
FailureMode(
|
| 86 |
+
name="unnecessary_shift_detection",
|
| 87 |
+
trigger_tag="detect_shift:adversarial_validation",
|
| 88 |
+
condition_field="has_shift", condition_value=False,
|
| 89 |
+
cv_effect=0.0, test_effect=0.0,
|
| 90 |
+
message="No distribution shift exists — adversarial validation wasted a step.",
|
| 91 |
+
),
|
| 92 |
+
FailureMode(
|
| 93 |
+
name="image_aug_on_tabular",
|
| 94 |
+
trigger_tag="augmentation:geometric",
|
| 95 |
+
condition_field="has_images", condition_value=False,
|
| 96 |
+
cv_effect=0.0, test_effect=-0.01,
|
| 97 |
+
message="Geometric augmentation is meaningless on tabular data.",
|
| 98 |
+
),
|
| 99 |
+
FailureMode(
|
| 100 |
+
name="target_encoding_without_group_cv",
|
| 101 |
+
trigger_tag="feature_engineering:target_encoding",
|
| 102 |
+
condition_field="has_group_column", condition_value=False,
|
| 103 |
+
cv_effect=0.06, test_effect=-0.04,
|
| 104 |
+
message="Target encoding without group-aware CV causes leakage — CV inflated but test hurt.",
|
| 105 |
+
),
|
| 106 |
+
]
|
| 107 |
+
|
| 108 |
+
context_relevance = {
|
| 109 |
+
# Highly relevant
|
| 110 |
+
"set_cv:kfold": 1.0,
|
| 111 |
+
"set_cv:repeated_kfold": 0.8,
|
| 112 |
+
"feature_engineering:normalize": 0.8,
|
| 113 |
+
"feature_engineering:domain_ratios": 1.0,
|
| 114 |
+
"train_model:xgboost": 1.0,
|
| 115 |
+
"train_model:lightgbm": 0.9,
|
| 116 |
+
"handle_imbalance:scale_pos_weight": 1.0,
|
| 117 |
+
"handle_imbalance:optimize_threshold": 0.8,
|
| 118 |
+
"ensemble:weighted_average": 0.7,
|
| 119 |
+
# Neutral
|
| 120 |
+
"feature_engineering:log_transform": 0.3,
|
| 121 |
+
"regularize:strong_regularization": 0.2,
|
| 122 |
+
# Irrelevant
|
| 123 |
+
"detect_shift:adversarial_validation": -0.5,
|
| 124 |
+
"augmentation:geometric": -1.0,
|
| 125 |
+
"augmentation:color_transform": -1.0,
|
| 126 |
+
"augmentation:camera_simulation": -1.0,
|
| 127 |
+
"feature_engineering:sin_cos_encoding": -0.8,
|
| 128 |
+
"feature_engineering:relative_coordinates": -1.0,
|
| 129 |
+
"feature_engineering:frequency_features": -0.8,
|
| 130 |
+
"tune_loss:auxiliary_physics_loss": -1.0,
|
| 131 |
+
"postprocess:physics_constraints": -1.0,
|
| 132 |
+
"train_model:pretrained_backbone": -0.5,
|
| 133 |
+
"train_model:temporal_cnn": -0.8,
|
| 134 |
+
"train_model:transformer_encoder": -0.5,
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
return TaskDefinition(
|
| 138 |
+
task_id="easy_churn",
|
| 139 |
+
title="Customer Churn Prediction",
|
| 140 |
+
difficulty="easy",
|
| 141 |
+
description=(
|
| 142 |
+
"Predict customer churn from clean tabular banking data. "
|
| 143 |
+
"Mild class imbalance (80/20). No missing values, no shift."
|
| 144 |
+
),
|
| 145 |
+
dataset_metadata=DatasetMetadata(
|
| 146 |
+
num_rows=10000, num_features=9, columns=columns,
|
| 147 |
+
target_column="target", task_type="classification",
|
| 148 |
+
class_balance={"0": 0.80, "1": 0.20},
|
| 149 |
+
),
|
| 150 |
+
dataset_properties=props,
|
| 151 |
+
base_cv_score=0.50,
|
| 152 |
+
base_test_score=0.50,
|
| 153 |
+
expected_strategies=[
|
| 154 |
+
"set_cv:kfold",
|
| 155 |
+
"feature_engineering:normalize",
|
| 156 |
+
"feature_engineering:domain_ratios",
|
| 157 |
+
"train_model:xgboost",
|
| 158 |
+
"handle_imbalance:scale_pos_weight",
|
| 159 |
+
],
|
| 160 |
+
ghost_scores=[0.90, 0.88, 0.86, 0.84, 0.82, 0.79, 0.75, 0.72, 0.68],
|
| 161 |
+
hints=[
|
| 162 |
+
"This is a clean tabular dataset — standard k-fold CV works well.",
|
| 163 |
+
"Create domain ratios like balance/income, products/tenure.",
|
| 164 |
+
"XGBoost dominates clean tabular data.",
|
| 165 |
+
"Tune scale_pos_weight for the 80/20 class split.",
|
| 166 |
+
"No distribution shift exists — don't waste steps detecting it.",
|
| 167 |
+
],
|
| 168 |
+
score_modifiers={
|
| 169 |
+
"set_cv:kfold": {"cv": 0.05, "test": 0.05},
|
| 170 |
+
"set_cv:repeated_kfold": {"cv": 0.06, "test": 0.06},
|
| 171 |
+
"set_cv:group_kfold": {"cv": 0.03, "test": 0.03},
|
| 172 |
+
"set_cv:time_split": {"cv": 0.02, "test": 0.01},
|
| 173 |
+
"feature_engineering:normalize": {"cv": 0.04, "test": 0.04},
|
| 174 |
+
"feature_engineering:log_transform": {"cv": 0.02, "test": 0.02},
|
| 175 |
+
"feature_engineering:interaction_terms": {"cv": 0.03, "test": 0.02},
|
| 176 |
+
"feature_engineering:domain_ratios": {"cv": 0.05, "test": 0.05},
|
| 177 |
+
"feature_engineering:target_encoding": {"cv": 0.06, "test": -0.04},
|
| 178 |
+
"feature_engineering:quantile_features": {"cv": 0.02, "test": 0.02},
|
| 179 |
+
"train_model:xgboost": {"cv": 0.20, "test": 0.19},
|
| 180 |
+
"train_model:lightgbm": {"cv": 0.19, "test": 0.18},
|
| 181 |
+
"train_model:catboost": {"cv": 0.18, "test": 0.18},
|
| 182 |
+
"train_model:random_forest": {"cv": 0.16, "test": 0.15},
|
| 183 |
+
"train_model:linear": {"cv": 0.10, "test": 0.10},
|
| 184 |
+
"train_model:neural_network": {"cv": 0.08, "test": 0.06},
|
| 185 |
+
"handle_imbalance:scale_pos_weight": {"cv": 0.04, "test": 0.04},
|
| 186 |
+
"handle_imbalance:calibrate_probabilities": {"cv": 0.02, "test": 0.03},
|
| 187 |
+
"handle_imbalance:optimize_threshold": {"cv": 0.03, "test": 0.03},
|
| 188 |
+
"ensemble:weighted_average": {"cv": 0.04, "test": 0.03},
|
| 189 |
+
"ensemble:stacking": {"cv": 0.05, "test": 0.03},
|
| 190 |
+
"ensemble:multi_seed_averaging": {"cv": 0.03, "test": 0.03},
|
| 191 |
+
"regularize:strong_regularization": {"cv": 0.01, "test": 0.02},
|
| 192 |
+
"pseudo_label:1": {"cv": 0.01, "test": 0.00},
|
| 193 |
+
"postprocess:bias_correction": {"cv": 0.01, "test": 0.01},
|
| 194 |
+
"inspect_top_solution": {"cv": 0.0, "test": 0.0},
|
| 195 |
+
},
|
| 196 |
+
overfitting_risk={
|
| 197 |
+
"feature_engineering:target_encoding": 0.06,
|
| 198 |
+
"feature_engineering:interaction_terms": 0.01,
|
| 199 |
+
"ensemble:stacking": 0.02,
|
| 200 |
+
},
|
| 201 |
+
strategy_combos=[
|
| 202 |
+
StrategyComboDef(
|
| 203 |
+
name="clean_tabular_pipeline",
|
| 204 |
+
required=frozenset({"set_cv:kfold", "train_model:xgboost", "feature_engineering:normalize"}),
|
| 205 |
+
cv_bonus=0.02, test_bonus=0.02,
|
| 206 |
+
),
|
| 207 |
+
StrategyComboDef(
|
| 208 |
+
name="imbalance_aware",
|
| 209 |
+
required=frozenset({"handle_imbalance:scale_pos_weight", "handle_imbalance:optimize_threshold"}),
|
| 210 |
+
cv_bonus=0.02, test_bonus=0.03,
|
| 211 |
+
),
|
| 212 |
+
],
|
| 213 |
+
failure_modes=failure_modes,
|
| 214 |
+
context_relevance=context_relevance,
|
| 215 |
+
max_steps=20,
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
# =========================================================================
|
| 220 |
+
# Task 2 – Medium: Temporal fraud detection + distribution shift
|
| 221 |
+
# =========================================================================
|
| 222 |
+
|
| 223 |
+
def _medium_task() -> TaskDefinition:
|
| 224 |
+
columns = [
|
| 225 |
+
ColumnInfo(name="id", dtype="int64", missing_pct=0.0, unique_count=50000),
|
| 226 |
+
ColumnInfo(name="timestamp", dtype="datetime64", missing_pct=0.0, unique_count=365),
|
| 227 |
+
ColumnInfo(name="merchant_id", dtype="int64", missing_pct=0.0, unique_count=500),
|
| 228 |
+
ColumnInfo(name="amount", dtype="float64", missing_pct=1.2, unique_count=15000),
|
| 229 |
+
ColumnInfo(name="category", dtype="object", missing_pct=0.0, unique_count=15),
|
| 230 |
+
ColumnInfo(name="customer_age", dtype="float64", missing_pct=3.5, unique_count=70),
|
| 231 |
+
ColumnInfo(name="customer_region", dtype="object", missing_pct=0.5, unique_count=8),
|
| 232 |
+
ColumnInfo(name="prev_transactions", dtype="int64", missing_pct=0.0, unique_count=200),
|
| 233 |
+
ColumnInfo(name="is_weekend", dtype="int64", missing_pct=0.0, unique_count=2),
|
| 234 |
+
ColumnInfo(name="device_type", dtype="object", missing_pct=2.0, unique_count=4),
|
| 235 |
+
ColumnInfo(name="is_fraud", dtype="int64", missing_pct=0.0, unique_count=2),
|
| 236 |
+
]
|
| 237 |
+
|
| 238 |
+
props = DatasetProperties(
|
| 239 |
+
has_shift=True,
|
| 240 |
+
has_imbalance=True, imbalance_ratio=0.05,
|
| 241 |
+
has_time_column=True, has_group_column=True,
|
| 242 |
+
is_safety_critical=True,
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
+
failure_modes = [
|
| 246 |
+
FailureMode(
|
| 247 |
+
name="kfold_on_temporal_data",
|
| 248 |
+
trigger_tag="set_cv:kfold",
|
| 249 |
+
condition_field="has_shift", condition_value=True,
|
| 250 |
+
cv_effect=0.08, test_effect=-0.04,
|
| 251 |
+
message="Random k-fold on shifted temporal data — CV is optimistic, test will be much worse.",
|
| 252 |
+
),
|
| 253 |
+
FailureMode(
|
| 254 |
+
name="ignoring_shift",
|
| 255 |
+
trigger_tag="train_model:xgboost",
|
| 256 |
+
condition_field="has_shift", condition_value=True,
|
| 257 |
+
cv_effect=0.0, test_effect=-0.06,
|
| 258 |
+
message="Training without addressing distribution shift — model memorises train distribution.",
|
| 259 |
+
),
|
| 260 |
+
FailureMode(
|
| 261 |
+
name="target_encoding_leakage",
|
| 262 |
+
trigger_tag="feature_engineering:target_encoding",
|
| 263 |
+
condition_field="has_shift", condition_value=True,
|
| 264 |
+
cv_effect=0.05, test_effect=-0.06,
|
| 265 |
+
message="Target encoding on shifted data leaks train-specific patterns.",
|
| 266 |
+
),
|
| 267 |
+
]
|
| 268 |
+
|
| 269 |
+
context_relevance = {
|
| 270 |
+
"detect_shift:adversarial_validation": 1.0,
|
| 271 |
+
"detect_shift:remove_identifiers": 1.0,
|
| 272 |
+
"detect_shift:domain_invariant_features": 0.9,
|
| 273 |
+
"set_cv:time_split": 1.0,
|
| 274 |
+
"set_cv:combined_group_time": 0.9,
|
| 275 |
+
"set_cv:kfold": -0.8,
|
| 276 |
+
"feature_engineering:log_transform": 0.8,
|
| 277 |
+
"feature_engineering:domain_ratios": 0.9,
|
| 278 |
+
"train_model:xgboost": 0.9,
|
| 279 |
+
"handle_imbalance:scale_pos_weight": 1.0,
|
| 280 |
+
"handle_imbalance:optimize_threshold": 0.9,
|
| 281 |
+
"tune_loss:asymmetric_loss": 1.0,
|
| 282 |
+
"regularize:strong_regularization": 0.8,
|
| 283 |
+
"ensemble:weighted_average": 0.7,
|
| 284 |
+
# Irrelevant
|
| 285 |
+
"augmentation:geometric": -1.0,
|
| 286 |
+
"augmentation:color_transform": -1.0,
|
| 287 |
+
"feature_engineering:sin_cos_encoding": -0.5,
|
| 288 |
+
"feature_engineering:relative_coordinates": -1.0,
|
| 289 |
+
"tune_loss:auxiliary_physics_loss": -1.0,
|
| 290 |
+
"postprocess:physics_constraints": -1.0,
|
| 291 |
+
"train_model:pretrained_backbone": -0.5,
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
return TaskDefinition(
|
| 295 |
+
task_id="medium_fraud",
|
| 296 |
+
title="Fraud Detection with Distribution Shift",
|
| 297 |
+
difficulty="medium",
|
| 298 |
+
description=(
|
| 299 |
+
"Detect fraudulent transactions. Test set comes from a later time "
|
| 300 |
+
"period with distribution shift. Heavy imbalance (95/5). Safety-critical."
|
| 301 |
+
),
|
| 302 |
+
dataset_metadata=DatasetMetadata(
|
| 303 |
+
num_rows=50000, num_features=10, columns=columns,
|
| 304 |
+
target_column="is_fraud", task_type="classification",
|
| 305 |
+
has_time_column=True, has_group_column=True,
|
| 306 |
+
class_balance={"0": 0.95, "1": 0.05},
|
| 307 |
+
),
|
| 308 |
+
dataset_properties=props,
|
| 309 |
+
base_cv_score=0.42,
|
| 310 |
+
base_test_score=0.38,
|
| 311 |
+
expected_strategies=[
|
| 312 |
+
"detect_shift:adversarial_validation",
|
| 313 |
+
"detect_shift:remove_identifiers",
|
| 314 |
+
"set_cv:time_split",
|
| 315 |
+
"feature_engineering:log_transform",
|
| 316 |
+
"feature_engineering:domain_ratios",
|
| 317 |
+
"train_model:xgboost",
|
| 318 |
+
"handle_imbalance:scale_pos_weight",
|
| 319 |
+
"handle_imbalance:optimize_threshold",
|
| 320 |
+
"tune_loss:asymmetric_loss",
|
| 321 |
+
"ensemble:weighted_average",
|
| 322 |
+
"regularize:strong_regularization",
|
| 323 |
+
],
|
| 324 |
+
ghost_scores=[0.92, 0.90, 0.87, 0.84, 0.80, 0.76, 0.72, 0.67, 0.60],
|
| 325 |
+
hints=[
|
| 326 |
+
"Adversarial validation AUC > 0.82 — significant distribution shift exists!",
|
| 327 |
+
"Use time-based CV — random k-fold will give you a false sense of accuracy.",
|
| 328 |
+
"Log-transform 'amount' — it has extreme skew.",
|
| 329 |
+
"Remove merchant_id and device_type — they are identifiers causing shift.",
|
| 330 |
+
"This is safety-critical: asymmetric loss prioritises fraud recall.",
|
| 331 |
+
"Tune scale_pos_weight on OOF outputs, then optimize threshold.",
|
| 332 |
+
"Strong regularisation prevents memorising train-specific patterns.",
|
| 333 |
+
],
|
| 334 |
+
score_modifiers={
|
| 335 |
+
"set_cv:kfold": {"cv": 0.08, "test": -0.04},
|
| 336 |
+
"set_cv:group_kfold": {"cv": 0.06, "test": 0.05},
|
| 337 |
+
"set_cv:time_split": {"cv": 0.05, "test": 0.10},
|
| 338 |
+
"set_cv:combined_group_time": {"cv": 0.05, "test": 0.09},
|
| 339 |
+
"detect_shift:adversarial_validation": {"cv": 0.02, "test": 0.08},
|
| 340 |
+
"detect_shift:feature_importance_shift": {"cv": 0.01, "test": 0.04},
|
| 341 |
+
"detect_shift:remove_identifiers": {"cv": -0.01, "test": 0.06},
|
| 342 |
+
"detect_shift:domain_invariant_features": {"cv": 0.02, "test": 0.05},
|
| 343 |
+
"feature_engineering:log_transform": {"cv": 0.05, "test": 0.06},
|
| 344 |
+
"feature_engineering:normalize": {"cv": 0.02, "test": 0.02},
|
| 345 |
+
"feature_engineering:domain_ratios": {"cv": 0.04, "test": 0.05},
|
| 346 |
+
"feature_engineering:interaction_terms": {"cv": 0.04, "test": 0.01},
|
| 347 |
+
"feature_engineering:target_encoding": {"cv": 0.05, "test": -0.06},
|
| 348 |
+
"train_model:xgboost": {"cv": 0.18, "test": 0.16},
|
| 349 |
+
"train_model:lightgbm": {"cv": 0.17, "test": 0.16},
|
| 350 |
+
"train_model:catboost": {"cv": 0.16, "test": 0.15},
|
| 351 |
+
"train_model:random_forest": {"cv": 0.14, "test": 0.12},
|
| 352 |
+
"train_model:linear": {"cv": 0.08, "test": 0.07},
|
| 353 |
+
"handle_imbalance:scale_pos_weight": {"cv": 0.04, "test": 0.05},
|
| 354 |
+
"handle_imbalance:calibrate_probabilities": {"cv": 0.02, "test": 0.03},
|
| 355 |
+
"handle_imbalance:optimize_threshold": {"cv": 0.03, "test": 0.04},
|
| 356 |
+
"tune_loss:asymmetric_loss": {"cv": 0.03, "test": 0.05},
|
| 357 |
+
"ensemble:weighted_average": {"cv": 0.05, "test": 0.05},
|
| 358 |
+
"ensemble:stacking": {"cv": 0.06, "test": 0.04},
|
| 359 |
+
"ensemble:multi_seed_averaging": {"cv": 0.03, "test": 0.04},
|
| 360 |
+
"ensemble:heterogeneous": {"cv": 0.04, "test": 0.05},
|
| 361 |
+
"regularize:strong_regularization": {"cv": -0.01, "test": 0.04},
|
| 362 |
+
"regularize:ema": {"cv": 0.01, "test": 0.02},
|
| 363 |
+
"pseudo_label:1": {"cv": 0.02, "test": 0.01},
|
| 364 |
+
"pseudo_label:2": {"cv": 0.03, "test": -0.02},
|
| 365 |
+
"postprocess:bias_correction": {"cv": 0.01, "test": 0.02},
|
| 366 |
+
"clean_data:remove_outliers": {"cv": 0.01, "test": 0.02},
|
| 367 |
+
"clean_data:remove_leaky_features": {"cv": -0.02, "test": 0.04},
|
| 368 |
+
"inspect_top_solution": {"cv": 0.0, "test": 0.0},
|
| 369 |
+
},
|
| 370 |
+
overfitting_risk={
|
| 371 |
+
"set_cv:kfold": 0.08,
|
| 372 |
+
"pseudo_label:2": 0.05,
|
| 373 |
+
"feature_engineering:target_encoding": 0.05,
|
| 374 |
+
"feature_engineering:interaction_terms": 0.03,
|
| 375 |
+
},
|
| 376 |
+
strategy_combos=[
|
| 377 |
+
StrategyComboDef(
|
| 378 |
+
name="shift_aware_pipeline",
|
| 379 |
+
required=frozenset({
|
| 380 |
+
"detect_shift:adversarial_validation",
|
| 381 |
+
"set_cv:time_split",
|
| 382 |
+
"detect_shift:remove_identifiers",
|
| 383 |
+
}),
|
| 384 |
+
cv_bonus=0.03, test_bonus=0.06,
|
| 385 |
+
),
|
| 386 |
+
StrategyComboDef(
|
| 387 |
+
name="safety_critical_fraud",
|
| 388 |
+
required=frozenset({
|
| 389 |
+
"handle_imbalance:scale_pos_weight",
|
| 390 |
+
"tune_loss:asymmetric_loss",
|
| 391 |
+
"handle_imbalance:optimize_threshold",
|
| 392 |
+
}),
|
| 393 |
+
cv_bonus=0.02, test_bonus=0.04,
|
| 394 |
+
),
|
| 395 |
+
StrategyComboDef(
|
| 396 |
+
name="robust_generalisation",
|
| 397 |
+
required=frozenset({
|
| 398 |
+
"regularize:strong_regularization",
|
| 399 |
+
"detect_shift:domain_invariant_features",
|
| 400 |
+
}),
|
| 401 |
+
cv_bonus=0.01, test_bonus=0.03,
|
| 402 |
+
),
|
| 403 |
+
],
|
| 404 |
+
failure_modes=failure_modes,
|
| 405 |
+
context_relevance=context_relevance,
|
| 406 |
+
max_steps=25,
|
| 407 |
+
)
|
| 408 |
+
|
| 409 |
+
|
| 410 |
+
# =========================================================================
|
| 411 |
+
# Task 3 – Hard: Leakage + noise + missing data + shift
|
| 412 |
+
# =========================================================================
|
| 413 |
+
|
| 414 |
+
def _hard_task() -> TaskDefinition:
|
| 415 |
+
columns = [
|
| 416 |
+
ColumnInfo(name="id", dtype="int64", missing_pct=0.0, unique_count=100000),
|
| 417 |
+
ColumnInfo(name="timestamp", dtype="datetime64", missing_pct=0.0, unique_count=730),
|
| 418 |
+
ColumnInfo(name="group_id", dtype="int64", missing_pct=0.0, unique_count=1000),
|
| 419 |
+
ColumnInfo(name="feature_0", dtype="float64", missing_pct=8.5, unique_count=50000),
|
| 420 |
+
ColumnInfo(name="feature_1", dtype="float64", missing_pct=12.0, unique_count=40000),
|
| 421 |
+
ColumnInfo(name="feature_2", dtype="float64", missing_pct=0.0, unique_count=60000),
|
| 422 |
+
ColumnInfo(name="feature_3", dtype="float64", missing_pct=5.0, unique_count=30000),
|
| 423 |
+
ColumnInfo(name="feature_4", dtype="object", missing_pct=15.0, unique_count=20),
|
| 424 |
+
ColumnInfo(name="feature_5_leaky", dtype="float64", missing_pct=0.0, unique_count=80000),
|
| 425 |
+
ColumnInfo(name="noise_0", dtype="float64", missing_pct=0.0, unique_count=100000),
|
| 426 |
+
ColumnInfo(name="noise_1", dtype="float64", missing_pct=0.0, unique_count=100000),
|
| 427 |
+
ColumnInfo(name="noise_2", dtype="float64", missing_pct=0.0, unique_count=100000),
|
| 428 |
+
ColumnInfo(name="target", dtype="int64", missing_pct=0.0, unique_count=2),
|
| 429 |
+
]
|
| 430 |
+
|
| 431 |
+
props = DatasetProperties(
|
| 432 |
+
has_shift=True, has_leakage=True, has_noise_features=True,
|
| 433 |
+
has_missing_data=True, has_imbalance=True, imbalance_ratio=0.08,
|
| 434 |
+
has_time_column=True, has_group_column=True,
|
| 435 |
+
)
|
| 436 |
+
|
| 437 |
+
failure_modes = [
|
| 438 |
+
FailureMode(
|
| 439 |
+
name="kfold_on_grouped_data",
|
| 440 |
+
trigger_tag="set_cv:kfold",
|
| 441 |
+
condition_field="has_group_column", condition_value=True,
|
| 442 |
+
cv_effect=0.10, test_effect=-0.05,
|
| 443 |
+
message="Random k-fold on grouped data leaks group structure — CV hugely inflated.",
|
| 444 |
+
),
|
| 445 |
+
FailureMode(
|
| 446 |
+
name="keeping_leaky_feature",
|
| 447 |
+
trigger_tag="train_model:xgboost",
|
| 448 |
+
condition_field="has_leakage", condition_value=True,
|
| 449 |
+
cv_effect=0.0, test_effect=-0.08,
|
| 450 |
+
message="Training on leaky features — model depends on data that won't exist at test time.",
|
| 451 |
+
),
|
| 452 |
+
FailureMode(
|
| 453 |
+
name="interaction_terms_on_noisy_data",
|
| 454 |
+
trigger_tag="feature_engineering:interaction_terms",
|
| 455 |
+
condition_field="has_noise_features", condition_value=True,
|
| 456 |
+
cv_effect=0.05, test_effect=-0.04,
|
| 457 |
+
message="Interaction terms on noisy features amplify noise — CV up, test down.",
|
| 458 |
+
),
|
| 459 |
+
FailureMode(
|
| 460 |
+
name="pseudo_label_overfit",
|
| 461 |
+
trigger_tag="pseudo_label:2",
|
| 462 |
+
condition_field="has_shift", condition_value=True,
|
| 463 |
+
cv_effect=0.05, test_effect=-0.06,
|
| 464 |
+
message="Iterative pseudo-labeling on shifted data compounds distribution errors.",
|
| 465 |
+
),
|
| 466 |
+
]
|
| 467 |
+
|
| 468 |
+
context_relevance = {
|
| 469 |
+
"detect_shift:adversarial_validation": 1.0,
|
| 470 |
+
"detect_shift:remove_identifiers": 0.9,
|
| 471 |
+
"set_cv:stratified_group_kfold": 1.0,
|
| 472 |
+
"set_cv:kfold": -0.9,
|
| 473 |
+
"clean_data:remove_leaky_features": 1.0,
|
| 474 |
+
"clean_data:remove_outliers": 0.8,
|
| 475 |
+
"clean_data:analytical_reconstruction": 1.0,
|
| 476 |
+
"clean_data:nan_native_model": 0.8,
|
| 477 |
+
"feature_engineering:log_transform": 0.7,
|
| 478 |
+
"feature_engineering:domain_ratios": 0.8,
|
| 479 |
+
"feature_engineering:interaction_terms": -0.7,
|
| 480 |
+
"feature_engineering:target_encoding": -0.9,
|
| 481 |
+
"train_model:xgboost": 0.9,
|
| 482 |
+
"train_model:lightgbm": 0.9,
|
| 483 |
+
"handle_imbalance:scale_pos_weight": 1.0,
|
| 484 |
+
"handle_imbalance:calibrate_probabilities": 0.9,
|
| 485 |
+
"ensemble:stacking": 0.8,
|
| 486 |
+
"ensemble:diverse_features": 0.9,
|
| 487 |
+
"regularize:strong_regularization": 0.9,
|
| 488 |
+
"pseudo_label:1": 0.5,
|
| 489 |
+
"pseudo_label:2": -0.8,
|
| 490 |
+
# Irrelevant
|
| 491 |
+
"augmentation:geometric": -1.0,
|
| 492 |
+
"augmentation:camera_simulation": -1.0,
|
| 493 |
+
"tune_loss:auxiliary_physics_loss": -1.0,
|
| 494 |
+
"postprocess:physics_constraints": -1.0,
|
| 495 |
+
"train_model:pretrained_backbone": -0.5,
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
return TaskDefinition(
|
| 499 |
+
task_id="hard_leaky_noisy",
|
| 500 |
+
title="Leaky Noisy Imbalanced Classification",
|
| 501 |
+
difficulty="hard",
|
| 502 |
+
description=(
|
| 503 |
+
"High-dimensional classification with data leakage (feature_5_leaky), "
|
| 504 |
+
"pure-noise columns (noise_0-2), structured missing data, class imbalance "
|
| 505 |
+
"(92/8), and temporal distribution shift."
|
| 506 |
+
),
|
| 507 |
+
dataset_metadata=DatasetMetadata(
|
| 508 |
+
num_rows=100000, num_features=12, columns=columns,
|
| 509 |
+
target_column="target", task_type="classification",
|
| 510 |
+
has_time_column=True, has_group_column=True,
|
| 511 |
+
class_balance={"0": 0.92, "1": 0.08},
|
| 512 |
+
),
|
| 513 |
+
dataset_properties=props,
|
| 514 |
+
base_cv_score=0.38,
|
| 515 |
+
base_test_score=0.32,
|
| 516 |
+
expected_strategies=[
|
| 517 |
+
"detect_shift:adversarial_validation",
|
| 518 |
+
"detect_shift:remove_identifiers",
|
| 519 |
+
"set_cv:stratified_group_kfold",
|
| 520 |
+
"clean_data:remove_leaky_features",
|
| 521 |
+
"clean_data:remove_outliers",
|
| 522 |
+
"clean_data:analytical_reconstruction",
|
| 523 |
+
"feature_engineering:log_transform",
|
| 524 |
+
"feature_engineering:domain_ratios",
|
| 525 |
+
"train_model:xgboost",
|
| 526 |
+
"train_model:lightgbm",
|
| 527 |
+
"handle_imbalance:scale_pos_weight",
|
| 528 |
+
"handle_imbalance:calibrate_probabilities",
|
| 529 |
+
"ensemble:stacking",
|
| 530 |
+
"ensemble:diverse_features",
|
| 531 |
+
"pseudo_label:1",
|
| 532 |
+
"regularize:strong_regularization",
|
| 533 |
+
],
|
| 534 |
+
ghost_scores=[0.91, 0.88, 0.85, 0.82, 0.78, 0.74, 0.69, 0.63, 0.55],
|
| 535 |
+
hints=[
|
| 536 |
+
"'feature_5_leaky' is suspiciously predictive — it encodes target leakage. Remove it!",
|
| 537 |
+
"noise_0/1/2 have zero signal; removing them reduces overfitting dramatically.",
|
| 538 |
+
"Use stratified group k-fold — random k-fold will leak group structure.",
|
| 539 |
+
"Adversarial validation AUC 0.85 — heavy distribution shift.",
|
| 540 |
+
"Many rows with structured missing data can be analytically reconstructed.",
|
| 541 |
+
"Calibrate probabilities with isotonic regression on OOF predictions.",
|
| 542 |
+
"Stack XGBoost + LightGBM with diverse feature subsets.",
|
| 543 |
+
"One iteration of pseudo-labeling helps; two iterations overfit badly on shifted data.",
|
| 544 |
+
],
|
| 545 |
+
score_modifiers={
|
| 546 |
+
"set_cv:kfold": {"cv": 0.10, "test": -0.05},
|
| 547 |
+
"set_cv:group_kfold": {"cv": 0.06, "test": 0.08},
|
| 548 |
+
"set_cv:stratified_group_kfold": {"cv": 0.05, "test": 0.10},
|
| 549 |
+
"set_cv:time_split": {"cv": 0.05, "test": 0.07},
|
| 550 |
+
"detect_shift:adversarial_validation": {"cv": 0.02, "test": 0.08},
|
| 551 |
+
"detect_shift:remove_identifiers": {"cv": -0.02, "test": 0.07},
|
| 552 |
+
"clean_data:remove_leaky_features": {"cv": -0.05, "test": 0.08},
|
| 553 |
+
"clean_data:remove_outliers": {"cv": 0.02, "test": 0.03},
|
| 554 |
+
"clean_data:analytical_reconstruction": {"cv": 0.03, "test": 0.04},
|
| 555 |
+
"clean_data:nan_native_model": {"cv": 0.02, "test": 0.03},
|
| 556 |
+
"feature_engineering:log_transform": {"cv": 0.04, "test": 0.05},
|
| 557 |
+
"feature_engineering:domain_ratios": {"cv": 0.04, "test": 0.05},
|
| 558 |
+
"feature_engineering:interaction_terms": {"cv": 0.05, "test": -0.04},
|
| 559 |
+
"feature_engineering:target_encoding": {"cv": 0.06, "test": -0.05},
|
| 560 |
+
"feature_engineering:normalize": {"cv": 0.03, "test": 0.03},
|
| 561 |
+
"train_model:xgboost": {"cv": 0.16, "test": 0.14},
|
| 562 |
+
"train_model:lightgbm": {"cv": 0.15, "test": 0.14},
|
| 563 |
+
"train_model:catboost": {"cv": 0.14, "test": 0.13},
|
| 564 |
+
"train_model:random_forest": {"cv": 0.12, "test": 0.10},
|
| 565 |
+
"train_model:linear": {"cv": 0.06, "test": 0.06},
|
| 566 |
+
"handle_imbalance:scale_pos_weight": {"cv": 0.04, "test": 0.05},
|
| 567 |
+
"handle_imbalance:calibrate_probabilities": {"cv": 0.02, "test": 0.04},
|
| 568 |
+
"handle_imbalance:optimize_threshold": {"cv": 0.03, "test": 0.03},
|
| 569 |
+
"tune_loss:asymmetric_loss": {"cv": 0.02, "test": 0.04},
|
| 570 |
+
"ensemble:stacking": {"cv": 0.06, "test": 0.05},
|
| 571 |
+
"ensemble:diverse_features": {"cv": 0.04, "test": 0.05},
|
| 572 |
+
"ensemble:weighted_average": {"cv": 0.04, "test": 0.04},
|
| 573 |
+
"regularize:strong_regularization": {"cv": -0.02, "test": 0.05},
|
| 574 |
+
"regularize:ema": {"cv": 0.01, "test": 0.02},
|
| 575 |
+
"pseudo_label:1": {"cv": 0.03, "test": 0.03},
|
| 576 |
+
"pseudo_label:2": {"cv": 0.05, "test": -0.06},
|
| 577 |
+
"postprocess:bias_correction": {"cv": 0.01, "test": 0.02},
|
| 578 |
+
"inspect_top_solution": {"cv": 0.0, "test": 0.0},
|
| 579 |
+
},
|
| 580 |
+
overfitting_risk={
|
| 581 |
+
"set_cv:kfold": 0.10,
|
| 582 |
+
"pseudo_label:2": 0.06,
|
| 583 |
+
"feature_engineering:interaction_terms": 0.05,
|
| 584 |
+
"feature_engineering:target_encoding": 0.06,
|
| 585 |
+
},
|
| 586 |
+
strategy_combos=[
|
| 587 |
+
StrategyComboDef(
|
| 588 |
+
name="data_hygiene",
|
| 589 |
+
required=frozenset({
|
| 590 |
+
"clean_data:remove_leaky_features",
|
| 591 |
+
"clean_data:remove_outliers",
|
| 592 |
+
"detect_shift:adversarial_validation",
|
| 593 |
+
}),
|
| 594 |
+
cv_bonus=0.02, test_bonus=0.06,
|
| 595 |
+
),
|
| 596 |
+
StrategyComboDef(
|
| 597 |
+
name="missing_data_mastery",
|
| 598 |
+
required=frozenset({
|
| 599 |
+
"clean_data:analytical_reconstruction",
|
| 600 |
+
"clean_data:nan_native_model",
|
| 601 |
+
}),
|
| 602 |
+
cv_bonus=0.02, test_bonus=0.03,
|
| 603 |
+
),
|
| 604 |
+
StrategyComboDef(
|
| 605 |
+
name="diverse_ensemble",
|
| 606 |
+
required=frozenset({
|
| 607 |
+
"train_model:xgboost", "train_model:lightgbm",
|
| 608 |
+
"ensemble:diverse_features", "ensemble:stacking",
|
| 609 |
+
}),
|
| 610 |
+
cv_bonus=0.03, test_bonus=0.05,
|
| 611 |
+
),
|
| 612 |
+
StrategyComboDef(
|
| 613 |
+
name="calibrated_imbalance",
|
| 614 |
+
required=frozenset({
|
| 615 |
+
"handle_imbalance:scale_pos_weight",
|
| 616 |
+
"handle_imbalance:calibrate_probabilities",
|
| 617 |
+
"handle_imbalance:optimize_threshold",
|
| 618 |
+
}),
|
| 619 |
+
cv_bonus=0.02, test_bonus=0.04,
|
| 620 |
+
),
|
| 621 |
+
],
|
| 622 |
+
failure_modes=failure_modes,
|
| 623 |
+
context_relevance=context_relevance,
|
| 624 |
+
max_steps=30,
|
| 625 |
+
)
|
| 626 |
+
|
| 627 |
+
|
| 628 |
+
# =========================================================================
|
| 629 |
+
# Task 4 – Image: Quality regression
|
| 630 |
+
# =========================================================================
|
| 631 |
+
|
| 632 |
+
def _image_task() -> TaskDefinition:
|
| 633 |
+
columns = [
|
| 634 |
+
ColumnInfo(name="image_id", dtype="int64", missing_pct=0.0, unique_count=25000),
|
| 635 |
+
ColumnInfo(name="image_path", dtype="object", missing_pct=0.0, unique_count=25000),
|
| 636 |
+
ColumnInfo(name="camera_model", dtype="object", missing_pct=0.0, unique_count=12),
|
| 637 |
+
ColumnInfo(name="exposure", dtype="float64", missing_pct=2.0, unique_count=500),
|
| 638 |
+
ColumnInfo(name="iso", dtype="int64", missing_pct=1.0, unique_count=20),
|
| 639 |
+
ColumnInfo(name="focal_length", dtype="float64", missing_pct=0.5, unique_count=30),
|
| 640 |
+
ColumnInfo(name="width", dtype="int64", missing_pct=0.0, unique_count=10),
|
| 641 |
+
ColumnInfo(name="height", dtype="int64", missing_pct=0.0, unique_count=10),
|
| 642 |
+
ColumnInfo(name="quality_score", dtype="float64", missing_pct=0.0, unique_count=1000),
|
| 643 |
+
]
|
| 644 |
+
|
| 645 |
+
props = DatasetProperties(
|
| 646 |
+
has_heavy_tails=True, has_group_column=True,
|
| 647 |
+
has_images=True,
|
| 648 |
+
)
|
| 649 |
+
|
| 650 |
+
failure_modes = [
|
| 651 |
+
FailureMode(
|
| 652 |
+
name="no_augmentation_on_images",
|
| 653 |
+
trigger_tag="submit",
|
| 654 |
+
condition_field="has_images", condition_value=True,
|
| 655 |
+
cv_effect=0.0, test_effect=-0.04,
|
| 656 |
+
message="Submitting image model without augmentation — overfitting on small image data.",
|
| 657 |
+
),
|
| 658 |
+
FailureMode(
|
| 659 |
+
name="tree_model_on_images",
|
| 660 |
+
trigger_tag="train_model:xgboost",
|
| 661 |
+
condition_field="has_images", condition_value=True,
|
| 662 |
+
cv_effect=0.04, test_effect=-0.02,
|
| 663 |
+
message="Tree models on raw image metadata miss the visual signal entirely.",
|
| 664 |
+
),
|
| 665 |
+
]
|
| 666 |
+
|
| 667 |
+
context_relevance = {
|
| 668 |
+
"train_model:pretrained_backbone": 1.0,
|
| 669 |
+
"regularize:freeze_backbone": 1.0,
|
| 670 |
+
"augmentation:geometric": 1.0,
|
| 671 |
+
"augmentation:color_transform": 1.0,
|
| 672 |
+
"augmentation:camera_simulation": 1.0,
|
| 673 |
+
"augmentation:clahe": 0.9,
|
| 674 |
+
"postprocess:tta": 1.0,
|
| 675 |
+
"postprocess:per_group_calibration": 0.9,
|
| 676 |
+
"tune_loss:gaussian_nll": 1.0,
|
| 677 |
+
"tune_loss:multi_task": 0.8,
|
| 678 |
+
"regularize:ema": 0.8,
|
| 679 |
+
"ensemble:multi_seed_averaging": 0.8,
|
| 680 |
+
"ensemble:swa": 0.8,
|
| 681 |
+
"feature_engineering:log_transform": 0.7,
|
| 682 |
+
"set_cv:group_kfold": 1.0,
|
| 683 |
+
"set_cv:kfold": -0.6,
|
| 684 |
+
# Irrelevant
|
| 685 |
+
"feature_engineering:tfidf_features": -1.0,
|
| 686 |
+
"feature_engineering:relative_coordinates": -0.8,
|
| 687 |
+
"tune_loss:auxiliary_physics_loss": -0.8,
|
| 688 |
+
"detect_shift:adversarial_validation": 0.2,
|
| 689 |
+
"train_model:xgboost": -0.3,
|
| 690 |
+
"train_model:linear": -0.7,
|
| 691 |
+
}
|
| 692 |
+
|
| 693 |
+
return TaskDefinition(
|
| 694 |
+
task_id="image_quality",
|
| 695 |
+
title="Image Quality Assessment",
|
| 696 |
+
difficulty="hard",
|
| 697 |
+
description=(
|
| 698 |
+
"Predict continuous quality scores from photographs. Images from "
|
| 699 |
+
"multiple cameras with varying resolution. Heavy-tailed target "
|
| 700 |
+
"and camera-specific biases."
|
| 701 |
+
),
|
| 702 |
+
dataset_metadata=DatasetMetadata(
|
| 703 |
+
num_rows=25000, num_features=8, columns=columns,
|
| 704 |
+
target_column="quality_score", task_type="regression",
|
| 705 |
+
has_image_data=True, has_group_column=True,
|
| 706 |
+
target_distribution="heavy_tailed",
|
| 707 |
+
),
|
| 708 |
+
dataset_properties=props,
|
| 709 |
+
base_cv_score=0.35,
|
| 710 |
+
base_test_score=0.30,
|
| 711 |
+
expected_strategies=[
|
| 712 |
+
"set_cv:group_kfold",
|
| 713 |
+
"train_model:pretrained_backbone",
|
| 714 |
+
"augmentation:geometric",
|
| 715 |
+
"augmentation:color_transform",
|
| 716 |
+
"augmentation:camera_simulation",
|
| 717 |
+
"augmentation:clahe",
|
| 718 |
+
"feature_engineering:log_transform",
|
| 719 |
+
"tune_loss:gaussian_nll",
|
| 720 |
+
"tune_loss:multi_task",
|
| 721 |
+
"regularize:freeze_backbone",
|
| 722 |
+
"regularize:ema",
|
| 723 |
+
"ensemble:multi_seed_averaging",
|
| 724 |
+
"ensemble:swa",
|
| 725 |
+
"postprocess:tta",
|
| 726 |
+
"postprocess:per_group_calibration",
|
| 727 |
+
],
|
| 728 |
+
ghost_scores=[0.90, 0.87, 0.84, 0.81, 0.77, 0.73, 0.68, 0.62, 0.55],
|
| 729 |
+
hints=[
|
| 730 |
+
"Group k-fold by camera_model — camera bias leaks between folds.",
|
| 731 |
+
"Pretrained ImageNet backbones are essential with only 25k images.",
|
| 732 |
+
"Freeze backbone first, train head, then fine-tune.",
|
| 733 |
+
"Geometric + color augmentations are a must for image tasks.",
|
| 734 |
+
"Camera simulation augmentation handles focal length variance.",
|
| 735 |
+
"CLAHE normalisation helps with exposure variation.",
|
| 736 |
+
"Heavy-tailed target → log transform + Gaussian NLL loss.",
|
| 737 |
+
"Multi-task: add classification head for quality bins alongside regression.",
|
| 738 |
+
"EMA and SWA stabilise training; multi-seed averaging adds robustness.",
|
| 739 |
+
"TTA with brightness + crops gives 1-2% boost at inference.",
|
| 740 |
+
],
|
| 741 |
+
score_modifiers={
|
| 742 |
+
"set_cv:kfold": {"cv": 0.06, "test": 0.00},
|
| 743 |
+
"set_cv:group_kfold": {"cv": 0.04, "test": 0.08},
|
| 744 |
+
"train_model:pretrained_backbone": {"cv": 0.22, "test": 0.20},
|
| 745 |
+
"train_model:neural_network": {"cv": 0.14, "test": 0.11},
|
| 746 |
+
"train_model:xgboost": {"cv": 0.04, "test": -0.02},
|
| 747 |
+
"train_model:linear": {"cv": 0.02, "test": 0.02},
|
| 748 |
+
"augmentation:geometric": {"cv": 0.04, "test": 0.05},
|
| 749 |
+
"augmentation:color_transform": {"cv": 0.03, "test": 0.04},
|
| 750 |
+
"augmentation:clahe": {"cv": 0.03, "test": 0.03},
|
| 751 |
+
"augmentation:camera_simulation": {"cv": 0.03, "test": 0.04},
|
| 752 |
+
"augmentation:gaussian_noise": {"cv": 0.02, "test": 0.02},
|
| 753 |
+
"augmentation:image_rectification": {"cv": 0.02, "test": 0.03},
|
| 754 |
+
"augmentation:multi_view_processing": {"cv": 0.03, "test": 0.03},
|
| 755 |
+
"feature_engineering:log_transform": {"cv": 0.03, "test": 0.04},
|
| 756 |
+
"feature_engineering:normalize": {"cv": 0.02, "test": 0.02},
|
| 757 |
+
"tune_loss:gaussian_nll": {"cv": 0.04, "test": 0.05},
|
| 758 |
+
"tune_loss:multi_task": {"cv": 0.03, "test": 0.04},
|
| 759 |
+
"tune_loss:interval_regression": {"cv": 0.02, "test": 0.03},
|
| 760 |
+
"tune_loss:quantile_regression": {"cv": 0.02, "test": 0.03},
|
| 761 |
+
"regularize:freeze_backbone": {"cv": 0.03, "test": 0.05},
|
| 762 |
+
"regularize:ema": {"cv": 0.02, "test": 0.03},
|
| 763 |
+
"regularize:dropout": {"cv": 0.01, "test": 0.02},
|
| 764 |
+
"ensemble:multi_seed_averaging": {"cv": 0.03, "test": 0.04},
|
| 765 |
+
"ensemble:swa": {"cv": 0.03, "test": 0.04},
|
| 766 |
+
"ensemble:weighted_average": {"cv": 0.03, "test": 0.03},
|
| 767 |
+
"postprocess:tta": {"cv": 0.03, "test": 0.04},
|
| 768 |
+
"postprocess:per_group_calibration": {"cv": 0.02, "test": 0.04},
|
| 769 |
+
"postprocess:prediction_shrinkage": {"cv": 0.02, "test": 0.03},
|
| 770 |
+
"pseudo_label:1": {"cv": 0.02, "test": 0.01},
|
| 771 |
+
"inspect_top_solution": {"cv": 0.0, "test": 0.0},
|
| 772 |
+
},
|
| 773 |
+
overfitting_risk={
|
| 774 |
+
"set_cv:kfold": 0.06,
|
| 775 |
+
"train_model:xgboost": 0.06,
|
| 776 |
+
},
|
| 777 |
+
strategy_combos=[
|
| 778 |
+
StrategyComboDef(
|
| 779 |
+
name="vision_pipeline",
|
| 780 |
+
required=frozenset({
|
| 781 |
+
"train_model:pretrained_backbone", "regularize:freeze_backbone",
|
| 782 |
+
"augmentation:geometric", "augmentation:color_transform",
|
| 783 |
+
}),
|
| 784 |
+
cv_bonus=0.03, test_bonus=0.05,
|
| 785 |
+
),
|
| 786 |
+
StrategyComboDef(
|
| 787 |
+
name="heavy_tail_handling",
|
| 788 |
+
required=frozenset({
|
| 789 |
+
"feature_engineering:log_transform",
|
| 790 |
+
"tune_loss:gaussian_nll",
|
| 791 |
+
"postprocess:prediction_shrinkage",
|
| 792 |
+
}),
|
| 793 |
+
cv_bonus=0.02, test_bonus=0.04,
|
| 794 |
+
),
|
| 795 |
+
StrategyComboDef(
|
| 796 |
+
name="inference_boost",
|
| 797 |
+
required=frozenset({
|
| 798 |
+
"postprocess:tta", "ensemble:multi_seed_averaging", "ensemble:swa",
|
| 799 |
+
}),
|
| 800 |
+
cv_bonus=0.02, test_bonus=0.04,
|
| 801 |
+
),
|
| 802 |
+
StrategyComboDef(
|
| 803 |
+
name="camera_robustness",
|
| 804 |
+
required=frozenset({
|
| 805 |
+
"augmentation:camera_simulation", "augmentation:clahe",
|
| 806 |
+
"postprocess:per_group_calibration",
|
| 807 |
+
}),
|
| 808 |
+
cv_bonus=0.02, test_bonus=0.04,
|
| 809 |
+
),
|
| 810 |
+
],
|
| 811 |
+
failure_modes=failure_modes,
|
| 812 |
+
context_relevance=context_relevance,
|
| 813 |
+
max_steps=30,
|
| 814 |
+
)
|
| 815 |
+
|
| 816 |
+
|
| 817 |
+
# =========================================================================
|
| 818 |
+
# Task 5 – Trajectory: Multi-agent spatial-temporal prediction
|
| 819 |
+
# =========================================================================
|
| 820 |
+
|
| 821 |
+
def _trajectory_task() -> TaskDefinition:
|
| 822 |
+
columns = [
|
| 823 |
+
ColumnInfo(name="scene_id", dtype="int64", missing_pct=0.0, unique_count=5000),
|
| 824 |
+
ColumnInfo(name="frame_id", dtype="int64", missing_pct=0.0, unique_count=50),
|
| 825 |
+
ColumnInfo(name="agent_id", dtype="int64", missing_pct=0.0, unique_count=20),
|
| 826 |
+
ColumnInfo(name="x", dtype="float64", missing_pct=0.0, unique_count=100000),
|
| 827 |
+
ColumnInfo(name="y", dtype="float64", missing_pct=0.0, unique_count=100000),
|
| 828 |
+
ColumnInfo(name="vx", dtype="float64", missing_pct=0.0, unique_count=50000),
|
| 829 |
+
ColumnInfo(name="vy", dtype="float64", missing_pct=0.0, unique_count=50000),
|
| 830 |
+
ColumnInfo(name="heading", dtype="float64", missing_pct=0.0, unique_count=3600),
|
| 831 |
+
ColumnInfo(name="agent_type", dtype="object", missing_pct=0.0, unique_count=4),
|
| 832 |
+
ColumnInfo(name="future_x", dtype="float64", missing_pct=0.0, unique_count=100000),
|
| 833 |
+
ColumnInfo(name="future_y", dtype="float64", missing_pct=0.0, unique_count=100000),
|
| 834 |
+
]
|
| 835 |
+
|
| 836 |
+
props = DatasetProperties(
|
| 837 |
+
has_time_column=True, has_group_column=True,
|
| 838 |
+
has_spatial_data=True, needs_physics=True,
|
| 839 |
+
)
|
| 840 |
+
|
| 841 |
+
failure_modes = [
|
| 842 |
+
FailureMode(
|
| 843 |
+
name="tree_model_on_trajectories",
|
| 844 |
+
trigger_tag="train_model:xgboost",
|
| 845 |
+
condition_field="has_spatial_data", condition_value=True,
|
| 846 |
+
cv_effect=0.04, test_effect=-0.03,
|
| 847 |
+
message="Tree models cannot capture spatial-temporal dynamics — use neural approaches.",
|
| 848 |
+
),
|
| 849 |
+
FailureMode(
|
| 850 |
+
name="raw_heading_without_sincos",
|
| 851 |
+
trigger_tag="submit",
|
| 852 |
+
condition_field="has_spatial_data", condition_value=True,
|
| 853 |
+
cv_effect=0.0, test_effect=-0.03,
|
| 854 |
+
message="Submitting without sin/cos encoding — raw heading angles wrap around and hurt predictions.",
|
| 855 |
+
),
|
| 856 |
+
]
|
| 857 |
+
|
| 858 |
+
context_relevance = {
|
| 859 |
+
"set_cv:group_kfold": 1.0,
|
| 860 |
+
"feature_engineering:sin_cos_encoding": 1.0,
|
| 861 |
+
"feature_engineering:relative_coordinates": 1.0,
|
| 862 |
+
"feature_engineering:distance_features": 1.0,
|
| 863 |
+
"train_model:transformer_encoder": 1.0,
|
| 864 |
+
"train_model:temporal_cnn": 0.9,
|
| 865 |
+
"augmentation:rotation_invariant": 1.0,
|
| 866 |
+
"augmentation:symmetry_augmentation": 0.9,
|
| 867 |
+
"augmentation:temporal_augmentation": 0.9,
|
| 868 |
+
"tune_loss:gaussian_nll": 1.0,
|
| 869 |
+
"tune_loss:auxiliary_physics_loss": 1.0,
|
| 870 |
+
"regularize:ema": 0.9,
|
| 871 |
+
"ensemble:multi_seed_averaging": 0.8,
|
| 872 |
+
"postprocess:physics_constraints": 1.0,
|
| 873 |
+
"clean_data:remove_corrupted": 0.8,
|
| 874 |
+
"clean_data:remove_outliers": 0.7,
|
| 875 |
+
"set_cv:kfold": -0.8,
|
| 876 |
+
# Irrelevant
|
| 877 |
+
"train_model:xgboost": -0.6,
|
| 878 |
+
"train_model:linear": -0.9,
|
| 879 |
+
"augmentation:color_transform": -1.0,
|
| 880 |
+
"augmentation:clahe": -1.0,
|
| 881 |
+
"augmentation:camera_simulation": -0.8,
|
| 882 |
+
"feature_engineering:tfidf_features": -1.0,
|
| 883 |
+
"feature_engineering:target_encoding": -0.7,
|
| 884 |
+
}
|
| 885 |
+
|
| 886 |
+
return TaskDefinition(
|
| 887 |
+
task_id="trajectory_pred",
|
| 888 |
+
title="Multi-Agent Trajectory Prediction",
|
| 889 |
+
difficulty="hard",
|
| 890 |
+
description=(
|
| 891 |
+
"Predict future (x,y) positions of multiple interacting agents in "
|
| 892 |
+
"driving scenes. Requires spatial-temporal encoding, rotation-invariant "
|
| 893 |
+
"processing, multi-agent interaction modeling, and physics-aware predictions."
|
| 894 |
+
),
|
| 895 |
+
dataset_metadata=DatasetMetadata(
|
| 896 |
+
num_rows=500000, num_features=10, columns=columns,
|
| 897 |
+
target_column="future_x,future_y", task_type="regression",
|
| 898 |
+
has_time_column=True, has_group_column=True,
|
| 899 |
+
has_spatial_data=True,
|
| 900 |
+
),
|
| 901 |
+
dataset_properties=props,
|
| 902 |
+
base_cv_score=0.32,
|
| 903 |
+
base_test_score=0.28,
|
| 904 |
+
expected_strategies=[
|
| 905 |
+
"set_cv:group_kfold",
|
| 906 |
+
"feature_engineering:sin_cos_encoding",
|
| 907 |
+
"feature_engineering:relative_coordinates",
|
| 908 |
+
"feature_engineering:distance_features",
|
| 909 |
+
"clean_data:remove_corrupted",
|
| 910 |
+
"clean_data:remove_outliers",
|
| 911 |
+
"train_model:transformer_encoder",
|
| 912 |
+
"train_model:temporal_cnn",
|
| 913 |
+
"augmentation:rotation_invariant",
|
| 914 |
+
"augmentation:symmetry_augmentation",
|
| 915 |
+
"augmentation:temporal_augmentation",
|
| 916 |
+
"tune_loss:gaussian_nll",
|
| 917 |
+
"tune_loss:auxiliary_physics_loss",
|
| 918 |
+
"regularize:ema",
|
| 919 |
+
"ensemble:multi_seed_averaging",
|
| 920 |
+
"postprocess:physics_constraints",
|
| 921 |
+
],
|
| 922 |
+
ghost_scores=[0.89, 0.86, 0.83, 0.80, 0.76, 0.71, 0.66, 0.60, 0.52],
|
| 923 |
+
hints=[
|
| 924 |
+
"Group k-fold by scene_id — prevents leaking scene-specific patterns.",
|
| 925 |
+
"Encode heading with sin/cos — raw angles wrap around and mislead models.",
|
| 926 |
+
"Use relative coordinates instead of absolute positions.",
|
| 927 |
+
"Distance features to nearby agents help interaction modeling.",
|
| 928 |
+
"Remove scenes with abnormal sequence lengths.",
|
| 929 |
+
"Transformer encoders capture multi-agent interactions best.",
|
| 930 |
+
"Temporal CNN with gradual pooling works well for positional time series.",
|
| 931 |
+
"Rotation-invariant augmentation: rotate scene, preserve relative positions.",
|
| 932 |
+
"Gaussian NLL lets the model express uncertainty per prediction.",
|
| 933 |
+
"Auxiliary physics loss: penalise unrealistic acceleration/jerk.",
|
| 934 |
+
"Physics post-processing: clamp velocity, smooth jerk, enforce kinematic limits.",
|
| 935 |
+
],
|
| 936 |
+
score_modifiers={
|
| 937 |
+
"set_cv:kfold": {"cv": 0.06, "test": -0.02},
|
| 938 |
+
"set_cv:group_kfold": {"cv": 0.04, "test": 0.08},
|
| 939 |
+
"feature_engineering:sin_cos_encoding": {"cv": 0.04, "test": 0.05},
|
| 940 |
+
"feature_engineering:relative_coordinates": {"cv": 0.05, "test": 0.06},
|
| 941 |
+
"feature_engineering:distance_features": {"cv": 0.04, "test": 0.05},
|
| 942 |
+
"feature_engineering:normalize": {"cv": 0.02, "test": 0.02},
|
| 943 |
+
"feature_engineering:spatial_encoding": {"cv": 0.03, "test": 0.03},
|
| 944 |
+
"clean_data:remove_corrupted": {"cv": 0.02, "test": 0.03},
|
| 945 |
+
"clean_data:remove_outliers": {"cv": 0.02, "test": 0.03},
|
| 946 |
+
"train_model:transformer_encoder": {"cv": 0.18, "test": 0.17},
|
| 947 |
+
"train_model:temporal_cnn": {"cv": 0.16, "test": 0.15},
|
| 948 |
+
"train_model:neural_network": {"cv": 0.12, "test": 0.10},
|
| 949 |
+
"train_model:xgboost": {"cv": 0.04, "test": -0.03},
|
| 950 |
+
"train_model:linear": {"cv": 0.02, "test": 0.02},
|
| 951 |
+
"augmentation:rotation_invariant": {"cv": 0.04, "test": 0.05},
|
| 952 |
+
"augmentation:symmetry_augmentation": {"cv": 0.03, "test": 0.04},
|
| 953 |
+
"augmentation:temporal_augmentation": {"cv": 0.03, "test": 0.04},
|
| 954 |
+
"tune_loss:gaussian_nll": {"cv": 0.04, "test": 0.05},
|
| 955 |
+
"tune_loss:auxiliary_physics_loss": {"cv": 0.03, "test": 0.05},
|
| 956 |
+
"tune_loss:multi_task": {"cv": 0.02, "test": 0.03},
|
| 957 |
+
"regularize:ema": {"cv": 0.03, "test": 0.04},
|
| 958 |
+
"regularize:dropout": {"cv": 0.01, "test": 0.02},
|
| 959 |
+
"ensemble:multi_seed_averaging": {"cv": 0.03, "test": 0.04},
|
| 960 |
+
"ensemble:swa": {"cv": 0.02, "test": 0.03},
|
| 961 |
+
"ensemble:heterogeneous": {"cv": 0.04, "test": 0.04},
|
| 962 |
+
"postprocess:physics_constraints": {"cv": 0.03, "test": 0.05},
|
| 963 |
+
"postprocess:tta": {"cv": 0.02, "test": 0.02},
|
| 964 |
+
"pseudo_label:1": {"cv": 0.02, "test": 0.01},
|
| 965 |
+
"inspect_top_solution": {"cv": 0.0, "test": 0.0},
|
| 966 |
+
},
|
| 967 |
+
overfitting_risk={
|
| 968 |
+
"set_cv:kfold": 0.08,
|
| 969 |
+
"train_model:xgboost": 0.07,
|
| 970 |
+
},
|
| 971 |
+
strategy_combos=[
|
| 972 |
+
StrategyComboDef(
|
| 973 |
+
name="spatial_encoding_suite",
|
| 974 |
+
required=frozenset({
|
| 975 |
+
"feature_engineering:sin_cos_encoding",
|
| 976 |
+
"feature_engineering:relative_coordinates",
|
| 977 |
+
"feature_engineering:distance_features",
|
| 978 |
+
}),
|
| 979 |
+
cv_bonus=0.03, test_bonus=0.05,
|
| 980 |
+
),
|
| 981 |
+
StrategyComboDef(
|
| 982 |
+
name="geometric_augmentation_suite",
|
| 983 |
+
required=frozenset({
|
| 984 |
+
"augmentation:rotation_invariant",
|
| 985 |
+
"augmentation:symmetry_augmentation",
|
| 986 |
+
"augmentation:temporal_augmentation",
|
| 987 |
+
}),
|
| 988 |
+
cv_bonus=0.03, test_bonus=0.05,
|
| 989 |
+
),
|
| 990 |
+
StrategyComboDef(
|
| 991 |
+
name="physics_aware_training",
|
| 992 |
+
required=frozenset({
|
| 993 |
+
"tune_loss:gaussian_nll",
|
| 994 |
+
"tune_loss:auxiliary_physics_loss",
|
| 995 |
+
"postprocess:physics_constraints",
|
| 996 |
+
}),
|
| 997 |
+
cv_bonus=0.03, test_bonus=0.06,
|
| 998 |
+
),
|
| 999 |
+
StrategyComboDef(
|
| 1000 |
+
name="stable_training",
|
| 1001 |
+
required=frozenset({
|
| 1002 |
+
"regularize:ema", "ensemble:multi_seed_averaging",
|
| 1003 |
+
}),
|
| 1004 |
+
cv_bonus=0.02, test_bonus=0.03,
|
| 1005 |
+
),
|
| 1006 |
+
],
|
| 1007 |
+
failure_modes=failure_modes,
|
| 1008 |
+
context_relevance=context_relevance,
|
| 1009 |
+
max_steps=30,
|
| 1010 |
+
)
|
| 1011 |
+
|
| 1012 |
+
|
| 1013 |
+
# =========================================================================
|
| 1014 |
+
# Registry
|
| 1015 |
+
# =========================================================================
|
| 1016 |
+
|
| 1017 |
+
TASK_REGISTRY: dict[str, TaskDefinition] = {
|
| 1018 |
+
"easy_churn": _easy_task(),
|
| 1019 |
+
"medium_fraud": _medium_task(),
|
| 1020 |
+
"hard_leaky_noisy": _hard_task(),
|
| 1021 |
+
"image_quality": _image_task(),
|
| 1022 |
+
"trajectory_pred": _trajectory_task(),
|
| 1023 |
+
}
|
| 1024 |
+
|
| 1025 |
+
|
| 1026 |
+
def get_task(task_id: str) -> TaskDefinition:
|
| 1027 |
+
if task_id not in TASK_REGISTRY:
|
| 1028 |
+
raise ValueError(
|
| 1029 |
+
f"Unknown task '{task_id}'. Available: {list(TASK_REGISTRY.keys())}"
|
| 1030 |
+
)
|
| 1031 |
+
return TASK_REGISTRY[task_id]
|
openenv.yaml
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: KaggleSimEnv
|
| 2 |
+
version: "3.0.0"
|
| 3 |
+
description: >
|
| 4 |
+
Production-grade OpenEnv RL environment simulating Kaggle competitions.
|
| 5 |
+
Features hierarchical action categories, causal dataset properties,
|
| 6 |
+
failure-mode traps, contextual strategy scoring, and 50+ advanced strategies.
|
| 7 |
+
|
| 8 |
+
author: OpenEnv Hackathon Team
|
| 9 |
+
license: MIT
|
| 10 |
+
|
| 11 |
+
environment:
|
| 12 |
+
module: kaggle_sim_env.environment
|
| 13 |
+
class: KaggleSimEnv
|
| 14 |
+
|
| 15 |
+
api:
|
| 16 |
+
framework: fastapi
|
| 17 |
+
module: api.server
|
| 18 |
+
app: app
|
| 19 |
+
port: 7860
|
| 20 |
+
|
| 21 |
+
action_space:
|
| 22 |
+
types: 14
|
| 23 |
+
techniques: 60+
|
| 24 |
+
hierarchical: true
|
| 25 |
+
format: '{"action_type": "...", "parameters": {"category": "...", "<key>": "..."}}'
|
| 26 |
+
|
| 27 |
+
tasks:
|
| 28 |
+
- id: easy_churn
|
| 29 |
+
difficulty: easy
|
| 30 |
+
failure_modes: 3
|
| 31 |
+
combos: 2
|
| 32 |
+
- id: medium_fraud
|
| 33 |
+
difficulty: medium
|
| 34 |
+
failure_modes: 3
|
| 35 |
+
combos: 3
|
| 36 |
+
- id: hard_leaky_noisy
|
| 37 |
+
difficulty: hard
|
| 38 |
+
failure_modes: 4
|
| 39 |
+
combos: 4
|
| 40 |
+
- id: image_quality
|
| 41 |
+
difficulty: hard
|
| 42 |
+
failure_modes: 2
|
| 43 |
+
combos: 4
|
| 44 |
+
- id: trajectory_pred
|
| 45 |
+
difficulty: hard
|
| 46 |
+
failure_modes: 2
|
| 47 |
+
combos: 4
|
| 48 |
+
|
| 49 |
+
grader:
|
| 50 |
+
module: kaggle_sim_env.grader
|
| 51 |
+
class: Grader
|
| 52 |
+
score_range: [0.0, 1.0]
|
| 53 |
+
weights:
|
| 54 |
+
performance: 0.40
|
| 55 |
+
strategy: 0.25
|
| 56 |
+
combo: 0.20
|
| 57 |
+
trap_avoidance: 0.15
|
| 58 |
+
|
| 59 |
+
reward:
|
| 60 |
+
components:
|
| 61 |
+
- cv_improvement
|
| 62 |
+
- strategy_bonus
|
| 63 |
+
- context_bonus
|
| 64 |
+
- combo_bonus
|
| 65 |
+
- redundancy_penalty
|
| 66 |
+
- irrelevant_penalty
|
| 67 |
+
- trap_penalty
|
| 68 |
+
- overfitting_penalty
|
| 69 |
+
- submission_bonus
|
| 70 |
+
|
| 71 |
+
baseline:
|
| 72 |
+
script: baseline/run_baseline.py
|
| 73 |
+
approach: structured_phases_with_hints
|
| 74 |
+
requires:
|
| 75 |
+
- OPENAI_API_KEY
|
| 76 |
+
|
| 77 |
+
docker:
|
| 78 |
+
port: 7860
|
| 79 |
+
dockerfile: Dockerfile
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi>=0.115.0
|
| 2 |
+
uvicorn[standard]>=0.32.0
|
| 3 |
+
pydantic>=2.9.0
|
| 4 |
+
openai>=1.55.0
|
| 5 |
+
requests>=2.32.0
|