Lesson 5 — PyTorch, TensorFlow and environments
Two topics that look unrelated and are not: which framework to learn, and how to stop your machine becoming a place where nothing works twice. The second causes more lost hours than the first.
PyTorch and TensorFlow
Both train neural networks on graphics processors. Both are mature, well documented and capable of anything you will do for years. The historical difference was philosophical, and it has largely been resolved in PyTorch's favour.
TensorFlow, released by Google in 2015, originally required you to declare the whole computation as a static graph before running it. That allowed aggressive optimisation and made debugging miserable: an error surfaced during graph execution, far from the line that caused it, with a stack trace that pointed inside the framework.
PyTorch, released by Meta in 2016, executed operations immediately, as ordinary Python. You could print an intermediate value, step through with a debugger, and use a normal if statement to change the network's behaviour. Researchers switched almost immediately, because research is iteration and iteration needs debugging.
TensorFlow adopted immediate execution by default in version 2. The technical gap narrowed considerably; the community gap did not close.
| PyTorch | TensorFlow | |
|---|---|---|
| Reads like | ordinary Python | ordinary Python since v2 |
| Research adoption | overwhelming majority of papers | small minority |
| Industry adoption | now the default for new projects | large installed base in production |
| High-level interface | Lightning, fastai | Keras, built in |
| Mobile and browser | ExecuTorch, growing | TensorFlow Lite and TF.js, mature |
| Debugging | straightforward | good since v2 |
PyTorch. Almost every model implementation you will want to read is written in it, Hugging Face is built around it, and it is what new projects choose. Learn Keras afterwards if you want the gentlest possible on-ramp to building a network, since it expresses a model in remarkably few lines. Learning TensorFlow itself is worth doing when a job requires maintaining an existing system in it.
What Hugging Face changed
Worth knowing before you write any deep learning code: for text, images and audio, you will very rarely train from scratch.
Hugging Face hosts hundreds of thousands of pre-trained models with a uniform interface. Loading a state-of-the-art model is a couple of lines, and adapting it to your task — fine-tuning — needs orders of magnitude less data and compute than starting from nothing. A team with a few thousand labelled examples can reach a genuinely good result on a task that would otherwise be out of reach entirely.
The practical consequence: the skill that matters most is not implementing architectures. It is choosing an appropriate pre-trained model and adapting it well.
Why environments are not optional
Now the part that will save you real time.
Python installs packages into a shared location. Install a library globally and it is available everywhere, which sounds convenient and creates a specific, guaranteed failure:
- Project A needs version 1.2 of a library.
- Project B needs version 2.0, which changed a function signature.
- Installing one breaks the other, silently, with an error that appears in code you did not touch.
With deep learning this gets worse, because the framework version is coupled to the CUDA version, which is coupled to the graphics driver. A mismatch does not produce a clear message; it produces a machine that no longer sees the GPU.
A virtual environment gives each project its own isolated set of packages. Projects stop interfering, and — more importantly — the environment becomes a description of what your code needs, which is what makes a result reproducible six months later.
The tools, and which to choose
| Tool | Best for | Note |
|---|---|---|
| venv | any pure-Python project | built into Python, nothing to install |
| uv | most new projects in 2026 | same role, dramatically faster, replaces pip too |
| conda / mamba | scientific stacks with non-Python dependencies | handles CUDA and compiled libraries; heavier |
| Poetry | libraries you intend to publish | strong dependency resolution and locking |
| Docker | production, and hard-to-reproduce setups | isolates the whole operating system, not just packages |
The reasonable default: uv for new work, conda when you are fighting CUDA or geospatial libraries, Docker when the environment has to be identical on someone else's machine.
Pinning versions
An environment is only reproducible if the versions are recorded. requirements.txt listing pandas means "whatever is current", which will not be the same next year. Listing pandas==2.1.4 means what it says.
This matters more in machine learning than in most software, because library updates change numerical behaviour, not just interfaces. A different default in a random number generator or an algorithm's implementation can move your metrics. When you cannot reproduce a number from three months ago, unpinned versions are the usual culprit.
On Google Colab all of this is already handled: the environment is fresh each session and the whole stack is present. Set up local environments when you start projects you want to keep, version and re-run — which is exactly when the discipline starts paying.
In three sentences
PyTorch and TensorFlow do the same job, and PyTorch is where you should start because research, Hugging Face and new industry projects all live there. For text, images and audio you will adapt a pre-trained model rather than train from scratch, which makes model selection and fine-tuning the skills that matter. Virtual environments with pinned versions are not hygiene theatre: library updates change numerical behaviour, so they are the difference between a result you can defend and a number you cannot reproduce.
Next — Lesson 6: recap and FAQ →