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
371 lines
14 KiB
Python
371 lines
14 KiB
Python
from typing import Dict, Any, List, Tuple, Optional, Union
|
|
|
|
import numpy as np
|
|
import scipy.sparse
|
|
from safetensors.numpy import load_file
|
|
from safetensors.numpy import save_file
|
|
|
|
import rasa.shared.utils.io
|
|
|
|
|
|
def _recursive_serialize(
|
|
array: Any, prefix: str, data_dict: Dict[str, Any], metadata: List[Dict[str, Any]]
|
|
) -> None:
|
|
"""Recursively serialize arrays and matrices for high dimensional data."""
|
|
if isinstance(array, np.ndarray) and array.ndim <= 2:
|
|
data_key = f"{prefix}_array"
|
|
data_dict[data_key] = array
|
|
metadata.append({"type": "dense", "key": data_key, "shape": array.shape})
|
|
|
|
elif isinstance(array, list) and all([isinstance(v, float) for v in array]):
|
|
data_key = f"{prefix}_list"
|
|
data_dict[data_key] = np.array(array, dtype=np.float32)
|
|
metadata.append({"type": "list", "key": data_key})
|
|
|
|
elif isinstance(array, list) and all([isinstance(v, int) for v in array]):
|
|
data_key = f"{prefix}_list"
|
|
data_dict[data_key] = np.array(array, dtype=np.int64)
|
|
metadata.append({"type": "list", "key": data_key})
|
|
|
|
elif isinstance(array, scipy.sparse.spmatrix):
|
|
data_key_data = f"{prefix}_data"
|
|
data_key_row = f"{prefix}_row"
|
|
data_key_col = f"{prefix}_col"
|
|
array = array.tocoo()
|
|
data_dict.update(
|
|
{
|
|
data_key_data: array.data,
|
|
data_key_row: array.row,
|
|
data_key_col: array.col,
|
|
}
|
|
)
|
|
metadata.append({"type": "sparse", "key": prefix, "shape": array.shape})
|
|
|
|
elif isinstance(array, list) or isinstance(array, np.ndarray):
|
|
group_metadata = {"type": "group", "subcomponents": []}
|
|
for idx, item in enumerate(array):
|
|
new_prefix = f"{prefix}_{idx}"
|
|
_recursive_serialize(
|
|
item, new_prefix, data_dict, group_metadata["subcomponents"]
|
|
)
|
|
metadata.append(group_metadata)
|
|
|
|
|
|
def _serialize_nested_data(
|
|
nested_data: Dict[str, Dict[str, List["FeatureArray"]]],
|
|
prefix: str,
|
|
data_dict: Dict[str, np.ndarray],
|
|
metadata: List[Dict[str, Union[str, List]]],
|
|
) -> None:
|
|
"""Handle serialization across dictionary and list levels."""
|
|
for outer_key, inner_dict in nested_data.items():
|
|
inner_metadata = {"key": outer_key, "components": []}
|
|
|
|
for inner_key, feature_arrays in inner_dict.items():
|
|
array_metadata = {
|
|
"key": inner_key,
|
|
"number_of_dimensions": feature_arrays[0].number_of_dimensions,
|
|
"features": [],
|
|
}
|
|
|
|
for idx, feature_array in enumerate(feature_arrays):
|
|
feature_prefix = f"{prefix}_{outer_key}_{inner_key}_{idx}"
|
|
_recursive_serialize(
|
|
feature_array.tolist(),
|
|
feature_prefix,
|
|
data_dict,
|
|
array_metadata["features"],
|
|
)
|
|
|
|
inner_metadata["components"].append( # type:ignore[attr-defined]
|
|
array_metadata
|
|
)
|
|
|
|
metadata.append(inner_metadata)
|
|
|
|
|
|
def serialize_nested_feature_arrays(
|
|
nested_feature_array: Dict[str, Dict[str, List["FeatureArray"]]],
|
|
data_filename: str,
|
|
metadata_filename: str,
|
|
) -> None:
|
|
data_dict: Dict[str, np.ndarray] = {}
|
|
metadata: List[Dict[str, Union[str, List]]] = []
|
|
|
|
_serialize_nested_data(nested_feature_array, "component", data_dict, metadata)
|
|
|
|
# Save serialized data and metadata
|
|
save_file(data_dict, data_filename)
|
|
rasa.shared.utils.io.dump_obj_as_json_to_file(metadata_filename, metadata)
|
|
|
|
|
|
def _recursive_deserialize(
|
|
metadata: List[Dict[str, Any]], data: Dict[str, Any]
|
|
) -> List[Any]:
|
|
"""Recursively deserialize arrays and matrices for high dimensional data."""
|
|
result = []
|
|
|
|
for item in metadata:
|
|
if item["type"] == "dense":
|
|
key = item["key"]
|
|
array = np.asarray(data[key]).reshape(item["shape"])
|
|
result.append(array)
|
|
|
|
elif item["type"] == "list":
|
|
key = item["key"]
|
|
result.append(list(data[key]))
|
|
|
|
elif item["type"] == "sparse":
|
|
data_vals = data[f"{item['key']}_data"]
|
|
row_vals = data[f"{item['key']}_row"]
|
|
col_vals = data[f"{item['key']}_col"]
|
|
sparse_matrix = scipy.sparse.coo_matrix(
|
|
(data_vals, (row_vals, col_vals)), shape=item["shape"]
|
|
)
|
|
result.append(sparse_matrix)
|
|
elif item["type"] == "group":
|
|
sublist = _recursive_deserialize(item["subcomponents"], data)
|
|
result.append(sublist)
|
|
|
|
return result
|
|
|
|
|
|
def _deserialize_nested_data(
|
|
metadata: List[Dict[str, Any]], data_dict: Dict[str, Any]
|
|
) -> Dict[str, Dict[str, List["FeatureArray"]]]:
|
|
"""Handle deserialization across all dictionary and list levels."""
|
|
result: Dict[str, Dict[str, List["FeatureArray"]]] = {}
|
|
|
|
for outer_item in metadata:
|
|
outer_key = outer_item["key"]
|
|
result[outer_key] = {}
|
|
|
|
for inner_item in outer_item["components"]:
|
|
inner_key = inner_item["key"]
|
|
feature_arrays = []
|
|
|
|
# Reconstruct the list of FeatureArrays
|
|
for feature_item in inner_item["features"]:
|
|
# Reconstruct the list of FeatureArrays
|
|
feature_array_data = _recursive_deserialize([feature_item], data_dict)
|
|
# Prepare the input for the FeatureArray;
|
|
# ensure it is np.ndarray compatible
|
|
input_array = np.array(feature_array_data[0], dtype=object)
|
|
feature_array = FeatureArray(
|
|
input_array, inner_item["number_of_dimensions"]
|
|
)
|
|
feature_arrays.append(feature_array)
|
|
|
|
result[outer_key][inner_key] = feature_arrays
|
|
|
|
return result
|
|
|
|
|
|
def deserialize_nested_feature_arrays(
|
|
data_filename: str, metadata_filename: str
|
|
) -> Dict[str, Dict[str, List["FeatureArray"]]]:
|
|
metadata = rasa.shared.utils.io.read_json_file(metadata_filename)
|
|
data_dict = load_file(data_filename)
|
|
|
|
return _deserialize_nested_data(metadata, data_dict)
|
|
|
|
|
|
class FeatureArray(np.ndarray):
|
|
"""Stores any kind of features ready to be used by a RasaModel.
|
|
|
|
Next to the input numpy array of features, it also received the number of
|
|
dimensions of the features.
|
|
As our features can have 1 to 4 dimensions we might have different number of numpy
|
|
arrays stacked. The number of dimensions helps us to figure out how to handle this
|
|
particular feature array. Also, it is automatically determined whether the feature
|
|
array is sparse or not and the number of units is determined as well.
|
|
|
|
Subclassing np.array: https://numpy.org/doc/stable/user/basics.subclassing.html
|
|
"""
|
|
|
|
def __new__(
|
|
cls, input_array: np.ndarray, number_of_dimensions: int
|
|
) -> "FeatureArray":
|
|
"""Create and return a new object. See help(type) for accurate signature."""
|
|
FeatureArray._validate_number_of_dimensions(number_of_dimensions, input_array)
|
|
|
|
feature_array = np.asarray(input_array).view(cls)
|
|
|
|
if number_of_dimensions <= 2:
|
|
feature_array.units = input_array.shape[-1]
|
|
feature_array.is_sparse = isinstance(input_array[0], scipy.sparse.spmatrix)
|
|
elif number_of_dimensions == 3:
|
|
feature_array.units = input_array[0].shape[-1]
|
|
feature_array.is_sparse = isinstance(input_array[0], scipy.sparse.spmatrix)
|
|
elif number_of_dimensions == 4:
|
|
feature_array.units = input_array[0][0].shape[-1]
|
|
feature_array.is_sparse = isinstance(
|
|
input_array[0][0], scipy.sparse.spmatrix
|
|
)
|
|
else:
|
|
raise ValueError(
|
|
f"Number of dimensions '{number_of_dimensions}' currently not "
|
|
f"supported."
|
|
)
|
|
|
|
feature_array.number_of_dimensions = number_of_dimensions
|
|
|
|
return feature_array
|
|
|
|
def __init__(
|
|
self, input_array: Any, number_of_dimensions: int, **kwargs: Any
|
|
) -> None:
|
|
"""Initialize. FeatureArray.
|
|
|
|
Needed in order to avoid 'Invalid keyword argument number_of_dimensions
|
|
to function FeatureArray.__init__ '
|
|
Args:
|
|
input_array: the array that contains features
|
|
number_of_dimensions: number of dimensions in input_array
|
|
"""
|
|
super().__init__(**kwargs)
|
|
self.number_of_dimensions = number_of_dimensions
|
|
|
|
def __array_finalize__(self, obj: Optional[np.ndarray]) -> None:
|
|
"""This method is called when the system allocates a new array from obj.
|
|
|
|
Args:
|
|
obj: A subclass (subtype) of ndarray.
|
|
"""
|
|
if obj is None:
|
|
return
|
|
|
|
self.units = getattr(obj, "units", None)
|
|
self.number_of_dimensions = getattr(
|
|
obj, "number_of_dimensions", None
|
|
) # type: ignore[assignment]
|
|
self.is_sparse = getattr(obj, "is_sparse", None)
|
|
|
|
default_attributes = {
|
|
"units": self.units,
|
|
"number_of_dimensions": self.number_of_dimensions,
|
|
"is_spare": self.is_sparse,
|
|
}
|
|
self.__dict__.update(default_attributes)
|
|
|
|
# pytype: disable=attribute-error
|
|
def __array_ufunc__(
|
|
self, ufunc: Any, method: str, *inputs: Any, **kwargs: Any
|
|
) -> Any:
|
|
"""Overwrite this method as we are subclassing numpy array.
|
|
|
|
Args:
|
|
ufunc: The ufunc object that was called.
|
|
method: A string indicating which Ufunc method was called
|
|
(one of "__call__", "reduce", "reduceat", "accumulate", "outer",
|
|
"inner").
|
|
*inputs: A tuple of the input arguments to the ufunc.
|
|
**kwargs: Any additional arguments
|
|
|
|
Returns:
|
|
The result of the operation.
|
|
"""
|
|
f = {
|
|
"reduce": ufunc.reduce,
|
|
"accumulate": ufunc.accumulate,
|
|
"reduceat": ufunc.reduceat,
|
|
"outer": ufunc.outer,
|
|
"at": ufunc.at,
|
|
"__call__": ufunc,
|
|
}
|
|
# convert the inputs to np.ndarray to prevent recursion, call the function,
|
|
# then cast it back as FeatureArray
|
|
output = FeatureArray(
|
|
f[method](*(i.view(np.ndarray) for i in inputs), **kwargs),
|
|
number_of_dimensions=kwargs["number_of_dimensions"],
|
|
)
|
|
output.__dict__ = self.__dict__ # carry forward attributes
|
|
return output
|
|
|
|
def __reduce__(self) -> Tuple[Any, Any, Any]:
|
|
"""Needed in order to pickle this object.
|
|
|
|
Returns:
|
|
A tuple.
|
|
"""
|
|
pickled_state = super(FeatureArray, self).__reduce__()
|
|
if isinstance(pickled_state, str):
|
|
raise TypeError("np array __reduce__ returned string instead of tuple.")
|
|
new_state = pickled_state[2] + (
|
|
self.number_of_dimensions,
|
|
self.is_sparse,
|
|
self.units,
|
|
)
|
|
return pickled_state[0], pickled_state[1], new_state
|
|
|
|
def __setstate__(self, state: Any, **kwargs: Any) -> None:
|
|
"""Sets the state.
|
|
|
|
Args:
|
|
state: The state argument must be a sequence that contains the following
|
|
elements version, shape, dtype, isFortan, rawdata.
|
|
**kwargs: Any additional parameter
|
|
"""
|
|
# Needed in order to load the object
|
|
self.number_of_dimensions = state[-3]
|
|
self.is_sparse = state[-2]
|
|
self.units = state[-1]
|
|
super(FeatureArray, self).__setstate__(state[0:-3], **kwargs)
|
|
|
|
# pytype: enable=attribute-error
|
|
|
|
@staticmethod
|
|
def _validate_number_of_dimensions(
|
|
number_of_dimensions: int, input_array: np.ndarray
|
|
) -> None:
|
|
"""Validates if the input array has given number of dimensions.
|
|
|
|
Args:
|
|
number_of_dimensions: number of dimensions
|
|
input_array: input array
|
|
|
|
Raises: ValueError in case the dimensions do not match
|
|
"""
|
|
# when loading the feature arrays from disk, the shape represents
|
|
# the correct number of dimensions
|
|
if len(input_array.shape) == number_of_dimensions:
|
|
return
|
|
|
|
_sub_array = input_array
|
|
dim = 0
|
|
# Go number_of_dimensions into the given input_array
|
|
for i in range(1, number_of_dimensions + 1):
|
|
_sub_array = _sub_array[0]
|
|
if isinstance(_sub_array, scipy.sparse.spmatrix):
|
|
dim = i
|
|
break
|
|
if isinstance(_sub_array, np.ndarray) and _sub_array.shape[0] == 0:
|
|
# sequence dimension is 0, we are dealing with "fake" features
|
|
dim = i
|
|
break
|
|
|
|
# If the resulting sub_array is sparse, the remaining number of dimensions
|
|
# should be at least 2
|
|
if isinstance(_sub_array, scipy.sparse.spmatrix):
|
|
if dim > 2:
|
|
raise ValueError(
|
|
f"Given number of dimensions '{number_of_dimensions}' does not "
|
|
f"match dimensions of given input array: {input_array}."
|
|
)
|
|
elif isinstance(_sub_array, np.ndarray) and _sub_array.shape[0] == 0:
|
|
# sequence dimension is 0, we are dealing with "fake" features,
|
|
# but they should be of dim 2
|
|
if dim > 2:
|
|
raise ValueError(
|
|
f"Given number of dimensions '{number_of_dimensions}' does not "
|
|
f"match dimensions of given input array: {input_array}."
|
|
)
|
|
# If the resulting sub_array is dense, the sub_array should be a single number
|
|
elif not np.issubdtype(type(_sub_array), np.integer) and not isinstance(
|
|
_sub_array, (np.float32, np.float64)
|
|
):
|
|
raise ValueError(
|
|
f"Given number of dimensions '{number_of_dimensions}' does not match "
|
|
f"dimensions of given input array: {input_array}."
|
|
)
|