gboostrs 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gboostrs-0.1.0/.github/workflows/release.yml +137 -0
- gboostrs-0.1.0/.gitignore +4 -0
- gboostrs-0.1.0/Cargo.toml +34 -0
- gboostrs-0.1.0/LICENSE +21 -0
- gboostrs-0.1.0/PKG-INFO +151 -0
- gboostrs-0.1.0/README.md +224 -0
- gboostrs-0.1.0/benches/benchmark.rs +151 -0
- gboostrs-0.1.0/py-gboost/Cargo.lock +513 -0
- gboostrs-0.1.0/py-gboost/Cargo.toml +17 -0
- gboostrs-0.1.0/py-gboost/README.md +135 -0
- gboostrs-0.1.0/py-gboost/src/lib.rs +344 -0
- gboostrs-0.1.0/py-gboost/tests/test_gboost.py +105 -0
- gboostrs-0.1.0/pyproject.toml +25 -0
- gboostrs-0.1.0/src/boost_api.rs +322 -0
- gboostrs-0.1.0/src/boost_model.rs +712 -0
- gboostrs-0.1.0/src/boost_node.rs +705 -0
- gboostrs-0.1.0/src/boost_tree.rs +602 -0
- gboostrs-0.1.0/src/dataset.rs +425 -0
- gboostrs-0.1.0/src/histogram.rs +330 -0
- gboostrs-0.1.0/src/lib.rs +14 -0
- gboostrs-0.1.0/src/options.rs +352 -0
- gboostrs-0.1.0/src/parser.rs +335 -0
- gboostrs-0.1.0/src/quantize.rs +441 -0
- gboostrs-0.1.0/src/tree.rs +373 -0
- gboostrs-0.1.0/src/update_large.rs +617 -0
- gboostrs-0.1.0/src/update_small.rs +115 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
testpypi:
|
|
9
|
+
description: "Upload to TestPyPI instead of PyPI"
|
|
10
|
+
type: choice
|
|
11
|
+
options: ["false", "true"]
|
|
12
|
+
default: "false"
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: release-${{ github.ref }}
|
|
19
|
+
cancel-in-progress: false
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
build:
|
|
23
|
+
strategy:
|
|
24
|
+
fail-fast: false
|
|
25
|
+
matrix:
|
|
26
|
+
include:
|
|
27
|
+
- { label: linux-x86_64, runner: ubuntu-latest, python: "3.11" }
|
|
28
|
+
- { label: linux-x86_64, runner: ubuntu-latest, python: "3.12" }
|
|
29
|
+
- { label: linux-x86_64, runner: ubuntu-latest, python: "3.13" }
|
|
30
|
+
- { label: linux-aarch64, runner: ubuntu-24.04-arm, python: "3.11" }
|
|
31
|
+
- { label: linux-aarch64, runner: ubuntu-24.04-arm, python: "3.12" }
|
|
32
|
+
- { label: linux-aarch64, runner: ubuntu-24.04-arm, python: "3.13" }
|
|
33
|
+
- { label: macos-arm64, runner: macos-latest, python: "3.11" }
|
|
34
|
+
- { label: macos-arm64, runner: macos-latest, python: "3.12" }
|
|
35
|
+
- { label: macos-arm64, runner: macos-latest, python: "3.13" }
|
|
36
|
+
- { label: windows-x86_64, runner: windows-latest, python: "3.11" }
|
|
37
|
+
- { label: windows-x86_64, runner: windows-latest, python: "3.12" }
|
|
38
|
+
- { label: windows-x86_64, runner: windows-latest, python: "3.13" }
|
|
39
|
+
runs-on: ${{ matrix.runner }}
|
|
40
|
+
env:
|
|
41
|
+
CARGO_TERM_COLOR: always
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@v7
|
|
44
|
+
|
|
45
|
+
- uses: actions/setup-python@v7
|
|
46
|
+
with:
|
|
47
|
+
python-version: ${{ matrix.python }}
|
|
48
|
+
|
|
49
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
50
|
+
|
|
51
|
+
- name: Cache cargo
|
|
52
|
+
uses: actions/cache@v6
|
|
53
|
+
with:
|
|
54
|
+
path: |
|
|
55
|
+
~/.cargo/registry
|
|
56
|
+
~/.cargo/git
|
|
57
|
+
target
|
|
58
|
+
key: ${{ runner.os }}-${{ matrix.label }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
59
|
+
restore-keys: |
|
|
60
|
+
${{ runner.os }}-${{ matrix.label }}-cargo-
|
|
61
|
+
|
|
62
|
+
- name: Install maturin
|
|
63
|
+
run: python -m pip install -U pip maturin
|
|
64
|
+
|
|
65
|
+
- name: Build wheel
|
|
66
|
+
shell: bash
|
|
67
|
+
run: |
|
|
68
|
+
maturin build --release --manifest-path py-gboost/Cargo.toml -o wheels
|
|
69
|
+
|
|
70
|
+
- uses: actions/upload-artifact@v7
|
|
71
|
+
with:
|
|
72
|
+
name: wheels-${{ matrix.label }}-py${{ matrix.python }}
|
|
73
|
+
path: wheels/*.whl
|
|
74
|
+
if-no-files-found: error
|
|
75
|
+
|
|
76
|
+
sdist:
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
steps:
|
|
79
|
+
- uses: actions/checkout@v7
|
|
80
|
+
|
|
81
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
82
|
+
|
|
83
|
+
- name: Install maturin
|
|
84
|
+
run: python3 -m pip install -U pip maturin
|
|
85
|
+
|
|
86
|
+
- name: Build sdist
|
|
87
|
+
run: maturin sdist --manifest-path py-gboost/Cargo.toml --out dist
|
|
88
|
+
|
|
89
|
+
- uses: actions/upload-artifact@v7
|
|
90
|
+
with:
|
|
91
|
+
name: sdist
|
|
92
|
+
path: dist/*.tar.gz
|
|
93
|
+
if-no-files-found: error
|
|
94
|
+
|
|
95
|
+
publish:
|
|
96
|
+
needs: [build, sdist]
|
|
97
|
+
runs-on: ubuntu-latest
|
|
98
|
+
if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v')
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/download-artifact@v8
|
|
101
|
+
with:
|
|
102
|
+
pattern: "wheels-*"
|
|
103
|
+
path: dist
|
|
104
|
+
merge-multiple: true
|
|
105
|
+
|
|
106
|
+
- uses: actions/download-artifact@v8
|
|
107
|
+
with:
|
|
108
|
+
name: sdist
|
|
109
|
+
path: dist
|
|
110
|
+
merge-multiple: true
|
|
111
|
+
|
|
112
|
+
- name: List artifacts
|
|
113
|
+
run: ls -la dist
|
|
114
|
+
|
|
115
|
+
- uses: actions/setup-python@v7
|
|
116
|
+
with:
|
|
117
|
+
python-version: "3.13"
|
|
118
|
+
|
|
119
|
+
- name: Install twine
|
|
120
|
+
run: python -m pip install -U pip twine
|
|
121
|
+
|
|
122
|
+
- name: Check with twine
|
|
123
|
+
run: twine check dist/*
|
|
124
|
+
|
|
125
|
+
- name: Upload to TestPyPI
|
|
126
|
+
if: inputs.testpypi == 'true'
|
|
127
|
+
env:
|
|
128
|
+
TWINE_USERNAME: __token__
|
|
129
|
+
TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
|
130
|
+
run: twine upload --repository-url https://test.pypi.org/legacy/ dist/*
|
|
131
|
+
|
|
132
|
+
- name: Upload to PyPI
|
|
133
|
+
if: inputs.testpypi != 'true'
|
|
134
|
+
env:
|
|
135
|
+
TWINE_USERNAME: __token__
|
|
136
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
137
|
+
run: twine upload dist/*
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "gboost"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2024"
|
|
5
|
+
rust-version = "1.85"
|
|
6
|
+
description = "Gradient boosting library in Rust"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
repository = "https://github.com/rseko/gboost"
|
|
9
|
+
keywords = ["gradient-boosting", "machine-learning", "decision-tree"]
|
|
10
|
+
categories = ["algorithms", "science"]
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
|
|
13
|
+
[features]
|
|
14
|
+
large-index = []
|
|
15
|
+
|
|
16
|
+
[dependencies]
|
|
17
|
+
anyhow = "1.0"
|
|
18
|
+
itertools = "0.15"
|
|
19
|
+
num-traits = "0.2"
|
|
20
|
+
rand = "0.10"
|
|
21
|
+
rayon = "1.12"
|
|
22
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
23
|
+
serde_json = { version = "1.0", features = ["preserve_order"] }
|
|
24
|
+
thiserror = "2.0"
|
|
25
|
+
zerocopy = { version = "0.8", features = ["std"] }
|
|
26
|
+
|
|
27
|
+
[dev-dependencies]
|
|
28
|
+
criterion = "0.8"
|
|
29
|
+
half = { version = "2.7", features = ["num-traits"] }
|
|
30
|
+
tempfile = "3"
|
|
31
|
+
|
|
32
|
+
[[bench]]
|
|
33
|
+
name = "benchmark"
|
|
34
|
+
harness = false
|
gboostrs-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sergey Kozub
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gboostrs-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gboostrs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Classifier: Programming Language :: Rust
|
|
5
|
+
Classifier: Programming Language :: Python :: 3
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Requires-Dist: numpy
|
|
8
|
+
Summary: Python bindings for the gboost gradient boosting library
|
|
9
|
+
Keywords: gradient-boosting,machine-learning,decision-tree
|
|
10
|
+
Author: rseko
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
14
|
+
Project-URL: Repository, https://github.com/rseko/gboost
|
|
15
|
+
|
|
16
|
+
# py-gboost
|
|
17
|
+
|
|
18
|
+
Python bindings for the [`gboost`](../README.md) gradient-boosting library.
|
|
19
|
+
|
|
20
|
+
It exposes a single `GradientBooster` class backed by the Rust core, with a
|
|
21
|
+
sklearn-like `fit(x, y)` / `predict(x)` API over NumPy `float32` arrays. All
|
|
22
|
+
training work (histogram binning, split search, per-batch update) runs in Rust;
|
|
23
|
+
`fit` and `fit_next` release the GIL while the booster grows.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
`py-gboost` is a [maturin](https://www.maturin.rs) package; it builds the Rust
|
|
28
|
+
extension and installs it as the importable module `gboostrs`.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# From a release build
|
|
32
|
+
pip install .
|
|
33
|
+
|
|
34
|
+
# Or, for in-place development
|
|
35
|
+
pip install maturin
|
|
36
|
+
maturin develop
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Requires Python 3.9+. The import name is `gboostrs`:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import gboostrs
|
|
43
|
+
gboostrs.__version__
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import numpy as np
|
|
50
|
+
from gboostrs import GradientBooster
|
|
51
|
+
|
|
52
|
+
rng = np.random.default_rng(1)
|
|
53
|
+
x = rng.standard_normal((100, 5)).astype(np.float32) # (rows, cols)
|
|
54
|
+
y = rng.standard_normal(100).astype(np.float32) # one value per row
|
|
55
|
+
|
|
56
|
+
# Regression
|
|
57
|
+
gb = GradientBooster(objective="squaredloss", iterations=100)
|
|
58
|
+
gb.fit(x, y)
|
|
59
|
+
scores = gb.predict(x) # np.ndarray of float32, one score per row
|
|
60
|
+
gb.loss() # mean loss of the current predictions vs. the labels
|
|
61
|
+
|
|
62
|
+
# Binary classification (labels in {0, 1})
|
|
63
|
+
y = rng.integers(0, 2, 100).astype(np.float32)
|
|
64
|
+
gb = GradientBooster(objective="logloss", iterations=100)
|
|
65
|
+
gb.fit(x, y)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Growing round by round
|
|
69
|
+
|
|
70
|
+
`fit(x, y)` trains up to `iterations` rounds and replaces any previously
|
|
71
|
+
trained model. `fit_next()` grows one additional round on the existing model
|
|
72
|
+
and returns `False` when a round produced no tree (no gain) — the natural
|
|
73
|
+
early-stopping signal.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
gb = GradientBooster(objective="squaredloss", iterations=0)
|
|
77
|
+
gb.fit(x, y) # fit(0) builds the booster without growing trees
|
|
78
|
+
|
|
79
|
+
while gb.fit_next():
|
|
80
|
+
print(gb.loss())
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## `GradientBooster`
|
|
84
|
+
|
|
85
|
+
All parameters are keyword-only and have defaults, so `GradientBooster()` is a
|
|
86
|
+
valid, fully-configured instance.
|
|
87
|
+
|
|
88
|
+
| Parameter | Default | Description |
|
|
89
|
+
| -------------------- | :-----------: | ------------------------------------------------------------------------------- |
|
|
90
|
+
| `iterations` | `100` | Maximum boosting rounds run by `fit`. |
|
|
91
|
+
| `objective` | `"squaredloss"` | Loss to minimize: `"squaredloss"` (regression) or `"logloss"` (classification). |
|
|
92
|
+
| `base_score` | `0.0` | Initial constant prediction before any tree is added. |
|
|
93
|
+
| `max_bins` | `255` | Maximum number of quantization bins per column. |
|
|
94
|
+
| `persist_path` | `None` | Directory for on-disk artifacts, if persistence is used. |
|
|
95
|
+
| `parallel` | `True` | Enable the parallel (Rayon) execution paths. |
|
|
96
|
+
| `seed` | `0` | Seed for reproducible random draws (subsampling). |
|
|
97
|
+
| `learning_rate` | `0.3` | Per-tree update step size. |
|
|
98
|
+
| `subsample` | `1.0` | Row subsampling fraction per tree. |
|
|
99
|
+
| `colsample_bytree` | `1.0` | Per-tree feature subsampling fraction. |
|
|
100
|
+
| `colsample_bylevel` | `1.0` | Per-level feature subsampling fraction. |
|
|
101
|
+
| `colsample_bynode` | `1.0` | Per-node feature subsampling fraction. |
|
|
102
|
+
| `max_depth` | `6` | Maximum tree depth (`0` = no limit). |
|
|
103
|
+
| `max_leaves` | `0` | Maximum number of leaves (`0` = no limit). |
|
|
104
|
+
| `grow_policy` | `"depthwise"` | Tree growth policy: `"depthwise"` or `"lossguide"`. |
|
|
105
|
+
| `reg_alpha` | `0.0` | L1 regularizer on leaf weights. |
|
|
106
|
+
| `reg_lambda` | `1.0` | L2 regularizer on leaf weights. |
|
|
107
|
+
| `min_split_gain` | `0.0` | Minimum gain required to take a split. |
|
|
108
|
+
| `min_child_weight` | `1.0` | Minimum hessian sum allowed in a leaf. |
|
|
109
|
+
| `allow_missing_splits` | `False` | Whether missing values may drive their own split. |
|
|
110
|
+
| `onehot_columns` | `[]` | Indices of columns to treat as one-hot categoricals (exact-bin splits). |
|
|
111
|
+
|
|
112
|
+
### Methods
|
|
113
|
+
|
|
114
|
+
- **`fit(x, y) -> None`** — Train the booster. `x` is a 2-D `float32` matrix
|
|
115
|
+
of shape `(rows, cols)`; `y` is a 1-D `float32` target of length `rows`.
|
|
116
|
+
Replaces any previously trained model.
|
|
117
|
+
- **`fit_next() -> bool`** — Grow one additional round on the trained model.
|
|
118
|
+
Returns `False` when the round produced no tree (no gain).
|
|
119
|
+
- **`predict(x) -> np.ndarray`** — Score `x` (shape `(rows, cols)`, same
|
|
120
|
+
column count as training) with the trained model; returns one `float32`
|
|
121
|
+
score per row.
|
|
122
|
+
- **`loss() -> float | None`** — Mean loss of the current predictions against
|
|
123
|
+
the training labels, or `None` before `fit`.
|
|
124
|
+
- **`get_params() -> dict`** — Return the constructor kwargs as a dict of
|
|
125
|
+
`name -> value`; `GradientBooster(**gb.get_params())` reconstructs an
|
|
126
|
+
equivalent instance.
|
|
127
|
+
|
|
128
|
+
## Notes
|
|
129
|
+
|
|
130
|
+
- **Zero-copy inputs.** The Rust core works on column-major (Fortran-order)
|
|
131
|
+
arrays. Pass `x` as `np.asfortranarray(x)` to avoid a copy; row-major or
|
|
132
|
+
strided inputs are transposed into column-major internally.
|
|
133
|
+
- **Error types.** I/O failures (e.g. an invalid `persist_path`) raise
|
|
134
|
+
`IOError`; other problems (an untrained model, a `NaN` in the target, a
|
|
135
|
+
column-count mismatch in `predict`, an invalid `objective`/`grow_policy`)
|
|
136
|
+
raise `ValueError`.
|
|
137
|
+
- **`onehot_columns`.** Columns listed here are split on an exact bin (`==`)
|
|
138
|
+
rather than an ordered threshold (`<=`), so high-cardinality categoricals
|
|
139
|
+
are first-class. Other columns are treated as linear (ordered) features.
|
|
140
|
+
- **Determinism.** With a fixed `seed` and `subsample`, a run is reproducible.
|
|
141
|
+
|
|
142
|
+
## Tests
|
|
143
|
+
|
|
144
|
+
The test suite exercises the binding end-to-end (regression, classification,
|
|
145
|
+
`fit_next`, and error paths):
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pip install pytest
|
|
149
|
+
pytest tests/
|
|
150
|
+
```
|
|
151
|
+
|
gboostrs-0.1.0/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# gboost
|
|
2
|
+
|
|
3
|
+
A gradient-boosting library in Rust.
|
|
4
|
+
|
|
5
|
+
The distinguishing feature of `gboost` is out-of-core learning: the dataset is
|
|
6
|
+
staged to disk and each boosting round streams it through the gradient/hessian
|
|
7
|
+
computation, keeping only a subsample of rows in RAM — not the whole table.
|
|
8
|
+
You can train on tens of billions of rows on a machine that could never hold
|
|
9
|
+
the full columnar data at once.
|
|
10
|
+
|
|
11
|
+
It also works in-memory (fully columnar, histogram-binned, optionally parallel
|
|
12
|
+
via `rayon`) for datasets that do fit in RAM, behind the same object-safe
|
|
13
|
+
[`Booster`] trait.
|
|
14
|
+
|
|
15
|
+
`gboost` follows the XGBoost model: the same histogram-based boosting
|
|
16
|
+
algorithm, the same gradient/hessian leaf-value math, and training parameters
|
|
17
|
+
that mirror XGBoost's names (`learning_rate`, `subsample`, `colsample_bytree`,
|
|
18
|
+
`max_depth`, `min_split_gain`, `reg_alpha` / `reg_lambda`, …). If you know
|
|
19
|
+
XGBoost, the options will feel familiar — the main differences are the Rust
|
|
20
|
+
columnar API, a couple of gboost-specific options, and the out-of-core path.
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- **Out-of-core training** — stage columns to disk, then grow trees by
|
|
25
|
+
streaming the dataset with only a subsample of rows held in RAM, so memory
|
|
26
|
+
stays bounded regardless of dataset size. See [`ModelInit`] and
|
|
27
|
+
`BoostModel::init_large`.
|
|
28
|
+
- **One-hot (categorical) features** — a column can split on an exact bin
|
|
29
|
+
(`==`) rather than an ordered threshold (`<=`), so high-cardinality
|
|
30
|
+
categoricals are a first-class feature kind, not just `Linear` splits.
|
|
31
|
+
- **Half-precision floats** — the feature type `F` is generic over any
|
|
32
|
+
`QuantFloat`, so `f16` and `bf16` (from the `half` crate) are accepted in
|
|
33
|
+
addition to `f32` input type.
|
|
34
|
+
- **Histogram binning** — values are quantized into a bounded number of bins
|
|
35
|
+
(`max_bins` up to 65535), so split search scales with the bin count, not the
|
|
36
|
+
raw data.
|
|
37
|
+
- **Parallel training** — split search, histogram building and the per-batch
|
|
38
|
+
update pass run across threads via `rayon` (toggle with `set_parallel`).
|
|
39
|
+
- **Deterministic** — a single `seed` drives all random draws (subsampling),
|
|
40
|
+
so runs are reproducible.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```toml
|
|
45
|
+
[dependencies]
|
|
46
|
+
gboost = "0.1"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
MIT-licensed. Edition 2024 requires Rust 1.85+.
|
|
50
|
+
|
|
51
|
+
### Crate features
|
|
52
|
+
|
|
53
|
+
| Feature | Default | Description |
|
|
54
|
+
| ------------- | :-----: | ------------------------------------------------------------------ |
|
|
55
|
+
| `large-index` | no | Allow training targets with more than `u32::MAX` rows (`u64` row indices). |
|
|
56
|
+
|
|
57
|
+
## Training on data that doesn't fit in RAM
|
|
58
|
+
|
|
59
|
+
This is the primary use case. The flow is:
|
|
60
|
+
|
|
61
|
+
1. **Stage** the dataset to a persistence directory: for each feature column,
|
|
62
|
+
fit its quantizer and write the bin indices to `feature.<name>`; write the
|
|
63
|
+
target to `target`; the feature headers are appended to `model.txt`.
|
|
64
|
+
2. **Build** a booster over that on-disk dataset with `BoostModel::init_large`.
|
|
65
|
+
Rows are *not* loaded wholesale — only a subsample of rows is kept in RAM,
|
|
66
|
+
and the first pass streams the full dataset to seed the per-row
|
|
67
|
+
gradient/hessian index.
|
|
68
|
+
3. **Fit** one or more rounds. Each `fit_next` round takes the streaming
|
|
69
|
+
`update_large` path: it walks the on-disk batches, folds the new tree's
|
|
70
|
+
leaf values into the running predictions, and recomputes the derivatives.
|
|
71
|
+
|
|
72
|
+
The persistence path is mandatory on this route — set it with
|
|
73
|
+
`Options::set_persist_path`.
|
|
74
|
+
|
|
75
|
+
```rust
|
|
76
|
+
use gboost::boost_model::{BoostModel, ModelInit};
|
|
77
|
+
use gboost::dataset::{SquaredLoss, FeatureKind};
|
|
78
|
+
use gboost::options::Options;
|
|
79
|
+
use std::path::PathBuf;
|
|
80
|
+
|
|
81
|
+
let persist_path = PathBuf::from("/tmp/gboost");
|
|
82
|
+
let options = Options::default()
|
|
83
|
+
.set_persist_path(Some(persist_path.clone()))
|
|
84
|
+
.set_max_bins(255)
|
|
85
|
+
.set_subsample(0.5)
|
|
86
|
+
.set_seed(42)
|
|
87
|
+
.set_parallel(true);
|
|
88
|
+
|
|
89
|
+
// 1. Stage columns to disk. Each column is fit, quantized, and written.
|
|
90
|
+
let mut init = ModelInit::new(persist_path.clone(), 255)?;
|
|
91
|
+
for (name, column) in [
|
|
92
|
+
("age", &[/* one f32 per row */]),
|
|
93
|
+
("income", &[/* ... */]),
|
|
94
|
+
] {
|
|
95
|
+
init.write_feature::<u8, _>(name, FeatureKind::Linear, column)?;
|
|
96
|
+
}
|
|
97
|
+
init.write_target(&target)?; // one f32 per row
|
|
98
|
+
let _features = init.finish(); // get bin thresholds data
|
|
99
|
+
|
|
100
|
+
// 2. Build the booster over the on-disk dataset.
|
|
101
|
+
let mut model = BoostModel::<u8, u32, SquaredLoss>::init_large(options)?;
|
|
102
|
+
|
|
103
|
+
// 3. Grow rounds; each streams batches from disk.
|
|
104
|
+
model.fit_next()?;
|
|
105
|
+
model.fit_next()?;
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
> **Very wide datasets.** If the row count exceeds `u32::MAX`, enable the
|
|
109
|
+
> `large-index` cargo feature and instantiate with a `u64` index
|
|
110
|
+
> (`BoostModel::<u8, u64, SquaredLoss>`).
|
|
111
|
+
>
|
|
112
|
+
> **Many columns on multi-core machines.** In the parallel update path, each
|
|
113
|
+
> rayon worker holds one open file handle per feature column (plus the target
|
|
114
|
+
> and prediction files). With `T` threads and `C` columns this is roughly
|
|
115
|
+
> `T × (C + 2)` open descriptors at once, so a wide dataset on a many-core
|
|
116
|
+
> system can exhaust the process or system file-descriptor limit
|
|
117
|
+
> (`EMFILE`/`ENFILE`). Raise `ulimit -n`, or cap the worker count
|
|
118
|
+
> (e.g. via `RAYON_NUM_THREADS`) if you hit this.
|
|
119
|
+
|
|
120
|
+
## Training in-memory
|
|
121
|
+
|
|
122
|
+
For data that fits in RAM, use the `Booster` trait: build a boxed, object-safe
|
|
123
|
+
booster over in-memory feature columns, grow it, and predict. `inputs` holds
|
|
124
|
+
one column per feature, in feature order; `target` has one value per row.
|
|
125
|
+
|
|
126
|
+
```rust
|
|
127
|
+
use gboost::boost_api::{new_booster, Booster};
|
|
128
|
+
use gboost::options::{ObjectiveKind, Options};
|
|
129
|
+
|
|
130
|
+
let inputs: Vec<Vec<f32>> = vec![/* one column: one f32 per row */; /* n_features */];
|
|
131
|
+
let cols = inputs.iter().map(|c| c.as_slice()).collect::<Vec<_>>();
|
|
132
|
+
let target: Vec<f32> = vec![/* per-row */];
|
|
133
|
+
|
|
134
|
+
let options = Options::default()
|
|
135
|
+
.set_objective(ObjectiveKind::SquaredLoss)
|
|
136
|
+
.set_learning_rate(0.1);
|
|
137
|
+
|
|
138
|
+
let mut booster = new_booster::<f32>(options, &cols, &target)?;
|
|
139
|
+
|
|
140
|
+
// Grow up to 50 rounds; stops early if a round adds no tree.
|
|
141
|
+
booster.fit(50)?;
|
|
142
|
+
|
|
143
|
+
let predictions = booster.predict(&cols)?;
|
|
144
|
+
let per_row_loss = booster.loss();
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Growing round by round
|
|
148
|
+
|
|
149
|
+
`fit` is a convenience wrapper over `fit_next`. Each call to `fit_next`
|
|
150
|
+
grows one tree and folds it into the ensemble. To control the stopping
|
|
151
|
+
condition yourself, loop until it reports no gain:
|
|
152
|
+
|
|
153
|
+
```rust
|
|
154
|
+
use gboost::boost_api::Booster;
|
|
155
|
+
|
|
156
|
+
while booster.fit_next()? {
|
|
157
|
+
// keep boosting
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`fit_next` returns `Ok(false)` when a round produced no tree (no gain), which
|
|
162
|
+
is the natural early-stopping signal.
|
|
163
|
+
|
|
164
|
+
### Explicit feature descriptors
|
|
165
|
+
|
|
166
|
+
`new_booster` synthesizes `linear` features named by position. To supply your
|
|
167
|
+
own names and kinds, use `new_with_features`; bin boundaries are still refit
|
|
168
|
+
from the inputs. Each column is either `linear` (ordered threshold splits) or
|
|
169
|
+
`onehot` (exact-bin splits for categoricals).
|
|
170
|
+
|
|
171
|
+
```rust
|
|
172
|
+
use gboost::boost_api::{new_with_features, Booster};
|
|
173
|
+
use gboost::dataset::Feature;
|
|
174
|
+
use gboost::options::Options;
|
|
175
|
+
|
|
176
|
+
let inputs: Vec<Vec<f32>> = vec![/* one column per feature */];
|
|
177
|
+
let cols = inputs.iter().map(|c| c.as_slice()).collect::<Vec<_>>();
|
|
178
|
+
let target: Vec<f32> = vec![/* per-row */];
|
|
179
|
+
|
|
180
|
+
let options = Options::default().set_learning_rate(0.1);
|
|
181
|
+
|
|
182
|
+
let features = vec![
|
|
183
|
+
Feature::linear("age", None), // ordered threshold
|
|
184
|
+
Feature::onehot("country", None), // exact-bin categorical
|
|
185
|
+
];
|
|
186
|
+
let mut booster = new_with_features::<f32>(options, &cols, &target, features)?;
|
|
187
|
+
booster.fit(50)?;
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Options
|
|
191
|
+
|
|
192
|
+
`Options` aggregates booster-, tree-, and node-level settings. Parameter names
|
|
193
|
+
and defaults mirror XGBoost, plus a couple of gboost-specific options. Every
|
|
194
|
+
field is reachable through self-consuming `set_*` methods, so a fully
|
|
195
|
+
configured value is built in one chain from `Options::default()`:
|
|
196
|
+
|
|
197
|
+
```rust
|
|
198
|
+
let options = Options::default()
|
|
199
|
+
.set_objective(ObjectiveKind::LogLoss) // classification
|
|
200
|
+
.set_base_score(0.5)
|
|
201
|
+
.set_learning_rate(0.1)
|
|
202
|
+
.set_max_depth(10) // 0 = no limit
|
|
203
|
+
.set_max_leaves(100) // 0 = no limit
|
|
204
|
+
.set_allow_missing_splits(true);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Options round-trip to a portable JSON config:
|
|
208
|
+
|
|
209
|
+
```rust
|
|
210
|
+
let json = options.to_json(); // defaults and `persist_path` are pruned
|
|
211
|
+
let restored = Options::from_json(json)?;
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`persist_path` is machine-local and is never serialized; re-supply it via
|
|
215
|
+
`set_persist_path` after `from_json`.
|
|
216
|
+
|
|
217
|
+
## Python bindings
|
|
218
|
+
|
|
219
|
+
A Python package is provided under [`py-gboost/`](py-gboost/). See that
|
|
220
|
+
directory for its own setup and tests.
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT — see [LICENSE](LICENSE).
|