Skip to content

Unmodeled Reshape and Permute layer classes drop the output shape for the rest of the chain #840

Description

@khatchad

An unmodeled Keras layer class in the middle of a chain drops the output shape for everything downstream of it. Two such classes are reachable in ordinary models: MaxPool2D and LSTM.

Witness

The fixture tf2_test_layer_call_result_rank.py carries four chains behind four sinks, all ending in the same Dense(10) and all called with the same batch of 32.

chain consumer types as
Dense only (32, 10) float32
Conv2D then Flatten then Dense (32, 10) float32
Conv2D then MaxPool2D then Flatten then Dense {? of float32}
LSTM then Dense {? of float32}

The first two rows are the controls and they pass, so the isolation is pinned by passing tests beside the failing ones rather than by removing code and re-running. The dtype survives in every row. What is lost is the shape, and it is lost as rather than as a wildcard axis, so the result declares nothing about rank at all.

Inserting a single pooling layer into a chain that otherwise resolves is what turns the third row from the second.

Cause

The model file describes the layer classes Dense, Conv2D, Flatten, Embedding, Dropout and GlobalAveragePooling1D, each paired with a generator that computes the call's output shape. Neither MaxPool2D nor LSTM appears. An instance of an unmodeled class is called rather than applied, so the call produces no output shape and every value after it in the chain inherits .

This is a gap in an existing family rather than an absent capability: GlobalAveragePooling1DCall already models a rank-reducing pooling call, so the shape of the fix is established.

Why It Is Worth Prioritising

A shape is not a coarse answer, it is the absence of one. A declaration derived from it constrains nothing, and downstream consumers that read a static axis cannot be served by it at all. Pooling after a convolution and a recurrent layer before a projection are both ordinary shapes for a model to take, so the two missing classes sit on common paths rather than exotic ones.

MaxPool2D is the more mechanical of the two: the output shape is a function of the input shape, the pool size, the strides and the padding, in the same way the existing pooling generator computes its own. LSTM needs the units argument stored on the instance and read back at the call, in the same way Dense stores units and Embedding stores output_dim, with the wrinkle that return_sequences decides whether the temporal axis survives.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions