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()
|