-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathdescription.py
More file actions
165 lines (132 loc) · 6.07 KB
/
description.py
File metadata and controls
165 lines (132 loc) · 6.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
"""DescriptionScorer class for scoring utterances based on intent descriptions."""
from typing import Any
import numpy as np
import scipy
from numpy.typing import NDArray
from pydantic import PositiveFloat
from sklearn.metrics.pairwise import cosine_similarity
from autointent import Context, Embedder
from autointent.configs import EmbedderConfig, TaskTypeEnum
from autointent.context.optimization_info import ScorerArtifact
from autointent.custom_types import ListOfLabels
from autointent.metrics import SCORING_METRICS_MULTICLASS, SCORING_METRICS_MULTILABEL
from autointent.modules.abc import BaseScorer
class DescriptionScorer(BaseScorer):
r"""
Scoring module that scores utterances based on similarity to intent descriptions.
DescriptionScorer embeds both the utterances and the intent descriptions, then computes a similarity score
between the two, using either cosine similarity and softmax.
:ivar _embedder: The embedder used to generate embeddings for utterances and descriptions.
:ivar name: Name of the scorer, defaults to "description".
"""
_embedder: Embedder
name = "description"
_n_classes: int
_multilabel: bool
_description_vectors: NDArray[Any]
supports_multiclass = True
supports_multilabel = True
def __init__(
self,
embedder_config: EmbedderConfig | str | dict[str, Any] | None = None,
temperature: PositiveFloat = 1.0,
) -> None:
"""
Initialize the DescriptionScorer.
:param embedder_config: Config of the embedder model.
:param temperature: Temperature parameter for scaling logits, defaults to 1.0.
"""
self.temperature = temperature
self.embedder_config = EmbedderConfig.from_search_config(embedder_config)
if self.temperature < 0 or not isinstance(self.temperature, float | int):
msg = "`temperature` argument of `DescriptionScorer` must be a positive float"
raise ValueError(msg)
@classmethod
def from_context(
cls,
context: Context,
temperature: PositiveFloat,
embedder_config: EmbedderConfig | str | None = None,
) -> "DescriptionScorer":
"""
Create a DescriptionScorer instance using a Context object.
:param context: Context containing configurations and utilities.
:param temperature: Temperature parameter for scaling logits.
:param embedder_config: Config of the embedder model. If None, the best embedder is used.
:return: Initialized DescriptionScorer instance.
"""
if embedder_config is None:
embedder_config = context.resolve_embedder()
return cls(
temperature=temperature,
embedder_config=embedder_config,
)
def get_embedder_config(self) -> dict[str, Any]:
"""
Get the name of the embedder.
:return: Embedder name.
"""
return self.embedder_config.model_dump()
def fit(
self,
utterances: list[str],
labels: ListOfLabels,
descriptions: list[str],
) -> None:
"""
Fit the scorer by embedding utterances and descriptions.
:param utterances: List of utterances to embed.
:param labels: List of labels corresponding to the utterances.
:param descriptions: List of intent descriptions.
:raises ValueError: If descriptions contain None values or embeddings mismatch utterances.
"""
if hasattr(self, "_embedder"):
self._embedder.clear_ram()
self._validate_task(labels)
if any(description is None for description in descriptions):
error_text = (
"Some intent descriptions (label_description) are missing (None). "
"Please ensure all intents have descriptions."
)
raise ValueError(error_text)
embedder = Embedder(self.embedder_config)
self._description_vectors = embedder.embed(descriptions, TaskTypeEnum.sts)
self._embedder = embedder
def predict(self, utterances: list[str]) -> NDArray[np.float64]:
"""
Predict scores for utterances based on similarity to intent descriptions.
:param utterances: List of utterances to score.
:return: Array of probabilities for each utterance.
"""
utterance_vectors = self._embedder.embed(utterances, TaskTypeEnum.sts)
similarities: NDArray[np.float64] = cosine_similarity(utterance_vectors, self._description_vectors)
if self._multilabel:
probabilities = scipy.special.expit(similarities / self.temperature)
else:
probabilities = scipy.special.softmax(similarities / self.temperature, axis=1)
return probabilities # type: ignore[no-any-return]
def clear_cache(self) -> None:
"""Clear cached data in memory used by the embedder."""
self._embedder.clear_ram()
def get_train_data(self, context: Context) -> tuple[list[str], ListOfLabels, list[str]]:
return ( # type: ignore[return-value]
context.data_handler.train_utterances(0),
context.data_handler.train_labels(0),
context.data_handler.intent_descriptions,
)
def score_cv(self, context: Context, metrics: list[str]) -> dict[str, float]:
"""
Evaluate the scorer on a test set and compute the specified metric.
:param context: Context containing test set and other data.
:param metrics: List of metric names to compute.
:return: Computed metrics value for the test set or error code of metrics
"""
metrics_dict = SCORING_METRICS_MULTILABEL if context.is_multilabel() else SCORING_METRICS_MULTICLASS
chosen_metrics = {name: fn for name, fn in metrics_dict.items() if name in metrics}
metrics_calculated, all_val_scores = self.score_metrics_cv(
chosen_metrics,
context.data_handler.validation_iterator(),
descriptions=context.data_handler.intent_descriptions,
)
self._artifact = ScorerArtifact(folded_scores=all_val_scores)
return metrics_calculated