Η ομαλά επιταχυνόμενη κίνηση περιγράφει την κίνηση ενός σώματος όταν η επιτάχυνση παραμένει σταθερή στον χρόνο.
Στην περίπτωση αυτή, η ταχύτητα μεταβάλλεται γραμμικά ενώ η θέση μεταβάλλεται παραβολικά ως προς τον χρόνο.
Οι βασικές εξισώσεις που τη διέπουν είναι:
\(v(t)=v_0+at\)
\(x(t)=x_0+v_0 t+\frac{1}{2}at^2\)
Για την εξομοίωση της κίνησης ξεκινάμε από αρχικές συνθήκες (θέση \(x_0\) και ταχύτητα \(v_0\)) και για κάθε μικρό χρονικό διάστημα \(dt\), ενημερώνουμε πρώτα την ταχύτητα με βάση την επιτάχυνση μέσω της σχέσης \(v = v + a \cdot dt\), και στη συνέχεια τη θέση μέσω της σχέσης \(x = x + v \cdot dt\). Με την επανάληψη αυτής της διαδικασίας, προκύπτει αριθμητικά η τροχιά του σωματιδίου, η οποία προσεγγίζει την αναλυτική παραβολική λύση.
import numpy as np
import matplotlib.pyplot as plt
# αρχικές συνθήκες
x = 0
v = 2
a = 1
t_max = 10
N = 200 # αριθμός βημάτων
dt = t_max / N
t_values = []
x_values = []
t = 0
for i in range(N):
t_values.append(t)
x_values.append(x)
# update (βελτιωμένο)
x = x + v*dt + 0.5*a*dt**2
v = v + a*dt
t += dt
# plot
plt.figure()
plt.plot(t_values, x_values)
plt.xlabel("t")
plt.ylabel("x(t)")
plt.title("Trajectory with fixed steps")
plt.grid()
plt.show()
Στόχος είναι να μετατρέψουμε το φυσικό πρόβλημα σε ένα σύνολο δεδομένων εισόδου–εξόδου (dataset), ώστε να μπορεί να χρησιμοποιηθεί για την εκπαίδευση ενός νευρωνικού δικτύου (NN).
Ως είσοδο (input) χρησιμοποιούμε τον χρόνο \(t\), ενώ ως έξοδο (target) τη θέση \(x(t)\). Δηλαδή, το δίκτυο καλείται να μάθει τη συνάρτηση:
\( t \rightarrow x(t) \)
Για τον σκοπό αυτό μετατρέπουμε τον κώδικα της εξομοίωσης:
import numpy as np
import matplotlib.pyplot as plt
# -------------------------
# Simulation
# -------------------------
x = 0.0
v = 2.0
a = 1.0
t_max = 10
N = 200
dt = t_max / N
t_values = []
x_values = []
t = 0.0
for i in range(N):
t_values.append(t)
x_values.append(x)
x = x + v * dt + 0.5 * a * dt**2
v = v + a * dt
t += dt
# -------------------------
# Dataset
# -------------------------
X = np.array(t_values).reshape(-1, 1)
y = np.array(x_values).reshape(-1, 1)
# -------------------------
# Train / Validation Split
# -------------------------
split = int(0.8 * N)
X_train = X[:split]
y_train = y[:split]
X_val = X[split:]
y_val = y[split:]
# -------------------------
# Plot trajectory
# -------------------------
plt.figure()
plt.plot(X, y)
plt.xlabel("t")
plt.ylabel("x(t)")
plt.title("Trajectory")
plt.grid()
plt.show()
Για να μετατρέψουμε τα δεδομένα της εξομοίωσης σε μορφή κατάλληλη για ένα νευρωνικό δίκτυο, χρειάζεται να τα οργανώσουμε ως πίνακες δύο διαστάσεων, δηλαδή σε μορφή (samples, features). Αυτό επιτυγχάνεται με τη χρήση της εντολής reshape(-1, 1) στη NumPy, η οποία μετατρέπει ένα μονοδιάστατο array τιμών χρόνου ή θέσης σε στήλη. Με αυτόν τον τρόπο, το διάνυσμα του χρόνου t γίνεται πίνακας διαστάσεων (N, 1) και μπορεί να χρησιμοποιηθεί ως είσοδος (input), ενώ αντίστοιχα και οι τιμές της θέσης x(t) μετατρέπονται στην ίδια μορφή για να αποτελέσουν την έξοδο του μοντέλου.
Ο διαχωρισμός (splitting) των δεδομένων εκπαίδευσης σε σύνολα εκπαίδευσης και επικύρωσης είναι απαραίτητος για την αξιόπιστη αξιολόγηση της απόδοσης του νευρωνικού δικτύου. Με αυτόν τον τρόπο, το μοντέλο εκπαιδεύεται σε ένα υποσύνολο των δεδομένων (train set), ενώ ελέγχεται η ικανότητά του να γενικεύει σε νέα δεδομένα μέσω του validation set, με αναλογία 80% - 20%.
Αφού δημιουργήσουμε το σύνολο δεδομένων εισόδου–εξόδου, θα εκπαιδεύσουμε ένα νευρωνικό δίκτυο ώστε να προσεγγίσει τη συνάρτηση που περιγράφει την κίνηση. Το δίκτυο λαμβάνει ως είσοδο τον χρόνο \(t\) και προσπαθεί να προβλέψει τη θέση \(x(t)\). Κατά την εκπαίδευση, το μοντέλο προσαρμόζει τις παραμέτρους του ώστε να ελαχιστοποιήσει το σφάλμα μεταξύ των προβλέψεων και των πραγματικών τιμών.
Χρησιμοποιούμε ένα feedforward neural network που αποτελείται από ένα επίπεδο εισόδου που δέχεται τον χρόνο \(t\), ένα κρυφό επίπεδο (hidden layer) με 16 νευρώνες και ένα επίπεδο εξόδου που επιστρέφει την εκτιμώμενη θέση \(x(t)\). Στο κρυφό επίπεδο χρησιμοποιείται η συνάρτηση ενεργοποίησης ReLU (Rectified Linear Unit), η οποία εισάγει μη γραμμικότητα και επιτρέπει στο δίκτυο να προσεγγίσει μη γραμμικές συναρτήσεις, όπως η παραβολική τροχιά της επιταχυνόμενης κίνησης. Στο επίπεδο εξόδου χρησιμοποιείται γραμμική ενεργοποίηση, καθώς το πρόβλημα είναι πρόβλημα παλινδρόμησης (regression). Η εκπαίδευση του δικτύου πραγματοποιείται με τον αλγόριθμο gradient descent και ως συνάρτηση κόστους χρησιμοποιείται το μέσο τετραγωνικό σφάλμα (Mean Squared Error), το οποίο μετρά τη διαφορά μεταξύ των προβλεπόμενων και των πραγματικών τιμών.
Για την εκπαίδευση του νευρωνικού δικτύου, επεκτείνουμε τον προηγούμενο κώδικα εξομοίωσης προσθέτοντας τον νέο κώδικα στο τέλος του αρχείου.
# -------------------------
# Neural Network
# -------------------------
np.random.seed(0)
W1 = np.random.randn(1, 16) * 0.1
b1 = np.zeros((1, 16))
W2 = np.random.randn(16, 1) * 0.1
b2 = np.zeros((1, 1))
def relu(x):
return np.maximum(0, x)
def relu_deriv(x):
return (x > 0).astype(float)
# -------------------------
# Training
# -------------------------
lr = 0.001
epochs = 5000
train_loss = []
val_loss = []
for epoch in range(epochs):
# forward (train)
z1 = X_train @ W1 + b1
a1 = relu(z1)
y_pred = a1 @ W2 + b2
loss_train = np.mean((y_pred - y_train) ** 2)
# forward (validation)
z1_val = X_val @ W1 + b1
a1_val = relu(z1_val)
y_val_pred = a1_val @ W2 + b2
loss_val = np.mean((y_val_pred - y_val) ** 2)
train_loss.append(loss_train)
val_loss.append(loss_val)
# backward
dL = 2 * (y_pred - y_train) / len(y_train)
dW2 = a1.T @ dL
db2 = np.sum(dL, axis=0, keepdims=True)
da1 = dL @ W2.T
dz1 = da1 * relu_deriv(z1)
dW1 = X_train.T @ dz1
db1 = np.sum(dz1, axis=0, keepdims=True)
# update
W1 -= lr * dW1
b1 -= lr * db1
W2 -= lr * dW2
b2 -= lr * db2
if epoch % 500 == 0:
print(f"Epoch {epoch}, Train Loss: {loss_train[-1]:.5f}, Val Loss: {loss_val[-1]:.5f}")
# -------------------------
# Final prediction
# -------------------------
z1_all = X @ W1 + b1
a1_all = relu(z1_all)
y_pred_all = a1_all @ W2 + b2
# -------------------------
# Plot results
# -------------------------
plt.figure()
plt.plot(X, y, label="True")
plt.plot(X, y_pred_all, '--', label="NN Prediction")
plt.legend()
plt.grid()
plt.title("Trajectory Fit")
plt.show()
# Loss curves
plt.figure()
plt.plot(train_loss, label="Train Loss")
plt.plot(val_loss, label="Validation Loss")
plt.legend()
plt.grid()
plt.title("Loss Curves")
plt.show()
Για την εισαγωγή μη γραμμικότητας στο νευρωνικό δίκτυο χρησιμοποιούμε τη συνάρτηση ενεργοποίησης ReLU (Rectified Linear Unit), η οποία ορίζεται ως:
\( \text{ReLU}(x) = \max(0, x) \)
Η παράγωγός της είναι ιδιαίτερα απλή και δίνεται από:
\( \text{ReLU}'(x) = \begin{cases} 1, & x > 0 \\ 0, & x \le 0 \end{cases} \)
Η εκπαίδευση του δικτύου βασίζεται στη μέθοδο του backpropagation, η οποία επιτρέπει τον υπολογισμό των παραγώγων της συνάρτησης κόστους ως προς τα βάρη του δικτύου. Αρχικά πραγματοποιείται το forward pass:
\( z_1 = X W_1 + b_1 \), \( a_1 = \text{ReLU}(z_1) \)
\( z_2 = a_1 W_2 + b_2 \), \( \hat{y} = z_2 \)
Η συνάρτηση κόστους που χρησιμοποιείται είναι το μέσο τετραγωνικό σφάλμα (MSE):
\( L = \frac{1}{N} \sum (\hat{y} - y)^2 \)
Κατά το backward pass, υπολογίζονται οι παράγωγοι μέσω του κανόνα αλυσίδας. Αρχικά:
\( \frac{\partial L}{\partial \hat{y}} = \frac{2}{N} (\hat{y} - y) \)
και στη συνέχεια:
\( \frac{\partial L}{\partial W_2} = a_1^T \frac{\partial L}{\partial \hat{y}} \), \quad \( \frac{\partial L}{\partial b_2} = \sum \frac{\partial L}{\partial \hat{y}} \)
Η οπισθοδιάδοση συνεχίζεται προς το κρυφό επίπεδο:
\( \frac{\partial L}{\partial a_1} = \frac{\partial L}{\partial \hat{y}} W_2^T \)
\( \frac{\partial L}{\partial z_1} = \frac{\partial L}{\partial a_1} \cdot \text{ReLU}'(z_1) \)
και τελικά:
\( \frac{\partial L}{\partial W_1} = X^T \frac{\partial L}{\partial z_1} \), \quad \( \frac{\partial L}{\partial b_1} = \sum \frac{\partial L}{\partial z_1} \)
Τα βάρη ενημερώνονται με τη μέθοδο gradient descent:
\( W = W - \eta \frac{\partial L}{\partial W} \)
όπου \( \eta \) είναι ο ρυθμός μάθησης (learning rate).
Το printed output:
| Epoch | Train Loss | Validation Loss |
|---|---|---|
| 0 | 543.87007 | 3474.32099 |
| 500 | 4.50670 | 112.22162 |
| 1000 | 3.23337 | 79.12764 |
| 1500 | 2.99041 | 70.58636 |
| 2000 | 2.41047 | 65.27658 |
| 2500 | 1.74741 | 56.70248 |
| 3000 | 0.92018 | 46.62152 |
| 3500 | 0.49515 | 36.04609 |
| 4000 | 0.32532 | 29.26809 |
| 4500 | 0.22649 | 24.80880 |
Από τα αποτελέσματα της εκπαίδευσης παρατηρείται σταθερή μείωση τόσο του σφάλματος εκπαίδευσης (train loss) όσο και του validation loss, γεγονός που υποδεικνύει ότι το μοντέλο μαθαίνει αποτελεσματικά τη σχέση μεταξύ χρόνου και θέσης. Η σύγκλιση των δύο καμπυλών σφάλματος χωρίς σημαντική απόκλιση μεταξύ τους δείχνει ότι δεν εμφανίζεται έντονο φαινόμενο overfitting και ότι το νευρωνικό δίκτυο γενικεύει ικανοποιητικά στα δεδομένα που δεν έχουν χρησιμοποιηθεί στην εκπαίδευση. Επιπλέον, από το διάγραμμα φαίνεται ότι η πρόβλεψη του μοντέλου ακολουθεί αρκετά καλά την πραγματική καμπύλη, με μικρές αποκλίσεις σε μεγαλύτερες τιμές, κάτι που υποδηλώνει περιθώριο περαιτέρω βελτίωσης.
Geron, A. (2019). Hands-on machine learning with Scikit-Learn, Keras and TensorFlow: concepts, tools, and techniques to build intelligent systems (2nd ed.). O’Reilly.
Hagan, M. T., Demuth, H. B., Beale, M. H., & De Jesús, O. (2014). Neural network design (2nd ed.).