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

465 lines
14 KiB
Python

import logging
import operator
from collections import defaultdict, Counter
from typing import List, Tuple, Text, Optional, Dict, Any, TYPE_CHECKING
from rasa.nlu.constants import (
TOKENS_NAMES,
BILOU_ENTITIES,
BILOU_ENTITIES_GROUP,
BILOU_ENTITIES_ROLE,
)
from rasa.shared.nlu.constants import (
TEXT,
ENTITIES,
ENTITY_ATTRIBUTE_START,
ENTITY_ATTRIBUTE_END,
ENTITY_ATTRIBUTE_TYPE,
ENTITY_ATTRIBUTE_GROUP,
ENTITY_ATTRIBUTE_ROLE,
NO_ENTITY_TAG,
)
if TYPE_CHECKING:
from rasa.nlu.tokenizers.tokenizer import Token
from rasa.shared.nlu.training_data.training_data import TrainingData
from rasa.shared.nlu.training_data.message import Message
logger = logging.getLogger(__name__)
BEGINNING = "B-"
INSIDE = "I-"
LAST = "L-"
UNIT = "U-"
BILOU_PREFIXES = [BEGINNING, INSIDE, LAST, UNIT]
def bilou_prefix_from_tag(tag: Text) -> Optional[Text]:
"""Returns the BILOU prefix from the given tag.
Args:
tag: the tag
Returns: the BILOU prefix of the tag
"""
if tag[:2] in BILOU_PREFIXES:
return tag[:2]
return None
def tag_without_prefix(tag: Text) -> Text:
"""Remove the BILOU prefix from the given tag.
Args:
tag: the tag
Returns: the tag without the BILOU prefix
"""
if tag[:2] in BILOU_PREFIXES:
return tag[2:]
return tag
def bilou_tags_to_ids(
message: "Message",
tag_id_dict: Dict[Text, int],
tag_name: Text = ENTITY_ATTRIBUTE_TYPE,
) -> List[int]:
"""Maps the entity tags of the message to the ids of the provided dict.
Args:
message: the message
tag_id_dict: mapping of tags to ids
tag_name: tag name of interest
Returns: a list of tag ids
"""
bilou_key = get_bilou_key_for_tag(tag_name)
if message.get(bilou_key):
_tags = [
tag_id_dict[_tag] if _tag in tag_id_dict else tag_id_dict[NO_ENTITY_TAG]
for _tag in message.get(bilou_key)
]
else:
_tags = [tag_id_dict[NO_ENTITY_TAG] for _ in message.get(TOKENS_NAMES[TEXT])]
return _tags
def get_bilou_key_for_tag(tag_name: Text) -> Text:
"""Get the message key for the BILOU tagging format of the provided tag name.
Args:
tag_name: the tag name
Returns:
the message key to store the BILOU tags
"""
if tag_name == ENTITY_ATTRIBUTE_ROLE:
return BILOU_ENTITIES_ROLE
if tag_name == ENTITY_ATTRIBUTE_GROUP:
return BILOU_ENTITIES_GROUP
return BILOU_ENTITIES
def build_tag_id_dict(
training_data: "TrainingData", tag_name: Text = ENTITY_ATTRIBUTE_TYPE
) -> Optional[Dict[Text, int]]:
"""Create a mapping of unique tags to ids.
Args:
training_data: the training data
tag_name: tag name of interest
Returns: a mapping of tags to ids
"""
bilou_key = get_bilou_key_for_tag(tag_name)
distinct_tags = set(
[
tag_without_prefix(e)
for example in training_data.nlu_examples
if example.get(bilou_key)
for e in example.get(bilou_key)
]
) - {NO_ENTITY_TAG}
if not distinct_tags:
return None
tag_id_dict = {
f"{prefix}{tag}": idx_1 * len(BILOU_PREFIXES) + idx_2 + 1
for idx_1, tag in enumerate(sorted(distinct_tags))
for idx_2, prefix in enumerate(BILOU_PREFIXES)
}
# NO_ENTITY_TAG corresponds to non-entity which should correspond to 0 index
# needed for correct prediction for padding
tag_id_dict[NO_ENTITY_TAG] = 0
return tag_id_dict
def apply_bilou_schema(training_data: "TrainingData") -> None:
"""Get a list of BILOU entity tags and set them on the given messages.
Args:
training_data: the training data
"""
for message in training_data.nlu_examples:
apply_bilou_schema_to_message(message)
def apply_bilou_schema_to_message(message: "Message") -> None:
"""Get a list of BILOU entity tags and set them on the given message.
Args:
message: the message
"""
entities = message.get(ENTITIES)
if not entities:
return
tokens = message.get(TOKENS_NAMES[TEXT])
for attribute, message_key in [
(ENTITY_ATTRIBUTE_TYPE, BILOU_ENTITIES),
(ENTITY_ATTRIBUTE_ROLE, BILOU_ENTITIES_ROLE),
(ENTITY_ATTRIBUTE_GROUP, BILOU_ENTITIES_GROUP),
]:
entities = map_message_entities(message, attribute)
output = bilou_tags_from_offsets(tokens, entities)
message.set(message_key, output)
def map_message_entities(
message: "Message", attribute_key: Text = ENTITY_ATTRIBUTE_TYPE
) -> List[Tuple[int, int, Text]]:
"""Maps the entities of the given message to their start, end, and tag values.
Args:
message: the message
attribute_key: key of tag value to use
Returns: a list of start, end, and tag value tuples
"""
def convert_entity(entity: Dict[Text, Any]) -> Tuple[int, int, Text]:
return (
entity[ENTITY_ATTRIBUTE_START],
entity[ENTITY_ATTRIBUTE_END],
entity.get(attribute_key) or NO_ENTITY_TAG,
)
entities = [convert_entity(entity) for entity in message.get(ENTITIES, [])]
# entities is a list of tuples (start, end, tag value).
# filter out all entities with tag value == NO_ENTITY_TAG.
tag_value_idx = 2
return [entity for entity in entities if entity[tag_value_idx] != NO_ENTITY_TAG]
def bilou_tags_from_offsets(
tokens: List["Token"], entities: List[Tuple[int, int, Text]]
) -> List[Text]:
"""Creates BILOU tags for the given tokens and entities.
Args:
message: The message object.
tokens: The list of tokens.
entities: The list of start, end, and tag tuples.
missing: The tag for missing entities.
Returns:
BILOU tags.
"""
start_pos_to_token_idx = {token.start: i for i, token in enumerate(tokens)}
end_pos_to_token_idx = {token.end: i for i, token in enumerate(tokens)}
bilou = [NO_ENTITY_TAG for _ in tokens]
_add_bilou_tags_to_entities(
bilou, entities, end_pos_to_token_idx, start_pos_to_token_idx
)
return bilou
def _add_bilou_tags_to_entities(
bilou: List[Text],
entities: List[Tuple[int, int, Text]],
end_pos_to_token_idx: Dict[int, int],
start_pos_to_token_idx: Dict[int, int],
) -> None:
for start_pos, end_pos, label in entities:
start_token_idx = start_pos_to_token_idx.get(start_pos)
end_token_idx = end_pos_to_token_idx.get(end_pos)
# Only interested if the tokenization is correct
if start_token_idx is not None and end_token_idx is not None:
if start_token_idx == end_token_idx:
bilou[start_token_idx] = f"{UNIT}{label}"
else:
bilou[start_token_idx] = f"{BEGINNING}{label}"
for i in range(start_token_idx + 1, end_token_idx):
bilou[i] = f"{INSIDE}{label}"
bilou[end_token_idx] = f"{LAST}{label}"
def ensure_consistent_bilou_tagging(
predicted_tags: List[Text], predicted_confidences: List[float]
) -> Tuple[List[Text], List[float]]:
"""
Ensure predicted tags follow the BILOU tagging schema.
We assume that starting B- tags are correct. Followed tags that belong to start
tag but have a different entity type are updated considering also the confidence
values of those tags.
For example, B-a I-b L-a is updated to B-a I-a L-a and B-a I-a O is changed to
B-a L-a.
Args:
predicted_tags: predicted tags
predicted_confidences: predicted confidences
Return:
List of tags.
List of confidences.
"""
for idx, predicted_tag in enumerate(predicted_tags):
prefix = bilou_prefix_from_tag(predicted_tag)
tag = tag_without_prefix(predicted_tag)
if prefix == BEGINNING:
last_idx = _find_bilou_end(idx, predicted_tags)
relevant_confidences = predicted_confidences[idx : last_idx + 1]
relevant_tags = [
tag_without_prefix(tag) for tag in predicted_tags[idx : last_idx + 1]
]
# if not all tags are the same, for example, B-person I-person L-location
# we need to check what tag we should use depending on the confidence
# values and update the tags and confidences accordingly
if not all(relevant_tags[0] == tag for tag in relevant_tags):
# decide which tag this entity should use
tag, tag_score = _tag_to_use(relevant_tags, relevant_confidences)
logger.debug(
f"Using tag '{tag}' for entity with mixed tag labels "
f"(original tags: {predicted_tags[idx : last_idx + 1]}, "
f"(original confidences: "
f"{predicted_confidences[idx : last_idx + 1]})."
)
# all tags that change get the score of that tag assigned
predicted_confidences = _update_confidences(
predicted_confidences, predicted_tags, tag, tag_score, idx, last_idx
)
# ensure correct BILOU annotations
if last_idx == idx:
predicted_tags[idx] = f"{UNIT}{tag}"
elif last_idx - idx == 1:
predicted_tags[idx] = f"{BEGINNING}{tag}"
predicted_tags[last_idx] = f"{LAST}{tag}"
else:
predicted_tags[idx] = f"{BEGINNING}{tag}"
predicted_tags[last_idx] = f"{LAST}{tag}"
for i in range(idx + 1, last_idx):
predicted_tags[i] = f"{INSIDE}{tag}"
return predicted_tags, predicted_confidences
def _tag_to_use(
relevant_tags: List[Text], relevant_confidences: List[float]
) -> Tuple[Text, float]:
"""Decide what tag to use according to the following metric:
Calculate the average confidence per tag.
Calculate the percentage of tokens assigned to a tag within the entity per tag.
The harmonic mean of those two metrics is the score for the tag.
The tag with the highest score is taken as the tag for the entity.
Args:
relevant_tags: The tags of the entity.
relevant_confidences: The confidence values.
Returns:
The tag to use. The score of that tag.
"""
# Calculate the average confidence per tag.
avg_confidence_per_tag = _avg_confidence_per_tag(
relevant_tags, relevant_confidences
)
# Calculate the percentage of tokens assigned to a tag per tag.
tag_counts = Counter(relevant_tags)
token_percentage_per_tag: Dict[Text, float] = {}
for tag, count in tag_counts.items():
token_percentage_per_tag[tag] = round(count / len(relevant_tags), 2)
# Calculate the harmonic mean between the two metrics per tag.
score_per_tag = {}
for tag, token_percentage in token_percentage_per_tag.items():
avg_confidence = avg_confidence_per_tag[tag]
score_per_tag[tag] = (
2
* (avg_confidence * token_percentage)
/ (avg_confidence + token_percentage)
)
# Take the tag with the highest score as the tag for the entity
tag, score = max(score_per_tag.items(), key=operator.itemgetter(1))
return tag, score
def _update_confidences(
predicted_confidences: List[float],
predicted_tags: List[Text],
tag: Text,
score: float,
idx: int,
last_idx: int,
) -> List[float]:
"""Update the confidence values.
Set the confidence value of a tag to score value if the predicated
tag changed.
Args:
predicted_confidences: The list of predicted confidences.
predicted_tags: The list of predicted tags.
tag: The tag of the entity.
score: The score value of that tag.
idx: The start index of the entity.
last_idx: The end index of the entity.
Returns:
The updated list of confidences.
"""
for i in range(idx, last_idx + 1):
predicted_confidences[i] = (
round(score, 2)
if tag_without_prefix(predicted_tags[i]) != tag
else predicted_confidences[i]
)
return predicted_confidences
def _avg_confidence_per_tag(
relevant_tags: List[Text], relevant_confidences: List[float]
) -> Dict[Text, float]:
confidences_per_tag = defaultdict(list)
for tag, confidence in zip(relevant_tags, relevant_confidences):
confidences_per_tag[tag].append(confidence)
avg_confidence_per_tag = {}
for tag, confidences in confidences_per_tag.items():
avg_confidence_per_tag[tag] = round(sum(confidences) / len(confidences), 2)
return avg_confidence_per_tag
def _find_bilou_end(start_idx: int, predicted_tags: List[Text]) -> int:
"""Find the last index of the entity.
The start index is pointing to a B- tag. The entity is closed as soon as we find
a L- tag or a O tag.
Args:
start_idx: The start index of the entity
predicted_tags: The list of predicted tags
Returns:
The end index of the entity
"""
current_idx = start_idx + 1
finished = False
start_tag = tag_without_prefix(predicted_tags[start_idx])
while not finished:
if current_idx >= len(predicted_tags):
logger.debug(
"Inconsistent BILOU tagging found, B- tag not closed by L- tag, "
"i.e [B-a, I-a, O] instead of [B-a, L-a, O].\n"
"Assuming last tag is L- instead of I-."
)
current_idx -= 1
break
current_label = predicted_tags[current_idx]
prefix = bilou_prefix_from_tag(current_label)
tag = tag_without_prefix(current_label)
if tag != start_tag:
# words are not tagged the same entity class
logger.debug(
"Inconsistent BILOU tagging found, B- tag, L- tag pair encloses "
"multiple entity classes.i.e. [B-a, I-b, L-a] instead of "
"[B-a, I-a, L-a].\nAssuming B- class is correct."
)
if prefix == LAST:
finished = True
elif prefix == INSIDE:
# middle part of the entity
current_idx += 1
else:
# entity not closed by an L- tag
finished = True
current_idx -= 1
logger.debug(
"Inconsistent BILOU tagging found, B- tag not closed by L- tag, "
"i.e [B-a, I-a, O] instead of [B-a, L-a, O].\n"
"Assuming last tag is L- instead of I-."
)
return current_idx