Skip to content

memory

MemoryMechanism

Implements the memory handling/manipulation for continual learning algorithms.

Source code in sequel/benchmarks/memory.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
class MemoryMechanism:
    """Implements the memory handling/manipulation for continual learning algorithms."""

    def __init__(self, per_task_memory_samples: int, groupby: str = "class"):
        logging.info("Initializing MemoryCallback")
        self.per_task_memory_samples = per_task_memory_samples

        if groupby not in ("task", "class"):
            raise ValueError("Only class and task are supported as options for groupby argument.")

        self.groupby = groupby

    def update_memory(self, algo: "BaseAlgorithm"):
        """Updates the memory by selecting `per_task_memory_samples` samples from the current dataset. The selection
        process is defined by the `groupby` instance attribute.

        Args:
            algo (BaseAlgorithm): the algorithm instance.
        """
        logging.info("Setting memory indices for task {}".format(algo.task_counter))
        task = algo.task_counter
        dataset = algo.benchmark.get_train_dataset(task)
        if self.groupby == "class":
            memory_indices = MemoryMechanism.sample_uniform_class_indices(dataset, self.per_task_memory_samples)
        else:
            memory_indices = MemoryMechanism.sample_uniform_task_indices(dataset, self.per_task_memory_samples)
        algo.benchmark.set_memory_indices(task, memory_indices)

    def update_memory_(self, benchmark, task):
        """Updates the memory by selecting `per_task_memory_samples` samples from the current dataset. The selection
        process is defined by the `groupby` instance attribute.
        """
        logging.info("Setting memory indices for task {}".format(task))
        dataset = benchmark.get_train_dataset(task)
        if self.groupby == "class":
            memory_indices = MemoryMechanism.sample_uniform_class_indices(dataset, self.per_task_memory_samples)
        else:
            memory_indices = MemoryMechanism.sample_uniform_task_indices(dataset, self.per_task_memory_samples)
        benchmark.set_memory_indices(task, memory_indices)

    @staticmethod
    def sample_uniform_task_indices(dataset: ContinualDataset, num_samples: int) -> List[int]:
        """Selects a specified number of indices uniformly at random.

        Args:
            dataset (ContinualDataset): The dataset that is sampled.
            num_samples (int): the number of samples to draw.

        Returns:
            List[int]: The selected dataset indices.
        """
        to_remove = len(dataset) - num_samples
        dataset, _ = random_split(dataset, [num_samples, to_remove])
        return dataset.indices

    @staticmethod
    def sample_uniform_class_indices(dataset: ContinualDataset, num_samples: int) -> List[int]:
        """Selects an approximately equal (ties broken arbitrarily) number of indices corresponding to each class from
        the input dataset. Each dataset yields ~num_samples // num_classes samples.

        Args:
            dataset (ContinualDataset): The dataset that is sampled.
            num_samples (int): the number of samples to draw.

        Returns:
            List[int]: The selected dataset indices.
        """
        target_classes = dataset.targets.clone().detach().numpy()
        classes = np.unique(target_classes).tolist()
        num_classes = len(classes)
        num_examples_per_class = MemoryMechanism.pack_bins_uniformly(num_samples * num_classes, num_classes)
        class_indices = []

        for class_id, cls_number in enumerate(classes):
            candidates = np.array([i for i, t in enumerate(target_classes) if t == cls_number])
            np.random.shuffle(candidates)

            selected_indices = candidates[: num_examples_per_class[class_id]]
            class_indices += list(selected_indices)
        return class_indices

    @staticmethod
    def pack_bins_uniformly(num_samples: int, num_categories: int) -> List[int]:
        """Splits an integer to a specified number of bins so that bins have approximately the same size. If
        `num_categories` is not a divisor of `num_samples`, the reminder is split an equal number of bins selectrd
        uniformly at random.

        Args:
            num_samples (int): The number of items.
            num_categories (int): the number of bins.

        Returns:
            List[int]: a list containing the number of items corresponding to each bin.
        """
        num_samples_per_cat = np.ones(num_categories) * num_samples // num_categories
        remaining = num_samples % num_categories
        correction_vector = np.array([0] * (num_categories - remaining) + [1] * remaining)
        np.random.shuffle(correction_vector)
        num_samples_per_cat += correction_vector
        return num_samples_per_cat.astype("int").tolist()

pack_bins_uniformly(num_samples, num_categories) staticmethod

Splits an integer to a specified number of bins so that bins have approximately the same size. If num_categories is not a divisor of num_samples, the reminder is split an equal number of bins selectrd uniformly at random.

Parameters:

Name Type Description Default
num_samples int

The number of items.

required
num_categories int

the number of bins.

required

Returns:

Type Description
List[int]

List[int]: a list containing the number of items corresponding to each bin.

Source code in sequel/benchmarks/memory.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
@staticmethod
def pack_bins_uniformly(num_samples: int, num_categories: int) -> List[int]:
    """Splits an integer to a specified number of bins so that bins have approximately the same size. If
    `num_categories` is not a divisor of `num_samples`, the reminder is split an equal number of bins selectrd
    uniformly at random.

    Args:
        num_samples (int): The number of items.
        num_categories (int): the number of bins.

    Returns:
        List[int]: a list containing the number of items corresponding to each bin.
    """
    num_samples_per_cat = np.ones(num_categories) * num_samples // num_categories
    remaining = num_samples % num_categories
    correction_vector = np.array([0] * (num_categories - remaining) + [1] * remaining)
    np.random.shuffle(correction_vector)
    num_samples_per_cat += correction_vector
    return num_samples_per_cat.astype("int").tolist()

sample_uniform_class_indices(dataset, num_samples) staticmethod

Selects an approximately equal (ties broken arbitrarily) number of indices corresponding to each class from the input dataset. Each dataset yields ~num_samples // num_classes samples.

Parameters:

Name Type Description Default
dataset ContinualDataset

The dataset that is sampled.

required
num_samples int

the number of samples to draw.

required

Returns:

Type Description
List[int]

List[int]: The selected dataset indices.

Source code in sequel/benchmarks/memory.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
@staticmethod
def sample_uniform_class_indices(dataset: ContinualDataset, num_samples: int) -> List[int]:
    """Selects an approximately equal (ties broken arbitrarily) number of indices corresponding to each class from
    the input dataset. Each dataset yields ~num_samples // num_classes samples.

    Args:
        dataset (ContinualDataset): The dataset that is sampled.
        num_samples (int): the number of samples to draw.

    Returns:
        List[int]: The selected dataset indices.
    """
    target_classes = dataset.targets.clone().detach().numpy()
    classes = np.unique(target_classes).tolist()
    num_classes = len(classes)
    num_examples_per_class = MemoryMechanism.pack_bins_uniformly(num_samples * num_classes, num_classes)
    class_indices = []

    for class_id, cls_number in enumerate(classes):
        candidates = np.array([i for i, t in enumerate(target_classes) if t == cls_number])
        np.random.shuffle(candidates)

        selected_indices = candidates[: num_examples_per_class[class_id]]
        class_indices += list(selected_indices)
    return class_indices

sample_uniform_task_indices(dataset, num_samples) staticmethod

Selects a specified number of indices uniformly at random.

Parameters:

Name Type Description Default
dataset ContinualDataset

The dataset that is sampled.

required
num_samples int

the number of samples to draw.

required

Returns:

Type Description
List[int]

List[int]: The selected dataset indices.

Source code in sequel/benchmarks/memory.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@staticmethod
def sample_uniform_task_indices(dataset: ContinualDataset, num_samples: int) -> List[int]:
    """Selects a specified number of indices uniformly at random.

    Args:
        dataset (ContinualDataset): The dataset that is sampled.
        num_samples (int): the number of samples to draw.

    Returns:
        List[int]: The selected dataset indices.
    """
    to_remove = len(dataset) - num_samples
    dataset, _ = random_split(dataset, [num_samples, to_remove])
    return dataset.indices

update_memory(algo)

Updates the memory by selecting per_task_memory_samples samples from the current dataset. The selection process is defined by the groupby instance attribute.

Parameters:

Name Type Description Default
algo BaseAlgorithm

the algorithm instance.

required
Source code in sequel/benchmarks/memory.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def update_memory(self, algo: "BaseAlgorithm"):
    """Updates the memory by selecting `per_task_memory_samples` samples from the current dataset. The selection
    process is defined by the `groupby` instance attribute.

    Args:
        algo (BaseAlgorithm): the algorithm instance.
    """
    logging.info("Setting memory indices for task {}".format(algo.task_counter))
    task = algo.task_counter
    dataset = algo.benchmark.get_train_dataset(task)
    if self.groupby == "class":
        memory_indices = MemoryMechanism.sample_uniform_class_indices(dataset, self.per_task_memory_samples)
    else:
        memory_indices = MemoryMechanism.sample_uniform_task_indices(dataset, self.per_task_memory_samples)
    algo.benchmark.set_memory_indices(task, memory_indices)

update_memory_(benchmark, task)

Updates the memory by selecting per_task_memory_samples samples from the current dataset. The selection process is defined by the groupby instance attribute.

Source code in sequel/benchmarks/memory.py
41
42
43
44
45
46
47
48
49
50
51
def update_memory_(self, benchmark, task):
    """Updates the memory by selecting `per_task_memory_samples` samples from the current dataset. The selection
    process is defined by the `groupby` instance attribute.
    """
    logging.info("Setting memory indices for task {}".format(task))
    dataset = benchmark.get_train_dataset(task)
    if self.groupby == "class":
        memory_indices = MemoryMechanism.sample_uniform_class_indices(dataset, self.per_task_memory_samples)
    else:
        memory_indices = MemoryMechanism.sample_uniform_task_indices(dataset, self.per_task_memory_samples)
    benchmark.set_memory_indices(task, memory_indices)