Introduction
The error AttributeError: 'Tensor' object has no attribute '_keras_shape' (or 'keras_shape') occurs when mixing TensorFlow 1.x-style code with TensorFlow 2.x, or when passing raw TensorFlow tensors to Keras layers that expect Keras tensors. In TensorFlow 1.x, Keras tracked shape information using a custom _keras_shape attribute on tensors. TensorFlow 2.x removed this attribute because eager execution makes shape information available directly through tensor.shape. The fix is to use TensorFlow 2.x APIs consistently and avoid mixing raw tf.Tensor objects with Keras symbolic tensors.
Why This Error Happens
In TensorFlow 1.x with standalone Keras, the library attached a _keras_shape attribute to tensors to track shape information through the computation graph:
1# TensorFlow 1.x / standalone Keras (OLD — causes error in TF2)
2import keras
3from keras import backend as K
4
5# In TF1, Keras added _keras_shape to tensors
6x = K.placeholder(shape=(None, 10))
7print(x._keras_shape) # (None, 10) — worked in TF1
8
9# In TF2, this attribute no longer exists
10import tensorflow as tf
11x = tf.constant([[1.0, 2.0]])
12print(x._keras_shape) # AttributeError!
Fix 1: Use tf.keras Instead of Standalone Keras
The most common cause is importing standalone keras instead of tf.keras:
1# WRONG — standalone keras may conflict with TF2
2import keras
3from keras.layers import Dense
4from keras.models import Model
5
6# CORRECT — use tf.keras for TF2 compatibility
7import tensorflow as tf
8from tensorflow.keras.layers import Dense
9from tensorflow.keras.models import Model
Uninstall standalone Keras if both are installed:
pip uninstall keras
pip install tensorflow # tf.keras is included
Keras layers expect keras.Input() tensors, not raw tf.Tensor objects:
1import tensorflow as tf
2
3# WRONG — raw tensor passed to Keras layer
4raw_tensor = tf.constant([[1.0, 2.0, 3.0]])
5dense = tf.keras.layers.Dense(10)
6output = dense(raw_tensor) # May work in eager mode but fails in model building
7
8# CORRECT — use keras.Input for model definition
9inputs = tf.keras.Input(shape=(3,))
10x = tf.keras.layers.Dense(10, activation='relu')(inputs)
11outputs = tf.keras.layers.Dense(1)(x)
12model = tf.keras.Model(inputs=inputs, outputs=outputs)
TensorFlow 2.x deprecated K.placeholder(). Use tf.keras.Input() instead:
1# WRONG — TF1 style
2from tensorflow.keras import backend as K
3x = K.placeholder(shape=(None, 784)) # May cause _keras_shape error
4
5# CORRECT — TF2 style
6x = tf.keras.Input(shape=(784,))
Fix 4: Update Custom Layers
Custom layers that access _keras_shape must be updated:
1# WRONG — accessing deprecated attribute
2class OldCustomLayer(tf.keras.layers.Layer):
3 def call(self, inputs):
4 shape = inputs._keras_shape # AttributeError in TF2
5 return tf.reshape(inputs, (-1, shape[-1]))
6
7# CORRECT — use tensor.shape or tf.shape()
8class CustomLayer(tf.keras.layers.Layer):
9 def call(self, inputs):
10 # Static shape (known at graph build time)
11 static_shape = inputs.shape # TensorShape object
12 last_dim = static_shape[-1]
13
14 # Dynamic shape (evaluated at runtime)
15 dynamic_shape = tf.shape(inputs)
16
17 return tf.reshape(inputs, (-1, last_dim))
Getting Tensor Shapes in TF2
1import tensorflow as tf
2
3x = tf.constant([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
4
5# Static shape — known at graph construction time
6print(x.shape) # TensorShape([2, 3])
7print(x.shape[0]) # 2
8print(x.shape.as_list()) # [2, 3]
9
10# Dynamic shape — evaluated at runtime (useful inside tf.function)
11dynamic = tf.shape(x) # Tensor([2, 3], dtype=int32)
12
13# For Keras layers, use build() to access input shape
14class MyLayer(tf.keras.layers.Layer):
15 def build(self, input_shape):
16 self.units = input_shape[-1]
17 self.kernel = self.add_weight(
18 shape=(self.units, 64),
19 initializer='glorot_uniform'
20 )
21
22 def call(self, inputs):
23 return tf.matmul(inputs, self.kernel)
Static vs Dynamic Shapes
1@tf.function
2def process(x):
3 # Static shape — may have None dimensions
4 print(x.shape) # (None, 10) — batch size unknown
5
6 # Dynamic shape — always concrete at runtime
7 batch_size = tf.shape(x)[0] # Actual batch size
8
9 return tf.reshape(x, (batch_size, -1))
10
11# When shapes are fully known
12concrete = tf.constant([[1.0, 2.0]])
13print(concrete.shape) # (1, 2) — fully defined
14
15# When shapes have unknown dimensions (inside tf.function, Keras models)
16inputs = tf.keras.Input(shape=(10,))
17print(inputs.shape) # (None, 10) — batch dim is None
Migration Checklist
1# 1. Replace standalone keras imports
2# OLD: import keras
3# NEW: import tensorflow as tf; from tensorflow import keras
4
5# 2. Replace K.placeholder
6# OLD: x = K.placeholder(shape=(None, 10))
7# NEW: x = tf.keras.Input(shape=(10,))
8
9# 3. Replace _keras_shape access
10# OLD: shape = tensor._keras_shape
11# NEW: shape = tensor.shape
12
13# 4. Replace K.int_shape
14# OLD: dims = K.int_shape(tensor)
15# NEW: dims = tensor.shape.as_list()
16
17# 5. Update get_shape() calls
18# OLD: tensor.get_shape().as_list()
19# NEW: tensor.shape.as_list() # Simpler in TF2
Common Pitfalls
Having both keras and tensorflow installed: Standalone keras and tf.keras conflict. Uninstall standalone keras (pip uninstall keras) and use only tensorflow.keras to avoid attribute errors.
Using K.placeholder() in TF2: K.placeholder() creates TF1-style placeholders that lack TF2 shape tracking. Replace with tf.keras.Input() which integrates with the Keras functional API.
Passing raw tensors to Keras model-building code: Keras functional API expects Input() tensors at the start of the graph. Passing tf.constant() or tf.Variable directly to layers during model construction causes shape tracking failures.
Confusing static and dynamic shapes: tensor.shape returns the static shape (may contain None). tf.shape(tensor) returns the dynamic shape (always concrete at runtime). Use static shapes for layer configuration and dynamic shapes for runtime operations.
Running TF1 code without tf.compat.v1 compatibility mode: Legacy code that depends on _keras_shape can be run with import tensorflow.compat.v1 as tf; tf.disable_v2_behavior() as a temporary migration aid, but updating to TF2 APIs is the proper fix.
Summary
The _keras_shape attribute was removed in TensorFlow 2.x — use tensor.shape instead
Replace import keras with from tensorflow import keras (or import tensorflow.keras)
Use tf.keras.Input(shape=(...)) instead of K.placeholder()
Access shapes with tensor.shape (static) or tf.shape(tensor) (dynamic)
Update custom layers to use input_shape in build() instead of _keras_shape