Handwritten Digit Project Basics: Understanding train.csv, test.csv, and Labels
Handwritten Digit Project Basics: Understanding train.csv, test.csv, and Labels
Search
Ask the AI

Handwritten Digit Project Basics: Understanding train.csv, test.csv, and Labels

This handwritten digit project is a good bridge between theory-heavy machine learning notes and a real classification workflow. The input is simple enough to inspect row by row, but the project still forces you to deal with data loading, normalization, model training, and prediction output in a coherent way.

The best place to start is not the training loop. It is the dataset structure. The C classifier, the browser playground, and the final submission file all depend on the same flat 28 by 28 pixel format, so understanding the CSV layout makes the rest of the project much easier to follow.

1. What files are in the project

  • train.csv: the training set with 42000 labeled samples
  • test.csv: the test set with 28000 unlabeled samples
  • sample_submission.csv: the expected output format
  • submission.csv: the prediction file generated by the current implementation
  • digit_softmax_classifier.c: the C implementation used on the site

This layout is common in beginner-friendly supervised learning challenges because it keeps the separation of responsibilities clear: one file for learning parameters, one file for final predictions.

2. What one row in train.csv means

The first column is the label, which is the true digit for that image. The remaining 784 columns are grayscale pixel intensities between 0 and 255:

label,pixel0,pixel1,pixel2,...,pixel783
5,0,0,0,0,...,0
0,0,0,12,178,...,0
4,0,0,0,0,...,0

The important detail is that the original image has already been flattened into a feature vector. The program does not read image files. It reads numeric rows.

Because 28 x 28 = 784, every sample is effectively:

row 1 pixels + row 2 pixels + ... + row 28 pixels
= one 784-dimensional feature vector

That is why a plain linear classifier can still work on this task. To the model, the image is just a structured numeric input vector.

3. How test.csv differs from the training set

test.csv contains only pixels and no labels. That means the program cannot keep training on it. It must use the parameters learned from train.csv and produce predictions directly.

  • Training: input features plus the correct answer
  • Inference: input features only, no answer attached

This distinction matters because it forces the implementation to separate training logic from prediction logic. The exported submission.csv is simply the predicted label for each test sample written back into the required output format.

4. How the C program loads the data

The loader is intentionally straightforward. It splits each CSV row by commas, stores the first field as the label, and turns the remaining 784 fields into numeric features.

y_train[sample_count] = atoi(tokens[0]);
for (int j = 0; j < FEATURES; j++) {
    X_train[sample_count][j] = atof(tokens[j + 1]) / 255.0;
}

Two implementation details matter here:

  • The label is stored separately so the training loop can compute loss and accuracy
  • The pixels are divided by 255 so the values stay in the 0 to 1 range

If you skip the normalization step and train directly on raw 0 to 255 pixel values, gradient-based optimization becomes less stable. For flat image tables like this one, simple scaling is the right default.

5. Why this format is good for learning

This project is useful because it removes a lot of incidental complexity:

  • Simple input structure: no image decoding pipeline required
  • Clear labels: ten classes, one digit per sample
  • Direct debugging path: any row can be reshaped back into a 28 by 28 grid

That makes it a strong practice task for the full machine learning workflow: load data, normalize features, train parameters, run predictions, and export a CSV result.

6. What to validate before training

If you implement your own version, check these first:

  • Whether the header row is skipped correctly
  • Whether the training and test counts are close to 42000 and 28000
  • Whether each row contains exactly 785 or 784 fields
  • Whether pixel values have been scaled to 0 to 1
  • Whether labels still stay in the 0 to 9 range

Those checks matter more than changing the model too early. Many broken training runs come from bad CSV parsing, off-by-one field mistakes, or missing normalization.

7. Dataset Audit Table

Before training a classifier, the dataset itself needs an audit trail. The table below turns the CSV description into concrete checks a reader can repeat locally, which is more useful than only saying that the file has pixels and labels.

Audit item What to verify Why it matters Failure signal
Row shape train.csv has 785 fields per row; test.csv has 784. The label column exists only in training data. Predictions shift by one pixel column or labels are parsed as features.
Pixel scale Values are integers from 0 to 255 before normalization. The C model divides by 255.0 to keep optimization stable. Loss becomes unstable or gradients are much larger than expected.
Label range Training labels stay in the 0 to 9 class range. Softmax output has exactly ten classes. Array indexing errors or impossible labels appear in logs.
Output contract submission.csv keeps ImageId,Label and one prediction per test row. The pipeline must export reusable predictions, not only train locally. Wrong row count, missing header, or predicted labels outside 0 to 9.

The two files’ columns are offset by one, silently

Section three noted that test.csv has no label column. That sentence is easy to skim past, and its consequence deserves stating on its own: column 0 means something different in each file.

train.csv:  label, pixel0, pixel1, ..., pixel783    785 columns
test.csv:          pixel0, pixel1, ..., pixel783    784 columns

If the reading code shares one set of column indices across both paths, row[1] is pixel0 during training and pixel1 at inference — the entire image is shifted by one pixel.

What makes this bug nasty is that it raises nothing: the column count works, types are fine, the program runs cleanly, and training accuracy looks normal (the training path is correct). Only the submitted score comes out inexplicably low, and you go hunting through the model and hyperparameters, never suspecting the data reader is off by one.

Prevent it by locating columns from the header rather than hardcoded indices; if you insist on indices, leave an assertion on each path:

/* training set: the first field must be a 0-9 label */
assert(label >= 0 && label <= 9);
/* both paths must yield exactly 784 pixels */
assert(pixel_count == 784);

A more direct check is to reconstruct the first row as a 28×28 grid and print it (section eight shows how). An off-by-one image comes out visibly skewed, and this check only has to run once.

Skipping normalisation multiplies the learning rate by 255

Pixels in the CSV are integers from 0 to 255. Feeding them in directly does run, but training is unstable, and the arithmetic is worth doing.

In a linear layer, a weight's gradient is proportional to its corresponding input value. With inputs in 0–255 rather than 0–1, gradient magnitudes are two orders of magnitude larger. The learning rate is shared across all parameters, so the same learning rate represents an effective step size 255× different before and after normalisation.

The symptom: a slightly larger learning rate diverges (loss goes to nan or oscillates violently), while any rate that does converge is absurdly slow. People conclude the model is inadequate or the data is hard, when the scales simply were not aligned.

x[i] = raw[i] / 255.0;      /* one line */

A related but subtler issue: training and inference must use the same normalisation. Dividing by 255 during training and forgetting to at inference means the model receives inputs 255× larger than it was trained on, and the output is meaningless — again with no error. Writing normalisation as one function called from both paths is considerably more reliable than writing it twice.

As an aside, MNIST-style data needs only the division by 255 because every pixel shares one scale. Tabular data with features on different scales needs standardisation (subtract mean, divide by standard deviation) — and that mean and standard deviation may only be computed from the training set, since using the full dataset causes leakage.

8. What to read next

Once the dataset format makes sense, continue with the C softmax classifier article. That article walks through the weight matrix, softmax probabilities, gradient updates, and how the project produces submission.csv.

The downloadable files now live on the downloads page, and the lightweight interactive version is available in the handwritten digit tab inside the playground.

Leave a Reply

Scroll down