Partition Data using Dirichlet Distribution

Dirichlet partitioning is a common technique to split labelled data into heterogeneous partitions with a configurable degree of class-imbalance (i.e., heterogeneity). Since a 5 minute web search did not lead me to a readily implemented solution, this guide provides tensorflow implementation of Dirichlet partitioning with a short explanation of important code chunks.

Note that this implementation is based on FedDyn. Our code is also available at GitHub.

Setup

We start by installing the required libraries via pip:

import numpy as np
import pandas as pd
import queue
import tensorflow as tf

Suppose that the following variables are the inputs to our partitioning function, thus they are already defined:

  • data: The data as type `tf.data.Dataset’ containing features and labels. See Tensorflow Tutorial: Load Custom Image Dataset for an example.
  • n_partitions: The desired number of resulting partitions.
  • n_classes: The number of unique classes/labels in the dataset.
  • alpha: The parameter for the Dirichlet distribution. Controls the degree of heterogeneity.
  • seed: A seed for reproducibility.

First, we set the seed and derive some helper variables:

np.random.seed(seed)
n_rows = data.cardinality().numpy()
def partitionDataDirichlet(data, n_partitions, n_classes, alpha, seed):
    np.random.seed(seed)
    iter_data = data.as_numpy_iterator()
    class_data_indices = dict()
    class_lookup_dict = dict()
    newclass_index = 0
    for idx, elem in enumerate(iter_data):
        response = elem[1]
        if(not class_lookup_dict or
            not np.any(np.all(response == np.array(list(class_lookup_dict.values())), axis=1))):
            class_lookup_dict[newclass_index] = response
            class_data_indices[newclass_index] = queue.Queue()
            newclass_index += 1
        class_data_indices[next(filter(lambda key: np.all(class_lookup_dict[key] == response),
            class_lookup_dict))].put(idx)

    n_rows = data.cardinality().numpy()
    distribute_remainder = lambda idx: 1 if idx < (n_rows % n_partitions) else 0
    # balanced cardinality
    partition_num_elements = {p_idx: (n_rows // n_partitions) + distribute_remainder(p_idx)
        for p_idx in range(n_partitions)}

    # draw the class priors for all partitions from a dirichlet distribution
    class_priors = np.random.dirichlet(alpha=[alpha]*n_classes, size=n_partitions)
    partition_assigned_classes = dict()
    partition_assigned_indices = dict()
    for _ in range(n_rows):
        # randomly select the partition to which to add an element
        current_partition = np.random.choice([p_idx for p_idx, n_elements
            in partition_num_elements.items() if n_elements > 0])
        current_priors = [class_priors[current_partition][c_idx] * (not class_data_indices[c_idx].empty())
                for c_idx in class_data_indices.keys()]
        current_priors = np.array(current_priors) / np.sum(current_priors)
        assign_class = np.random.choice(list(class_data_indices.keys()),
            p=current_priors)
        partition_assigned_classes[current_partition][assign_class] = partition_assigned_classes.setdefault(
            current_partition, dict()).setdefault(assign_class, 0) + 1
        partition_assigned_indices.setdefault(current_partition, list()).append(
            class_data_indices[assign_class].get())
    partition_assigned_indices = {p_idx: partition_assigned_indices[p_idx] for p_idx in range(n_partitions)}

    partitions = list()
    for p_idx, pai in partition_assigned_indices.items():
        part_x, part_y = zip(*list(map(list(data).__getitem__, pai)))
        part_x = np.array(part_x)
        part_y = np.array(part_y)
        part_x = tf.data.Dataset.from_tensor_slices(part_x)
        part_y = tf.data.Dataset.from_tensor_slices(part_y)
        partitions.append(tf.data.Dataset.zip((part_x, part_y)))

    print(f'Partitioned data into {n_partitions} partitions with ' +
        f'{list(partition_num_elements.values())} respectively with the following ' +
        "distribution of target labels:\n" +
        pd.DataFrame.from_dict(partition_assigned_classes).sort_index().to_string())

    return partitions

Unfinished.




Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • Tensorflow Federated Tutorial
  • Gradient Evolution
  • Tensorflow Tutorial: Load Custom Image Dataset
  • Tensorflow Tutorial: Train a Keras Model
  • Data Spaces