Open to OpportunitiesTehan Wijeratne — CSE - First Direct Banking Operations UK
Open to Opportunities
Let's Talktehanwijeratne7@gmail.com
← Back home

I broke a model on purpose

I ran it on the public abalone dataset. The target was Rings. I compared a split-first pipeline with two deliberately contaminated alternatives.

The setup#

Sex was one-hot encoded. The other inputs were Length, Diameter, Height, Whole weight, Shucked weight, Viscera weight and Shell weight.

I used an 80/20 split and random_state=42 for the split and the random forest. Settings were fixed before the run.

SEED = 42
TEST_SIZE = 0.2
AGE_OFFSET = 1.5
QUANTILES = (0.25, 0.5, 0.75)
N_QUARTILES = 4
RunTrain R²Test R²Test RMSE (rings)
CLEAN0.9388990.5298702.255939
LEAKY FEATURE0.9999660.9999320.027050
SUBTLE LEAK0.9388140.5297792.256156

R² and RMSE are reported from the saved run. RMSE is measured in rings. The clean model’s training score and test score are different; neither is replaced by a preferred number.

What was wrong#

In the direct leak, age in years = Rings + 1.5 was added before splitting.

age_in_years = y + AGE_OFFSET
leaky_X = np.column_stack((X, age_in_years))

That feature exposes the target. In the subtler run, I calculated Shell weight quartiles and the mean Rings per quartile using the whole dataset. I also fitted the preprocessing, including the scaler, on the whole dataset before splitting.

quartile_edges = np.quantile(shell_weight, QUANTILES)
groups = np.digitize(shell_weight, quartile_edges, right=True)
group_means = tuple(np.mean(np.compress(groups == group, y))
                    for group in range(N_QUARTILES))
target_means = np.take(group_means, groups)
subtle_X = np.column_stack((X, target_means))
full_preprocess = preprocessing(np.size(subtle_X, axis=1))
scaled_whole_dataset = full_preprocess.fit_transform(subtle_X)

The subtle run did not beat the clean test score. Its preprocessing still used test-set information, so I would not treat it as a clean evaluation.

The fix#

  1. Split first. Before any scaling, cleaning, or feature engineering.
  2. Put everything in a pipeline. So preprocessing is fitted on training data only.
  3. Remove any feature I couldn’t explain.

These are the key parts of the clean pipeline. The forest retained its library defaults, with a fixed random state and a single worker.

def forest():
    # Library defaults retained, with a fixed seed and one worker for reproducibility.
    return RandomForestRegressor(random_state=SEED, n_jobs=1)

preprocess = ColumnTransformer((
    ('numeric', StandardScaler(), numeric_columns),
    ('sex', OneHotEncoder(handle_unknown='ignore', sparse_output=False), sex_columns),
))

pipe = Pipeline((
    ('preprocess', preprocess),
    ('model', forest()),
))

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=TEST_SIZE, random_state=SEED)
pipe = make_pipeline(np.size(X, axis=1))
pipe.fit(X_train, y_train)

Three things I took from this#

  1. Suspiciously good results deserve more scrutiny than bad ones. If it’s too good, assume a bug.
  2. Order matters. Split, then preprocess. The pipeline makes it impossible to forget.
  3. Report the honest number. A modest result you can defend beats an impressive one you can’t.

The same lesson at work#

In my day job, I check things carefully against strict controls. The mindset is the same: when something looks too clean, look again.

A report that matches perfectly isn’t always right. A dataset with no errors isn’t always correct. Sometimes the most useful thing you can do is stay a little suspicious.

Conclusion#

This was a deliberate experiment on public data. The direct target-derived feature exposed information the clean pipeline did not have. The subtle leak did not improve the test score, but its data flow was still contaminated.

If you’re learning data science, try this. Build a model, get a great score, then try to break it.

Data: Nash, W., Sellers, T., Talbot, S., Cawthorn, A., & Ford, W. (1994). Abalone, UCI Machine Learning Repository. CC BY 4.0. DOI.

More writing

Certificate viewer

Send an email

Choose your email app or a webmail service.

tehanwijeratne7@gmail.com

If your email app does not open, choose Gmail or Outlook.