I have been following this tutorial on using an LSTM model for timeseries classification: https://www.tensorflow.org/tutorials/structured_data/time_series
My dataset consists of multiple timeseries, which I partition/pad into equal window sizes and then make sure that each input "window" is treated separately.
I would like to weight the different windows, either by sample or by class, but so far have hit issues in both cases.
Here is my adapted code, attempting sample-based weighting:
class WindowGenerator():
def __init__(self, input_width, label_width, shift,
train_df, val_df, test_df, window_width,
input_columns=None, label_columns=None, weight_column=['weight']):
if window_width is not None:
if (input_width + shift != window_width) :
raise ValueError('Data partitioned into windows of length %d'%window_width)
# Store the raw data.
self.train_df = train_df
self.val_df = val_df
self.test_df = test_df
# Work out the label column indices.
self.column_indices = {name: i for i, name in
enumerate(train_df.columns)}
self.input_columns = input_columns
if input_columns is not None:
self.input_columns_indices = {name: i for i, name in
enumerate(input_columns)}
self.weight_column = weight_column
if weight_column is not None:
self.weight_column_index = {name: i for i, name in
enumerate(weight_column)}
self.label_columns = label_columns
if label_columns is not None:
self.label_columns_indices = {name: i for i, name in
enumerate(label_columns)}
# Work out the window parameters.
self.input_width = input_width
self.label_width = label_width
self.shift = shift
self.total_window_size = input_width + shift
self.input_slice = slice(0, input_width)
self.input_indices = np.arange(self.total_window_size)[self.input_slice]
self.label_start = self.total_window_size - self.label_width
self.labels_slice = slice(self.label_start, None)
self.label_indices = np.arange(self.total_window_size)[self.labels_slice]
def __repr__(self):
return '\n'.join([
f'Total window size: {self.total_window_size}',
f'Input indices: {self.input_indices}',
f'Label indices: {self.label_indices}',
f'Input column name(s): {self.input_columns}',
f'Label column name(s): {self.label_columns}'])
def split_window(self, features):
inputs = features[:, self.input_slice, :]
if self.input_columns is not None:
inputs = tf.stack(
[inputs[:, :, self.column_indices[name]] for name in self.input_columns],
axis=-1)
weights = features[:, self.input_slice, :]
if self.weight_column is not None:
weights = tf.stack(
[weights[:, :, self.column_indices[name]] for name in self.weight_column],
axis=-1)
labels = features[:, self.labels_slice, :]
if self.label_columns is not None:
labels = tf.stack(
[labels[:, :, self.column_indices[name]] for name in self.label_columns],
axis=-1)
# Slicing doesn't preserve static shape information, so set the shapes
# manually. This way the `tf.data.Datasets` are easier to inspect.
inputs.set_shape([None, self.input_width, None])
weights.set_shape([None, self.input_width, None])
labels.set_shape([None, self.label_width, None])
return inputs, labels, weights
def plot(self, model=None, plot_col='pressure', sample_freq=30, max_subplots=3):
inputs, labels = self.example
plt.figure(figsize=(12, 8))
plot_col_index = self.column_indices[plot_col]
max_n = min(max_subplots, len(inputs))
for n in range(max_n):
plt.subplot(max_n, 1, n+1)
plt.ylabel(f'{plot_col} [normed]')
plt.plot(self.input_indices/sample_freq, inputs[n, :, plot_col_index],
label='Inputs', marker='.', zorder=-10)
if self.label_columns:
label_col_index = self.label_columns_indices.get(plot_col, None)
else:
label_col_index = plot_col_index
if label_col_index is None:
continue
plt.scatter(self.label_indices/sample_freq, labels[n, :, label_col_index],
edgecolors='k', label='Labels', c='#2ca02c', s=64)
if model is not None:
predictions = model(inputs)
try:
plt.scatter(self.label_indices/sample_freq, predictions[n, :, label_col_index],
marker='X', edgecolors='k', label='Predictions',
c='#ff7f0e', s=64)
except:
plt.scatter(self.label_indices/sample_freq, predictions[n, -1, label_col_index],
marker='X', edgecolors='k', label='Predictions',
c='#ff7f0e', s=64)
if n == 0:
plt.legend()
plt.xlabel('time offset [s]')
def make_dataset(self, data):
data = np.array(data, dtype=np.float32)
ds = tf.keras.preprocessing.timeseries_dataset_from_array(
data=data,
targets=None,
sequence_length=self.total_window_size,
sequence_stride=self.total_window_size,
shuffle=False,
batch_size=32,)
ds = ds.map(self.split_window)
return ds
@property
def train(self):
return self.make_dataset(self.train_df)
@property
def val(self):
return self.make_dataset(self.val_df)
@property
def test(self):
return self.make_dataset(self.test_df)
@property
def example(self):
"""Get and cache an example batch of `inputs, labels` for plotting."""
result = getattr(self, '_example', None)
if result is None:
# No example batch was found, so get one from the `.train` dataset
result = next(iter(self.train))
# And cache it for next time
self._example = result
return result
def compile_and_fit(model, window, patience=2, max_epochs = 20):
early_stopping = tf.keras.callbacks.EarlyStopping(monitor='val_loss',
patience=patience,
mode='min')
model.compile(loss="categorical_crossentropy", optimizer= "adam",
metrics=['categorical_accuracy', 'categorical_crossentropy'])
history = model.fit(window.train, epochs=max_epochs,
validation_data=window.val,
callbacks=[early_stopping]
)
return history
Here is my example call:
df_columns = input_columns+label_columns
wide_window = WindowGenerator(input_width=(window_width-1),
label_width=(window_width-1),shift=1,
train_df=train_df[df_columns+['weight']],
val_df=val_df[df_columns],
test_df=test_df[df_columns],
input_columns=input_columns,
label_columns=label_columns,
weight_column = ['weight'],
window_width=window_width)
print(wide_window)
for example_inputs, example_labels, example_weights in wide_window.train.take(1):
print(f'Inputs shape (batch, time, features): {example_inputs.shape}')
print(f'Labels shape (batch, time, features): {example_labels.shape}')
print(f'Weights shape (batch, time, features): {example_weights.shape}')
lstm = tf.keras.models.Sequential([
# Shape [batch, time, features] => [batch, time, lstm_units]
tf.keras.layers.LSTM(32, return_sequences=True),
# Shape => [batch, time, features]
tf.keras.layers.Dense(units=len(label_columns), activation='softmax')
])
And the output/error:
Total window size: 120
Input indices: [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
108 109 110 111 112 113 114 115 116 117 118]
Label indices: [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
109 110 111 112 113 114 115 116 117 118 119]
Input column name(s): ['f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6']
Label column name(s): ['c0', 'c1', 'c2', 'c3']
Inputs shape (batch, time, features): (32, 119, 7)
Labels shape (batch, time, features): (32, 119, 4)
Weights shape (batch, time, features): (32, 119, 1)
Wide window
Input shape: (32, 119, 7)
Labels shape: (32, 119, 4)
Weights shape: (32, 119, 1)
Output shape: (32, 119, 4)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
/var/folders/81/gpwcygkx6v39bl3d3p38n0n00000gn/T/ipykernel_8173/3400155790.py in <module>
48 continue
49
---> 50 history = compile_and_fit(lstm, wide_window, max_epochs = num_epochs)
51
52 stacked_predictions = lstm.predict(wide_window.train)
/var/folders/81/gpwcygkx6v39bl3d3p38n0n00000gn/T/ipykernel_8173/2330381046.py in compile_and_fit(model, window, patience, max_epochs)
8
9 history = model.fit(window.train, epochs=max_epochs,
---> 10 validation_data=window.val,
11 callbacks=[early_stopping]
12 )
/var/folders/81/gpwcygkx6v39bl3d3p38n0n00000gn/T/ipykernel_8173/2013461020.py in val(self)
143 @property
144 def val(self):
--> 145 return self.make_dataset(self.val_df)
146
147 @property
/var/folders/81/gpwcygkx6v39bl3d3p38n0n00000gn/T/ipykernel_8173/2013461020.py in make_dataset(self, data)
133 batch_size=32,)
134
--> 135 ds = ds.map(self.split_window)
136
137 return ds
~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/tensorflow/python/data/ops/dataset_ops.py in map(self, map_func, num_parallel_calls, deterministic, name)
2002 warnings.warn("The `deterministic` argument has no effect unless the "
2003 "`num_parallel_calls` argument is specified.")
-> 2004 return MapDataset(self, map_func, preserve_cardinality=True, name=name)
2005 else:
2006 return ParallelMapDataset(
~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/tensorflow/python/data/ops/dataset_ops.py in __init__(self, input_dataset, map_func, use_inter_op_parallelism, preserve_cardinality, use_legacy_function, name)
5453 self._use_inter_op_parallelism = use_inter_op_parallelism
5454 self._preserve_cardinality = preserve_cardinality
-> 5455 self._map_func = StructuredFunctionWrapper(
5456 map_func,
5457 self._transformation_name(),
~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/tensorflow/python/data/ops/dataset_ops.py in __init__(self, func, transformation_name, dataset, input_classes, input_shapes, input_types, input_structure, add_to_graph, use_legacy_function, defun_kwargs)
4531 fn_factory = trace_tf_function(defun_kwargs)
4532
-> 4533 self._function = fn_factory()
4534 # There is no graph to add in eager mode.
4535 add_to_graph &= not context.executing_eagerly()
~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/tensorflow/python/eager/function.py in get_concrete_function(self, *args, **kwargs)
3242 or `tf.Tensor` or `tf.TensorSpec`.
3243 """
-> 3244 graph_function = self._get_concrete_function_garbage_collected(
3245 *args, **kwargs)
3246 graph_function._garbage_collector.release() # pylint: disable=protected-access
~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/tensorflow/python/eager/function.py in _get_concrete_function_garbage_collected(self, *args, **kwargs)
3208 args, kwargs = None, None
3209 with self._lock:
-> 3210 graph_function, _ = self._maybe_define_function(args, kwargs)
3211 seen_names = set()
3212 captured = object_identity.ObjectIdentitySet(
~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/tensorflow/python/eager/function.py in _maybe_define_function(self, args, kwargs)
3555
3556 self._function_cache.missed.add(call_context_key)
-> 3557 graph_function = self._create_graph_function(args, kwargs)
3558 self._function_cache.primary[cache_key] = graph_function
3559
~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/tensorflow/python/eager/function.py in _create_graph_function(self, args, kwargs, override_flat_arg_shapes)
3390 arg_names = base_arg_names + missing_arg_names
3391 graph_function = ConcreteFunction(
-> 3392 func_graph_module.func_graph_from_py_func(
3393 self._name,
3394 self._python_function,
~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/tensorflow/python/framework/func_graph.py in func_graph_from_py_func(name, python_func, args, kwargs, signature, func_graph, autograph, autograph_options, add_control_dependencies, arg_names, op_return_value, collections, capture_by_value, override_flat_arg_shapes, acd_record_initial_resource_uses)
1141 _, original_func = tf_decorator.unwrap(python_func)
1142
-> 1143 func_outputs = python_func(*func_args, **func_kwargs)
1144
1145 # invariant: `func_outputs` contains only Tensors, CompositeTensors,
~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/tensorflow/python/data/ops/dataset_ops.py in wrapped_fn(*args)
4508 attributes=defun_kwargs)
4509 def wrapped_fn(*args): # pylint: disable=missing-docstring
-> 4510 ret = wrapper_helper(*args)
4511 ret = structure.to_tensor_list(self._output_structure, ret)
4512 return [ops.convert_to_tensor(t) for t in ret]
~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/tensorflow/python/data/ops/dataset_ops.py in wrapper_helper(*args)
4438 if not _should_unpack(nested_args):
4439 nested_args = (nested_args,)
-> 4440 ret = autograph.tf_convert(self._func, ag_ctx)(*nested_args)
4441 if _should_pack(ret):
4442 ret = tuple(ret)
~/.pyenv/versions/3.8.12/lib/python3.8/site-packages/tensorflow/python/autograph/impl/api.py in wrapper(*args, **kwargs)
697 except Exception as e: # pylint:disable=broad-except
698 if hasattr(e, 'ag_error_metadata'):
--> 699 raise e.ag_error_metadata.to_exception(e)
700 else:
701 raise
ValueError: in user code:
File "/var/folders/81/gpwcygkx6v39bl3d3p38n0n00000gn/T/ipykernel_8173/1692845515.py", line 68, in split_window *
weights = tf.stack(
ValueError: slice index 11 of dimension 2 out of bounds. for '{{node strided_slice_9}} = StridedSlice[Index=DT_INT32, T=DT_FLOAT, begin_mask=3, ellipsis_mask=0, end_mask=3, new_axis_mask=0, shrink_axis_mask=4](strided_slice_8, strided_slice_9/stack, strided_slice_9/stack_1, strided_slice_9/stack_2)' with input shapes: [?,?,11], [3], [3], [3] and with computed input tensors: input[1] = <0 0 11>, input[2] = <0 0 12>, input[3] = <1 1 1>.
And when I attempted feeding just a dictionary of weights into the model.fit() call (so only using ds = ds.map((inputs, labels)), etc.) I received the error
ValueError: `class_weight` not supported for 3+ dimensional targets.
Does anyone know how to work around either of these issues?
Thank you!