Introduction

Welcome back to lesson 4 of "Building and Applying Your Neural Network Library"! You've accomplished so much on this journey. We began by modularizing our core components into clean, reusable modules for layers and activations. Then, we organized our training components by creating dedicated modules for loss functions and optimizers, building a solid foundation for our neural network package.

Now it's time to put our neural network library to work on a real-world problem! While our XOR examples have been perfect for learning and testing, real machine learning applications involve working with actual datasets that come with their own challenges: multiple features, varying scales, missing values, and the need for proper data preprocessing.

In this lesson, we'll prepare the California Housing dataset — a classic regression problem that predicts house prices based on various demographic and geographic features. We'll learn essential data handling techniques, including loading datasets from scikit-learn via the reticulate package, understanding feature characteristics, splitting data properly for evaluation using the caret package, and applying feature scaling to ensure our neural network can learn effectively. This foundation will set us up perfectly for our final lesson, where we'll apply our complete neural network library to solve this practical prediction problem.

Understanding Real-World Datasets

Real datasets come with complexities that require careful handling before we can apply machine learning algorithms effectively:

  • Feature diversity is one key challenge — real datasets often contain features measured in completely different units and scales. For example, our California Housing dataset includes features like median income (measured in tens of thousands of dollars), house age (measured in years), and geographic coordinates (latitude and longitude). These dramatically different scales can cause problems for neural networks, which work best when all inputs are in similar ranges.
  • Data splitting becomes crucial when working with real datasets. Unlike our toy examples, where we could evaluate on the same data we trained on, real applications require us to reserve some data for testing. This allows us to get an honest estimate of how our model will perform on new, unseen data — the true test of machine learning success.
  • Preprocessing requirements also become more sophisticated. We need to standardize features so they have similar scales, handle the train-test split properly to avoid data leakage, and ensure our preprocessing steps are applied consistently between training and testing phases.
Loading the California Housing Dataset
Splitting Data for Proper Evaluation

Now we need to split our data into training and testing sets. This is crucial for getting an honest evaluation of our model's performance. We'll use the caret package's createDataPartition() function, which ensures balanced sampling for our regression problem:

# Set seed for reproducibility
set.seed(42)

# Create train-test split (80% train, 20% test)
train_indices <- createDataPartition(y, p = 0.8, list = FALSE)
X_train <- X[train_indices, ]
X_test <- X[-train_indices, ]
y_train <- y[train_indices, , drop = FALSE]
y_test <- y[-train_indices, , drop = FALSE]

cat("\nData split shapes:\n")
cat("  X_train:", dim(X_train), ", y_train:", dim(y_train), "\n")
cat("  X_test:", dim(X_test), ", y_test:", dim(y_test), "\n")

The output confirms our split:

Data split shapes:
  X_train: 16512 8 , y_train: 16512 1
  X_test: 4128 8 , y_test: 4128 1

We've allocated 80% of our data (16,512 samples) for training and 20% (4,128 samples) for testing. The createDataPartition() function from caret ensures that the split maintains the distribution of the target variable, which is particularly important for regression problems. The set.seed(42) function ensures reproducible results — we'll get the same split every time we run the code.

The key principle here is that our model will never see the test data during training. This separation allows us to evaluate how well our neural network generalizes to new, unseen examples, which is the ultimate goal of machine learning.

Feature Scaling for Neural Networks

Neural networks are particularly sensitive to the scale of input features. When features have very different ranges, the network may have difficulty learning effectively. We'll use the caret package's preProcess() function to apply standardization (also called z-score normalization):

# Apply feature scaling (Standardization)
# Initialize preprocessing parameters on training data
preproc_X <- preProcess(X_train, method = c("center", "scale"))
# Apply scaling to training data
X_train_scaled <- predict(preproc_X, X_train)
# Apply same scaling to test data
X_test_scaled <- predict(preproc_X, X_test)

Notice the crucial distinction here: we use preProcess() on the training data to calculate the scaling parameters (mean and standard deviation), then apply these same parameters to both training and test data using predict(). This ensures we don't introduce data leakage — the test data statistics don't influence our preprocessing parameters.

We also scale our target variable, which often helps with regression problems:

# Scale target variable y as well (often beneficial for regression)
preproc_y <- preProcess(y_train, method = c("center", "scale"))
y_train_scaled <- predict(preproc_y, y_train)
y_test_scaled <- predict(preproc_y, y_test)

Scaling the target variable helps our neural network learn more effectively by keeping the output values in a reasonable range, typically around zero with unit variance.

Verifying Our Preprocessing

Let's verify that our scaling worked correctly by examining the statistical properties of our transformed data:

cat("\n--- After Scaling ---\n")
cat("X_train_scaled mean (should be close to 0):", round(colMeans(X_train_scaled), 2), "\n")
cat("X_train_scaled std (should be close to 1):", round(apply(X_train_scaled, 2, sd), 2), "\n")
cat("Sample X_train_scaled[1,]:", round(X_train_scaled[1,], 2), "\n")

cat("\ny_train_scaled mean:", round(mean(y_train_scaled), 2), "\n")
cat("y_train_scaled std:", round(sd(y_train_scaled), 2), "\n")
cat("Sample y_train_scaled[1,]:", round(y_train_scaled[1,], 2), "\n")

The output confirms our scaling is working correctly:

--- After Scaling ---
X_train_scaled mean (should be close to 0): 0 0 0 0 0 0 0 0
X_train_scaled std (should be close to 1): 1 1 1 1 1 1 1 1
Sample X_train_scaled[1,]: 2.34 0.98 0.63 -0.15 -0.97 -0.04 1.11 -1.17

y_train_scaled mean: 0
y_train_scaled std: 1
Sample y_train_scaled[1,]: 2.48

Perfect! Our scaled features now have means of 0 and standard deviations of 1 across all dimensions. The sample that originally had values ranging from 8.3 to -122.23 now has standardized values ranging from -1.17 to 2.34. Similarly, our target variable has been scaled to have zero mean and unit variance.

This transformation puts all our features on equal footing, allowing our neural network to learn effectively without being dominated by features that happen to have larger numerical values. The network can now focus on the actual patterns and relationships in the data rather than fighting against scale differences.

Conclusion and Next Steps

Excellent work! We've successfully prepared the California Housing dataset for neural network training by implementing all the essential preprocessing steps using modern R packages. You now understand how to load real-world datasets using reticulate and scikit-learn, handle the challenges of multi-feature problems, and apply proper data splitting and scaling techniques using the caret package. Our dataset is ready with 16,512 training samples and 4,128 test samples, all properly standardized for effective neural network learning.

The skills you've developed in this lesson — data loading, splitting, and preprocessing — are fundamental to any machine learning project. In the upcoming practice exercises, you'll get hands-on experience implementing these preprocessing steps yourself, building confidence in your ability to handle real-world data preparation challenges. Then, we'll be ready to apply our neural network package to this dataset!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal