Skip to content

Commit fc5b2bc

Browse files
authored
Merge pull request #54 from vyron-arvanitis/dev-emanuel
Feedbackround I
2 parents b7a2394 + c3a9836 commit fc5b2bc

18 files changed

Lines changed: 218 additions & 166 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,4 @@ target/
7575
.LSOverride
7676

7777
# auto-generated files
78-
bde/_version.py
78+
bde/_version.py

.idea/bde.iml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/dictionaries/project.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/inspectionProfiles/profiles_settings.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/modules.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/vcs.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
bde: Bayesian Deep Ensembles for scikit-learn and JAX
1+
bde: Bayesian Deep Ensembles for scikit-learn
22
====================================================
33

44
![tests](https://github.com/scikit-learn-contrib/bde/actions/workflows/python-app.yml/badge.svg)
@@ -13,6 +13,14 @@ both scikit-learn and JAX. It exposes estimators that plug into scikit-learn
1313
pipelines while leveraging JAX for accelerator-backed training, sampling, and
1414
uncertainty estimation.
1515

16+
In particular, **bde** implements **Microcanonical Langevin Ensembles (MILE)** as
17+
introduced in [*Microcanonical Langevin Ensembles: Advancing the Sampling of Bayesian Neural Networks* (ICLR 2025)](https://arxiv.org/abs/2502.06335).
18+
A conceptual overview of MILE is shown below (in general implementation details of this package are not exactly matching this diagram):
19+
20+
<div style="width: 60%; margin: auto;">
21+
<img src="doc/_static/img/flowchart.png" alt="MILE Overview" style="width: 100%;">
22+
</div>
23+
1624
Installation
1725
------------
1826

@@ -42,10 +50,11 @@ Example Usage
4250

4351
Minimal runnable scripts live in `examples/`, and the snippets below highlight the
4452
most common regression and classification workflows. When running outside those
45-
scripts, remember to set the XLA device count so JAX allocates enough host devices:
46-
NOTE MENTNION THAT HTHE EXPORT NEEDS TO BE DONE BEFORE JAX
53+
scripts, remember to set the XLA device count so JAX allocates enough host devices (
54+
this needs to be done before importing JAX):
55+
4756
```
48-
export XLA_FLAGS="--xla_force_host_platform_device_count=8"
57+
export XLA_FLAGS="--xla_force_host_platform_device_count=8"
4958
```
5059

5160
Adjust the value to match the number of CPU (or GPU) devices you plan to use.
@@ -67,16 +76,13 @@ from bde.loss import GaussianNLL
6776

6877

6978
data = fetch_openml(name="airfoil_self_noise", as_frame=True)
70-
X = data.data.values # shape (1503, 5)
71-
y = data.target.values.reshape(-1, 1) # shape (1503, 1)
7279

80+
X = data.data.values
81+
y = data.target.values.reshape(-1, 1)
7382
X_train, X_test, y_train, y_test = train_test_split(
74-
X,
75-
y,
76-
test_size=0.2,
77-
random_state=0,
83+
X, y, test_size=0.2, random_state=42
7884
)
79-
# Normalize data
85+
8086
Xmu, Xstd = jnp.mean(X_train, 0), jnp.std(X_train, 0) + 1e-8
8187
Ymu, Ystd = jnp.mean(y_train, 0), jnp.std(y_train, 0) + 1e-8
8288

@@ -87,26 +93,29 @@ yte = (y_test - Ymu) / Ystd
8793

8894
regressor = BdeRegressor(
8995
hidden_layers=[16, 16],
90-
n_members=20,
96+
n_members=8,
9197
seed=0,
9298
loss=GaussianNLL(),
9399
epochs=200,
94100
lr=1e-3,
95-
warmup_steps=500,
96-
n_samples=100,
97-
n_thinning=1,
101+
warmup_steps=5000, # 50k in the original paper
102+
n_samples=2000, # 10k in the original paper
103+
n_thinning=2,
98104
patience=10,
99105
)
100106

101107
regressor.fit(x=Xtr, y=ytr)
102108

103-
mean, std = regressor.predict(jnp.array(X_test), mean_and_std=True)
104-
mu, intervals = regressor.predict(Xte, credible_intervals=[0.9, 0.95])
105-
raw = regressor.predict(Xte, raw=True)
106-
print("RSME: ", root_mean_squared_error(y_true=yte, y_pred=mean))
107-
score = regressor.score(Xtr, ytr)
108-
print(f"the sklearn score is {score}")
109+
means, sigmas = regressor.predict(Xte, mean_and_std=True)
110+
111+
print("RSME: ", root_mean_squared_error(y_true=yte, y_pred=means))
109112

113+
mean, intervals = regressor.predict(Xte, credible_intervals=[0.1, 0.9])
114+
115+
lower = intervals[0]
116+
upper = intervals[1]
117+
coverage = jnp.mean((yte.ravel() >= lower) & (yte.ravel() <= upper))
118+
print(f"Coverage of the 80% credible interval: {coverage * 100:.2f}%")
110119

111120
```
112121

@@ -125,32 +134,32 @@ from bde.loss import CategoricalCrossEntropy
125134

126135
iris = load_iris()
127136
X = iris.data.astype("float32")
128-
y = iris.target.astype("int32").ravel()
137+
y = iris.target.astype("int32").ravel() # 0, 1, 2
129138
X_train, X_test, y_train, y_test = train_test_split(
130-
X, y, test_size=0.2, random_state=42)
139+
X, y, test_size=0.2, random_state=42
140+
)
141+
131142
classifier = BdeClassifier(
132-
n_members=2,
143+
n_members=4,
133144
hidden_layers=[16, 16],
134145
seed=0,
135146
loss=CategoricalCrossEntropy(),
136147
activation="relu",
137-
epochs=4,
148+
epochs=100,
138149
lr=1e-3,
139-
warmup_steps=50,
140-
n_samples=2,
150+
warmup_steps=400, # very few steps required for this simple dataset
151+
n_samples=100,
141152
n_thinning=1,
142-
patience=2
143-
)
153+
patience=10,
154+
)
155+
144156
classifier.fit(x=X_train, y=y_train)
157+
145158
preds = classifier.predict(X_test)
146159
probs = classifier.predict_proba(X_test)
147-
score = classifier.score(X_train, y_train)
148-
raw = classifier.predict(X_test, raw=True)
149-
print("Predicted class probabilities:\n", probs)
150-
print("Predicted class labels:\n", preds)
151-
print("True labels:\n", y_test)
152-
print(f"the sklearn score is {score}")
153-
print(f"The shape of the raw predictions are {raw.shape}")
160+
161+
accuracy = jnp.mean(preds == y_test)
162+
print(f"Test accuracy: {accuracy * 100:.2f}%")
154163
```
155164

156165
Workflow
@@ -210,7 +219,3 @@ flowchart TD
210219
Cache --> EvalCall --> MakePred --> Predictor --> Outputs
211220
Posterior --> Predictor
212221
```
213-
214-
215-
Mathematical Background
216-
-----------------------

bde/bde.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,8 @@ def predict(
588588
x: ArrayLike,
589589
mean_and_std: bool = False,
590590
credible_intervals: list[float] | None = None,
591+
# Docstring necessary to explain this parameter which
592+
# actually lists quantiles not the intervals
591593
raw: bool = False,
592594
):
593595
out = self._evaluate(

bde/bde_evaluator.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,13 @@ def _predict_regression(
118118
if mean_and_std:
119119
out["std"] = std_total
120120
if credible_intervals:
121+
# this diregards the sigmas and uses only the mus which is an ok strategy
122+
# (the cheapest one, could be a default)
123+
# if you want to incorporate the sigmas you could sample for example n
124+
# (n=1 for more than 1000 samples and n=10 for less than 1000 samples)
125+
# predictions from each mu,sigma (gaussian) pair and then compute the
126+
# quantiles over all these sampled predictions - likely a better
127+
# strategy but more expensive.
121128
qs = jnp.quantile(mu, q=jnp.array(credible_intervals), axis=(0, 1))
122129
out["credible_intervals"] = qs
123130
if raw:

0 commit comments

Comments
 (0)