Skip to content

Commit

Permalink
Merge pull request #4985 from RasaHQ/bug-fix-response-selector
Browse files Browse the repository at this point in the history
Fix errors during training in ResponseSelector
  • Loading branch information
tabergma committed Dec 17, 2019
2 parents 6449f7f + b5103ff commit 86cb4f5
Show file tree
Hide file tree
Showing 15 changed files with 96 additions and 60 deletions.
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``.
101 changes: 51 additions & 50 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,12 +137,12 @@ 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 (
self.share_hidden_layers
and self.hidden_layer_sizes["text"] != self.hidden_layer_sizes["intent"]
and self.hidden_layer_sizes["text"] != self.hidden_layer_sizes["label"]
):
raise ValueError(
"If hidden layer weights are shared,"
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)
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

0 comments on commit 86cb4f5

Please sign in to comment.