Skip to content

Commit 677f7e5

Browse files
RicardoSantos-99josevalim
authored andcommitted
Keep NaiveBayes class_log_priors in the model's float type
class_log_priors was computed in f32 regardless of the input type on two of its three branches, while class_count and feature_log_probability followed the input, so an f64 model silently carried an f32 prior into every joint log-likelihood: * explicit priors: class_priors was built with Nx.tensor/1 without type:, unlike alpha and sample_weights next to it, which both pass type: to_float_type(x). * fit_priors: false: Nx.log/1 on the bare num_classes integer defaults to f32. Multiplying a typed literal by num_classes keeps the log in the target type rather than widening an f32 result afterwards, which would keep f32 precision. Verified that -log(3) and log(0.3) now come back bit-exact against the f64 reference (previously off by ~2e-8), and that class_log_priors matches feature_log_probability's type across all three prior branches, both backends. Applies to all four modules since the block is identical in each.
1 parent 561e795 commit 677f7e5

3 files changed

Lines changed: 37 additions & 18 deletions

File tree

lib/scholar/naive_bayes/bernoulli.ex

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ defmodule Scholar.NaiveBayes.Bernoulli do
1010
import Scholar.Shared
1111

1212
@derive {Nx.Container,
13+
keep: [:binarize],
1314
containers: [
1415
:feature_count,
1516
:class_count,
1617
:class_log_priors,
1718
:feature_log_probability
1819
]}
19-
defstruct [:feature_count, :class_count, :class_log_priors, :feature_log_probability]
20+
defstruct [:feature_count, :class_count, :class_log_priors, :feature_log_probability, :binarize]
2021

2122
opts_schema = [
2223
num_classes: [
@@ -27,7 +28,12 @@ defmodule Scholar.NaiveBayes.Bernoulli do
2728
"""
2829
],
2930
alpha: [
30-
type: {:or, [:float, {:list, :float}]},
31+
type:
32+
{:or,
33+
[
34+
{:custom, Scholar.Options, :non_negative_number, []},
35+
{:list, {:custom, Scholar.Options, :non_negative_number, []}}
36+
]},
3137
default: 1.0,
3238
doc: ~S"""
3339
Additive (Laplace/Lidstone) smoothing parameter
@@ -127,7 +133,8 @@ defmodule Scholar.NaiveBayes.Bernoulli do
127133
[-1.0986123085021973, -1.0986123085021973, -0.40546512603759766],
128134
[-0.28768205642700195, -0.28768205642700195, -0.28768205642700195]
129135
]
130-
)
136+
),
137+
binarize: 1.0
131138
}
132139
133140
iex> x = Nx.iota({4, 3})
@@ -153,7 +160,8 @@ defmodule Scholar.NaiveBayes.Bernoulli do
153160
[-23.025850296020508, 0.0, 0.0],
154161
[0.0, 0.0, 0.0]
155162
]
156-
)
163+
),
164+
binarize: 0.0
157165
}
158166
"""
159167

@@ -203,7 +211,7 @@ defmodule Scholar.NaiveBayes.Bernoulli do
203211
priors_flag = opts[:class_priors] != nil
204212

205213
{class_priors, opts} = Keyword.pop(opts, :class_priors, :nan)
206-
class_priors = Nx.tensor(class_priors)
214+
class_priors = Nx.tensor(class_priors, type: type)
207215

208216
if priors_flag and Nx.size(class_priors) != num_classes do
209217
raise ArgumentError,
@@ -281,14 +289,16 @@ defmodule Scholar.NaiveBayes.Bernoulli do
281289
Nx.log(class_count) - Nx.log(Nx.sum(class_count))
282290

283291
true ->
284-
Nx.broadcast(-Nx.log(num_classes), {num_classes})
292+
num_classes_t = Nx.tensor(1.0, type: type) * num_classes
293+
Nx.broadcast(-Nx.log(num_classes_t), {num_classes})
285294
end
286295

287296
%__MODULE__{
288297
class_count: class_count,
289298
class_log_priors: class_log_priors,
290299
feature_count: feature_count,
291-
feature_log_probability: feature_log_probability
300+
feature_log_probability: feature_log_probability,
301+
binarize: opts[:binarize]
292302
}
293303
end
294304

@@ -343,8 +353,8 @@ defmodule Scholar.NaiveBayes.Bernoulli do
343353
#Nx.Tensor<
344354
f32[2][3]
345355
[
346-
[-4.7047806, -12.329399, -0.009097099],
347-
[-8.750494, -19.147701, -1.5830994e-4]
356+
[-1.4696369, -2.162784, -0.42314053],
357+
[-1.4696369, -2.162784, -0.42314053]
348358
]
349359
>
350360
"""
@@ -373,8 +383,8 @@ defmodule Scholar.NaiveBayes.Bernoulli do
373383
#Nx.Tensor<
374384
f32[2][3]
375385
[
376-
[0.0090519, 4.419875e-6, 0.99094415],
377-
[1.5838306e-4, 4.8334696e-9, 0.9998417]
386+
[0.23000899, 0.11500449, 0.65498656],
387+
[0.23000899, 0.11500449, 0.65498656]
378388
]
379389
>
380390
"""
@@ -395,8 +405,8 @@ defmodule Scholar.NaiveBayes.Bernoulli do
395405
#Nx.Tensor<
396406
f32[2][3]
397407
[
398-
[3.6356335, -3.988985, 8.331317],
399-
[10.567104, 0.16989732, 19.31744]
408+
[-2.6026897, -3.295837, -1.5561934],
409+
[-2.6026897, -3.295837, -1.5561934]
400410
]
401411
>
402412
"""
@@ -421,10 +431,17 @@ defmodule Scholar.NaiveBayes.Bernoulli do
421431
defnp joint_log_likelihood(
422432
%__MODULE__{
423433
feature_log_probability: feature_log_probability,
424-
class_log_priors: class_log_priors
434+
class_log_priors: class_log_priors,
435+
binarize: binarize
425436
},
426437
x
427438
) do
439+
x =
440+
case binarize do
441+
nil -> x
442+
threshold -> Scholar.Preprocessing.Binarizer.fit_transform(x, threshold: threshold)
443+
end
444+
428445
neg_prob = Nx.log(1 - Nx.exp(feature_log_probability))
429446
jll = Nx.dot(x, [1], feature_log_probability - neg_prob, [1])
430447
jll + class_log_priors + Nx.sum(neg_prob, axes: [1])

lib/scholar/naive_bayes/complement.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ defmodule Scholar.NaiveBayes.Complement do
186186
sample_weights = Nx.tensor(sample_weights, type: x_type)
187187

188188
{priors, opts} = Keyword.pop(opts, :priors, Nx.tensor(0.0, type: x_type))
189-
class_priors = Nx.tensor(priors)
189+
class_priors = Nx.tensor(priors, type: x_type)
190190
{alpha, opts} = Keyword.pop!(opts, :alpha)
191191
alpha = Nx.tensor(alpha, type: x_type)
192192

@@ -385,7 +385,8 @@ defmodule Scholar.NaiveBayes.Complement do
385385
Nx.log(class_count) - Nx.log(Nx.sum(class_count))
386386

387387
true ->
388-
Nx.broadcast(-Nx.log(num_classes), {num_classes})
388+
num_classes_t = Nx.tensor(1.0, type: x_type) * num_classes
389+
Nx.broadcast(-Nx.log(num_classes_t), {num_classes})
389390
end
390391

391392
%__MODULE__{

lib/scholar/naive_bayes/multinomial.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ defmodule Scholar.NaiveBayes.Multinomial do
197197
priors_flag = opts[:class_priors] != nil
198198

199199
{class_priors, opts} = Keyword.pop(opts, :class_priors, :nan)
200-
class_priors = Nx.tensor(class_priors)
200+
class_priors = Nx.tensor(class_priors, type: type)
201201

202202
if priors_flag and Nx.size(class_priors) != num_classes do
203203
raise ArgumentError,
@@ -271,7 +271,8 @@ defmodule Scholar.NaiveBayes.Multinomial do
271271
Nx.log(class_count) - Nx.log(Nx.sum(class_count))
272272

273273
true ->
274-
Nx.broadcast(-Nx.log(num_classes), {num_classes})
274+
num_classes_t = Nx.tensor(1.0, type: type) * num_classes
275+
Nx.broadcast(-Nx.log(num_classes_t), {num_classes})
275276
end
276277

277278
%__MODULE__{

0 commit comments

Comments
 (0)