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
1313pipelines while leveraging JAX for accelerator-backed training, sampling, and
1414uncertainty 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+
1624Installation
1725------------
1826
@@ -42,10 +50,11 @@ Example Usage
4250
4351Minimal runnable scripts live in ` examples/ ` , and the snippets below highlight the
4452most 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
5160Adjust 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
6978data = 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 )
7382X_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+
8086Xmu, Xstd = jnp.mean(X_train, 0 ), jnp.std(X_train, 0 ) + 1e-8
8187Ymu, Ystd = jnp.mean(y_train, 0 ), jnp.std(y_train, 0 ) + 1e-8
8288
@@ -87,26 +93,29 @@ yte = (y_test - Ymu) / Ystd
8793
8894regressor = 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
101107regressor.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
126135iris = load_iris()
127136X = iris.data.astype(" float32" )
128- y = iris.target.astype(" int32" ).ravel()
137+ y = iris.target.astype(" int32" ).ravel() # 0, 1, 2
129138X_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+
131142classifier = 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+
144156classifier.fit(x = X_train, y = y_train)
157+
145158preds = classifier.predict(X_test)
146159probs = 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
156165Workflow
@@ -210,7 +219,3 @@ flowchart TD
210219 Cache --> EvalCall --> MakePred --> Predictor --> Outputs
211220 Posterior --> Predictor
212221```
213-
214-
215- Mathematical Background
216- -----------------------
0 commit comments