nnfs_book icon indicating copy to clipboard operation
nnfs_book copied to clipboard

Chapter 19 Model class `train` method validation should be aligned with the outer for loop

Open PREPONDERANCE opened this issue 1 year ago • 0 comments

In Chapter 18, we have the train method as:

def train(
        self,
        X: np.ndarray,
        y: np.ndarray,
        *,
        validation: tuple[np.ndarray, np.ndarray] = None,
        epochs: int = 100,
        print_every: int = 10,
    ):
        self.accuracy.init(y)

        for epoch in range(1, 1 + epochs):
            output = self.forward(X)

            data_loss, reg_loss = self.loss.calculate(output, y)
            loss = data_loss + reg_loss

            predictions = self.output_layer.predict(output)
            acc = self.accuracy.calculate(predictions, y)

            self.backward(output, y)

            self.optimizer.pre_update_params()
            self.optimizer.update()
            self.optimizer.post_update_params()

            if not epoch % print_every:
                print(
                    f"epoch: {epoch}, "
                    + f"acc: {acc:.3f}, "
                    + f"loss: {loss:.3f}, "
                    + f"data_loss: {data_loss:.3f}, "
                    + f"reg_loss: {reg_loss:.3f}, "
                    + f"lr: {self.optimizer.curr_lr}"
                )

        if validation:
            x_val, y_val = validation

            output = self.forward(x_val, training=False)
            loss = self.loss.calculate(output, y_val, include_regularization=False)
            predictions = self.output_layer.predict(output)
            accuracy = self.accuracy.calculate(predictions, y_val)

            print(f"Validation, acc: {accuracy:.3f}, loss: {loss:.3f}")

The if validation clause is aligned with the outer for loop, which is understandable since we only need to validate the data once after the training process.

In chapter 19 sample code, we have:

# Main training loop
        for epoch in range(1, epochs+1):

            # Print epoch number
            print(f'epoch: {epoch}')

            # Reset accumulated values in loss and accuracy objects
            self.loss.new_pass()
            self.accuracy.new_pass()

            # Iterate over steps
            for step in range(train_steps):

                # If batch size is not set -
                # train using one step and full dataset
                if batch_size is None:
                    batch_X = X
                    batch_y = y

                # Otherwise slice a batch
                else:
                    batch_X = X[step*batch_size:(step+1)*batch_size]
                    batch_y = y[step*batch_size:(step+1)*batch_size]

                # Perform the forward pass
                output = self.forward(batch_X, training=True)

                # Calculate loss
                data_loss, regularization_loss = \
                    self.loss.calculate(output, batch_y,
                                        include_regularization=True)
                loss = data_loss + regularization_loss

                # Get predictions and calculate an accuracy
                predictions = self.output_layer_activation.predictions(
                                  output)
                accuracy = self.accuracy.calculate(predictions,
                                                   batch_y)

                # Perform backward pass
                self.backward(output, batch_y)


                # Optimize (update parameters)
                self.optimizer.pre_update_params()
                for layer in self.trainable_layers:
                    self.optimizer.update_params(layer)
                self.optimizer.post_update_params()

                # Print a summary
                if not step % print_every or step == train_steps - 1:
                    print(f'step: {step}, ' +
                          f'acc: {accuracy:.3f}, ' +
                          f'loss: {loss:.3f} (' +
                          f'data_loss: {data_loss:.3f}, ' +
                          f'reg_loss: {regularization_loss:.3f}), ' +
                          f'lr: {self.optimizer.current_learning_rate}')

            # Get and print epoch loss and accuracy
            epoch_data_loss, epoch_regularization_loss = \
                self.loss.calculate_accumulated(
                    include_regularization=True)
            epoch_loss = epoch_data_loss + epoch_regularization_loss
            epoch_accuracy = self.accuracy.calculate_accumulated()

            print(f'training, ' +
                  f'acc: {epoch_accuracy:.3f}, ' +
                  f'loss: {epoch_loss:.3f} (' +
                  f'data_loss: {epoch_data_loss:.3f}, ' +
                  f'reg_loss: {epoch_regularization_loss:.3f}), ' +
                  f'lr: {self.optimizer.current_learning_rate}')

            # If there is the validation data
            if validation_data is not None:

                # Reset accumulated values in loss
                # and accuracy objects
                self.loss.new_pass()
                self.accuracy.new_pass()

                # Iterate over steps
                for step in range(validation_steps):

                    # If batch size is not set -
                    # train using one step and full dataset
                    if batch_size is None:
                        batch_X = X_val
                        batch_y = y_val


                    # Otherwise slice a batch
                    else:
                        batch_X = X_val[
                            step*batch_size:(step+1)*batch_size
                        ]
                        batch_y = y_val[
                            step*batch_size:(step+1)*batch_size
                        ]

                    # Perform the forward pass
                    output = self.forward(batch_X, training=False)

                    # Calculate the loss
                    self.loss.calculate(output, batch_y)

                    # Get predictions and calculate an accuracy
                    predictions = self.output_layer_activation.predictions(
                                      output)
                    self.accuracy.calculate(predictions, batch_y)

                # Get and print validation loss and accuracy
                validation_loss = self.loss.calculate_accumulated()
                validation_accuracy = self.accuracy.calculate_accumulated()

                # Print a summary
                print(f'validation, ' +
                      f'acc: {validation_accuracy:.3f}, ' +
                      f'loss: {validation_loss:.3f}')

If I was correct on

we only need to validate the data once after the training process

then why should we validate the test data through the epochs?

PREPONDERANCE avatar Aug 15 '24 08:08 PREPONDERANCE