Jensen-holm commited on
Commit
d6cc61a
·
unverified ·
2 Parent(s): ff1254a 498c4e0

Merge pull request #2 from Jensen-holm/dev

Browse files
example.py CHANGED
@@ -1,7 +1,7 @@
1
  from sklearn import datasets
2
  from sklearn.preprocessing import OneHotEncoder
3
  from sklearn.model_selection import train_test_split
4
- from sklearn.metrics import accuracy_score, precision_score, recall_score
5
  import numpy as np
6
  from numpyneuron import (
7
  NN,
@@ -14,7 +14,7 @@ from numpyneuron import (
14
  RANDOM_SEED = 2
15
 
16
 
17
- def _preprocess_digits(
18
  seed: int,
19
  ) -> tuple[np.ndarray, ...]:
20
  digits = datasets.load_digits(as_frame=False)
@@ -30,9 +30,10 @@ def _preprocess_digits(
30
  return X_train, X_test, y_train, y_test
31
 
32
 
33
- def train_nn_classifier() -> None:
34
- X_train, X_test, y_train, y_test = _preprocess_digits(seed=RANDOM_SEED)
35
-
 
36
  nn_classifier = NN(
37
  epochs=2_000,
38
  hidden_size=16,
@@ -50,16 +51,16 @@ def train_nn_classifier() -> None:
50
  X_train=X_train,
51
  y_train=y_train,
52
  )
 
 
53
 
54
- pred = nn_classifier.predict(X_test=X_test)
 
 
55
 
 
56
  pred = np.argmax(pred, axis=1)
57
  y_test = np.argmax(y_test, axis=1)
58
 
59
  accuracy = accuracy_score(y_true=y_test, y_pred=pred)
60
-
61
  print(f"accuracy on validation set: {accuracy:.4f}")
62
-
63
-
64
- if __name__ == "__main__":
65
- train_nn_classifier()
 
1
  from sklearn import datasets
2
  from sklearn.preprocessing import OneHotEncoder
3
  from sklearn.model_selection import train_test_split
4
+ from sklearn.metrics import accuracy_score
5
  import numpy as np
6
  from numpyneuron import (
7
  NN,
 
14
  RANDOM_SEED = 2
15
 
16
 
17
+ def preprocess_digits(
18
  seed: int,
19
  ) -> tuple[np.ndarray, ...]:
20
  digits = datasets.load_digits(as_frame=False)
 
30
  return X_train, X_test, y_train, y_test
31
 
32
 
33
+ def train_nn_classifier(
34
+ X_train: np.ndarray,
35
+ y_train: np.ndarray,
36
+ ) -> NN:
37
  nn_classifier = NN(
38
  epochs=2_000,
39
  hidden_size=16,
 
51
  X_train=X_train,
52
  y_train=y_train,
53
  )
54
+ return nn_classifier
55
+
56
 
57
+ if __name__ == "__main__":
58
+ X_train, X_test, y_train, y_test = preprocess_digits(seed=RANDOM_SEED)
59
+ classifier = train_nn_classifier(X_train, y_train)
60
 
61
+ pred = classifier.predict(X_test)
62
  pred = np.argmax(pred, axis=1)
63
  y_test = np.argmax(y_test, axis=1)
64
 
65
  accuracy = accuracy_score(y_true=y_test, y_pred=pred)
 
66
  print(f"accuracy on validation set: {accuracy:.4f}")
 
 
 
 
numpyneuron/__init__.py CHANGED
@@ -8,3 +8,9 @@ ACTIVATIONS: dict[str, Activation] = {
8
  "TanH": TanH(),
9
  "SoftMax": SoftMax(),
10
  }
 
 
 
 
 
 
 
8
  "TanH": TanH(),
9
  "SoftMax": SoftMax(),
10
  }
11
+
12
+ LOSSES: dict[str, Loss] = {
13
+ "MSE": MSE(),
14
+ "CrossEntropy": CrossEntropy(),
15
+ "CrossEntropyWithLogitsLoss": CrossEntropyWithLogits(),
16
+ }
numpyneuron/activation.py CHANGED
@@ -4,11 +4,11 @@ from abc import abstractmethod, ABC
4
 
5
  class Activation(ABC):
6
  @abstractmethod
7
- def forward(self, X: np.ndarray) -> np.ndarray:
8
  pass
9
 
10
  @abstractmethod
11
- def backward(self, X: np.ndarray) -> np.ndarray:
12
  pass
13
 
14
 
 
4
 
5
  class Activation(ABC):
6
  @abstractmethod
7
+ def forward(X: np.ndarray) -> np.ndarray:
8
  pass
9
 
10
  @abstractmethod
11
+ def backward(X: np.ndarray) -> np.ndarray:
12
  pass
13
 
14
 
numpyneuron/loss.py CHANGED
@@ -4,12 +4,14 @@ import numpy as np
4
 
5
 
6
  class Loss(ABC):
 
7
  @abstractmethod
8
- def forward(self, y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
9
  pass
10
 
 
11
  @abstractmethod
12
- def backward(self, y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
13
  pass
14
 
15
 
@@ -18,19 +20,22 @@ class LogitsLoss(Loss):
18
 
19
 
20
  class MSE(Loss):
21
- def forward(self, y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
 
22
  return np.sum(np.square(y_hat - y_true)) / y_true.shape[0]
23
 
24
- def backward(self, y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
 
25
  return (y_hat - y_true) * (2 / y_true.shape[0])
26
 
27
 
28
  class CrossEntropy(Loss):
29
- def forward(self, y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
 
30
  y_hat = np.asarray(y_hat)
31
  y_true = np.asarray(y_true)
32
  m = y_true.shape[0]
33
- p = self._softmax(y_hat)
34
  eps = 1e-15 # to prevent log(0)
35
  log_likelihood = -np.log(
36
  np.clip(p[range(m), y_true.argmax(axis=1)], a_min=eps, a_max=None)
@@ -38,19 +43,17 @@ class CrossEntropy(Loss):
38
  loss = np.sum(log_likelihood) / m
39
  return loss
40
 
41
- def backward(self, y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
 
42
  y_hat = np.asarray(y_hat)
43
  y_true = np.asarray(y_true)
44
  grad = y_hat - y_true
45
  return grad / y_true.shape[0]
46
 
47
- @staticmethod
48
- def _softmax(X: np.ndarray) -> np.ndarray:
49
- return SoftMax().forward(X)
50
-
51
 
52
  class CrossEntropyWithLogits(LogitsLoss):
53
- def forward(self, y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
 
54
  # Apply the log-sum-exp trick for numerical stability
55
  max_logits = np.max(y_hat, axis=1, keepdims=True)
56
  log_sum_exp = np.log(np.sum(np.exp(y_hat - max_logits), axis=1, keepdims=True))
@@ -59,17 +62,11 @@ class CrossEntropyWithLogits(LogitsLoss):
59
  loss = -np.sum(log_probs * y_true) / y_true.shape[0]
60
  return loss
61
 
62
- def backward(self, y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
 
63
  # Compute softmax probabilities
64
  exps = np.exp(y_hat - np.max(y_hat, axis=1, keepdims=True))
65
  probs = exps / np.sum(exps, axis=1, keepdims=True)
66
  # Subtract the one-hot encoded labels from the probabilities
67
  grad = (probs - y_true) / y_true.shape[0]
68
  return grad
69
-
70
-
71
- LOSSES: dict[str, Loss] = {
72
- "MSE": MSE(),
73
- "CrossEntropy": CrossEntropy(),
74
- "CrossEntropyWithLogitsLoss": CrossEntropyWithLogits(),
75
- }
 
4
 
5
 
6
  class Loss(ABC):
7
+ @staticmethod
8
  @abstractmethod
9
+ def forward(y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
10
  pass
11
 
12
+ @staticmethod
13
  @abstractmethod
14
+ def backward(y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
15
  pass
16
 
17
 
 
20
 
21
 
22
  class MSE(Loss):
23
+ @staticmethod
24
+ def forward(y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
25
  return np.sum(np.square(y_hat - y_true)) / y_true.shape[0]
26
 
27
+ @staticmethod
28
+ def backward(y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
29
  return (y_hat - y_true) * (2 / y_true.shape[0])
30
 
31
 
32
  class CrossEntropy(Loss):
33
+ @staticmethod
34
+ def forward(y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
35
  y_hat = np.asarray(y_hat)
36
  y_true = np.asarray(y_true)
37
  m = y_true.shape[0]
38
+ p = SoftMax().forward(y_hat)
39
  eps = 1e-15 # to prevent log(0)
40
  log_likelihood = -np.log(
41
  np.clip(p[range(m), y_true.argmax(axis=1)], a_min=eps, a_max=None)
 
43
  loss = np.sum(log_likelihood) / m
44
  return loss
45
 
46
+ @staticmethod
47
+ def backward(y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
48
  y_hat = np.asarray(y_hat)
49
  y_true = np.asarray(y_true)
50
  grad = y_hat - y_true
51
  return grad / y_true.shape[0]
52
 
 
 
 
 
53
 
54
  class CrossEntropyWithLogits(LogitsLoss):
55
+ @staticmethod
56
+ def forward(y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
57
  # Apply the log-sum-exp trick for numerical stability
58
  max_logits = np.max(y_hat, axis=1, keepdims=True)
59
  log_sum_exp = np.log(np.sum(np.exp(y_hat - max_logits), axis=1, keepdims=True))
 
62
  loss = -np.sum(log_probs * y_true) / y_true.shape[0]
63
  return loss
64
 
65
+ @staticmethod
66
+ def backward(y_hat: np.ndarray, y_true: np.ndarray) -> np.ndarray:
67
  # Compute softmax probabilities
68
  exps = np.exp(y_hat - np.max(y_hat, axis=1, keepdims=True))
69
  probs = exps / np.sum(exps, axis=1, keepdims=True)
70
  # Subtract the one-hot encoded labels from the probabilities
71
  grad = (probs - y_true) / y_true.shape[0]
72
  return grad
 
 
 
 
 
 
 
test/{test_activation.py → test_activation_fns.py} RENAMED
File without changes
test/test_loss_fns.py ADDED
File without changes