TensorFlow 2.0: Keras as the Standard
TensorFlow 1.x was powerful but painful. Sessions, graphs, inconsistent APIs—it was showing its age. TensorFlow 2.0 fixes this by embracing Keras as the high-level API.
What Changed
Before: TensorFlow 1.x Pain
import tensorflow as tf
# Define computation graph
x = tf.placeholder(tf.float32, shape=[None, 784])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
y = tf.nn.softmax(tf.matmul(x, W) + b)
# You can't just run this
# Need a session
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
result = sess.run(y, feed_dict={x: data})
Confusing separation between graph definition and execution.
After: TensorFlow 2.0 Simplicity
import tensorflow as tf
# Eager execution by default
x = tf.constant([[1.0, 2.0, 3.0]])
W = tf.Variable(tf.random.normal([3, 2]))
result = tf.matmul(x, W) # Runs immediately!
print(result.numpy())
Write code like NumPy. Execute immediately.
Keras as the Standard
The Sequential API
For simple stack-of-layers models:
import tensorflow as tf
from tensorflow import keras
model = keras.Sequential([
keras.layers.Dense(128, activation='relu', input_shape=(784,)),
keras.layers.Dropout(0.2),
keras.layers.Dense(64, activation='relu'),
keras.layers.Dense(10, activation='softmax')
])
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
model.fit(x_train, y_train, epochs=5, validation_split=0.2)
Clean, readable, pythonic.
The Functional API
For complex architectures:
inputs = keras.Input(shape=(784,))
x = keras.layers.Dense(128, activation='relu')(inputs)
x = keras.layers.Dropout(0.2)(x)
x = keras.layers.Dense(64, activation='relu')(x)
outputs = keras.layers.Dense(10, activation='softmax')(x)
model = keras.Model(inputs=inputs, outputs=outputs)
Subclassing for Full Control
When you need complete flexibility:
class MyModel(keras.Model):
def __init__(self):
super().__init__()
self.dense1 = keras.layers.Dense(128, activation='relu')
self.dropout = keras.layers.Dropout(0.2)
self.dense2 = keras.layers.Dense(10)
def call(self, inputs, training=False):
x = self.dense1(inputs)
if training:
x = self.dropout(x, training=training)
return self.dense2(x)
model = MyModel()
Training Improvements
Built-in Training Loop
model.fit(
train_dataset,
epochs=10,
validation_data=val_dataset,
callbacks=[
keras.callbacks.EarlyStopping(patience=3),
keras.callbacks.TensorBoard(log_dir='./logs'),
keras.callbacks.ModelCheckpoint('best_model.h5', save_best_only=True)
]
)
Custom Training Loops
When you need control:
@tf.function
def train_step(x, y):
with tf.GradientTape() as tape:
predictions = model(x, training=True)
loss = loss_fn(y, predictions)
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
return loss
for epoch in range(epochs):
for x_batch, y_batch in train_dataset:
loss = train_step(x_batch, y_batch)
tf.function for Performance
Convert Python functions to TensorFlow graphs:
@tf.function
def compute(x):
return tf.reduce_mean(tf.square(x))
# First call: traces and compiles
result = compute(tf.constant([1.0, 2.0, 3.0]))
# Subsequent calls: fast graph execution
result = compute(tf.constant([4.0, 5.0, 6.0]))
Get eager execution ergonomics with graph performance.
Data Pipeline with tf.data
dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
dataset = dataset.shuffle(buffer_size=10000)
dataset = dataset.batch(32)
dataset = dataset.prefetch(tf.data.AUTOTUNE)
# Or from files
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(parse_function, num_parallel_calls=tf.data.AUTOTUNE)
dataset = dataset.batch(32).prefetch(tf.data.AUTOTUNE)
Efficient, scalable data loading.
Distributed Training
strategy = tf.distribute.MirroredStrategy()
with strategy.scope():
model = create_model()
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy')
model.fit(train_dataset, epochs=10)
Multi-GPU training with minimal code changes.
SavedModel Format
Universal model format:
# Save
model.save('my_model')
# Load
loaded_model = keras.models.load_model('my_model')
# Export for TensorFlow Serving
tf.saved_model.save(model, 'serving_model')
Migration from TensorFlow 1.x
Automated Script
tf_upgrade_v2 --infile old_code.py --outfile new_code.py
Handles most mechanical changes.
Manual Changes
- Remove
tf.Session,feed_dict - Replace
tf.placeholderwith function arguments - Use
tf.functionfor performance-critical paths - Switch to
tf.keraslayers and models
TensorFlow 2.0 vs PyTorch
| Aspect | TensorFlow 2.0 | PyTorch |
|---|---|---|
| Default mode | Eager execution | Eager execution |
| Production | TF Serving, TFLite | TorchServe, ONNX |
| Research popularity | Growing | Higher |
| Mobile/Edge | TFLite (mature) | PyTorch Mobile |
| Enterprise support | Google Cloud | AWS (primary) |
Both are excellent. TensorFlow has better production tooling. PyTorch has more research momentum.
Getting Started
pip install tensorflow
# Or GPU version
pip install tensorflow-gpu
import tensorflow as tf
# Verify GPU
print(tf.config.list_physical_devices('GPU'))
Final Thoughts
TensorFlow 2.0 is the cleanup the framework needed. Keras as the default makes deep learning accessible. Eager execution makes debugging possible.
If you left TensorFlow for PyTorch due to usability, give TF 2.0 a try. The experience is dramatically improved.
Simple things should be simple. Complex things should be possible.