Skip to content

Commit 193e20b

Browse files
committed
fix: 5 bug fixes across linear models, trees, KNN, and Bayesian regression
- LinearRegression: fix np.squeeze producing 0-d weights when N=1 (fixes #77) - DecisionTree: handle empty child splits to prevent ZeroDivisionError (fixes #58) - KNN: add epsilon to prevent division by zero when distance=0 - BayesianRegression: replace np.linalg.inv with pinv for numerical stability - GP: replace bare except with ImportError for scipy import
1 parent b0359af commit 193e20b

5 files changed

Lines changed: 15 additions & 11 deletions

File tree

numpy_ml/linear_models/bayesian_regression.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def fit(self, X, y):
111111
# sigma
112112
I = np.eye(N) # noqa: E741
113113
a = y - (X @ mu)
114-
b = np.linalg.inv(X @ V @ X.T + I)
114+
b = np.linalg.pinv(X @ V @ X.T + I)
115115
c = y - (X @ mu)
116116

117117
shape = N + alpha
@@ -122,8 +122,8 @@ def fit(self, X, y):
122122
sigma = scale / (shape - 1)
123123

124124
# mean
125-
V_inv = np.linalg.inv(V)
126-
L = np.linalg.inv(V_inv + X.T @ X)
125+
V_inv = np.linalg.pinv(V)
126+
L = np.linalg.pinv(V_inv + X.T @ X)
127127
R = V_inv @ mu + X.T @ y
128128

129129
mu = L @ R
@@ -263,8 +263,8 @@ def fit(self, X, y):
263263
mu = self.mu
264264
sigma = self.sigma
265265

266-
V_inv = np.linalg.inv(V)
267-
L = np.linalg.inv(V_inv + X.T @ X)
266+
V_inv = np.linalg.pinv(V)
267+
L = np.linalg.pinv(V_inv + X.T @ X)
268268
R = V_inv @ mu + X.T @ y
269269

270270
mu = L @ R

numpy_ml/linear_models/linear_regression.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def fit(self, X, y, weights=None):
197197
N = X.shape[0]
198198

199199
weights = np.ones(N) if weights is None else np.atleast_1d(weights)
200-
weights = np.squeeze(weights) if weights.size > 1 else weights
200+
weights = np.atleast_1d(np.squeeze(weights)) if weights.size > 1 else weights
201201
err_str = f"weights must have shape ({N},) but got {weights.shape}"
202202
assert weights.shape == (N,), err_str
203203

numpy_ml/nonparametric/gp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
try:
66
_SCIPY = True
77
from scipy.stats import norm
8-
except:
8+
except ImportError:
99
_SCIPY = False
1010
warnings.warn(
1111
"Could not import scipy.stats. Confidence scores "

numpy_ml/nonparametric/knn.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def fit(self, X, y):
5656
Targets for the `N` rows in `X`.
5757
"""
5858
if X.ndim != 2:
59-
raise Exception("X must be two-dimensional")
59+
raise ValueError("X must be two-dimensional")
6060
self._ball_tree.fit(X, y)
6161

6262
def predict(self, X):
@@ -88,14 +88,15 @@ def predict(self, X):
8888
pred, _ = sorted(counts, key=lambda x: (-x[1], x[0]))[0]
8989
elif H["weights"] == "distance":
9090
best_score = -np.inf
91+
eps = np.finfo(float).eps
9192
for label in set(targets):
92-
scores = [1 / n.distance for n in nearest if n.val == label]
93+
scores = [1 / max(n.distance, eps) for n in nearest if n.val == label]
9394
pred = label if np.sum(scores) > best_score else pred
9495
else:
9596
if H["weights"] == "uniform":
9697
pred = np.mean(targets)
9798
elif H["weights"] == "distance":
98-
weights = [1 / n.distance for n in nearest]
99+
weights = [1 / max(n.distance, eps) for n in nearest]
99100
pred = np.average(targets, weights=weights)
100101
predictions.append(pred)
101102
return np.array(predictions)

numpy_ml/trees/dt.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,10 @@ def predict_class_probs(self, X):
120120
return np.array([self._traverse(x, self.root, prob=True) for x in X])
121121

122122
def _grow(self, X, Y, cur_depth=0):
123-
# if all labels are the same, return a leaf
123+
# if all labels are the same, or node is empty, return a leaf
124+
if len(Y) == 0:
125+
prob = np.zeros(self.n_classes) if self.classifier else 0.0
126+
return Leaf(prob)
124127
if len(set(Y)) == 1:
125128
if self.classifier:
126129
prob = np.zeros(self.n_classes)

0 commit comments

Comments
 (0)