Concepts / Evaluating Model Performance for Spam Classification

Evaluating Model Performance for Spam Classification

Classification accuracy is the performance measure a practitioner reports, but it is not differentiable, so a gradient-based training procedure cannot follow it directly.

  • Programming

Why Accuracy Is the Score You Report

Suppose you are building a spam classifier. Your report card is the fraction of messages it labels correctly. That fraction is the number you ultimately care about. Yet when you train the model, you do not hand the training procedure that fraction and ask it to climb. Instead, you hand it a different quantity, a loss, and ask it to shrink that. This section explains why that swap happens and how the rest of the evaluation process depends on it.

Two quantities play different roles in this article. Accuracy is the measure of performance you report. Cross-entropy loss is the quantity that training reduces, chosen because it can be optimized where accuracy cannot. Keeping these two roles separate is the key to reading everything that follows.

Why Training Cannot Climb Accuracy Directly

Training adjusts a model by nudging its internal numbers in a direction that makes the score better. That nudging works smoothly only when the score changes gradually as the numbers change. Classification accuracy does not behave that way. It is a count of correct decisions, so it changes in discrete steps: a message either flips from wrong to right, or it does not.

Between two nearby settings of the model, the accuracy may stay exactly the same and then jump. A function with that stepped behavior is not differentiable, which means there is no usable slope to follow when deciding which way to adjust. A gradient-based procedure needs that slope, so it has nothing reliable to follow when the target is raw accuracy.

Cross-Entropy Loss as a Stand-In

Definition

A proxy is a different quantity that is chosen because it is workable to optimize while still being connected to the goal. Here, cross-entropy loss is the proxy for classification accuracy.

Cross-entropy loss is a loss function, so it produces a value that training tries to reduce. Lowering it is meant to push the model toward higher accuracy, even though the loss and the accuracy are not the same number. Training reduces the loss in the hope of improving accuracy. The loss is the lever training can pull; accuracy is the outcome you care about.

Where the Loss Is Computed for a Batch

The function that computes the loss for a batch of data is named calc_loss_batch. The source describes its structure as staying the same when the model is adapted to classification. There is one change, and it concerns which part of the model output feeds into the loss. Before adaptation the loss used every token position. After adaptation it uses only the last token position.

python
Notes on the code
  • Line 1: the full model output, which covers all token positions
  • Line 2: the adjusted expression, which optimizes only the final token position

The adjustment is the only change to calc_loss_batch. Everything else about how a batch flows into the loss stays the same. That is why the single slice is so important to read carefully: it decides which slice of the output the training signal is attached to.

Reading the Slice One Axis at a Time

A slice with three positions separated by colons selects along three axes. Read the expression [:, -1, :] one axis at a time. Each part of the slice has its own job, and reading them in order prevents most mistakes.

  • The first colon keeps everything along the batch axis, so every item in the batch remains.
  • The -1 chooses one position along the token axis. Python counts negative indices from the end, so -1 means the final position.
  • The last colon keeps everything along the remaining axis, so all values at that chosen position are kept.
Worked example

Slicing a batch of two messages

A generated example: the model output for a batch of two messages, each with four token positions, where each position holds a vector of values. Which parts remain after applying [:, -1, :]?

  1. 1
    Name the axes

    The output has a batch axis (two messages), a token axis (four positions), and a values axis (the vector at each position).

  2. 2
    Apply the first colon

    Both messages remain, so the batch axis still has two items.

  3. 3
    Apply -1 on the token axis

    -1 selects the fourth and final token position. Positions one through three are set aside.

  4. 4
    Apply the last colon

    The full vector of values at that final position is kept unchanged.

Result

The adjusted output keeps the final token position's vector for each of the two messages. It does not keep the whole sequence.

token 0set asidetoken 1set asidetoken 2set asidetoken -1 (final)kept, full value vector
Which dimension of the model output is being indexed, and which single token position and vocabulary vector remain after the slice?

Predict Which Position Survives

What do you think happens?

Given the expression model(input_batch)[:, -1, :], which token position of each message is kept in the loss?

  • The first token position

  • Every token position

  • The final token position

Reveal answer
Answer:

The final token position

The -1 on the token axis selects the last position, counting negative indices from the end. The first and last colons keep every batch item and every value at that position.

Mistakes With the Last-Token Adjustment

  • Common mistake

    Believing the loss now uses only one message from the batch.

    What it looks like

    Reading [:, -1, :] as selecting the last message in the batch.

    Why it fails

    The first colon is on the batch axis and keeps every item in the batch.

    Do this instead

    The -1 applies to the token axis. Every message still contributes.

  • Common mistake

    Treating the whole sequence as the prediction.

    What it looks like

    Assuming model(input_batch) and model(input_batch)[:, -1, :] feed the same quantity into the loss.

    Why it fails

    The unadjusted expression keeps all token positions, while the adjusted one keeps only the final position.

    Do this instead

    After the adjustment, only the final token position's values feed the loss for each message.

  • Common mistake

    Assuming -1 means the first position.

    What it looks like

    Reading -1 as the start of the sequence.

    Why it fails

    Python counts negative indices from the end, so -1 is the final position.

    Do this instead

    Read -1 as the last token position.

  • Common mistake

    Assuming the slice changes the structure of calc_loss_batch.

    What it looks like

    Thinking the adjustment requires rewriting the whole function.

    Why it fails

    The source says calc_loss_batch keeps its overall structure, with one adjustment to which part of the output feeds the loss.

    Do this instead

    Locate the single changed expression and treat the rest of the function as unchanged.

ReadingWhat the expression keepsCorrect or misreading
model(input_batch)All token positions for every batch itemDescribes the unadjusted full output
model(input_batch)[:, -1, :]The final token position's values for every batch itemCorrect reading of the adjustment
Only the last messageOne batch item across all positionsMisreading: the first colon keeps all batch items
The first token positionPosition 0Misreading: -1 is the final position

Readings of the last-token adjustment

Why Losses Are Measured After Updates

Training a language model means repeatedly updating its weights and watching the loss. A falling loss is encouraging, but it does not by itself answer the question that matters: does the training actually improve the model? To answer that, the training process needs a separate step that measures the model's loss on both the training data and held-out validation data after each model update.

The loss that reflects the updated model is the one measured after the weight update. Measuring after each update is what lets the training process be judged rather than assumed.

The Inputs and Outputs of evaluate_model

In this course, the evaluation step is handled by a function named evaluate_model. It takes the model, a training data loader, a validation data loader, the device the computation runs on, and an eval_iter value that controls how many batches are used. It returns two numbers: a training loss and a validation loss.

The two losses form a pair. The training loss shows how the model performs on data it has learned from. The validation loss shows how it performs on data kept aside for checking. Comparing them after each model update is what allows training progress to be judged.

What Evaluation Mode Changes

The function does more than compute numbers. It changes how the model behaves for the duration of the calculation. Two effects are separate ideas. Dropout is a behavior of the model's layers, and calling model.eval() switches it off so that repeated evaluations are stable and reproducible. Gradient tracking is a bookkeeping behavior of the computation, and it is switched off by the no-gradient context because evaluation only needs the loss value, not the gradients used to update weights.

Tracing the Order Inside evaluate_model

The order of operations is significant. The mode is set before any loss is computed, so both losses are measured under the same conditions. Once the model is in evaluation mode, evaluate_model wraps the loss calculations in a no-gradient context named torch.no_grad(). Inside that context, the function calls calc_loss_loader once on the training loader and once on the validation loader. Both calls receive the same model and device, and both use eval_iter as the number of batches. The mode is restored only after both losses have been computed.

A faulty evaluation step would show up in the reported losses at the point where the mode or the context is wrong. For example, if the restoring step were moved ahead of the loss calculations, the model would be back in training mode while the losses were being measured, so the two numbers would no longer be taken under the same conditions. This is a diagnostic inference from the function's structure, not a fixed procedure stated in the source.

Diagnosing Inconsistent Evaluation Losses

MEDIUM

A training run reports training and validation losses that do not look consistent from one evaluation to the next. Using the execution order of evaluate_model, list the steps in order and identify the first step where a fault would change the reported numbers.

Hints
  • Start with the mode switch at the top of the function and check whether the model is in evaluation mode before any loss is computed.
  • Check whether both losses are computed inside the same no-gradient context under the same model and device.
  • Check where model.train() is called relative to the two loss calculations.

Key Takeaways

  • Accuracy is the performance measure you report, but it changes in discrete steps and has no usable slope, so gradient-based training cannot follow it directly. Cross-entropy loss is the proxy that training reduces in the hope of improving accuracy.
  • The only change to calc_loss_batch is the expression model(input_batch)[:, -1, :], which keeps every batch item, the final token position (-1), and all values at that position.
  • Evaluation mode disables dropout for stable, reproducible results and the no-gradient context disables gradient tracking to reduce overhead. The model is restored to training mode at the end of evaluate_model.
  • evaluate_model sets the mode, computes the training and validation losses under identical conditions after model updates, and then restores training mode. A fault in this order shows up in the reported losses.

Related concepts

More concepts starting with E