Skip to content

Commit 58c74f1

Browse files
committed
Add LSTM vs CNN timeseries classification example for FordA dataset
1 parent 4c7bcd3 commit 58c74f1

File tree

2 files changed

+311
-0
lines changed

2 files changed

+311
-0
lines changed
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
"""
2+
Title: Timeseries Classification with LSTM and CNN Models
3+
Author: [Georgios Sklavounakos] (https://github.com/gsklavounakos)
4+
Date created: 2025/06/05
5+
Last modified: 2025/06/05
6+
Description: Comparing LSTM and 1D CNN models for timeseries classification on the FordA dataset from the UCR/UEA archive.
7+
Accelerator: GPU
8+
"""
9+
10+
"""
11+
## Introduction
12+
13+
This example demonstrates how to perform timeseries classification using two deep learning models:
14+
a Long Short-Term Memory (LSTM) model, which processes sequences using recurrent layers, and a 1D Convolutional
15+
Neural Network (CNN) model, which uses convolutions to detect local temporal patterns. We use the FordA dataset
16+
from the [UCR/UEA archive](https://www.cs.ucr.edu/~eamonn/time_series_data_2018/), which contains univariate
17+
timeseries of engine noise measurements for binary classification (fault detection). This example compares the
18+
performance of both models under identical training conditions.
19+
20+
The workflow includes:
21+
- Loading and preprocessing the FordA dataset
22+
- Building and training LSTM and CNN models
23+
- Evaluating and comparing their performance
24+
- Visualizing training metrics
25+
"""
26+
27+
"""
28+
## Setup
29+
"""
30+
31+
import numpy as np
32+
import keras
33+
from keras import layers
34+
import matplotlib.pyplot as plt
35+
36+
"""
37+
## Load the Data: FordA Dataset
38+
39+
### Dataset Description
40+
41+
The FordA dataset contains 3601 training instances and 1320 testing instances, each a univariate timeseries of
42+
500 timesteps representing engine noise measurements. The task is to classify whether a fault is present (label 1)
43+
or not (label -1). The data is z-normalized (mean=0, std=1) and sourced from the UCR/UEA archive. For details, see
44+
[the dataset description](http://www.j-wichard.de/publications/FordPaper.pdf).
45+
"""
46+
47+
def readucr(filename):
48+
"""Read UCR timeseries dataset from a TSV file."""
49+
data = np.loadtxt(filename, delimiter="\t")
50+
y = data[:, 0].astype(int)
51+
x = data[:, 1:].astype(np.float32)
52+
return x, y
53+
54+
root_url = "https://raw.githubusercontent.com/hfawaz/cd-diagram/master/FordA/"
55+
56+
x_train, y_train = readucr(root_url + "FordA_TRAIN.tsv")
57+
x_test, y_test = readucr(root_url + "FordA_TEST.tsv")
58+
59+
"""
60+
## Visualize the Data
61+
62+
We plot one example timeseries from each class to understand the data's characteristics.
63+
"""
64+
65+
classes = np.unique(np.concatenate((y_train, y_test), axis=0))
66+
67+
plt.figure(figsize=(8, 4))
68+
for c in classes:
69+
c_x_train = x_train[y_train == c]
70+
plt.plot(c_x_train[0], label=f"class {c}")
71+
plt.title("Sample Time Series from Each Class")
72+
plt.xlabel("Timestep")
73+
plt.ylabel("Amplitude")
74+
plt.legend(loc="best")
75+
plt.show()
76+
plt.close()
77+
78+
"""
79+
## Preprocess the Data
80+
81+
The timeseries are already z-normalized and of fixed length (500 timesteps). We reshape the data to
82+
`(samples, timesteps, 1)` to represent univariate timeseries as multivariate with one channel, enabling
83+
compatibility with both LSTM and CNN models. We also standardize labels to 0 and 1 for binary classification.
84+
"""
85+
86+
x_train = x_train.reshape((x_train.shape[0], x_train.shape[1], 1))
87+
x_test = x_test.reshape((x_test.shape[0], x_test.shape[1], 1))
88+
89+
# Standardize labels: map {-1, 1} to {0, 1}
90+
y_train = np.where(y_train == -1, 0, y_train)
91+
y_test = np.where(y_test == -1, 0, y_test)
92+
93+
# Shuffle training data for validation split
94+
idx = np.random.permutation(len(x_train))
95+
x_train = x_train[idx]
96+
y_train = y_train[idx]
97+
98+
num_classes = len(np.unique(y_train))
99+
100+
"""
101+
## Build the Models
102+
103+
We define two models:
104+
1. **LSTM Model**: A recurrent model with two LSTM layers for sequential processing, with dropout for regularization.
105+
2. **CNN Model**: A convolutional model with 1D convolutions, batch normalization, max-pooling, and global average pooling,
106+
inspired by [this paper](https://arxiv.org/abs/1611.06455).
107+
108+
Both models use similar hyperparameters for a fair comparison.
109+
"""
110+
111+
def build_lstm_model(input_shape, num_classes):
112+
"""Build an LSTM-based model for timeseries classification."""
113+
model = keras.Sequential([
114+
layers.Input(shape=input_shape),
115+
layers.LSTM(64, return_sequences=True, dropout=0.3, recurrent_dropout=0.3),
116+
layers.LSTM(32, dropout=0.3, recurrent_dropout=0.3),
117+
layers.Dense(16, activation="relu"),
118+
layers.Dense(num_classes, activation="softmax")
119+
])
120+
return model
121+
122+
def build_cnn_model(input_shape, num_classes):
123+
"""Build a 1D CNN-based model for timeseries classification."""
124+
model = keras.Sequential([
125+
layers.Input(shape=input_shape),
126+
layers.Conv1D(filters=64, kernel_size=7, padding="same", activation="relu"),
127+
layers.BatchNormalization(),
128+
layers.MaxPooling1D(pool_size=2),
129+
layers.Dropout(0.3),
130+
layers.Conv1D(filters=128, kernel_size=5, padding="same", activation="relu"),
131+
layers.BatchNormalization(),
132+
layers.MaxPooling1D(pool_size=2),
133+
layers.Dropout(0.3),
134+
layers.GlobalAveragePooling1D(),
135+
layers.Dense(16, activation="relu"),
136+
layers.Dense(num_classes, activation="softmax")
137+
])
138+
return model
139+
140+
input_shape = x_train.shape[1:]
141+
lstm_model = build_lstm_model(input_shape, num_classes)
142+
cnn_model = build_cnn_model(input_shape, num_classes)
143+
144+
"""
145+
## Train the Models
146+
147+
We train both models with identical settings: Adam optimizer, sparse categorical crossentropy loss,
148+
and early stopping to prevent overfitting. We save the best model weights based on validation loss
149+
and reload them for evaluation.
150+
"""
151+
152+
epochs = 100
153+
batch_size = 32
154+
155+
def train_model(model, model_name, x_train, y_train, x_test, y_test):
156+
"""Train and evaluate a model, return history and test metrics."""
157+
model.compile(
158+
optimizer="adam",
159+
loss="sparse_categorical_crossentropy",
160+
metrics=["sparse_categorical_accuracy"]
161+
)
162+
163+
history = model.fit(
164+
x_train,
165+
y_train,
166+
epochs=epochs,
167+
batch_size=batch_size,
168+
validation_split=0.2,
169+
callbacks=[
170+
keras.callbacks.ModelCheckpoint(
171+
f"best_{model_name}_model.keras",
172+
save_best_only=True,
173+
monitor="val_loss"
174+
),
175+
keras.callbacks.EarlyStopping(
176+
monitor="val_loss",
177+
patience=20,
178+
restore_best_weights=True
179+
)
180+
],
181+
verbose=1
182+
)
183+
184+
# Load best model for evaluation
185+
best_model = keras.models.load_model(f"best_{model_name}_model.keras")
186+
test_loss, test_acc = best_model.evaluate(x_test, y_test, verbose=0)
187+
print(f"{model_name} Test Accuracy: {test_acc:.4f}, Loss: {test_loss:.4f}")
188+
189+
return history, test_acc, test_loss
190+
191+
# Train LSTM model
192+
print("Training LSTM model...")
193+
lstm_history, lstm_test_acc, lstm_test_loss = train_model(lstm_model, "LSTM", x_train, y_train, x_test, y_test)
194+
195+
# Train CNN model
196+
print("Training CNN model...")
197+
cnn_history, cnn_test_acc, cnn_test_loss = train_model(cnn_model, "CNN", x_train, y_train, x_test, y_test)
198+
199+
"""
200+
## Visualize Training Metrics
201+
202+
We plot the training and validation accuracy and loss for both models to compare their performance.
203+
"""
204+
205+
def plot_training_metrics(histories, model_names):
206+
"""Plot training and validation accuracy/loss for multiple models."""
207+
plt.figure(figsize=(12, 4))
208+
209+
# Accuracy plot
210+
plt.subplot(1, 2, 1)
211+
for history, name in zip(histories, model_names):
212+
plt.plot(history.history["sparse_categorical_accuracy"], label=f"{name} Train")
213+
plt.plot(history.history["val_sparse_categorical_accuracy"], linestyle="--", label=f"{name} Val")
214+
plt.title("Model Accuracy")
215+
plt.xlabel("Epoch")
216+
plt.ylabel("Accuracy")
217+
plt.legend()
218+
219+
# Loss plot
220+
plt.subplot(1, 2, 2)
221+
for history, name in zip(histories, model_names):
222+
plt.plot(history.history["loss"], label=f"{name} Train")
223+
plt.plot(history.history["val_loss"], linestyle="--", label=f"{name} Val")
224+
plt.title("Model Loss")
225+
plt.xlabel("Epoch")
226+
plt.ylabel("Loss")
227+
plt.legend()
228+
229+
plt.tight_layout()
230+
plt.show()
231+
232+
plot_training_metrics([lstm_history, cnn_history], ["LSTM", "CNN"])
233+
234+
"""
235+
## Evaluate and Compare Models
236+
237+
We compare the test accuracy and loss of both models to assess their performance.
238+
"""
239+
240+
print("\nModel Comparison:")
241+
print(f"LSTM Test Accuracy: {lstm_test_acc:.4f}, Loss: {lstm_test_loss:.4f}")
242+
print(f"CNN Test Accuracy: {cnn_test_acc:.4f}, Loss: {cnn_test_loss:.4f}")
243+
244+
"""
245+
## Conclusions
246+
247+
This example compared an LSTM-based model and a 1D CNN-based model for timeseries classification
248+
on the FordA dataset. The LSTM model leverages sequential dependencies, while the CNN model captures
249+
local temporal patterns through convolutions. The CNN often converges faster due to its robust architecture
250+
with batch normalization and global pooling, while the LSTM may better handle long-term dependencies.
251+
252+
To improve performance, consider:
253+
- Tuning hyperparameters (e.g., number of layers, units, or kernel sizes) using Keras Tuner.
254+
- Adding further regularization (e.g., L2 regularization) to prevent overfitting.
255+
- Experimenting with hybrid architectures combining LSTM and CNN layers.
256+
- Using data augmentation techniques for timeseries, such as jittering or scaling.
257+
"""
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Timeseries Classification with LSTM and CNN Models
2+
3+
**Author**: [Georgios Sklavounakos] (https://github.com/gsklavounakos)
4+
**Date created**: 2025/06/05
5+
**Last modified**: 2025/06/05
6+
**Description**: Compare LSTM and 1D CNN models for timeseries classification using the FordA dataset.
7+
**Accelerator**: GPU
8+
9+
---
10+
11+
## Introduction
12+
13+
This example shows how to classify univariate timeseries data using two types of deep learning models:
14+
15+
- A Long Short-Term Memory (LSTM) model, which processes sequences using recurrent layers.
16+
- A 1D Convolutional Neural Network (CNN) model, which extracts local patterns via convolution.
17+
18+
We use the FordA dataset from the [UCR/UEA archive](https://www.cs.ucr.edu/~eamonn/time_series_data_2018/), which consists of engine noise measurements recorded over time. The classification task is to determine whether a fault is present in the signal.
19+
20+
---
21+
22+
## What the Example Demonstrates
23+
24+
- How to preprocess univariate timeseries data for model input
25+
- How to build and train two deep learning models with different architectures
26+
- How to compare performance in terms of accuracy and training dynamics
27+
- How to visualize model learning curves
28+
29+
---
30+
31+
## Dataset Overview
32+
33+
- **Name**: FordA
34+
- **Source**: [UCR/UEA Timeseries Archive](https://www.cs.ucr.edu/~eamonn/time_series_data_2018/)
35+
- **Samples**: 3601 train, 1320 test
36+
- **Length**: 500 time steps
37+
- **Channels**: 1 (univariate)
38+
- **Labels**: -1 (no fault), 1 (fault) → mapped to 0 and 1
39+
40+
Each timeseries is z-normalized and represents a sensor reading from an automotive engine.
41+
42+
---
43+
44+
## Results
45+
46+
The models are trained with the same optimizer, loss function, and validation strategy. After training, the test accuracy and loss are reported for both models. Training and validation curves are also plotted for visual comparison.
47+
48+
---
49+
50+
## References
51+
52+
- [UCR/UEA Timeseries Archive](https://www.cs.ucr.edu/~eamonn/time_series_data_2018/)
53+
- Wang et al., “Time Series Classification from Scratch with Deep Neural Networks”
54+
[[arXiv:1611.06455](https://arxiv.org/abs/1611.06455)]

0 commit comments

Comments
 (0)