TensorFlow 2.0: Keras as the Standard

ai machine-learning tensorflow python

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

TensorFlow 2.0 vs PyTorch

AspectTensorFlow 2.0PyTorch
Default modeEager executionEager execution
ProductionTF Serving, TFLiteTorchServe, ONNX
Research popularityGrowingHigher
Mobile/EdgeTFLite (mature)PyTorch Mobile
Enterprise supportGoogle CloudAWS (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.

All posts