Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix errors during training in ResponseSelector #4985

Merged
merged 13 commits into from
Dec 17, 2019
1 change: 1 addition & 0 deletions changelog/4985.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix errors during training and testing of ``ResponseSelector``.
99 changes: 50 additions & 49 deletions rasa/nlu/classifiers/embedding_intent_classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class EmbeddingIntentClassifier(Component):
"random_seed": None,
# embedding parameters
# default dense dimension used if no dense features are present
"dense_dim": {"text": 512, "intent": 20},
"dense_dim": {"text": 512, "label": 20},
# dimension size of embedding vectors
"embed_dim": 20,
# the type of the similarity
Expand Down Expand Up @@ -137,7 +137,7 @@ def _check_old_config_variables(config: Dict[Text, Any]) -> None:
def _load_nn_architecture_params(self, config: Dict[Text, Any]) -> None:
self.hidden_layer_sizes = {
"text": config["hidden_layers_sizes_a"],
"intent": config["hidden_layers_sizes_b"],
"label": config["hidden_layers_sizes_b"],
}
self.share_hidden_layers = config["share_hidden_layers"]
if (
Expand Down Expand Up @@ -307,15 +307,15 @@ def _extract_and_add_features(
return sparse_features, dense_features

def _extract_labels_precomputed_features(
self, label_examples: List["Message"]
self, label_examples: List["Message"], attribute: Text = INTENT_ATTRIBUTE
) -> List[np.ndarray]:
"""Collect precomputed encodings"""

sparse_features = []
dense_features = []

for e in label_examples:
_sparse, _dense = self._extract_and_add_features(e, INTENT_ATTRIBUTE)
_sparse, _dense = self._extract_and_add_features(e, attribute)
if _sparse is not None:
sparse_features.append(_sparse)
if _dense is not None:
Expand Down Expand Up @@ -369,21 +369,23 @@ def _create_label_data(

# Collect features, precomputed if they exist, else compute on the fly
if self._check_labels_features_exist(labels_example, attribute):
features = self._extract_labels_precomputed_features(labels_example)
features = self._extract_labels_precomputed_features(
labels_example, attribute
)
else:
features = self._compute_default_label_features(labels_example)

label_data = {}
self._add_to_session_data(label_data, "intent_features", features)
self._add_mask_to_session_data(label_data, "intent_mask", "intent_features")
self._add_to_session_data(label_data, "label_features", features)
self._add_mask_to_session_data(label_data, "label_mask", "label_features")

return label_data

def _use_default_label_features(self, label_ids: np.ndarray) -> List[np.ndarray]:
return [
np.array(
[
self._label_data["intent_features"][0][label_id]
self._label_data["label_features"][0][label_id]
for label_id in label_ids
]
)
Expand All @@ -394,7 +396,7 @@ def _create_session_data(
self,
training_data: List["Message"],
label_id_dict: Optional[Dict[Text, int]] = None,
label_attribute: Optional[Text] = None,
label_attribute: Optional[Text] = INTENT_ATTRIBUTE,
) -> "SessionDataType":
"""Prepare data for training and create a SessionDataType object"""

Expand All @@ -405,20 +407,21 @@ def _create_session_data(
label_ids = []

for e in training_data:
_sparse, _dense = self._extract_and_add_features(e, TEXT_ATTRIBUTE)
if _sparse is not None:
X_sparse.append(_sparse)
if _dense is not None:
X_dense.append(_dense)

_sparse, _dense = self._extract_and_add_features(e, INTENT_ATTRIBUTE)
if _sparse is not None:
Y_sparse.append(_sparse)
if _dense is not None:
Y_dense.append(_dense)

if label_attribute and e.get(label_attribute):
label_ids.append(label_id_dict[e.get(label_attribute)])
if e.get(label_attribute):
_sparse, _dense = self._extract_and_add_features(e, TEXT_ATTRIBUTE)
if _sparse is not None:
X_sparse.append(_sparse)
if _dense is not None:
X_dense.append(_dense)

_sparse, _dense = self._extract_and_add_features(e, label_attribute)
if _sparse is not None:
Y_sparse.append(_sparse)
if _dense is not None:
Y_dense.append(_dense)

if label_id_dict:
label_ids.append(label_id_dict[e.get(label_attribute)])

X_sparse = np.array(X_sparse)
X_dense = np.array(X_dense)
Expand All @@ -428,23 +431,21 @@ def _create_session_data(

session_data = {}
self._add_to_session_data(session_data, "text_features", [X_sparse, X_dense])
self._add_to_session_data(session_data, "intent_features", [Y_sparse, Y_dense])
self._add_to_session_data(session_data, "label_features", [Y_sparse, Y_dense])
# explicitly add last dimension to label_ids
# to track correctly dynamic sequences
self._add_to_session_data(
session_data, "intent_ids", [np.expand_dims(label_ids, -1)]
session_data, "label_ids", [np.expand_dims(label_ids, -1)]
)

if label_attribute and (
"intent_features" not in session_data or not session_data["intent_features"]
if label_id_dict and (
"label_features" not in session_data or not session_data["label_features"]
):
# no label features are present, get default features from _label_data
session_data["intent_features"] = self._use_default_label_features(
label_ids
)
session_data["label_features"] = self._use_default_label_features(label_ids)

self._add_mask_to_session_data(session_data, "text_mask", "text_features")
self._add_mask_to_session_data(session_data, "intent_mask", "intent_features")
self._add_mask_to_session_data(session_data, "label_mask", "label_features")

return session_data

Expand Down Expand Up @@ -547,29 +548,29 @@ def _build_tf_train_graph(
batch_data["text_features"], batch_data["text_mask"][0], "text"
)
b = self._combine_sparse_dense_features(
batch_data["intent_features"], batch_data["intent_mask"][0], "intent"
batch_data["label_features"], batch_data["label_mask"][0], "label"
)
all_bs = self._combine_sparse_dense_features(
label_data["intent_features"], label_data["intent_mask"][0], "intent"
label_data["label_features"], label_data["label_mask"][0], "label"
)

self.message_embed = self._create_tf_embed_fnn(
a,
self.hidden_layer_sizes["text"],
fnn_name="text_intent" if self.share_hidden_layers else "text",
fnn_name="text_label" if self.share_hidden_layers else "text",
embed_name="text",
)
self.label_embed = self._create_tf_embed_fnn(
b,
self.hidden_layer_sizes["intent"],
fnn_name="text_intent" if self.share_hidden_layers else "intent",
embed_name="intent",
self.hidden_layer_sizes["label"],
fnn_name="text_label" if self.share_hidden_layers else "label",
embed_name="label",
)
self.all_labels_embed = self._create_tf_embed_fnn(
all_bs,
self.hidden_layer_sizes["intent"],
fnn_name="text_intent" if self.share_hidden_layers else "intent",
embed_name="intent",
self.hidden_layer_sizes["label"],
fnn_name="text_label" if self.share_hidden_layers else "label",
embed_name="label",
)

return train_utils.calculate_loss_acc(
Expand Down Expand Up @@ -606,15 +607,15 @@ def _build_tf_pred_graph(self, session_data: "SessionDataType") -> "tf.Tensor":
batch_data["text_features"], batch_data["text_mask"][0], "text"
)
b = self._combine_sparse_dense_features(
batch_data["intent_features"], batch_data["intent_mask"][0], "intent"
batch_data["label_features"], batch_data["label_mask"][0], "label"
)

self.all_labels_embed = tf.constant(self.session.run(self.all_labels_embed))

self.message_embed = self._create_tf_embed_fnn(
a,
self.hidden_layer_sizes["text"],
fnn_name="text_intent" if self.share_hidden_layers else "text",
fnn_name="text_label" if self.share_hidden_layers else "text",
embed_name="text",
)

Expand All @@ -626,9 +627,9 @@ def _build_tf_pred_graph(self, session_data: "SessionDataType") -> "tf.Tensor":

self.label_embed = self._create_tf_embed_fnn(
b,
self.hidden_layer_sizes["intent"],
fnn_name="text_intent" if self.share_hidden_layers else "intent",
embed_name="intent",
self.hidden_layer_sizes["label"],
fnn_name="text_label" if self.share_hidden_layers else "label",
embed_name="label",
)

self.sim = train_utils.tf_raw_sim(
Expand All @@ -651,7 +652,7 @@ def check_input_dimension_consistency(self, session_data: "SessionDataType"):
if self.share_hidden_layers:
num_text_features = self._get_num_of_features(session_data, "text_features")
num_intent_features = self._get_num_of_features(
session_data, "intent_features"
session_data, "label_features"
)

if num_text_features != num_intent_features:
Expand Down Expand Up @@ -689,7 +690,7 @@ def preprocess_train_data(self, training_data: "TrainingData"):

@staticmethod
def _check_enough_labels(session_data: "SessionDataType") -> bool:
return len(np.unique(session_data["intent_ids"])) >= 2
return len(np.unique(session_data["label_ids"])) >= 2

def train(
self,
Expand Down Expand Up @@ -721,7 +722,7 @@ def train(
session_data,
self.evaluate_on_num_examples,
self.random_seed,
label_key="intent_ids",
label_key="label_ids",
)
else:
eval_session_data = None
Expand All @@ -743,7 +744,7 @@ def train(
eval_session_data,
batch_size_in,
self.batch_in_strategy,
label_key="intent_ids",
label_key="label_ids",
)

self._is_training = tf.placeholder_with_default(False, shape=())
Expand Down
2 changes: 1 addition & 1 deletion rasa/nlu/classifiers/sklearn_intent_classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import typing
from typing import Any, Dict, List, Optional, Text, Tuple

from rasa.nlu.featurizers.featurzier import sequence_to_sentence_features
from rasa.nlu.featurizers.featurizer import sequence_to_sentence_features
from rasa.nlu import utils
from rasa.nlu.classifiers import LABEL_RANKING_LENGTH
from rasa.nlu.components import Component
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
import warnings
from rasa.nlu.featurizers.featurzier import Featurizer
from rasa.nlu.featurizers.featurizer import Featurizer
from typing import Any, Dict, List, Optional, Text, Tuple
from rasa.nlu.config import RasaNLUModelConfig
from rasa.nlu.training_data import Message, TrainingData
Expand Down
2 changes: 1 addition & 1 deletion rasa/nlu/featurizers/dense_featurizer/mitie_featurizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Any, List, Text, Dict, Optional

from rasa.nlu.config import RasaNLUModelConfig
from rasa.nlu.featurizers.featurzier import Featurizer
from rasa.nlu.featurizers.featurizer import Featurizer
from rasa.nlu.tokenizers.tokenizer import Token
from rasa.nlu.training_data import Message, TrainingData

Expand Down
2 changes: 1 addition & 1 deletion rasa/nlu/featurizers/dense_featurizer/spacy_featurizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Any, Optional, Dict, Text

from rasa.nlu.config import RasaNLUModelConfig
from rasa.nlu.featurizers.featurzier import Featurizer
from rasa.nlu.featurizers.featurizer import Featurizer
from rasa.nlu.training_data import Message, TrainingData

if typing.TYPE_CHECKING:
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from sklearn.feature_extraction.text import CountVectorizer
from rasa.nlu import utils
from rasa.nlu.config import RasaNLUModelConfig
from rasa.nlu.featurizers.featurzier import Featurizer
from rasa.nlu.featurizers.featurizer import Featurizer
from rasa.nlu.model import Metadata
from rasa.nlu.training_data import Message, TrainingData
from rasa.nlu.constants import (
Expand Down
2 changes: 1 addition & 1 deletion rasa/nlu/featurizers/sparse_featurizer/ngram_featurizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from typing import Any, Dict, Optional, Text

from rasa.nlu.featurizers.featurzier import Featurizer
from rasa.nlu.featurizers.featurizer import Featurizer

logger = logging.getLogger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion rasa/nlu/featurizers/sparse_featurizer/regex_featurizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from rasa.nlu import utils
from rasa.nlu.config import RasaNLUModelConfig
from rasa.nlu.featurizers.featurzier import Featurizer
from rasa.nlu.featurizers.featurizer import Featurizer
from rasa.nlu.training_data import Message, TrainingData
import rasa.utils.io
from rasa.nlu.constants import (
Expand Down
4 changes: 2 additions & 2 deletions rasa/nlu/selectors/embedding_response_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class ResponseSelector(EmbeddingIntentClassifier):

provides = ["response", "response_ranking"]

requires = [SPARSE_FEATURE_NAMES[TEXT_ATTRIBUTE]]
requires = []

# default properties (DOC MARKER - don't remove)
defaults = {
Expand All @@ -68,7 +68,7 @@ class ResponseSelector(EmbeddingIntentClassifier):
"random_seed": None,
# embedding parameters
# default dense dimension used if no dense features are present
"dense_dim": 512,
"dense_dim": {"text": 512, "label": 20},
# dimension size of embedding vectors
"embed_dim": 20,
# the type of the similarity
Expand Down
9 changes: 9 additions & 0 deletions sample_configs/config_embedding_intent_response_selector.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
language: "en"

pipeline:
- name: "WhitespaceTokenizer"
- name: "CountVectorsFeaturizer"
- name: "EmbeddingIntentClassifier"
epochs: 2
- name: "ResponseSelector"
epochs: 2
2 changes: 1 addition & 1 deletion tests/nlu/featurizers/test_featurizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import pytest
import scipy.sparse

from rasa.nlu.featurizers.featurzier import Featurizer, sequence_to_sentence_features
from rasa.nlu.featurizers.featurizer import Featurizer, sequence_to_sentence_features
from rasa.nlu.constants import DENSE_FEATURE_NAMES, SPARSE_FEATURE_NAMES, TEXT_ATTRIBUTE
from rasa.nlu.training_data import Message

Expand Down
Empty file added tests/nlu/selectors/__init__.py
Empty file.
25 changes: 25 additions & 0 deletions tests/nlu/selectors/test_embedding_response_selector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from rasa.nlu.training_data import load_data
from rasa.nlu import config
from rasa.nlu.train import Trainer, Interpreter


def test_train_response_selector(component_builder, tmpdir):
td = load_data("data/examples/rasa/demo-rasa.md")
td_responses = load_data("data/examples/rasa/demo-rasa-responses.md")
td = td.merge(td_responses)
td.fill_response_phrases()

nlu_config = config.load(
"sample_configs/config_embedding_intent_response_selector.yml"
)

trainer = Trainer(nlu_config)
interpreter = trainer.train(td)

persisted_path = trainer.persist(tmpdir)

assert trainer.pipeline
loaded = Interpreter.load(persisted_path, component_builder)
assert loaded.pipeline
assert loaded.parse("hello") is not None
assert loaded.parse("Hello today is Monday, again!") is not None