Least Core for Data Valuation ¶
This notebook introduces Least Core methods for the computation of data values using pyDVL.
Shapley values define a fair way of distributing the worth of the whole training set when every data point is part of it. But they do not consider the question of stability of subsets: Could some data points obtain a higher payoff if they formed smaller subsets? It is argued that this might be relevant if data providers are paid based on data value, since Shapley values can incentivise them not to contribute their data to the "grand coalition", but instead try to form smaller ones. Whether this is of actual practical relevance is debatable, but in any case, the least core is an alternative tool available for any task of Data Valuation
The Core is another approach to compute data values originating in cooperative game theory that attempts to answer those questions. It is the set of feasible payoffs that cannot be improved upon by a coalition of the participants.
Its use for Data Valuation was first described in the paper If You Like Shapley Then You’ll Love the Core by Tom Yan and Ariel D. Procaccia.
The Least Core value \(v\) of the \(i\) -th sample in dataset \(D\) wrt. utility \(u\) is computed by solving the following Linear Program:
To illustrate this method we will use a synthetic dataset. We will first use a subset of 10 data point to compute the exact values and use them to assess the Monte Carlo approximation. Afterwards, we will conduct the data removal experiments as described by Ghorbani and Zou in their paper Data Shapley: Equitable Valuation of Data for Machine Learning : We compute the data valuation given different computation budgets and incrementally remove a percentage of the best, respectively worst, data points and observe how that affects the utility.
Setup ¶
We begin by importing the main libraries and setting some defaults.
We will be using the following functions and classes from pyDVL.
Dataset ¶
We generate a synthetic dataset using the
make_classification
function from scikit-learn.
We sample 200 data points from a 50-dimensional Gaussian distribution with 25 informative features and 25 non-informative features (generated as random linear combinations of the informative features).
The 200 samples are uniformly distributed across 3 classes with a small percentage of noise added to the labels to make the task a bit more difficult.
Estimating Least Core Values ¶
In this first section we use a smaller subset of the dataset containing 12 samples in order to be able to compute exact values reasonably fast. Recall that, in order to assemble the problem, for every subset \(S \subset D\) we must compute the utility \(u(S).\) We then have a linear problem with \(2^{|D|}\) constraints to solve. After doing this, we use the Monte Carlo method with a limited budget (maximum number of constraints) to approximate the LC values on the same reduced dataset, and we repeat this several times to assess the stability of the approximation.
def relative_error(x, estimate, norm):
return np.linalg.norm(x - estimate, ord=norm) / np.linalg.norm(x, ord=norm)
budget_array = np.logspace(8, len(train_mini), base=2, num=8, endpoint=False, dtype=int)
all_estimated_values_df = []
all_errors = {budget: [] for budget in budget_array}
with tqdm(total=len(budget_array) * n_runs) as pbar:
for budget in budget_array:
pbar.set_description(f"Computing values with {budget} constraints")
dfs = []
errors = []
column_name = f"estimated_{budget}"
valuation = MonteCarloLeastCoreValuation(
utility=utility, n_samples=budget, progress=False
)
for i in range(n_runs):
with parallel_config(n_jobs=n_jobs):
valuation.fit(train_mini)
df = (
valuation.values()
.to_dataframe(column=column_name)
.drop(columns=[f"{column_name}_variances", f"{column_name}_counts"])
.T
)
df = df[sorted(df.columns)]
error = relative_error(
exact_df.loc["exact"].values, np.nan_to_num(df.values.ravel()), norm=1
)
all_errors[budget].append(error)
df["budget"] = budget
dfs.append(df)
pbar.update(1)
estimated_values_df = pd.concat(dfs)
all_estimated_values_df.append(estimated_values_df)
values_df = pd.concat(all_estimated_values_df)
errors_df = pd.DataFrame(all_errors)
We can see that the approximation error decreases as the number of constraints increases, but that there are diminishing returns for increasing the budget beyond a certain point.
Data Removal ¶
In the final two experiments, we rank the training set according to the value estimates obtained with Monte Carlo Least Core. Then, we incrementally remove up to 40% of the most / least valuable training points, train the model on this subset and compute its accuracy on the previously held-out test set.
Remove Best ¶
We start by removing the best data points and seeing how the model's accuracy evolves. We repeat the whole process (valuation and removal) several times to assess the stability of the results.
from pydvl.valuation.methods.random import RandomValuation
removal_percentages = np.arange(0, 0.41, 0.05)
methods = [
RandomValuation(random_state=seed),
MonteCarloLeastCoreValuation(
utility=utility, n_samples=n_iterations, progress=False, seed=seed
),
]
all_scores = []
for i in trange(n_runs, position=0, desc=f"Removing best points, {n_runs} times"):
for method in methods:
with parallel_config(n_jobs=n_jobs):
valuation.fit(train)
result = valuation.values()
scores = compute_removal_score(
utility,
result,
train,
removal_percentages,
remove_best=True,
progress=False,
)
scores["method_name"] = method.__class__.__name__
all_scores.append(scores)
scores_df = pd.DataFrame(all_scores)
We can clearly see that removing the most valuable data points, as given by the Least Core method, leads to, on average, a decrease in the model's performance and that the method outperforms random removal of data points.
Remove Worst ¶
We then proceed to removing the worst data points and seeing how the model's accuracy evolves.
all_scores = []
for i in trange(n_runs, position=0, desc=f"Removing best points, {n_runs} times"):
for method in methods:
with parallel_config(n_jobs=n_jobs):
valuation.fit(train)
result = valuation.values()
scores = compute_removal_score(
utility,
result,
train,
removal_percentages,
remove_best=False,
progress=False,
)
scores["method_name"] = method.__class__.__name__
all_scores.append(scores)
scores_df = pd.DataFrame(all_scores)
We can clearly see that removing the least valuable data points, as given by the Least Core method, leads to, on average, an increase in the model's performance and that the method outperforms the random removal of data points.