Se agregaron gráficas y métricas de evaluación del modelo

main
Sofia Samaniego 2 days ago
parent 8d7771a56f
commit c67dd87231

File diff suppressed because one or more lines are too long

@ -197,13 +197,37 @@ MyANN.feedforward([0.1, 0.2, 0.3])
### 2.3 Backpropagation (Training)
The core learning algorithm. It calculates the prediction error, propagates it backward, and dynamically updates the weight matrices using gradient descent and the chain rule.
#### Mathematical Derivation of the Cost Function and Gradient
The Gradient of Error
$$ \frac{\partial E}{\partial w_{ho}}= -e_o\cdot \sigma \left(w_{ho} O_h\right) \left(1-\sigma\left (w_{ho} O_h\right) \right) O_h $$
To thoroughly understand the network's learning mechanics, we must derive the gradient of the error with respect to the synaptic weights. This procedure uses the **Chain Rule** from calculus and establishes the mathematical foundation for the Gradient Descent optimization strategy used in our backpropagation algorithm.
Thus,
**1. The Cost Function (SSE)**
We define the Total Error ($E$) using the Sum of Squared Errors:
$$E = \frac{1}{2} \sum (T - O_o)^2$$
Where $T$ represents the target label and $O_o$ is the predicted output. We define the output error as $e_o = (T - O_o)$.
$$ \frac{\partial E}{\partial w_{ho}}= -e_o\cdot O_o \left(1-O_o \right) O_h $$
**2. The Chain Rule Application**
To update the weight matrix $w_{ho}$ (connecting the hidden layer to the output layer), we need to determine how a change in $w_{ho}$ impacts the total error $E$. We calculate the partial derivative using the Chain Rule:
$$\frac{\partial E}{\partial w_{ho}} = \frac{\partial E}{\partial O_o} \cdot \frac{\partial O_o}{\partial X_o} \cdot \frac{\partial X_o}{\partial w_{ho}}$$
*(Note: $X_o = w_{ho} \cdot O_h$ represents the raw signal entering the output node before activation).*
**3. Solving the Partial Derivatives**
* **Error derivative:** How the total error changes with respect to the final output.
$$\frac{\partial E}{\partial O_o} = -(T - O_o) = -e_o$$
* **Activation derivative:** The derivative of the Sigmoid activation function $\sigma(X_o)$.
$$\frac{\partial O_o}{\partial X_o} = \sigma(X_o)(1 - \sigma(X_o)) = O_o(1 - O_o)$$
* **Weight derivative:** How the raw input $X_o$ changes with respect to the weight matrix $w_{ho}$. This evaluates directly to the output of the preceding hidden layer $O_h$.
$$\frac{\partial X_o}{\partial w_{ho}} = O_h$$
**4. Final Gradient Equation**
Multiplying these individual derivatives yields the final gradient of the error for $w_{ho}$:
$$\frac{\partial E}{\partial w_{ho}}= -e_o\cdot \sigma \left(w_{ho} O_h\right) \left(1-\sigma\left (w_{ho} O_h\right) \right) O_h$$
Thus, by substituting the activated output $O_o$, we arrive at the simplified expression:
$$\frac{\partial E}{\partial w_{ho}}= -e_o\cdot O_o \left(1-O_o \right) O_h$$
This precise formulation dictates the weight update rule programmed in our `backpropagation` method, scaled by the learning rate ($\eta$) to ensure stable convergence:
$$w_{ho_{new}} = w_{ho} + \eta \cdot e_o \cdot O_o(1 - O_o) \cdot O_h^T$$
```python
@ -324,8 +348,9 @@ MyANN = ann(inputNodes, hiddenNodes, outNodes)
```python
# Iterative training loop
epoch = 1
for e in range (epoch):
epochs = 5
for e in range(epochs):
total_loss = 0
for record in list:
values = record.split(",")
@ -337,12 +362,26 @@ for e in range (epoch):
target = np.zeros(outNodes) + 0.01
target[index] = 0.99
# Calculate loss before updating weights
output = MyANN.feedforward(data)
# Using SSE formulation: 0.5 * sum((target - output)^2)
loss = np.sum(0.5 * (target.reshape(-1, 1) - output)**2)
total_loss += loss
# Train
MyANN.backpropagation(data, target, learningRate)
pass
pass
average_loss = total_loss / len(list)
print(f"Epoch {e+1}/{epochs} - Average Loss: {average_loss:.4f}")
```
Epoch 1/5 - Average Loss: 0.0972
Epoch 2/5 - Average Loss: 0.0559
Epoch 3/5 - Average Loss: 0.0461
Epoch 4/5 - Average Loss: 0.0403
Epoch 5/5 - Average Loss: 0.0361
## 5. Validation & Inference
Evaluating model performance using unseen test data. A new sample is normalized and processed to extract the final prediction vector, which is then visually compared to the ground truth image.
@ -362,9 +401,40 @@ file2.close
```python
#Test Set Evaluation (Network Score)
scorecard = []
for record in list2:
values = record.split(",")
correct_label = int(values[0])
# Normalize input
data = np.asarray(values[1:], dtype=float) / 255.0 * 0.99 + 0.01
# Get network prediction
outputs = MyANN.feedforward(data)
# The index of the highest value corresponds to the predicted class
predicted_label = np.argmax(outputs)
# Append 1 if correct, 0 if incorrect
if predicted_label == correct_label:
scorecard.append(1)
else:
scorecard.append(0)
scorecard_array = np.asarray(scorecard)
accuracy = scorecard_array.sum() / scorecard_array.size
print(f"Network Accuracy (Score) on Test Set: {accuracy * 100:.2f}%")
```
Network Accuracy (Score) on Test Set: 95.27%
```python
# Inference on sample 500
values = list2[500].split(",")
values = list2[700].split(",")
data = np.asarray(values[1:], dtype=int)/255*0.99+0.01
# Display probability vector for the 10 classes
@ -374,16 +444,16 @@ MyANN.feedforward(data)
array([[1.12958417e-03],
[7.04806122e-03],
[1.33450332e-03],
[9.98132913e-01],
[1.21412650e-04],
[6.66539345e-03],
[2.96287176e-05],
[1.83308214e-03],
[8.11709452e-04],
[1.75208890e-04]])
array([[3.92210113e-05],
[9.64025823e-01],
[1.22524901e-03],
[1.47886768e-02],
[1.61851002e-03],
[3.98689305e-03],
[7.74585782e-05],
[2.65175424e-03],
[4.98386616e-03],
[2.37478194e-03]])
@ -397,6 +467,194 @@ plt.show()
![png](README_files/README_32_0.png)
![png](README_files/README_33_0.png)
## 6.1 Hyperparameter Tuning: Learning Rate Impact
To optimize the network's performance, we evaluate the impact of the Learning Rate ($\eta$) on the final classification accuracy. The network is trained across a sweep of different learning rates `[0.01, 0.1, 0.2, 0.3, 0.6, 0.9]` while keeping the hidden nodes constant (100 nodes).
The results are plotted to identify the optimal step size for the Gradient Descent algorithm, avoiding both slow convergence (values too close to 0) and divergent oscillations (values too close to 1).
```python
# 6. Hyperparameter Tuning: Learning Rate Sweep
learning_rates = [0.01, 0.1, 0.2, 0.3, 0.6, 0.9]
performances = []
hidden_nodes_baseline = 100
print("Starting Learning Rate sweep. This may take a few minutes...")
for lr in learning_rates:
print(f"Training network with Learning Rate: {lr}...")
# Initialize a fresh network for each test
testANN = ann(784, hidden_nodes_baseline, 10)
# Train 1 epoch
for record in list:
values = record.split(",")
data = np.asarray(values[1:], dtype=float) / 255.0 * 0.99 + 0.01
target = np.zeros(10) + 0.01
target[int(values[0])] = 0.99
testANN.backpropagation(data, target, lr)
# Evaluate on the Test Set
score = 0
for record in list2:
values = record.split(",")
correct_label = int(values[0])
data = np.asarray(values[1:], dtype=float) / 255.0 * 0.99 + 0.01
outputs = testANN.feedforward(data)
if np.argmax(outputs) == correct_label:
score += 1
# Calculate performance (accuracy as a decimal between 0 and 1)
performance = score / len(list2)
performances.append(performance)
print(f"Performance for LR {lr}: {performance:.4f}\n")
# Plotting the exact graph requested
plt.figure(figsize=(8, 5))
plt.plot(learning_rates, performances, marker='s', markersize=8, color='#003366', linewidth=1.5)
# Formatting to match the requested style
plt.title("Performance vs. Learning Rate")
plt.xlabel("learning rate")
plt.ylabel("performance")
# Setting axes limits and ticks
plt.xlim(0, 1)
plt.xticks(np.arange(0, 1.1, 0.1))
plt.ylim(0.8, 0.98)
plt.yticks(np.arange(0.8, 1.0, 0.02))
# Adding horizontal grid lines
plt.grid(axis='y', linestyle='-', alpha=0.7)
plt.show()
```
Starting Learning Rate sweep. This may take a few minutes...
Training network with Learning Rate: 0.01...
Performance for LR 0.01: 0.8683
Training network with Learning Rate: 0.1...
Performance for LR 0.1: 0.9249
Training network with Learning Rate: 0.2...
Performance for LR 0.2: 0.9274
Training network with Learning Rate: 0.3...
Performance for LR 0.3: 0.9178
Training network with Learning Rate: 0.6...
Performance for LR 0.6: 0.8587
Training network with Learning Rate: 0.9...
Performance for LR 0.9: 0.8326
![png](README_files/README_35_1.png)
## 6.2 Hyperparameter Tuning: Hidden Nodes Capacity
In this experiment, we evaluate the effect of the network's capacity by varying the number of hidden nodes `[10, 50, 100, 200, 500]`. The Learning Rate is kept constant at $0.2$.
The resulting curve demonstrates the law of diminishing returns in neural network architecture. While increasing the number of nodes initially provides a massive boost in classification performance, the accuracy plateaus after approximately 200 nodes. Beyond this threshold, adding more nodes significantly increases computational cost and memory footprint without yielding proportional accuracy gains.
```python
# 6.2 Hyperparameter Tuning: Hidden Nodes Sweep
hidden_nodes_options = [10, 50, 100, 200, 500]
performances_hn = []
optimal_lr = 0.2 # Fixed learning rate from previous experiment
print("Starting Hidden Nodes sweep. This will take a while...")
for hn in hidden_nodes_options:
print(f"Training network with {hn} hidden nodes...")
testANN = ann(784, hn, 10)
# Train 1 epoch
for record in list:
values = record.split(",")
data = np.asarray(values[1:], dtype=float) / 255.0 * 0.99 + 0.01
target = np.zeros(10) + 0.01
target[int(values[0])] = 0.99
testANN.backpropagation(data, target, optimal_lr)
# Evaluate on the Test Set
score = 0
for record in list2:
values = record.split(",")
correct_label = int(values[0])
data = np.asarray(values[1:], dtype=float) / 255.0 * 0.99 + 0.01
outputs = testANN.feedforward(data)
if np.argmax(outputs) == correct_label:
score += 1
# Calculate performance
performance = score / len(list2)
performances_hn.append(performance)
print(f"Performance for {hn} nodes: {performance:.4f}\n")
# Plotting the exact graph requested
plt.figure(figsize=(8, 5))
# marker='D' creates the diamond shapes seen in the reference image
plt.plot(hidden_nodes_options, performances_hn, marker='D', markersize=6, color='#003366', linewidth=1.5)
# Formatting axes
plt.xlabel("number of hidden nodes")
plt.ylabel("performance")
# Setting axes limits and ticks to match the image
plt.xlim(0, 600)
plt.xticks(np.arange(0, 601, 100))
plt.ylim(0.6, 1.0)
plt.yticks(np.arange(0.6, 1.05, 0.05))
# Adding horizontal grid lines
plt.grid(axis='y', linestyle='-', alpha=0.7)
plt.show()
```
Starting Hidden Nodes sweep. This will take a while...
Training network with 10 hidden nodes...
Performance for 10 nodes: 0.8064
Training network with 50 hidden nodes...
Performance for 50 nodes: 0.9183
Training network with 100 hidden nodes...
Performance for 100 nodes: 0.9274
Training network with 200 hidden nodes...
Performance for 200 nodes: 0.9344
Training network with 500 hidden nodes...
Performance for 500 nodes: 0.9167
![png](README_files/README_37_1.png)
## 7. Model Selection and Architecture Evaluation
**Which is the best ANN and how do you select which network is better?**
The best network is the one that achieves the highest accuracy on the Test Set while keeping the Cost Function (Loss) to a minimum, without falling into Overfitting. It is selected by comparing different combinations of hyperparameters (Hidden nodes and Learning Rate), evaluating them with data the network never saw during training. The winning network is the one that best generalizes to new data, not the one that memorizes the training data.
## 8. Microcontroller Deployment Strategy
**How do we implement it on a microcontroller?**
Once the network is trained on the computer, we extract the final weight matrices ($W_{ih}$ and $W_{ho}$) and export them as constant arrays (`const float`) in C/C++ language. On the microcontroller, only the inference stage (Feedforward) is programmed (matrix multiplication and the sigmoid function). The Backpropagation algorithm is completely omitted, which saves the microcontroller's limited memory and processing capacity, allowing for real-time signal processing.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Loading…
Cancel
Save