sylearn 0.1.0__py3-none-any.whl
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.
- sylearn/README.md +47 -0
- sylearn/__init__.py +7 -0
- sylearn/decision_tree.py +98 -0
- sylearn/linear_regression.py +24 -0
- sylearn/random_forest.py +33 -0
- sylearn-0.1.0.dist-info/METADATA +63 -0
- sylearn-0.1.0.dist-info/RECORD +9 -0
- sylearn-0.1.0.dist-info/WHEEL +4 -0
- sylearn-0.1.0.dist-info/licenses/LICENSE +21 -0
sylearn/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# sylearn
|
|
2
|
+
|
|
3
|
+
Regression models written from scratch with only NumPy. No scikit-learn inside.
|
|
4
|
+
|
|
5
|
+
- `syLinearRegression`: ordinary least squares, solved with the pseudo-inverse
|
|
6
|
+
- `syDecisionTreeRegressor`: a regression tree that picks splits by mean squared error
|
|
7
|
+
- `syRandomForestRegressor`: bagging (bootstrap samples) over `syDecisionTreeRegressor`
|
|
8
|
+
|
|
9
|
+
They were built to learn how these models work inside. On an air-quality (AQI) dataset they reach the same R² as scikit-learn's models with the same settings.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install sylearn
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Use
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import numpy as np
|
|
21
|
+
from sylearn import syLinearRegression, syDecisionTreeRegressor, syRandomForestRegressor
|
|
22
|
+
|
|
23
|
+
X = np.random.rand(200, 3)
|
|
24
|
+
y = 4 * X[:, 0] - 2 * X[:, 1] + np.random.normal(0, 0.1, 200)
|
|
25
|
+
|
|
26
|
+
lr = syLinearRegression()
|
|
27
|
+
weights, intercept = lr.fit(X, y) # fit returns the learned weights and intercept
|
|
28
|
+
print(lr.predict(X[:5]))
|
|
29
|
+
|
|
30
|
+
tree = syDecisionTreeRegressor(max_depth=5, min_samples_split=2).fit(X, y)
|
|
31
|
+
print(tree.predict(X[:5]))
|
|
32
|
+
|
|
33
|
+
np.random.seed(42) # the forest uses np.random, so seed it for repeatable results
|
|
34
|
+
forest = syRandomForestRegressor(n_trees=10, max_depth=5).fit(X, y)
|
|
35
|
+
print(forest.predict(X[:5]))
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`X` can be a NumPy array, a list of lists or a pandas DataFrame. `y` is a 1-D array.
|
|
39
|
+
|
|
40
|
+
## Good to know
|
|
41
|
+
|
|
42
|
+
- The decision tree tries every possible split, so training gets slow on large datasets (tens of thousands of rows).
|
|
43
|
+
- The models do not handle missing values. Fill or drop NaN values first.
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
MIT
|
sylearn/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""My from-scratch ML models (no sklearn inside)."""
|
|
2
|
+
from sylearn.linear_regression import syLinearRegression
|
|
3
|
+
from sylearn.decision_tree import Node, syDecisionTreeRegressor
|
|
4
|
+
from sylearn.random_forest import syRandomForestRegressor
|
|
5
|
+
|
|
6
|
+
__version__ = "0.1.0"
|
|
7
|
+
__all__ = ["syLinearRegression", "Node", "syDecisionTreeRegressor", "syRandomForestRegressor"]
|
sylearn/decision_tree.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Node:
|
|
5
|
+
def __init__(self, feature=None,threshold=None,left=None,right=None,prediction=None):
|
|
6
|
+
self.feature = feature
|
|
7
|
+
self.threshold = threshold
|
|
8
|
+
self.left = left
|
|
9
|
+
self.right = right
|
|
10
|
+
self.prediction = prediction
|
|
11
|
+
|
|
12
|
+
class syDecisionTreeRegressor:
|
|
13
|
+
|
|
14
|
+
def __init__(self, max_depth=5, min_samples_split=2):
|
|
15
|
+
self.max_depth = max_depth
|
|
16
|
+
self.min_samples_split = min_samples_split
|
|
17
|
+
self.root = None
|
|
18
|
+
|
|
19
|
+
def fit(self, X, y):
|
|
20
|
+
X = np.array(X)
|
|
21
|
+
y = np.array(y)
|
|
22
|
+
self.root = self.build_me_a_tree(X, y)
|
|
23
|
+
return self
|
|
24
|
+
|
|
25
|
+
def predict(self, X):
|
|
26
|
+
X = np.array(X)
|
|
27
|
+
predictions = []
|
|
28
|
+
for x in X:
|
|
29
|
+
predictions.append(self.predict_one(x, self.root))
|
|
30
|
+
return np.array(predictions)
|
|
31
|
+
|
|
32
|
+
def build_me_a_tree(self, X, y, depth=0):
|
|
33
|
+
if depth>=self.max_depth or len(y)< self.min_samples_split:
|
|
34
|
+
prediction =np.mean(y)
|
|
35
|
+
return Node(prediction=prediction)
|
|
36
|
+
|
|
37
|
+
feature, threshold = self.find_best_split(X, y)
|
|
38
|
+
|
|
39
|
+
if feature is None:
|
|
40
|
+
prediction = np.mean(y)
|
|
41
|
+
return Node(prediction=prediction)
|
|
42
|
+
|
|
43
|
+
left_mask = X[:, feature] <= threshold
|
|
44
|
+
right_mask = X[:, feature] > threshold
|
|
45
|
+
|
|
46
|
+
X_left = X[left_mask]
|
|
47
|
+
X_right = X[right_mask]
|
|
48
|
+
y_left = y[left_mask]
|
|
49
|
+
y_right = y[right_mask]
|
|
50
|
+
|
|
51
|
+
left_node = self.build_me_a_tree(X_left, y_left, depth + 1)
|
|
52
|
+
right_node = self.build_me_a_tree(X_right, y_right, depth + 1)
|
|
53
|
+
|
|
54
|
+
return Node(feature=feature, threshold=threshold, left=left_node, right=right_node)
|
|
55
|
+
|
|
56
|
+
def find_best_split(self, X, y):
|
|
57
|
+
best_mse = float("inf")
|
|
58
|
+
best_feature = None
|
|
59
|
+
best_threshold = None
|
|
60
|
+
|
|
61
|
+
for i in range(X.shape[1]):
|
|
62
|
+
values = np.unique(X[:, i])
|
|
63
|
+
thresholds = (values[:-1] + values[1:])/2
|
|
64
|
+
#This part is not optimized by me. For now I am goining with this unomptimized version
|
|
65
|
+
for threshold in thresholds:
|
|
66
|
+
left = X[:, i] <= threshold
|
|
67
|
+
right = X[:, i] > threshold
|
|
68
|
+
|
|
69
|
+
y_left = y[left]
|
|
70
|
+
y_right = y[right]
|
|
71
|
+
|
|
72
|
+
if len(y_left) == 0 or len(y_right) == 0:
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
mse_left = self.calculate_mse(y_left)
|
|
76
|
+
mse_right = self.calculate_mse(y_right)
|
|
77
|
+
|
|
78
|
+
weighted_mse = (len(y_left)/len(y))*mse_left + (len(y_right)/len(y))*mse_right
|
|
79
|
+
|
|
80
|
+
if weighted_mse < best_mse:
|
|
81
|
+
best_mse = weighted_mse
|
|
82
|
+
best_feature = i
|
|
83
|
+
best_threshold = threshold
|
|
84
|
+
|
|
85
|
+
return best_feature, best_threshold
|
|
86
|
+
|
|
87
|
+
def calculate_mse(self, y):
|
|
88
|
+
mean = np.mean(y)
|
|
89
|
+
return np.mean((y-mean)**2)
|
|
90
|
+
|
|
91
|
+
def predict_one(self, x, node):
|
|
92
|
+
if node.prediction is not None:
|
|
93
|
+
return node.prediction
|
|
94
|
+
|
|
95
|
+
if x[node.feature] <= node.threshold:
|
|
96
|
+
return self.predict_one(x, node.left)
|
|
97
|
+
else:
|
|
98
|
+
return self.predict_one(x, node.right)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class syLinearRegression:
|
|
5
|
+
"""Linear Regression from scratch using OLS: w = pinv(X) @ y."""
|
|
6
|
+
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.weights = None
|
|
9
|
+
self.intercept_ = None
|
|
10
|
+
|
|
11
|
+
def fit(self, X, y):
|
|
12
|
+
npX = np.array(X)
|
|
13
|
+
npX = np.c_[np.ones(npX.shape[0]), npX]
|
|
14
|
+
npy = np.array(y)
|
|
15
|
+
# self.weights= np.linalg.inv((npX.T@ npX)) @ npX.T @ npy
|
|
16
|
+
mixed = np.linalg.pinv(npX) @ npy
|
|
17
|
+
|
|
18
|
+
self.intercept_ = mixed[0]
|
|
19
|
+
self.weights = mixed[1:]
|
|
20
|
+
return self.weights, self.intercept_
|
|
21
|
+
|
|
22
|
+
def predict(self, X):
|
|
23
|
+
npX = np.array(X)
|
|
24
|
+
return npX @ self.weights + self.intercept_
|
sylearn/random_forest.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from sylearn.decision_tree import syDecisionTreeRegressor
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class syRandomForestRegressor:
|
|
7
|
+
"""Random Forest from scratch: bagging over my own decision trees."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, n_trees=10, max_depth=5):
|
|
10
|
+
self.n_trees = n_trees
|
|
11
|
+
self.max_depth = max_depth
|
|
12
|
+
self.trees = []
|
|
13
|
+
|
|
14
|
+
def fit(self, X, y):
|
|
15
|
+
X = np.array(X)
|
|
16
|
+
y = np.array(y)
|
|
17
|
+
self.trees = []
|
|
18
|
+
for i in range(self.n_trees):
|
|
19
|
+
ids = np.random.choice(len(X), len(X), replace=True)
|
|
20
|
+
X_new = X[ids]
|
|
21
|
+
y_new = y[ids]
|
|
22
|
+
tree = syDecisionTreeRegressor(max_depth=self.max_depth)
|
|
23
|
+
tree.fit(X_new, y_new)
|
|
24
|
+
self.trees.append(tree)
|
|
25
|
+
return self
|
|
26
|
+
|
|
27
|
+
def predict(self, X):
|
|
28
|
+
results = []
|
|
29
|
+
for tree in self.trees:
|
|
30
|
+
pred = tree.predict(X)
|
|
31
|
+
results.append(pred)
|
|
32
|
+
final = np.mean(results, axis=0)
|
|
33
|
+
return final
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: sylearn
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Linear regression, decision tree and random forest regressors written from scratch in NumPy.
|
|
5
|
+
Author: Shashank
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: decision tree,from scratch,linear regression,machine learning,numpy,random forest
|
|
9
|
+
Classifier: Intended Audience :: Education
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Requires-Dist: numpy>=1.22
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# sylearn
|
|
18
|
+
|
|
19
|
+
Regression models written from scratch with only NumPy. No scikit-learn inside.
|
|
20
|
+
|
|
21
|
+
- `syLinearRegression`: ordinary least squares, solved with the pseudo-inverse
|
|
22
|
+
- `syDecisionTreeRegressor`: a regression tree that picks splits by mean squared error
|
|
23
|
+
- `syRandomForestRegressor`: bagging (bootstrap samples) over `syDecisionTreeRegressor`
|
|
24
|
+
|
|
25
|
+
They were built to learn how these models work inside. On an air-quality (AQI) dataset they reach the same R² as scikit-learn's models with the same settings.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install sylearn
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Use
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import numpy as np
|
|
37
|
+
from sylearn import syLinearRegression, syDecisionTreeRegressor, syRandomForestRegressor
|
|
38
|
+
|
|
39
|
+
X = np.random.rand(200, 3)
|
|
40
|
+
y = 4 * X[:, 0] - 2 * X[:, 1] + np.random.normal(0, 0.1, 200)
|
|
41
|
+
|
|
42
|
+
lr = syLinearRegression()
|
|
43
|
+
weights, intercept = lr.fit(X, y) # fit returns the learned weights and intercept
|
|
44
|
+
print(lr.predict(X[:5]))
|
|
45
|
+
|
|
46
|
+
tree = syDecisionTreeRegressor(max_depth=5, min_samples_split=2).fit(X, y)
|
|
47
|
+
print(tree.predict(X[:5]))
|
|
48
|
+
|
|
49
|
+
np.random.seed(42) # the forest uses np.random, so seed it for repeatable results
|
|
50
|
+
forest = syRandomForestRegressor(n_trees=10, max_depth=5).fit(X, y)
|
|
51
|
+
print(forest.predict(X[:5]))
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`X` can be a NumPy array, a list of lists or a pandas DataFrame. `y` is a 1-D array.
|
|
55
|
+
|
|
56
|
+
## Good to know
|
|
57
|
+
|
|
58
|
+
- The decision tree tries every possible split, so training gets slow on large datasets (tens of thousands of rows).
|
|
59
|
+
- The models do not handle missing values. Fill or drop NaN values first.
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
sylearn/README.md,sha256=2Tkslizy3BxzP33VRz8ScS_xuKT0vGbkRXX7Yc3NU8A,1521
|
|
2
|
+
sylearn/__init__.py,sha256=rMjrVVufYwfPva8G0PYt-MTEZSKNl37FDk6rkScaBdo,350
|
|
3
|
+
sylearn/decision_tree.py,sha256=gkjajwSwl72UiT01C64z7YP1Pf7HkC1IU8Gfs--pwoY,3074
|
|
4
|
+
sylearn/linear_regression.py,sha256=q0qXZuWrP_okipY3PxpVZoZKQiVoIg1_JnB5z2O-_-k,653
|
|
5
|
+
sylearn/random_forest.py,sha256=e33yI5HYz_NC7s0v1muqQpG39TMAzUXc0vkTPOutcdI,950
|
|
6
|
+
sylearn-0.1.0.dist-info/METADATA,sha256=SkUaP3F_j4Oe1CE7wYSd-upts4tXbBfOwjFKIOOwNfs,2129
|
|
7
|
+
sylearn-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
8
|
+
sylearn-0.1.0.dist-info/licenses/LICENSE,sha256=eYuSzROvJkRDDgu8C_crfO_sPXaxh8N5HoniowLrz94,1065
|
|
9
|
+
sylearn-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shashank
|
|
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.
|