Publish the Covertype XGBoost classifier
Browse filesReplaces the breast-cancer demo with a 400-tree model trained on all 581,012 UCI Covertype rows. Test accuracy 0.9109 and macro F1 0.8942, scored once against a majority baseline and a small XGBoost baseline.
- .gitattributes +1 -0
- README.md +50 -46
- metrics.json +285 -53
- model.json +0 -0
- train.py +188 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
model.json filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -7,57 +7,72 @@ tags:
|
|
| 7 |
- tabular
|
| 8 |
- bleu-js
|
| 9 |
datasets:
|
| 10 |
-
-
|
| 11 |
metrics:
|
| 12 |
- accuracy
|
| 13 |
- f1
|
| 14 |
-
- roc_auc
|
| 15 |
model-index:
|
| 16 |
- name: bleu-xgboost-classifier
|
| 17 |
results:
|
| 18 |
- task:
|
| 19 |
-
type:
|
| 20 |
dataset:
|
| 21 |
-
name:
|
| 22 |
-
type:
|
| 23 |
metrics:
|
| 24 |
- type: accuracy
|
| 25 |
-
value: 0.
|
| 26 |
-
- type:
|
| 27 |
-
value: 0.
|
| 28 |
-
- type:
|
| 29 |
-
value: 0.
|
| 30 |
---
|
| 31 |
|
| 32 |
# bleu-xgboost-classifier
|
| 33 |
|
| 34 |
-
XGBoost
|
| 35 |
|
| 36 |
-
This is
|
| 37 |
|
| 38 |
-
##
|
| 39 |
|
| 40 |
-
|
| 41 |
|
| 42 |
-
|
|
| 43 |
-
| --- | --- |
|
| 44 |
-
|
|
| 45 |
-
|
|
| 46 |
-
|
|
| 47 |
|
| 48 |
-
|
| 49 |
|
| 50 |
-
|
| 51 |
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
-
|
| 55 |
-
- **Trainer:** `HybridTrainer(model_type="xgboost")` in `src/bleujs/ml.py`. Quantum features were off.
|
| 56 |
-
- **Hyperparameters:** `max_depth=6`, `learning_rate=0.1`, `n_estimators=100`, `objective=binary:logistic`.
|
| 57 |
-
- **XGBoost:** 3.2.0. No feature scaler. The trainer fits the classifier on the raw feature values.
|
| 58 |
-
- **File:** `model.json` is the XGBoost native save, not a pickle. `metrics.json` records the split, feature names, and scores.
|
| 59 |
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
```bash
|
| 63 |
pip install xgboost huggingface_hub
|
|
@@ -74,30 +89,19 @@ path = hf_hub_download(
|
|
| 74 |
model = xgb.XGBClassifier()
|
| 75 |
model.load_model(path)
|
| 76 |
|
| 77 |
-
# X
|
|
|
|
| 78 |
pred = model.predict(X)
|
| 79 |
-
proba = model.predict_proba(X)
|
| 80 |
-
```
|
| 81 |
-
|
| 82 |
-
## Train the same way on your own table
|
| 83 |
-
|
| 84 |
-
```bash
|
| 85 |
-
pip install "bleu-js[ml]"
|
| 86 |
```
|
| 87 |
|
| 88 |
-
|
| 89 |
-
from bleujs.ml import HybridTrainer
|
| 90 |
-
|
| 91 |
-
trainer = HybridTrainer(model_type="xgboost")
|
| 92 |
-
model = trainer.train(X_train, y_train)
|
| 93 |
-
```
|
| 94 |
|
| 95 |
-
|
| 96 |
|
| 97 |
-
##
|
| 98 |
|
| 99 |
-
|
| 100 |
|
| 101 |
## Contact
|
| 102 |
|
| 103 |
-
support@helloblue.ai
|
|
|
|
| 7 |
- tabular
|
| 8 |
- bleu-js
|
| 9 |
datasets:
|
| 10 |
+
- covertype
|
| 11 |
metrics:
|
| 12 |
- accuracy
|
| 13 |
- f1
|
|
|
|
| 14 |
model-index:
|
| 15 |
- name: bleu-xgboost-classifier
|
| 16 |
results:
|
| 17 |
- task:
|
| 18 |
+
type: multiclass-classification
|
| 19 |
dataset:
|
| 20 |
+
name: UCI Covertype
|
| 21 |
+
type: covertype
|
| 22 |
metrics:
|
| 23 |
- type: accuracy
|
| 24 |
+
value: 0.9108887033897576
|
| 25 |
+
- type: macro_f1
|
| 26 |
+
value: 0.8941719351718758
|
| 27 |
+
- type: weighted_f1
|
| 28 |
+
value: 0.9115377043223944
|
| 29 |
---
|
| 30 |
|
| 31 |
# bleu-xgboost-classifier
|
| 32 |
|
| 33 |
+
Multiclass XGBoost model that predicts forest cover type from cartographic measurements. Trained for the Bleu.js open-source SDK on the full public UCI Covertype table: **581,012 rows**, **54 features**, **7 cover types**.
|
| 34 |
|
| 35 |
+
This is not the hosted Bleu.js API. That API is `https://api.bleujs.org`. `pip install bleu-js` does not download these weights.
|
| 36 |
|
| 37 |
+
## Held-out test result
|
| 38 |
|
| 39 |
+
The test split is 20% of the table (116,203 rows), stratified, `random_state=42`. It was scored once. Tree count was chosen on an inner 10% slice of the training rows only. Early stopping did not fire, so the published model uses the full cap of **400 trees**.
|
| 40 |
|
| 41 |
+
| Model | Accuracy | Macro F1 | Weighted F1 |
|
| 42 |
+
| --- | ---: | ---: | ---: |
|
| 43 |
+
| This model | 0.9109 | 0.8942 | 0.9115 |
|
| 44 |
+
| Small XGBoost baseline (depth 3, 50 trees, no class weights) | 0.7319 | 0.5328 | 0.7167 |
|
| 45 |
+
| Majority class | 0.4876 | 0.0937 | 0.3197 |
|
| 46 |
|
| 47 |
+
Log loss on the test rows is 0.2391 for this model and 0.6358 for the small baseline.
|
| 48 |
|
| 49 |
+
Per-class F1 on the same test rows:
|
| 50 |
|
| 51 |
+
| Cover type | F1 | Support |
|
| 52 |
+
| --- | ---: | ---: |
|
| 53 |
+
| Spruce/Fir | 0.9057 | 42,368 |
|
| 54 |
+
| Lodgepole Pine | 0.9164 | 56,661 |
|
| 55 |
+
| Ponderosa Pine | 0.9371 | 7,151 |
|
| 56 |
+
| Cottonwood/Willow | 0.8942 | 549 |
|
| 57 |
+
| Aspen | 0.7673 | 1,899 |
|
| 58 |
+
| Douglas-fir | 0.8961 | 3,473 |
|
| 59 |
+
| Krummholz | 0.9425 | 4,102 |
|
| 60 |
|
| 61 |
+
Aspen is the weak class. Lodgepole Pine is almost half the table, which is why accuracy alone is a poor summary and macro F1 is the number to quote.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
+
The largest gain features are Cache la Poudre wilderness, several soil-type indicators, and elevation. The full list is in `metrics.json`.
|
| 64 |
+
|
| 65 |
+
## What the weights are
|
| 66 |
+
|
| 67 |
+
- `model.json` — XGBoost native format, not a pickle. About 29 MB.
|
| 68 |
+
- `metrics.json` — split, hyperparameters, baselines, per-class scores, feature gain.
|
| 69 |
+
- `train.py` — the program that produced this file. Run it again to reproduce the scores.
|
| 70 |
+
|
| 71 |
+
Settings: `max_depth=8`, `learning_rate=0.1`, `subsample=0.8`, `colsample_bytree=0.8`, `min_child_weight=5`, `reg_lambda=1.0`, `tree_method=hist`, `objective=multi:softprob`, balanced sample weights, XGBoost 3.2.0.
|
| 72 |
+
|
| 73 |
+
`bleujs.ml.HybridTrainer` fits a binary classifier (`binary:logistic`). This model has seven classes, so it is trained with XGBoost directly. The SDK trainer remains the path for your own binary tables: `pip install "bleu-js[ml]"`.
|
| 74 |
+
|
| 75 |
+
## Load
|
| 76 |
|
| 77 |
```bash
|
| 78 |
pip install xgboost huggingface_hub
|
|
|
|
| 89 |
model = xgb.XGBClassifier()
|
| 90 |
model.load_model(path)
|
| 91 |
|
| 92 |
+
# X shape (n_samples, 54), column order in metrics.json
|
| 93 |
+
# class 0 is Spruce/Fir ... class 6 is Krummholz (UCI codes 1..7)
|
| 94 |
pred = model.predict(X)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
```
|
| 96 |
|
| 97 |
+
## Data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
+
Covertype, UCI Machine Learning Repository, loaded with `sklearn.datasets.fetch_covtype`. Four wilderness areas in Roosevelt National Forest, Colorado. Copyright Jock A. Blackard and Colorado State University. The model card, training script, and published weights are MIT. The dataset keeps its own terms.
|
| 100 |
|
| 101 |
+
## Out of scope
|
| 102 |
|
| 103 |
+
These scores are for this split of this Colorado table. They are not a map product, not a claim about other forests, and not a leaderboard entry. No search was run against the test split.
|
| 104 |
|
| 105 |
## Contact
|
| 106 |
|
| 107 |
+
support@helloblue.ai · [HelloblueAI/Bleu.js](https://github.com/HelloblueAI/Bleu.js)
|
metrics.json
CHANGED
|
@@ -1,65 +1,297 @@
|
|
| 1 |
{
|
| 2 |
-
"dataset": "
|
| 3 |
-
"
|
| 4 |
-
"
|
| 5 |
-
"
|
| 6 |
-
"
|
| 7 |
-
"
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
"
|
| 11 |
-
"
|
| 12 |
-
"
|
| 13 |
-
"
|
| 14 |
-
"
|
| 15 |
-
"mean compactness",
|
| 16 |
-
"mean concavity",
|
| 17 |
-
"mean concave points",
|
| 18 |
-
"mean symmetry",
|
| 19 |
-
"mean fractal dimension",
|
| 20 |
-
"radius error",
|
| 21 |
-
"texture error",
|
| 22 |
-
"perimeter error",
|
| 23 |
-
"area error",
|
| 24 |
-
"smoothness error",
|
| 25 |
-
"compactness error",
|
| 26 |
-
"concavity error",
|
| 27 |
-
"concave points error",
|
| 28 |
-
"symmetry error",
|
| 29 |
-
"fractal dimension error",
|
| 30 |
-
"worst radius",
|
| 31 |
-
"worst texture",
|
| 32 |
-
"worst perimeter",
|
| 33 |
-
"worst area",
|
| 34 |
-
"worst smoothness",
|
| 35 |
-
"worst compactness",
|
| 36 |
-
"worst concavity",
|
| 37 |
-
"worst concave points",
|
| 38 |
-
"worst symmetry",
|
| 39 |
-
"worst fractal dimension"
|
| 40 |
],
|
|
|
|
| 41 |
"split": {
|
| 42 |
"test_size": 0.2,
|
|
|
|
| 43 |
"random_state": 42,
|
| 44 |
"stratify": true
|
| 45 |
},
|
| 46 |
-
"
|
| 47 |
-
"
|
|
|
|
| 48 |
"hyperparameters": {
|
| 49 |
-
"max_depth":
|
| 50 |
"learning_rate": 0.1,
|
| 51 |
-
"
|
| 52 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
},
|
| 54 |
"xgboost_version": "3.2.0",
|
| 55 |
-
"
|
| 56 |
-
|
| 57 |
-
"
|
| 58 |
-
"
|
| 59 |
-
"
|
| 60 |
-
"
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
"
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
}
|
|
|
|
| 1 |
{
|
| 2 |
+
"dataset": "UCI Covertype",
|
| 3 |
+
"source": "sklearn.datasets.fetch_covtype",
|
| 4 |
+
"citation": "Blackard, Jock A. Covertype. UCI Machine Learning Repository.",
|
| 5 |
+
"n_rows": 581012,
|
| 6 |
+
"n_features": 54,
|
| 7 |
+
"class_names": [
|
| 8 |
+
"Spruce/Fir",
|
| 9 |
+
"Lodgepole Pine",
|
| 10 |
+
"Ponderosa Pine",
|
| 11 |
+
"Cottonwood/Willow",
|
| 12 |
+
"Aspen",
|
| 13 |
+
"Douglas-fir",
|
| 14 |
+
"Krummholz"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
],
|
| 16 |
+
"label_encoding": "model class i is UCI cover type i+1",
|
| 17 |
"split": {
|
| 18 |
"test_size": 0.2,
|
| 19 |
+
"inner_val_size": 0.1,
|
| 20 |
"random_state": 42,
|
| 21 |
"stratify": true
|
| 22 |
},
|
| 23 |
+
"n_train": 464809,
|
| 24 |
+
"n_test": 116203,
|
| 25 |
+
"n_estimators_selected_on_inner_val": 400,
|
| 26 |
"hyperparameters": {
|
| 27 |
+
"max_depth": 8,
|
| 28 |
"learning_rate": 0.1,
|
| 29 |
+
"subsample": 0.8,
|
| 30 |
+
"colsample_bytree": 0.8,
|
| 31 |
+
"min_child_weight": 5,
|
| 32 |
+
"reg_lambda": 1.0,
|
| 33 |
+
"tree_method": "hist",
|
| 34 |
+
"objective": "multi:softprob",
|
| 35 |
+
"num_class": 7,
|
| 36 |
+
"random_state": 42,
|
| 37 |
+
"n_jobs": -1,
|
| 38 |
+
"n_estimators": 400,
|
| 39 |
+
"sample_weight": "balanced"
|
| 40 |
},
|
| 41 |
"xgboost_version": "3.2.0",
|
| 42 |
+
"trainer_note": "XGBClassifier. bleujs.ml.HybridTrainer targets binary classification with binary:logistic, so this multiclass model is trained with the same library and a documented multiclass objective.",
|
| 43 |
+
"test_metrics": {
|
| 44 |
+
"accuracy": 0.9108887033897576,
|
| 45 |
+
"macro_f1": 0.8941719351718758,
|
| 46 |
+
"weighted_f1": 0.9115377043223944,
|
| 47 |
+
"log_loss": 0.23914723098278046
|
| 48 |
+
},
|
| 49 |
+
"simple_xgboost_baseline": {
|
| 50 |
+
"accuracy": 0.7318916034870012,
|
| 51 |
+
"macro_f1": 0.5327755702159982,
|
| 52 |
+
"weighted_f1": 0.7166892879546547,
|
| 53 |
+
"log_loss": 0.6357948184013367
|
| 54 |
+
},
|
| 55 |
+
"majority_baseline": {
|
| 56 |
+
"accuracy": 0.48760359026875383,
|
| 57 |
+
"macro_f1": 0.09365083038028243,
|
| 58 |
+
"weighted_f1": 0.3196513678755306,
|
| 59 |
+
"log_loss": 8.168821334838867
|
| 60 |
+
},
|
| 61 |
+
"per_class_f1": {
|
| 62 |
+
"Spruce/Fir": 0.9057362118202865,
|
| 63 |
+
"Lodgepole Pine": 0.9163635050597145,
|
| 64 |
+
"Ponderosa Pine": 0.9370629370629371,
|
| 65 |
+
"Cottonwood/Willow": 0.8941798941798942,
|
| 66 |
+
"Aspen": 0.767287784679089,
|
| 67 |
+
"Douglas-fir": 0.8960845413900556,
|
| 68 |
+
"Krummholz": 0.9424886720111537
|
| 69 |
+
},
|
| 70 |
+
"per_class_support": {
|
| 71 |
+
"Spruce/Fir": 42368,
|
| 72 |
+
"Lodgepole Pine": 56661,
|
| 73 |
+
"Ponderosa Pine": 7151,
|
| 74 |
+
"Cottonwood/Willow": 549,
|
| 75 |
+
"Aspen": 1899,
|
| 76 |
+
"Douglas-fir": 3473,
|
| 77 |
+
"Krummholz": 4102
|
| 78 |
+
},
|
| 79 |
+
"feature_importance_gain": [
|
| 80 |
+
{
|
| 81 |
+
"feature": "Wilderness_Area_3",
|
| 82 |
+
"gain": 249.5696563720703
|
| 83 |
+
},
|
| 84 |
+
{
|
| 85 |
+
"feature": "Soil_Type_38",
|
| 86 |
+
"gain": 129.66807556152344
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
"feature": "Soil_Type_11",
|
| 90 |
+
"gain": 120.6527328491211
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"feature": "Soil_Type_2",
|
| 94 |
+
"gain": 113.96896362304688
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
"feature": "Elevation",
|
| 98 |
+
"gain": 106.00982666015625
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"feature": "Soil_Type_9",
|
| 102 |
+
"gain": 95.48499298095703
|
| 103 |
+
},
|
| 104 |
+
{
|
| 105 |
+
"feature": "Soil_Type_29",
|
| 106 |
+
"gain": 79.35009765625
|
| 107 |
+
},
|
| 108 |
+
{
|
| 109 |
+
"feature": "Soil_Type_3",
|
| 110 |
+
"gain": 69.28760528564453
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"feature": "Wilderness_Area_0",
|
| 114 |
+
"gain": 67.1938247680664
|
| 115 |
+
},
|
| 116 |
+
{
|
| 117 |
+
"feature": "Soil_Type_39",
|
| 118 |
+
"gain": 65.25533294677734
|
| 119 |
+
},
|
| 120 |
+
{
|
| 121 |
+
"feature": "Soil_Type_37",
|
| 122 |
+
"gain": 62.631927490234375
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"feature": "Soil_Type_17",
|
| 126 |
+
"gain": 60.56175231933594
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"feature": "Soil_Type_1",
|
| 130 |
+
"gain": 53.69615936279297
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"feature": "Soil_Type_34",
|
| 134 |
+
"gain": 49.5882453918457
|
| 135 |
+
},
|
| 136 |
+
{
|
| 137 |
+
"feature": "Soil_Type_21",
|
| 138 |
+
"gain": 46.41263198852539
|
| 139 |
+
},
|
| 140 |
+
{
|
| 141 |
+
"feature": "Wilderness_Area_2",
|
| 142 |
+
"gain": 43.265750885009766
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
"feature": "Soil_Type_12",
|
| 146 |
+
"gain": 40.007808685302734
|
| 147 |
+
},
|
| 148 |
+
{
|
| 149 |
+
"feature": "Soil_Type_16",
|
| 150 |
+
"gain": 38.801177978515625
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"feature": "Soil_Type_31",
|
| 154 |
+
"gain": 34.57275390625
|
| 155 |
+
},
|
| 156 |
+
{
|
| 157 |
+
"feature": "Wilderness_Area_1",
|
| 158 |
+
"gain": 31.31096839904785
|
| 159 |
+
},
|
| 160 |
+
{
|
| 161 |
+
"feature": "Soil_Type_5",
|
| 162 |
+
"gain": 30.942785263061523
|
| 163 |
+
},
|
| 164 |
+
{
|
| 165 |
+
"feature": "Soil_Type_4",
|
| 166 |
+
"gain": 28.343896865844727
|
| 167 |
+
},
|
| 168 |
+
{
|
| 169 |
+
"feature": "Soil_Type_30",
|
| 170 |
+
"gain": 24.622051239013672
|
| 171 |
+
},
|
| 172 |
+
{
|
| 173 |
+
"feature": "Soil_Type_10",
|
| 174 |
+
"gain": 24.064062118530273
|
| 175 |
+
},
|
| 176 |
+
{
|
| 177 |
+
"feature": "Soil_Type_0",
|
| 178 |
+
"gain": 23.00113296508789
|
| 179 |
+
},
|
| 180 |
+
{
|
| 181 |
+
"feature": "Soil_Type_32",
|
| 182 |
+
"gain": 22.943172454833984
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
"feature": "Soil_Type_22",
|
| 186 |
+
"gain": 22.0693302154541
|
| 187 |
+
},
|
| 188 |
+
{
|
| 189 |
+
"feature": "Soil_Type_28",
|
| 190 |
+
"gain": 21.283544540405273
|
| 191 |
+
},
|
| 192 |
+
{
|
| 193 |
+
"feature": "Soil_Type_23",
|
| 194 |
+
"gain": 19.36112403869629
|
| 195 |
+
},
|
| 196 |
+
{
|
| 197 |
+
"feature": "Hillshade_9am",
|
| 198 |
+
"gain": 18.579858779907227
|
| 199 |
+
},
|
| 200 |
+
{
|
| 201 |
+
"feature": "Soil_Type_19",
|
| 202 |
+
"gain": 17.969446182250977
|
| 203 |
+
},
|
| 204 |
+
{
|
| 205 |
+
"feature": "Horizontal_Distance_To_Hydrology",
|
| 206 |
+
"gain": 17.332067489624023
|
| 207 |
+
},
|
| 208 |
+
{
|
| 209 |
+
"feature": "Soil_Type_36",
|
| 210 |
+
"gain": 16.103273391723633
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"feature": "Horizontal_Distance_To_Roadways",
|
| 214 |
+
"gain": 15.786291122436523
|
| 215 |
+
},
|
| 216 |
+
{
|
| 217 |
+
"feature": "Soil_Type_25",
|
| 218 |
+
"gain": 15.337019920349121
|
| 219 |
+
},
|
| 220 |
+
{
|
| 221 |
+
"feature": "Horizontal_Distance_To_Fire_Points",
|
| 222 |
+
"gain": 14.771939277648926
|
| 223 |
+
},
|
| 224 |
+
{
|
| 225 |
+
"feature": "Soil_Type_13",
|
| 226 |
+
"gain": 14.268491744995117
|
| 227 |
+
},
|
| 228 |
+
{
|
| 229 |
+
"feature": "Soil_Type_18",
|
| 230 |
+
"gain": 12.966172218322754
|
| 231 |
+
},
|
| 232 |
+
{
|
| 233 |
+
"feature": "Soil_Type_33",
|
| 234 |
+
"gain": 11.3596830368042
|
| 235 |
+
},
|
| 236 |
+
{
|
| 237 |
+
"feature": "Vertical_Distance_To_Hydrology",
|
| 238 |
+
"gain": 11.144617080688477
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
"feature": "Aspect",
|
| 242 |
+
"gain": 10.377111434936523
|
| 243 |
+
},
|
| 244 |
+
{
|
| 245 |
+
"feature": "Soil_Type_8",
|
| 246 |
+
"gain": 9.88647174835205
|
| 247 |
+
},
|
| 248 |
+
{
|
| 249 |
+
"feature": "Hillshade_Noon",
|
| 250 |
+
"gain": 9.631880760192871
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"feature": "Soil_Type_27",
|
| 254 |
+
"gain": 9.097264289855957
|
| 255 |
+
},
|
| 256 |
+
{
|
| 257 |
+
"feature": "Slope",
|
| 258 |
+
"gain": 8.322596549987793
|
| 259 |
+
},
|
| 260 |
+
{
|
| 261 |
+
"feature": "Hillshade_3pm",
|
| 262 |
+
"gain": 8.168411254882812
|
| 263 |
+
},
|
| 264 |
+
{
|
| 265 |
+
"feature": "Soil_Type_14",
|
| 266 |
+
"gain": 7.7810516357421875
|
| 267 |
+
},
|
| 268 |
+
{
|
| 269 |
+
"feature": "Soil_Type_20",
|
| 270 |
+
"gain": 7.463456630706787
|
| 271 |
+
},
|
| 272 |
+
{
|
| 273 |
+
"feature": "Soil_Type_15",
|
| 274 |
+
"gain": 7.146697998046875
|
| 275 |
+
},
|
| 276 |
+
{
|
| 277 |
+
"feature": "Soil_Type_26",
|
| 278 |
+
"gain": 7.062173366546631
|
| 279 |
+
},
|
| 280 |
+
{
|
| 281 |
+
"feature": "Soil_Type_24",
|
| 282 |
+
"gain": 4.399511814117432
|
| 283 |
+
},
|
| 284 |
+
{
|
| 285 |
+
"feature": "Soil_Type_35",
|
| 286 |
+
"gain": 3.7608084678649902
|
| 287 |
+
},
|
| 288 |
+
{
|
| 289 |
+
"feature": "Soil_Type_7",
|
| 290 |
+
"gain": 1.7688950300216675
|
| 291 |
+
},
|
| 292 |
+
{
|
| 293 |
+
"feature": "Soil_Type_6",
|
| 294 |
+
"gain": 1.6422888040542603
|
| 295 |
+
}
|
| 296 |
+
]
|
| 297 |
}
|
model.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
train.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Train and score the Bleu.js forest-cover XGBoost classifier.
|
| 2 |
+
|
| 3 |
+
Protocol, fixed before the test set is scored:
|
| 4 |
+
- Data: sklearn.datasets.fetch_covtype (UCI Covertype, full table)
|
| 5 |
+
- Labels shifted from 1..7 to 0..6
|
| 6 |
+
- Outer split: stratified 80/20, random_state=42. The 20% is scored once.
|
| 7 |
+
- Inner split: stratified 90/10 of the training rows, random_state=42,
|
| 8 |
+
used only to pick n_estimators with early stopping.
|
| 9 |
+
- Final fit: all training rows, n_estimators = best_iteration + 1,
|
| 10 |
+
sample_weight = balanced class weights from the training rows.
|
| 11 |
+
- Baselines, same test rows, no retuning: majority class; a depth-3
|
| 12 |
+
XGBoost with 50 trees and no class weights.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import json
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
import xgboost as xgb
|
| 20 |
+
from sklearn.datasets import fetch_covtype
|
| 21 |
+
from sklearn.metrics import (
|
| 22 |
+
accuracy_score,
|
| 23 |
+
classification_report,
|
| 24 |
+
f1_score,
|
| 25 |
+
log_loss,
|
| 26 |
+
)
|
| 27 |
+
from sklearn.model_selection import train_test_split
|
| 28 |
+
from sklearn.utils.class_weight import compute_sample_weight
|
| 29 |
+
|
| 30 |
+
OUT = Path(__file__).resolve().parent
|
| 31 |
+
RANDOM_STATE = 42
|
| 32 |
+
CLASS_NAMES = [
|
| 33 |
+
"Spruce/Fir",
|
| 34 |
+
"Lodgepole Pine",
|
| 35 |
+
"Ponderosa Pine",
|
| 36 |
+
"Cottonwood/Willow",
|
| 37 |
+
"Aspen",
|
| 38 |
+
"Douglas-fir",
|
| 39 |
+
"Krummholz",
|
| 40 |
+
]
|
| 41 |
+
FEATURE_NAMES = [
|
| 42 |
+
"Elevation",
|
| 43 |
+
"Aspect",
|
| 44 |
+
"Slope",
|
| 45 |
+
"Horizontal_Distance_To_Hydrology",
|
| 46 |
+
"Vertical_Distance_To_Hydrology",
|
| 47 |
+
"Horizontal_Distance_To_Roadways",
|
| 48 |
+
"Hillshade_9am",
|
| 49 |
+
"Hillshade_Noon",
|
| 50 |
+
"Hillshade_3pm",
|
| 51 |
+
"Horizontal_Distance_To_Fire_Points",
|
| 52 |
+
*[f"Wilderness_Area_{i}" for i in range(4)],
|
| 53 |
+
*[f"Soil_Type_{i}" for i in range(40)],
|
| 54 |
+
]
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def scores(y_true, pred, proba):
|
| 58 |
+
return {
|
| 59 |
+
"accuracy": float(accuracy_score(y_true, pred)),
|
| 60 |
+
"macro_f1": float(f1_score(y_true, pred, average="macro")),
|
| 61 |
+
"weighted_f1": float(f1_score(y_true, pred, average="weighted")),
|
| 62 |
+
"log_loss": float(log_loss(y_true, proba, labels=list(range(7)))),
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def main():
|
| 67 |
+
bunch = fetch_covtype(as_frame=False)
|
| 68 |
+
X = np.asarray(bunch.data, dtype=np.float32)
|
| 69 |
+
y = np.asarray(bunch.target, dtype=np.int32) - 1
|
| 70 |
+
if X.shape[1] != len(FEATURE_NAMES) or y.min() != 0 or y.max() != 6:
|
| 71 |
+
raise SystemExit(f"Unexpected covertype shape {X.shape} labels {y.min()}..{y.max()}")
|
| 72 |
+
|
| 73 |
+
X_train, X_test, y_train, y_test = train_test_split(
|
| 74 |
+
X, y, test_size=0.2, random_state=RANDOM_STATE, stratify=y
|
| 75 |
+
)
|
| 76 |
+
X_fit, X_val, y_fit, y_val = train_test_split(
|
| 77 |
+
X_train, y_train, test_size=0.1, random_state=RANDOM_STATE, stratify=y_train
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
common = dict(
|
| 81 |
+
max_depth=8,
|
| 82 |
+
learning_rate=0.1,
|
| 83 |
+
subsample=0.8,
|
| 84 |
+
colsample_bytree=0.8,
|
| 85 |
+
min_child_weight=5,
|
| 86 |
+
reg_lambda=1.0,
|
| 87 |
+
tree_method="hist",
|
| 88 |
+
objective="multi:softprob",
|
| 89 |
+
num_class=7,
|
| 90 |
+
random_state=RANDOM_STATE,
|
| 91 |
+
n_jobs=-1,
|
| 92 |
+
)
|
| 93 |
+
search = xgb.XGBClassifier(n_estimators=400, early_stopping_rounds=20, **common)
|
| 94 |
+
search.fit(
|
| 95 |
+
X_fit,
|
| 96 |
+
y_fit,
|
| 97 |
+
sample_weight=compute_sample_weight("balanced", y_fit),
|
| 98 |
+
eval_set=[(X_val, y_val)],
|
| 99 |
+
verbose=False,
|
| 100 |
+
)
|
| 101 |
+
n_trees = int(search.best_iteration) + 1
|
| 102 |
+
|
| 103 |
+
model = xgb.XGBClassifier(n_estimators=n_trees, **common)
|
| 104 |
+
model.fit(
|
| 105 |
+
X_train,
|
| 106 |
+
y_train,
|
| 107 |
+
sample_weight=compute_sample_weight("balanced", y_train),
|
| 108 |
+
verbose=False,
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
simple = xgb.XGBClassifier(
|
| 112 |
+
n_estimators=50,
|
| 113 |
+
max_depth=3,
|
| 114 |
+
learning_rate=0.1,
|
| 115 |
+
tree_method="hist",
|
| 116 |
+
objective="multi:softprob",
|
| 117 |
+
num_class=7,
|
| 118 |
+
random_state=RANDOM_STATE,
|
| 119 |
+
n_jobs=-1,
|
| 120 |
+
)
|
| 121 |
+
simple.fit(X_train, y_train, verbose=False)
|
| 122 |
+
|
| 123 |
+
proba = model.predict_proba(X_test)
|
| 124 |
+
pred = proba.argmax(axis=1)
|
| 125 |
+
simple_proba = simple.predict_proba(X_test)
|
| 126 |
+
simple_pred = simple_proba.argmax(axis=1)
|
| 127 |
+
majority = int(np.bincount(y_train).argmax())
|
| 128 |
+
majority_pred = np.full_like(y_test, majority)
|
| 129 |
+
majority_proba = np.zeros_like(proba)
|
| 130 |
+
majority_proba[:, majority] = 1.0
|
| 131 |
+
|
| 132 |
+
report = classification_report(
|
| 133 |
+
y_test, pred, target_names=CLASS_NAMES, output_dict=True, digits=4
|
| 134 |
+
)
|
| 135 |
+
gain = model.get_booster().get_score(importance_type="gain")
|
| 136 |
+
importance = sorted(
|
| 137 |
+
(
|
| 138 |
+
{"feature": FEATURE_NAMES[int(k[1:])], "gain": float(v)}
|
| 139 |
+
for k, v in gain.items()
|
| 140 |
+
),
|
| 141 |
+
key=lambda row: row["gain"],
|
| 142 |
+
reverse=True,
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
payload = {
|
| 146 |
+
"dataset": "UCI Covertype",
|
| 147 |
+
"source": "sklearn.datasets.fetch_covtype",
|
| 148 |
+
"citation": "Blackard, Jock A. Covertype. UCI Machine Learning Repository.",
|
| 149 |
+
"n_rows": int(X.shape[0]),
|
| 150 |
+
"n_features": int(X.shape[1]),
|
| 151 |
+
"class_names": CLASS_NAMES,
|
| 152 |
+
"label_encoding": "model class i is UCI cover type i+1",
|
| 153 |
+
"split": {
|
| 154 |
+
"test_size": 0.2,
|
| 155 |
+
"inner_val_size": 0.1,
|
| 156 |
+
"random_state": RANDOM_STATE,
|
| 157 |
+
"stratify": True,
|
| 158 |
+
},
|
| 159 |
+
"n_train": int(len(y_train)),
|
| 160 |
+
"n_test": int(len(y_test)),
|
| 161 |
+
"n_estimators_selected_on_inner_val": n_trees,
|
| 162 |
+
"hyperparameters": {**common, "n_estimators": n_trees, "sample_weight": "balanced"},
|
| 163 |
+
"xgboost_version": xgb.__version__,
|
| 164 |
+
"trainer_note": (
|
| 165 |
+
"XGBClassifier. bleujs.ml.HybridTrainer targets binary classification "
|
| 166 |
+
"with binary:logistic, so this multiclass model is trained with the "
|
| 167 |
+
"same library and a documented multiclass objective."
|
| 168 |
+
),
|
| 169 |
+
"test_metrics": scores(y_test, pred, proba),
|
| 170 |
+
"simple_xgboost_baseline": scores(y_test, simple_pred, simple_proba),
|
| 171 |
+
"majority_baseline": scores(y_test, majority_pred, majority_proba),
|
| 172 |
+
"per_class_f1": {name: float(report[name]["f1-score"]) for name in CLASS_NAMES},
|
| 173 |
+
"per_class_support": {name: int(report[name]["support"]) for name in CLASS_NAMES},
|
| 174 |
+
"feature_importance_gain": importance,
|
| 175 |
+
}
|
| 176 |
+
model.save_model(OUT / "model.json")
|
| 177 |
+
(OUT / "metrics.json").write_text(json.dumps(payload, indent=2))
|
| 178 |
+
print(json.dumps({
|
| 179 |
+
"n_trees": n_trees,
|
| 180 |
+
"test": payload["test_metrics"],
|
| 181 |
+
"simple": payload["simple_xgboost_baseline"],
|
| 182 |
+
"majority": payload["majority_baseline"],
|
| 183 |
+
"per_class_f1": payload["per_class_f1"],
|
| 184 |
+
}, indent=2))
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
if __name__ == "__main__":
|
| 188 |
+
main()
|