Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0
This notebook shows use of the What-If Tool on a regression model.
This notebook trains a linear regressor to predict a person's age from their census data using the UCI census data.
It then visualizes the results of the trained regressor on test data using the What-If Tool.
In [0]:
#@title Install the What-If Tool widget if running in colab {display-mode: "form"}
try:
import google.colab
!pip install --upgrade witwidget
except:
pass
In [0]:
#@title Define helper functions {display-mode: "form"}
import pandas as pd
import numpy as np
import tensorflow as tf
import functools
# Creates a tf feature spec from the dataframe and columns specified.
def create_feature_spec(df, columns=None):
feature_spec = {}
if columns == None:
columns = df.columns.values.tolist()
for f in columns:
if df[f].dtype is np.dtype(np.int64) or df[f].dtype is np.dtype(np.int32):
feature_spec[f] = tf.io.FixedLenFeature(shape=(), dtype=tf.int64)
elif df[f].dtype is np.dtype(np.float64):
feature_spec[f] = tf.io.FixedLenFeature(shape=(), dtype=tf.float32)
else:
feature_spec[f] = tf.io.FixedLenFeature(shape=(), dtype=tf.string)
return feature_spec
# Creates simple numeric and categorical feature columns from a feature spec and a
# list of columns from that spec to use.
#
# NOTE: Models might perform better with some feature engineering such as bucketed
# numeric columns and hash-bucket/embedding columns for categorical features.
def create_feature_columns(columns, feature_spec):
ret = []
for col in columns:
if feature_spec[col].dtype is tf.int64 or feature_spec[col].dtype is tf.float32:
ret.append(tf.feature_column.numeric_column(col))
else:
ret.append(tf.feature_column.indicator_column(
tf.feature_column.categorical_column_with_vocabulary_list(col, list(df[col].unique()))))
return ret
# An input function for providing input to a model from tf.Examples
def tfexamples_input_fn(examples, feature_spec, label, mode=tf.estimator.ModeKeys.EVAL,
num_epochs=None,
batch_size=64):
def ex_generator():
for i in range(len(examples)):
yield examples[i].SerializeToString()
dataset = tf.data.Dataset.from_generator(
ex_generator, tf.dtypes.string, tf.TensorShape([]))
if mode == tf.estimator.ModeKeys.TRAIN:
dataset = dataset.shuffle(buffer_size=2 * batch_size + 1)
dataset = dataset.batch(batch_size)
dataset = dataset.map(lambda tf_example: parse_tf_example(tf_example, label, feature_spec))
dataset = dataset.repeat(num_epochs)
return dataset
# Parses Tf.Example protos into features for the input function.
def parse_tf_example(example_proto, label, feature_spec):
parsed_features = tf.io.parse_example(serialized=example_proto, features=feature_spec)
target = parsed_features.pop(label)
return parsed_features, target
# Converts a dataframe into a list of tf.Example protos.
def df_to_examples(df, columns=None):
examples = []
if columns == None:
columns = df.columns.values.tolist()
for index, row in df.iterrows():
example = tf.train.Example()
for col in columns:
if df[col].dtype is np.dtype(np.int64) or df[col].dtype is np.dtype(np.int32):
example.features.feature[col].int64_list.value.append(int(row[col]))
elif df[col].dtype is np.dtype(np.float64):
example.features.feature[col].float_list.value.append(row[col])
elif row[col] == row[col]:
example.features.feature[col].bytes_list.value.append(row[col].encode('utf-8'))
examples.append(example)
return examples
# Converts a dataframe column into a column of 0's and 1's based on the provided test.
# Used to force label columns to be numeric for binary classification using a TF estimator.
def make_label_column_numeric(df, label_column, test):
df[label_column] = np.where(test(df[label_column]), 1, 0)
In [0]:
#@title Read training dataset from CSV {display-mode: "form"}
import pandas as pd
# Set the path to the CSV containing the dataset to train on.
csv_path = 'https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data'
# Set the column names for the columns in the CSV. If the CSV's first line is a header line containing
# the column names, then set this to None.
csv_columns = [
"Age", "Workclass", "fnlwgt", "Education", "Education-Num", "Marital-Status",
"Occupation", "Relationship", "Race", "Sex", "Capital-Gain", "Capital-Loss",
"Hours-per-week", "Country", "Over-50K"]
# Read the dataset from the provided CSV and print out information about it.
df = pd.read_csv(csv_path, names=csv_columns, skipinitialspace=True)
df
In [0]:
#@title Specify input columns and column to predict {display-mode: "form"}
import numpy as np
# Set the column in the dataset you wish for the model to predict
label_column = 'Age'
# Set list of all columns from the dataset we will use for model input.
input_features = [
'Over-50K', 'Workclass', 'Education', 'Marital-Status', 'Occupation',
'Relationship', 'Race', 'Sex', 'Capital-Gain', 'Capital-Loss',
'Hours-per-week', 'Country']
# Create a list containing all input features and the label column
features_and_labels = input_features + [label_column]
In [0]:
#@title Convert dataset to tf.Example protos {display-mode: "form"}
examples = df_to_examples(df)
In [0]:
#@title Create and train the regressor {display-mode: "form"}
num_steps = 200 #@param {type: "number"}
# Create a feature spec for the classifier
feature_spec = create_feature_spec(df, features_and_labels)
# Define and train the classifier
train_inpf = functools.partial(tfexamples_input_fn, examples, feature_spec, label_column)
regressor = tf.estimator.LinearRegressor(
feature_columns=create_feature_columns(input_features, feature_spec))
regressor.train(train_inpf, steps=num_steps)
In [0]:
#@title Invoke What-If Tool for test data and the trained model {display-mode: "form"}
num_datapoints = 2000 #@param {type: "number"}
tool_height_in_px = 1000 #@param {type: "number"}
from witwidget.notebook.visualization import WitConfigBuilder
from witwidget.notebook.visualization import WitWidget
# Load up the test dataset
test_csv_path = 'https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.test'
test_df = pd.read_csv(test_csv_path, names=csv_columns, skipinitialspace=True,
skiprows=1)
test_examples = df_to_examples(test_df[0:num_datapoints])
# Setup the tool with the test examples and the trained classifier
config_builder = WitConfigBuilder(test_examples[0:num_datapoints]).set_estimator_and_feature_spec(
regressor, feature_spec).set_model_type('regression')
WitWidget(config_builder, height=tool_height_in_px)
Create a scatter plot of age on the X-axis by inference value on the Y-axis to see how the predicted age compares to the actual age for all test examples.
In the "Performance" tab, set the ground truth feature to "age". You can now see a simple breakdown of the mean error, mean absolute error, and mean squared error over the entire test set. Additionally, you can set a scatter axis or binning option to be any of those three calculations (i.e. you can make a scatter plot of age vs inference error).
Replace the LinearRegressor with a DNNRegressor (you'll need to decide on the size and number of the hidden layers). Can you create a better model? Does this model have the same pattern of performance issues as the previous model, or are there errors more evenly distributed?
In the "Performance" tab, slice the dataset by any feature or pair of features. Are there feature values for which the model seems to perform better or worse on?