Vision Systems & Engineering

TensorFlow

Google's open-source machine learning framework, which runs computations as dataflow graphs, executes eagerly by default since version 2.0, and pairs Keras for model building with tf.data for input pipelines, SavedModel for export, TensorFlow Serving for servers and LiteRT for devices.

beginner

TensorFlow is an open-source machine learning framework developed at Google and released under the Apache License 2.0. It represents a computation as a dataflow graph of operations on tensors (multi-dimensional arrays), differentiates such graphs automatically, and runs them on CPUs, GPUs and Google’s Tensor Processing Units (TPUs). Around that core sit Keras for building and training models, tf.data for input pipelines, the SavedModel format for export, TensorFlow Serving for model servers and LiteRT (formerly TensorFlow Lite) for phones and embedded devices.

Every version statement and code example in this article was checked against TensorFlow 2.21.0 with Keras 3.15.1, the current stable releases as of October 2026. Training methods themselves, such as splitting data, choosing a learning rate and reading learning curves, are covered in Training a Neural Network; this article is about the framework.

Purpose

TensorFlow was built to take one program from research to production. Abadi et al. (2016), the system’s paper, describe it as the successor to DistBelief, the distributed training system Google had used since 2011 (Dean et al., 2012). DistBelief composed networks from predefined layers written in C++, and kept the parameters on separate parameter servers. The paper names three kinds of flexibility its advanced users lacked: defining new layers without writing C++, changing the optimization algorithm without modifying the parameter servers, and training models that do not fit the fixed forward-backward-update loop, such as recurrent networks, adversarial networks and reinforcement learning. It also notes that DistBelief was hard to scale down to a single workstation or a phone. TensorFlow’s answer was a single dataflow graph of primitive operators (matrix multiplication, convolution and the like) in which parameters and their update rules are themselves nodes, so the same program could be trained on a cluster, refined on a workstation and run on a mobile device.

Jeff Dean and Rajat Monga’s announcement of the open-source release on 9 November 2015 called TensorFlow Google’s second-generation machine learning system. It stated that DistBelief had trained the Inception model that won the 2014 ImageNet Large Scale Visual Recognition Challenge, an image classification benchmark.

Core Capabilities

Graphs, sessions and eager execution

TensorFlow 1.x used deferred execution. A program first built a symbolic graph, with placeholders for the input data and variables for the state, and then ran it in a tf.Session, feeding batches through session.run. Abadi et al. (2016) explain the choice: with the whole program available before it runs, the runtime can optimize globally, for example by issuing a sequence of GPU kernels without waiting for intermediate results. The paper names the cost: features such as dynamic control flow had to be pushed into the graph. Paszke et al. (2019) later added ease of use and of debugging to the price of static graphs. The paper’s conclusion already reports that some users had begun to find a static dataflow graph limiting, especially for deep reinforcement learning.

Eager execution, in which each operation runs immediately and returns a concrete value to Python, arrived as a preview in TensorFlow 1.5 (January 2018) and moved out of tf.contrib in 1.7 (March 2018). TensorFlow 2.0 (30 September 2019) made it the default. Its release notes summarize the change as “Functions, not Sessions”: the graph-and-session model is discouraged, and a Python function decorated with tf.function is turned into a graph when it is needed. The 1.x API stayed available under tf.compat.v1. The official migration guide lists the behavioral differences: TensorFlow 2 has no global graph collections (a variable that is no longer referenced is garbage-collected), uses resource variables and differentiable control flow, and compares tensors with == by value rather than by object identity.

tf.function, tracing and AutoGraph

tf.function wraps a Python function in a polymorphic function that holds one or more graphs. On the first call with a new input signature (new dtypes, or shapes that no existing graph accepts), it traces the Python code: it runs the function once with symbolic tensors and records the TensorFlow operations into a tf.Graph. Later calls with a matching signature execute the stored graph without running the Python code again. AutoGraph rewrites Python control flow that depends on tensors during tracing: if becomes tf.cond, and for and while loops become tf.while_loop. A graph can be optimized by Grappler, TensorFlow’s graph optimizer (constant folding, common-subexpression elimination, splitting independent work across threads or devices), compiled with XLA through tf.function(jit_compile=True), and saved and run without Python.

Tracing has consequences that the official guide states plainly. Python side effects, such as print, appending to a list or changing a global, run only while tracing, not on every call, whereas tf.print and tf.Variable.assign run every time. Each retrace costs time, so a function called with many different shapes or Python values can run slower than eager code. A fixed input_signature or reduce_retracing=True limits retracing.

Gradients come from tf.GradientTape, which records the operations run inside its context and then applies reverse-mode automatic differentiation (backpropagation) to them.

Keras, the high-level API

Keras is the high-level API of TensorFlow, and the TensorFlow guide advises every TensorFlow user to use the Keras APIs by default. Keras provides Sequential and functional models, subclassed models, layers, losses, optimizers, metrics, and the fit, evaluate and predict loops. tf.keras became part of the core TensorFlow API in version 1.4 (November 2017), and TensorFlow 2.0 made Keras the central API for building and training models.

Keras 3, released on 28 November 2023, is in the Keras team’s words a full rewrite with several backends. As its announcement states it, the same Keras code runs on JAX, TensorFlow or PyTorch, with an inference-only OpenVINO backend added in Keras 3.8. The backend is chosen with the KERAS_BACKEND environment variable or the ~/.keras/keras.json file. Components written with the keras.ops namespace, which implements the NumPy API plus neural-network functions, run on every backend. A model saved in the .keras format (a zip archive holding a JSON configuration and the weights) can be reloaded under any backend, and fit accepts tf.data datasets, PyTorch DataLoader objects and NumPy arrays whichever backend runs. Since TensorFlow 2.16 (March 2024), tf.keras resolves to Keras 3. Projects that need Keras 2 install the tf_keras package (Keras 2.15 under another name, in maintenance mode) and set TF_USE_LEGACY_KERAS=1. In TensorFlow 2.21.0 with Keras 3.15.1, tf.keras.Sequential and keras.Sequential are the same class.

tf.data input pipelines

tf.data, part of the core API since version 1.4, builds input pipelines from a tf.data.Dataset, a sequence of elements that can be nested tuples or dictionaries of tensors. A pipeline starts from a source (in-memory arrays, TFRecord or text files, a Python generator) and chains transformations such as map, shuffle, batch and prefetch. Because the transformations run as TensorFlow graphs, they can run in parallel threads outside the Python interpreter. map(..., num_parallel_calls=tf.data.AUTOTUNE) decodes and augments several elements at once, and prefetch uses a background thread and buffer to prepare the next batch while the model trains on the current one. The performance guide notes that prefetching makes a step take the maximum of the input time and the training time, rather than their sum. For vision this is where image decoding, resizing and data augmentation usually run.

SavedModel and TensorFlow Serving

A SavedModel is a directory that holds a complete TensorFlow program: the traced graphs and their named signatures in saved_model.pb, the variable values as a training checkpoint in variables/, files such as vocabularies in assets/, and a fingerprint.pb of hashes that identify the contents. Because the graphs are stored, it can be loaded without the Python code that built the model, by TensorFlow Serving, TensorFlow.js or the LiteRT converter. TensorFlow 2.0 made SavedModel the common exchange format of the ecosystem. Only the traced functions are saved: calling a reloaded function with an input signature it was never traced for fails. The SavedModel guide warns that models are code, so an untrusted SavedModel should be handled as untrusted code. For Keras models, the guide recommends the .keras format for saving and Model.export for producing an inference-only SavedModel. The low-level tf.saved_model API is still supported for existing code.

TensorFlow Serving is a separate model server for SavedModels. Its architecture document describes models as versioned servables. Sources discover new versions in storage, and loaders load and unload them, so a server can roll out a new version, or keep several versions loaded at once, without restarting. An optional batcher groups requests to use accelerators efficiently. Clients use a gRPC API or a REST API (for example POST /v1/models/<name>:predict), and each version of a model is exported to its own numbered sub-directory, as in shapes/1/ in the example below. As of October 2026 the latest TensorFlow Serving release is 2.21.0, of 29 September 2026.

TensorFlow Lite and LiteRT

TensorFlow Lite runs models on mobile and embedded devices. It appeared as a developer preview in TensorFlow 1.5, and Google dates its debut to 2017. A converter turns a SavedModel, a Keras model or concrete functions into a .tflite file, a FlatBuffer that the on-device runtime executes on a CPU, GPU or NPU. Post-training quantization can be applied during conversion, and TensorFlow ops that have no LiteRT implementation can be kept through the TF Select fallback. On 4 September 2024 Google renamed TensorFlow Lite to LiteRT, moved its documentation to ai.google.dev/edge/litert, and said that the .tflite format would not change and that LiteRT also accepts models from PyTorch and JAX. Its development is moving to a separate LiteRT repository, which the TensorFlow 2.18 release notes announced. TensorFlow 2.19 deprecated tf.lite.Interpreter in favor of the ai_edge_litert package, and the 2.20 release notes announced that tf.lite itself will be deprecated. In TensorFlow 2.21.0, tf.lite.TFLiteConverter still works, and constructing tf.lite.Interpreter prints a warning that it is deprecated and “scheduled for deletion in TF 2.20”. This is a transition in progress, so the LiteRT documentation is the reference for on-device deployment.

Distribution and determinism

tf.distribute.Strategy, the TensorFlow 1.13 name for the earlier tf.contrib.distribute.DistributionStrategy and a headline feature of TensorFlow 2.0, spreads training across devices with few code changes, both for Model.fit and for custom training loops. At the 2.0 release it supported several GPUs, with experimental support for several machines and Cloud TPUs. For reproducibility, tf.config.experimental.enable_op_determinism() makes ops produce identical outputs when run repeatedly with the same inputs on the same hardware. The documentation says that this usually costs speed, especially in tf.data pipelines whose map functions contain random ops, and that some ops raise an error because they have no deterministic implementation. It is used together with keras.utils.set_random_seed, which seeds Python, NumPy and TensorFlow.

Minimal Example

The first script shows eager execution, tracing and the gradient tape on two synthetic 32×32 images and one 64×64 image, drawn from TensorFlow’s stateless random generator so that they are the same on every run. The function standardizes an image to zero mean and unit standard deviation. Its Python append is a side effect, so it records each trace.

import os
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
import tensorflow as tf

traces = []  # a Python side effect: runs only while tf.function traces

@tf.function
def normalize(img):
    traces.append(tuple(img.shape))
    mean = tf.reduce_mean(img)
    std = tf.math.reduce_std(img)
    return (img - mean) / (std + 1e-6)

a = tf.random.stateless_uniform((32, 32), seed=(1, 2))
b = tf.random.stateless_uniform((32, 32), seed=(3, 4))
c = tf.random.stateless_uniform((64, 64), seed=(5, 6))
for x in (a, b, c, a):
    y = normalize(x)
print("calls: 4, traces:", len(traces), traces)
print("eager by default:", tf.executing_eagerly())
g = normalize.get_concrete_function(a).graph
print("ops in the 32x32 graph:", len(g.get_operations()))
print("op types:", sorted({op.type for op in g.get_operations()}))
print("output mean %.4f, std %.4f" % (float(tf.reduce_mean(y)),
                                     float(tf.math.reduce_std(y))))

# Automatic differentiation: fit a brightness gain w so that w * a matches b.
w = tf.Variable(2.0)
with tf.GradientTape() as tape:
    loss = tf.reduce_mean((w * a - b) ** 2)
grad = tape.gradient(loss, w)
by_hand = tf.reduce_mean(2 * (w * a - b) * a)
print("dloss/dw: tape %.6f, by hand %.6f" % (float(grad), float(by_hand)))
calls: 4, traces: 2 [(32, 32), (64, 64)]
eager by default: True
ops in the 32x32 graph: 15
op types: ['AddV2', 'Const', 'Identity', 'Mean', 'Placeholder', 'RealDiv', 'Sqrt', 'Square', 'Sub']
output mean 0.0000, std 1.0000
dloss/dw: tape 0.854100, by hand 0.854100

Four calls produced two traces. The second 32×32 image reused the first graph, and only the 64×64 image, a new shape, forced a retrace. The Python list therefore says how often the function was traced, not how often it ran, which is the side-effect behaviour the guide warns about. The traced graph holds 15 operations. Its input is a Placeholder, the same op type that TensorFlow 1.x programs created by hand with tf.placeholder; here tf.function creates it. The standard deviation appears as Square, Mean and Sqrt, because reduce_std is itself built from primitive ops. The tape’s derivative of the mean squared error with respect to the gain matches the hand-derived 2N∑i(wai−bi) ai\frac{2}{N}\sum_i (w a_i - b_i)\, a_i to all six printed digits.

The second script trains a small convolutional network from scratch to tell rendered discs, squares and triangles apart. The 1,200 images are 8-bit, 32×32 pixels, with random position, size and pixel noise; 900 are for training and 300 for testing. tf.data converts them to floats in a parallel map, reshuffles them every epoch with a fixed seed, batches and prefetches them. Keras builds, trains and evaluates the model with Adam and a cross-entropy loss on the logits. Five seeds are trained to show the spread. The script then exports the seed-0 model as a SavedModel, reloads it without the model code, and converts it to a .tflite file. It uses TensorFlow 2.21.0, Keras 3.15.1 and NumPy 2.5.4 on a CPU, with op determinism on and one thread for intra-op and inter-op work.

import os, sys
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
import numpy as np
import tensorflow as tf
import keras

tf.config.experimental.enable_op_determinism()
tf.config.threading.set_intra_op_parallelism_threads(1)
tf.config.threading.set_inter_op_parallelism_threads(1)

def render(kind, rng, size=32):
    """An 8-bit image of a disc, square or triangle at a random place and scale."""
    yy, xx = np.mgrid[0:size, 0:size] + 0.5
    r = rng.uniform(5, 10)
    cx, cy = rng.uniform(r + 1, size - r - 1, 2)
    if kind == 0:
        mask = (xx - cx) ** 2 + (yy - cy) ** 2 <= r ** 2
    elif kind == 1:
        mask = (abs(xx - cx) <= 0.8 * r) & (abs(yy - cy) <= 0.8 * r)
    else:  # upward triangle with apex at (cx, cy - r)
        t = (yy - (cy - r)) / (1.8 * r)
        mask = (t >= 0) & (t <= 1) & (abs(xx - cx) <= t * r)
    img = 0.2 + 0.6 * mask + rng.normal(0, 0.1, (size, size))
    return np.round(255 * np.clip(img, 0, 1)).astype(np.uint8)

rng = np.random.default_rng(0)
labels = rng.integers(0, 3, 1200)
images = np.stack([render(k, rng) for k in labels])[..., None]   # uint8, (1200, 32, 32, 1)
x_train, y_train = images[:900], labels[:900]
x_test, y_test = images[900:], labels[900:]

def to_float(img, label):                   # runs inside the tf.data graph
    return tf.cast(img, tf.float32) / 255.0, label

def make_train_ds(seed):
    return (tf.data.Dataset.from_tensor_slices((x_train, y_train))
            .shuffle(900, seed=seed)       # reshuffled every epoch
            .map(to_float, num_parallel_calls=tf.data.AUTOTUNE)
            .batch(32)
            .prefetch(tf.data.AUTOTUNE))   # overlap input and training

test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test)).map(to_float).batch(100)
print("element_spec:", make_train_ds(0).element_spec)

def make_model():
    return keras.Sequential([
        keras.Input((32, 32, 1)),
        keras.layers.Conv2D(8, 3, activation="relu"),
        keras.layers.MaxPooling2D(),
        keras.layers.Conv2D(16, 3, activation="relu"),
        keras.layers.MaxPooling2D(),
        keras.layers.Flatten(),
        keras.layers.Dense(3),
    ])

def train(seed, verbose=False):
    keras.utils.set_random_seed(seed)       # initial weights
    model = make_model()
    model.compile(optimizer=keras.optimizers.Adam(3e-3),
                  loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                  metrics=["accuracy"])
    hist = model.fit(make_train_ds(seed), epochs=15, verbose=0)
    if verbose:
        print("parameters:", model.count_params())
        for e in range(0, 15, 2):
            print(f"epoch {e + 1:2d}: loss {hist.history['loss'][e]:.4f}"
                  f"  train acc {hist.history['accuracy'][e]:.3f}")
    return model, model.evaluate(test_ds, verbose=0)

model, (loss, acc) = train(0, verbose=True)
print(f"seed 0 test loss {loss:.4f}  test acc {acc:.3f}  ({len(y_test)} images)")
accs = [train(s)[1][1] for s in range(1, 5)]
print("seeds 1-4 test acc:", " ".join(f"{a:.3f}" for a in accs))

# Export a SavedModel for serving, then reload it without the Python model code.
out = os.path.join(sys.argv[1] if len(sys.argv) > 1 else "export", "shapes", "1")
model.export(out, format="tf_saved_model", verbose=False)
print("SavedModel files:", sorted(os.listdir(out)))
reloaded = tf.saved_model.load(out)
sig = reloaded.signatures["serving_default"]
print("serving_default input:", sig.structured_input_signature[1])
batch = x_test[:100].astype(np.float32) / 255.0
diff = np.abs(model.predict(batch, verbose=0) - reloaded.serve(batch).numpy()).max()
print(f"max |keras - SavedModel| logit difference: {diff:.2e}")

# Convert the SavedModel to a .tflite flatbuffer (LiteRT runs this format).
tflite = tf.lite.TFLiteConverter.from_saved_model(out).convert()
print(f".tflite size: {len(tflite)} bytes for {model.count_params()} float32 parameters")
element_spec: (TensorSpec(shape=(None, 32, 32, 1), dtype=tf.float32, name=None), TensorSpec(shape=(None,), dtype=tf.int64, name=None))
parameters: 2979
epoch  1: loss 1.0932  train acc 0.369
epoch  3: loss 0.9668  train acc 0.546
epoch  5: loss 0.6804  train acc 0.726
epoch  7: loss 0.4371  train acc 0.841
epoch  9: loss 0.2979  train acc 0.908
epoch 11: loss 0.2025  train acc 0.949
epoch 13: loss 0.1447  train acc 0.960
epoch 15: loss 0.1212  train acc 0.971
seed 0 test loss 0.1662  test acc 0.957  (300 images)
seeds 1-4 test acc: 0.983 0.980 0.980 0.980
SavedModel files: ['assets', 'fingerprint.pb', 'saved_model.pb', 'variables']
serving_default input: {'keras_tensor': TensorSpec(shape=(None, 32, 32, 1), dtype=tf.float32, name='keras_tensor')}
max |keras - SavedModel| logit difference: 0.00e+00
.tflite size: 15676 bytes for 2979 float32 parameters

The element_spec line shows what the pipeline produces before any data flows. The images are already float32, because the uint8 arrays were converted inside map, and the batch dimension is None because the last batch is partial: 900 images make 28 batches of 32 and one of 4. The labels stay int64, NumPy’s default integer type, which the sparse cross-entropy loss accepts directly.

The network has 2,979 parameters: 80 in the first convolution, 1,168 in the second and 1,731 in the dense layer that reads the 6×6×166 \times 6 \times 16 feature map. Training loss falls steadily. On the 300 held-out images the seed-0 model reaches an accuracy of 0.957, close to the 0.971 it averaged over its last training epoch, which suggests that it has learned the shapes rather than memorized the training images. Over the five seeds, which change the initial weights and the shuffling order, test accuracy ranges from 0.957 to 0.983. Seed 0 gives the lowest value of the five.

The exported directory has the layout the SavedModel guide describes, and its default serving signature accepts batches of any size of 32×32×1 float images. The reloaded SavedModel reproduces the Keras model’s logits exactly on this machine. Our reading is that both run the same traced graph with the same CPU kernels; across different hardware or after conversion, only closeness, not identity, should be expected. The .tflite file is 15,676 bytes: the 2,979 float32 parameters take 11,916 bytes, and the rest is the serialized graph and its metadata. By arithmetic, storing the weights in 8 bits instead of 32 would cut the weight part to about a quarter.

Run twice, each script printed identical output. With op determinism and single-threaded execution, TensorFlow guarantees repeatable results on the same hardware, not across machines; these outputs came from an Apple M5 CPU under macOS, and another processor or a GPU may give different digits.

Ecosystem and Integrations

As of October 2026 the main projects around TensorFlow are these, each documented on its own site:

  • Keras for models and training, and KerasHub and KerasCV for pretrained models (YOLOv8 and Segment Anything among them, as the Keras 3 announcement lists). Keras 3 also runs on JAX and PyTorch, as described above.
  • TensorFlow Serving for servers, and LiteRT for Android, iOS, embedded devices and microcontrollers.
  • TensorFlow.js for training and inference in the browser and Node.js. TensorFlow 2.0’s announcement lists it among the runtimes that accept SavedModels.
  • TensorBoard for training curves and graph visualization. TensorFlow 2.21 removed it from TensorFlow’s dependencies, and the 2.22 release candidate’s notes say it must be installed separately for tf.summary and the Keras TensorBoard callback.
  • TensorFlow Datasets, a standard interface to public datasets presented alongside TensorFlow 2.0.
  • XLA, the compiler for TensorFlow graphs, first released as experimental in TensorFlow 1.0 (February 2017) and used through jit_compile=True.

For interchange with other frameworks, Keras 3.15.1’s Model.export writes TensorFlow SavedModel, ONNX, OpenVINO, LiteRT and PyTorch formats.

Alternatives

PyTorch and JAX are the other widely used deep learning frameworks. PyTorch’s authors describe it as an imperative, define-by-run library in which the model is an ordinary Python program (Paszke et al., 2019). Their introduction contrasts it with frameworks that build a static dataflow graph, naming TensorFlow among them. That contrast describes TensorFlow 1.x: TensorFlow 2.0, released in the same year as the paper, made eager execution the default and builds graphs only on request through tf.function.

Published speed comparisons depend on their set-up and date. Paszke et al. (2019) measured single-GPU training throughput on six models, on a workstation with two Intel Xeon E5-2698 v4 CPUs and one NVIDIA Quadro GP100 GPU. The appendix in the paper’s NeurIPS supplementary material (not in the proceedings PDF) gives the versions as TensorFlow 1.13.1 and PyTorch 1.0.1, the latest stable releases when the experiments began in late April 2019. On ResNet-50 they report 200 ± 1 images per second for TensorFlow and 212 ± 2 for PyTorch. Over all six benchmarks they found PyTorch within 17% of the fastest framework, and attributed the similarity to the frameworks’ shared use of the same cuDNN and cuBLAS versions. The paper comes from PyTorch’s developers and measures releases that are now several years old. The Keras 3 announcement (November 2023) reports that, in the Keras team’s benchmarks, JAX was usually the fastest Keras backend on GPU, TPU and CPU, but that results vary by model and TensorFlow without XLA was occasionally faster on GPU.

Versions and Compatibility

TensorFlow’s history, from its release notes and announcements:

  • From 2011: DistBelief. Google’s first-generation distributed training system (Dean et al., 2012), TensorFlow’s predecessor.
  • 9 November 2015: open-source release as TensorFlow 0.5.0, under Apache 2.0, with a graph-and-session Python API.
  • 2016: the OSDI paper (Abadi et al., 2016). It describes the dataflow model, deferred execution, the device abstraction for CPUs, GPUs and TPUs, and the PS tasks that take the place of parameter servers.
  • 15 February 2017: TensorFlow 1.0. It brought experimental XLA, installation with pip install tensorflow, and Python API changes towards NumPy conventions, with a conversion script for earlier code.
  • 2017–2018: 1.x additions. tf.keras and tf.data joined the core API in 1.4 (November 2017). Eager execution and TensorFlow Lite arrived as previews in 1.5 (January 2018), and eager execution left contrib in 1.7 (March 2018).
  • 30 September 2019: TensorFlow 2.0. Eager execution became the default, with tf.function and AutoGraph in place of sessions, Keras as the central API, tf.distribute.Strategy for Model.fit and custom training loops, SavedModel as the common format, the removal of tf.contrib, and the 1.x API kept under tf.compat.v1.
  • 7 March 2024: TensorFlow 2.16.1. Keras 3 became the default tf.keras, and the Estimator API was removed. The 2.16 release was published as 2.16.1.
  • 4 September 2024: TensorFlow Lite renamed LiteRT. TensorFlow 2.19 (March 2025) deprecated tf.lite.Interpreter, and 2.20 (August 2025) announced the deprecation of tf.lite in favor of the LiteRT repository.
  • 6 March 2026: TensorFlow 2.21.0, the current stable release as of October 2026. It dropped Python 3.9 and the TensorBoard dependency. A 2.22.0 release candidate appeared on 24 September 2026.

Code written for 1.x does not run unchanged on 2.x. The TF2 upgrade script renames many 1.x symbols, but the migration guide says that a migration is complete only when the code works with all TensorFlow 2 behaviors. That means rewriting session-and-placeholder programs as functions, and replacing global collections and tf.get_variable with tracked objects. The guide also lists “false friends” in the TensorFlow 2 namespace that keep 1.x graph behavior underneath, such as tf.Graph().as_default() and tf.estimator, which TensorFlow 2.16 then removed. Code written for Keras 2 mostly runs on Keras 3 with the TensorFlow backend, with exceptions that the Keras migration guide lists, and the two cannot be mixed in one model, since they are separate packages. Platform support changes from release to release. TensorFlow 2.16’s notes announced that Mac x86 builds would no longer be released as pip packages from 2.17 onwards, and 2.17 removed support for NVIDIA GPUs of compute capability 5.x from the published wheels. The release notes are the place to check before upgrading.

Limitations

  • Two execution modes to understand. Code that is correct eagerly can behave differently inside tf.function: Python side effects run only during tracing, appending to a Python list inside a loop that AutoGraph converts does not work as expected (tf.TensorArray is needed), and calls with changing shapes or Python arguments retrace, which costs time. tf.config.run_functions_eagerly(True) helps with debugging by turning graphs off.
  • API churn across major versions. The move from 1.x to 2.x changed the programming model, and the move to Keras 3 changed the high-level API package. Vision code published before TensorFlow 2.0 used the 1.x API and needs tf.compat.v1 or a rewrite to run on current releases.
  • Determinism costs speed and is not universal. Op determinism can slow tf.data pipelines with random ops by orders of magnitude, according to the documentation. Some ops have no deterministic implementation, and results are repeatable only on the same hardware.
  • On-device deployment is in transition. As of October 2026, tf.lite is deprecated in favor of LiteRT but still ships with TensorFlow 2.21.0. Ops that LiteRT lacks must be avoided or kept through the TF Select fallback.
  • Untrusted models are untrusted code. A SavedModel contains executable graphs, and the official guide advises treating models from unknown sources accordingly.

Related

  • Deep Learning

    Learning many layers of representation directly from pixels with neural networks trained by gradient descent; what makes depth useful, the history from the perceptron through the 2012 ImageNet result to foundation models, the main architecture families, how training at scale works, and a map of the deep-learning articles on this site.

  • Convolutional Neural Network

    A neural network built from convolutional layers, small learned filters applied at every image position, interleaved with nonlinearities and downsampling, so that features grow from edges to object parts while the parameter count stays independent of image size.

  • Backpropagation

    The algorithm that computes the gradient of a network's loss with respect to every weight by applying the chain rule backward through the computation graph, at a cost comparable to one forward pass.

  • Image Classification

    Assigning one or more category labels to a whole image, usually by predicting a probability for each of a fixed set of classes.

  • MobileNet

    Google's family of convolutional networks for phones and other small devices, from MobileNet v1 (2017), built from depthwise separable convolutions with width and resolution multipliers, through MobileNetV2's inverted residuals with linear bottlenecks and MobileNetV3's latency-driven architecture search, to MobileNetV4's universal inverted bottleneck and mobile attention (2024).

  • Data Augmentation

    Training a network on randomly transformed copies of its training images, such as crops, flips, colour changes, erasing and mixing, so that it learns invariances the small training set cannot show, provided each transformation keeps the label correct.

References

  1. Abadi, M., Barham, P., Chen, J., Chen, Z., Davis, A., Dean, J., Devin, M., Ghemawat, S., Irving, G., Isard, M., et al. (2016). TensorFlow: A System for Large-Scale Machine Learning. 12th USENIX Symposium on Operating Systems Design and Implementation (OSDI 16), 265–283.
  2. Dean, J., Corrado, G. S., Monga, R., Chen, K., Devin, M., Le, Q. V., Mao, M. Z., Ranzato, M., Senior, A., Tucker, P., Yang, K. & Ng, A. Y. (2012). Large Scale Distributed Deep Networks. Advances in Neural Information Processing Systems 25 (NIPS).
  3. Dean, J. & Monga, R. (2015, November 9). TensorFlow – Google's latest machine learning system, open sourced for everyone. Google Research Blog.
  4. TensorFlow Team (2019, September 30). TensorFlow 2.0 is now available! The TensorFlow Blog.
  5. TensorFlow release notes: TensorFlow 1.0.0 (15 February 2017). GitHub.
  6. TensorFlow release notes: TensorFlow 1.4.0 (2 November 2017). GitHub.
  7. TensorFlow release notes: TensorFlow 1.5.0 (26 January 2018). GitHub.
  8. TensorFlow release notes: TensorFlow 1.7.0 (29 March 2018). GitHub.
  9. TensorFlow release notes: TensorFlow 2.0.0 (30 September 2019). GitHub.
  10. TensorFlow release notes: TensorFlow 2.16.1 (7 March 2024). GitHub.
  11. TensorFlow release notes: TensorFlow 2.18.0 (24 October 2024). GitHub.
  12. TensorFlow release notes: TensorFlow 2.19.0 (12 March 2025). GitHub.
  13. TensorFlow release notes: TensorFlow 2.20.0 (13 August 2025). GitHub.
  14. TensorFlow release notes: TensorFlow 2.21.0 (6 March 2026). GitHub.
  15. TensorFlow release notes: TensorFlow 2.22.0-rc0 (24 September 2026, pre-release). GitHub.
  16. TensorFlow licence (Apache License 2.0), tensorflow/tensorflow repository at tag v2.21.0. GitHub.
  17. TensorFlow documentation: Introduction to graphs and tf.function (accessed October 2026).
  18. TensorFlow documentation: Better performance with tf.function (accessed October 2026).
  19. TensorFlow documentation: TensorFlow 1.x vs TensorFlow 2 – Behaviors and APIs (accessed October 2026).
  20. TensorFlow documentation: tf.data: Build TensorFlow input pipelines (accessed October 2026).
  21. TensorFlow documentation: Better performance with the tf.data API (accessed October 2026).
  22. TensorFlow documentation: Using the SavedModel format (accessed October 2026).
  23. TensorFlow documentation: tf.config.experimental.enable_op_determinism (accessed October 2026).
  24. TensorFlow Serving documentation: Architecture. tensorflow/serving repository (accessed October 2026).
  25. TensorFlow Serving documentation: Serving a TensorFlow Model (accessed October 2026).
  26. TensorFlow Serving documentation: RESTful API (accessed October 2026).
  27. Keras Team (2023). Introducing Keras 3.0 (Keras 3: Multi-backend deep learning). keras.io (accessed October 2026).
  28. Keras release notes: Keras 3.0.0 (28 November 2023). GitHub.
  29. Keras documentation: Save, serialize, and export models (accessed October 2026).
  30. Keras documentation: Model export API (accessed October 2026).
  31. Google AI Edge Team (2024, September 4). TensorFlow Lite is now LiteRT. Google Developers Blog.
  32. Google AI Edge documentation: Convert TensorFlow models (LiteRT) (accessed October 2026).
  33. Paszke, A., Gross, S., Massa, F., Lerer, A., Bradbury, J., Chanan, G., Killeen, T., Lin, Z., Gimelshein, N., Antiga, L., et al. (2019). PyTorch: An Imperative Style, High-Performance Deep Learning Library. Advances in Neural Information Processing Systems 32 (NeurIPS).