Skip to content

Debugging a pipeline

river encourages users to make use of pipelines. The biggest pain point of pipelines is that it can be hard to understand what's happening to the data, especially when the pipeline is complex. Fortunately the Pipeline class has a debug_one method that can help out.

Let's look at a fairly complex pipeline for predicting the number of bikes in 5 bike stations from the city of Toulouse. It doesn't matter if you understand the pipeline or not; the point of this notebook is to learn how to introspect a pipeline.

import datetime as dt
from river import compose
from river import datasets
from river import feature_extraction
from river import linear_model
from river import metrics
from river import preprocessing
from river import stats
from river import stream


X_y = datasets.Bikes()
X_y = stream.simulate_qa(X_y, moment='moment', delay=dt.timedelta(minutes=30))

def add_time_features(x):
    return {
        **x,
        'hour': x['moment'].hour,
        'day': x['moment'].weekday()
    }

model = add_time_features
model |= (
    compose.Select('clouds', 'humidity', 'pressure', 'temperature', 'wind') +
    feature_extraction.TargetAgg(by=['station', 'hour'], how=stats.Mean()) +
    feature_extraction.TargetAgg(by='station', how=stats.EWMean())
)
model |= preprocessing.StandardScaler()
model |= linear_model.LinearRegression()

metric = metrics.MAE()

questions = {}

for i, x, y in X_y:
    # Question
    is_question = y is None
    if is_question:
        y_pred = model.predict_one(x)
        questions[i] = y_pred

    # Answer
    else:
        metric.update(y, questions[i])
        model = model.learn_one(x, y)

        if i >= 30000 and i % 30000 == 0:
            print(i, metric)
30000 MAE: 2.220942
60000 MAE: 2.270271
90000 MAE: 2.301302
120000 MAE: 2.275876
150000 MAE: 2.275224
180000 MAE: 2.289347

Let's start by looking at the pipeline. You can click each cell to display the current state for each step of the pipeline.

model
add_time_features
def add_time_features(x): return { **x, 'hour': x['moment'].hour, 'day': x['moment'].weekday() }
['clouds', 'humidity', 'pressure', 'temperature', 'wind']
{'keys': {'clouds', 'temperature', 'wind', 'pressure', 'humidity'}}
y_mean_by_station_and_hour
{'_feature_name': 'y_mean_by_station_and_hour', '_groups': defaultdict(functools.partial(<function deepcopy at 0x7f7d47503af0>, Mean: 0.), {('metro-canal-du-midi', 0): Mean: 7.93981, ('metro-canal-du-midi', 1): Mean: 8.179704, ('metro-canal-du-midi', 2): Mean: 8.35824, ('metro-canal-du-midi', 3): Mean: 8.656051, ('metro-canal-du-midi', 4): Mean: 8.868445, ('metro-canal-du-midi', 5): Mean: 8.99656, ('metro-canal-du-midi', 6): Mean: 9.09966, ('metro-canal-du-midi', 7): Mean: 8.852642, ('metro-canal-du-midi', 8): Mean: 12.66712, ('metro-canal-du-midi', 9): Mean: 13.412186, ('metro-canal-du-midi', 10): Mean: 12.486815, ('metro-canal-du-midi', 11): Mean: 11.675479, ('metro-canal-du-midi', 12): Mean: 10.197409, ('metro-canal-du-midi', 13): Mean: 10.650855, ('metro-canal-du-midi', 14): Mean: 11.109123, ('metro-canal-du-midi', 15): Mean: 11.068934, ('metro-canal-du-midi', 16): Mean: 11.274958, ('metro-canal-du-midi', 17): Mean: 8.459136, ('metro-canal-du-midi', 18): Mean: 7.587469, ('metro-canal-du-midi', 19): Mean: 7.734677, ('metro-canal-du-midi', 20): Mean: 7.582465, ('metro-canal-du-midi', 21): Mean: 7.190665, ('metro-canal-du-midi', 22): Mean: 7.486895, ('metro-canal-du-midi', 23): Mean: 7.840791, ('place-des-carmes', 0): Mean: 4.720696, ('place-des-carmes', 1): Mean: 3.390295, ('place-des-carmes', 2): Mean: 2.232181, ('place-des-carmes', 3): Mean: 1.371981, ('place-des-carmes', 4): Mean: 1.051665, ('place-des-carmes', 5): Mean: 0.984993, ('place-des-carmes', 6): Mean: 2.039947, ('place-des-carmes', 7): Mean: 3.850369, ('place-des-carmes', 8): Mean: 3.792624, ('place-des-carmes', 9): Mean: 5.957182, ('place-des-carmes', 10): Mean: 8.575303, ('place-des-carmes', 11): Mean: 9.321546, ('place-des-carmes', 12): Mean: 10.511931, ('place-des-carmes', 13): Mean: 11.392745, ('place-des-carmes', 14): Mean: 10.735003, ('place-des-carmes', 15): Mean: 10.198787, ('place-des-carmes', 16): Mean: 9.941479, ('place-des-carmes', 17): Mean: 9.125579, ('place-des-carmes', 18): Mean: 7.660775, ('place-des-carmes', 19): Mean: 6.847649, ('place-des-carmes', 20): Mean: 9.626876, ('place-des-carmes', 21): Mean: 11.602929, ('place-des-carmes', 22): Mean: 10.405537, ('place-des-carmes', 23): Mean: 7.700904, ('place-esquirol', 0): Mean: 7.415789, ('place-esquirol', 1): Mean: 5.244396, ('place-esquirol', 2): Mean: 2.858635, ('place-esquirol', 3): Mean: 1.155929, ('place-esquirol', 4): Mean: 0.73306, ('place-esquirol', 5): Mean: 0.668546, ('place-esquirol', 6): Mean: 1.21265, ('place-esquirol', 7): Mean: 3.107535, ('place-esquirol', 8): Mean: 8.518696, ('place-esquirol', 9): Mean: 15.470588, ('place-esquirol', 10): Mean: 19.465005, ('place-esquirol', 11): Mean: 22.976512, ('place-esquirol', 12): Mean: 25.324159, ('place-esquirol', 13): Mean: 25.428847, ('place-esquirol', 14): Mean: 24.57762, ('place-esquirol', 15): Mean: 24.416851, ('place-esquirol', 16): Mean: 23.555125, ('place-esquirol', 17): Mean: 22.062564, ('place-esquirol', 18): Mean: 18.10623, ('place-esquirol', 19): Mean: 11.916638, ('place-esquirol', 20): Mean: 13.346362, ('place-esquirol', 21): Mean: 16.743318, ('place-esquirol', 22): Mean: 15.562088, ('place-esquirol', 23): Mean: 10.911134, ('place-jeanne-darc', 0): Mean: 6.541667, ('place-jeanne-darc', 1): Mean: 5.99892, ('place-jeanne-darc', 2): Mean: 5.598169, ('place-jeanne-darc', 3): Mean: 5.180556, ('place-jeanne-darc', 4): Mean: 4.779626, ('place-jeanne-darc', 5): Mean: 4.67063, ('place-jeanne-darc', 6): Mean: 4.611995, ('place-jeanne-darc', 7): Mean: 4.960718, ('place-jeanne-darc', 8): Mean: 5.552273, ('place-jeanne-darc', 9): Mean: 6.249573, ('place-jeanne-darc', 10): Mean: 5.735553, ('place-jeanne-darc', 11): Mean: 5.616142, ('place-jeanne-darc', 12): Mean: 5.787478, ('place-jeanne-darc', 13): Mean: 5.817699, ('place-jeanne-darc', 14): Mean: 5.657546, ('place-jeanne-darc', 15): Mean: 6.224604, ('place-jeanne-darc', 16): Mean: 5.796141, ('place-jeanne-darc', 17): Mean: 5.743089, ('place-jeanne-darc', 18): Mean: 5.674784, ('place-jeanne-darc', 19): Mean: 5.833068, ('place-jeanne-darc', 20): Mean: 6.015755, ('place-jeanne-darc', 21): Mean: 6.242541, ('place-jeanne-darc', 22): Mean: 6.141509, ('place-jeanne-darc', 23): Mean: 6.493028, ('pomme', 0): Mean: 3.301532, ('pomme', 1): Mean: 2.312914, ('pomme', 2): Mean: 2.144453, ('pomme', 3): Mean: 1.563622, ('pomme', 4): Mean: 0.947328, ('pomme', 5): Mean: 0.924175, ('pomme', 6): Mean: 1.287805, ('pomme', 7): Mean: 1.299456, ('pomme', 8): Mean: 2.94988, ('pomme', 9): Mean: 7.89396, ('pomme', 10): Mean: 11.791436, ('pomme', 11): Mean: 12.976854, ('pomme', 12): Mean: 13.962654, ('pomme', 13): Mean: 11.692257, ('pomme', 14): Mean: 11.180851, ('pomme', 15): Mean: 11.939586, ('pomme', 16): Mean: 12.267051, ('pomme', 17): Mean: 12.132993, ('pomme', 18): Mean: 11.399108, ('pomme', 19): Mean: 6.37021, ('pomme', 20): Mean: 5.279234, ('pomme', 21): Mean: 6.254257, ('pomme', 22): Mean: 6.568678, ('pomme', 23): Mean: 5.235756}), 'by': ['station', 'hour'], 'how': Mean: 0., 'on': 'y'}
y_ewm_0.5_by_station
{'_feature_name': 'y_ewm_0.5_by_station', '_groups': defaultdict(functools.partial(<function deepcopy at 0x7f7d47503af0>, EWMean: 0.), {('metro-canal-du-midi',): EWMean: 4.690531, ('place-des-carmes',): EWMean: 3.295317, ('place-esquirol',): EWMean: 31.539759, ('place-jeanne-darc',): EWMean: 22.449934, ('pomme',): EWMean: 11.803716}), 'by': ['station'], 'how': EWMean: 0., 'on': 'y'}
StandardScaler
{'counts': Counter({'y_ewm_0.5_by_station': 182470, 'y_mean_by_station_and_hour': 182470, 'clouds': 182470, 'temperature': 182470, 'wind': 182470, 'pressure': 182470, 'humidity': 182470}), 'means': defaultdict(<class 'float'>, {'clouds': 30.315131254453505, 'humidity': 62.24244533347998, 'pressure': 1017.0563060996391, 'temperature': 20.50980692716619, 'wind': 3.4184331122924543, 'y_ewm_0.5_by_station': 10.08331958752748, 'y_mean_by_station_and_hour': 9.410348580619415}), 'vars': defaultdict(<class 'float'>, {'clouds': 1389.0025610928221, 'humidity': 349.59967918503554, 'pressure': 33.298307526514115, 'temperature': 34.70701720774977, 'wind': 4.473627075744674, 'y_ewm_0.5_by_station': 80.17355266024735, 'y_mean_by_station_and_hour': 33.98249801051089}), 'with_std': True}
LinearRegression
{'_weights': {'y_ewm_0.5_by_station': 9.264175276315452, 'y_mean_by_station_and_hour': 0.19801400070497813, 'clouds': -0.3269694794458259, 'temperature': -0.42112178062191546, 'wind': -0.040879547751793026, 'pressure': 0.18137498909137337, 'humidity': 1.0125248437612906}, '_y_name': None, 'clip_gradient': 1000000000000.0, 'initializer': Zeros (), 'intercept': 9.223158690689178, 'intercept_init': 0.0, 'intercept_lr': Constant({'learning_rate': 0.01}), 'l1': 0.0, 'l2': 0.0, 'loss': Squared({}), 'optimizer': SGD({'lr': Constant({'learning_rate': 0.01}), 'n_iterations': 182470})}

As mentioned above the Pipeline class has a debug_one method. You can use this at any point you want to visualize what happen to an input x. For example, let's see what happens to the last seen x.

print(model.debug_one(x))
0. Input
--------
clouds: 88 (int)
description: overcast clouds (str)
humidity: 84 (int)
moment: 2016-10-05 09:57:18 (datetime)
pressure: 1,017.34000 (float)
station: pomme (str)
temperature: 17.45000 (float)
wind: 1.95000 (float)

1. add_time_features
--------------------
clouds: 88 (int)
day: 2 (int)
description: overcast clouds (str)
hour: 9 (int)
humidity: 84 (int)
moment: 2016-10-05 09:57:18 (datetime)
pressure: 1,017.34000 (float)
station: pomme (str)
temperature: 17.45000 (float)
wind: 1.95000 (float)

2. Transformer union
--------------------
    2.0 Select
    ----------
    clouds: 88 (int)
    humidity: 84 (int)
    pressure: 1,017.34000 (float)
    temperature: 17.45000 (float)
    wind: 1.95000 (float)

    2.1 TargetAgg
    -------------
    y_mean_by_station_and_hour: 7.89396 (float)

    2.2 TargetAgg1
    --------------
    y_ewm_0.5_by_station: 11.80372 (float)

clouds: 88 (int)
humidity: 84 (int)
pressure: 1,017.34000 (float)
temperature: 17.45000 (float)
wind: 1.95000 (float)
y_ewm_0.5_by_station: 11.80372 (float)
y_mean_by_station_and_hour: 7.89396 (float)

3. StandardScaler
-----------------
clouds: 1.54778 (float)
humidity: 1.16366 (float)
pressure: 0.04916 (float)
temperature: -0.51938 (float)
wind: -0.69426 (float)
y_ewm_0.5_by_station: 0.19214 (float)
y_mean_by_station_and_hour: -0.26013 (float)

4. LinearRegression
-------------------
Name                         Value      Weight     Contribution  
                 Intercept    1.00000    9.22316        9.22316  
      y_ewm_0.5_by_station    0.19214    9.26418        1.78000  
                  humidity    1.16366    1.01252        1.17823  
               temperature   -0.51938   -0.42112        0.21872  
                      wind   -0.69426   -0.04088        0.02838  
                  pressure    0.04916    0.18137        0.00892  
y_mean_by_station_and_hour   -0.26013    0.19801       -0.05151  
                    clouds    1.54778   -0.32697       -0.50608

Prediction: 11.87982

The pipeline does quite a few things, but using debug_one shows what happens step by step. This is really useful for checking that the pipeline is behaving as you're expecting it too. Remember that you can debug_one whenever you wish, be it before, during, or after training a model.