Understand weights, training, and useful applications with a one-weight numerical experiment.
Project: Workshop Assistant · Audience: basic Python developers · Run: CPU; optional external training only in Part 10.
Fine-tuning means taking a model that has already learned something and training it further for a more specific task. A model contains numbers called parameters or weights. Training changes those numbers using examples. Fine-tuning starts from learned numbers rather than starting again from random numbers.
Imagine a developer who already knows Python. Teaching that developer your team's naming conventions is easier than teaching programming from zero. The analogy has limits: a model is calculating outputs, not understanding a lesson like a person. Still, it helps separate pretraining, which builds broad capabilities, from fine-tuning, which adapts an existing starting point.
What can you use it for? A support team might classify questions into Python, Linux, and IoT queues. A coding assistant might learn a consistent JSON response format. An image model might recognize defects on your own camera feed. A writing model might follow reviewed examples of your organization's tone. These are tasks with patterns that can be demonstrated repeatedly.
Fine-tuning is not a dependable database update mechanism. If an ESP32 product price changes every day, retrieve the current price from a database. Changing weights does not guarantee exact recall, correct citations, or deletion of old facts. Prompting, retrieval, validation, and fine-tuning can be combined.
We will build a tiny Workshop Assistant for Python, Linux, and ESP32 questions. Parts 1–9 use original, synthetic examples and small PyTorch models on CPU. They do not call a paid API or download model weights. You will see actual gradient updates, frozen layers, LoRA matrices, training curves, and evaluation results.
These are teaching models, not a capable pretrained chatbot. We explicitly train a small source model before adapting it. Part 10 shows how the same workflow connects to a public pretrained language model. Its external-model training is optional and separately marked.
You only need basic Python: variables, lists, functions, imports, and classes. A tensor is an array that can track derivatives; a gradient tells us which direction increases a loss; a loss is a number that measures a mismatch. We will introduce each when it appears. Install the ZIP's requirements and open the notebook matching this part. The README has Windows and Linux instructions.
Suppose an existing model predicts y = w*x. Its learned weight is w = 2. Our new task has the example x = 2, desired answer y = 6. The model currently predicts 4. Use squared error: (prediction - target)^2 = (4 - 6)^2 = 4.
The derivative with respect to the weight is 2*x*(w*x - y) = 2*2*(-2) = -8. With learning rate 0.1, the update is w_new = w - 0.1*gradient = 2.8. The prediction becomes 5.6 and the loss becomes 0.16. We improved this example by changing an existing weight.

import torch
w = torch.tensor(2.0, requires_grad=True)
x, target = torch.tensor(2.0), torch.tensor(6.0)
loss = (w*x - target)**2
loss.backward()
print('before:', w.item(), 'loss:', loss.item(), 'gradient:', w.grad.item())
with torch.no_grad():
w -= 0.1*w.grad
new_loss = (w*x-target)**2
print('after:', w.item(), 'prediction:', (w*x).item(), 'loss:', new_loss.item())
assert abs(w.item()-2.8) < 1e-5
assert new_loss < loss
Executed output (CPU teaching experiment):
before: 2.0 loss: 4.0 gradient: -8.0 after: 2.799999952316284 prediction: 5.599999904632568 loss: 0.16000007092952728
requires_grad=True asks PyTorch to track operations involving the weight. backward() calculates its gradient. The no_grad() block updates the value without recording that update as another differentiable calculation. In a larger model, an optimizer performs updates for thousands or billions of weights.
One successful example does not establish a useful model. If the original job needed w=2, moving toward 3 can make the old job worse. This is a first glimpse of forgetting. We need unseen examples, a baseline, and often a mix of old and new tasks.
| Situation | First experiment |
|---|---|
| The instruction is unclear | Improve the prompt and examples |
| The answer depends on fresh documents | Retrieve documents; check sources |
| A rule can decide the answer exactly | Implement the rule |
| Many reviewed examples demonstrate the same behavior | Evaluate a fine-tuning experiment |
| You have only a few inconsistent labels | Improve the data before training |
Fine-tuning has costs: collecting examples, checking labels, running experiments, and maintaining a model version. A small classifier may solve your problem more cheaply than adapting a language model. Decide by measured behavior, not by the popularity of a method.
Change the learning rate to 0.01, rerun the cell, and explain why the update is smaller. Then try 1.0: a larger step can overshoot and make the loss worse. A learning rate is a step-size choice, not a percentage improvement.
By the end of this part, you should be able to explain the difference between changing a prompt and changing weights. Next, we will define a concrete task and separate training examples from the examples used to judge the result.
Further reading: PyTorch transfer learning tutorial.