Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
keras-team
GitHub Repository: keras-team/keras-io
Path: blob/master/examples/timeseries/timeseries_classification_from_scratch.py
3507 views
1
"""
2
Title: Timeseries classification from scratch
3
Author: [hfawaz](https://github.com/hfawaz/)
4
Date created: 2020/07/21
5
Last modified: 2023/11/10
6
Description: Training a timeseries classifier from scratch on the FordA dataset from the UCR/UEA archive.
7
Accelerator: GPU
8
"""
9
10
"""
11
## Introduction
12
13
This example shows how to do timeseries classification from scratch, starting from raw
14
CSV timeseries files on disk. We demonstrate the workflow on the FordA dataset from the
15
[UCR/UEA archive](https://www.cs.ucr.edu/%7Eeamonn/time_series_data_2018/).
16
17
"""
18
19
"""
20
## Setup
21
22
"""
23
import keras
24
import numpy as np
25
import matplotlib.pyplot as plt
26
27
"""
28
## Load the data: the FordA dataset
29
30
### Dataset description
31
32
The dataset we are using here is called FordA.
33
The data comes from the UCR archive.
34
The dataset contains 3601 training instances and another 1320 testing instances.
35
Each timeseries corresponds to a measurement of engine noise captured by a motor sensor.
36
For this task, the goal is to automatically detect the presence of a specific issue with
37
the engine. The problem is a balanced binary classification task. The full description of
38
this dataset can be found [here](http://www.j-wichard.de/publications/FordPaper.pdf).
39
40
### Read the TSV data
41
42
We will use the `FordA_TRAIN` file for training and the
43
`FordA_TEST` file for testing. The simplicity of this dataset
44
allows us to demonstrate effectively how to use ConvNets for timeseries classification.
45
In this file, the first column corresponds to the label.
46
"""
47
48
49
def readucr(filename):
50
data = np.loadtxt(filename, delimiter="\t")
51
y = data[:, 0]
52
x = data[:, 1:]
53
return x, y.astype(int)
54
55
56
root_url = "https://raw.githubusercontent.com/hfawaz/cd-diagram/master/FordA/"
57
58
x_train, y_train = readucr(root_url + "FordA_TRAIN.tsv")
59
x_test, y_test = readucr(root_url + "FordA_TEST.tsv")
60
61
"""
62
## Visualize the data
63
64
Here we visualize one timeseries example for each class in the dataset.
65
66
"""
67
68
classes = np.unique(np.concatenate((y_train, y_test), axis=0))
69
70
plt.figure()
71
for c in classes:
72
c_x_train = x_train[y_train == c]
73
plt.plot(c_x_train[0], label="class " + str(c))
74
plt.legend(loc="best")
75
plt.show()
76
plt.close()
77
78
"""
79
## Standardize the data
80
81
Our timeseries are already in a single length (500). However, their values are
82
usually in various ranges. This is not ideal for a neural network;
83
in general we should seek to make the input values normalized.
84
For this specific dataset, the data is already z-normalized: each timeseries sample
85
has a mean equal to zero and a standard deviation equal to one. This type of
86
normalization is very common for timeseries classification problems, see
87
[Bagnall et al. (2016)](https://link.springer.com/article/10.1007/s10618-016-0483-9).
88
89
Note that the timeseries data used here are univariate, meaning we only have one channel
90
per timeseries example.
91
We will therefore transform the timeseries into a multivariate one with one channel
92
using a simple reshaping via numpy.
93
This will allow us to construct a model that is easily applicable to multivariate time
94
series.
95
"""
96
97
x_train = x_train.reshape((x_train.shape[0], x_train.shape[1], 1))
98
x_test = x_test.reshape((x_test.shape[0], x_test.shape[1], 1))
99
100
"""
101
Finally, in order to use `sparse_categorical_crossentropy`, we will have to count
102
the number of classes beforehand.
103
"""
104
105
num_classes = len(np.unique(y_train))
106
107
"""
108
Now we shuffle the training set because we will be using the `validation_split` option
109
later when training.
110
"""
111
112
idx = np.random.permutation(len(x_train))
113
x_train = x_train[idx]
114
y_train = y_train[idx]
115
116
"""
117
Standardize the labels to positive integers.
118
The expected labels will then be 0 and 1.
119
"""
120
121
y_train[y_train == -1] = 0
122
y_test[y_test == -1] = 0
123
124
"""
125
## Build a model
126
127
We build a Fully Convolutional Neural Network originally proposed in
128
[this paper](https://arxiv.org/abs/1611.06455).
129
The implementation is based on the TF 2 version provided
130
[here](https://github.com/hfawaz/dl-4-tsc/).
131
The following hyperparameters (kernel_size, filters, the usage of BatchNorm) were found
132
via random search using [KerasTuner](https://github.com/keras-team/keras-tuner).
133
134
"""
135
136
137
def make_model(input_shape):
138
input_layer = keras.layers.Input(input_shape)
139
140
conv1 = keras.layers.Conv1D(filters=64, kernel_size=3, padding="same")(input_layer)
141
conv1 = keras.layers.BatchNormalization()(conv1)
142
conv1 = keras.layers.ReLU()(conv1)
143
144
conv2 = keras.layers.Conv1D(filters=64, kernel_size=3, padding="same")(conv1)
145
conv2 = keras.layers.BatchNormalization()(conv2)
146
conv2 = keras.layers.ReLU()(conv2)
147
148
conv3 = keras.layers.Conv1D(filters=64, kernel_size=3, padding="same")(conv2)
149
conv3 = keras.layers.BatchNormalization()(conv3)
150
conv3 = keras.layers.ReLU()(conv3)
151
152
gap = keras.layers.GlobalAveragePooling1D()(conv3)
153
154
output_layer = keras.layers.Dense(num_classes, activation="softmax")(gap)
155
156
return keras.models.Model(inputs=input_layer, outputs=output_layer)
157
158
159
model = make_model(input_shape=x_train.shape[1:])
160
keras.utils.plot_model(model, show_shapes=True)
161
162
"""
163
## Train the model
164
165
"""
166
167
epochs = 500
168
batch_size = 32
169
170
callbacks = [
171
keras.callbacks.ModelCheckpoint(
172
"best_model.keras", save_best_only=True, monitor="val_loss"
173
),
174
keras.callbacks.ReduceLROnPlateau(
175
monitor="val_loss", factor=0.5, patience=20, min_lr=0.0001
176
),
177
keras.callbacks.EarlyStopping(monitor="val_loss", patience=50, verbose=1),
178
]
179
model.compile(
180
optimizer="adam",
181
loss="sparse_categorical_crossentropy",
182
metrics=["sparse_categorical_accuracy"],
183
)
184
history = model.fit(
185
x_train,
186
y_train,
187
batch_size=batch_size,
188
epochs=epochs,
189
callbacks=callbacks,
190
validation_split=0.2,
191
verbose=1,
192
)
193
194
"""
195
## Evaluate model on test data
196
"""
197
198
model = keras.models.load_model("best_model.keras")
199
200
test_loss, test_acc = model.evaluate(x_test, y_test)
201
202
print("Test accuracy", test_acc)
203
print("Test loss", test_loss)
204
205
"""
206
## Plot the model's training and validation loss
207
"""
208
209
metric = "sparse_categorical_accuracy"
210
plt.figure()
211
plt.plot(history.history[metric])
212
plt.plot(history.history["val_" + metric])
213
plt.title("model " + metric)
214
plt.ylabel(metric, fontsize="large")
215
plt.xlabel("epoch", fontsize="large")
216
plt.legend(["train", "val"], loc="best")
217
plt.show()
218
plt.close()
219
220
"""
221
We can see how the training accuracy reaches almost 0.95 after 100 epochs.
222
However, by observing the validation accuracy we can see how the network still needs
223
training until it reaches almost 0.97 for both the validation and the training accuracy
224
after 200 epochs. Beyond the 200th epoch, if we continue on training, the validation
225
accuracy will start decreasing while the training accuracy will continue on increasing:
226
the model starts overfitting.
227
"""
228
229