TensorFlow and Deep Learning
This skill covers project structure, model development, training, evaluation, and deployment practices for neural networks built with TensorFlow and Keras.
Workflow for Training and Shipping a Model
- Separate concerns into modules — Keep data loading, model definition, training, evaluation, and serving code in distinct files; treat notebooks as exploratory only.
- Build the input pipeline first — Use
tf.data.Dataset for scalable, prefetching input processing; validate shapes, dtypes, label ranges, and class balance before writing any model code.
- Split before any fitting — Create train/validation/test splits before fitting normalization statistics or augmentation parameters, to avoid leakage.
- Start with a tiny baseline — Build a small model and run a tiny overfit test (fit on a handful of examples until loss goes to ~0) to prove the training loop works before scaling up.
- Train with callbacks — Wire in
ModelCheckpoint (save best by validation metric, not final epoch), EarlyStopping, a learning-rate schedule, and TensorBoard logging.
- Evaluate with task-appropriate metrics — Use a held-out test set only for final reporting, never for tuning.
- Export with a explicit signature — Save with
model.export() or tf.saved_model.save() and define serving input signatures explicitly.
- Smoke test the export — Load the exported model and run inference on sample inputs before deploying.
Project Structure
- Separate data loading, model definition, training, evaluation, and serving code into distinct modules (e.g.,
data.py, model.py, train.py, evaluate.py, serve.py).
- Keep model hyperparameters in typed config objects (dataclasses) or config files, not scattered as literals through training code.
- Store checkpoints, logs, and exported models outside source directories (e.g., under
artifacts/ or a configured output path), so they don't get swept into version control accidentally.
- Keep notebooks exploratory; once an approach is validated, move the repeatable training code into modules that can be run as scripts and covered by tests.
project/
data.py # tf.data pipeline construction
model.py # Keras model definition
config.py # typed hyperparameter config
train.py # training loop / Keras fit orchestration
evaluate.py # metrics computation on held-out data
serve.py # export + inference smoke test
artifacts/ # checkpoints, logs, saved models (gitignored)
Input Pipeline (tf.data)
import tensorflow as tf
def build_dataset(file_pattern: str, batch_size: int, shuffle: bool) -> tf.data.Dataset:
files = tf.data.Dataset.list_files(file_pattern, shuffle=shuffle)
ds = files.interleave(
tf.data.TFRecordDataset,
cycle_length=tf.data.AUTOTUNE,
num_parallel_calls=tf.data.AUTOTUNE,
)
ds = ds.map(_parse_example, num_parallel_calls=tf.data.AUTOTUNE)
if shuffle:
ds = ds.shuffle(buffer_size=10_000)
ds = ds.batch(batch_size, drop_remainder=False)
return ds.prefetch(tf.data.AUTOTUNE)
def _parse_example(record: tf.Tensor) -> tuple[tf.Tensor, tf.Tensor]:
feature_spec = {
"features": tf.io.FixedLenFeature([32], tf.float32),
"label": tf.io.FixedLenFeature([], tf.int64),
}
parsed = tf.io.parse_single_example(record, feature_spec)
return parsed["features"], parsed["label"]
- Use
num_parallel_calls=tf.data.AUTOTUNE on .map()/.interleave() and always end the pipeline with .prefetch(tf.data.AUTOTUNE).
- Shuffle before batching, with a buffer large enough to approximate a full shuffle for the dataset size.
- Validate data shapes, dtypes, label ranges, and class balance on a sample batch before starting a long training run.
Model Development
- Use Keras layers and models (
tf.keras.Model, tf.keras.Sequential) unless a specific need requires lower-level tf.GradientTape control.
- Prefer explicit input shapes and named inputs/outputs — this makes serving signatures and debugging easier.
- Start with a small baseline model and confirm it can overfit a tiny subset of data (loss near zero) before scaling depth, width, or dataset size — this catches pipeline and loss-function bugs early.
- Pin random seeds (
tf.random.set_seed, numpy.random.seed) where reproducibility matters, but document that GPU execution can still be nondeterministic for some ops (e.g., certain cuDNN reductions).
- Only enable mixed precision (
tf.keras.mixed_precision.set_global_policy("mixed_float16")) after validating numerical stability at full precision — some losses (e.g., ones involving log or exp) need loss scaling to avoid underflow.
import tensorflow as tf
def build_model(input_dim: int, num_classes: int) -> tf.keras.Model:
inputs = tf.keras.Input(shape=(input_dim,), name="features")
x = tf.keras.layers.Dense(128, activation="relu")(inputs)
x = tf.keras.layers.Dropout(0.3)(x)
x = tf.keras.layers.Dense(64, activation="relu")(x)
outputs = tf.keras.layers.Dense(num_classes, activation="softmax", name="predictions")(x)
return tf.keras.Model(inputs=inputs, outputs=outputs, name="baseline_classifier")
Training
import tensorflow as tf
model = build_model(input_dim=32, num_classes=10)
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
callbacks = [
tf.keras.callbacks.ModelCheckpoint(
filepath="artifacts/checkpoints/best.keras",
monitor="val_accuracy",
save_best_only=True,
),
tf.keras.callbacks.EarlyStopping(
monitor="val_loss", patience=5, restore_best_weights=True,
),
tf.keras.callbacks.ReduceLROnPlateau(
monitor="val_loss", factor=0.5, patience=3, min_lr=1e-6,
),
tf.keras.callbacks.TensorBoard(log_dir="artifacts/logs"),
]
train_ds = build_dataset("data/train-*.tfrecord", batch_size=64, shuffle=True)
val_ds = build_dataset("data/val-*.tfrecord", batch_size=64, shuffle=False)
history = model.fit(
train_ds,
validation_data=val_ds,
epochs=50,
callbacks=callbacks,
)
- Split data into train/validation/test before fitting any normalization layer or augmentation parameters — fitting on the full dataset leaks test statistics into training.
- Use the validation set for tuning (architecture, learning rate, regularization); reserve a separate test set purely for final reporting, touched once.
- Track loss curves, metrics, learning rate, and resource utilization (
TensorBoard, or an experiment tracker) for every run.
- Save the checkpoint with the best validation metric (
save_best_only=True), not just the weights from the final epoch — the final epoch is not necessarily the best one, especially with EarlyStopping.
Evaluation
- Report task-appropriate metrics: AUROC/F1/calibration for classification, perplexity or BLEU/ROUGE for language tasks, MAE/RMSE for regression.
- Include confusion matrices or per-class error slices for classification tasks — aggregate accuracy hides class-level failures.
- Evaluate on edge cases and known distribution shifts when data allows (e.g., a held-out slice from a different time period or source).
- Compare against non-neural baselines (logistic regression, gradient-boosted trees) when the dataset is small or tabular — a deep model that underperforms a simple baseline is a signal, not a footnote.
test_ds = build_dataset("data/test-*.tfrecord", batch_size=64, shuffle=False)
results = model.evaluate(test_ds, return_dict=True)
print(results) # {'loss': ..., 'accuracy': ...}
Deployment
- Export models with an explicit input signature so serving infrastructure (TF Serving, Vertex AI, etc.) knows the expected input shape and dtype:
model.export("artifacts/saved_model/v1")
# Or with an explicit serving signature:
@tf.function(input_signature=[tf.TensorSpec(shape=[None, 32], dtype=tf.float32, name="features")])
def serve_fn(features):
return {"predictions": model(features, training=False)}
tf.saved_model.save(model, "artifacts/saved_model/v1", signatures={"serving_default": serve_fn})
- Keep preprocessing identical between training and serving — bake normalization/tokenization into the exported graph (e.g.,
tf.keras.layers.Normalization as part of the model) rather than duplicating logic in a separate serving-side script.
- Add a smoke test that loads the exported
SavedModel and runs inference on a handful of known sample inputs, asserting outputs are in the expected range/shape, before promoting a model to production.
- Monitor latency, memory, prediction drift, and input schema changes once deployed — a model that was correct at export time can silently degrade as upstream data shifts.
Common Mistakes
- Tuning architecture before verifying labels and data quality — a data bug will outlast any architecture change.
- Leaking validation or test data through preprocessing or augmentation fit on the full dataset instead of the training split alone.
- Relying on accuracy alone for imbalanced datasets — use precision/recall, F1, or AUROC instead.
- Deploying a notebook-only model with no reproducible training script or pinned dependencies.
- Ignoring batch size, dtype, or device (CPU/GPU) differences between training and inference, which can silently change numerical results (especially under mixed precision).
1---2name: tensorflow-deep-learning3description: Best practices for building, training, evaluating, and deploying neural networks with TensorFlow and Keras. Use when writing tf.data input pipelines, defining or training a Keras model, configuring callbacks for checkpointing and early stopping, evaluating a trained model, or exporting a model for serving.4---5
6# TensorFlow and Deep Learning
7
8This skill covers project structure, model development, training, evaluation, and deployment practices for neural networks built with TensorFlow and Keras.
9
10## Workflow for Training and Shipping a Model
11
121. **Separate concerns into modules** — Keep data loading, model definition, training, evaluation, and serving code in distinct files; treat notebooks as exploratory only.
132. **Build the input pipeline first** — Use `tf.data.Dataset` for scalable, prefetching input processing; validate shapes, dtypes, label ranges, and class balance before writing any model code.
143. **Split before any fitting** — Create train/validation/test splits before fitting normalization statistics or augmentation parameters, to avoid leakage.
154. **Start with a tiny baseline** — Build a small model and run a tiny overfit test (fit on a handful of examples until loss goes to ~0) to prove the training loop works before scaling up.
165. **Train with callbacks** — Wire in `ModelCheckpoint` (save best by validation metric, not final epoch), `EarlyStopping`, a learning-rate schedule, and `TensorBoard` logging.
176. **Evaluate with task-appropriate metrics** — Use a held-out test set only for final reporting, never for tuning.
187. **Export with a explicit signature** — Save with `model.export()` or `tf.saved_model.save()` and define serving input signatures explicitly.
198. **Smoke test the export** — Load the exported model and run inference on sample inputs before deploying.
20
21## Project Structure
22
23- Separate data loading, model definition, training, evaluation, and serving code into distinct modules (e.g., `data.py`, `model.py`, `train.py`, `evaluate.py`, `serve.py`).
24- Keep model hyperparameters in typed config objects (dataclasses) or config files, not scattered as literals through training code.
25- Store checkpoints, logs, and exported models outside source directories (e.g., under `artifacts/` or a configured output path), so they don't get swept into version control accidentally.
26- Keep notebooks exploratory; once an approach is validated, move the repeatable training code into modules that can be run as scripts and covered by tests.
27
28```
29project/
30 data.py # tf.data pipeline construction
31 model.py # Keras model definition
32 config.py # typed hyperparameter config
33 train.py # training loop / Keras fit orchestration
34 evaluate.py # metrics computation on held-out data
35 serve.py # export + inference smoke test
36 artifacts/ # checkpoints, logs, saved models (gitignored)
37```
38
39## Input Pipeline (tf.data)
40
41```python
42import tensorflow as tf
43
44def build_dataset(file_pattern: str, batch_size: int, shuffle: bool) -> tf.data.Dataset:
45 files = tf.data.Dataset.list_files(file_pattern, shuffle=shuffle)
46 ds = files.interleave(
47 tf.data.TFRecordDataset,
48 cycle_length=tf.data.AUTOTUNE,
49 num_parallel_calls=tf.data.AUTOTUNE,
50 )
51 ds = ds.map(_parse_example, num_parallel_calls=tf.data.AUTOTUNE)
52 if shuffle:
53 ds = ds.shuffle(buffer_size=10_000)
54 ds = ds.batch(batch_size, drop_remainder=False)
55 return ds.prefetch(tf.data.AUTOTUNE)
56
57def _parse_example(record: tf.Tensor) -> tuple[tf.Tensor, tf.Tensor]:
58 feature_spec = {
59 "features": tf.io.FixedLenFeature([32], tf.float32),
60 "label": tf.io.FixedLenFeature([], tf.int64),
61 }
62 parsed = tf.io.parse_single_example(record, feature_spec)
63 return parsed["features"], parsed["label"]
64```
65
66- Use `num_parallel_calls=tf.data.AUTOTUNE` on `.map()`/`.interleave()` and always end the pipeline with `.prefetch(tf.data.AUTOTUNE)`.
67- Shuffle before batching, with a buffer large enough to approximate a full shuffle for the dataset size.
68- Validate data shapes, dtypes, label ranges, and class balance on a sample batch before starting a long training run.
69
70## Model Development
71
72- Use Keras layers and models (`tf.keras.Model`, `tf.keras.Sequential`) unless a specific need requires lower-level `tf.GradientTape` control.
73- Prefer explicit input shapes and named inputs/outputs — this makes serving signatures and debugging easier.
74- Start with a small baseline model and confirm it can overfit a tiny subset of data (loss near zero) before scaling depth, width, or dataset size — this catches pipeline and loss-function bugs early.
75- Pin random seeds (`tf.random.set_seed`, `numpy.random.seed`) where reproducibility matters, but document that GPU execution can still be nondeterministic for some ops (e.g., certain cuDNN reductions).
76- Only enable mixed precision (`tf.keras.mixed_precision.set_global_policy("mixed_float16")`) after validating numerical stability at full precision — some losses (e.g., ones involving `log` or `exp`) need loss scaling to avoid underflow.
77
78```python
79import tensorflow as tf
80
81def build_model(input_dim: int, num_classes: int) -> tf.keras.Model:
82 inputs = tf.keras.Input(shape=(input_dim,), name="features")
83 x = tf.keras.layers.Dense(128, activation="relu")(inputs)
84 x = tf.keras.layers.Dropout(0.3)(x)
85 x = tf.keras.layers.Dense(64, activation="relu")(x)
86 outputs = tf.keras.layers.Dense(num_classes, activation="softmax", name="predictions")(x)
87 return tf.keras.Model(inputs=inputs, outputs=outputs, name="baseline_classifier")
88```
89
90## Training
91
92```python
93import tensorflow as tf
94
95model = build_model(input_dim=32, num_classes=10)
96model.compile(
97 optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3),
98 loss="sparse_categorical_crossentropy",
99 metrics=["accuracy"],
100)
101
102callbacks = [
103 tf.keras.callbacks.ModelCheckpoint(
104 filepath="artifacts/checkpoints/best.keras",
105 monitor="val_accuracy",
106 save_best_only=True,
107 ),
108 tf.keras.callbacks.EarlyStopping(
109 monitor="val_loss", patience=5, restore_best_weights=True,
110 ),
111 tf.keras.callbacks.ReduceLROnPlateau(
112 monitor="val_loss", factor=0.5, patience=3, min_lr=1e-6,
113 ),
114 tf.keras.callbacks.TensorBoard(log_dir="artifacts/logs"),
115]
116
117train_ds = build_dataset("data/train-*.tfrecord", batch_size=64, shuffle=True)
118val_ds = build_dataset("data/val-*.tfrecord", batch_size=64, shuffle=False)
119
120history = model.fit(
121 train_ds,
122 validation_data=val_ds,
123 epochs=50,
124 callbacks=callbacks,
125)
126```
127
128- Split data into train/validation/test before fitting any normalization layer or augmentation parameters — fitting on the full dataset leaks test statistics into training.
129- Use the validation set for tuning (architecture, learning rate, regularization); reserve a separate test set purely for final reporting, touched once.
130- Track loss curves, metrics, learning rate, and resource utilization (`TensorBoard`, or an experiment tracker) for every run.
131- Save the checkpoint with the best validation metric (`save_best_only=True`), not just the weights from the final epoch — the final epoch is not necessarily the best one, especially with `EarlyStopping`.
132
133## Evaluation
134
135- Report task-appropriate metrics: AUROC/F1/calibration for classification, perplexity or BLEU/ROUGE for language tasks, MAE/RMSE for regression.
136- Include confusion matrices or per-class error slices for classification tasks — aggregate accuracy hides class-level failures.
137- Evaluate on edge cases and known distribution shifts when data allows (e.g., a held-out slice from a different time period or source).
138- Compare against non-neural baselines (logistic regression, gradient-boosted trees) when the dataset is small or tabular — a deep model that underperforms a simple baseline is a signal, not a footnote.
139
140```python
141test_ds = build_dataset("data/test-*.tfrecord", batch_size=64, shuffle=False)
142results = model.evaluate(test_ds, return_dict=True)
143print(results) # {'loss': ..., 'accuracy': ...}
144```
145
146## Deployment
147
148- Export models with an explicit input signature so serving infrastructure (TF Serving, Vertex AI, etc.) knows the expected input shape and dtype:
149
150```python
151model.export("artifacts/saved_model/v1")
152
153# Or with an explicit serving signature:
154@tf.function(input_signature=[tf.TensorSpec(shape=[None, 32], dtype=tf.float32, name="features")])
155def serve_fn(features):
156 return {"predictions": model(features, training=False)}
157
158tf.saved_model.save(model, "artifacts/saved_model/v1", signatures={"serving_default": serve_fn})
159```
160
161- Keep preprocessing identical between training and serving — bake normalization/tokenization into the exported graph (e.g., `tf.keras.layers.Normalization` as part of the model) rather than duplicating logic in a separate serving-side script.
162- Add a smoke test that loads the exported `SavedModel` and runs inference on a handful of known sample inputs, asserting outputs are in the expected range/shape, before promoting a model to production.
163- Monitor latency, memory, prediction drift, and input schema changes once deployed — a model that was correct at export time can silently degrade as upstream data shifts.
164
165## Common Mistakes
166
167- Tuning architecture before verifying labels and data quality — a data bug will outlast any architecture change.
168- Leaking validation or test data through preprocessing or augmentation fit on the full dataset instead of the training split alone.
169- Relying on accuracy alone for imbalanced datasets — use precision/recall, F1, or AUROC instead.
170- Deploying a notebook-only model with no reproducible training script or pinned dependencies.
171- Ignoring batch size, dtype, or device (CPU/GPU) differences between training and inference, which can silently change numerical results (especially under mixed precision).