Short answer
Install scikit-learn, load its built-in handwritten digits, and fit a small neural network on the CPU; on one processor thread this took under half a second and reached 96.9% accuracy on images it never saw. The point is the habit, not the score: keep test data apart, compare against a dumb baseline, save the model and check that it reloads.
Follow-up question What should you change first once the model works?
Training a model does not start with buying hardware. It starts with a question a program can learn to answer from examples. "Which digit is in this tiny picture?" is a good first one: the data ships with a free library, the answer can be checked, and the whole job fits in the memory of an old computer.
To train a model is to adjust a large set of numbers until the program's answers on known examples get better. A GPU, or graphics processor, only does that arithmetic faster. When the data and the model are small, the ordinary processor in any laptop is plenty, and a mistake costs you a few seconds, not a rental bill.
The whole project, in one file
You need Python 3 and two minutes. The commands below create a separate environment, so nothing touches the rest of your system.
python3 -m venv first-model
source first-model/bin/activate # on Windows: first-model\Scripts\activate
pip install scikit-learn
Save this as first_model.py and run python first_model.py.
import os
os.environ["OMP_NUM_THREADS"] = "1" # one CPU thread, on purpose
import joblib
from sklearn.datasets import load_digits
from sklearn.dummy import DummyClassifier
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPClassifier
digits = load_digits()
X, y = digits.data / 16.0, digits.target # pixels go from 0 to 16; bring them to 0-1
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=0)
baseline = DummyClassifier(strategy="most_frequent").fit(X_train, y_train)
print("always guessing one digit:", baseline.score(X_test, y_test))
model = MLPClassifier(hidden_layer_sizes=(64,), max_iter=400, random_state=0)
model.fit(X_train, y_train)
print("small neural network:", model.score(X_test, y_test))
joblib.dump(model, "digits.joblib")
again = joblib.load("digits.joblib")
print("reloaded model agrees:", (again.predict(X_test) == model.predict(X_test)).all())
The digits dataset holds 1,797 images of 8 by 8 pixels, in 10 classes. train_test_split keeps 360 of them away from training. stratify=y makes sure every digit appears in both piles in the same proportion. The MLPClassifier is a small neural network: 64 inputs, one hidden layer of 64 units, 10 outputs.
What it measured
These numbers come from running the longer companion script, 01_first_model.py, once, limited to a single CPU thread on an Apple M5 Max computer with 64 GB of memory, Python 3.14 and scikit-learn 1.9.1. An older laptop will take longer; that was not tested here.
| Model | Training time | Accuracy on 360 unseen images | Mistakes |
|---|---|---|---|
| Always guess the most common digit | 0.000 s | 10.0% | 324 |
| Logistic regression (a straight-line classifier) | 0.016 s | 96.7% | 12 |
| Small neural network, 4,810 parameters | 0.379 s | 96.9% | 11 |
Measured here as well: the saved file is 165 KB, the reloaded model gave the same answer on all 360 test images, and the whole program peaked at 215 MB of memory. The slowest part of the first run was not training at all. Starting Python and loading the libraries from a cold disk took about 50 seconds; later runs start in a second or two.
Look at the table again. The neural network beat the much simpler straight-line model by one image out of 360. That is not a meaningful win, and the series comes back to why in project four. A beginner who skips the simple model would never learn that the "real AI" barely mattered here.
Why four habits matter more than this model
Everything else in this series is a bigger version of what those twenty lines do.
| Habit | In this project | What it prevents |
|---|---|---|
| Hold out test data | 360 images never used in training | Grading the model on the answers it has memorized, which the scikit-learn documentation calls a methodological mistake |
| Beat a baseline | 10% from guessing | Celebrating a score that means nothing |
| Fix the random seed | random_state=0 | Not knowing whether a change or plain luck moved your score |
| Save and reload | joblib.dump, then compare | Finding out later that the model only worked in memory |
joblib files use Python's pickle format, which can run code when loaded. Open only the ones you made yourself. Project 14 moves to a safer format.
When a laptop stops being enough
scikit-learn says plainly that this network is not meant for large-scale work and has no GPU support. You will outgrow it, though usually later than you expect. Later projects in this series were all run on one machine, and they show exactly where the time and memory go. For now, one rule of thumb is enough: if a run finishes in minutes on your processor, you do not need anything else yet.
Try it: Change test_size=0.2 to test_size=0.9, so that only 179 images train the model, then run it again and write down what happens to the accuracy.
What should you change first once the model works?
Change one thing at a time and write down the result. Good first changes are the amount of training data, then the hidden layer size, for example (16,) or (256,). Those two experiments teach you how much a problem depends on data and how much on model size.
Next, find out which mistakes it makes. Print the confusion between true and predicted digits with sklearn.metrics.confusion_matrix, then look at the pictures themselves. In the companion run the most common error was calling an 8 a 1, twice. Looking at errors tells you more than one more decimal of accuracy.
Then swap in your own small table of data, if you have one you are allowed to use. The code hardly changes, while the questions get harder: where the labels come from, whether they are right, and whether your test set looks like real use. Project three is about exactly that. Before that, project two opens the box and shows what "training" actually changes.
Download the companion code for this series (zip), with every script and the recorded results.
Sources
- load_digits — scikit-learn
- Neural network models (supervised) — scikit-learn
- Cross-validation: evaluating estimator performance — scikit-learn
From the Guide to Artificial Intelligence, by Robert Scales, published by Holistic Hybrid Inc. Share and adapt it under CC BY-NC-SA 4.0: credit the guide, link to https://guidetoartificialintelligence.com/en/learn/train-first-model-laptop-no-gpu/, no commercial use without permission, and keep the same licence.