TensorFlow Basics: A Beginner's Guide
TensorFlow is Google’s end-to-end open-source machine learning platform. TensorFlow 2.x integrates Keras as its high-level API, providing an accessible interface for building, training, and deploying deep learning models.
Tensors: The Core Data Structure
Tensors are multi-dimensional arrays, like NumPy arrays but optimized for GPU computation:
import tensorflow as tf
# Creating tensors
scalar = tf.constant(42) # rank 0
vector = tf.constant([1, 2, 3]) # rank 1
matrix = tf.constant([[1, 2], [3, 4]]) # rank 2
tensor_3d = tf.constant([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) # rank 3
# Basic properties
print(f"Shape: {tensor_3d.shape}")
print(f"Rank: {tf.rank(tensor_3d)}")
print(f"Data type: {tensor_3d.dtype}")
print(f"Size: {tf.size(tensor_3d)}")
# Tensor operations
a = tf.constant([[1, 2], [3, 4]])
b = tf.constant([[5, 6], [7, 8]])
c = tf.add(a, b) # [[6, 8], [10, 12]]
d = tf.matmul(a, b) # matrix multiply: [[19, 22], [43, 50]]
e = tf.reduce_mean(a) # scalar mean: 2.5
f = tf.reshape(a, [4]) # flatten: [1, 2, 3, 4]
# Converting to/from NumPy
import numpy as np
np_array = np.array([[1, 2], [3, 4]])
tensor = tf.convert_to_tensor(np_array)
back_to_numpy = tensor.numpy()Eager Execution
TensorFlow 2.x runs eagerly by default — operations execute immediately as they are called:
# Eager execution (default)
x = tf.constant([[1., 2.], [3., 4.]])
y = tf.constant([[5., 6.], [7., 8.]])
z = x @ y + tf.ones((2, 2)) # immediate computation
print(z)
# tf.Tensor(
# [[19. 21.]
# [46. 52.]], shape=(2, 2), dtype=float32)
# You can print, debug, and inspect at any point
print("Shape:", z.shape)
print("Value:", z.numpy())The Keras API
Keras is TensorFlow’s high-level API for building and training models.
Sequential API (Simple Models)
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
layers.Dense(64, activation='relu', input_shape=(20,)),
layers.Dropout(0.5),
layers.Dense(64, activation='relu'),
layers.Dense(10, activation='softmax'),
])
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'],
)
history = model.fit(
x_train, y_train,
batch_size=32,
epochs=50,
validation_split=0.2,
callbacks=[
keras.callbacks.EarlyStopping(patience=5),
keras.callbacks.ModelCheckpoint('best_model.h5', save_best_only=True),
]
)
test_loss, test_acc = model.evaluate(x_test, y_test)
print(f"Test accuracy: {test_acc:.3f}")Functional API (Complex Models)
For models with multiple inputs, outputs, or shared layers:
# Functional API — more flexible
inputs = keras.Input(shape=(20,), name='features')
# Shared layers
dense = layers.Dense(64, activation='relu')
x = dense(inputs)
x = layers.Dropout(0.5)(x)
x = dense(x) # shared dense layer reused
x = layers.Dense(32, activation='relu')(x)
# Multiple outputs
output_main = layers.Dense(10, activation='softmax', name='main')(x)
output_aux = layers.Dense(1, activation='sigmoid', name='auxiliary')(x)
model = keras.Model(
inputs=inputs,
outputs=[output_main, output_aux],
name='multi_output_model'
)
model.compile(
optimizer='adam',
loss={
'main': 'sparse_categorical_crossentropy',
'auxiliary': 'binary_crossentropy',
},
loss_weights={'main': 1.0, 'auxiliary': 0.2},
metrics=['accuracy'],
)Subclassing API (Full Control)
class CustomModel(keras.Model):
def __init__(self):
super().__init__()
self.conv1 = layers.Conv2D(32, 3, activation='relu')
self.pool = layers.GlobalAveragePooling2D()
self.dropout = layers.Dropout(0.5)
self.fc = layers.Dense(10, activation='softmax')
def call(self, inputs, training=False):
x = self.conv1(inputs)
x = self.pool(x)
x = self.dropout(x, training=training) # dropout only during training
return self.fc(x)
model = CustomModel()Custom Training Loops
When model.fit() is not enough:
# Define components
model = keras.Sequential([layers.Dense(10)])
optimizer = keras.optimizers.Adam(learning_rate=0.001)
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)
train_acc = keras.metrics.SparseCategoricalAccuracy()
# Training step
@tf.function
def train_step(x, y):
with tf.GradientTape() as tape:
logits = model(x, training=True)
loss = loss_fn(y, logits)
gradients = tape.gradient(loss, model.trainable_weights)
optimizer.apply_gradients(zip(gradients, model.trainable_weights))
train_acc.update_state(y, logits)
return loss
# Training loop
for epoch in range(10):
train_acc.reset_state()
for batch_x, batch_y in train_dataset:
loss = train_step(batch_x, batch_y)
print(f"Epoch {epoch}: loss={loss:.4f}, acc={train_acc.result():.3f}")Callbacks
Callbacks hook into the training process at various points:
callbacks = [
# Stop when validation performance plateaus
keras.callbacks.EarlyStopping(
monitor='val_loss',
patience=10,
restore_best_weights=True,
),
# Save best model during training
keras.callbacks.ModelCheckpoint(
'model.{epoch:02d}-{val_loss:.2f}.h5',
monitor='val_loss',
save_best_only=True,
),
# Reduce learning rate when plateau
keras.callbacks.ReduceLROnPlateau(
monitor='val_loss',
factor=0.5,
patience=5,
min_lr=1e-6,
),
# TensorBoard logging
keras.callbacks.TensorBoard(
log_dir='./logs',
histogram_freq=1,
),
# Custom callback
class CustomCallback(keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs=None):
if logs.get('accuracy') > 0.95:
print(f"\nReached 95% accuracy at epoch {epoch}")
self.model.stop_training = True
]
model.fit(x_train, y_train, callbacks=callbacks, epochs=100)Saving and Loading
# Save entire model
model.save('my_model.keras') # Keras v3 format
model.save('my_model.h5') # H5 format
# Load model
loaded = keras.models.load_model('my_model.keras')
# Save weights only
model.save_weights('model_weights.h5')
model.load_weights('model_weights.h5')
# Save architecture only (JSON)
json_config = model.to_json()
with open('model_config.json', 'w') as f:
f.write(json_config)
# Load architecture from JSON
model = keras.models.model_from_json(json_config)Distribution Strategy
Train across multiple GPUs:
# Mirrored strategy (single host, multiple GPUs)
strategy = tf.distribute.MirroredStrategy()
print(f"Number of devices: {strategy.num_replicas_in_sync}")
with strategy.scope():
model = create_model()
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy')
model.fit(train_dataset, epochs=10)Real-World Implementation Tips
Production Considerations
When moving from development to production, several factors become critical. Error handling should be comprehensive — every external call (database, API, file system) should have proper error checking, logging, and retry logic where appropriate. Performance monitoring through metrics and structured logging helps identify bottlenecks before they affect users.
Testing Strategy
A thorough testing approach combines multiple levels:
- Unit tests verify individual functions and methods in isolation
- Integration tests validate that components work together correctly
- Edge case tests cover boundary conditions, empty inputs, and error states
- Performance tests ensure the system meets latency and throughput requirements
Test data should be realistic but controlled. Mock external dependencies to make tests fast and deterministic. Aim for tests that are independent, repeatable, and fast enough to run on every commit.
Documentation
Good documentation is essential for maintainable code. Follow these principles:
- Document the “why” not just the “what” — explain design decisions
- Keep examples up to date with the code
- Include usage examples for public APIs
- Document configuration options and their defaults
- Explain error conditions and recovery strategies
Security Best Practices
Security should be considered throughout development:
- Validate all inputs at system boundaries
- Use parameterized queries for database access
- Store secrets in environment variables or secret managers
- Keep dependencies updated to patch vulnerabilities
- Apply the principle of least privilege
Performance Optimization
Optimize based on measured data, not assumptions:
- Profile before optimizing — identify actual bottlenecks
- Measure the impact of each change
- Consider the trade-off between speed and readability
- Cache expensive operations with appropriate invalidation
- Use connection pooling for database and network resources
Monitoring and Observability
Production systems need visibility:
- Structured logging with correlation IDs for request tracking
- Metrics for latency, throughput, error rates, and resource usage
- Health check endpoints for load balancers and orchestration
- Distributed tracing for request flows across services
- Alerts for anomaly detection based on baselines
These patterns apply across all programming languages and frameworks. The specific implementation varies, but the principles remain consistent.
FAQ
Q: What is the difference between tf.constant and tf.Variable?
A: tf.constant is immutable (cannot be changed). tf.Variable is mutable — used for model weights that are updated during training.
Q: Should I use Keras Sequential or Functional API? A: Sequential for simple feed-forward models. Functional for multi-input, multi-output, or branched architectures. Subclassing for research or custom behavior that the other APIs cannot express.
Q: How do I use TensorFlow with GPUs?
A: TensorFlow automatically detects and uses CUDA-compatible GPUs. Verify with tf.config.list_physical_devices('GPU'). Use tf.distribute.MirroredStrategy() for multi-GPU training.
Q: What is the difference between model.fit and a custom training loop?
A: model.fit is convenient for standard training. Custom loops give full control over each step, useful for research, GANs, or non-standard loss computations.
Q: How do I handle large datasets that don’t fit in memory?
A: Use tf.data.Dataset for efficient data pipelines. It supports streaming, caching, prefetching, parallel processing, and can read from files in batches.
Q: What is TF Lite and when should I use it? A: TF Lite optimizes models for mobile, embedded, and edge devices. Use it when you need on-device inference with low latency, offline capability, or privacy constraints.
TensorFlow 2.x Workflow
Custom Training Loop
import tensorflow as tf
# Define model, loss, optimizer
model = tf.keras.Sequential([...])
loss_fn = tf.keras.losses.SparseCategoricalCrossentropy()
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
# Custom training step
@tf.function
def train_step(x_batch, y_batch):
with tf.GradientTape() as tape:
logits = model(x_batch, training=True)
loss = loss_fn(y_batch, logits)
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
return loss
# Training loop
for epoch in range(10):
for batch, (x, y) in enumerate(train_dataset):
loss = train_step(x, y)
if batch % 100 == 0:
print(f"Epoch {epoch}, Batch {batch}, Loss {loss:.4f}")TensorFlow Data Pipeline
# Efficient data loading with tf.data
dataset = tf.data.Dataset.from_tensor_slices((X, y))
dataset = dataset.shuffle(buffer_size=10000)
dataset = dataset.batch(32)
dataset = dataset.prefetch(tf.data.AUTOTUNE)
dataset = dataset.cache() # Cache after first epochModel Export for Production
# Save in SavedModel format
model.save('saved_model/my_model/')
# Convert to TensorFlow Lite
converter = tf.lite.TFLiteConverter.from_saved_model('saved_model/my_model/')
tflite_model = converter.convert()
with open('model.tflite', 'wb') as f:
f.write(tflite_model)TensorFlow Advanced Topics
Custom Layers
import tensorflow as tf
class MyDenseLayer(tf.keras.layers.Layer):
def __init__(self, units, activation='relu', **kwargs):
super().__init__(**kwargs)
self.units = units
self.activation = tf.keras.activations.get(activation)
def build(self, input_shape):
self.w = self.add_weight(
shape=(input_shape[-1], self.units),
initializer='glorot_uniform',
trainable=True
)
self.b = self.add_weight(
shape=(self.units,),
initializer='zeros',
trainable=True
)
def call(self, inputs):
return self.activation(tf.matmul(inputs, self.w) + self.b)Mixed Precision Training
# Use float16 where possible for 2x speedup on modern GPUs
from tensorflow.keras import mixed_precision
policy = mixed_precision.Policy('mixed_float16')
mixed_precision.set_global_policy(policy)
# Loss scaling handled automatically with optimizer
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)Distribution Strategies
# Multi-GPU training with MirroredStrategy
strategy = tf.distribute.MirroredStrategy()
with strategy.scope():
model = create_model()
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy')
model.fit(train_dataset, epochs=10)
# TPU training with TPUStrategy
resolver = tf.distribute.cluster_resolver.TPUClusterResolver()
tf.config.experimental_connect_to_cluster(resolver)
tf.tpu.experimental.initialize_tpu_system(resolver)
strategy = tf.distribute.TPUStrategy(resolver)For a comprehensive overview, read our article on Deep Learning Guide.
For a comprehensive overview, read our article on Ensemble Methods Guide.