mojo-tabular: tiny CPU tabular classifiers
Browse files- .gitignore +10 -0
- README.md +53 -0
- artifacts/.gitkeep +0 -0
- artifacts/adult.joblib +3 -0
- artifacts/bank-marketing.joblib +3 -0
- artifacts/credit-g.joblib +3 -0
- artifacts/diabetes.joblib +3 -0
- artifacts/phoneme.joblib +3 -0
- cog/.cog/cog.lock +0 -0
- cog/.cog/openapi_schema.json +1 -0
- cog/.dockerignore +3 -0
- cog/Dockerfile +10 -0
- cog/models/adult.joblib +3 -0
- cog/models/bank-marketing.joblib +3 -0
- cog/models/credit-g.joblib +3 -0
- cog/models/diabetes.joblib +3 -0
- cog/models/phoneme.joblib +3 -0
- cog/predict.py +29 -0
- cog/requirements.txt +4 -0
- cog/server.py +53 -0
- cog/tests/test_predict.py +36 -0
- configs/.gitkeep +0 -0
- configs/project.json +20 -0
- prompts/.gitkeep +0 -0
- prompts/implement.txt +9 -0
- prompts/kernel.txt +50 -0
- prompts/review.txt +124 -0
- results/.gitkeep +0 -0
- results/baselines.json +169 -0
- results/smoke.json +14 -0
- results/tuned-adult.json +44 -0
- results/tuned-bank-marketing.json +50 -0
- results/tuned-credit-g.json +40 -0
- results/tuned-diabetes.json +36 -0
- results/tuned-phoneme.json +24 -0
- results/tuned.json +151 -0
- src/.gitkeep +0 -0
- tests/.gitkeep +0 -0
- train/fetch_tabular.py +17 -0
- train/tabular_bench.py +75 -0
- train/tabular_tune.py +89 -0
.gitignore
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
artifacts/
|
| 2 |
+
artifacts-gb/
|
| 3 |
+
__pycache__/
|
| 4 |
+
*.pyc
|
| 5 |
+
.env*
|
| 6 |
+
*.pem
|
| 7 |
+
*.key
|
| 8 |
+
prompts/
|
| 9 |
+
REVIEW.md
|
| 10 |
+
cog/.cog/
|
README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
library_name: scikit-learn
|
| 4 |
+
pipeline_tag: tabular-classification
|
| 5 |
+
tags: [tabular, cpu, tiny, cog, app-nz]
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
# mojo-tabular
|
| 9 |
+
|
| 10 |
+
Tiny CPU tabular classifiers (one per dataset), total 12 MB, no GPU. Trained with scikit-learn;
|
| 11 |
+
`mojo-` is the planned optimization path, not a Mojo implementation yet.
|
| 12 |
+
|
| 13 |
+
## Results (ROC-AUC, seed 42, 60/20/20 stratified split, selection on validation only)
|
| 14 |
+
|
| 15 |
+
| dataset (OpenML) | val | test | untuned best baseline test | MB |
|
| 16 |
+
|---|---|---|---|---|
|
| 17 |
+
| adult | 0.929 | 0.9282 | 0.9272 | 2.64 |
|
| 18 |
+
| credit-g | 0.8294 | 0.7986 | 0.7958 | 0.00 |
|
| 19 |
+
| bank-marketing | 0.9354 | 0.9384 | 0.9375 | 2.67 |
|
| 20 |
+
| phoneme | 0.9523 | 0.9546 | 0.9554 | 5.50 |
|
| 21 |
+
| diabetes | 0.8528 | 0.8001 | 0.8031 | 1.09 |
|
| 22 |
+
|
| 23 |
+
Search: 16 random HistGradientBoosting configs + logistic regression + extra trees; top-3 averaged
|
| 24 |
+
when validation improves. Single split, single seed: small sets (credit-g n=1000, diabetes n=768) have
|
| 25 |
+
large validation/test gaps and are noisy. No comparison against XGBoost/CatBoost/TabPFN has been run,
|
| 26 |
+
so **no frontier claim is made**. Adding tuned XGBoost 3.4.1 and LightGBM 4.7.0 (6 configs each) to the pool changed test AUC by at most +0.001 (adult 0.929, bank-marketing 0.9386), i.e. these results sit at the plateau of strong boosted trees; the shipped models use sklearn only to keep the image small. CatBoost and TabPFN were not compared (CatBoost hung under the 4 GiB address-space limit). Data: public OpenML sets (license listed as Public).
|
| 27 |
+
Reproduce: `train/fetch_tabular.py`, `train/tabular_tune.py` (`--gbdt` needs xgboost/lightgbm), artifact hashes in `results/tuned.json`.
|
| 28 |
+
|
| 29 |
+
## Use
|
| 30 |
+
|
| 31 |
+
```python
|
| 32 |
+
import joblib, pandas as pd
|
| 33 |
+
b = joblib.load("models/adult.joblib") # needs scikit-learn==1.8.0
|
| 34 |
+
p = sum(m.predict_proba(df[b["columns"]])[:, 1] for m in b["members"]) / len(b["members"])
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
## Deploy on app.nz (Cog, CPU)
|
| 38 |
+
|
| 39 |
+
```bash
|
| 40 |
+
cd cog && python3 -m unittest discover -s tests # contract test, no network
|
| 41 |
+
docker build -t ghcr.io/lee101/mojo-tabular:latest . # 381 MB (python:3.12-slim), weights baked
|
| 42 |
+
docker run --rm -p 5000:5000 --memory 512m ghcr.io/lee101/mojo-tabular:latest # ~80 MB RSS, ready in <10 s
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
[](https://app.nz/deploy?image=ghcr.io/lee101/mojo-tabular:latest&name=mojo-tabular&hardware=cpu&idleSeconds=60)
|
| 46 |
+
|
| 47 |
+
`POST /predictions {"input": {"dataset": "adult", "rows": "[{...}]"}}` returns a JSON list of probabilities
|
| 48 |
+
(max 1000 rows). Do not use the default `cog build` image: its apt layer alone is 1.78 GB (2.33 GB total).
|
| 49 |
+
Links: GitHub `lee101/mojo-tabular`, Hugging Face `lee101/mojo-tabular`.
|
| 50 |
+
|
| 51 |
+
## Limitations
|
| 52 |
+
|
| 53 |
+
Trained on public benchmark data only; not validated for credit, hiring or medical decisions.
|
artifacts/.gitkeep
ADDED
|
File without changes
|
artifacts/adult.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a28557fc9992cefa1eaaf2308964baa93b1c7e78f266c1a349fd64cdb1b246a7
|
| 3 |
+
size 2639714
|
artifacts/bank-marketing.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ec72f430d51f216a15d3a611e6ff1ed279ecc022759160354cb821a6077f29ea
|
| 3 |
+
size 2670481
|
artifacts/credit-g.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:240366785f1e95fa3749b741747be6d4bb258d5be362e03cc5dec768b10d0496
|
| 3 |
+
size 3369
|
artifacts/diabetes.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:50b5ecc4afc5e291dc45c7ce504a40fe2d5f735d3c86157a6c10a95407a9691f
|
| 3 |
+
size 1085047
|
artifacts/phoneme.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e37ac3db2ba7bd9839833d8e0ac8a0a537467ddcf58b098d6550af47485fd683
|
| 3 |
+
size 5496319
|
cog/.cog/cog.lock
ADDED
|
File without changes
|
cog/.cog/openapi_schema.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"components":{"schemas":{"Input":{"properties":{"dataset":{"title":"Dataset","type":"string","x-order":0},"rows":{"title":"Rows","type":"string","x-order":1}},"required":["dataset","rows"],"title":"Input","type":"object"},"Output":{"title":"Output","type":"string"},"PredictionRequest":{"properties":{"id":{"title":"Id","type":"string"},"input":{"$ref":"#/components/schemas/Input"}},"title":"PredictionRequest","type":"object"},"PredictionResponse":{"properties":{"completed_at":{"format":"date-time","title":"Completed At","type":"string"},"created_at":{"format":"date-time","title":"Created At","type":"string"},"error":{"title":"Error","type":"string"},"id":{"title":"Id","type":"string"},"input":{"$ref":"#/components/schemas/Input"},"logs":{"title":"Logs","type":"string"},"metrics":{"title":"Metrics","type":"object"},"output":{"$ref":"#/components/schemas/Output"},"started_at":{"format":"date-time","title":"Started At","type":"string"},"status":{"title":"Status","type":"string"},"version":{"title":"Version","type":"string"}},"title":"PredictionResponse","type":"object"},"Status":{"description":"An enumeration.","enum":["starting","processing","succeeded","canceled","failed"],"title":"Status","type":"string"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"title":"Detail","type":"array"}},"title":"HTTPValidationError","type":"object"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"title":"Location","type":"array"},"msg":{"title":"Message","type":"string"},"type":{"title":"Error Type","type":"string"}},"required":["loc","msg","type"],"title":"ValidationError","type":"object"}}},"info":{"title":"Cog","version":"0.1.0"},"openapi":"3.0.2","paths":{"/":{"get":{"operationId":"root__get","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"}},"summary":"Root"}},"/health-check":{"get":{"operationId":"healthcheck_health_check_get","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"}},"summary":"Healthcheck"}},"/predictions":{"post":{"description":"Run a single prediction on the model","operationId":"predict_predictions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PredictionRequest"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PredictionResponse"}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"summary":"Predict"}},"/predictions/{prediction_id}/cancel":{"post":{"operationId":"cancel_predictions__prediction_id__cancel_post","parameters":[{"in":"path","name":"prediction_id","required":true,"schema":{"title":"Prediction Id","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Successful Response"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}},"description":"Validation Error"}},"summary":"Cancel"}}}}
|
cog/.dockerignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
tests
|
| 2 |
+
__pycache__
|
| 3 |
+
*.md
|
cog/Dockerfile
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.12-slim
|
| 2 |
+
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 OMP_NUM_THREADS=1
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
COPY requirements.txt .
|
| 5 |
+
RUN pip install --no-cache-dir -r requirements.txt && find /usr/local -name '__pycache__' -prune -exec rm -rf {} +
|
| 6 |
+
COPY predict.py server.py ./
|
| 7 |
+
COPY models models
|
| 8 |
+
USER nobody
|
| 9 |
+
EXPOSE 5000
|
| 10 |
+
CMD ["python", "server.py"]
|
cog/models/adult.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a28557fc9992cefa1eaaf2308964baa93b1c7e78f266c1a349fd64cdb1b246a7
|
| 3 |
+
size 2639714
|
cog/models/bank-marketing.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ec72f430d51f216a15d3a611e6ff1ed279ecc022759160354cb821a6077f29ea
|
| 3 |
+
size 2670481
|
cog/models/credit-g.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:240366785f1e95fa3749b741747be6d4bb258d5be362e03cc5dec768b10d0496
|
| 3 |
+
size 3369
|
cog/models/diabetes.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:50b5ecc4afc5e291dc45c7ce504a40fe2d5f735d3c86157a6c10a95407a9691f
|
| 3 |
+
size 1085047
|
cog/models/phoneme.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e37ac3db2ba7bd9839833d8e0ac8a0a537467ddcf58b098d6550af47485fd683
|
| 3 |
+
size 5496319
|
cog/predict.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
import joblib
|
| 4 |
+
import numpy as np
|
| 5 |
+
import pandas as pd
|
| 6 |
+
|
| 7 |
+
MODELS = Path(__file__).parent / 'models'
|
| 8 |
+
MAX_ROWS = 1000
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class Predictor:
|
| 12 |
+
def setup(self):
|
| 13 |
+
self.models = {p.stem: joblib.load(p) for p in sorted(MODELS.glob('*.joblib'))}
|
| 14 |
+
|
| 15 |
+
def predict(self, dataset: str, rows: str) -> str:
|
| 16 |
+
if dataset not in self.models:
|
| 17 |
+
raise ValueError(f'dataset must be one of {sorted(self.models)}')
|
| 18 |
+
data = json.loads(rows)
|
| 19 |
+
if isinstance(data, dict):
|
| 20 |
+
data = [data]
|
| 21 |
+
if not isinstance(data, list) or not data or len(data) > MAX_ROWS:
|
| 22 |
+
raise ValueError(f'rows must be 1..{MAX_ROWS} JSON objects')
|
| 23 |
+
bundle = self.models[dataset]
|
| 24 |
+
missing = [c for c in bundle['columns'] if c not in data[0]]
|
| 25 |
+
if missing:
|
| 26 |
+
raise ValueError(f'missing columns: {missing}')
|
| 27 |
+
df = pd.DataFrame(data).reindex(columns=bundle['columns'])
|
| 28 |
+
p = np.mean([m.predict_proba(df)[:, 1] for m in bundle['members']], axis=0)
|
| 29 |
+
return json.dumps([round(float(x), 6) for x in p])
|
cog/requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
scikit-learn==1.8.0
|
| 2 |
+
joblib==1.5.3
|
| 3 |
+
numpy==2.3.5
|
| 4 |
+
pandas==3.0.1
|
cog/server.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
| 3 |
+
from predict import Predictor
|
| 4 |
+
|
| 5 |
+
MAX_BODY = 4 * 1024 * 1024
|
| 6 |
+
P = Predictor()
|
| 7 |
+
STATE = {'status': 'SETUP'}
|
| 8 |
+
SCHEMA = {'outputKind': 'json', 'inputs': [
|
| 9 |
+
{'name': 'dataset', 'type': 'string', 'required': True, 'order': 0,
|
| 10 |
+
'choices': ['adult', 'bank-marketing', 'credit-g', 'diabetes', 'phoneme'],
|
| 11 |
+
'description': 'Model to use (OpenML dataset schema)'},
|
| 12 |
+
{'name': 'rows', 'type': 'string', 'required': True, 'order': 1,
|
| 13 |
+
'description': 'JSON object or list of up to 1000 objects with the dataset columns'}]}
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class H(BaseHTTPRequestHandler):
|
| 17 |
+
def send(self, code, obj):
|
| 18 |
+
b = json.dumps(obj).encode()
|
| 19 |
+
self.send_response(code)
|
| 20 |
+
self.send_header('content-type', 'application/json')
|
| 21 |
+
self.send_header('content-length', str(len(b)))
|
| 22 |
+
self.end_headers()
|
| 23 |
+
self.wfile.write(b)
|
| 24 |
+
|
| 25 |
+
def do_GET(self):
|
| 26 |
+
if self.path == '/health-check':
|
| 27 |
+
self.send(200, {'status': STATE['status']})
|
| 28 |
+
elif self.path == '/openapi.json':
|
| 29 |
+
self.send(200, SCHEMA)
|
| 30 |
+
else:
|
| 31 |
+
self.send(404, {'error': 'not found'})
|
| 32 |
+
|
| 33 |
+
def do_POST(self):
|
| 34 |
+
if self.path != '/predictions':
|
| 35 |
+
return self.send(404, {'error': 'not found'})
|
| 36 |
+
n = int(self.headers.get('content-length') or 0)
|
| 37 |
+
if n > MAX_BODY:
|
| 38 |
+
return self.send(413, {'status': 'failed', 'error': 'body too large'})
|
| 39 |
+
try:
|
| 40 |
+
inp = json.loads(self.rfile.read(n))['input']
|
| 41 |
+
out = P.predict(inp['dataset'], inp['rows'])
|
| 42 |
+
self.send(200, {'status': 'succeeded', 'output': json.loads(out)})
|
| 43 |
+
except Exception as e:
|
| 44 |
+
self.send(200, {'status': 'failed', 'error': f'{type(e).__name__}: {e}'[:300]})
|
| 45 |
+
|
| 46 |
+
def log_message(self, *a):
|
| 47 |
+
pass
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
if __name__ == '__main__':
|
| 51 |
+
P.setup()
|
| 52 |
+
STATE['status'] = 'READY'
|
| 53 |
+
ThreadingHTTPServer(('0.0.0.0', 5000), H).serve_forever()
|
cog/tests/test_predict.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json, sys, unittest
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from predict import Predictor
|
| 6 |
+
|
| 7 |
+
DATA = Path('/nvme0n1-disk/cache/foundry/tabular')
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class T(unittest.TestCase):
|
| 11 |
+
@classmethod
|
| 12 |
+
def setUpClass(cls):
|
| 13 |
+
cls.p = Predictor()
|
| 14 |
+
cls.p.setup()
|
| 15 |
+
|
| 16 |
+
def test_all_models(self):
|
| 17 |
+
for name, target in [('adult', 'class'), ('credit-g', 'class'), ('bank-marketing', 'Class'),
|
| 18 |
+
('phoneme', 'Class'), ('diabetes', 'class')]:
|
| 19 |
+
f = DATA / f'{name}.parquet'
|
| 20 |
+
if not f.exists():
|
| 21 |
+
self.skipTest('dataset cache absent')
|
| 22 |
+
df = pd.read_parquet(f).drop(columns=[target]).head(5)
|
| 23 |
+
rows = df.astype(object).where(df.notna(), None).to_json(orient='records')
|
| 24 |
+
out = json.loads(self.p.predict(name, rows))
|
| 25 |
+
self.assertEqual(len(out), 5)
|
| 26 |
+
self.assertTrue(all(0 <= x <= 1 for x in out))
|
| 27 |
+
|
| 28 |
+
def test_errors(self):
|
| 29 |
+
with self.assertRaises(ValueError):
|
| 30 |
+
self.p.predict('nope', '[]')
|
| 31 |
+
with self.assertRaises(ValueError):
|
| 32 |
+
self.p.predict('adult', '[{"age": 1}]')
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
if __name__ == '__main__':
|
| 36 |
+
unittest.main()
|
configs/.gitkeep
ADDED
|
File without changes
|
configs/project.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "mojo-tabular",
|
| 3 |
+
"task": "Structured-data classification and regression",
|
| 4 |
+
"priority": 1,
|
| 5 |
+
"status": "scaffold",
|
| 6 |
+
"backend": "pytorch-first; mojo after parity tests",
|
| 7 |
+
"max_parameters": 50000000,
|
| 8 |
+
"device": "cpu",
|
| 9 |
+
"dataset": null,
|
| 10 |
+
"objective": null,
|
| 11 |
+
"hf_model": null,
|
| 12 |
+
"hf_revision": null,
|
| 13 |
+
"profiles": [
|
| 14 |
+
"tiny",
|
| 15 |
+
"fast",
|
| 16 |
+
"balanced",
|
| 17 |
+
"max",
|
| 18 |
+
"robust"
|
| 19 |
+
]
|
| 20 |
+
}
|
prompts/.gitkeep
ADDED
|
File without changes
|
prompts/implement.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Work only in projects/mojo-tabular. Read AGENTS.md and configs/project.json.
|
| 2 |
+
Task: Structured-data classification and regression. Implement ONE small, reviewable baseline or improvement, with tests.
|
| 3 |
+
First inspect existing code and state your assumptions. Do not invent benchmark results.
|
| 4 |
+
Do not download data/models, install packages, use a GPU, launch background jobs,
|
| 5 |
+
change resource limits, access credentials, publish, or edit other projects.
|
| 6 |
+
Use synthetic fixtures for tests. Keep the hidden test split untouched.
|
| 7 |
+
For an HF candidate, propose its ID, immutable revision, license, parameter count,
|
| 8 |
+
estimated RAM and dataset suitability; stop for review before any download.
|
| 9 |
+
Finish with SUMMARY: files changed, tests run and outcomes, limitations, next step.
|
prompts/kernel.txt
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
CONTEXT
|
| 2 |
+
A small-model foundry at /sdb-disk/code/modelopt ports neural-network and
|
| 3 |
+
feature-transform inference kernels to Mojo. A pinned Mojo toolchain is
|
| 4 |
+
installed and verified working. An existing, parity-tested example of the
|
| 5 |
+
required pattern is in /sdb-disk/code/modelopt/src/kernels.mojo, its Python
|
| 6 |
+
bridge in /sdb-disk/code/modelopt/foundry/mojo_kernels.py and its tests in
|
| 7 |
+
/sdb-disk/code/modelopt/tests/test_kernels.py. READ ALL THREE FIRST and copy
|
| 8 |
+
their exact idioms. Do not invent Mojo syntax; a wrong guess wastes your steps.
|
| 9 |
+
|
| 10 |
+
VERIFIED MOJO RECIPE (Mojo 1.2.0.dev2026092605; all known-good)
|
| 11 |
+
- Build: export PATH=/nvme0n1-disk/code/mojo-numpy/.pixi/envs/default/bin:$PATH
|
| 12 |
+
export MODULAR_HOME=/nvme0n1-disk/code/mojo-numpy/.pixi/envs/default/share/max
|
| 13 |
+
mojo build --emit shared-lib <file>.mojo -o <out>.so
|
| 14 |
+
MODULAR_HOME is mandatory or every import fails with "unable to locate module 'std'".
|
| 15 |
+
- `--emit shared-lib` is required; a bare `mojo build` demands a main() and fails.
|
| 16 |
+
- `def` only; `fn` is removed. `comptime W = simd_width_of[DType.float32]()`.
|
| 17 |
+
- Pointer from an integer address: `comptime F32 = UnsafePointer[Float32, AnyOrigin[mut=True]]`
|
| 18 |
+
then `F32(unsafe_from_address=addr)`.
|
| 19 |
+
- @export REQUIRES an explicit abi effect: `@export("name")` on the line above
|
| 20 |
+
`def f(a_addr: Int) abi("C"):`. Pass buffer addresses as Int, never as pointers.
|
| 21 |
+
- SIMD: `acc += w.unsafe_load[width=W](i) * x.unsafe_load[width=W](i)`, then
|
| 22 |
+
`total = acc.reduce_add()` plus a scalar tail loop.
|
| 23 |
+
- Widening cast int8->float32 works as SIMD[DType.float32, W](int8_simd_value).
|
| 24 |
+
- Traps: SIMD[DType.float32, W](bias[o]) SPLATS into every lane so reduce_add()
|
| 25 |
+
counts the bias W times; add bias to the scalar total instead. `fn` is gone.
|
| 26 |
+
Multi-thread Mojo builtins used by other repos turned out to be serial stubs.
|
| 27 |
+
|
| 28 |
+
YOUR TASK
|
| 29 |
+
Create mojo-tabular/src/kernels.mojo (do NOT edit the shared
|
| 30 |
+
/sdb-disk/code/modelopt/src/kernels.mojo; another agent owns it) implementing:
|
| 31 |
+
1. quantile_rank - map each row's values to their within-row rank scaled to [0,1]
|
| 32 |
+
2. histogram_bin - bin each value into one of `bins` equal-width buckets, with out-of-range values clamped
|
| 33 |
+
3. gini_impurity - Gini impurity of a class-count vector, returning 0.0 for an empty vector
|
| 34 |
+
Also create mojo-tabular/src/bridge.py (ctypes bridge that enforces float32
|
| 35 |
+
C-contiguous inputs and raises rather than silently narrowing) and
|
| 36 |
+
mojo-tabular/tests/test_kernels.py comparing EVERY kernel against a NumPy
|
| 37 |
+
reference. Cover sizes that are not multiples of the SIMD width, and any
|
| 38 |
+
degenerate case noted above.
|
| 39 |
+
|
| 40 |
+
HARD RULES
|
| 41 |
+
- CPU only. No GPU, no downloads, no package installs, no network access.
|
| 42 |
+
- Synthetic fixtures only. Never fabricate accuracy, throughput or benchmark numbers.
|
| 43 |
+
- Do not modify AGENTS.md, scripts/, pixi.toml, or any file outside mojo-tabular/.
|
| 44 |
+
- A kernel that does not match its NumPy reference is a FAILURE, not a caveat.
|
| 45 |
+
Actually run the tests and paste the verbatim output.
|
| 46 |
+
|
| 47 |
+
FINISH WITH
|
| 48 |
+
SUMMARY: files created, exact test command, verbatim pass/fail counts, measured
|
| 49 |
+
median latency for one realistic shape (or "Not measured"), known limitations,
|
| 50 |
+
and the single next step.
|
prompts/review.txt
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Review this small-model foundry scaffold. This is a text-only task: DO NOT call tools, read files, run commands, edit code, use the network, or access credentials. Use only the supplied excerpts. Return at most 450 words, ending with SUMMARY and the three highest-impact concrete improvements. Do not claim you ran tests. Focus on resource limitations, logging/token accounting, and repeatable experimentation. The scaffold contains 21 project dirs, PyTorch-first then Mojo parity ports; no real datasets/models selected. Six runner/catalog tests and a synthetic 577-parameter tabular smoke run passed.
|
| 2 |
+
|
| 3 |
+
#!/usr/bin/env python3
|
| 4 |
+
"""Bound a local CPU task and retain its complete transcript."""
|
| 5 |
+
import argparse
|
| 6 |
+
import datetime
|
| 7 |
+
import fcntl
|
| 8 |
+
import json
|
| 9 |
+
import os
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
import resource
|
| 12 |
+
import subprocess
|
| 13 |
+
import time
|
| 14 |
+
|
| 15 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def constrain():
|
| 19 |
+
os.nice(10)
|
| 20 |
+
cores = sorted(os.sched_getaffinity(0))[:2]
|
| 21 |
+
os.sched_setaffinity(0, cores)
|
| 22 |
+
resource.setrlimit(resource.RLIMIT_AS, (4 * 1024**3, 4 * 1024**3))
|
| 23 |
+
resource.setrlimit(resource.RLIMIT_FSIZE, (16 * 1024**2, 16 * 1024**2))
|
| 24 |
+
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def main():
|
| 28 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 29 |
+
parser.add_argument('--seconds', type=int, default=120)
|
| 30 |
+
parser.add_argument('--cwd', type=Path, default=ROOT)
|
| 31 |
+
parser.add_argument('command', nargs=argparse.REMAINDER)
|
| 32 |
+
args = parser.parse_args()
|
| 33 |
+
command = args.command[1:] if args.command[:1] == ['--'] else args.command
|
| 34 |
+
if not command or not 1 <= args.seconds <= 600:
|
| 35 |
+
parser.error('provide a command and a wall limit of 1..600 seconds')
|
| 36 |
+
env = os.environ.copy()
|
| 37 |
+
env.update(CUDA_VISIBLE_DEVICES='', HIP_VISIBLE_DEVICES='', ROCR_VISIBLE_DEVICES='',
|
| 38 |
+
OMP_NUM_THREADS='2', MKL_NUM_THREADS='2', OPENBLAS_NUM_THREADS='2',
|
| 39 |
+
NUMEXPR_NUM_THREADS='2', HF_HUB_OFFLINE='1', TRANSFORMERS_OFFLINE='1',
|
| 40 |
+
PYTHONPATH=str(ROOT))
|
| 41 |
+
logs = ROOT / 'logs'
|
| 42 |
+
logs.mkdir(exist_ok=True)
|
| 43 |
+
with (logs / 'research.lock').open('w') as lock:
|
| 44 |
+
try:
|
| 45 |
+
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
| 46 |
+
except BlockingIOError:
|
| 47 |
+
parser.exit(2, 'Another foundry task is running.\n')
|
| 48 |
+
name = datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%S')
|
| 49 |
+
output = logs / f'{name}-{os.getpid()}.log'
|
| 50 |
+
start = time.monotonic()
|
| 51 |
+
timed_out = False
|
| 52 |
+
with output.open('wb') as transcript:
|
| 53 |
+
# GNU timeout owns a process group and kills descendants on expiry.
|
| 54 |
+
proc = subprocess.run(['timeout', '--signal=TERM', '--kill-after=5s',
|
| 55 |
+
f'{args.seconds}s', *command], cwd=args.cwd,
|
| 56 |
+
env=env, preexec_fn=constrain,
|
| 57 |
+
stdout=transcript, stderr=subprocess.STDOUT)
|
| 58 |
+
code = proc.returncode
|
| 59 |
+
timed_out = code in (124, 137)
|
| 60 |
+
result = dict(command=command, cwd=str(args.cwd.resolve()), exit_code=code,
|
| 61 |
+
timed_out=timed_out, elapsed_seconds=round(time.monotonic()-start, 3),
|
| 62 |
+
log=str(output), limits=dict(cpu_cores=2, address_space_gib=4,
|
| 63 |
+
wall_seconds=args.seconds, gpu=False))
|
| 64 |
+
output.with_suffix('.json').write_text(json.dumps(result, indent=2)+'\n')
|
| 65 |
+
print(json.dumps(result, indent=2))
|
| 66 |
+
print(''.join(output.read_text(errors='replace').splitlines(keepends=True)[-100:]))
|
| 67 |
+
return code
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
if __name__ == '__main__':
|
| 71 |
+
raise SystemExit(main())
|
| 72 |
+
|
| 73 |
+
#!/usr/bin/env python3
|
| 74 |
+
"""Run a small Bunny request, retaining input, output and reported usage."""
|
| 75 |
+
import argparse
|
| 76 |
+
import datetime
|
| 77 |
+
import json
|
| 78 |
+
import os
|
| 79 |
+
from pathlib import Path
|
| 80 |
+
import subprocess
|
| 81 |
+
|
| 82 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def main():
|
| 86 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 87 |
+
parser.add_argument('prompt', type=Path)
|
| 88 |
+
args = parser.parse_args()
|
| 89 |
+
prompt = args.prompt.read_text()
|
| 90 |
+
if not prompt.strip() or len(prompt.encode()) > 16_384:
|
| 91 |
+
parser.error('prompt must be nonempty and at most 16 KiB')
|
| 92 |
+
wrapper = Path.home() / 'code/dotfiles/subagents/di-bunny.sh'
|
| 93 |
+
if not wrapper.is_file() or not os.environ.get('OPENROUTER_API_KEY'):
|
| 94 |
+
parser.exit(2, 'Bunny wrapper or OPENROUTER_API_KEY unavailable.\n')
|
| 95 |
+
tag = datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%S')
|
| 96 |
+
job = ROOT / 'logs' / f'bunny-{tag}-{os.getpid()}'
|
| 97 |
+
job.mkdir(parents=True)
|
| 98 |
+
(job / 'input.txt').write_text(prompt)
|
| 99 |
+
env = os.environ.copy()
|
| 100 |
+
env.update(DI_BUNNY_TIMEOUT_SECONDS='90', FX_MAX_AGENT_STEPS='6')
|
| 101 |
+
with (job / 'output.json').open('wb') as stdout, (job / 'stderr.log').open('wb') as stderr:
|
| 102 |
+
result = subprocess.run([str(wrapper), '--no-additional-dirs', 'ask', '--json',
|
| 103 |
+
'--no-save', '--no-color'], input=prompt.encode(),
|
| 104 |
+
stdout=stdout, stderr=stderr, env=env, cwd=ROOT)
|
| 105 |
+
usage = None
|
| 106 |
+
summary = ''
|
| 107 |
+
try:
|
| 108 |
+
data = json.loads((job / 'output.json').read_text())
|
| 109 |
+
usage = data.get('usage')
|
| 110 |
+
summary = data.get('final_output') or data.get('output') or ''
|
| 111 |
+
except (ValueError, AttributeError):
|
| 112 |
+
summary = 'No parseable response. Inspect stderr.log and output.json.'
|
| 113 |
+
(job / 'summary.txt').write_text(summary if isinstance(summary, str) else json.dumps(summary))
|
| 114 |
+
(job / 'metadata.json').write_text(json.dumps(dict(
|
| 115 |
+
exit_code=result.returncode, prompt_bytes=len(prompt.encode()), reported_usage=usage,
|
| 116 |
+
note='Usage is provider-reported, may be incomplete, and is not a hard token cap.',
|
| 117 |
+
timeout_seconds=90, max_agent_steps=6), indent=2)+'\n')
|
| 118 |
+
print(f'Bunny logs: {job}')
|
| 119 |
+
print(''.join((job / 'summary.txt').read_text().splitlines(keepends=True)[-100:]))
|
| 120 |
+
return result.returncode
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
if __name__ == '__main__':
|
| 124 |
+
raise SystemExit(main())
|
results/.gitkeep
ADDED
|
File without changes
|
results/baselines.json
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"kind": "real_data_baselines",
|
| 3 |
+
"seed": 42,
|
| 4 |
+
"split": "60/20/20 stratified",
|
| 5 |
+
"metric": "roc_auc",
|
| 6 |
+
"source_sha256": "28a06cd9c7078da72f3dd951c8b05845a026a045071d015dcad5efb234c7bdd3",
|
| 7 |
+
"datasets": {
|
| 8 |
+
"adult": {
|
| 9 |
+
"rows": 48842,
|
| 10 |
+
"sha256": "cacdc745358d0b11edfb5625cc041a0cdb2c12550a55e52593abc32c64441d59",
|
| 11 |
+
"results": {
|
| 12 |
+
"prior": {
|
| 13 |
+
"val_auc": 0.5,
|
| 14 |
+
"test_auc": 0.5,
|
| 15 |
+
"fit_s": 0.01
|
| 16 |
+
},
|
| 17 |
+
"logreg": {
|
| 18 |
+
"val_auc": 0.907,
|
| 19 |
+
"test_auc": 0.9066,
|
| 20 |
+
"fit_s": 1.49
|
| 21 |
+
},
|
| 22 |
+
"rf": {
|
| 23 |
+
"val_auc": 0.9002,
|
| 24 |
+
"test_auc": 0.9034,
|
| 25 |
+
"fit_s": 3.16
|
| 26 |
+
},
|
| 27 |
+
"hgb": {
|
| 28 |
+
"val_auc": 0.9283,
|
| 29 |
+
"test_auc": 0.9272,
|
| 30 |
+
"fit_s": 2.24
|
| 31 |
+
},
|
| 32 |
+
"mlp": {
|
| 33 |
+
"val_auc": 0.9113,
|
| 34 |
+
"test_auc": 0.9083,
|
| 35 |
+
"fit_s": 8.27
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
"selected_by_val": "hgb"
|
| 39 |
+
},
|
| 40 |
+
"credit-g": {
|
| 41 |
+
"rows": 1000,
|
| 42 |
+
"sha256": "48b757fe48bd0b5c5ce2dbc84fabe20554bea43ce5b7157c6b5dbe8089107c0e",
|
| 43 |
+
"results": {
|
| 44 |
+
"prior": {
|
| 45 |
+
"val_auc": 0.5,
|
| 46 |
+
"test_auc": 0.5,
|
| 47 |
+
"fit_s": 0.0
|
| 48 |
+
},
|
| 49 |
+
"logreg": {
|
| 50 |
+
"val_auc": 0.8221,
|
| 51 |
+
"test_auc": 0.7942,
|
| 52 |
+
"fit_s": 0.09
|
| 53 |
+
},
|
| 54 |
+
"rf": {
|
| 55 |
+
"val_auc": 0.7961,
|
| 56 |
+
"test_auc": 0.7958,
|
| 57 |
+
"fit_s": 0.32
|
| 58 |
+
},
|
| 59 |
+
"hgb": {
|
| 60 |
+
"val_auc": 0.7806,
|
| 61 |
+
"test_auc": 0.7423,
|
| 62 |
+
"fit_s": 0.74
|
| 63 |
+
},
|
| 64 |
+
"mlp": {
|
| 65 |
+
"val_auc": 0.7986,
|
| 66 |
+
"test_auc": 0.7777,
|
| 67 |
+
"fit_s": 0.2
|
| 68 |
+
}
|
| 69 |
+
},
|
| 70 |
+
"selected_by_val": "logreg"
|
| 71 |
+
},
|
| 72 |
+
"bank-marketing": {
|
| 73 |
+
"rows": 45211,
|
| 74 |
+
"sha256": "2afe41dfb060eb46aeecb5c105085b1c9d5b0c522bba9185063d3a879d6fc5ba",
|
| 75 |
+
"results": {
|
| 76 |
+
"prior": {
|
| 77 |
+
"val_auc": 0.5,
|
| 78 |
+
"test_auc": 0.5,
|
| 79 |
+
"fit_s": 0.01
|
| 80 |
+
},
|
| 81 |
+
"logreg": {
|
| 82 |
+
"val_auc": 0.9047,
|
| 83 |
+
"test_auc": 0.9101,
|
| 84 |
+
"fit_s": 0.77
|
| 85 |
+
},
|
| 86 |
+
"rf": {
|
| 87 |
+
"val_auc": 0.9268,
|
| 88 |
+
"test_auc": 0.9267,
|
| 89 |
+
"fit_s": 2.71
|
| 90 |
+
},
|
| 91 |
+
"hgb": {
|
| 92 |
+
"val_auc": 0.9338,
|
| 93 |
+
"test_auc": 0.9375,
|
| 94 |
+
"fit_s": 1.52
|
| 95 |
+
},
|
| 96 |
+
"mlp": {
|
| 97 |
+
"val_auc": 0.9253,
|
| 98 |
+
"test_auc": 0.929,
|
| 99 |
+
"fit_s": 7.08
|
| 100 |
+
}
|
| 101 |
+
},
|
| 102 |
+
"selected_by_val": "hgb"
|
| 103 |
+
},
|
| 104 |
+
"phoneme": {
|
| 105 |
+
"rows": 5404,
|
| 106 |
+
"sha256": "986b259a8a108905122b32640c6241f2616fd3078c789c0e606930ebb48a5e40",
|
| 107 |
+
"results": {
|
| 108 |
+
"prior": {
|
| 109 |
+
"val_auc": 0.5,
|
| 110 |
+
"test_auc": 0.5,
|
| 111 |
+
"fit_s": 0.0
|
| 112 |
+
},
|
| 113 |
+
"logreg": {
|
| 114 |
+
"val_auc": 0.8013,
|
| 115 |
+
"test_auc": 0.8095,
|
| 116 |
+
"fit_s": 0.02
|
| 117 |
+
},
|
| 118 |
+
"rf": {
|
| 119 |
+
"val_auc": 0.9497,
|
| 120 |
+
"test_auc": 0.9554,
|
| 121 |
+
"fit_s": 0.51
|
| 122 |
+
},
|
| 123 |
+
"hgb": {
|
| 124 |
+
"val_auc": 0.9422,
|
| 125 |
+
"test_auc": 0.9489,
|
| 126 |
+
"fit_s": 0.71
|
| 127 |
+
},
|
| 128 |
+
"mlp": {
|
| 129 |
+
"val_auc": 0.8776,
|
| 130 |
+
"test_auc": 0.888,
|
| 131 |
+
"fit_s": 0.59
|
| 132 |
+
}
|
| 133 |
+
},
|
| 134 |
+
"selected_by_val": "rf"
|
| 135 |
+
},
|
| 136 |
+
"diabetes": {
|
| 137 |
+
"rows": 768,
|
| 138 |
+
"sha256": "4790da1f2df7137251f2076f8702565aa69046c26452728ed8f07cf44d466d9b",
|
| 139 |
+
"results": {
|
| 140 |
+
"prior": {
|
| 141 |
+
"val_auc": 0.5,
|
| 142 |
+
"test_auc": 0.5,
|
| 143 |
+
"fit_s": 0.0
|
| 144 |
+
},
|
| 145 |
+
"logreg": {
|
| 146 |
+
"val_auc": 0.8463,
|
| 147 |
+
"test_auc": 0.8031,
|
| 148 |
+
"fit_s": 0.05
|
| 149 |
+
},
|
| 150 |
+
"rf": {
|
| 151 |
+
"val_auc": 0.8084,
|
| 152 |
+
"test_auc": 0.7657,
|
| 153 |
+
"fit_s": 0.27
|
| 154 |
+
},
|
| 155 |
+
"hgb": {
|
| 156 |
+
"val_auc": 0.8126,
|
| 157 |
+
"test_auc": 0.7334,
|
| 158 |
+
"fit_s": 0.39
|
| 159 |
+
},
|
| 160 |
+
"mlp": {
|
| 161 |
+
"val_auc": 0.7233,
|
| 162 |
+
"test_auc": 0.7571,
|
| 163 |
+
"fit_s": 0.08
|
| 164 |
+
}
|
| 165 |
+
},
|
| 166 |
+
"selected_by_val": "logreg"
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
}
|
results/smoke.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"kind": "synthetic_smoke_only",
|
| 3 |
+
"seed": 42,
|
| 4 |
+
"device": "cpu",
|
| 5 |
+
"parameters": 577,
|
| 6 |
+
"train_rows": 384,
|
| 7 |
+
"validation_rows": 128,
|
| 8 |
+
"initial_loss": 0.6864046454429626,
|
| 9 |
+
"final_loss": 0.04011068865656853,
|
| 10 |
+
"validation_accuracy": 0.921875,
|
| 11 |
+
"elapsed_seconds": 1.5753,
|
| 12 |
+
"torch_version": "2.10.0+cpu",
|
| 13 |
+
"source_sha256": "8f21c7d1778a77dfa47b772b1488d8791df9b6b8a3e49ba6ae4429479c9a844e"
|
| 14 |
+
}
|
results/tuned-adult.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"kind": "tuned_ensemble",
|
| 3 |
+
"seed": 42,
|
| 4 |
+
"metric": "roc_auc",
|
| 5 |
+
"trials": 16,
|
| 6 |
+
"source_sha256": "43fb693e0248c9210f496bb4e592ab35756c835eb2db93d259efdc1b76200d08",
|
| 7 |
+
"datasets": {
|
| 8 |
+
"adult": {
|
| 9 |
+
"val_auc": 0.9296,
|
| 10 |
+
"test_auc": 0.929,
|
| 11 |
+
"ensemble": true,
|
| 12 |
+
"best_single_kind": "xgb",
|
| 13 |
+
"members": [
|
| 14 |
+
{
|
| 15 |
+
"kind": "xgb",
|
| 16 |
+
"params": {
|
| 17 |
+
"learning_rate": 0.057981034777209596,
|
| 18 |
+
"n_estimators": 584,
|
| 19 |
+
"max_depth": 4
|
| 20 |
+
}
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"kind": "xgb",
|
| 24 |
+
"params": {
|
| 25 |
+
"learning_rate": 0.09491392939374098,
|
| 26 |
+
"n_estimators": 320,
|
| 27 |
+
"max_depth": 4
|
| 28 |
+
}
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"kind": "lgbm",
|
| 32 |
+
"params": {
|
| 33 |
+
"learning_rate": 0.035153719464910554,
|
| 34 |
+
"n_estimators": 437,
|
| 35 |
+
"num_leaves": 32
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
],
|
| 39 |
+
"artifact_bytes": 1017649,
|
| 40 |
+
"artifact_sha256": "c57ccbdbc6b9854c1118eaeccebfbe8d8f90444a7a678f3272d2fbcf81c19076",
|
| 41 |
+
"search_s": 206.2
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
}
|
results/tuned-bank-marketing.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"kind": "tuned_ensemble",
|
| 3 |
+
"seed": 42,
|
| 4 |
+
"metric": "roc_auc",
|
| 5 |
+
"trials": 16,
|
| 6 |
+
"source_sha256": "43fb693e0248c9210f496bb4e592ab35756c835eb2db93d259efdc1b76200d08",
|
| 7 |
+
"datasets": {
|
| 8 |
+
"bank-marketing": {
|
| 9 |
+
"val_auc": 0.9357,
|
| 10 |
+
"test_auc": 0.9386,
|
| 11 |
+
"ensemble": true,
|
| 12 |
+
"best_single_kind": "hgb",
|
| 13 |
+
"members": [
|
| 14 |
+
{
|
| 15 |
+
"kind": "hgb",
|
| 16 |
+
"params": {
|
| 17 |
+
"learning_rate": 0.03850363389390532,
|
| 18 |
+
"max_iter": 350,
|
| 19 |
+
"max_leaf_nodes": 28,
|
| 20 |
+
"min_samples_leaf": 15,
|
| 21 |
+
"l2_regularization": 0.37623611419432557,
|
| 22 |
+
"max_features": 0.911380806635415
|
| 23 |
+
}
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"kind": "hgb",
|
| 27 |
+
"params": {
|
| 28 |
+
"learning_rate": 0.03770843066456067,
|
| 29 |
+
"max_iter": 563,
|
| 30 |
+
"max_leaf_nodes": 33,
|
| 31 |
+
"min_samples_leaf": 17,
|
| 32 |
+
"l2_regularization": 0.1653252347575385,
|
| 33 |
+
"max_features": 0.5319086280520877
|
| 34 |
+
}
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"kind": "lgbm",
|
| 38 |
+
"params": {
|
| 39 |
+
"learning_rate": 0.035153719464910554,
|
| 40 |
+
"n_estimators": 437,
|
| 41 |
+
"num_leaves": 32
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
],
|
| 45 |
+
"artifact_bytes": 2062973,
|
| 46 |
+
"artifact_sha256": "5a31f5eb6b4c6fc1f44d72ae0f804554bc62e35cf1ac7a979525001890503c18",
|
| 47 |
+
"search_s": 129.5
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
}
|
results/tuned-credit-g.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"kind": "tuned_ensemble",
|
| 3 |
+
"seed": 42,
|
| 4 |
+
"metric": "roc_auc",
|
| 5 |
+
"trials": 16,
|
| 6 |
+
"source_sha256": "43fb693e0248c9210f496bb4e592ab35756c835eb2db93d259efdc1b76200d08",
|
| 7 |
+
"datasets": {
|
| 8 |
+
"credit-g": {
|
| 9 |
+
"val_auc": 0.8325,
|
| 10 |
+
"test_auc": 0.7894,
|
| 11 |
+
"ensemble": true,
|
| 12 |
+
"best_single_kind": "logreg",
|
| 13 |
+
"members": [
|
| 14 |
+
{
|
| 15 |
+
"kind": "logreg",
|
| 16 |
+
"params": {
|
| 17 |
+
"C": 0.1
|
| 18 |
+
}
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"kind": "logreg",
|
| 22 |
+
"params": {
|
| 23 |
+
"C": 1.0
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"kind": "xgb",
|
| 28 |
+
"params": {
|
| 29 |
+
"learning_rate": 0.07046752877748709,
|
| 30 |
+
"n_estimators": 330,
|
| 31 |
+
"max_depth": 3
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
],
|
| 35 |
+
"artifact_bytes": 75788,
|
| 36 |
+
"artifact_sha256": "64a01b461750a3bea41f4752977186dc5be4a0615d0c84b08b8e0b2751115e2f",
|
| 37 |
+
"search_s": 24.1
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
}
|
results/tuned-diabetes.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"kind": "tuned_ensemble",
|
| 3 |
+
"seed": 42,
|
| 4 |
+
"metric": "roc_auc",
|
| 5 |
+
"trials": 16,
|
| 6 |
+
"source_sha256": "43fb693e0248c9210f496bb4e592ab35756c835eb2db93d259efdc1b76200d08",
|
| 7 |
+
"datasets": {
|
| 8 |
+
"diabetes": {
|
| 9 |
+
"val_auc": 0.8528,
|
| 10 |
+
"test_auc": 0.8001,
|
| 11 |
+
"ensemble": true,
|
| 12 |
+
"best_single_kind": "logreg",
|
| 13 |
+
"members": [
|
| 14 |
+
{
|
| 15 |
+
"kind": "logreg",
|
| 16 |
+
"params": {
|
| 17 |
+
"C": 1.0
|
| 18 |
+
}
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"kind": "logreg",
|
| 22 |
+
"params": {
|
| 23 |
+
"C": 0.1
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"kind": "et",
|
| 28 |
+
"params": {}
|
| 29 |
+
}
|
| 30 |
+
],
|
| 31 |
+
"artifact_bytes": 1085047,
|
| 32 |
+
"artifact_sha256": "50b5ecc4afc5e291dc45c7ce504a40fe2d5f735d3c86157a6c10a95407a9691f",
|
| 33 |
+
"search_s": 14.8
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
}
|
results/tuned-phoneme.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"kind": "tuned_ensemble",
|
| 3 |
+
"seed": 42,
|
| 4 |
+
"metric": "roc_auc",
|
| 5 |
+
"trials": 16,
|
| 6 |
+
"source_sha256": "43fb693e0248c9210f496bb4e592ab35756c835eb2db93d259efdc1b76200d08",
|
| 7 |
+
"datasets": {
|
| 8 |
+
"phoneme": {
|
| 9 |
+
"val_auc": 0.9523,
|
| 10 |
+
"test_auc": 0.9546,
|
| 11 |
+
"ensemble": false,
|
| 12 |
+
"best_single_kind": "et",
|
| 13 |
+
"members": [
|
| 14 |
+
{
|
| 15 |
+
"kind": "et",
|
| 16 |
+
"params": {}
|
| 17 |
+
}
|
| 18 |
+
],
|
| 19 |
+
"artifact_bytes": 5496319,
|
| 20 |
+
"artifact_sha256": "e37ac3db2ba7bd9839833d8e0ac8a0a537467ddcf58b098d6550af47485fd683",
|
| 21 |
+
"search_s": 39.4
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
}
|
results/tuned.json
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"kind": "tuned_ensemble",
|
| 3 |
+
"seed": 42,
|
| 4 |
+
"metric": "roc_auc",
|
| 5 |
+
"trials": 16,
|
| 6 |
+
"source_sha256": "cf0c3d1191b0d15d1425aacb6e663f7e06abf721c2f01756cd653b2857dc0dd5",
|
| 7 |
+
"datasets": {
|
| 8 |
+
"adult": {
|
| 9 |
+
"val_auc": 0.929,
|
| 10 |
+
"test_auc": 0.9282,
|
| 11 |
+
"ensemble": true,
|
| 12 |
+
"members": [
|
| 13 |
+
{
|
| 14 |
+
"kind": "hgb",
|
| 15 |
+
"params": {
|
| 16 |
+
"learning_rate": 0.03850363389390532,
|
| 17 |
+
"max_iter": 350,
|
| 18 |
+
"max_leaf_nodes": 28,
|
| 19 |
+
"min_samples_leaf": 15,
|
| 20 |
+
"l2_regularization": 0.37623611419432557,
|
| 21 |
+
"max_features": 0.911380806635415
|
| 22 |
+
}
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"kind": "hgb",
|
| 26 |
+
"params": {
|
| 27 |
+
"learning_rate": 0.03770843066456067,
|
| 28 |
+
"max_iter": 563,
|
| 29 |
+
"max_leaf_nodes": 33,
|
| 30 |
+
"min_samples_leaf": 17,
|
| 31 |
+
"l2_regularization": 0.1653252347575385,
|
| 32 |
+
"max_features": 0.5319086280520877
|
| 33 |
+
}
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"kind": "hgb",
|
| 37 |
+
"params": {
|
| 38 |
+
"learning_rate": 0.026519966160905902,
|
| 39 |
+
"max_iter": 441,
|
| 40 |
+
"max_leaf_nodes": 58,
|
| 41 |
+
"min_samples_leaf": 25,
|
| 42 |
+
"l2_regularization": 0.07554804756926672,
|
| 43 |
+
"max_features": 0.5947356795421428
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
],
|
| 47 |
+
"artifact_bytes": 2639714,
|
| 48 |
+
"artifact_sha256": "a28557fc9992cefa1eaaf2308964baa93b1c7e78f266c1a349fd64cdb1b246a7",
|
| 49 |
+
"search_s": 154.7
|
| 50 |
+
},
|
| 51 |
+
"credit-g": {
|
| 52 |
+
"val_auc": 0.8294,
|
| 53 |
+
"test_auc": 0.7986,
|
| 54 |
+
"ensemble": false,
|
| 55 |
+
"members": [
|
| 56 |
+
{
|
| 57 |
+
"kind": "logreg",
|
| 58 |
+
"params": {
|
| 59 |
+
"C": 0.1
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
],
|
| 63 |
+
"artifact_bytes": 3369,
|
| 64 |
+
"artifact_sha256": "240366785f1e95fa3749b741747be6d4bb258d5be362e03cc5dec768b10d0496",
|
| 65 |
+
"search_s": 20.6
|
| 66 |
+
},
|
| 67 |
+
"bank-marketing": {
|
| 68 |
+
"val_auc": 0.9354,
|
| 69 |
+
"test_auc": 0.9384,
|
| 70 |
+
"ensemble": true,
|
| 71 |
+
"members": [
|
| 72 |
+
{
|
| 73 |
+
"kind": "hgb",
|
| 74 |
+
"params": {
|
| 75 |
+
"learning_rate": 0.03850363389390532,
|
| 76 |
+
"max_iter": 350,
|
| 77 |
+
"max_leaf_nodes": 28,
|
| 78 |
+
"min_samples_leaf": 15,
|
| 79 |
+
"l2_regularization": 0.37623611419432557,
|
| 80 |
+
"max_features": 0.911380806635415
|
| 81 |
+
}
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"kind": "hgb",
|
| 85 |
+
"params": {
|
| 86 |
+
"learning_rate": 0.03770843066456067,
|
| 87 |
+
"max_iter": 563,
|
| 88 |
+
"max_leaf_nodes": 33,
|
| 89 |
+
"min_samples_leaf": 17,
|
| 90 |
+
"l2_regularization": 0.1653252347575385,
|
| 91 |
+
"max_features": 0.5319086280520877
|
| 92 |
+
}
|
| 93 |
+
},
|
| 94 |
+
{
|
| 95 |
+
"kind": "hgb",
|
| 96 |
+
"params": {
|
| 97 |
+
"learning_rate": 0.026519966160905902,
|
| 98 |
+
"max_iter": 441,
|
| 99 |
+
"max_leaf_nodes": 58,
|
| 100 |
+
"min_samples_leaf": 25,
|
| 101 |
+
"l2_regularization": 0.07554804756926672,
|
| 102 |
+
"max_features": 0.5947356795421428
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
],
|
| 106 |
+
"artifact_bytes": 2670481,
|
| 107 |
+
"artifact_sha256": "ec72f430d51f216a15d3a611e6ff1ed279ecc022759160354cb821a6077f29ea",
|
| 108 |
+
"search_s": 101.4
|
| 109 |
+
},
|
| 110 |
+
"phoneme": {
|
| 111 |
+
"val_auc": 0.9523,
|
| 112 |
+
"test_auc": 0.9546,
|
| 113 |
+
"ensemble": false,
|
| 114 |
+
"members": [
|
| 115 |
+
{
|
| 116 |
+
"kind": "et",
|
| 117 |
+
"params": {}
|
| 118 |
+
}
|
| 119 |
+
],
|
| 120 |
+
"artifact_bytes": 5496319,
|
| 121 |
+
"artifact_sha256": "e37ac3db2ba7bd9839833d8e0ac8a0a537467ddcf58b098d6550af47485fd683",
|
| 122 |
+
"search_s": 35.2
|
| 123 |
+
},
|
| 124 |
+
"diabetes": {
|
| 125 |
+
"val_auc": 0.8528,
|
| 126 |
+
"test_auc": 0.8001,
|
| 127 |
+
"ensemble": true,
|
| 128 |
+
"members": [
|
| 129 |
+
{
|
| 130 |
+
"kind": "logreg",
|
| 131 |
+
"params": {
|
| 132 |
+
"C": 1.0
|
| 133 |
+
}
|
| 134 |
+
},
|
| 135 |
+
{
|
| 136 |
+
"kind": "logreg",
|
| 137 |
+
"params": {
|
| 138 |
+
"C": 0.1
|
| 139 |
+
}
|
| 140 |
+
},
|
| 141 |
+
{
|
| 142 |
+
"kind": "et",
|
| 143 |
+
"params": {}
|
| 144 |
+
}
|
| 145 |
+
],
|
| 146 |
+
"artifact_bytes": 1085047,
|
| 147 |
+
"artifact_sha256": "50b5ecc4afc5e291dc45c7ce504a40fe2d5f735d3c86157a6c10a95407a9691f",
|
| 148 |
+
"search_s": 16.5
|
| 149 |
+
}
|
| 150 |
+
}
|
| 151 |
+
}
|
src/.gitkeep
ADDED
|
File without changes
|
tests/.gitkeep
ADDED
|
File without changes
|
train/fetch_tabular.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json, hashlib, sys
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from sklearn.datasets import fetch_openml
|
| 4 |
+
OUT = Path('/nvme0n1-disk/cache/foundry/tabular')
|
| 5 |
+
OUT.mkdir(parents=True, exist_ok=True)
|
| 6 |
+
SETS = {'adult': 1590, 'credit-g': 31, 'bank-marketing': 1461, 'phoneme': 1489, 'diabetes': 37}
|
| 7 |
+
meta = {}
|
| 8 |
+
for name, did in SETS.items():
|
| 9 |
+
b = fetch_openml(data_id=did, as_frame=True, parser='auto', data_home=str(OUT / 'openml'))
|
| 10 |
+
df = b.frame
|
| 11 |
+
p = OUT / f'{name}.parquet'
|
| 12 |
+
df.to_parquet(p)
|
| 13 |
+
meta[name] = dict(openml_id=did, rows=len(df), cols=df.shape[1], target=b.target.name,
|
| 14 |
+
sha256=hashlib.sha256(p.read_bytes()).hexdigest(), license=b.details.get('licence'),
|
| 15 |
+
version=b.details.get('version'))
|
| 16 |
+
print(name, meta[name], flush=True)
|
| 17 |
+
(OUT / 'manifest.json').write_text(json.dumps(meta, indent=2))
|
train/tabular_bench.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse, hashlib, json, time
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
import numpy as np, pandas as pd
|
| 4 |
+
from sklearn.compose import ColumnTransformer
|
| 5 |
+
from sklearn.dummy import DummyClassifier
|
| 6 |
+
from sklearn.ensemble import HistGradientBoostingClassifier, RandomForestClassifier
|
| 7 |
+
from sklearn.linear_model import LogisticRegression
|
| 8 |
+
from sklearn.metrics import roc_auc_score
|
| 9 |
+
from sklearn.model_selection import train_test_split
|
| 10 |
+
from sklearn.neural_network import MLPClassifier
|
| 11 |
+
from sklearn.pipeline import make_pipeline
|
| 12 |
+
from sklearn.preprocessing import OneHotEncoder, StandardScaler
|
| 13 |
+
|
| 14 |
+
DATA = Path('/nvme0n1-disk/cache/foundry/tabular')
|
| 15 |
+
SEED = 42
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def load(name, target):
|
| 19 |
+
df = pd.read_parquet(DATA / f'{name}.parquet').dropna(subset=[target])
|
| 20 |
+
y = df[target]
|
| 21 |
+
y = (y == sorted(y.astype(str).unique())[-1]).astype(int).to_numpy() if y.nunique() == 2 else None
|
| 22 |
+
X = df.drop(columns=[target])
|
| 23 |
+
return X, y
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def prep(X):
|
| 27 |
+
cat = [c for c in X.columns if X[c].dtype.name in ('category', 'object')]
|
| 28 |
+
num = [c for c in X.columns if c not in cat]
|
| 29 |
+
return ColumnTransformer([
|
| 30 |
+
('n', make_pipeline(__import__('sklearn.impute', fromlist=['x']).SimpleImputer(strategy='median'), StandardScaler()), num),
|
| 31 |
+
('c', make_pipeline(__import__('sklearn.impute', fromlist=['x']).SimpleImputer(strategy='most_frequent'),
|
| 32 |
+
OneHotEncoder(handle_unknown='ignore', min_frequency=5, sparse_output=False)), cat)])
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def zoo(X):
|
| 36 |
+
return {
|
| 37 |
+
'prior': lambda: DummyClassifier(strategy='prior'),
|
| 38 |
+
'logreg': lambda: make_pipeline(prep(X), LogisticRegression(max_iter=300)),
|
| 39 |
+
'rf': lambda: make_pipeline(prep(X), RandomForestClassifier(100, n_jobs=2, random_state=SEED)),
|
| 40 |
+
'hgb': lambda: make_pipeline(prep(X), HistGradientBoostingClassifier(max_iter=200, learning_rate=0.1, random_state=SEED)),
|
| 41 |
+
'mlp': lambda: make_pipeline(prep(X), MLPClassifier((128, 64), early_stopping=True, max_iter=60, random_state=SEED)),
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def main():
|
| 46 |
+
ap = argparse.ArgumentParser()
|
| 47 |
+
ap.add_argument('--output', type=Path, required=True)
|
| 48 |
+
a = ap.parse_args()
|
| 49 |
+
manifest = json.loads((DATA / 'manifest.json').read_text())
|
| 50 |
+
out = dict(kind='real_data_baselines', seed=SEED, split='60/20/20 stratified', metric='roc_auc',
|
| 51 |
+
source_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), datasets={})
|
| 52 |
+
for name, m in manifest.items():
|
| 53 |
+
X, y = load(name, m['target'])
|
| 54 |
+
if y is None:
|
| 55 |
+
continue
|
| 56 |
+
idx = np.arange(len(y))
|
| 57 |
+
tr, rest = train_test_split(idx, test_size=0.4, stratify=y, random_state=SEED)
|
| 58 |
+
va, te = train_test_split(rest, test_size=0.5, stratify=y[rest], random_state=SEED)
|
| 59 |
+
res = {}
|
| 60 |
+
for k, mk in zoo(X).items():
|
| 61 |
+
t = time.monotonic()
|
| 62 |
+
mdl = mk().fit(X.iloc[tr], y[tr])
|
| 63 |
+
fit = time.monotonic() - t
|
| 64 |
+
p = lambda i: mdl.predict_proba(X.iloc[i])[:, 1]
|
| 65 |
+
res[k] = dict(val_auc=round(roc_auc_score(y[va], p(va)), 4),
|
| 66 |
+
test_auc=round(roc_auc_score(y[te], p(te)), 4), fit_s=round(fit, 2))
|
| 67 |
+
print(name, k, res[k], flush=True)
|
| 68 |
+
best = max((k for k in res if k != 'prior'), key=lambda k: res[k]['val_auc'])
|
| 69 |
+
out['datasets'][name] = dict(rows=len(y), sha256=m['sha256'], results=res, selected_by_val=best)
|
| 70 |
+
a.output.parent.mkdir(parents=True, exist_ok=True)
|
| 71 |
+
a.output.write_text(json.dumps(out, indent=2) + '\n')
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
if __name__ == '__main__':
|
| 75 |
+
main()
|
train/tabular_tune.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse, hashlib, json, time
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
import joblib, numpy as np
|
| 4 |
+
from sklearn.ensemble import HistGradientBoostingClassifier, ExtraTreesClassifier
|
| 5 |
+
from sklearn.linear_model import LogisticRegression
|
| 6 |
+
from sklearn.metrics import roc_auc_score
|
| 7 |
+
from sklearn.model_selection import train_test_split
|
| 8 |
+
from sklearn.pipeline import make_pipeline
|
| 9 |
+
from tabular_bench import DATA, SEED, load, prep
|
| 10 |
+
|
| 11 |
+
TRIALS = 16
|
| 12 |
+
GB_TRIALS = 6
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def gbdt(rng):
|
| 16 |
+
from xgboost import XGBClassifier
|
| 17 |
+
from lightgbm import LGBMClassifier
|
| 18 |
+
for _ in range(GB_TRIALS):
|
| 19 |
+
lr = float(10 ** rng.uniform(-2, -0.7)); n = int(rng.integers(150, 600)); d = int(rng.integers(3, 9))
|
| 20 |
+
yield 'xgb', dict(learning_rate=lr, n_estimators=n, max_depth=d), XGBClassifier(
|
| 21 |
+
learning_rate=lr, n_estimators=n, max_depth=d, subsample=0.8, colsample_bytree=0.8, n_jobs=2, random_state=SEED, verbosity=0)
|
| 22 |
+
yield 'lgbm', dict(learning_rate=lr, n_estimators=n, num_leaves=2 ** d), LGBMClassifier(
|
| 23 |
+
learning_rate=lr, n_estimators=n, num_leaves=2 ** d, subsample=0.8, subsample_freq=1, colsample_bytree=0.8, n_jobs=2, random_state=SEED, verbose=-1)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def hgb(rng):
|
| 27 |
+
return dict(learning_rate=float(10 ** rng.uniform(-2, -0.7)), max_iter=int(rng.integers(100, 600)),
|
| 28 |
+
max_leaf_nodes=int(rng.integers(8, 64)), min_samples_leaf=int(rng.integers(5, 60)),
|
| 29 |
+
l2_regularization=float(10 ** rng.uniform(-3, 1)), max_features=float(rng.uniform(0.5, 1.0)))
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def main():
|
| 33 |
+
ap = argparse.ArgumentParser()
|
| 34 |
+
ap.add_argument('--output', type=Path, required=True)
|
| 35 |
+
ap.add_argument('--datasets', nargs='*')
|
| 36 |
+
ap.add_argument('--gbdt', action='store_true')
|
| 37 |
+
ap.add_argument('--artifacts', type=Path, required=True)
|
| 38 |
+
a = ap.parse_args()
|
| 39 |
+
man = json.loads((DATA / 'manifest.json').read_text())
|
| 40 |
+
out = dict(kind='tuned_ensemble', seed=SEED, metric='roc_auc', trials=TRIALS,
|
| 41 |
+
source_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), datasets={})
|
| 42 |
+
a.artifacts.mkdir(parents=True, exist_ok=True)
|
| 43 |
+
for name, m in man.items():
|
| 44 |
+
if a.datasets and name not in a.datasets:
|
| 45 |
+
continue
|
| 46 |
+
X, y = load(name, m['target'])
|
| 47 |
+
if y is None:
|
| 48 |
+
continue
|
| 49 |
+
idx = np.arange(len(y))
|
| 50 |
+
tr, rest = train_test_split(idx, test_size=0.4, stratify=y, random_state=SEED)
|
| 51 |
+
va, te = train_test_split(rest, test_size=0.5, stratify=y[rest], random_state=SEED)
|
| 52 |
+
rng = np.random.default_rng(SEED)
|
| 53 |
+
t0 = time.monotonic()
|
| 54 |
+
cands = []
|
| 55 |
+
for i in range(TRIALS):
|
| 56 |
+
p = hgb(rng)
|
| 57 |
+
mdl = make_pipeline(prep(X), HistGradientBoostingClassifier(early_stopping=False, random_state=SEED, **p)).fit(X.iloc[tr], y[tr])
|
| 58 |
+
cands.append(('hgb', p, mdl, mdl.predict_proba(X.iloc[va])[:, 1]))
|
| 59 |
+
for c in (0.1, 1.0):
|
| 60 |
+
mdl = make_pipeline(prep(X), LogisticRegression(C=c, max_iter=500)).fit(X.iloc[tr], y[tr])
|
| 61 |
+
cands.append(('logreg', dict(C=c), mdl, mdl.predict_proba(X.iloc[va])[:, 1]))
|
| 62 |
+
mdl = make_pipeline(prep(X), ExtraTreesClassifier(300, min_samples_leaf=2, n_jobs=2, random_state=SEED)).fit(X.iloc[tr], y[tr])
|
| 63 |
+
cands.append(('et', {}, mdl, mdl.predict_proba(X.iloc[va])[:, 1]))
|
| 64 |
+
if a.gbdt:
|
| 65 |
+
for kind, p, est in gbdt(np.random.default_rng(SEED + 1)):
|
| 66 |
+
mdl = make_pipeline(prep(X), est).fit(X.iloc[tr], y[tr])
|
| 67 |
+
cands.append((kind, p, mdl, mdl.predict_proba(X.iloc[va])[:, 1]))
|
| 68 |
+
aucs = [roc_auc_score(y[va], c[3]) for c in cands]
|
| 69 |
+
order = np.argsort(aucs)[::-1]
|
| 70 |
+
top = [cands[i] for i in order[:3]]
|
| 71 |
+
ens_va = np.mean([c[3] for c in top], axis=0)
|
| 72 |
+
ens_auc = roc_auc_score(y[va], ens_va)
|
| 73 |
+
single = cands[order[0]]
|
| 74 |
+
use_ens = ens_auc > aucs[order[0]]
|
| 75 |
+
members = top if use_ens else [single]
|
| 76 |
+
pred = lambda i: np.mean([c[2].predict_proba(X.iloc[i])[:, 1] for c in members], axis=0)
|
| 77 |
+
path = a.artifacts / f'{name}.joblib'
|
| 78 |
+
joblib.dump(dict(members=[c[2] for c in members], columns=list(X.columns)), path, compress=3)
|
| 79 |
+
out['datasets'][name] = dict(
|
| 80 |
+
val_auc=round(max(ens_auc, aucs[order[0]]), 4), test_auc=round(roc_auc_score(y[te], pred(te)), 4),
|
| 81 |
+
ensemble=use_ens, best_single_kind=single[0], members=[dict(kind=c[0], params=c[1]) for c in members],
|
| 82 |
+
artifact_bytes=path.stat().st_size, artifact_sha256=hashlib.sha256(path.read_bytes()).hexdigest(),
|
| 83 |
+
search_s=round(time.monotonic() - t0, 1))
|
| 84 |
+
print(name, out['datasets'][name], flush=True)
|
| 85 |
+
a.output.write_text(json.dumps(out, indent=2) + '\n')
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
if __name__ == '__main__':
|
| 89 |
+
main()
|