Files
wehub-resource-sync dc6079821b
Docs Tests / Check for file changes (push) Has been cancelled
Docs Tests / Test Documentation (push) Has been cancelled
Docs Tests / Documentation Linting Checks (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-policies) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.10) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.8) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.9) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.10) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.8) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.9) (push) Has been cancelled
Continuous Integration / Check for file changes (push) Has been cancelled
Continuous Integration / Wait for docs tests (push) Has been cancelled
Continuous Integration / Code Quality (push) Has been cancelled
Continuous Integration / Check for changelog (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Upload coverage reports to codeclimate (push) Has been cancelled
Continuous Integration / Run Non-Sequential Integration Tests (push) Has been cancelled
Continuous Integration / Run Broker Integration Tests (push) Has been cancelled
Continuous Integration / Run Sequential Integration Tests (push) Has been cancelled
Continuous Integration / Build Docker base images and setup environment (push) Has been cancelled
Continuous Integration / Build Docker (default) (push) Has been cancelled
Continuous Integration / Build Docker (full) (push) Has been cancelled
Continuous Integration / Build Docker (mitie-en) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-de) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-en) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-it) (push) Has been cancelled
Continuous Integration / Deploy to PyPI (push) Has been cancelled
Continuous Integration / Notify Slack & Publish Release Notes (push) Has been cancelled
Publish Documentation / Evaluate release tag (push) Has been cancelled
Publish Documentation / Prebuild Docs (push) Has been cancelled
Publish Documentation / Preview Docs (push) Has been cancelled
Publish Documentation / Check for file changes (push) Has been cancelled
Publish Documentation / Publish Docs (push) Has been cancelled
Automatic PR Merger / mergepal (push) Has been cancelled
CI Github Actions / Run Tests (push) Has been cancelled
Semgrep / Semgrep Workflow Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:47 +08:00

441 lines
16 KiB
Python

import math
from typing import List, Union, Text, Optional, Any, Tuple, Dict, cast
import logging
import scipy.sparse
import numpy as np
from tensorflow.keras.utils import Sequence
from rasa.utils.tensorflow.constants import SEQUENCE, BALANCED
from rasa.utils.tensorflow.model_data import RasaModelData, Data, FeatureArray
logger = logging.getLogger(__name__)
class RasaDataGenerator(Sequence):
"""Abstract data generator."""
def __init__(
self,
model_data: RasaModelData,
batch_size: Union[int, List[int]],
batch_strategy: Text = SEQUENCE,
shuffle: bool = True,
):
"""Initializes the data generator.
Args:
model_data: The model data to use.
batch_size: The batch size(s).
batch_strategy: The batch strategy.
shuffle: If 'True', data should be shuffled.
"""
self.model_data = model_data
self.batch_size = batch_size
self.shuffle = shuffle
self.batch_strategy = batch_strategy
def __len__(self) -> int:
"""Number of batches in the Sequence.
Returns:
The number of batches in the Sequence.
"""
raise NotImplementedError
def __getitem__(self, index: int) -> Tuple[Any, Any]:
"""Gets batch at position `index`.
Arguments:
index: position of the batch in the Sequence.
Returns:
A batch (tuple of input data and target data).
"""
raise NotImplementedError
def on_epoch_end(self) -> None:
"""Update the data after every epoch."""
raise NotImplementedError
def _shuffle_and_balance(self, batch_size: int) -> Data:
data = self.model_data.data
if self.shuffle:
data = self.model_data.shuffled_data(data)
if self.batch_strategy == BALANCED:
data = self.model_data.balanced_data(data, batch_size, self.shuffle)
# do not override self.model_data.data, because we need original data for
# balancing on the next epoch
return data
@staticmethod
def prepare_batch(
data: Data,
start: Optional[int] = None,
end: Optional[int] = None,
tuple_sizes: Optional[Dict[Text, int]] = None,
) -> Tuple[Optional[np.ndarray], ...]:
"""Slices model data into batch using given start and end value.
Args:
data: The data to prepare.
start: The start index of the batch
end: The end index of the batch
tuple_sizes: In case the feature is not present we propagate the batch with
None. Tuple sizes contains the number of how many None values to add for
what kind of feature.
Returns:
The features of the batch.
"""
batch_data = []
for key, attribute_data in data.items():
for sub_key, f_data in attribute_data.items():
# add None for not present values during processing
if not f_data:
if tuple_sizes:
batch_data += [None] * tuple_sizes[key]
else:
batch_data.append(None)
continue
for v in f_data:
if start is not None and end is not None:
_data = v[start:end]
elif start is not None:
_data = v[start:]
elif end is not None:
_data = v[:end]
else:
_data = v[:]
if cast(FeatureArray, _data).is_sparse:
batch_data.extend(
RasaDataGenerator._scipy_matrix_to_values(_data)
)
else:
batch_data.append(RasaDataGenerator._pad_dense_data(_data))
# len of batch_data is equal to the number of keys in model data
return tuple(batch_data)
@staticmethod
def _pad_dense_data(array_of_dense: FeatureArray) -> np.ndarray:
"""Pad data of different lengths.
Sequential data is padded with zeros. Zeros are added to the end of data.
Args:
array_of_dense: The array to pad.
Returns:
The padded array.
"""
if array_of_dense.number_of_dimensions == 4:
return RasaDataGenerator._pad_4d_dense_data(array_of_dense)
if array_of_dense[0].ndim < 2:
# data doesn't contain a sequence
return array_of_dense.astype(np.float32)
data_size = len(array_of_dense)
max_seq_len = max([x.shape[0] for x in array_of_dense])
data_padded = np.zeros(
[data_size, max_seq_len, array_of_dense[0].shape[-1]],
dtype=array_of_dense[0].dtype,
)
for i in range(data_size):
data_padded[i, : array_of_dense[i].shape[0], :] = array_of_dense[i]
return data_padded.astype(np.float32)
@staticmethod
def _pad_4d_dense_data(feature_array: FeatureArray) -> np.ndarray:
# in case of dialogue data we may have 4 dimensions
# batch size x dialogue history length x sequence length x number of features
# as transformers cannot handle 4D tensors pad and reshape the data
# so that the resulting tensor is 3D
# the shape is (sum of dialogue history length for all tensors in the
# batch x max sequence length x number of features)
# the original shape and the original dialogue length is passed on to the model
# it can be used to transform the 3D tensor back into 4D
# in order to create 4d tensor inputs, we created "fake" zero features
# for nonexistent inputs. To save calculation we filter this features before
# input to tf methods.
number_of_features = feature_array[0][0].shape[-1]
array_of_array_of_dense = RasaDataGenerator._filter_out_fake_inputs(
feature_array
)
if not array_of_array_of_dense:
# return empty 3d array with appropriate last dims
return np.zeros((0, 0, number_of_features), dtype=np.float32)
combined_dialogue_len = sum(
len(array_of_dense) for array_of_dense in array_of_array_of_dense
)
max_seq_len = max(
[
x.shape[0]
for array_of_dense in array_of_array_of_dense
for x in array_of_dense
]
)
data_padded = np.zeros(
[combined_dialogue_len, max_seq_len, number_of_features],
dtype=array_of_array_of_dense[0][0].dtype,
)
current_sum_dialogue_len = 0
for i, array_of_dense in enumerate(array_of_array_of_dense):
for j, dense in enumerate(array_of_dense):
data_padded[current_sum_dialogue_len + j, : dense.shape[0], :] = dense
current_sum_dialogue_len += len(array_of_dense)
return data_padded.astype(np.float32)
@staticmethod
def _scipy_matrix_to_values(array_of_sparse: FeatureArray) -> List[np.ndarray]:
"""Convert a scipy matrix into indices, data, and shape.
Args:
array_of_sparse: The sparse data array.
Returns:
A list of dense numpy arrays representing the sparse data.
"""
if array_of_sparse.number_of_dimensions == 4:
return RasaDataGenerator._4d_scipy_matrix_to_values(array_of_sparse)
# we need to make sure that the matrices are coo_matrices otherwise the
# transformation does not work (e.g. you cannot access x.row, x.col)
if not isinstance(array_of_sparse[0], scipy.sparse.coo_matrix):
array_of_sparse = [x.tocoo() for x in array_of_sparse] # type: ignore[assignment] # noqa: E501
max_seq_len = max([x.shape[0] for x in array_of_sparse])
# get the indices of values
indices = np.hstack(
[
np.vstack([i * np.ones_like(x.row), x.row, x.col])
for i, x in enumerate(array_of_sparse)
]
).T
data = np.hstack([x.data for x in array_of_sparse])
number_of_features = array_of_sparse[0].shape[-1]
shape = np.array((len(array_of_sparse), max_seq_len, number_of_features))
return [
indices.astype(np.int64),
data.astype(np.float32),
shape.astype(np.int64),
]
@staticmethod
def _4d_scipy_matrix_to_values(feature_array: FeatureArray) -> List[np.ndarray]:
# in case of dialogue data we may have 4 dimensions
# batch size x dialogue history length x sequence length x number of features
# transformers cannot handle 4D tensors, therefore pad and reshape the data
# so that the resulting tensor is 3D
# the shape is (sum of dialogue history length for all tensors in the
# batch x max sequence length x number of features)
# the original shape and the original dialogue length is passed on to the model
# it can be used to transform the 3D tensor back into 4D
# in order to create 4d tensor inputs, we created "fake" zero features
# for nonexistent inputs. To save calculation we filter this features before
# input to tf methods.
number_of_features = feature_array[0][0].shape[-1]
array_of_array_of_sparse = RasaDataGenerator._filter_out_fake_inputs(
feature_array
)
if not array_of_array_of_sparse:
# create empty array with appropriate last dims
return [
np.empty((0, 3), dtype=np.int64),
np.array([], dtype=np.float32),
np.array([0, 0, number_of_features], dtype=np.int64),
]
# we need to make sure that the matrices are coo_matrices otherwise the
# transformation does not work (e.g. you cannot access x.row, x.col)
if not isinstance(array_of_array_of_sparse[0][0], scipy.sparse.coo_matrix):
array_of_array_of_sparse = [
[
x.tocoo() if isinstance(x, scipy.sparse.spmatrix) else x
for x in array_of_sparse
]
for array_of_sparse in array_of_array_of_sparse
]
dialogue_len = [
len(array_of_sparse) for array_of_sparse in array_of_array_of_sparse
]
combined_dialogue_len = sum(dialogue_len)
max_seq_len = max(
[
x.shape[0]
for array_of_sparse in array_of_array_of_sparse
for x in array_of_sparse
]
)
# get the indices of values
indices = np.hstack(
[
np.vstack(
[sum(dialogue_len[:i]) + j * np.ones_like(x.row), x.row, x.col]
)
for i, array_of_sparse in enumerate(array_of_array_of_sparse)
for j, x in enumerate(array_of_sparse)
]
).T
data = np.hstack(
[
x.data
for array_of_sparse in array_of_array_of_sparse
for x in array_of_sparse
]
)
shape = np.array((combined_dialogue_len, max_seq_len, number_of_features))
return [
indices.astype(np.int64),
data.astype(np.float32),
shape.astype(np.int64),
]
@staticmethod
def _filter_out_fake_inputs(
array_of_array_of_features: FeatureArray,
) -> Union[List[List[np.ndarray]], List[List[scipy.sparse.spmatrix]]]:
return list(
filter(
# filter empty lists created by another filter
lambda x: len(x) > 0,
[
# filter all the "fake" inputs, we know the input is "fake",
# when sequence dimension is `0`
list(filter(lambda x: x.shape[0] > 0, array_of_features))
for array_of_features in array_of_array_of_features
],
)
)
class RasaBatchDataGenerator(RasaDataGenerator):
"""Data generator with an optional increasing batch size."""
def __init__(
self,
model_data: RasaModelData,
batch_size: Union[List[int], int],
epochs: int = 1,
batch_strategy: Text = SEQUENCE,
shuffle: bool = True,
drop_small_last_batch: bool = False,
):
"""Initializes the increasing batch size data generator.
Args:
model_data: The model data to use.
batch_size: The batch size.
epochs: The total number of epochs.
batch_strategy: The batch strategy.
shuffle: If 'True', data will be shuffled.
drop_small_last_batch: if 'True', the last batch in an epoch will be dropped
if it has less examples than half the batch size
"""
super().__init__(model_data, batch_size, batch_strategy, shuffle)
if isinstance(batch_size, list):
logger.debug(
"The provided batch size is a list, this data generator will use a "
"linear increasing batch size."
)
self._epochs = epochs
# we use `on_epoch_end` method to prepare data for the next epoch
# set current epoch to `-1`, so that `on_epoch_end` will increase it to `0`
self._current_epoch = -1
# actual batch size will be set inside `on_epoch_end`
self._current_batch_size = 0
# create separate data variable that will store modified data for each batch
self._data: Data = {}
self.drop_small_last_batch = drop_small_last_batch
self.on_epoch_end()
def __len__(self) -> int:
"""Number of batches in the Sequence.
Returns:
The number of batches in the Sequence.
"""
# data was rebalanced, so need to recalculate number of examples
num_examples = self.model_data.number_of_examples(self._data)
batch_size = self._current_batch_size
if self.drop_small_last_batch:
# keep last batch only if it has at least half a batch size of examples
last_batch_half_full = num_examples % batch_size >= math.ceil(
batch_size / 2
)
num_batches = num_examples // batch_size + int(last_batch_half_full)
# Return at least 1 if there is an example
return max(num_batches, int(num_examples > 0))
else:
return num_examples // batch_size + int(num_examples % batch_size > 0)
def __getitem__(self, index: int) -> Tuple[Any, Any]:
"""Gets batch at position `index`.
Arguments:
index: position of the batch in the Sequence.
Returns:
A batch (tuple of input data and target data).
"""
start = index * self._current_batch_size
end = start + self._current_batch_size
# return input and target data, as our target data is inside the input
# data return None for the target data
return self.prepare_batch(self._data, start, end), None
def on_epoch_end(self) -> None:
"""Update the data after every epoch."""
self._current_epoch += 1
self._current_batch_size = self._linearly_increasing_batch_size()
self._data = self._shuffle_and_balance(self._current_batch_size)
def _linearly_increasing_batch_size(self) -> int:
"""Linearly increase batch size with every epoch.
The idea comes from https://arxiv.org/abs/1711.00489.
Returns:
The batch size to use in this epoch.
"""
if not isinstance(self.batch_size, list):
return int(self.batch_size)
if self._epochs > 1:
return int(
self.batch_size[0]
+ self._current_epoch
* (self.batch_size[1] - self.batch_size[0])
/ (self._epochs - 1)
)
else:
return int(self.batch_size[0])