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

493 lines
18 KiB
Python

from __future__ import annotations
import itertools
from dataclasses import dataclass
from typing import Iterable, Union, Text, Optional, List, Any, Tuple, Dict, Set
import numpy as np
import scipy.sparse
from safetensors.numpy import save_file, load_file
import rasa.shared.nlu.training_data.util
import rasa.shared.utils.io
from rasa.shared.nlu.constants import FEATURE_TYPE_SEQUENCE, FEATURE_TYPE_SENTENCE
@dataclass
class FeatureMetadata:
data_type: str
attribute: str
origin: Union[str, List[str]]
is_sparse: bool
shape: tuple
safetensors_key: str
def save_features(
features_dict: Dict[Text, List[Features]], file_name: str
) -> Dict[str, Any]:
"""Save a dictionary of Features lists to disk using safetensors.
Args:
features_dict: Dictionary mapping strings to lists of Features objects
file_name: File to save the features to
Returns:
The metadata to reconstruct the features.
"""
# All tensors are stored in a single safetensors file
tensors_to_save = {}
# Metadata will be stored separately
metadata = {}
for key, features_list in features_dict.items():
feature_metadata_list = []
for idx, feature in enumerate(features_list):
# Create a unique key for this tensor in the safetensors file
safetensors_key = f"{key}_{idx}"
# Convert sparse matrices to dense if needed
if feature.is_sparse():
# For sparse matrices, use the COO format
coo = feature.features.tocoo() # type:ignore[union-attr]
# Save data, row indices and col indices separately
tensors_to_save[f"{safetensors_key}_data"] = coo.data
tensors_to_save[f"{safetensors_key}_row"] = coo.row
tensors_to_save[f"{safetensors_key}_col"] = coo.col
else:
tensors_to_save[safetensors_key] = feature.features
# Store metadata
metadata_item = FeatureMetadata(
data_type=feature.type,
attribute=feature.attribute,
origin=feature.origin,
is_sparse=feature.is_sparse(),
shape=feature.features.shape,
safetensors_key=safetensors_key,
)
feature_metadata_list.append(vars(metadata_item))
metadata[key] = feature_metadata_list
# Save tensors
save_file(tensors_to_save, file_name)
return metadata
def load_features(
filename: str, metadata: Dict[str, Any]
) -> Dict[Text, List[Features]]:
"""Load Features dictionary from disk.
Args:
filename: File name of the safetensors file.
metadata: Metadata to reconstruct the features.
Returns:
Dictionary mapping strings to lists of Features objects
"""
# Load tensors
tensors = load_file(filename)
# Reconstruct the features dictionary
features_dict: Dict[Text, List[Features]] = {}
for key, feature_metadata_list in metadata.items():
features_list = []
for meta in feature_metadata_list:
safetensors_key = meta["safetensors_key"]
if meta["is_sparse"]:
# Reconstruct sparse matrix from COO format
data = tensors[f"{safetensors_key}_data"]
row = tensors[f"{safetensors_key}_row"]
col = tensors[f"{safetensors_key}_col"]
features_matrix = scipy.sparse.coo_matrix(
(data, (row, col)), shape=tuple(meta["shape"])
).tocsr() # Convert back to CSR format
else:
features_matrix = tensors[safetensors_key]
# Reconstruct Features object
features = Features(
features=features_matrix,
feature_type=meta["data_type"],
attribute=meta["attribute"],
origin=meta["origin"],
)
features_list.append(features)
features_dict[key] = features_list
return features_dict
class Features:
"""Stores the features produced by any featurizer."""
def __init__(
self,
features: Union[np.ndarray, scipy.sparse.spmatrix],
feature_type: Text,
attribute: Text,
origin: Union[Text, List[Text]],
) -> None:
"""Initializes the Features object.
Args:
features: The features.
feature_type: Type of the feature, e.g. FEATURE_TYPE_SENTENCE.
attribute: Message attribute, e.g. INTENT or TEXT.
origin: Name of the component that created the features.
"""
self.features = features
self.type = feature_type
self.origin = origin
self.attribute = attribute
self._cached_fingerprint: Optional[Text] = None
if not self.is_dense() and not self.is_sparse():
raise ValueError(
"Features must either be a numpy array for dense "
"features or a scipy sparse matrix for sparse features."
)
def __repr__(self) -> Text:
return (
f"{self.__class__.__name__}("
f"features={self.features}, "
f"type={self.type}, "
f"origin={self.origin}, "
f"attribute={self.attribute})"
)
def __str__(self) -> Text:
return (
f"{self.__class__.__name__}("
f"features.shape={self.features.shape}, "
f"is_sparse={self.is_sparse()}, "
f"type={self.type}, "
f"origin={self.origin}, "
f"attribute={self.attribute})"
)
def is_sparse(self) -> bool:
"""Checks if features are sparse or not.
Returns:
True, if features are sparse, false otherwise.
"""
return isinstance(self.features, scipy.sparse.spmatrix)
def is_dense(self) -> bool:
"""Checks if features are dense or not.
Returns:
True, if features are dense, false otherwise.
"""
return not self.is_sparse()
def combine_with_features(self, additional_features: Optional[Features]) -> None:
"""Combine the incoming features with this instance's features.
Args:
additional_features: additional features to add
Returns:
Combined features.
"""
if additional_features is None:
return
if self.is_dense() and additional_features.is_dense():
self._combine_dense_features(additional_features)
elif self.is_sparse() and additional_features.is_sparse():
self._combine_sparse_features(additional_features)
else:
raise ValueError("Cannot combine sparse and dense features.")
def _combine_dense_features(self, additional_features: Features) -> None:
if self.features.ndim != additional_features.features.ndim:
raise ValueError(
f"Cannot combine dense features as sequence dimensions do not "
f"match: {self.features.ndim} != {additional_features.features.ndim}."
)
self.features = np.concatenate(
(self.features, additional_features.features), axis=-1
)
self._cached_fingerprint = None
def _combine_sparse_features(self, additional_features: Features) -> None:
from scipy.sparse import hstack
if self.features.shape[0] != additional_features.features.shape[0]:
raise ValueError(
f"Cannot combine sparse features as sequence dimensions do not "
f"match: {self.features.shape[0]} != "
f"{additional_features.features.shape[0]}."
)
self.features = hstack([self.features, additional_features.features])
self._cached_fingerprint = None
def __key__(
self,
) -> Tuple[
Text, Text, Union[np.ndarray, scipy.sparse.spmatrix], Union[Text, List[Text]]
]:
"""Returns a 4-tuple of defining properties.
Returns:
Tuple of type, attribute, features, and origin properties.
"""
return self.type, self.attribute, self.features, self.origin
def __eq__(self, other: Any) -> bool:
"""Tests if the `self` `Feature` equals to the `other`.
Args:
other: The other object.
Returns:
`True` when the other object is a `Feature` and has the same
type, attribute, and feature tensors.
"""
if not isinstance(other, Features):
return False
return (
other.type == self.type
and other.attribute == self.attribute
and other.features == self.features
)
def fingerprint(self) -> Text:
"""Calculate a stable string fingerprint for the features."""
if self._cached_fingerprint is None:
if self.is_dense():
f_as_text = self.features.tobytes()
else:
f_as_text = rasa.shared.nlu.training_data.util.sparse_matrix_to_string(
self.features
)
self._cached_fingerprint = rasa.shared.utils.io.deep_container_fingerprint(
[self.type, self.origin, self.attribute, f_as_text]
)
return self._cached_fingerprint
@staticmethod
def filter(
features_list: List[Features],
attributes: Optional[Iterable[Text]] = None,
type: Optional[Text] = None,
origin: Optional[List[Text]] = None,
is_sparse: Optional[bool] = None,
) -> List[Features]:
"""Filters the given list of features.
Args:
features_list: list of features to be filtered
attributes: List of attributes that we're interested in. Set this to `None`
to disable this filter.
type: The type of feature we're interested in. Set this to `None`
to disable this filter.
origin: If specified, this method will check that the exact order of origins
matches the given list of origins. The reason for this is that if
multiple origins are listed for a Feature, this means that this feature
has been created by concatenating Features from the listed origins in
that particular order.
is_sparse: Defines whether all features that we're interested in should be
sparse. Set this to `None` to disable this filter.
Returns:
sub-list of features with the desired properties
"""
filtered = features_list
if attributes is not None:
attributes = set(attributes)
filtered = [f for f in filtered if f.attribute in attributes]
if origin is not None:
filtered = [
f
for f in filtered
if (f.origin if not isinstance(f.origin, Text) else list([f.origin]))
== origin
]
if type is not None:
filtered = [f for f in filtered if f.type == type]
if is_sparse is not None:
filtered = [f for f in filtered if f.is_sparse() == is_sparse]
return filtered
@staticmethod
def groupby_attribute(
features_list: List[Features], attributes: Optional[Iterable[Text]] = None
) -> Dict[Text, List[Features]]:
"""Groups the given features according to their attribute.
Args:
features_list: list of features to be grouped
attributes: If specified, the result will be a grouping with respect to
the given attributes. If some specified attribute has no features attached
to it, then the resulting dictionary will map it to an empty list.
If this is None, the result will be a grouping according to all attributes
for which features can be found.
Returns:
a mapping from the requested attributes to the list of correspoding
features
"""
# ensure all requested attributes are present in the output - regardless
# of whether we find features later
extracted: Dict[Text, List[Features]] = (
dict()
if attributes is None
else {attribute: [] for attribute in attributes}
)
# extract features for all (requested) attributes
for feat in features_list:
if attributes is None or feat.attribute in attributes:
extracted.setdefault(feat.attribute, []).append(feat)
return extracted
@staticmethod
def combine(
features_list: List[Features], expected_origins: Optional[List[Text]] = None
) -> Features:
"""Combine features of the same type and level that describe the same attribute.
If sequence features are to be combined, then they must have the same
sequence dimension.
Args:
features: Non-empty list of Features of the same type and level that
describe the same attribute.
expected_origins: The expected origins of the given features. This method
will check that the origin information of each feature is as expected, i.e.
the origin of the i-th feature in the given list is the i-th origin
in this list of origins.
Raises:
`ValueError` will be raised
- if the given list is empty
- if there are inconsistencies in the given list of `Features`
- if the origins aren't as expected
"""
if len(features_list) == 0:
raise ValueError("Expected a non-empty list of Features.")
if len(features_list) == 1:
# nothing to combine here
return features_list[0]
# Un-Pack the Origin information
origin_of_combination = [f.origin for f in features_list]
origin_of_combination = [
featurizer_name
for origin in origin_of_combination
for featurizer_name in (origin if isinstance(origin, List) else [origin])
]
# Sanity Checks
# (1) origins must be as expected
if expected_origins is not None:
if origin_of_combination is not None:
for idx, (expected, actual) in enumerate(
itertools.zip_longest(expected_origins, origin_of_combination)
):
if expected != actual:
raise ValueError(
f"Expected '{expected}' to be the origin of the {idx}-th "
f"feature (because of `origin_of_combination`) but found a "
f"feature from '{actual}'."
)
# (2) attributes (is_sparse, type, attribute) must coincide
# Note: we could also use `filter` for this check, but then the error msgs
# aren't as nice.
sparseness: Set[bool] = set(f.is_sparse() for f in features_list)
if len(sparseness) > 1:
raise ValueError(
"Expected all Features to have the same sparseness property but "
"found both (sparse and dense)."
)
types: Set[Text] = set(f.type for f in features_list)
if len(types) > 1:
raise ValueError(
f"Expected all Features to have the same type but found the "
f"following types {types}."
)
attributes: Set[Text] = set(f.attribute for f in features_list)
if len(attributes) > 1:
raise ValueError(
f"Expected all Features to describe the same attribute but found "
f"attributes: {attributes}."
)
# (3) dimensions must match
# Note: We shouldn't have to check sentence-level features here but it doesn't
# hurt either.
dimensions = set(f.features.shape[0] for f in features_list)
if len(dimensions) > 1:
raise ValueError(
f"Expected all sequence dimensions to match but found {dimensions}."
)
# Combine the features
arbitrary_feature = features_list[0]
if not arbitrary_feature.is_sparse():
features = np.concatenate([f.features for f in features_list], axis=-1)
else:
features = scipy.sparse.hstack([f.features for f in features_list])
return Features(
features=features,
feature_type=arbitrary_feature.type,
attribute=arbitrary_feature.attribute,
origin=origin_of_combination,
)
@staticmethod
def reduce(
features_list: List[Features], expected_origins: Optional[List[Text]] = None
) -> List[Features]:
"""Combines features of same type and level into one Feature.
Args:
features_list: list of Features which must all describe the same attribute
expected_origins: if specified, this list will be used to validate that
the features from the right featurizers are combined in the right order
(cf. `Features.combine`)
Returns:
a list of the combined Features, i.e. at most 4 Features, where
- all the sparse features are listed before the dense features
- sequence feature is always listed before the sentence feature with the
same sparseness property
"""
if len(features_list) == 1:
return features_list
# sanity check
different_settings = set(f.attribute for f in features_list)
if len(different_settings) > 1:
raise ValueError(
f"Expected all Features to describe the same attribute but found "
f" {different_settings}."
)
output = []
for is_sparse in [True, False]:
# all sparse features before all dense features
for type in [FEATURE_TYPE_SEQUENCE, FEATURE_TYPE_SENTENCE]:
# sequence feature that is (not) sparse before sentence feature that is
# (not) sparse
sublist = Features.filter(
features_list=features_list, type=type, is_sparse=is_sparse
)
if sublist:
combined_feature = Features.combine(
sublist, expected_origins=expected_origins
)
output.append(combined_feature)
return output