MartialTerran
commited on
Commit
•
42a5011
1
Parent(s):
8d9edc5
Create Modular_Pytorch_Sine-x_formula_model.py
Browse filesSee How to use tensorflow lite to export model to Arduino: https://github.com/Azacus1/Modelling-for-sin-wave-function/tree/main
Intro to TinyML Part 1: Training a Neural Network for Arduino in TensorFlow | Digi-Key Electronics
https://www.youtube.com/watch?v=BzzqYNYOcWc&t=0s
Intro to TinyML Part 2: Deploying a TensorFlow Lite Model to Arduino | Digi-Key Electronics
https://www.youtube.com/watch?v=dU01M61RW8s&t
Modular_Pytorch_Sine-x_formula_model.py
ADDED
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#Modular Sine-x Model with hyperparameter dictionary-control.
|
2 |
+
#Based on Tensorflow example published by githubusercontent Azacus1 Modelling-for-sin-wave-function Model.py
|
3 |
+
|
4 |
+
Hyperparameters = {
|
5 |
+
'hidden_layers': [32, 32, 32], # Example: 3 hidden layers with 32 neurons each
|
6 |
+
'activation': 'relu', # Activation function for hidden layers ('relu', 'sigmoid', 'tanh', 'elu', 'leakyrelu')
|
7 |
+
'epochs': 500,
|
8 |
+
'batch_size': 64,
|
9 |
+
'learning_rate': 0.001,
|
10 |
+
}
|
11 |
+
|
12 |
+
print("load libraries")
|
13 |
+
import torch
|
14 |
+
import torch.nn as nn
|
15 |
+
import torch.optim as optim
|
16 |
+
import numpy as np
|
17 |
+
import pandas as pd
|
18 |
+
import matplotlib.pyplot as plt
|
19 |
+
import math
|
20 |
+
print("done loading libraries")
|
21 |
+
|
22 |
+
# Set seed for experiment reproducibility
|
23 |
+
seed = 1
|
24 |
+
np.random.seed(seed)
|
25 |
+
torch.manual_seed(seed)
|
26 |
+
|
27 |
+
# Number of sample datapoints
|
28 |
+
SAMPLES = 400
|
29 |
+
|
30 |
+
def generate_sine_data(samples):
|
31 |
+
print("# --- Synthetic Data Generation ---")
|
32 |
+
x_values = np.random.uniform(low=0, high=2 * math.pi, size=samples).astype(np.float32)
|
33 |
+
np.random.shuffle(x_values)
|
34 |
+
y_values = np.sin(x_values).astype(np.float32)
|
35 |
+
y_values += 0.01 * np.random.randn(*y_values.shape)
|
36 |
+
return x_values, y_values
|
37 |
+
|
38 |
+
# --- Data Splitting ---
|
39 |
+
def split_data(x_values, y_values, train_split=0.6, test_split=0.2):
|
40 |
+
print("# --- Train/Test Data Splitting ---")
|
41 |
+
train_split_index = int(train_split * SAMPLES)
|
42 |
+
test_split_index = int(test_split * SAMPLES) + train_split_index
|
43 |
+
x_train, x_test, x_validate = np.split(x_values, [train_split_index, test_split_index])
|
44 |
+
y_train, y_test, y_validate = np.split(y_values, [train_split_index, test_split_index])
|
45 |
+
assert (x_train.size + x_validate.size + x_test.size) == SAMPLES
|
46 |
+
return (x_train, y_train), (x_test, y_test), (x_validate, y_validate)
|
47 |
+
|
48 |
+
# --- Data Conversion to Tensors ---
|
49 |
+
def convert_to_tensors(x_train, y_train, x_test, y_test, x_validate, y_validate):
|
50 |
+
print("# --- Data Conversion to Tensors ---")
|
51 |
+
x_train_tensor = torch.from_numpy(x_train).unsqueeze(1)
|
52 |
+
y_train_tensor = torch.from_numpy(y_train).unsqueeze(1)
|
53 |
+
x_test_tensor = torch.from_numpy(x_test).unsqueeze(1)
|
54 |
+
y_test_tensor = torch.from_numpy(y_test).unsqueeze(1)
|
55 |
+
x_validate_tensor = torch.from_numpy(x_validate).unsqueeze(1)
|
56 |
+
y_validate_tensor = torch.from_numpy(y_validate).unsqueeze(1)
|
57 |
+
return x_train_tensor, y_train_tensor, x_test_tensor, y_test_tensor, x_validate_tensor, y_validate_tensor
|
58 |
+
|
59 |
+
# --- Plotting Utilities ---
|
60 |
+
def plot_data(x_train, y_train, x_test, y_test, x_validate, y_validate):
|
61 |
+
print("# --- Plotting Utilities ---")
|
62 |
+
plt.plot(x_train, y_train, 'b.', label="Train")
|
63 |
+
plt.plot(x_test, y_test, 'r.', label="Test")
|
64 |
+
plt.plot(x_validate, y_validate, 'y.', label="Validate")
|
65 |
+
plt.legend()
|
66 |
+
plt.show()
|
67 |
+
|
68 |
+
def plot_loss(train_losses, val_losses, skip=0):
|
69 |
+
print("# --- Plotting train_losses, val_losses ---")
|
70 |
+
epochs_range = range(1, len(train_losses) + 1)
|
71 |
+
plt.figure(figsize=(10, 4))
|
72 |
+
plt.subplot(1, 2, 1)
|
73 |
+
plt.plot(epochs_range[skip:], train_losses[skip:], 'g.', label='Training loss')
|
74 |
+
plt.plot(epochs_range[skip:], val_losses[skip:], 'b.', label='Validation loss')
|
75 |
+
plt.title('Training and validation loss')
|
76 |
+
plt.xlabel('Epochs')
|
77 |
+
plt.ylabel('Loss')
|
78 |
+
plt.legend()
|
79 |
+
plt.show()
|
80 |
+
|
81 |
+
def plot_mae(epochs_range, train_mae, val_mae):
|
82 |
+
print("# --- Plotting MAE ---")
|
83 |
+
plt.subplot(1, 2, 2)
|
84 |
+
plt.plot([epochs_range[-1]], [train_mae], 'g.', label='Training MAE')
|
85 |
+
plt.plot([epochs_range[-1]], [val_mae], 'b.', label='Validation MAE')
|
86 |
+
plt.title('Training and validation mean absolute error')
|
87 |
+
plt.xlabel('Epochs (only final epoch shown for MAE)')
|
88 |
+
plt.ylabel('MAE')
|
89 |
+
plt.legend()
|
90 |
+
plt.tight_layout()
|
91 |
+
plt.show()
|
92 |
+
|
93 |
+
def plot_predictions(x_test, y_test, y_test_pred_tensor):
|
94 |
+
print("# --- Plotting Predictions ---")
|
95 |
+
plt.clf()
|
96 |
+
plt.title('Comparison of predictions and actual values')
|
97 |
+
plt.plot(x_test, y_test, 'b.', label='Actual values')
|
98 |
+
plt.plot(x_test, y_test_pred_tensor.detach().numpy(), 'r.', label='PyTorch predicted')
|
99 |
+
plt.legend()
|
100 |
+
plt.show()
|
101 |
+
|
102 |
+
# --- Model Definition ---
|
103 |
+
class DynamicSineModel(nn.Module):
|
104 |
+
def __init__(self, config):
|
105 |
+
super(DynamicSineModel, self).__init__()
|
106 |
+
self.layers = nn.ModuleList()
|
107 |
+
self.config = config
|
108 |
+
input_dim = 1
|
109 |
+
|
110 |
+
# Build hidden layers
|
111 |
+
for i, num_neurons in enumerate(self.config['hidden_layers']):
|
112 |
+
self.layers.append(nn.Linear(input_dim, num_neurons))
|
113 |
+
input_dim = num_neurons # Update input dimension for the next layer
|
114 |
+
|
115 |
+
# Output layer
|
116 |
+
self.layers.append(nn.Linear(input_dim, 1))
|
117 |
+
|
118 |
+
# Determine activation functions
|
119 |
+
self.activation_functions = []
|
120 |
+
for _ in range(len(self.config['hidden_layers'])):
|
121 |
+
activation_name = self.config.get('activation', 'relu').lower()
|
122 |
+
if activation_name == 'relu':
|
123 |
+
self.activation_functions.append(nn.ReLU())
|
124 |
+
elif activation_name == 'sigmoid':
|
125 |
+
self.activation_functions.append(nn.Sigmoid())
|
126 |
+
elif activation_name == 'tanh':
|
127 |
+
self.activation_functions.append(nn.Tanh())
|
128 |
+
elif activation_name == 'elu':
|
129 |
+
self.activation_functions.append(nn.ELU())
|
130 |
+
elif activation_name == 'leakyrelu':
|
131 |
+
self.activation_functions.append(nn.LeakyReLU())
|
132 |
+
else:
|
133 |
+
raise ValueError(f"Activation function '{activation_name}' not supported.")
|
134 |
+
|
135 |
+
def forward(self, x):
|
136 |
+
for i, (layer, activation) in enumerate(zip(self.layers[:-1], self.activation_functions)):
|
137 |
+
x = activation(layer(x))
|
138 |
+
x = self.layers[-1](x) # Output layer without activation
|
139 |
+
return x
|
140 |
+
|
141 |
+
# --- Training and Evaluation Functions ---
|
142 |
+
def train_model(model, optimizer, loss_fn, x_train_tensor, y_train_tensor, x_validate_tensor, y_validate_tensor, config):
|
143 |
+
epochs = config['epochs']
|
144 |
+
batch_size = config['batch_size']
|
145 |
+
train_losses = []
|
146 |
+
val_losses = []
|
147 |
+
|
148 |
+
for epoch in range(1, epochs + 1):
|
149 |
+
model.train()
|
150 |
+
permutation = torch.randperm(x_train_tensor.size()[0])
|
151 |
+
epoch_train_loss = 0.0
|
152 |
+
for i in range(0, x_train_tensor.size()[0], batch_size):
|
153 |
+
indices = permutation[i:i + batch_size]
|
154 |
+
x_batch, y_batch = x_train_tensor[indices], y_train_tensor[indices]
|
155 |
+
optimizer.zero_grad()
|
156 |
+
y_pred = model(x_batch)
|
157 |
+
loss = loss_fn(y_pred, y_batch)
|
158 |
+
loss.backward()
|
159 |
+
optimizer.step()
|
160 |
+
epoch_train_loss += loss.item() * x_batch.size(0)
|
161 |
+
train_losses.append(epoch_train_loss / x_train_tensor.size(0))
|
162 |
+
|
163 |
+
model.eval()
|
164 |
+
with torch.no_grad():
|
165 |
+
y_val_pred = model(x_validate_tensor)
|
166 |
+
val_loss = loss_fn(y_val_pred, y_validate_tensor).item()
|
167 |
+
val_losses.append(val_loss)
|
168 |
+
|
169 |
+
if epoch % 100 == 0:
|
170 |
+
print(f'Epoch {epoch}/{epochs}, Training Loss: {train_losses[-1]:.4f}, Validation Loss: {val_loss:.4f}')
|
171 |
+
return train_losses, val_losses
|
172 |
+
|
173 |
+
def evaluate_model(model, loss_fn, x_test_tensor, y_test_tensor, x_train_tensor, y_train_tensor, x_validate_tensor, y_validate_tensor):
|
174 |
+
model.eval()
|
175 |
+
with torch.no_grad():
|
176 |
+
y_test_pred_tensor = model(x_test_tensor)
|
177 |
+
test_loss = loss_fn(y_test_pred_tensor, y_test_tensor).item()
|
178 |
+
test_mae = torch.mean(torch.abs(y_test_pred_tensor - y_test_tensor)).item()
|
179 |
+
train_mae = torch.mean(torch.abs(model(x_train_tensor) - y_train_tensor)).item()
|
180 |
+
val_mae = torch.mean(torch.abs(model(x_validate_tensor) - y_validate_tensor)).item()
|
181 |
+
|
182 |
+
print(f'Test Loss: {test_loss:.4f}')
|
183 |
+
print(f'Test MAE: {test_mae:.4f}')
|
184 |
+
return test_loss, test_mae, train_mae, val_mae, y_test_pred_tensor
|
185 |
+
|
186 |
+
# --- Main Execution ---
|
187 |
+
def main():
|
188 |
+
# Hyperparameters
|
189 |
+
config = Hyperparameters
|
190 |
+
|
191 |
+
# Generate and split data
|
192 |
+
x_values, y_values = generate_sine_data(SAMPLES)
|
193 |
+
(x_train, y_train), (x_test, y_test), (x_validate, y_validate) = split_data(x_values, y_values)
|
194 |
+
plot_data(x_train, y_train, x_test, y_test, x_validate, y_validate)
|
195 |
+
x_train_tensor, y_train_tensor, x_test_tensor, y_test_tensor, x_validate_tensor, y_validate_tensor = convert_to_tensors(
|
196 |
+
x_train, y_train, x_test, y_test, x_validate, y_validate
|
197 |
+
)
|
198 |
+
|
199 |
+
# Model, Optimizer, and Loss Function
|
200 |
+
model = DynamicSineModel(config)
|
201 |
+
optimizer = optim.Adam(model.parameters(), lr=config['learning_rate'])
|
202 |
+
loss_fn = nn.MSELoss()
|
203 |
+
|
204 |
+
# Training
|
205 |
+
train_losses, val_losses = train_model(
|
206 |
+
model, optimizer, loss_fn, x_train_tensor, y_train_tensor, x_validate_tensor, y_validate_tensor, config
|
207 |
+
)
|
208 |
+
plot_loss(train_losses, val_losses)
|
209 |
+
|
210 |
+
# Evaluation
|
211 |
+
test_loss, test_mae, train_mae, val_mae, y_test_pred_tensor = evaluate_model(
|
212 |
+
model, loss_fn, x_test_tensor, y_test_tensor, x_train_tensor, y_train_tensor, x_validate_tensor, y_validate_tensor
|
213 |
+
)
|
214 |
+
plot_mae(range(1, len(train_losses) + 1), train_mae, val_mae)
|
215 |
+
plot_predictions(x_test, y_test, y_test_pred_tensor)
|
216 |
+
|
217 |
+
if __name__ == "__main__":
|
218 |
+
main()
|