TensorFlow Best Practices
This chapter summarizes best practices in TensorFlow development, covering code organization, performance optimization, debugging techniques, project management, and more, helping developers build high-quality machine learning projects.
Project Structure and Code Organization
Recommended Project Structure
ml_project/
├── README.md
├── requirements.txt
├── setup.py
├── .gitignore
├── .env
├── config/
│ ├── __init__.py
│ ├── config.py
│ └── logging.conf
├── data/
│ ├── raw/
│ ├── processed/
│ └── external/
├── models/
│ ├── saved_models/
│ ├── checkpoints/
│ └── exports/
├── notebooks/
│ ├── exploratory/
│ └── experiments/
├── src/
│ ├── __init__.py
│ ├── data/
│ │ ├── __init__.py
│ │ ├── preprocessing.py
│ │ └── data_loader.py
│ ├── models/
│ │ ├── __init__.py
│ │ ├── base_model.py
│ │ └── custom_models.py
│ ├── training/
│ │ ├── __init__.py
│ │ ├── trainer.py
│ │ └── callbacks.py
│ ├── evaluation/
│ │ ├── __init__.py
│ │ └── metrics.py
│ └── utils/
│ ├── __init__.py
│ ├── helpers.py
│ └── visualization.py
├── tests/
│ ├── __init__.py
│ ├── test_data/
│ ├── test_models/
│ └── test_utils/
├── scripts/
│ ├── train.py
│ ├── evaluate.py
│ └── deploy.py
└── docs/
├── api/
└── tutorials/Configuration Management
python
import os
import yaml
from dataclasses import dataclass
from typing import Dict, Any, Optional
@dataclass
class ModelConfig:
"""Model configuration class"""
name: str
architecture: str
input_shape: tuple
num_classes: int
learning_rate: float = 0.001
batch_size: int = 32
epochs: int = 100
dropout_rate: float = 0.2
@dataclass
class DataConfig:
"""Data configuration class"""
data_path: str
validation_split: float = 0.2
test_split: float = 0.1
shuffle: bool = True
seed: int = 42
@dataclass
class TrainingConfig:
"""Training configuration class"""
model: ModelConfig
data: DataConfig
output_dir: str
log_dir: str
save_checkpoints: bool = True
early_stopping_patience: int = 10
reduce_lr_patience: int = 5
class ConfigManager:
"""Configuration manager"""
def __init__(self, config_path: str):
self.config_path = config_path
self._config = None
def load_config(self) -> TrainingConfig:
"""Load configuration file"""
with open(self.config_path, 'r') as f:
config_dict = yaml.safe_load(f)
# Parse configuration
model_config = ModelConfig(**config_dict['model'])
data_config = DataConfig(**config_dict['data'])
training_config = TrainingConfig(
model=model_config,
data=data_config,
**config_dict['training']
)
self._config = training_config
return training_config
def save_config(self, config: TrainingConfig, path: str):
"""Save configuration file"""
config_dict = {
'model': config.model.__dict__,
'data': config.data.__dict__,
'training': {
'output_dir': config.output_dir,
'log_dir': config.log_dir,
'save_checkpoints': config.save_checkpoints,
'early_stopping_patience': config.early_stopping_patience,
'reduce_lr_patience': config.reduce_lr_patience
}
}
with open(path, 'w') as f:
yaml.dump(config_dict, f, default_flow_style=False)
# Example configuration file (config.yaml)
def create_sample_config():
"""Create sample configuration file"""
config_content = """
model:
name: "mnist_classifier"
architecture: "cnn"
input_shape: [28, 28, 1]
num_classes: 10
learning_rate: 0.001
batch_size: 32
epochs: 100
dropout_rate: 0.2
data:
data_path: "./data/mnist"
validation_split: 0.2
test_split: 0.1
shuffle: true
seed: 42
training:
output_dir: "./models/mnist_classifier"
log_dir: "./logs/mnist_classifier"
save_checkpoints: true
early_stopping_patience: 10
reduce_lr_patience: 5
"""
with open('config.yaml', 'w') as f:
f.write(config_content)
print("Sample configuration file created: config.yaml")
create_sample_config()Data Processing Best Practices
Efficient Data Pipelines
python
import tensorflow as tf
from typing import Tuple, Callable, Optional
import functools
class DataPipeline:
"""Efficient data pipeline class"""
def __init__(self, batch_size: int = 32, prefetch_size: int = tf.data.AUTOTUNE):
self.batch_size = batch_size
self.prefetch_size = prefetch_size
def create_dataset_from_generator(self,
generator_func: Callable,
output_signature: Tuple,
shuffle_buffer_size: int = 1000) -> tf.data.Dataset:
"""Create dataset from generator"""
dataset = tf.data.Dataset.from_generator(
generator_func,
output_signature=output_signature
)
return self._optimize_dataset(dataset, shuffle_buffer_size)
def create_dataset_from_files(self,
file_pattern: str,
parse_func: Callable,
shuffle_buffer_size: int = 1000) -> tf.data.Dataset:
"""Create dataset from files"""
files = tf.data.Dataset.list_files(file_pattern, shuffle=True)
dataset = files.interleave(
lambda x: tf.data.TFRecordDataset(x),
cycle_length=tf.data.AUTOTUNE,
num_parallel_calls=tf.data.AUTOTUNE
)
dataset = dataset.map(parse_func, num_parallel_calls=tf.data.AUTOTUNE)
return self._optimize_dataset(dataset, shuffle_buffer_size)
def _optimize_dataset(self,
dataset: tf.data.Dataset,
shuffle_buffer_size: int) -> tf.data.Dataset:
"""Optimize dataset performance"""
# Cache dataset (if dataset is small)
dataset = dataset.cache()
# Shuffle data
dataset = dataset.shuffle(shuffle_buffer_size)
# Batch
dataset = dataset.batch(self.batch_size)
# Prefetch data
dataset = dataset.prefetch(self.prefetch_size)
return dataset
def create_augmentation_layer():
"""Create data augmentation layer"""
return tf.keras.Sequential([
tf.keras.layers.RandomFlip("horizontal"),
tf.keras.layers.RandomRotation(0.1),
tf.keras.layers.RandomZoom(0.1),
tf.keras.layers.RandomContrast(0.1),
tf.keras.layers.RandomBrightness(0.1),
])
@tf.function
def preprocess_image(image, label, img_size=(224, 224)):
"""Preprocess image"""
# Resize
image = tf.image.resize(image, img_size)
# Normalize
image = tf.cast(image, tf.float32) / 255.0
# Ensure shape
image = tf.ensure_shape(image, (*img_size, 3))
return image, label
def create_mixed_precision_policy():
"""Create mixed precision policy"""
policy = tf.keras.mixed_precision.Policy('mixed_float16')
tf.keras.mixed_precision.set_global_policy(policy)
print(f"Mixed precision policy set: {policy.name}")
return policy
# Example: Create efficient data pipeline
def example_data_pipeline():
"""Example data pipeline"""
# Create data pipeline
pipeline = DataPipeline(batch_size=32)
# Example generator function
def data_generator():
for i in range(1000):
image = tf.random.normal((224, 224, 3))
label = tf.random.uniform((), maxval=10, dtype=tf.int32)
yield image, label
# Output signature
output_signature = (
tf.TensorSpec(shape=(224, 224, 3), dtype=tf.float32),
tf.TensorSpec(shape=(), dtype=tf.int32)
)
# Create dataset
dataset = pipeline.create_dataset_from_generator(
data_generator, output_signature
)
# Add preprocessing
dataset = dataset.map(preprocess_image, num_parallel_calls=tf.data.AUTOTUNE)
return dataset
# Create sample dataset
example_dataset = example_data_pipeline()
print(f"Dataset element specification: {example_dataset.element_spec}")Model Design Best Practices
Modular Model Design
python
import tensorflow as tf
from tensorflow import keras
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional
class BaseModel(ABC):
"""Base model abstract class"""
def __init__(self, config: Dict[str, Any]):
self.config = config
self.model = None
self._compiled = False
@abstractmethod
def build_model(self) -> keras.Model:
"""Build model"""
pass
def compile_model(self,
optimizer: str = 'adam',
loss: str = 'sparse_categorical_crossentropy',
metrics: list = None):
"""Compile model"""
if metrics is None:
metrics = ['accuracy']
if self.model is None:
self.model = self.build_model()
self.model.compile(
optimizer=optimizer,
loss=loss,
metrics=metrics
)
self._compiled = True
def summary(self):
"""Display model summary"""
if self.model is None:
self.model = self.build_model()
return self.model.summary()
def save_model(self, filepath: str):
"""Save model"""
if self.model is None:
raise ValueError("Model not yet built")
self.model.save(filepath)
def load_model(self, filepath: str):
"""Load model"""
self.model = keras.models.load_model(filepath)
self._compiled = True
class CNNClassifier(BaseModel):
"""CNN classifier"""
def build_model(self) -> keras.Model:
"""Build CNN model"""
inputs = keras.layers.Input(shape=self.config['input_shape'])
# Data augmentation (only during training)
if self.config.get('use_augmentation', False):
x = create_augmentation_layer()(inputs)
else:
x = inputs
# Convolutional blocks
for i, filters in enumerate(self.config['conv_filters']):
x = self._conv_block(x, filters, f'conv_block_{i}')
# Global pooling
x = keras.layers.GlobalAveragePooling2D()(x)
# Classification head
x = keras.layers.Dense(
self.config['dense_units'],
activation='relu',
name='dense_features'
)(x)
x = keras.layers.Dropout(self.config['dropout_rate'])(x)
outputs = keras.layers.Dense(
self.config['num_classes'],
activation='softmax',
name='predictions'
)(x)
model = keras.Model(inputs=inputs, outputs=outputs, name='cnn_classifier')
return model
def _conv_block(self, x, filters: int, name: str):
"""Convolutional block"""
x = keras.layers.Conv2D(
filters, 3, padding='same',
activation='relu', name=f'{name}_conv1'
)(x)
x = keras.layers.BatchNormalization(name=f'{name}_bn1')(x)
x = keras.layers.Conv2D(
filters, 3, padding='same',
activation='relu', name=f'{name}_conv2'
)(x)
x = keras.layers.BatchNormalization(name=f'{name}_bn2')(x)
x = keras.layers.MaxPooling2D(2, name=f'{name}_pool')(x)
x = keras.layers.Dropout(0.25, name=f'{name}_dropout')(x)
return x
class ResNetClassifier(BaseModel):
"""ResNet classifier"""
def build_model(self) -> keras.Model:
"""Build ResNet model"""
inputs = keras.layers.Input(shape=self.config['input_shape'])
# Initial convolution
x = keras.layers.Conv2D(64, 7, strides=2, padding='same')(inputs)
x = keras.layers.BatchNormalization()(x)
x = keras.layers.ReLU()(x)
x = keras.layers.MaxPooling2D(3, strides=2, padding='same')(x)
# Residual blocks
filters = [64, 128, 256, 512]
for i, f in enumerate(filters):
strides = 1 if i == 0 else 2
x = self._residual_block(x, f, strides, f'stage_{i}')
# Add more residual blocks
for j in range(self.config.get('blocks_per_stage', 2) - 1):
x = self._residual_block(x, f, 1, f'stage_{i}_block_{j+1}')
# Classification head
x = keras.layers.GlobalAveragePooling2D()(x)
x = keras.layers.Dense(self.config['num_classes'], activation='softmax')(x)
model = keras.Model(inputs=inputs, outputs=x, name='resnet_classifier')
return model
def _residual_block(self, x, filters: int, strides: int, name: str):
"""Residual block"""
shortcut = x
# Main path
x = keras.layers.Conv2D(filters, 3, strides=strides, padding='same')(x)
x = keras.layers.BatchNormalization()(x)
x = keras.layers.ReLU()(x)
x = keras.layers.Conv2D(filters, 3, padding='same')(x)
x = keras.layers.BatchNormalization()(x)
# Skip connection
if strides != 1 or shortcut.shape[-1] != filters:
shortcut = keras.layers.Conv2D(filters, 1, strides=strides)(shortcut)
shortcut = keras.layers.BatchNormalization()(shortcut)
x = keras.layers.Add()([shortcut, x])
x = keras.layers.ReLU()(x)
return x
# Model factory
class ModelFactory:
"""Model factory class"""
_models = {
'cnn': CNNClassifier,
'resnet': ResNetClassifier,
}
@classmethod
def create_model(cls, model_type: str, config: Dict[str, Any]) -> BaseModel:
"""Create model"""
if model_type not in cls._models:
raise ValueError(f"Unsupported model type: {model_type}")
return cls._models[model_type](config)
@classmethod
def register_model(cls, name: str, model_class: type):
"""Register new model type"""
cls._models[name] = model_class
# Example usage
def example_model_creation():
"""Example model creation"""
# CNN configuration
cnn_config = {
'input_shape': (224, 224, 3),
'num_classes': 10,
'conv_filters': [32, 64, 128],
'dense_units': 512,
'dropout_rate': 0.5,
'use_augmentation': True
}
# Create CNN model
cnn_model = ModelFactory.create_model('cnn', cnn_config)
cnn_model.compile_model()
cnn_model.summary()
# ResNet configuration
resnet_config = {
'input_shape': (224, 224, 3),
'num_classes': 10,
'blocks_per_stage': 2
}
# Create ResNet model
resnet_model = ModelFactory.create_model('resnet', resnet_config)
resnet_model.compile_model()
return cnn_model, resnet_model
# Create example models
# cnn_model, resnet_model = example_model_creation()Summary
This chapter introduces TensorFlow development best practices:
Key Points:
- Project Organization: Clear directory structure and configuration management
- Code Quality: Modular design and reusable components
- Data Processing: Efficient data pipelines and preprocessing
- Model Design: Flexible model architectures and factory patterns
- Training Management: Complete training workflows and monitoring
- Debugging Tools: Comprehensive debugging and performance analysis
- Experiment Management: Systematic experiment tracking and version control
Best Practices Summary:
- Establish standardized project structure
- Use configuration files to manage hyperparameters
- Implement modular and reusable code
- Optimize data pipeline performance
- Establish comprehensive monitoring and logging systems
- Conduct systematic experiment management
- Emphasize code quality and documentation
- Continuous learning and improvement