Write a working classifier from your own data, and learn to trust an accuracy number because you understand how it was measured.
Raw sensor readings depend on brightness as well as colour — a Skittle held slightly closer to the sensor reads brighter across every channel, even though its colour hasn't changed. Normalising (dividing each channel by the sum of all eight) removes that brightness effect and leaves just the shape of the spectrum, which is what colour actually is. Averaging every normalised reading for a colour gives a centroid — a typical fingerprint for that class — and cosine similarity measures how closely a new reading's shape matches each centroid, ignoring overall magnitude. Leave-one-out validation is a way to get an honest accuracy estimate from a small dataset: train on everything except one example, test on that one, and repeat for every example in turn.
Read the TSV and print the first few rows to confirm it parsed correctly before writing anything else:
import csv
with open("skittles.tsv") as f:
reader = csv.DictReader(f, delimiter="\t")
rows = list(reader)
print(rows[0])
Divide each value in a vector by the vector's sum, guarding against a divide-by-zero:
def normalize(vec):
total = sum(vec)
return [v / total if total > 0 else 0.0 for v in vec]
Compare two normalised vectors by the angle between them:
import math
def cosine_similarity(a, b):
dot = sum(x * y for x, y in zip(a, b))
na = math.sqrt(sum(x * x for x in a))
nb = math.sqrt(sum(y * y for y in b))
return dot / (na * nb) if na > 0 and nb > 0 else 0.0
Group your normalised readings by colour label, average each group, and treat the result as that colour's fingerprint.
Normalise a new reading and compare it against every centroid, returning the best match:
def classify(vec, centroids):
x = normalize(vec)
scores = [(label, cosine_similarity(x, c)) for label, c in centroids.items()]
scores.sort(key=lambda s: s[1], reverse=True)
return scores[0], scores # best match, all scores
Pick one of your own logged readings and run it through classify() before building any automatic evaluation — does the top match look right?
For every row in your dataset, retrain the centroids on everything else, test on that one row, and tally correct vs incorrect per colour. Report both an overall accuracy and a per-class breakdown.
Find one misclassified example, look at its full ranked score list, and write a sentence or two on why it was likely confused — e.g. two colours that share a lot of spectral overlap.