Object Classification from Robotic Sensor Features
My Machine Learning for Robotics assignment from Spring 2026. K-Nearest Neighbours and Gaussian Naive Bayes classify four object types from five synthetic sensor readings.
Live Links
The repository, this page, and a ZIP download.
Overview
CS-4090 Machine Learning for Robotics, Assignment 4, Spring 2026, FAST-NUCES, with Dr. Maryam Iqbal. The brief asked for a classification system a robot could use to tell objects apart from sensor readings, using KNN and Naive Bayes.
500 synthetic sensor rows
125 rows each of metal can, plastic bottle, wooden block and rubber ball. Five features: distance, size, colour intensity, weight and sensor voltage. Each class has its own ranges. Seed 42, no missing values.
KNN and GaussianNB
Features are scaled on the training set only. A 100-row stratified hold-out is used for the numbers below. After a small grid search both tuned models reach 100% on that test set.
CS-4090 · Spring 2026
FAST-NUCES, roll 22I-2327. The assignment brief is in assignment/assignment-04.pdf. The write-up I submitted is in report/report.pdf.
The submitted report recorded 31% for both models. That run had trained on a first version of the table where labels were assigned at random, so the features had no relationship with the class. The scripts and notebook here train on the class-conditional table. The accuracies are high because weight and voltage already separate the four classes fairly cleanly — this is a clean synthetic table, not a claim about real sensors.
Dataset
The table lives in data/synthetic_robotic_dataset.csv and can be rebuilt with the same seed from src/generate_dataset.py.
| Class | Distance | Size | Colour intensity | Weight | Sensor voltage |
|---|---|---|---|---|---|
| metal_can | 15–90 | 8–20 | 120–220 | 250–500 | 3.0–5.0 |
| plastic_bottle | 15–90 | 18–40 | 60–180 | 30–150 | 0.5–2.0 |
| wooden_block | 15–90 | 10–30 | 40–130 | 150–320 | 1.5–3.2 |
| rubber_ball | 15–90 | 6–18 | 150–255 | 80–220 | 1.0–2.8 |
Feature means on the generated table. Distance is almost the same for every class; weight and voltage are the strongest separators. Wooden block and rubber ball overlap more than the others, which is where the one default-KNN mistake happened.
| Class | Distance | Size | Colour | Weight | Voltage |
|---|---|---|---|---|---|
| metal_can | 51.56 | 14.20 | 169.66 | 380.92 | 4.05 |
| plastic_bottle | 49.95 | 29.04 | 123.47 | 88.77 | 1.25 |
| rubber_ball | 52.29 | 12.04 | 203.02 | 151.99 | 1.86 |
| wooden_block | 51.94 | 20.51 | 84.52 | 237.70 | 2.35 |
Models and Results
80/20 split, stratified, seed 42: 400 training rows and 100 test rows, 25 per class. StandardScaler is fitted on the training set only. Class names follow LabelEncoder order: metal_can, plastic_bottle, rubber_ball, wooden_block.
| Model | Test accuracy | Notes |
|---|---|---|
| KNN (k=3) | 99% | One wooden block predicted as rubber_ball |
| GaussianNB | 100% | All 100 test rows correct |
KNN (k=3) per class
| Class | Precision | Recall | F1 |
|---|---|---|---|
| metal_can | 1.0000 | 1.0000 | 1.0000 |
| plastic_bottle | 1.0000 | 1.0000 | 1.0000 |
| rubber_ball | 0.9615 | 1.0000 | 0.9804 |
| wooden_block | 1.0000 | 0.9600 | 0.9796 |
After grid search
| Model | Best parameters | 5-fold CV | Test accuracy |
|---|---|---|---|
| KNN | metric=manhattan, k=11, weights=distance | 99.25% | 100% |
| GaussianNB | var_smoothing=1e-09 | 99.75% | 100% |
On this test set the two tuned models tie at 100%. GaussianNB was already perfect with its defaults; the search kept the default smoothing. I would pick GaussianNB if I had to choose one here — it is already at 100% without extra knobs, and it is cheaper to evaluate than an 11-neighbour KNN.
src/predict.py applies the scaler that was fitted at training time. The fitted models, scaler and label encoder are written together to models/object_classifiers.joblib when you run src/train.py. That file is left out of git on purpose — it is rebuilt in a few seconds.
Screenshots
The dataset, the two models, a few predictions, and this page. Click a picture to enlarge it; use the arrow keys to move between them.
Folder Structure
Object_Classification/
├── assignment/
│ └── assignment-04.pdf
├── report/
│ ├── report.pdf
│ ├── report.docx
│ └── notebook-colab-export.pdf
├── notebooks/
│ └── object_classification.ipynb
├── src/
│ ├── generate_dataset.py
│ ├── train.py
│ └── predict.py
├── data/
│ └── synthetic_robotic_dataset.csv
├── models/ # created by src/train.py
├── screenshots/
├── assets/
│ ├── css/
│ │ └── style.css
│ └── js/
│ └── main.js
├── index.html
├── README.md
├── requirements.txt
├── LICENSE
├── .gitignore
├── .gitattributes
└── .nojekyll
How to Run
Python 3.10 or later. From the repository root:
python -m venv .venv
# Windows: .venv\Scripts\activate
# Linux / macOS: source .venv/bin/activate
pip install -r requirements.txt
python src/generate_dataset.py
python src/train.py
python src/predict.py --distance 40 --size 12 --color 180 --weight 400 --voltage 4.2 --model knn
generate_dataset.py is optional if you just want to reuse the CSV already in data/. train.py will also generate the table if the CSV is missing. The notebook is notebooks/object_classification.ipynb — it looks for src/ and data/ from the repository root or from notebooks/.
To preview this page locally, open index.html or serve the folder with python -m http.server 8080 and visit http://localhost:8080.
License
The code I wrote is under the MIT License. The assignment brief in assignment/ and the report in report/ remain course material and are included as I received or submitted them.